65.9K
CodeProject 正在变化。 阅读更多。
Home

Azure Event Grid 测试器

starIconstarIconstarIconstarIconstarIcon

5.00/5 (5投票s)

2018年7月30日

CPOL

26分钟阅读

viewsIcon

27011

downloadIcon

573

为在本地机器上探索 Azure Event Grid 模型设计和实现一个小工具。

注意: 文章已更新 - 请参阅 附录 A - 版本 1.1

目录

特点

  • 探索 Azure 事件网格
  • 使用混合连接和/或 ngrok 隧道通道克隆任何事件订阅以用于本地主机测试器
  • 探索本地主机上的事件消息
  • 探索和管理任何事件订阅
  • 触发自定义主题
  • 支持事件域、高级筛选等
  • 无需 SDK
  • 多租户使用(通过多个 Azure 订阅打开多个实例)
  • Azure 事件网格 REST API 请求(模板)
  • 支持授权 Bearer 令牌的 REST 管理 API
  • api-version=2018-09-15-preview

引言

Azure 事件网格代表一个事件发布/订阅模型,其中包含 Azure 资源中事件兴趣的预构建发布者。下图显示了该模型以及事件源、主题、事件订阅和事件处理程序等基本实体

基本上,有具有事件兴趣的事件源,而在消费者端,有接收这些兴趣的订阅者。每个订阅者都需要使用事件订阅订阅事件源以获取其特定的事件兴趣。根据订阅,事件网格服务知道事件发生时要传递什么、到哪里以及如何传递事件兴趣。上图显示了当前版本,例如 2018-05-01-preview

Azure 事件网格是松散耦合(推送)事件模型。它是每个区域的 PaaS“独立服务”,作为无服务器云架构的一部分。它的能力是每秒向目标端点(事件接收器)传递数百万个事件。

下图可以简化这个发布/订阅模型,其中 Publishers 在左侧用于发出事件,而 Subscribers 在另一侧用于消费由源事件兴趣表示的事件消息,例如:blob 已在 Azure 存储中创建/删除。

从逻辑模型的角度来看,Topic 表示输入端点,Subscription 点表示此路由模型的输出点。下图显示了这一点

根据上述描述,我们可以说,每个订阅都代表事件模型中事件兴趣(主题)和消费者(订阅者)之间的逻辑连接。Azure 事件网格对事件模型的限制如下面的屏幕截图所示

现在,如果您知道主题和订阅在 Azure 事件网格模型中的位置,让我们描述它们的连接模式。

基本上,有两种模式,即 Fan-In Fan-Out

下图显示了 Pattern Fan-In,其中多个主题聚合到一个事件接收器(订阅者)中。请注意,Azure 事件网格模型仅支持主题和订阅之间的单个连接模型,换句话说,订阅只能有一个输入(主题)和一个输出(事件处理程序)端点。

上述扇入模式使用多个主题,但使用相同的事件接收器(事件处理程序)。

第二种模式是 Pattern Fan-Out,其中单个主题分发到多个事件接收器。换句话说,订阅中的输入主题相同,但每个目标端点(事件处理程序)的输出端点不同

请注意,上述扇出模式用于此测试工具的概念,其中具有“本地主机事件处理程序”的测试工具订阅被添加到事件源主题的探索中。

下图显示了一个 Azure 事件网格测试工具,它允许从本地计算机探索事件消息、订阅、触发自定义主题等。

太棒了。让我们描述一下它的概念和设计。我假设您对 Azure 事件网格有一些了解。

概念与设计

Azure 事件网格测试工具的概念基于为事件处理程序中的“本地主机目标”克隆事件订阅。换句话说,对于每个感兴趣的主题(订阅),测试工具将为其事件消息消费创建新的或克隆的订阅。

基本上,有两种方法可以在本地环境中从 Azure 事件网格接收事件消息,例如使用隧道通道或 Azure 混合连接。Azure 事件网格测试工具内置支持通过这两种方式订阅事件订阅和这些事件处理程序。请注意,Azure 混合连接在撰写本文时仍处于预览版,并且最近才添加。

下图显示了使用名为 ngrok 的免费公共隧道工具的场景。

如您所见,Azure 与本地计算机上的测试工具之间是 ngrok 隧道,用于通过 NAT 或防火墙在 HTTP 端口(80、443)上处理安全连接。对于此隧道,需要在本地计算机上安装 ngrok 代理,以便将请求转发到您的目标应用程序。ngrok 隧道可以通过公共互联网地址访问,该地址在隧道创建时由 ngrok 为您生成。

