Monitorowanie interfejsów API za pomocą usług Azure API Management, Event Hubs i Moesif
DOTYCZY: Wszystkie warstwy usługi API Management
Usługa API Management udostępnia wiele funkcji w celu zwiększenia przetwarzania żądań HTTP wysyłanych do interfejsu API HTTP. Jednak istnienie żądań i odpowiedzi jest przejściowe. Żądanie jest wykonywane i przepływa przez usługę API Management do interfejsu API zaplecza. Interfejs API przetwarza żądanie i odpowiedź przepływa z powrotem do odbiorcy interfejsu API. Usługa API Management przechowuje ważne statystyki dotyczące interfejsów API do wyświetlania na pulpicie nawigacyjnym witryny Azure Portal, ale poza tym szczegóły nie zostały wyświetlone.
Korzystając z log-to-eventhub
zasad w usłudze API Management, możesz wysłać wszelkie szczegóły z żądania i odpowiedzi na usługę Azure Event Hubs. Istnieje kilka powodów, dla których możesz chcieć wygenerować zdarzenia z komunikatów HTTP wysyłanych do interfejsów API. Niektóre przykłady obejmują dziennik inspekcji aktualizacji, analizę użycia, alerty wyjątków i integracje innych firm.
W tym artykule pokazano, jak przechwycić całe żądanie HTTP i komunikat odpowiedzi, wysłać go do centrum zdarzeń, a następnie przekazać ten komunikat do usługi innej firmy, która zapewnia usługi rejestrowania i monitorowania HTTP.
Dlaczego warto wysłać z usługi API Management?
Istnieje możliwość zapisania oprogramowania pośredniczącego HTTP, które może podłączyć się do struktur interfejsu API HTTP w celu przechwytywania żądań i odpowiedzi HTTP oraz podawania ich do systemów rejestrowania i monitorowania. Wadą tego podejścia jest to, że oprogramowanie pośredniczące HTTP musi zostać zintegrowane z interfejsem API zaplecza i musi być zgodne z platformą interfejsu API. Jeśli istnieje wiele interfejsów API, każdy z nich musi wdrożyć oprogramowanie pośredniczące. Często istnieją powody, dla których nie można zaktualizować interfejsów API zaplecza.
Korzystanie z usługi Azure API Management do integracji z infrastrukturą rejestrowania zapewnia scentralizowane i niezależne od platformy rozwiązanie. Jest również skalowalna, częściowo ze względu na możliwości replikacji geograficznej usługi Azure API Management.
Dlaczego warto wysłać do centrum zdarzeń?
Warto zapytać, dlaczego warto utworzyć zasady specyficzne dla usługi Azure Event Hubs? Istnieje wiele różnych miejsc, w których mogę chcieć rejestrować moje żądania. Dlaczego nie tylko wysyłać żądania bezpośrednio do końcowego miejsca docelowego? Jest to opcja. Jednak podczas rejestrowania żądań z usługi API Management należy wziąć pod uwagę wpływ komunikatów rejestrowania na wydajność interfejsu API. Stopniowy wzrost obciążenia można obsłużyć przez zwiększenie dostępnych wystąpień składników systemu lub wykorzystanie replikacji geograficznej. Jednak krótkie skoki ruchu mogą spowodować opóźnienie żądań, jeśli żądania rejestrowania infrastruktury zaczynają spowalniać pod obciążeniem.
Usługa Azure Event Hubs jest przeznaczona do ściągnięcia ogromnych ilości danych z pojemnością do obsługi znacznie większej liczby zdarzeń niż liczba żądań HTTP, które przetwarza większość interfejsów API. Centrum zdarzeń działa jako rodzaj zaawansowanego buforu między usługą API Management a infrastrukturą, która przechowuje i przetwarza komunikaty. Gwarantuje to, że wydajność interfejsu API nie będzie cierpieć z powodu infrastruktury rejestrowania.
Gdy dane zostały przekazane do centrum zdarzeń, będą się powtarzać i czekać na przetworzenie ich przez użytkowników centrum zdarzeń. Centrum zdarzeń nie obchodzi, jak jest przetwarzane, po prostu dba o upewnienie się, że komunikat został pomyślnie dostarczony.
Usługa Event Hubs ma możliwość przesyłania strumieniowego zdarzeń do wielu grup odbiorców. Dzięki temu zdarzenia mogą być przetwarzane przez różne systemy. Umożliwia to obsługę wielu scenariuszy integracji bez wprowadzania dodatkowych opóźnień w przetwarzaniu żądania interfejsu API w usłudze API Management, ponieważ należy wygenerować tylko jedno zdarzenie.
Zasady wysyłania komunikatów aplikacji/http
Centrum zdarzeń akceptuje dane zdarzeń jako prosty ciąg. Zawartość tego ciągu jest do Ciebie. Aby móc spakować żądanie HTTP i wysłać je do usługi Azure Event Hubs, musimy sformatować ciąg przy użyciu informacji o żądaniu lub odpowiedzi. W takich sytuacjach, jeśli istnieje istniejący format, który możemy użyć ponownie, być może nie będziemy musieli pisać własnego kodu analizy. Początkowo rozważałem użycie har do wysyłania żądań HTTP i odpowiedzi. Jednak ten format jest zoptymalizowany pod kątem przechowywania sekwencji żądań HTTP w formacie opartym na formacie JSON. Zawierał wiele obowiązkowych elementów, które dodały niepotrzebną złożoność scenariusza przekazywania komunikatu HTTP przez przewody.
Alternatywną opcją było użycie typu nośnika application/http
zgodnie z opisem w specyfikacji HTTP RFC 7230. Ten typ nośnika używa dokładnie tego samego formatu, który jest używany do rzeczywistego wysyłania komunikatów HTTP za pośrednictwem przewodu, ale cały komunikat można umieścić w treści innego żądania HTTP. W naszym przypadku używamy treści jako komunikatu do wysyłania do usługi Event Hubs. Wygodnie istnieje analizator, który istnieje w bibliotece klienta microsoft ASP.NET Web API 2.2, które mogą analizować ten format i konwertować go na obiekty natywne HttpRequestMessage
i HttpResponseMessage
.
Aby móc utworzyć ten komunikat, musimy skorzystać z wyrażeń zasad opartych na języku C# w usłudze Azure API Management. Poniżej przedstawiono zasady, które wysyła komunikat żądania HTTP do usługi 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>
Deklaracja zasad
Istnieje kilka konkretnych kwestii, które warto wspomnieć o tym wyrażeniu zasad. Zasady log-to-eventhub
mają atrybut o nazwie logger-id
, który odwołuje się do nazwy rejestratora, który został utworzony w usłudze API Management. Szczegółowe informacje na temat konfigurowania rejestratora centrum zdarzeń w usłudze API Management można znaleźć w dokumencie How to log events to Azure Event Hubs in Azure API Management (Jak rejestrować zdarzenia w usłudze Azure Event Hubs w usłudze Azure API Management). Drugi atrybut to opcjonalny parametr, który instruuje usługę Event Hubs, w której partycji ma być przechowywany komunikat. Usługa Event Hubs używa partycji, aby umożliwić skalowalność i wymaga co najmniej dwóch. Uporządkowana dostawa komunikatów jest gwarantowana tylko w ramach partycji. Jeśli nie poinstruujemy usługi Azure Event Hubs, w której partycji ma być umieszczany komunikat, używa algorytmu działania okrężnego do dystrybucji obciążenia. Może to jednak spowodować przetworzenie niektórych komunikatów poza zamówieniem.
Partycje
Aby upewnić się, że nasze komunikaty są dostarczane do użytkowników w kolejności i korzystają z możliwości dystrybucji obciążenia partycji, zdecydowałem się wysyłać komunikaty żądań HTTP do jednej partycji i komunikatów odpowiedzi HTTP do drugiej partycji. Zapewnia to równomierną dystrybucję obciążenia i możemy zagwarantować, że wszystkie żądania będą używane w kolejności, a wszystkie odpowiedzi są używane w kolejności. Istnieje możliwość, aby odpowiedź była zużywana przed odpowiednim żądaniem, ale ponieważ nie jest to problem, ponieważ mamy inny mechanizm korelowania żądań do odpowiedzi i wiemy, że żądania zawsze pojawiają się przed odpowiedziami.
Ładunki HTTP
Po utworzeniu elementu requestLine
sprawdzamy, czy treść żądania powinna zostać obcięta. Treść żądania jest obcięta tylko do 1024. Można to zwiększyć, jednak poszczególne komunikaty centrum zdarzeń są ograniczone do 256 KB, więc prawdopodobnie niektóre treści komunikatów HTTP nie będą mieścić się w jednym komunikacie. Podczas rejestrowania i analizy można uzyskać znaczną ilość informacji tylko z wiersza i nagłówków żądania HTTP. Ponadto wiele interfejsów API żąda tylko małych ciał i dlatego utrata wartości informacji przez obcięcie dużych ciał jest dość minimalna w porównaniu z zmniejszeniem kosztów transferu, przetwarzania i magazynowania, aby zachować całą zawartość treści. Jedną z ostatnich uwag dotyczących przetwarzania treści jest to, że musimy przekazać true
do As<string>()
metody, ponieważ odczytujemy zawartość treści, ale chcieliśmy również, aby interfejs API zaplecza mógł odczytać treść. Przekazując wartość true do tej metody, powodujemy buforację treści, aby można było ją odczytać po raz drugi. Ważne jest, aby pamiętać o tym, czy masz interfejs API, który przekazuje duże pliki lub używa długiego sondowania. W takich przypadkach najlepiej byłoby unikać czytania ciała w ogóle.
Nagłówki HTTP
Nagłówki HTTP można przenosić do formatu komunikatu w prostym formacie pary klucz/wartość. Postanowiliśmy usunąć niektóre pola poufne zabezpieczeń, aby uniknąć niepotrzebnego wycieku informacji o poświadczeniach. Jest mało prawdopodobne, aby klucze interfejsu API i inne poświadczenia były używane do celów analitycznych. Jeśli chcemy przeprowadzić analizę użytkownika i konkretnego używanego produktu, możemy pobrać je z context
obiektu i dodać go do komunikatu.
Metadane komunikatu
Podczas kompilowania kompletnego komunikatu do wysłania do centrum zdarzeń linia frontu nie jest w rzeczywistości częścią komunikatu application/http
. Pierwszy wiersz to dodatkowe metadane składające się z tego, czy komunikat jest żądaniem, czy komunikatem odpowiedzi i identyfikatorem komunikatu, który służy do korelowania żądań z odpowiedziami. Identyfikator komunikatu jest tworzony przy użyciu innych zasad, które wyglądają następująco:
<set-variable name="message-id" value="@(Guid.NewGuid())" />
Moglibyśmy utworzyć komunikat żądania, przechowywany w zmiennej do momentu zwrócenia odpowiedzi, a następnie wysłania żądania i odpowiedzi jako pojedynczego komunikatu. Jednak wysyłając żądanie i odpowiedź niezależnie i używając message-id
elementu w celu skorelowania tych dwóch, uzyskamy nieco większą elastyczność w rozmiarze komunikatu, możliwość korzystania z wielu partycji przy zachowaniu kolejności komunikatów, a żądanie pojawi się wcześniej na pulpicie nawigacyjnym rejestrowania. Mogą również wystąpić pewne scenariusze, w których prawidłowa odpowiedź nigdy nie jest wysyłana do centrum zdarzeń, prawdopodobnie z powodu błędu żądania krytycznego w usłudze API Management, ale nadal mamy rekord żądania.
Zasady wysyłania komunikatu HTTP odpowiedzi wyglądają podobnie do żądania, więc kompletna konfiguracja zasad wygląda następująco:
<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>
Zasady set-variable
tworzą wartość dostępną zarówno log-to-eventhub
przez zasady w sekcji, jak <inbound>
i w <outbound>
sekcji.
Odbieranie zdarzeń z usługi Event Hubs
Zdarzenia z usługi Azure Event Hubs są odbierane przy użyciu protokołu AMQP. Zespół usługi Microsoft Service Bus udostępnił biblioteki klienckie, aby ułatwić korzystanie z zdarzeń. Obsługiwane są dwa różne podejścia: jeden jest klientem bezpośrednim , a drugi korzysta z EventProcessorHost
klasy . Przykłady tych dwóch podejść można znaleźć w Przewodniku programowania usługi Event Hubs. Krótka wersja różnic jest, daje pełną kontrolę i EventProcessorHost
wykonuje niektóre prace hydrauliczne dla Ciebie, Direct Consumer
ale sprawia, że pewne założenia dotyczące sposobu przetwarzania tych zdarzeń.
EventProcessorHost
W tym przykładzie używamy elementu EventProcessorHost
dla uproszczenia, jednak może nie być najlepszym wyborem dla tego konkretnego scenariusza. EventProcessorHost
Wykonuje ciężką pracę, upewniając się, że nie musisz martwić się o problemy wątkowe w określonej klasie procesora zdarzeń. Jednak w naszym scenariuszu po prostu konwertujemy komunikat na inny format i przekazujemy go do innej usługi przy użyciu metody asynchronicznej. Nie ma potrzeby aktualizowania stanu udostępnionego i dlatego nie ma ryzyka problemów z wątkami. W przypadku większości scenariuszy EventProcessorHost
jest prawdopodobnie najlepszym wyborem i z pewnością jest to łatwiejsza opcja.
IEventProcessor
Centralną koncepcją podczas używania EventProcessorHost
jest utworzenie implementacji interfejsu IEventProcessor
, która zawiera metodę ProcessEventAsync
. Istota tej metody jest pokazana tutaj:
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 ...
}
Lista obiektów EventData jest przekazywana do metody i iterujemy tę listę. Bajty każdej metody są analizowane w obiekcie HttpMessage i ten obiekt jest przekazywany do wystąpienia klasy IHttpMessageProcessor.
HttpMessage
Wystąpienie HttpMessage
zawiera trzy fragmenty danych:
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 ...
}
Wystąpienie HttpMessage
zawiera MessageId
identyfikator GUID, który umożliwia nam połączenie żądania HTTP z odpowiednią odpowiedzią HTTP i wartością logiczną, która identyfikuje, czy obiekt zawiera wystąpienie httpRequestMessage i HttpResponseMessage. Korzystając z wbudowanych klas HTTP z System.Net.Http
klasy , byłem w stanie skorzystać z application/http
kodu analizowania, który jest zawarty w pliku System.Net.Http.Formatting
.
IHttpMessageProcessor
Wystąpienie HttpMessage
jest następnie przekazywane do implementacji IHttpMessageProcessor
elementu , który jest interfejsem utworzonym w celu oddzielenia odbierania i interpretowania zdarzenia z usługi Azure Event Hubs oraz rzeczywistego przetwarzania go.
Przekazywanie komunikatu HTTP
W tym przykładzie zdecydowałem, że warto wypchnąć żądanie HTTP do usługi Moesif API Analytics. Moesif to usługa oparta na chmurze, która specjalizuje się w analizie i debugowaniu HTTP. Mają warstwę bezpłatną, więc można łatwo wypróbować i umożliwić nam wyświetlanie żądań HTTP w czasie rzeczywistym przepływających za pośrednictwem naszej usługi API Management.
Implementacja IHttpMessageProcessor
wygląda następująco:
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));
}
}
Funkcja MoesifHttpMessageProcessor
korzysta z biblioteki interfejsu API języka C# dla środowiska Moesif , która ułatwia wypychanie danych zdarzeń HTTP do usługi. Aby wysyłać dane HTTP do interfejsu API modułu zbierającego Moesif, potrzebne jest konto i identyfikator aplikacji. Otrzymasz identyfikator aplikacji Moesif, tworząc konto w witrynie internetowej Moesif, a następnie przejdź do menu u góry po prawej stronie —> Konfiguracja aplikacji.
Kompletny przykład
Kod źródłowy i testy dla przykładu znajdują się w witrynie GitHub. Aby samodzielnie uruchomić przykład, potrzebujesz usługi API Management, połączonego centrum zdarzeń i konta magazynu.
Przykład jest tylko prostą aplikacją konsolową, która nasłuchuje zdarzeń pochodzących z centrum zdarzeń, konwertuje je na moesif EventRequestModel
i EventResponseModel
obiekty, a następnie przekazuje je do interfejsu API modułu zbierającego Moesif.
Na poniższej animowanej ilustracji można zobaczyć żądanie wysyłane do interfejsu API w portalu dla deweloperów, w aplikacji konsolowej z wyświetlonym komunikatem odbieranym, przetworzonym i przesłanym dalej, a następnie żądanie i odpowiedź wyświetlaną w strumieniu zdarzeń.
Podsumowanie
Usługa Azure API Management zapewnia idealne miejsce do przechwytywania ruchu HTTP przychodzącego do i z interfejsów API. Usługa Azure Event Hubs to wysoce skalowalne, tanie rozwiązanie do przechwytywania tego ruchu i przekazywania go do pomocniczych systemów przetwarzania na potrzeby rejestrowania, monitorowania i innych zaawansowanych analiz. Nawiązywanie połączenia z systemami monitorowania ruchu innych firm, takimi jak Moesif, jest tak proste, jak kilkadziesiąt wierszy kodu.
Następne kroki
- Dowiedz się więcej o usłudze Azure Event Hubs
- Dowiedz się więcej o integracji usług API Management i Event Hubs