接收 HTTP 端點的事件
本文說明如何驗證 HTTP 端點以從事件訂閱接收事件,然後接收和還原序列化事件。 本文針對示範用途使用 Azure Function,但是不論應用程式裝載的位置都適用相同概念。
注意
建議您在使用事件方格觸發 Azure 函數時使用事件方格觸發程序。 其提供事件方格與 Azure Functions 之間更簡單且更快速的整合。 不過請注意,Azure Functions 事件方格觸發程序不支援以下情節:裝載的程式碼需要控制傳回事件方格的 HTTP 狀態碼。 例如,基於這項限制,您在 Azure 函數上執行的程式碼將無法傳回 5XX 錯誤,以起始事件方格的事件傳遞重試。
必要條件
您需要具有 HTTP 觸發函式的函數應用程式。
新增相依性
若您正以 .NET 進行開發,請為 Azure.Messaging.EventGrid
Nuget 套件將相依性新增到函數。
其他語言的 SDK 可以透過發行 SDK 參考取得。 這些套件包含原生事件類型的模型,例如 EventGridEvent
、StorageBlobCreatedEventData
和 EventHubCaptureFileCreatedEventData
。
端點驗證
首先您應該做的是處理 Microsoft.EventGrid.SubscriptionValidationEvent
事件。 每次有人訂閱事件時,事件方格即會將驗證事件傳送至在資料承載中具有 validationCode
的端點。 需要端點才能在回應主體中回應以證明端點有效且為您所擁有。 若您是使用事件方格觸發程序而非 WebHook 觸發的函式,系統會為您處理端點的驗證。 如果您使用第三方 API 服務 (例如 Zapier 或 IFTTT),可能就無法以程式設計方式回應驗證程式碼。 針對那些服務,您可以使用要在訂用帳戶驗證事件中傳送的驗證 URL,以手動方式驗證訂用帳戶。 在 validationUrl
屬性中複製該 URL,並透過 REST 用戶端或您的網頁瀏覽器傳送 GET 要求。
在 C# 中,ParseMany()
方法可用來將包含一個或多個事件的 BinaryData
執行個體還原序列化為 EventGridEvent
陣列。 如果您事先知道您要還原序列化的只有單一事件,則可以改用 Parse
方法。
若要以程式設計方式回應驗證程式碼,請使用下列程式碼。
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using System;
using Azure.Messaging.EventGrid;
using Azure.Messaging.EventGrid.SystemEvents;
namespace Function1
{
public static class Function1
{
[FunctionName("Function1")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
string response = string.Empty;
BinaryData events = await BinaryData.FromStreamAsync(req.Body);
log.LogInformation($"Received events: {events}");
EventGridEvent[] eventGridEvents = EventGridEvent.ParseMany(events);
foreach (EventGridEvent eventGridEvent in eventGridEvents)
{
// Handle system events
if (eventGridEvent.TryGetSystemEventData(out object eventData))
{
// Handle the subscription validation event
if (eventData is SubscriptionValidationEventData subscriptionValidationEventData)
{
log.LogInformation($"Got SubscriptionValidation event data, validation code: {subscriptionValidationEventData.ValidationCode}, topic: {eventGridEvent.Topic}");
// Do any additional validation (as required) and then return back the below response
var responseData = new
{
ValidationResponse = subscriptionValidationEventData.ValidationCode
};
return new OkObjectResult(responseData);
}
}
}
return new OkObjectResult(response);
}
}
}
module.exports = function (context, req) {
context.log('JavaScript HTTP trigger function begun');
var validationEventType = "Microsoft.EventGrid.SubscriptionValidationEvent";
for (var events in req.body) {
var body = req.body[events];
// Deserialize the event data into the appropriate type based on event type
if (body.data && body.eventType == validationEventType) {
context.log("Got SubscriptionValidation event data, validation code: " + body.data.validationCode + " topic: " + body.topic);
// Do any additional validation (as required) and then return back the below response
var code = body.data.validationCode;
context.res = { status: 200, body: { "ValidationResponse": code } };
}
}
context.done();
};
測試驗證回應
藉由將範例事件貼至函式的測試欄位來測試驗證回應函式:
[{
"id": "2d1781af-3a4c-4d7c-bd0c-e34b19da4e66",
"topic": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"subject": "",
"data": {
"validationCode": "512d38b6-c7b8-40c8-89fe-f46f9e9622b6"
},
"eventType": "Microsoft.EventGrid.SubscriptionValidationEvent",
"eventTime": "2018-01-25T22:12:19.4556811Z",
"metadataVersion": "1",
"dataVersion": "1"
}]
當您選取 [執行] 時,在主體中輸出應該是「200 OK」和 {"validationResponse":"512d38b6-c7b8-40c8-89fe-f46f9e9622b6"}
:
處理 Blob 儲存體事件
現在,我們要擴充函數以處理 Microsoft.Storage.BlobCreated
系統事件:
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using System;
using Azure.Messaging.EventGrid;
using Azure.Messaging.EventGrid.SystemEvents;
namespace Function1
{
public static class Function1
{
[FunctionName("Function1")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
string response = string.Empty;
BinaryData events = await BinaryData.FromStreamAsync(req.Body);
log.LogInformation($"Received events: {events}");
EventGridEvent[] eventGridEvents = EventGridEvent.ParseMany(events);
foreach (EventGridEvent eventGridEvent in eventGridEvents)
{
// Handle system events
if (eventGridEvent.TryGetSystemEventData(out object eventData))
{
// Handle the subscription validation event
if (eventData is SubscriptionValidationEventData subscriptionValidationEventData)
{
log.LogInformation($"Got SubscriptionValidation event data, validation code: {subscriptionValidationEventData.ValidationCode}, topic: {eventGridEvent.Topic}");
// Do any additional validation (as required) and then return back the below response
var responseData = new
{
ValidationResponse = subscriptionValidationEventData.ValidationCode
};
return new OkObjectResult(responseData);
}
// Handle the storage blob created event
else if (eventData is StorageBlobCreatedEventData storageBlobCreatedEventData)
{
log.LogInformation($"Got BlobCreated event data, blob URI {storageBlobCreatedEventData.Url}");
}
}
}
return new OkObjectResult(response);
}
}
}
module.exports = function (context, req) {
context.log('JavaScript HTTP trigger function begun');
var validationEventType = "Microsoft.EventGrid.SubscriptionValidationEvent";
var storageBlobCreatedEvent = "Microsoft.Storage.BlobCreated";
for (var events in req.body) {
var body = req.body[events];
// Deserialize the event data into the appropriate type based on event type
if (body.data && body.eventType == validationEventType) {
context.log("Got SubscriptionValidation event data, validation code: " + body.data.validationCode + " topic: " + body.topic);
// Do any additional validation (as required) and then return back the below response
var code = body.data.validationCode;
context.res = { status: 200, body: { "ValidationResponse": code } };
}
else if (body.data && body.eventType == storageBlobCreatedEvent) {
var blobCreatedEventData = body.data;
context.log("Relaying received blob created event payload:" + JSON.stringify(blobCreatedEventData));
}
}
context.done();
};
測試 Blob 建立事件處理
測試函式的新功能,方法是將 Blob 儲存體事件放入測試欄位並執行:
[{
"topic": "/subscriptions/{subscription-id}/resourceGroups/Storage/providers/Microsoft.Storage/storageAccounts/xstoretestaccount",
"subject": "/blobServices/default/containers/testcontainer/blobs/testfile.txt",
"eventType": "Microsoft.Storage.BlobCreated",
"eventTime": "2017-06-26T18:41:00.9584103Z",
"id": "831e1650-001e-001b-66ab-eeb76e069631",
"data": {
"api": "PutBlockList",
"clientRequestId": "6d79dbfb-0e37-4fc4-981f-442c9ca65760",
"requestId": "831e1650-001e-001b-66ab-eeb76e000000",
"eTag": "0x8D4BCC2E4835CD0",
"contentType": "text/plain",
"contentLength": 524288,
"blobType": "BlockBlob",
"url": "https://example.blob.core.windows.net/testcontainer/testfile.txt",
"sequencer": "00000000000004420000000000028963",
"storageDiagnostics": {
"batchId": "b68529f3-68cd-4744-baa4-3c0498ec19f0"
}
},
"dataVersion": "",
"metadataVersion": "1"
}]
您應該會在函式記錄中看到 Blob URL 輸出:
2022-11-14T22:40:45.978 [Information] Executing 'Function1' (Reason='This function was programmatically called via the host APIs.', Id=8429137d-9245-438c-8206-f9e85ef5dd61)
2022-11-14T22:40:46.012 [Information] C# HTTP trigger function processed a request.
2022-11-14T22:40:46.017 [Information] Received events: [{"topic": "/subscriptions/{subscription-id}/resourceGroups/Storage/providers/Microsoft.Storage/storageAccounts/xstoretestaccount","subject": "/blobServices/default/containers/testcontainer/blobs/testfile.txt","eventType": "Microsoft.Storage.BlobCreated","eventTime": "2017-06-26T18:41:00.9584103Z","id": "831e1650-001e-001b-66ab-eeb76e069631","data": {"api": "PutBlockList","clientRequestId": "6d79dbfb-0e37-4fc4-981f-442c9ca65760","requestId": "831e1650-001e-001b-66ab-eeb76e000000","eTag": "0x8D4BCC2E4835CD0","contentType": "text/plain","contentLength": 524288,"blobType": "BlockBlob","url": "https://example.blob.core.windows.net/testcontainer/testfile.txt","sequencer": "00000000000004420000000000028963","storageDiagnostics": {"batchId": "b68529f3-68cd-4744-baa4-3c0498ec19f0"}},"dataVersion": "","metadataVersion": "1"}]
2022-11-14T22:40:46.335 [Information] Got BlobCreated event data, blob URI https://example.blob.core.windows.net/testcontainer/testfile.txt
2022-11-14T22:40:46.346 [Information] Executed 'Function1' (Succeeded, Id=8429137d-9245-438c-8206-f9e85ef5dd61, Duration=387ms)
您也可以進行測試,方法是建立 Blob 儲存體帳戶或一般用途 V2 儲存體帳戶、新增事件訂閱,以及將端點設為函數 URL:
處理自訂事件
最後,讓我們再次擴充函式,讓它也可以處理自訂事件。
為您的事件 Contoso.Items.ItemReceived
新增檢查。 最終的程式碼應該會如下所示:
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using System;
using Azure.Messaging.EventGrid;
using Azure.Messaging.EventGrid.SystemEvents;
namespace Function1
{
public static class Function1
{
[FunctionName("Function1")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
string response = string.Empty;
BinaryData events = await BinaryData.FromStreamAsync(req.Body);
log.LogInformation($"Received events: {events}");
EventGridEvent[] eventGridEvents = EventGridEvent.ParseMany(events);
foreach (EventGridEvent eventGridEvent in eventGridEvents)
{
// Handle system events
if (eventGridEvent.TryGetSystemEventData(out object eventData))
{
// Handle the subscription validation event
if (eventData is SubscriptionValidationEventData subscriptionValidationEventData)
{
log.LogInformation($"Got SubscriptionValidation event data, validation code: {subscriptionValidationEventData.ValidationCode}, topic: {eventGridEvent.Topic}");
// Do any additional validation (as required) and then return back the below response
var responseData = new
{
ValidationResponse = subscriptionValidationEventData.ValidationCode
};
return new OkObjectResult(responseData);
}
// Handle the storage blob created event
else if (eventData is StorageBlobCreatedEventData storageBlobCreatedEventData)
{
log.LogInformation($"Got BlobCreated event data, blob URI {storageBlobCreatedEventData.Url}");
}
}
// Handle the custom contoso event
else if (eventGridEvent.EventType == "Contoso.Items.ItemReceived")
{
var contosoEventData = eventGridEvent.Data.ToObjectFromJson<ContosoItemReceivedEventData>();
log.LogInformation($"Got ContosoItemReceived event data, item SKU {contosoEventData.ItemSku}");
}
}
return new OkObjectResult(response);
}
}
}
module.exports = function (context, req) {
context.log('JavaScript HTTP trigger function begun');
var validationEventType = "Microsoft.EventGrid.SubscriptionValidationEvent";
var storageBlobCreatedEvent = "Microsoft.Storage.BlobCreated";
var customEventType = "Contoso.Items.ItemReceived";
for (var events in req.body) {
var body = req.body[events];
// Deserialize the event data into the appropriate type based on event type
if (body.data && body.eventType == validationEventType) {
context.log("Got SubscriptionValidation event data, validation code: " + body.data.validationCode + " topic: " + body.topic);
// Do any additional validation (as required) and then return back the below response
var code = body.data.validationCode;
context.res = { status: 200, body: { "ValidationResponse": code } };
}
else if (body.data && body.eventType == storageBlobCreatedEvent) {
var blobCreatedEventData = body.data;
context.log("Relaying received blob created event payload:" + JSON.stringify(blobCreatedEventData));
}
else if (body.data && body.eventType == customEventType) {
var payload = body.data;
context.log("Relaying received custom payload:" + JSON.stringify(payload));
}
}
context.done();
};
測試自訂事件處理
最後,測試您的函式現在是否可以處理您的自訂事件類型:
[{
"subject": "Contoso/foo/bar/items",
"eventType": "Contoso.Items.ItemReceived",
"eventTime": "2017-08-16T01:57:26.005121Z",
"id": "602a88ef-0001-00e6-1233-1646070610ea",
"data": {
"itemSku": "Standard"
},
"dataVersion": "",
"metadataVersion": "1"
}]
您也可以即時測試此功能,方法是從入口網站傳送自訂事件與 CURL,或使用可張貼到端點的任何服務或應用程式來張貼到自訂主題。 在端點設為函式 URL 時建立自訂主題和事件訂閱。
訊息標頭
以下是您在訊息標頭中收到的屬性:
屬性名稱 | 描述 |
---|---|
aeg-subscription-name | 事件訂閱的名稱。 |
aeg-delivery-count | 對事件進行的嘗試次數。 |
aeg-event-type | 事件的類型。 它可能是下列其中一個值:
|
aeg-metadata-version | 事件的中繼資料版本。 針對事件方格事件結構描述,此屬性代表中繼資料版本;而針對雲端事件結構描述,其代表規格版本。 |
aeg-data-version | 事件的資料版本。 針對事件方格事件結構描述,此屬性代表資料版本;而針對雲端事件結構描述,則不會套用。 |
aeg-output-event-id | 事件方格事件的識別碼。 |
下一步
- 探索 Azure 事件方格管理和發行 SDK
- 深入了解如何張貼到自訂主題