Notitie
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen u aan te melden of mappen te wijzigen.
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen om mappen te wijzigen.
Van toepassing op: Alle API Management-lagen
De API Management-service biedt veel mogelijkheden om de verwerking van HTTP-aanvragen die naar uw HTTP-API worden verzonden, te verbeteren. Het bestaan van de aanvragen en antwoorden is echter tijdelijk. De aanvraag wordt gedaan en doorloopt de API Management-service naar uw back-end-API. Uw API verwerkt de aanvraag en een antwoord loopt terug naar de API-consument. De API Management-service bewaart belangrijke statistieken over de API's die moeten worden weergegeven in het Dashboard van Azure Portal, maar verder zijn de details verdwenen.
Met behulp van het log-to-eventhub beleid in de API Management-service kunt u alle details van de aanvraag en reactie naar een Azure Event Hubs verzenden. Er zijn verschillende redenen waarom u mogelijk gebeurtenissen wilt genereren van HTTP-berichten die naar uw API's worden verzonden. Enkele voorbeelden zijn audittrail van updates, gebruiksanalyse, uitzonderingswaarschuwingen en integraties van derden.
In dit artikel wordt gedemonstreert hoe u het volledige HTTP-aanvraag- en antwoordbericht vastlegt, verzendt naar een Event Hub en dat bericht vervolgens doorstuurt naar een service van derden die HTTP-logboekregistratie en -bewakingsservices biedt.
Waarom verzenden vanuit API Management-service?
U kunt HTTP-middleware schrijven die kan worden aangesloten op HTTP API-frameworks om HTTP-aanvragen en -antwoorden vast te leggen en deze in logboekregistratie- en bewakingssystemen in te voeren. Het nadeel van deze benadering is dat de HTTP-middleware moet worden geïntegreerd in de back-end-API en moet overeenkomen met het API-platform. Als er meerdere API's zijn, moet elke API de middleware implementeren. Vaak zijn er redenen waarom back-end-API's niet kunnen worden bijgewerkt.
Het gebruik van de Azure API Management-service voor integratie met logboekregistratie-infrastructuur biedt een gecentraliseerde en platformonafhankelijke oplossing. Het is ook schaalbaar, deels vanwege de geo-replicatiemogelijkheden van Azure API Management.
Waarom verzenden naar een Event Hub?
Het is redelijk om te vragen: waarom maakt u een beleid dat specifiek is voor Azure Event Hubs? Er zijn veel verschillende plaatsen waar u uw aanvragen mogelijk wilt registreren. Waarom verstuurt u de aanvragen niet rechtstreeks naar de uiteindelijke bestemming? Dat is een optie. Wanneer u echter logboekregistratieaanvragen van een API Management-service maakt, moet u rekening houden met de invloed van logboekberichten op de prestaties van de API. Geleidelijke toename van de belasting kan worden verwerkt door het verhogen van beschikbare exemplaren van systeemonderdelen of door gebruik te maken van geo-replicatie. Korte pieken in het verkeer kunnen er echter toe leiden dat verzoeken worden vertraagd als verzoeken naar de log-infrastructuur langzaam worden onder druk.
Azure Event Hubs is ontworpen voor het inkomende grote hoeveelheden gegevens, met capaciteit voor het verwerken van een veel hoger aantal gebeurtenissen dan het aantal HTTP-aanvragen dat de meeste API's verwerken. De Event Hub fungeert als een soort geavanceerde buffer tussen uw API Management-service en de infrastructuur waarin de berichten worden opgeslagen en verwerkt. Dit zorgt ervoor dat uw API-prestaties niet te lijden hebben vanwege de infrastructuur voor logboekregistratie.
Zodra de gegevens zijn doorgegeven aan een Event Hub, blijven deze behouden en wachten totdat de event hub-consumenten deze verwerken. Het kan de Event Hub niet schelen hoe het wordt verwerkt, het vindt het alleen belangrijk dat het bericht succesvol wordt bezorgd.
Event Hubs heeft de mogelijkheid om gebeurtenissen naar meerdere consumentengroepen te streamen. Hierdoor kunnen gebeurtenissen door verschillende systemen worden verwerkt. Dit ondersteunt veel integratiescenario's zonder dat er meer vertragingen optreden bij de verwerking van de API-aanvraag in de API Management-service, omdat er slechts één gebeurtenis hoeft te worden gegenereerd.
Een beleid voor het verzenden van toepassings-/HTTP-berichten
Een Event Hub accepteert gebeurtenisgegevens als een eenvoudige tekenreeks. De inhoud van die tekenreeks bepaalt u. Als u een HTTP-aanvraag wilt verpakken en naar Azure Event Hubs wilt verzenden, moet u de tekenreeks opmaken met de aanvraag- of antwoordgegevens. In situaties zoals deze, als er een bestaande indeling is die u opnieuw kunt gebruiken, hoeft u mogelijk niet uw eigen parseringscode te schrijven. In eerste instantie kunt u overwegen om de HAR te gebruiken voor het verzenden van HTTP-aanvragen en -antwoorden. Deze indeling is echter geoptimaliseerd voor het opslaan van een reeks HTTP-aanvragen in een JSON-indeling. Het bevat veel verplichte elementen die onnodige complexiteit hebben toegevoegd voor het scenario van het doorgeven van het HTTP-bericht via de kabel.
Een alternatieve optie is het application/http mediatype te gebruiken zoals beschreven in de HTTP-specificatie RFC 7230. Dit mediatype gebruikt exact dezelfde indeling die wordt gebruikt voor het verzenden van HTTP-berichten via de kabel, maar het hele bericht kan in de hoofdtekst van een andere HTTP-aanvraag worden geplaatst. In ons geval gebruiken we gewoon de hoofdtekst als ons bericht om naar Event Hubs te verzenden. Handig is er een parser die bestaat in Microsoft ASP.NET Web API 2.2-clientbibliotheken die deze indeling kunnen parseren en converteren naar de systeemeigen HttpRequestMessage en HttpResponseMessage objecten.
Om dit bericht te kunnen maken, moeten we profiteren van op C# gebaseerde beleidsexpressies in Azure API Management. Dit is het beleid dat een HTTP-aanvraagbericht naar Azure Event Hubs verzendt.
<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>
Beleidsdeclaratie
Er zijn enkele dingen die het vermelden waard zijn over deze beleidsexpressie. Het log-to-eventhub beleid heeft een kenmerk met de naam logger-id van de logger die is gemaakt in de API Managementservice. U vindt de details van het instellen van een Event Hub-logboekregistratie in de API Management-service in het document Gebeurtenissen registreren bij Azure Event Hubs in Azure API Management. Het tweede kenmerk is een optionele parameter die Event Hubs instrueert over de partitie waarin het bericht moet worden opgeslagen. Event Hubs maakt gebruik van partities om schaalbaarheid mogelijk te maken en vereist minimaal twee. De geordende bezorging van berichten wordt alleen gegarandeerd binnen een partitie. Als we Azure Event Hubs niet instrueren in welke partitie het bericht moet plaatsen, wordt er een round robin-algoritme gebruikt om de belasting te verdelen. Dit kan er echter toe leiden dat sommige berichten niet op volgorde worden verwerkt.
Partitions
Om ervoor te zorgen dat berichten in de juiste volgorde bij consumenten worden afgeleverd en om gebruik te maken van de mogelijkheden voor belastingverdeling van partities, kunnen we HTTP-verzoekberichten naar één partitie sturen en HTTP-antwoordberichten naar een tweede partitie. Dit zorgt voor een gelijkmatige verdeling van de werklast en kan garanderen dat alle aanvragen en alle antwoorden in volgorde worden verwerkt. Het is mogelijk dat een reactie vóór de bijbehorende aanvraag wordt gebruikt, maar dat is geen probleem omdat we een ander mechanisme hebben voor het correleren van aanvragen aan antwoorden en we weten dat aanvragen altijd vóór antwoorden komen.
HTTP-nettoladingen
Controleer na het requestLinebouwen of de aanvraagbody moet worden afgekapt. De aanvraagbody wordt afgekapt tot slechts 1024. Dit kan worden verhoogd; Afzonderlijke Event Hub-berichten zijn echter beperkt tot 256 kB, dus het is waarschijnlijk dat sommige HTTP-berichtteksten niet in één bericht passen. Wanneer u logboekregistratie en analyses uitvoert, kunt u een aanzienlijke hoeveelheid informatie afleiden van alleen de HTTP-aanvraagregel en headers. Bovendien retourneren veel API's alleen kleine hoeveelheden gegevens, waardoor het verlies van informatiewaarde door het inkorten van grote inhoud vrij minimaal is in vergelijking met de vermindering van de overdracht-, verwerkings-, en opslagkosten om alle inhoud te behouden.
Een laatste opmerking over het verwerken van de berichttekst is dat we true moeten doorgeven aan de As<string>() methode, omdat we de inhoud van de berichttekst lezen, maar we willen ook dat de backend-API de berichttekst kan lezen. Door true door te geven aan deze methode, zorgen we ervoor dat de hoofdtekst wordt gebufferd, zodat deze een tweede keer kan worden gelezen. Dit is belangrijk als u een API hebt die grote bestanden uploadt of lange polling gebruikt. In deze gevallen is het raadzaam om het lezen van de inhoud geheel te vermijden.
HTTP-kopteksten
HTTP-headers kunnen worden overgedragen naar de berichtindeling in een eenvoudige sleutel-waarde-paar indeling. We hebben ervoor gekozen om bepaalde beveiligingsgevoelige velden te verwijderen om onnodige lekken van referentiegegevens te voorkomen. Het is onwaarschijnlijk dat API-sleutels en andere referenties worden gebruikt voor analysedoeleinden. Als we de gebruiker en het specifieke product willen analyseren dat ze gebruiken, kunnen we dat ophalen uit het context object en het toevoegen aan het bericht.
Berichtmetagegevens
Bij het bouwen van het volledige bericht dat naar de Event Hub moet worden verzonden, maakt de frontline geen deel uit van het application/http bericht. De eerste regel bestaat uit aanvullende metagegevens die bestaan uit het feit of het bericht een aanvraag- of antwoordbericht is en een bericht-id, die wordt gebruikt om aanvragen aan reacties te correleren. De bericht-id wordt gemaakt met behulp van een ander beleid dat er als volgt uitziet:
<set-variable name="message-id" value="@(Guid.NewGuid())" />
We kunnen het aanvraagbericht maken, dat opslaan in een variabele totdat het antwoord is geretourneerd en vervolgens de aanvraag en het antwoord verzenden als één bericht. Door de aanvraag en het antwoord onafhankelijk te verzenden en een message-id te gebruiken om de twee aan elkaar te koppelen, verkrijgen we meer flexibiliteit in de berichtgrootte, de mogelijkheid om te profiteren van meerdere partities terwijl de berichtvolgorde behouden blijft, en een snellere aankomst van het verzoek in ons logdashboard. Er kunnen ook scenario's zijn waarin een geldig antwoord nooit naar de Event Hub wordt verzonden (mogelijk vanwege een fatale aanvraagfout in de API Management-service), maar we hebben nog steeds een record van de aanvraag.
Het beleid voor het verzenden van het HTTP-antwoordbericht lijkt op de aanvraag en de volledige beleidsconfiguratie ziet er als volgt uit:
<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>
Het set-variable beleid maakt een waarde die toegankelijk is voor zowel het log-to-eventhub beleid in de <inbound> sectie als de <outbound> sectie.
Gebeurtenissen ontvangen van Event Hubs
Gebeurtenissen van Azure Event Hubs worden ontvangen met behulp van het AMQP-protocol. Microsoft Service Bus-team heeft clientbibliotheken beschikbaar gemaakt om het consumeren van evenementen te vereenvoudigen. Er worden twee verschillende benaderingen ondersteund, één is een Direct Consumer en de andere gebruikt de EventProcessorHost klasse. Voorbeelden van deze twee benaderingen vindt u in de opslagplaats met Event Hubs-voorbeelden. De korte versie van de verschillen: Direct Consumer geeft u volledige controle en het EventProcessorHost doet enkele van de sanitairwerkzaamheden voor u, maar maakt bepaalde veronderstellingen over hoe u deze gebeurtenissen verwerkt.
EventProcessorHost
In dit voorbeeld gebruiken we de EventProcessorHost voor eenvoud, maar het is mogelijk niet de beste keuze voor dit specifieke scenario.
EventProcessorHost doet het harde werk om ervoor te zorgen dat u zich geen zorgen hoeft te maken over threadingproblemen binnen een bepaalde gebeurtenisprocessorklasse. In ons scenario wordt het bericht echter geconverteerd naar een andere indeling en doorgegeven aan een andere service met behulp van een asynchrone methode. Het is niet nodig om de gedeelde status bij te werken en daarom is er geen risico op threadingproblemen. Voor de meeste scenario's EventProcessorHost is dit waarschijnlijk de beste keuze en is dit zeker de eenvoudigere optie.
IEventProcessor
Het centrale concept bij gebruik EventProcessorHost is het maken van een implementatie van de IEventProcessor interface, die de methode ProcessEventAsyncbevat. Dit is de essentie van die methode:
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 ...
}
Een lijst met EventData-objecten wordt doorgegeven aan de methode en we herhalen deze lijst. De bytes van elke methode worden geparseerd in een HttpMessage-object en dat object wordt doorgegeven aan een exemplaar van IHttpMessageProcessor.
HttpMessage
Het HttpMessage exemplaar bevat drie stukjes gegevens:
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 ...
}
Het HttpMessage exemplaar bevat een MessageId GUID waarmee we de HTTP-aanvraag kunnen verbinden met het bijbehorende HTTP-antwoord en een Booleaanse waarde die aangeeft of het object een exemplaar van een HttpRequestMessage en HttpResponseMessage bevat. Door gebruik te maken van de ingebouwde HTTP-klassen van System.Net.Http, kon ik profiteren van de application/http parseringscode die is opgenomen in System.Net.Http.Formatting.
IHttpMessageProcessor
Het HttpMessage exemplaar wordt vervolgens doorgestuurd naar de implementatie van IHttpMessageProcessor, een interface die ik heb gemaakt om het ontvangen en interpreteren van de gebeurtenis van Azure Event Hubs los te koppelen en de daadwerkelijke verwerking ervan.
Het HTTP-bericht doorsturen
Voor dit voorbeeld hebben we besloten om de HTTP-aanvraag naar Moesif API Analytics te pushen. Moesif is een cloudservice die gespecialiseerd is in HTTP-analyse en foutopsporing. Ze hebben een gratis versie, dus het is gemakkelijk om uit te proberen. Moesif stelt ons in staat om de HTTP-aanvragen in realtime te zien die via onze API Management-service stromen.
De IHttpMessageProcessor implementatie ziet er als volgt uit:
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));
}
}
Het MoesifHttpMessageProcessor maakt gebruik van een C#-API-bibliotheek voor Moesif waarmee u eenvoudig HTTP-gebeurtenisgegevens naar hun service kunt pushen. Als u HTTP-gegevens wilt verzenden naar de Moesif Collector-API, hebt u een account en een toepassings-id nodig. U krijgt een Moesif-toepassings-id door een account te maken op de website van Moesif en vervolgens naar het menu rechtsboven te gaan en App Setup te selecteren.
Volledig voorbeeld
De broncode en tests voor het voorbeeld bevinden zich op GitHub. U hebt een API Management-service, een verbonden Event Hub en een opslagaccount nodig om het voorbeeld voor uzelf uit te voeren.
Het voorbeeld is slechts een eenvoudige consoletoepassing die luistert naar gebeurtenissen die afkomstig zijn van Event Hub, converteert ze naar een Moesif EventRequestModel en EventResponseModel objecten en stuurt ze vervolgens door naar de Moesif Collector-API.
In de volgende geanimeerde afbeelding ziet u een aanvraag naar een API in de ontwikkelaarsportal, de consoletoepassing met het bericht dat wordt ontvangen, verwerkt en doorgestuurd en vervolgens de aanvraag en het antwoord weergegeven in de eventstream.
Summary
De Azure API Management-service biedt een ideale plek om het HTTP-verkeer van en naar uw API's vast te leggen. Azure Event Hubs is een zeer schaalbare, goedkope oplossing voor het vastleggen van dat verkeer en het invoeren ervan in secundaire verwerkingssystemen voor logboekregistratie, bewaking en andere geavanceerde analyses. Verbinding maken met systemen voor verkeersbewaking van derden, zoals Moesif, is net zo eenvoudig als enkele tientallen regels code.
Verwante inhoud
- Meer informatie over Azure Event Hubs
- Meer informatie over API Management en Event Hubs-integratie