Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
SE APLICA A: Todos los niveles de API Management
El servicio Administración de API proporciona muchas capacidades para mejorar el procesamiento de solicitudes de HTTP enviadas a la API HTTP. Sin embargo, la existencia de las solicitudes y respuestas es transitoria. La solicitud se realiza y fluye a través del servicio API Management a la API de back-end. La API procesa la solicitud y se pasa una respuesta al consumidor de API. El servicio API Management mantiene algunas estadísticas importantes acerca de las API que se muestran en el panel de Azure Portal, pero aparte de eso, los detalles desaparecen.
Mediante la directiva log-to-eventhub del servicio API Management, puede enviar los detalles de la solicitud y la respuesta a un Azure Event Hubs. Hay varias razones por las que es posible que quiera generar eventos a partir de mensajes HTTP que se envían a las API. Algunos ejemplos incluyen traza de auditoría de las actualizaciones, análisis de uso, alertas de excepción e integraciones de terceros.
En este artículo se muestra cómo capturar toda la solicitud HTTP y el mensaje de respuesta, enviarlo a un centro de eventos y, a continuación, retransmitir ese mensaje a un servicio de terceros que proporciona servicios de registro y supervisión HTTP.
¿Por qué enviar desde el servicio API Management?
Puede escribir middleware HTTP que pueda conectar a marcos de API HTTP para capturar solicitudes y respuestas HTTP y alimentarlas en sistemas de registro y supervisión. El inconveniente de este enfoque es que el middleware HTTP debe integrarse en la API de back-end y debe coincidir con la plataforma de API. Si hay varias API, todas deben implementar el middleware. A menudo existen motivos por los que no se pueden actualizar las API de back-end.
El usp del servicio Azure API Management para la integración con la infraestructura de registro proporciona una solución centralizada e independiente de la plataforma. También es escalable, en parte debido a las funcionalidades de replicación geográfica de Azure API Management.
¿Por qué se envía a un centro de eventos?
Es razonable preguntar: ¿por qué crear una directiva específica de Azure Event Hubs? Hay muchos lugares diferentes en los que es posible que quiera registrar las solicitudes. ¿Por qué no simplemente enviar las solicitudes directamente al destino final? Esa es una opción. Sin embargo, al realizar solicitudes de registro desde un servicio de API Management, es necesario tener en cuenta cómo afectan los mensajes de registro al rendimiento de la API. Para manejar los aumentos graduales de la carga puede aumentar las instancias disponibles de los componentes del sistema o aprovechar las ventajas de la replicación geográfica. Sin embargo, picos de tráfico cortos pueden hacer que las solicitudes se retrasen si las solicitudes a la infraestructura de registro empiezan a ralentizarse debido a la carga.
Azure Event Hubs está diseñado para la entrada de grandes volúmenes de datos, con capacidad para gestionar un número mucho mayor de eventos que el que la mayoría de las API procesan. El centro de eventos actúa como un tipo de búfer sofisticado entre el servicio API Management y la infraestructura que almacena y procesa los mensajes. Esto garantiza que no se verá afectado el rendimiento de la API debido a la infraestructura de registro.
Una vez que los datos se han pasado a un centro de eventos, se conserva y espera a que los consumidores del centro de eventos lo procese. Al centro de eventos no le importa cómo se procesa, simplemente se ocupa de asegurarse de que el mensaje se entregue correctamente.
Event Hubs ofrece la posibilidad de transmitir eventos a varios grupos de consumidores. Esto permite que sistemas diferentes procesen los eventos. Esto admite muchos escenarios de integración sin poner más retrasos en el procesamiento de la solicitud de API dentro del servicio API Management, ya que solo es necesario generar un evento.
Una directiva para enviar mensajes de aplicación o HTTP
Un Centro de eventos acepta datos de eventos como una cadena simple. El contenido de esa cadena es decisión suya. Para poder empaquetar una solicitud HTTP y enviarlo a Azure Event Hubs, debe dar formato a la cadena con la información de solicitud o respuesta. En situaciones como esta, si hay un formato existente que puede reutilizar, es posible que no tenga que escribir su propio código de análisis. Inicialmente, puede considerar el uso de HAR para enviar solicitudes y respuestas HTTP. Sin embargo, este formato está optimizado para almacenar una secuencia de solicitudes HTTP en un formato basado en JSON. Contenía muchos elementos obligatorios que agregaba una complejidad innecesaria para el escenario de pasar el mensaje HTTP a través del cable.
Una opción alternativa es utilizar el tipo de medios application/http, tal como se describe en la especificación HTTP RFC 7230. Este tipo de medio usa el mismo formato exacto que se utiliza para enviar mensajes HTTP a través de la red, pero todo el mensaje se puede colocar en el cuerpo de otra solicitud HTTP. En nuestro caso, el cuerpo se usará solo como mensaje para enviarlo a Event Hubs. Convenientemente, hay un analizador en las bibliotecas de Microsoft ASP.NET Web API 2.2 Cliente que puede analizar este formato y convertirlo a HttpRequestMessage nativo y objetos HttpResponseMessage.
Para poder crear este mensaje, se deben aprovechar las expresiones de directiva basadas en C# en Azure API Management. Esta es la directiva, que envía un mensaje de solicitud HTTP a 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>
Declaración de directiva
Hay algunas cosas específicas que merece la pena mencionar acerca de esta expresión de directiva. La log-to-eventhub directiva tiene un atributo denominado logger-id, que hace referencia al nombre del registrador creado en el servicio API Management. Puede encontrar los detalles de cómo configurar un registrador de centro de eventos en el servicio API Management en el documento Registro de eventos en Azure Event Hubs en Azure API Management. El segundo atributo es un parámetro opcional que indica a Event Hubs en qué partición almacenar el mensaje. Event Hubs usa particiones para habilitar la escalabilidad y requiere dos como mínimo. La entrega ordenada de mensajes solo se garantiza dentro de una partición. Si no se indica a Azure Event Hubs en qué partición desea colocar el mensaje, usa un algoritmo round-robin para distribuir la carga. Sin embargo, esto puede hacer que algunos mensajes se procesen fuera de orden.
Particiones
Para garantizar que los mensajes se entreguen a los consumidores y aprovechen la funcionalidad de distribución de carga de las particiones, podemos enviar mensajes de solicitud HTTP a una partición y mensajes de respuesta HTTP a una segunda partición. Esto garantiza una distribución de carga uniforme y puede garantizar que todas las solicitudes y todas las respuestas se consuman en orden. Es posible que se consuma una respuesta antes de la solicitud correspondiente, pero eso no es un problema porque tenemos un mecanismo diferente para correlacionar las solicitudes a las respuestas y sabemos que las solicitudes siempre vienen antes de las respuestas.
Cargas de HTTP
Después de construir el requestLine, compruebe si se debe truncar el cuerpo de la solicitud. El cuerpo de la solicitud se trunca a solo 1024. Esto podría aumentarse; Sin embargo, los mensajes individuales del centro de eventos están limitados a 256 KB, por lo que es probable que algunos cuerpos de mensajes HTTP no se ajusten a un solo mensaje. Al realizar el registro y el análisis, puede derivar una cantidad significativa de información de solo la línea de solicitud HTTP y los encabezados. Además, muchas solicitudes de API devuelven solo cuerpos pequeños, por lo que la pérdida de valor de la información debido al truncamiento de cuerpos grandes es bastante mínima en comparación con la reducción de los costos de almacenamiento, transferencia y procesamiento para mantener todo el contenido del cuerpo.
Una nota final sobre el procesamiento del cuerpo es que necesitamos pasar true al método As<string>(), ya que estamos leyendo el contenido del cuerpo, pero también queremos que la API de backend pueda leer el cuerpo. Al pasar true a este método, hacemos que el cuerpo se almacene en la búfer para que se pueda leer una segunda vez. Esto es importante si tiene una API que carga archivos grandes o usa sondeos largos. En estos casos, es mejor evitar leer el cuerpo por completo.
Encabezados HTTP
Los encabezados HTTP pueden transferirse al formato del mensaje con un formato simple de par clave/valor. Decidimos quitar determinados campos confidenciales de seguridad para evitar la pérdida innecesaria de información de credenciales. No es probable que se usen claves de API y otras credenciales para los análisis. Si deseamos analizar el usuario y el producto concreto que están usando, podríamos obtenerlo del context objeto y agregarlo al mensaje.
Metadatos del mensaje
Cuando se crea el mensaje completo para enviarlo al Centro de eventos, la primera línea no es realmente parte del mensaje application/http. La primera línea son metadatos adicionales que incluyen si el mensaje es un mensaje de solicitud o de respuesta y un identificador de mensaje que se usa para correlacionar las solicitudes y las respuestas. El identificador del mensaje se crea mediante otra directiva que tiene este aspecto:
<set-variable name="message-id" value="@(Guid.NewGuid())" />
Podríamos crear el mensaje de solicitud, almacenarlo en una variable hasta que se devolvió la respuesta y, a continuación, enviar la solicitud y la respuesta como un único mensaje. Sin embargo, mediante el envío de la solicitud y la respuesta de forma independiente y usando message-id para correlacionar los dos, obtenemos un poco más de flexibilidad en el tamaño del mensaje, la capacidad de aprovechar varias particiones manteniendo el orden del mensaje y una llegada más rápida de la solicitud en nuestro tablero de registro. También puede haber algunos escenarios en los que nunca se envía una respuesta válida al centro de eventos (posiblemente debido a un error de solicitud irrecuperable en el servicio API Management), pero todavía tenemos un registro de la solicitud.
La directiva para enviar el mensaje de respuesta HTTP es parecida a la solicitud; por tanto, la configuración de directiva completa tiene el siguiente aspecto:
<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>
La directiva set-variable crea un valor que es accesible por la directiva log-to-eventhub en la sección <inbound> y la sección <outbound>.
Recepción de eventos de Event Hubs
Se reciben eventos de Azure Event Hubs mediante el protocolo AMQP. El equipo de Service Bus de Microsoft hizo que las bibliotecas cliente disponibles para facilitar los eventos de consumo. Existen dos enfoques diferentes admitidos, uno se está un Consumidor directo y la otra es emplear la clase EventProcessorHost. Puede encontrar ejemplos de estos dos enfoques en el repositorio de ejemplos de Event Hubs. La versión corta de las diferencias: Direct Consumer le proporciona control completo, y EventProcessorHost hace algunos de los trabajos de fontanería para usted, pero realiza ciertas suposiciones sobre cómo procesar esos eventos.
EventProcessorHost
En este ejemplo, utilizamos EventProcessorHost para simplificar; sin embargo, es posible que no sea la mejor opción para este escenario en particular.
EventProcessorHost realiza el trabajo duro de asegurarse de que no tiene que preocuparse acerca de problemas de subprocesamiento dentro de una clase de procesador de eventos concreto. Sin embargo, en nuestro escenario, convertimos el mensaje a otro formato y lo pasamos a otro servicio mediante un método asincrónico. No es necesario actualizar el estado compartido y, por lo tanto, no hay riesgo de problemas relacionados con los hilos. Para la mayoría de los escenarios, EventProcessorHost es probablemente la mejor opción y es ciertamente la opción más fácil.
IEventProcessor
El concepto central al usar EventProcessorHost es crear una implementación de la interfaz IEventProcessor que contenga el método ProcessEventAsync. Esta es la esencia de ese método:
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 ...
}
Se pasa una lista de objetos EventData al método y realizamos la iteración en esa lista. Se analizan los bytes de cada método en un objeto HttpMessage y ese objeto se pasa a una instancia de IHttpMessageProcessor.
HttpMessage
La instancia HttpMessage contiene tres elementos de datos:
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 ...
}
La instancia HttpMessage contiene un GUID MessageId que nos permite conectar la solicitud de HTTP a la respuesta de HTTP correspondiente y un valor booleano que identifica si el objeto contiene una instancia de HttpRequestMessage y HttpResponseMessage. Mediante las clases HTTP integradas de System.Net.Http, puede aprovechar el código de análisis application/http que se incluye en System.Net.Http.Formatting.
IHttpMessageProcessor
La instancia HttpMessage se reenvía luego a la implementación de IHttpMessageProcessor, que es una interfaz que se creó para desacoplar la recepción y la interpretación del evento de Azure Event Hubs y el procesamiento real de esta.
Reenvío del mensaje HTTP
En este ejemplo, decidimos insertar la solicitud HTTP en Moesif API Analytics. Moesif es un servicio basado en la nube que se especializa en análisis y depuración HTTP. Tienen un nivel gratis, por lo que es fácil probar. Moesif nos permite ver las solicitudes HTTP en tiempo real que fluyen a través de nuestro servicio API Management.
Las implementación IHttpMessageProcessor tiene este aspecto:
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));
}
}
MoesifHttpMessageProcessor aprovecha las ventajas de una biblioteca de API en C# para Moesif que facilita la inserción de datos de eventos HTTP en su servicio. Para enviar datos HTTP a la API del recopilador de Moesif, necesita una cuenta y un identificador de aplicación. Para obtener un identificador de aplicación de Moesif, cree una cuenta en el sitio web de Moesif y, a continuación, vaya al menú superior derecho y seleccione Configuración de la aplicación.
Ejemplo completo
El código fuente y las pruebas del ejemplo se encuentran en GitHub. Necesita un servicio API Management, un centro de eventos conectado y una cuenta de almacenamiento para ejecutar el ejemplo por su cuenta.
El ejemplo es simplemente una aplicación de consola sencilla que escucha eventos provenientes de Event Hub, los convierte en objetos Moesif EventRequestModel y EventResponseModel, y luego los envía a la API del Collector de Moesif.
En la siguiente imagen animada, puede ver una solicitud realizada a una API en el Portal para desarrolladores, la aplicación consola que muestra el mensaje que se recibe, procesa y reenvía y, a continuación, la solicitud y la respuesta se muestran en la secuencia de eventos.
Resumen
El servicio Azure API Management proporciona un lugar ideal para capturar el tráfico HTTP hacia y desde la API. Azure Event Hubs es una solución altamente escalable y de bajo costo para capturar ese tráfico y colocarlo en sistemas de procesamiento secundario para registro, supervisión y otros análisis sofisticados. La conexión a sistemas de supervisión de tráfico de terceros como Moesif se reduce a unas cuantas docenas de líneas de código.
Contenido relacionado
- Obtenga más información acerca de Azure Event Hubs
- Obtener más información acerca de la integración de API Management y Event Hubs