Anteckning
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
GÄLLER FÖR: Alla API Management-nivåer
API Management-tjänsten har många funktioner för att förbättra bearbetningen av HTTP-begäranden som skickas till HTTP-API:et. Förekomsten av begäranden och svar är dock tillfällig. Begäran görs och flödar via API Management-tjänsten till ditt serverdels-API. Ditt API bearbetar begäran och ett svar flödar tillbaka till API-konsumenten. API Management-tjänsten behåller viktig statistik om API:erna för visning i Azure Portal instrumentpanel, men utöver det är informationen borta.
Genom att använda log-to-eventhub principen i API Management-tjänsten kan du skicka all information från begäran och svaret till en Azure Event Hubs. Det finns flera orsaker till att du kanske vill generera händelser från HTTP-meddelanden som skickas till dina API:er. Några exempel är spårningsspårning av uppdateringar, användningsanalys, undantagsaviseringar och integreringar från tredje part.
Den här artikeln visar hur du samlar in hela HTTP-begäran och svarsmeddelandet, skickar det till en händelsehubb och vidarebefordrar sedan meddelandet till en tjänst från tredje part som tillhandahåller HTTP-loggnings- och övervakningstjänster.
Varför skicka från API Management-tjänsten?
Du kan skriva HTTP-mellanprogram som kan anslutas till HTTP API-ramverk för att samla in HTTP-begäranden och svar och mata in dem i loggnings- och övervakningssystem. Nackdelen med den här metoden är att HTTP-mellanprogrammet måste integreras i serverdels-API:et och måste matcha API-plattformen. Om det finns flera API:er måste var och en distribuera mellanprogrammet. Det finns ofta anledningar till varför serverdels-API:er inte kan uppdateras.
Att använda Azure API Management-tjänsten för att integrera med loggningsinfrastrukturen ger en centraliserad och plattformsoberoende lösning. Det är också skalbart, delvis på grund av Azure API Managements geo-replikeringsfunktioner .
Varför skicka till en händelsehubb?
Det är rimligt att fråga: varför skapa en princip som är specifik för Azure Event Hubs? Det finns många olika platser där du kanske vill logga dina begäranden. Varför inte bara skicka begäranden direkt till slutmålet? Det är ett alternativ. Men när du gör loggningsbegäranden från en API-hanteringstjänst är det nödvändigt att överväga hur loggningsmeddelanden påverkar API:ets prestanda. Gradvisa ökningar av belastningen kan hanteras genom att öka tillgängliga instanser av systemkomponenter eller genom att dra nytta av geo-replikering. Korta trafiktoppar kan dock leda till att begäranden fördröjs om begäranden till loggningsinfrastrukturen börjar bli långsamma under belastningen.
Azure Event Hubs är utformat för att komma in enorma mängder data, med kapacitet för att hantera ett mycket högre antal händelser än antalet HTTP-begäranden som de flesta API:er bearbetar. Händelsehubben fungerar som en slags avancerad buffert mellan api-hanteringstjänsten och infrastrukturen som lagrar och bearbetar meddelandena. Detta säkerställer att API-prestandan inte påverkas på grund av loggningsinfrastrukturen.
När data har skickats till en händelsehubb bevaras de och väntar på att händelsehubbens konsumenter ska bearbeta dem. Händelsehubben bryr sig inte om hur den bearbetas, den bryr sig bara om att se till att meddelandet har levererats.
Event Hubs har möjlighet att strömma händelser till flera konsumentgrupper. Detta gör att händelser kan bearbetas av olika system. Detta stöder många integreringsscenarier utan att fördröja bearbetningen av API-begäran i API Management-tjänsten, eftersom endast en händelse behöver genereras.
En princip för att skicka program-/HTTP-meddelanden
En händelsehubb accepterar händelsedata som en enkel sträng. Innehållet i strängen är upp till dig. För att kunna paketera en HTTP-begäran och skicka den till Azure Event Hubs måste du formatera strängen med information om begäran eller svar. Om det finns ett befintligt format som du kan återanvända i sådana situationer kanske du inte behöver skriva din egen parsningskod. Till en början kan du överväga att använda HAR för att skicka HTTP-begäranden och svar. Det här formatet är dock optimerat för att lagra en sekvens med HTTP-begäranden i ett JSON-baserat format. Den innehöll många obligatoriska element som lade till onödig komplexitet för scenariot att skicka HTTP-meddelandet över kabeln.
Ett annat alternativ är att använda application/http medietypen enligt beskrivningen i HTTP-specifikationen RFC 7230. Den här medietypen använder exakt samma format som används för att faktiskt skicka HTTP-meddelanden via kabeln, men hela meddelandet kan placeras i brödtexten i en annan HTTP-begäran. I vårt fall använder vi bara brödtexten som vårt meddelande för att skicka till Event Hubs. Det finns en parser som finns i Microsoft ASP.NET Web API 2.2-klientbibliotek som kan parsa det här formatet och konvertera det till inbyggda HttpRequestMessage objekt och HttpResponseMessage objekt.
För att kunna skapa det här meddelandet måste vi dra nytta av C#-baserade principuttryck i Azure API Management. Här är principen som skickar ett HTTP-begärandemeddelande till Azure Event Hubs.
<log-to-eventhub logger-id="myapilogger" partition-id="0">
@{
   var requestLine = string.Format("{0} {1} HTTP/1.1\r\n",
                                               context.Request.Method,
                                               context.Request.Url.Path + context.Request.Url.QueryString);
   var body = context.Request.Body?.As<string>(true);
   if (body != null && body.Length > 1024)
   {
       body = body.Substring(0, 1024);
   }
   var headers = context.Request.Headers
                          .Where(h => h.Key != "Authorization" && h.Key != "Ocp-Apim-Subscription-Key")
                          .Select(h => string.Format("{0}: {1}", h.Key, String.Join(", ", h.Value)))
                          .ToArray<string>();
   var headerString = (headers.Any()) ? string.Join("\r\n", headers) + "\r\n" : string.Empty;
   return "request:"   + context.Variables["message-id"] + "\n"
                       + requestLine + headerString + "\r\n" + body;
}
</log-to-eventhub>
Principdeklaration
Det finns några saker som är värda att nämna om det här principuttrycket. Policyn log-to-eventhub har ett attribut med namnet logger-id, som refererar till namnet på den logger som skapats i API Management-tjänsten. Du hittar information om hur du konfigurerar en händelsehubbloggare i API Management-tjänsten i dokumentet Så här loggar du händelser till Azure Event Hubs i Azure API Management. Det andra attributet är en valfri parameter som instruerar Event Hubs vilken partition som meddelandet ska lagras i. Event Hubs använder partitioner för att möjliggöra skalbarhet och kräver minst två. Den beställda leveransen av meddelanden garanteras endast inom en partition. Om vi inte instruerar Azure Event Hubs i vilken partition meddelandet ska placeras använder den en resursallokeringsalgoritm för att distribuera belastningen. Det kan dock leda till att vissa meddelanden bearbetas ur ordning.
Partitioner
För att säkerställa att meddelanden levereras till konsumenter i ordning och dra nytta av belastningsfördelningsfunktionen för partitioner kan vi skicka HTTP-begärandemeddelanden till en partition och HTTP-svarsmeddelanden till en andra partition. Detta säkerställer en jämn belastningsfördelning och kan garantera att alla begäranden och alla svar används i ordning. Det är möjligt att ett svar förbrukas före motsvarande begäran, men det är inte ett problem eftersom vi har en annan mekanism för att korrelera begäranden till svar och vi vet att begäranden alltid kommer före svar.
HTTP-nyttolaster
När du har skapat requestLinekontrollerar du om begärandetexten ska trunkeras. Begärandetexten trunkeras till endast 1024. Detta kan ökas. Enskilda event hub-meddelanden är dock begränsade till 256 KB, så det är troligt att vissa HTTP-meddelandetexter inte får plats i ett enda meddelande. När du utför loggning och analys kan du härleda en betydande mängd information från bara HTTP-begäranderaden och rubrikerna. Dessutom returnerar många API:er endast små mängder data, så förlusten av informationsvärdet genom att trunkera stora datamängder är ganska minimal jämfört med minskningen av överförings-, bearbetnings- och lagringskostnader för att behålla allt datainnehåll.
En sista anteckning om bearbetning av brödtexten är att vi måste skicka true till As<string>() metoden, eftersom vi läser brödtextinnehållet men vi vill också att serverdels-API:et ska kunna läsa brödtexten. Genom att skicka true till den här metoden gör vi att brödtexten buffras så att den kan läsas en andra gång. Detta är viktigt om du har ett API som laddar upp stora filer eller använder lång avsökning. I dessa fall är det bäst att undvika att läsa innehållet alls.
HTTP-rubriker
HTTP-huvuden kan överföras till meddelandeformatet i ett enkelt nyckel/värde-parformat. Vi valde att ta bort vissa säkerhetskänsliga fält för att undvika onödigt läckande information om autentiseringsuppgifter. Det är osannolikt att API-nycklar och andra autentiseringsuppgifter skulle användas i analyssyfte. Om vi vill analysera användaren och den specifika produkt som de använder kan vi hämta den från context objektet och lägga till den i meddelandet.
Meddelandemetadata
När du skapar det fullständiga meddelandet som ska skickas till händelsehubben är frontlinjen inte en del av application/http meddelandet. Den första raden är ytterligare metadata som består av om meddelandet är ett begärande- eller svarsmeddelande och ett meddelande-ID, som används för att korrelera begäranden till svar. Meddelande-ID:t skapas med hjälp av en annan princip som ser ut så här:
<set-variable name="message-id" value="@(Guid.NewGuid())" />
Vi kan skapa begärandemeddelandet, lagra det i en variabel tills svaret returnerades och sedan skicka begäran och svaret som ett enda meddelande. Men genom att skicka begäran och svar oberoende av varandra och använda en message-id för att korrelera de två får vi lite mer flexibilitet i meddelandestorleken, möjligheten att dra nytta av flera partitioner samtidigt som meddelandeordningen bibehålls och en tidigare begäran kommer till vår loggningsinstrumentpanel. Det kan också finnas scenarier där ett giltigt svar aldrig skickas till händelsehubben (möjligen på grund av ett allvarligt begärandefel i API Management-tjänsten), men vi har fortfarande en post för begäran.
Principen för att skicka HTTP-svaret ser ut ungefär som begäran, och den kompletta policykonfigurationen ser ut så här:
<policies>
  <inbound>
      <set-variable name="message-id" value="@(Guid.NewGuid())" />
      <log-to-eventhub logger-id="myapilogger" partition-id="0">
      @{
          var requestLine = string.Format("{0} {1} HTTP/1.1\r\n",
                                                      context.Request.Method,
                                                      context.Request.Url.Path + context.Request.Url.QueryString);
          var body = context.Request.Body?.As<string>(true);
          if (body != null && body.Length > 1024)
          {
              body = body.Substring(0, 1024);
          }
          var headers = context.Request.Headers
                               .Where(h => h.Key != "Authorization" && h.Key != "Ocp-Apim-Subscription-Key")
                               .Select(h => string.Format("{0}: {1}", h.Key, String.Join(", ", h.Value)))
                               .ToArray<string>();
          var headerString = (headers.Any()) ? string.Join("\r\n", headers) + "\r\n" : string.Empty;
          return "request:"   + context.Variables["message-id"] + "\n"
                              + requestLine + headerString + "\r\n" + body;
      }
  </log-to-eventhub>
  </inbound>
  <backend>
      <forward-request follow-redirects="true" />
  </backend>
  <outbound>
      <log-to-eventhub logger-id="myapilogger" partition-id="1">
      @{
          var statusLine = string.Format("HTTP/1.1 {0} {1}\r\n",
                                              context.Response.StatusCode,
                                              context.Response.StatusReason);
          var body = context.Response.Body?.As<string>(true);
          if (body != null && body.Length > 1024)
          {
              body = body.Substring(0, 1024);
          }
          var headers = context.Response.Headers
                                          .Select(h => string.Format("{0}: {1}", h.Key, String.Join(", ", h.Value)))
                                          .ToArray<string>();
          var headerString = (headers.Any()) ? string.Join("\r\n", headers) + "\r\n" : string.Empty;
          return "response:"  + context.Variables["message-id"] + "\n"
                              + statusLine + headerString + "\r\n" + body;
     }
  </log-to-eventhub>
  </outbound>
