Introducción
En este tutorial, vamos a mostrar cómo personalizar el WebClient de Spring, un cliente HTTP reactivo, para registrar tanto las solicitudes como las respuestas. La capacidad de registrar detalles de las solicitudes y respuestas es crucial en el desarrollo de aplicaciones, ya que permite depurar errores y entender mejor el flujo de datos. El WebClient es una herramienta poderosa en el ecosistema de Spring y su personalización puede enriquecer nuestras aplicaciones, así como facilitar la identificación de problemas de red.
1. Overview
En este tutorial, vamos a mostrar cómo personalizar Spring’s WebClient – un cliente HTTP reactivo – para registrar solicitudes y respuestas. Seremos claros y concisos, y le proporcionaremos ejemplos de código para ilustrar cada concepto.
2. WebClient
WebClient es una interfaz reactiva y no bloqueante para realizar solicitudes HTTP, basada en Spring WebFlux. Tiene una API funcional, fluida y utiliza tipos reactivos para la composición declarativa.
Detrás de escena, WebClient llama a un cliente HTTP. Reactor Netty es el cliente HTTP reactivo por defecto, también se admite el cliente HTTP de Jetty. Además, es posible conectar implementaciones adicionales del cliente HTTP configurando un ClientConnector
para WebClient.
3. Logging Requests and Responses
El cliente HTTP por defecto utilizado por WebClient es la implementación de Netty, por lo que, después de cambiar el nivel de registro de reactor.netty.http.client
a DEBUG
, podemos ver algo de registro de solicitudes, pero si necesitamos un registro más personalizado, podemos configurar nuestros registradores a través de WebClient#filters:
WebClient
.builder()
.filters(exchangeFilterFunctions -> {
exchangeFilterFunctions.add(logRequest());
exchangeFilterFunctions.add(logResponse());
})
.build();
En este fragmento de código, hemos añadido dos filtros separados para registrar la solicitud y la respuesta.
### Implementando logRequest
Implementemos logRequest
utilizando ExchangeFilterFunction#ofRequestProcessor
:
ExchangeFilterFunction logRequest() {
return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
if (log.isDebugEnabled()) {
StringBuilder sb = new StringBuilder("Request: \n");
clientRequest.headers().forEach((name, values) -> values.forEach(value -> sb.append(name).append(": ").append(value).append("\n")));
log.debug(sb.toString());
}
return Mono.just(clientRequest);
});
}
### Implementando logResponse
El proceso para logResponse
es similar, pero utilizaremos ExchangeFilterFunction#ofResponseProcessor
en su lugar.
ExchangeFilterFunction logResponse() {
return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
if (log.isDebugEnabled()) {
StringBuilder sb = new StringBuilder("Response: \n");
sb.append("Status: ").append(clientResponse.statusCode().toString()).append("\n");
clientResponse.headers().asHttpHeaders().forEach((name, values) -> values.forEach(value -> sb.append(name).append(": ").append(value).append("\n")));
log.debug(sb.toString());
}
return Mono.just(clientResponse);
});
}
Ahora podemos cambiar el nivel de registro de reactor.netty.http.client
a INFO
o ERROR
para tener una salida más limpia.
4. Logging Request and Response with Body
Los clientes HTTP tienen características para registrar los cuerpos de las solicitudes y respuestas. Por lo tanto, para lograr el objetivo, vamos a utilizar un cliente HTTP habilitado para registro con nuestro WebClient.
Podemos hacer esto configurando manualmente WebClient.Builder#clientConnector
. Veamos cómo hacerlo con el cliente HTTP de Jetty y Netty.
4.1. Logging with Jetty HttpClient
Primero, agreguemos la dependencia Maven para el *jetty-reactive-httpclient* a nuestro pom.xml:
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-reactive-httpclient</artifactId>
<version>1.1.6</version>
</dependency>
Luego, crearemos un cliente HTTP de Jetty personalizado:
SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
HttpClient httpClient = new HttpClient(sslContextFactory) {
@Override
public Request newRequest(URI uri) {
Request request = super.newRequest(uri);
return enhance(request);
}
};
Aquí, hemos anulado HttpClient#newRequest
, luego envuelto el Request
en un mejorador de registro.
A continuación, necesitamos registrar eventos con la solicitud para que podamos registrar cada parte de la solicitud cuando esté disponible:
Request enhance(Request request) {
StringBuilder group = new StringBuilder();
request.onRequestBegin(theRequest -> {
group.append("Request URI: ").append(theRequest.getURI()).append("\n");
group.append("Method: ").append(theRequest.getMethod()).append("\n");
});
request.onRequestHeaders(theRequest -> {
for (HttpField header : theRequest.getHeaders()) {
group.append(header.getName()).append(": ").append(header.getValue()).append("\n");
}
});
request.onRequestContent((theRequest, content) -> {
group.append("Content: ").append(content.toString()).append("\n");
});
request.onRequestSuccess(theRequest -> {
log.debug(group.toString());
group.setLength(0);
});
return request;
}
Finalmente, debemos construir la instancia de WebClient:
WebClient
.builder()
.clientConnector(new JettyClientHttpConnector(httpClient))
.build();
4.2. Logging with Netty HttpClient
Ahora, creemos un cliente HTTP de Netty:
HttpClient httpClient = HttpClient
.create()
.wiretap(true);
Habiendo habilitado el wiretap, cada solicitud y respuesta se registrará con detalle completo.
A continuación, tenemos que establecer el nivel de registro del paquete del cliente de Netty reactor.netty.http.client
a DEBUG
:
logging.level.reactor.netty.http.client=DEBUG
Ahora, construyamos el WebClient:
WebClient
.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build();
Nuestro WebClient registrará cada solicitud y respuesta en detalle completo. Sin embargo, el formato por defecto del registrador interno de Netty contiene tanto la representación en Hex como la representación en texto de los cuerpos, así como muchos datos sobre los eventos de solicitud y respuesta.
Para obtener solo el registrador de texto para Netty, podemos configurar el HttpClient
:
HttpClient httpClient = HttpClient
.create()
.wiretap("reactor.netty.http.client.HttpClient",
LogLevel.DEBUG, AdvancedByteBufFormat.TEXTUAL);
5. Conclusión
En este tutorial, hemos utilizado varias técnicas para registrar datos de solicitudes y respuestas mientras utilizamos Spring WebClient. Hemos aprendido cómo personalizar el cliente HTTP Reactivo de Spring para capturar y registrar información detallada, lo que es esencial para una aplicación robusta y transparentemente rastreable.
### Consejos prácticos:
- Usa niveles de log ajustados: Ajusta los niveles de log según el entorno (desarrollo, producción) para evitar la recopilación de información innecesaria.
- Filtros personalizados: Mantén los filtros para las solicitudes y respuestas personalizados y reutilizables para diversas partes de tu aplicación.
- Implementa trazabilidad: Considera integrar características adicionales, como rastreadores de solicitudes, para seguir el rastro a través de microservicios.
- Revisión periódica: Revisa y ajusta la lógica de registro según se detecten cuellos de botella o problemas en la aplicación.
Al seguir estos consejos y aprender a personalizar el WebClient, podrás optimizar el rendimiento de tus aplicaciones y facilitar la depuración y el mantenimiento a largo plazo. ¡Feliz codificación!