HTTP エンドポイントへのイベントの受信
この記事では、イベント サブスクリプションからイベントを受信する HTTP エンドポイントを検証した後、イベントを逆シリアル化する方法を説明します。 この記事では、デモの目的で Azure Functions を使用します。 ただし、アプリケーションがホストされている場所に関係なく、同じ概念が適用されます。
Note
Event Grid で Azure 関数をトリガーするときは、Event Grid トリガーを使用することをお勧めします。 それにより、Event Grid と Azure Functions をより簡単で迅速に統合できます。 ただし、Azure Functions の Event Grid トリガーでは、ホストされているコードが Event Grid に返される HTTP 状態コードを制御する必要があるシナリオはサポートされません。 この制限があるため、Azure Functions で実行されているコードは、たとえば、Event Grid によるイベント配信の再試行を開始するために 5XX エラーを返すことができません。
前提条件
- HTTP によってトリガーされる関数を含む関数アプリが必要です。
依存関係を追加する
.NET で開発する場合は、Azure.Messaging.EventGrid
NuGet パッケージの関数に依存関係を追加します。
他の言語用の SDK は、発行 SDK リファレンスを介して利用できます。 これらのパッケージには、EventGridEvent
StorageBlobCreatedEventData
、EventHubCaptureFileCreatedEventData
などのネイティブなイベントの種類用のモデルが含まれています。
エンドポイントの検証
最初に実行することは、Microsoft.EventGrid.SubscriptionValidationEvent
イベントの処理です。 だれかがイベントにサブスクライブするたびに、Event Grid は、データ ペイロード内に validationCode
を含む検証イベントをエンドポイントに送信します。 エンドポイントは、これを応答本文にエコー バックして、エンドポイントが有効であり、ユーザーによって所有されていることを証明する必要があります。 WebHook によってトリガーされる関数ではなく Event Grid トリガーを使用している場合、エンドポイントの検証は自動的に処理されます。 サード パーティ製 API サービス (Zapier や IFTTT など) を使用する場合は、検証コードをプログラムでエコーできないことがあります。 このようなサービスの場合は、サブスクリプション検証イベントで送信される検証 URL を使用すると、サブスクリプションを手動で検証できます。 その URL を validationUrl
プロパティにコピーし、REST クライアントまたは Web ブラウザーのいずれかを使って GET 要求を送信します。
C# では、ParseMany()
メソッドを使用して、1 つ以上のイベントを含む BinaryData
インスタンスを EventGridEvent
の配列に逆シリアル化します。 1 つのイベントのみを逆シリアル化することが事前にわかっている場合は、代わりに 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": "aaaa0a0a-bb1b-cc2c-dd3d-eeeeee4e4e4e",
"data": {
"api": "PutBlockList",
"clientRequestId": "bbbb1b1b-cc2c-dd3d-ee4e-ffffff5f5f5f",
"requestId": "cccc2c2c-dd3d-ee4e-ff5f-aaaaaa6a6a6a",
"eTag": "0x8D4BCC2E4835CD0",
"contentType": "text/plain",
"contentLength": 524288,
"blobType": "BlockBlob",
"url": "https://example.blob.core.windows.net/testcontainer/testfile.txt",
"sequencer": "00000000000004420000000000028963",
"storageDiagnostics": {
"batchId": "dddd3d3d-ee4e-ff5f-aa6a-bbbbbb7b7b7b"
}
},
"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": "aaaa0a0a-bb1b-cc2c-dd3d-eeeeee4e4e4e","data": {"api": "PutBlockList","clientRequestId": "bbbb1b1b-cc2c-dd3d-ee4e-ffffff5f5f5f","requestId": "cccc2c2c-dd3d-ee4e-ff5f-aaaaaa6a6a6a","eTag": "0x8D4BCC2E4835CD0","contentType": "text/plain","contentLength": 524288,"blobType": "BlockBlob","url": "https://example.blob.core.windows.net/testcontainer/testfile.txt","sequencer": "00000000000004420000000000028963","storageDiagnostics": {"batchId": "dddd3d3d-ee4e-ff5f-aa6a-bbbbbb7b7b7b"}},"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 ストレージ アカウントまたは General Purpose 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 を Portal から送信するか、エンドポイントに POST できる任意のサービスまたはアプリケーションを使用してカスタム トピックを投稿することで、ライブでテストすることもできます。 エンドポイントが関数 URL として設定されたカスタム トピックとイベント サブスクリプションを作成します。
メッセージ ヘッダー
これらは、メッセージ ヘッダーで受け取るプロパティです。
プロパティ名 | 説明 |
---|---|
aeg-subscription-name | イベント サブスクリプションの名前。 |
aeg-delivery-count | イベントに対して行われた試行の回数。 |
aeg-event-type | イベントの種類。 次のいずれかの値を指定できます。
|
aeg-metadata-version | イベントのメタデータ バージョン。 Event Grid のイベント スキーマの場合、このプロパティはメタデータのバージョンを表し、クラウド イベント スキーマの場合は仕様のバージョンを表します。 |
aeg-data-version | イベントのデータ バージョン。 Event Grid のイベント スキーマの場合、このプロパティはデータのバージョンを表し、クラウド イベント スキーマの場合は適用されません。 |
aeg-output-event-id | Event Grid イベントの ID。 |