</policies>
Principen set-variable skapar ett värde som är tillgängligt för både log-to-eventhub principen i <inbound> avsnittet och avsnittet <outbound> .
Ta emot händelser från Event Hubs
Händelser från Azure Event Hubs tas emot med hjälp av AMQP-protokollet. Microsoft Service Bus-teamet har gjort klientbibliotek tillgängliga för att göra de tidskrävande händelserna enklare. Det finns två olika metoder som stöds, den ena är direktkonsument  och den andra använder EventProcessorHost klassen. Exempel på dessa två metoder finns i event hubs-exempellagringsplatsen. Den korta versionen av skillnaderna: Direct Consumer ger dig fullständig kontroll och EventProcessorHost gör en del av VVS-arbetet åt dig men gör vissa antaganden om hur du bearbetar dessa händelser.
EventProcessorHost
I det här exemplet använder vi för enkelhetens EventProcessorHost skull, men det kanske inte är det bästa valet för det här scenariot. 
              EventProcessorHost gör det hårda arbetet med att se till att du inte behöver oroa dig för trådningsproblem i en viss händelseprocessorklass. Men i vårt scenario konverterar vi meddelandet till ett annat format och skickar det till en annan tjänst med hjälp av en asynkron metod. Det finns inget behov av att uppdatera delat tillstånd och därför ingen risk för trådningsproblem. För de flesta scenarier EventProcessorHost är förmodligen det bästa valet och är verkligen det enklare alternativet.