此 ngrok 地址,例如: https://e88ab6b4.ngrok.io/AzureEventGridTester,用于订阅测试工具处的 WebHook 端点处理程序。

将本地测试工具订阅到 Azure 事件网格主题的第二种方法是使用 Azure 中继混合连接。测试工具具有一个选定混合连接的内置侦听器,因此通过此事件处理程序,我们可以看到来自 Azure 事件源的任何事件消息。

ngrok 和混合连接与测试工具功能完全相同。请注意,混合连接是 可计费的。

正如我之前提到的,有两种主题-订阅模式。从测试工具的角度来看,这种模式是 Fan-In,其中所有事件兴趣都指向测试工具订阅者。

对于现有事件订阅,我们可以使用扇出模式和克隆的订阅测试工具。

最近,预览版在事件订阅中添加了一项新功能,例如 deadLetterDestination 属性。目前,我们只能选择一个 endpointType,例如 BlobStorage。如果事件消息在传递到事件订阅者时失败,该消息将作为 deadLetter 消息(blob)传递到指定的容器中。请注意,此 deadLetter 容器可以是事件驱动的,因此测试工具也可以看到此事件。

请注意,每个主题的订阅限制为每个区域 500 个,这对于我们的测试工具来说并不关键,此外,用完后可以轻松删除这些测试工具订阅。

作为概念的一部分,这就是我们管理 Azure 事件网格资源的方式。这部分解决方案是使用 REST 管理 API 完成的,请参阅以下内容

事件网格 REST API

Azure 事件网格测试工具通过 REST 管理 API 调用与 Azure 通信。以下屏幕截图显示了调用 REST API 以获取为特定范围(Azure SubscriptionId)在 Azure 事件网格中创建的自定义主题列表的示例。

Management API 的每个 REST 调用都需要授权 Bearer 令牌标头。此 Bearer 令牌取自 Azure Active Directory,稍后将详细介绍。一旦我们拥有有效的 Bearer 令牌,测试工具就可以管理 Azure 事件网格资源,例如查询提供程序、主题、订阅等。作为测试工具的一部分,treeView 中有一个通用的 REST-API 节点,用于从位于 *binaries* 文件夹中的模板 json 文件加载 REST 客户端请求。请注意,此模板 json 文件的名称是 Azure 订阅的名称。您可以根据需要自定义此模板 json 文件,此文件的样本包含在包中 (*rk20180618.json*)。

测试工具设计围绕 ResourceGroups 节点,并可向下钻取到请求的资源。选择的末尾是 Microsoft.EventGrid 提供程序(蓝色节点),其中存储了事件订阅。测试工具订阅者收到的事件消息将显示在所选提供程序下方,如下面的屏幕截图所示

上面的示例显示,测试工具订阅者收到了由资源组 (TESTER-74902843) 中的更改传递的事件消息,以及来自自定义主题 rk20180818topic1 (TESTER-49433140) 的六条事件消息。通过单击特定消息节点,我们可以看到消息内容,如下面的屏幕截图所示

蓝色节点(例如 Microsoft.EventGrid 提供程序)具有以下上下文菜单。在此节点中,我们可以管理事件订阅

好的,是时候展示了。让我们描述一下这个小工具能为您做什么。我假设您之前接触过 Azure 事件网格。

用法

在开始使用 Azure 事件网格测试工具之前,我假设您已经阅读了 Azure 事件网格 MSDN 文章,并且您有一个活跃的 Azure 帐户。

在您可以使用此测试工具之前,必须将其注册到 Azure Active Directory (AAD) 中。如果您没有注册,该工具将无法建立与您的 Azure 帐户的连接。

在您的 Azure Directory 中注册 EventGridTester(仅首次)

如果您没有默认的 AAD 目录,请创建一个。然后添加(注册)一个新应用程序,如下图所示

填写以下属性

  • 名称: EventGridTester
  • 应用程序类型: Native
  • 重定向 URI: https://login.live.com/oauth20_desktop.srf

按下 Create 按钮后,我们可以获得测试工具的 ApplicationID,将其保存以备后用

现在,我们必须添加 API 访问,因此选择 Setting 和以下 API,例如 Windows Azure Service Management API

在上述 Select 和单击 Done(下一页)后,此步骤完成。我们的测试工具已在 AAD 中注册并具有管理 API 访问权限,因此我们可以获取用于授权调用的 Bearer 令牌。

向 AAD 添加更多用户,测试工具可以与每个用户的凭据单独使用。

步骤 1. 启动 Azure 事件网格测试工具

