Odbieranie zdarzeń w punkcie końcowym HTTP

W tym artykule opisano sposób weryfikowania punktu końcowego HTTP w celu odbierania zdarzeń z subskrypcji zdarzeń, a następnie odbierania i deserializacji zdarzeń. W tym artykule użyto funkcji platformy Azure do celów demonstracyjnych, jednak te same pojęcia mają zastosowanie niezależnie od tego, gdzie jest hostowana aplikacja.

Uwaga

Zalecamy użycie wyzwalacza usługi Event Grid podczas wyzwalania funkcji platformy Azure za pomocą usługi Event Grid. Zapewnia łatwiejszą i szybszą integrację między usługą Event Grid i usługą Azure Functions. Należy jednak pamiętać, że wyzwalacz usługi Event Grid usługi Azure Functions nie obsługuje scenariusza, w którym hostowany kod musi kontrolować kod stanu HTTP zwrócony do usługi Event Grid. Biorąc pod uwagę to ograniczenie, kod uruchomiony w funkcji platformy Azure nie może zwrócić błędu 5XX w celu zainicjowania ponawiania dostarczania zdarzeń przez usługę Event Grid, na przykład.

Wymagania wstępne

Potrzebna jest aplikacja funkcji z funkcją wyzwalaną przez protokół HTTP.

Dodawanie zależności

Jeśli programujesz na platformie .NET, dodaj zależność do funkcji pakietu Azure.Messaging.EventGridNuGet.

Zestawy SDK dla innych języków są dostępne za pośrednictwem dokumentacji zestawów SDK publikowania. Te pakiety mają modele dla natywnych typów zdarzeń, takich jak EventGridEvent, StorageBlobCreatedEventDatai EventHubCaptureFileCreatedEventData.

Walidacja punktu końcowego

Pierwszą rzeczą, którą chcesz zrobić, jest obsługa Microsoft.EventGrid.SubscriptionValidationEvent zdarzeń. Za każdym razem, gdy ktoś subskrybuje zdarzenie, usługa Event Grid wysyła zdarzenie weryfikacji do punktu końcowego za pomocą validationCode elementu w ładunku danych. Punkt końcowy jest wymagany, aby powtórzyć to z powrotem w treści odpowiedzi, aby udowodnić , że punkt końcowy jest prawidłowy i należący do Ciebie. Jeśli używasz wyzwalacza usługi Event Grid, a nie wyzwalanej funkcji elementu WebHook, walidacja punktu końcowego jest obsługiwana. Jeśli używasz usługi interfejsu API innej firmy (takiej jak Zapier lub IFTTT), możesz nie być w stanie programowo powtórzyć kodu weryfikacji. W przypadku tych usług możesz ręcznie zweryfikować subskrypcję przy użyciu adresu URL weryfikacji wysyłanego w zdarzeniu weryfikacji subskrypcji. Skopiuj ten adres URL we validationUrl właściwości i wyślij żądanie GET za pośrednictwem klienta REST lub przeglądarki internetowej.

W języku ParseMany() C# metoda służy do deserializacji wystąpienia zawierającego BinaryData co najmniej jedno zdarzenie w tablicy EventGridEvent. Jeśli wiesz wcześniej, że deserializujesz tylko jedno zdarzenie, możesz zamiast tego użyć Parse metody .

Aby programowo powtórzyć kod weryfikacji, użyj następującego kodu.

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();
};

Testowa odpowiedź na walidację

Przetestuj funkcję odpowiedzi weryfikacji, wklejając przykładowe zdarzenie do pola testowego dla funkcji:

[{
  "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"
}]

Po wybraniu pozycji Uruchom dane wyjściowe powinny mieć wartość 200 OK i {"validationResponse":"512d38b6-c7b8-40c8-89fe-f46f9e9622b6"} w treści:

Validation request

Validation output

Obsługa zdarzeń usługi Blob Storage

Teraz rozszerzmy funkcję w celu obsługi zdarzenia systemowego 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();
};

Testowanie obsługi zdarzeń utworzonych przez obiekt blob

Przetestuj nową funkcjonalność funkcji, umieszczając zdarzenie usługi Blob Storage w polu testowym i uruchamiając polecenie:

[{
  "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"
}]

Dane wyjściowe adresu URL obiektu blob powinny zostać wyświetlone w dzienniku funkcji:

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)

Możesz również przetestować, tworząc konto usługi Blob Storage lub konto magazynu ogólnego przeznaczenia w wersji 2, dodając subskrypcję zdarzeń i ustawiając punkt końcowy na adres URL funkcji:

Function URL

Obsługa zdarzeń niestandardowych

Na koniec można jeszcze raz rozszerzyć funkcję, aby mogła również obsługiwać zdarzenia niestandardowe.

Dodaj sprawdzanie dla zdarzenia Contoso.Items.ItemReceived. Końcowy kod powinien wyglądać następująco:

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();
};

Testowanie niestandardowej obsługi zdarzeń

Na koniec przetestuj, czy funkcja może teraz obsługiwać niestandardowy typ zdarzenia:

[{
    "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"
}]

Możesz również przetestować tę funkcję na żywo, wysyłając zdarzenie niestandardowe za pomocą narzędzia CURL z portalu lub publikując w temacie niestandardowym przy użyciu dowolnej usługi lub aplikacji, która może wysyłać post do punktu końcowego, takiego jak Postman. Utwórz temat niestandardowy i subskrypcję zdarzeń z ustawionym punktem końcowym jako adresem URL funkcji.

Nagłówki komunikatów

Są to właściwości odbierane w nagłówkach komunikatów:

Nazwa właściwości Opis
aeg-subscription-name Nazwa subskrypcji zdarzeń.
aeg-delivery-count Liczba prób wykonanych dla zdarzenia.
aeg-event-type

Typ zdarzenia.

Może to być jedna z następujących wartości:

  • SubskrypcjaValidation
  • Powiadomienie
  • SubskrypcjaDeletion
aeg-metadata-version

Wersja metadanych zdarzenia.

W przypadku schematu zdarzeń usługi Event Grid ta właściwość reprezentuje wersję metadanych i schemat zdarzeń w chmurze, reprezentuje wersję specyfikacji.

aeg-data-version

Wersja danych zdarzenia.

W przypadku schematu zdarzeń usługi Event Grid ta właściwość reprezentuje wersję danych i schemat zdarzeń w chmurze, ale nie ma zastosowania.

aeg-output-event-id Identyfikator zdarzenia usługi Event Grid.

Następne kroki