IEventProcessor
Det centrala konceptet när du använder EventProcessorHost är att skapa en implementering av IEventProcessor gränssnittet, som innehåller metoden ProcessEventAsync. Här är kärnan i den metoden:
async Task IEventProcessor.ProcessEventsAsync(PartitionContext context, IEnumerable<EventData> messages)
{
    foreach (EventData eventData in messages)
    {
        _Logger.LogInfo(string.Format("Event received from partition: {0} - {1}", context.Lease.PartitionId,eventData.PartitionKey));
        try
        {
            var httpMessage = HttpMessage.Parse(eventData.GetBodyStream());
            await _MessageContentProcessor.ProcessHttpMessage(httpMessage);
        }
        catch (Exception ex)
        {
            _Logger.LogError(ex.Message);
        }
    }
    ... checkpointing code snipped ...
}
En lista över EventData-objekt skickas till metoden och vi itererar över den listan. Byteen för varje metod parsas till ett HttpMessage-objekt och objektet skickas till en instans av IHttpMessageProcessor.
HttpMessage
Instansen HttpMessage innehåller tre datadelar:
public class HttpMessage
{
    public Guid MessageId { get; set; }
    public bool IsRequest { get; set; }
    public HttpRequestMessage HttpRequestMessage { get; set; }
    public HttpResponseMessage HttpResponseMessage { get; set; }
... parsing code snipped ...
}
Instansen HttpMessage innehåller ett MessageId GUID som gör att vi kan ansluta HTTP-begäran till motsvarande HTTP-svar och ett booleskt värde som identifierar om objektet innehåller en instans av en HttpRequestMessage och HttpResponseMessage. Genom att använda de inbyggda HTTP-klasserna från System.Net.Httpkunde jag dra nytta av parsningskoden application/http som ingår i System.Net.Http.Formatting.
IHttpMessageProcessor
Instansen HttpMessage vidarebefordras sedan till implementeringen av IHttpMessageProcessor, vilket är ett gränssnitt som jag har skapat för att frikoppla mottagandet och tolkningen av händelsen från Azure Event Hubs och den faktiska bearbetningen av den.
Vidarebefordra HTTP-meddelandet
I det här exemplet bestämde vi oss för att skicka HTTP-begäran till Moesif API Analytics. Moesif är en molnbaserad tjänst som specialiserar sig på HTTP-analys och felsökning. De har en kostnadsfri nivå, så det är enkelt att prova. Med Moesif kan vi se HTTP-begäranden i realtid som flödar genom vår API Management-tjänst.
Implementeringen IHttpMessageProcessor ser ut så här.
public class MoesifHttpMessageProcessor : IHttpMessageProcessor
{
    private readonly string RequestTimeName = "MoRequestTime";
    private MoesifApiClient _MoesifClient;
    private ILogger _Logger;
    private string _SessionTokenKey;
    private string _ApiVersion;
    public MoesifHttpMessageProcessor(ILogger logger)
    {
        var appId = Environment.GetEnvironmentVariable("APIMEVENTS-MOESIF-APP-ID", EnvironmentVariableTarget.Process);
        _MoesifClient = new MoesifApiClient(appId);
        _SessionTokenKey = Environment.GetEnvironmentVariable("APIMEVENTS-MOESIF-SESSION-TOKEN", EnvironmentVariableTarget.Process);
        _ApiVersion = Environment.GetEnvironmentVariable("APIMEVENTS-MOESIF-API-VERSION", EnvironmentVariableTarget.Process);
        _Logger = logger;
    }
    public async Task ProcessHttpMessage(HttpMessage message)
    {
        if (message.IsRequest)
        {
            message.HttpRequestMessage.Properties.Add(RequestTimeName, DateTime.UtcNow);
            return;
        }
        EventRequestModel moesifRequest = new EventRequestModel()
        {
            Time = (DateTime) message.HttpRequestMessage.Properties[RequestTimeName],
            Uri = message.HttpRequestMessage.RequestUri.OriginalString,
            Verb = message.HttpRequestMessage.Method.ToString(),
            Headers = ToHeaders(message.HttpRequestMessage.Headers),
            ApiVersion = _ApiVersion,
            IpAddress = null,
            Body = message.HttpRequestMessage.Content != null ? System.Convert.ToBase64String(await message.HttpRequestMessage.Content.ReadAsByteArrayAsync()) : null,
            TransferEncoding = "base64"
        };
        EventResponseModel moesifResponse = new EventResponseModel()
        {
            Time = DateTime.UtcNow,
            Status = (int) message.HttpResponseMessage.StatusCode,
            IpAddress = Environment.MachineName,
            Headers = ToHeaders(message.HttpResponseMessage.Headers),
            Body = message.HttpResponseMessage.Content != null ? System.Convert.ToBase64String(await message.HttpResponseMessage.Content.ReadAsByteArrayAsync()) : null,
            TransferEncoding = "base64"
        };
        Dictionary<string, string> metadata = new Dictionary<string, string>();
        metadata.Add("ApimMessageId", message.MessageId.ToString());
        EventModel moesifEvent = new EventModel()
        {
            Request = moesifRequest,
            Response = moesifResponse,
            SessionToken = _SessionTokenKey != null ? message.HttpRequestMessage.Headers.GetValues(_SessionTokenKey).FirstOrDefault() : null,
            Tags = null,
            UserId = null,
            Metadata = metadata
        };
        Dictionary<string, string> response = await _MoesifClient.Api.CreateEventAsync(moesifEvent);
        _Logger.LogDebug("Message forwarded to Moesif");
    }
    private static Dictionary<string, string> ToHeaders(HttpHeaders headers)
    {
        IEnumerable<KeyValuePair<string, IEnumerable<string>>> enumerable = headers.GetEnumerator().ToEnumerable();
        return enumerable.ToDictionary(p => p.Key, p => p.Value.GetEnumerator()
                                                         .ToEnumerable()
                                                         .ToList()
                                                         .Aggregate((i, j) => i + ", " + j));
    }
}
Den MoesifHttpMessageProcessor drar nytta av ett C# API-bibliotek för Moesif som gör det enkelt att skicka HTTP-händelsedata till Moesifs tjänst. För att kunna skicka HTTP-data till Moesif Collector-API:et behöver du ett konto och ett program-ID. Du får ett Moesif-program-ID genom att skapa ett konto på Moesifs webbplats och sedan gå till menyn längst upp till höger och välja Appinstallation.
Fullständigt exempel
Källkoden och testerna för exemplet finns på GitHub. Du behöver en API Management-tjänst, en ansluten händelsehubb och ett lagringskonto för att kunna köra exemplet själv.
Exemplet är bara ett enkelt konsolprogram som lyssnar efter händelser som kommer från Event Hub, konverterar dem till Moesif EventRequestModel och EventResponseModel objekt och vidarebefordrar dem sedan till Moesif Collector-API:et.
I följande animerade bild kan du se en begäran som görs till ett API i utvecklarportalen, konsolprogrammet som visar meddelandet som tas emot, bearbetas och vidarebefordras och sedan visas begäran och svaret i händelseströmmen.
               
              
            
Sammanfattning
Azure API Management-tjänsten är en idealisk plats för att samla in HTTP-trafik som reser till och från dina API:er. Azure Event Hubs är en mycket skalbar, billig lösning för att samla in trafiken och mata in den i sekundära bearbetningssystem för loggning, övervakning och annan avancerad analys. Att ansluta till trafikövervakningssystem från tredje part som Moesif är så enkelt som några dussin rader med kod.
Relaterat innehåll
- Läs mer om Azure Event Hubs
- Läs mer om API Management- och Event Hubs-integrering