在此步骤中,当测试工具启动时,会显示以下提示对话框。这是一个警告对话框,提示 ngrok 隧道不存在,因此按 OK 按钮继续。

之后,以下表单应该会显示在您的屏幕上

正如我之前提到的,有两种方法可以订阅测试工具,例如使用 ngrok 隧道或混合连接。如果您的 Azure 帐户已经有混合连接或您打算创建一个,则可以跳过下一步,继续执行步骤 3。

步骤 2. 创建 ngrok 隧道

如果您决定使用 ngrok 隧道连接 Azure 和此测试工具,则每次本地计算机断电或 ngrok 代理关闭时都必须完成以下步骤。

首先,下载 ngrok for windows 并将其解压到文件夹中,例如:c:\\util

Administrator 身份启动 ngrok.exe 应用程序,如下面的屏幕截图所示

2a. 使用命令行启动 ngrok

File 菜单中,选择 Ngrok Tunnel 并单击 Get cmd line,如下面的屏幕截图所示

您的剪贴板包含 ngrok 命令行,将其粘贴到控制台程序中,如下图所示

运行此 cmd 行,结果如下

此时,互联网和您的本地机器之间已创建 ngrok 隧道。上述屏幕显示了 ngrok 隧道的公共互联网地址以及请求转发的位置。

2b. 获取隧道地址

在此步骤中,我们希望将 ngrok 隧道地址获取到测试工具中,因此单击 Get tunnel address,如下图所示

执行此操作后,隧道主机名将更新为实际地址,例如在此示例中为 206e8394.nqrok.io。

ngrok 隧道就这些了。

请注意,当测试工具重新打开且 ngrok 代理仍在运行时,测试工具将自动获取此 ngrok 隧道地址,因此无需执行步骤 2。

步骤 3. 打开 Azure 混合连接

要执行此步骤,需要提前准备 Azure 混合连接。假设我们的 Azure 帐户有一个,因此我们可以继续使用以下屏幕打开它

单击 Open 菜单,将显示以下对话框

从 Azure 门户复制并粘贴混合连接的 NameConnectionString,然后选择行并按 OK 按钮。

此时,此指定混合连接的侦听器已创建并打开。结果记录在日志面板中,上下文菜单已更改为 Close 操作,如下面的屏幕截图所示

如您所见,打开混合连接的步骤与 ngrok 隧道通道相比非常简单直接。

要继续,我们需要再执行一步,然后 Azure 事件网格工具就可以使用了。我们需要登录到我们的 Azure 帐户。好的,我们来做吧。

步骤 4. 登录到您的 Azure 帐户

注意:在此步骤之前,并且仅在首次登录您的 Azure 帐户时,请转到测试工具程序集的二进制文件夹,并将模板 (rk20180618) json 文件重命名为 Azure 订阅的名称。

转到 File 菜单并选择 Login to Azure Event Grid

实际上,这是测试工具登录到您的 AAD,允许管理事件网格资源等。

以下对话框将显示在您的屏幕上

添加新行并填写值

  • Name:Azure 订阅的名称
  • Id:Azure 订阅 ID (GUID)
  • ApplicationKey:AAD 中注册的应用程序 ID

选择行并按 OK 按钮,系统会要求您输入在 AAD 中注册的凭据用户名/密码

一旦您的凭据被接受(60 分钟内),测试工具将显示在 AzureEventGrid treeView 节点中,请参阅下一步。

步骤 5. Azure 事件网格测试工具已准备就绪

登录到您的 Azure 帐户后,测试工具即可使用。Azure 帐户(您的 Azure 订阅)表示 AzureEventGrid 根节点的子节点。此 treeView 中的每个 Azure 帐户都有三个子节点,如下图所示

有关这些节点的更多详细信息

  • REST-API - 此节点用于 REST API 调用的通用用法。它从位于 *binaries* 文件夹中的模板 json 文件加载。我在本次下载中包含了使用管理 API 的 Azure 事件网格的模板 REST API 调用。下图显示了此默认模板的示例。基本上,此节点允许将测试工具扩展到任何 REST API 调用,例如:创建存储帐户等。
  • TesterEventSubscriptions - 此节点开始搜索所有克隆的测试工具订阅者,并在您的资源组中带有 ngrok WebHook 端点。请注意,此过程需要一些时间才能完成所有搜索组。
  • ResourceGroups - 这是您的测试工具使用的主要入口。通过此节点,您可以向下钻取到特定资源和提供程序,例如 Microsoft.EventGrid。换句话说,ResourceGroup 是获取 Event SubscriptionCustom Topic 的根实体。

如果您将模板 json 文件重命名为您的 Azure 订阅的名称(在此示例中,名称为 rk20180618),那么 REST-API 节点如下所示

步骤 6. 示例:选择自定义主题资源

此示例演示了自定义主题的模拟和事件消息的接收。因此,选择自定义主题所在的资源组,如下面的屏幕截图所示

接下来,找到您的资源自定义主题(在此示例中,名称为 rk20180618topic1),然后单击之前操作中选定资源组的上下文菜单上的 Select Eventing Resource

现在,我们有一个 Microsoft.EventGrid 提供程序,我们可以在其中看到所有事件订阅。由于这是向下钻取过程的末尾,因此节点呈蓝色。此外,在此资源中,自定义主题将自动显示一个 FireTopic 节点(绿色),如下面的屏幕截图所示

上图显示了此主题的所有已订阅事件订阅的详细信息。详细信息以 datagrid 形式显示,对于选定的行,我们可以在 json 格式文本中查看事件订阅属性。

选择 Fire Topic 节点,我们可以看到一个带有用于触发自定义主题的示例负载的 REST 客户端,因此在此 REST 客户端中按下 SEND 按钮

注意:每次按下 Send 按钮时生成 guid 和/或日期时间属性,请使用以下替换

[
  {
    "id": "$Guid.NewGuid",
    "mySubject": "/myapp/vehicles/motorcycles",
    "myEventType": "recordInserted",
    "eventTime": "$DateTime.UtcNow",
    "data": {
      "make": "Ducati",
      "model": "Monster"
    }
  }
]

这里发生了什么?REST 调用向自定义主题(见 URL 地址)端点发送了 POST 请求。此发布者端点将发出事件消息,供事件网格根据其订阅将事件消息传递给此主题上的所有订阅者。以下屏幕截图显示了一个新节点,例如 Events,其中包含与此主题相关的所有消息

太棒了。我们可以看到发布者推送并由测试工具订阅者在此主题上接收到的完整事件。

步骤 7. 事件订阅

在此步骤中,我将演示在选定主题(Microsoft.EventGrid 提供程序)- 蓝色节点上创建/克隆/编辑/删除订阅。

以下屏幕截图显示了此节点的上下文菜单和事件订阅的选定行

基本上,这里有以下事件订阅选项

为测试工具创建新的事件订阅

为测试工具创建所选事件订阅的克隆

编辑所选事件订阅

如您所见,上述事件订阅对话框相同,具有预定义属性和一些只读属性。订阅者类型具有 WebHook 和混合连接的特殊功能,允许选择 ngrok 隧道地址或打开混合连接。

REST API 调用管理 API 的响应将记录在日志面板中。

此外,上下文菜单还有更多操作,例如

  • 删除订阅:此操作显示在 ngrok 或测试工具的混合连接处,换句话说,您只能删除与此测试工具相关的事件订阅。
  • 获取 SubscriberFullUrl:此操作显示在 WebHook 事件处理程序处,以查看包含查询字符串的完整端点 URL。
  • 删除:此操作将从 treeView 中删除节点。
  • 刷新:此操作将刷新 Azure 中的选定内容。请注意,当节点被选中时也会发生此刷新。

示例

此示例演示了具有重试策略和死信功能的自定义主题事件的传递。为此示例,我们需要以下内容

  • 带有发布者模拟器 (FireTopic) 的自定义主题
  • 具有重试策略和死信的自定义主题上的事件订阅。此订阅者将始终失败(代码 503)
  • 存储死信消息的存储帐户的事件订阅

让我们满足上述要求。

以下屏幕截图显示了我们在 Azure 事件网格测试工具上的需求

单击自定义主题事件节点,我们可以选择始终以代码 = 503 错误进行测试,因此如果测试工具订阅者收到事件,响应将为 HttpStatus.ServiceUnavailable (503)

现在,自定义主题的事件订阅者如下

如您所见,maxDeliveryAttempts = 3,因此我们期望传递 3 次事件消息,之后,消息将作为死信消息存储到死信容器中。

规范存储事件订阅

请注意,两个事件处理程序都是混合连接,因此我们必须在测试工具中打开它们才能接收事件。

现在,单击 Fire Topic 节点,我们可以模拟自定义主题并查看重试策略的工作原理。在所有重试交付之后,您应该会看到以下内容

如上图所示,有 3 次事件消息发送到测试工具订阅者(#0 0 秒,#1 ~8 秒,#2 ~38 秒)。死信已在大约 5 分钟后发送。我认为,这个时间框架不必太长,最后一次失败交付后 5-10 秒就足够了。我将询问 Microsoft 团队,为什么我们必须等待 5 分钟才能进行死信。

好的,最后下面的屏幕截图是死信消息的图片

请注意,关联 Id(例如由自定义主题生成的 Id)是消息负载的一部分。我认为,它也应该在 blob 元数据和/或 blob 路径名中。

无论如何,这个例子就这些了。

REST-API

REST-API 节点允许向 URL 地址发送 http(s) 请求。请求头可以像插入带有冒号分隔符的名称/值对一样简单。例如,content-type:application/json。每行仅表示一个头。

请求中有一个特殊的头,例如 Authorization 头。如果此头不存在,运行时客户端代理将从 Azure Active Directory 请求 Bearer 令牌。

使用此 REST-API 节点非常简单,就像其他 REST 工具一样,例如设置 URL、头、方法并单击 Send 按钮。请求将返回响应状态和负载。

正如我之前提到的,Azure 管理 API 用于处理 Azure 事件网格资源,因此 Azure 事件网格测试工具允许提供这些 REST API 调用,并提供最少所需的设置。

根据 MSDN 文档 事件网格 REST API,每个请求都由简单的 json 对象描述,该对象将用于在测试工具中创建其树节点。每个请求定义代表 json 数组中的一个项。请注意,json 文件的名称(存储数组的位置)必须是 Azure 订阅的名称,例如 rk20180618,带有扩展名 json,否则测试工具将找不到它。

此请求模板的结构如下面的代码片段所示

[
  {
    "category": "CustomTopic/EventPublisher",
    "name": "RegenerateKey",
    "method": "POST",
    "url": "/resourceGroups/rk2018-tester/providers/
    Microsoft.EventGrid/topics/testerTopic/regenerateKey?api-version=2018-05-01-preview",
    "headers": "content-type:application/json | myHeader:abcd",
    "payload": {
      "keyName": "key2"
    },
    "description": null
  },
  { 
    ...
  }
]

如您在上面的代码中看到的,标头分隔符是字符管道 (|),URL 地址只能有路径和查询,协议和域将在运行时由测试工具添加,例如

https://management.azure.com/subscriptions/{subscriptionId}

如果 URL 地址包含完整地址(协议、域等),测试工具将无修改地接受它。

注意文章下载包含名为 rk20180618.json 的 REST-API 模板示例,因此请根据您的 Azure 订阅名称重命名它。当测试工具用于多个 Azure 帐户时,每个 Azure 订阅都将拥有自己的模板 json 文件,该文件位于 binaries 文件夹中。

示例

此示例演示如何为 REST-API 节点创建模板,该节点在资源组 rk20180618resgroup 中创建一个新的存储帐户 rk2018stg。以下项可以添加到 json 文件中的 REST API 调用数组中

{
  "category": "Misc",
  "name": "CreateStorageAccount",
  "method": "PUT",
  "url": "/resourceGroups/rk20180618resgroup/providers/
  Microsoft.Storage/storageAccounts/rk2018stg?api-version=2017-10-01",
  "headers": null,
  "payload": {
    "sku": {
      "name": "Standard_LRS",
      "tier": "Standard"
      },
    "kind": "StorageV2",
    "location": "westus",
    "tags": {
      },
    "properties": {
      "accessTier": "Cool"
     }
  },
  "description": null
}

要获取存储密钥,我们可以创建 REST-API 模板,如下面的代码片段所示

{
  "category": "Misc",
  "name": "ListOfKeysForStorageAccount",
  "method": "POST",
  "url": "/resourceGroups/rk20180618resgroup/providers/Microsoft.Storage/
  storageAccounts/rk2018stg/listKeys?api-version=2017-10-01",
  "headers": null,
  "payload": {
  },
  "description": null
}

在上面的高级示例中,已演示如何通过使用预定义的 Http 模板调用此测试工具来轻松扩展此测试工具。

最后,下面的屏幕截图显示了 REST-API 节点下 Misc 类别中的上述模板。

实现

首先,以下是先决条件

  • Visual Studio 2017 版本 15.7.5 及更高版本
  • Microsoft Azure 帐户
  • Azure Relay 混合连接
  • Ngrok
  • 互联网连接
  • 下载本文的软件包(源代码和/或可执行文件)

我将描述一些方法、对 Azure 事件网格测试工具的概念和设计至关重要的片段。正如我之前提到的,测试工具通过 REST API 与 Azure 管理 API 通信。对管理 API 的每个 REST 请求都必须使用 Bearer 令牌进行身份验证。

从 Azure AD 获取此 Bearer 令牌的实现方式如下面的代码片段所示,该代码片段位于 *Form1.cs* 源文件中

private TokenInfo AccessTokenToARM(string clientID, string subscriptionId)
{
    string redirectUri = "https://login.live.com/oauth20_desktop.srf";
    authContext = new AuthenticationContext
    ("https://login.windows.net/common/oauth2/authorize", TokenCache.DefaultShared);
    authContext.ExtendedLifeTimeEnabled = true;
    var ar = authContext.AcquireTokenAsync("https://management.azure.com/", 
    clientID, new Uri(redirectUri), 
    new PlatformParameters(PromptBehavior.SelectAccount)).Result;
    return new TokenInfo() { Token = ar.AccessToken, ExpiresOn = ar.ExpiresOn, 
    ApplicationKey = clientID, SubscriptionId = subscriptionId };
}

上述结果存储在 REST-API 节点中作为标签对象 TokenInfo

[Serializable]
[DataContract(Namespace = "urn:rkiss.eventgrid/tester/2018/04")]
public class TokenInfo
{
    [DataMember]
    public string Token { get; set; }
    [DataMember]
    public DateTimeOffset ExpiresOn { get; set; }
    [DataMember]
    public string ApplicationKey { get; set; }
    [DataMember]
    public string SubscriptionId { get; set; }
}

每当 REST 客户端(在任何 treeview 节点)要调用 Azure 管理 API 时,都会执行以下方法

private TokenInfo AccessTokenToARM(TreeNode node, bool regenerate = false)
{
    TokenInfo tokenInfo = null;

    var node1 = this.GetRestApiNode(node);
    if (node1 != null && node1.Tag != null && node1.Tag is TokenInfo)
    {
        tokenInfo = node1.Tag as TokenInfo;
        if (regenerate || tokenInfo.ExpiresOn < DateTimeOffset.UtcNow - 
            TimeSpan.FromMinutes(1))
        {
            tokenInfo = AccessTokenToARM(tokenInfo.ApplicationKey, 
                        tokenInfo.SubscriptionId);
            node1.Tag = tokenInfo;
        }
    }
    return tokenInfo;
}

如上面的代码片段所示,Bearer 令牌是从 TokenCache 获取的,或者在请求用户凭据时再次检索。

另一个有趣的实现是混合连接的侦听器。当从对话框中选择混合连接时,将执行以下任务

ThreadPool.QueueUserWorkItem(delegate (object state)
{
    try
    {
        this.InvokeEx(() => this.openToolStripMenuItem.Enabled = false);

        var listener = new HybridConnectionListener
                       (selectedHybridConnectionInfo.ConnectionString);
        listener.Connecting += (o, hce) =>
        {
            this.InvokeEx(() => this.richTextBoxLog.AppendText
            ($"[{DateTime.Now.ToLocalTime().ToString("yyyy-MM-ddTHH:MM:ss.fff")}] 
            HybridConnection: Connecting, 
            listener:{listener.Address}\r\n", Color.Black));
        };
        listener.Online += (o, hce) =>
        {
            this.InvokeEx(() =>
            {
                this.hybridConnectionToolStripMenuItem.Tag = listener.Address;
                this.hybridConnectionToolStripMenuItem.ToolTipText = 
                                                  listener.Address.ToString();
                this.richTextBoxLog.AppendText($"[{DateTime.Now.ToLocalTime().ToString
                ("yyyy-MM-ddTHH:MM:ss.fff")}] 
                HybridConnection: Online, listener = 
                                          {listener.Address}\r\n", Color.Green);
                this.richTextBoxLog.AppendText($"  {sastoken}\r\n", Color.Gray);
                this.openToolStripMenuItem.Visible = false;
                this.closeToolStripMenuItem.Enabled = true;
                this.closeToolStripMenuItem.Visible = true;
            });
        };
        listener.Offline += (o, hce) =>
        {
            this.InvokeEx(() =>
            {
                this.hybridConnectionToolStripMenuItem.ToolTipText = "";
                this.hybridConnectionToolStripMenuItem.Tag = null;
                this.richTextBoxLog.AppendText($"[{DateTime.Now.ToLocalTime().ToString
                ("yyyy-MM-ddTHH:MM:ss.fff")}] HybridConnection: Offline, 
                listener = {listener.Address}\r\n", Color.Red);
                this.openToolStripMenuItem.Visible = true;
                this.closeToolStripMenuItem.Enabled = false;
            });
        };

        listener.RequestHandler = (context) =>
        {
            try
            {
                if (!context.Request.Headers.AllKeys.Contains("Aeg-Event-Type", 
                StringComparer.OrdinalIgnoreCase) || 
                !string.Equals(context.Request.Headers["Aeg-Event-Type"], 
                "Notification", StringComparison.CurrentCultureIgnoreCase))
                    throw new Exception
                    ("Received message is not for EventGrid subscriber");

                string jsontext = null;
                using (var reader = new StreamReader(context.Request.InputStream))
                {
                    var jtoken = JToken.Parse(reader.ReadToEnd());
                    if (jtoken is JArray)
                        jsontext = jtoken.SingleOrDefault<jtoken>().ToString
                        (Newtonsoft.Json.Formatting.Indented);
                    else if (jtoken is JObject)
                        jsontext = jtoken.ToString
                        (Newtonsoft.Json.Formatting.Indented);
                }

                this.InvokeEx(() => this.AddMessageToTreview
                (JsonConvert.DeserializeObject<eventgridevent>(jsontext), 
                context.Request.Headers, jsontext));
            }
            catch (Exception ex)
            {
                this.InvokeEx(() => this.richTextBoxLog.AppendText
                ($"[{DateTime.Now.ToLocalTime().ToString("yyyy-MM-ddTHH:MM:ss.fff")}] 
                HybridConnection: Message processing failed - 
                                  {ex.InnerMessage()}\r\n", Color.Red));
            }
            finally
            {
                context.Response.StatusCode = HttpStatusCode.NoContent;
                context.Response.Close();
            }
        };

        this.mre.Reset();
        listener.OpenAsync();
        this.mre.WaitOne();
        listener.CloseAsync();
    }
    catch (Exception ex)
    {
        this.InvokeEx(() => this.richTextBoxLog.AppendText
        ($"[{DateTime.Now.ToLocalTime().ToString("yyyy-MM-ddTHH:MM:ss.fff")}] 
        Open HybridConnection failed - {ex.InnerMessage()}\r\n", Color.Red));
    }
    finally
    {
        this.InvokeEx(() => this.hybridConnectionToolStripMenuItem.Tag = null);
        this.InvokeEx(() => this.openToolStripMenuItem.Enabled = true);
        this.InvokeEx(() => this.openToolStripMenuItem.Visible = true);
        this.InvokeEx(() => this.closeToolStripMenuItem.Visible = false);
    }
});

如上面的代码所示,后台任务将根据混合连接的连接字符串创建一个混合连接的侦听器对象。然后有三个处理程序用于在线/离线以及接收请求的处理程序。此处理程序处理来自 Azure 事件网格的传入事件消息。侦听器将一直打开,直到从 ManualResetEvent 对象收到信号,例如上下文菜单上的 Close 项或关闭/退出测试工具。

这就是实现的所有内容。

结论

本文为您提供了一个小巧的 Azure 事件网格测试工具。它可以在您评估和探索 Azure 命名空间中的事件驱动资源时提供帮助。这个小工具是我的系列工具中的下一个,例如 Azure Service Bus TesterAzure IoT Hub Tester。我希望您会觉得它有用。

附录 A - 版本 1.1

本附录描述了 Azure 事件网格测试工具版本 1.1.0.0 中实现的所有新的 Azure 事件网格版本 2018-09-15-preview 功能。

A1. 为此版本创建新文件夹

我建议将其下载到单独的文件夹中,然后手动复制/粘贴您之前版本的所有配置(例如 AzureSubscriptionsDialogAzureHybridConnectionsDialog)。此外,您的订阅 REST-API json 文件必须复制到此新文件夹中,并根据示例文件 *rk20180618.json* 进行更新,如果您想使用与版本 2018-09-15-preview 相关的新模板。请注意,自定义 REST-API 模板节点的此更新过程是手动的,并且需要为测试工具的每个新版本重新处理。

好的,让我们描述一下这个版本有什么新内容。

A2. 事件域

事件域是此更新版本 2018-09-15-preview 的一个重大新功能,用于管理事件 域主题 的流。之前的版本允许以一对一的方式处理每个 自定义主题 作为事件发布者端点,其中订阅者以紧密耦合的方式订阅现有自定义主题。

在事件域模型中,我们有不同的模式,例如一对多。一个事件域端点可以有多个动态主题,请参阅文档 了解用于管理事件网格主题的事件域 中的更多详细信息。

下图取自该文档,显示了事件域的模型

如您所见,上述事件域端点是事件发布者的入口点,用于根据事件消息中的主题属性在事件域内分发事件消息。此入口点的负载是事件消息的数组。如果域主题不存在,事件消息将路由到事件域根(无主题),其中可以订阅域范围订阅。请注意,上图未显示域范围订阅。

域主题是在其首次订阅期间创建的,这是与自定义主题的主要区别。事件域具有内置功能,可以在主题不匹配时将事件消息路由到事件域路由。此事件域发布/订阅模型允许以松散耦合的方式订阅,并将事件消息动态转发到特定主题,而不是事件域路由。换句话说,事件域根订阅者可以轻松找出所有现有域主题。

正如我所提到的,域主题是在其首次订阅期间创建的。此外,当最后一个订阅被删除时,域主题会自动删除。感谢事件域发布/订阅模型的这一内置功能,它看起来非常有用。

根据事件域功能,测试工具 UI 树节点已扩展,如下面的屏幕截图所示

如您所见,上面的事件域 (myDomain) 有一个用于接收根事件消息的节点和一个特殊资源节点 Topics。我们可以从右侧的数据网格中选择一个域主题,并将其添加到树节点中进行探索。

A3. 创建域主题

使用此测试工具创建域主题需要先拥有一个事件域(例如 myDomain)资源(端点)。在此节点上选择 New Subscription,将显示以下对话框

如您所见,上面的对话框中有一个 domainTopic 文本框。如果此文本框为空,则将为 myDomain 范围创建订阅,否则,如果此订阅是此主题的第一个订阅,则将创建域主题。

注意:使用测试工具中带有模板 CreateOrUpdateDomainREST-API EventDomain/EventPublisher 节点,我们可以在资源组中创建/更新任何事件域资源。

A4. 事件订阅生存时间 (TTL)

此新功能允许为订阅定义生存时间持续时间。过期时间将自动删除订阅。如果此订阅用于域主题且是最后一个订阅,则域主题也将从事件域范围中删除。请注意,expirationTimeUtc 属性是订阅的可更新属性,因此可以使用 Edit Subscription 对话框进行更新/删除。

A5. 高级筛选

advancedFilters 属性是一个过滤器数组,允许过滤信封属性以及数据负载的第一层。以下屏幕截图显示了订阅对话框的此属性。每个过滤器的语法格式与 Azure CLI 2.0 编程相同。过滤器分隔符使用字符“|”,如图片中突出显示,值数组中的分隔符使用字符空格“ ”。过滤器的验证在工具提示文本框中输入时进行。

在高级筛选中,您指定

  • key - 用于筛选的事件数据中的字段。它可以是数字、布尔值或字符串
  • operator type - 比较类型
  • valuevalues - 要与键进行比较的值或值列表

根据上述描述,advancedFilters 格式为

key operatorType value/values [ | other filter ...]

支持以下 operatorTypes、键、值

操作符类型

数字可用运算符有

  • NumberGreaterThan
  • NumberGreaterThanOrEquals
  • NumberLessThan
  • NumberLessThanOrEquals
  • NumberIn
  • NumberNotIn

布尔值的可用运算符是

  • BoolEquals

字符串的可用运算符是

  • StringContains
  • StringBeginsWith
  • StringEndsWith
  • StringIn
  • StringNotIn

所有 string 比较都不区分大小写。

对于 事件网格架构 中的事件,将以下值用于键

  • ID
  • 主题
  • 主题
  • EventType
  • DataVersion
  • 事件 数据 (如 Data.key1)

对于 云事件架构 中的事件,将以下值用于键

  • EventId
  • EventType
  • EventTypeVersion
  • 事件 数据 (如 Data.key1)

对于自定义输入架构,使用事件数据字段 (如 Data.key1)。

值可以是

  • number
  • 字符串
  • 布尔值
  • 数组

有关事件网格订阅高级筛选的更多详细信息,请参见此处

A6. DeadLetterDestination

此功能已内置在测试工具版本 1.0 中,在此我想指出,在与 Microsoft 事件网格团队 讨论 后,deadLetterDestination 属性不能像属性标签一样使用 REST PATCH 调用从事件订阅中删除。换句话说,一旦填充了 deadLetterDestination 属性(启用了 deadLettering),我们就无法将其关闭。启用的 deadLettering 功能只能修改。对于此问题,我们必须进行 克隆订阅,并在 deadLetterDestination groupbox 中选择 EndpointType

参考文献

历史

  • 2018年7月30日:初始版本
© . All rights reserved.