Registrando chamadas do Spring WebClient
1. Visão geral
Neste tutorial, vamos mostrar como personalizarSpring’s WebClient - um cliente HTTP reativo - para registrar solicitações e respostas.
2. WebClient
WebClient é uma interface reativa e sem bloqueio para solicitações HTTP, com base emSpring WebFlux. Possui uma API funcional e fluente com tipos reativos para composição declarativa.
Nos bastidores,WebClient chama um cliente HTTP. Reactor Netty é o padrão eHttpClient reativo do Jetty também é suportado. Além disso, é possível conectar outras implementações de cliente HTTP configurando umClientConnector paraWebClient.
3. Solicitações e respostas de log
OHttpClient padrão usado porWebClient é a implementação Netty, entãoafter we change the reactor.netty.http.client logging level to DEBUG, podemos ver alguns logs de solicitação, mas se precisarmos de um log personalizado, podemos configurare nossos loggers viaWebClient#filters:
WebClient
.builder()
.filters(exchangeFilterFunctions -> {
exchangeFilterFunctions.add(logRequest());
exchangeFilterFunctions.add(logResponse());
})
.build()
Neste snippet de código, adicionamos dois filtros separados para registrar a solicitação e a resposta.
Vamos implementarlogRequest usandoExchangeFilterFunction#ofRequestProcessor:
ExchangeFilterFunction logRequest() {
return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
if (log.isDebugEnabled()) {
StringBuilder sb = new StringBuilder("Request: \n");
//append clientRequest method and url
clientRequest
.headers()
.forEach((name, values) -> values.forEach(value -> /* append header key/value */));
log.debug(sb.toString());
}
return Mono.just(clientRequest);
});
}
logResponse é o mesmo,but we have to use ExchangeFilterFunction#ofResponseProcessor instead.
Agora podemos alterar o nível de logreactor.netty.http.client paraINFO ouERROR para ter uma saída mais limpa.
4. Solicitação de log e resposta com o corpo
Os clientes HTTP têm recursos para registrar os corpos de solicitações e respostas. Assim,to achieve the goal, we are going to use a log-enabled HTTP client with our WebClient.
Podemos fazer isso configurando manualmenteWebClient.Builder#clientConnector – let’s see com os clientes HTTP Jetty e Netty.
4.1. Registrando com JettyHttpClient
Primeiro, vamos adicionar a dependência Maven parajetty-reactive-httpclient ao nosso pom:
org.eclipse.jetty
jetty-reactive-httpclient
1.0.3
Então, vamos criar um JettyHttpClient 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);
}
};
Aqui, substituímosHttpClient#newRequest e envolvemosRequest em um intensificador de log.
Em seguida, precisamos registrar eventos com a solicitação para podermos registrar à medida que cada parte da solicitação estiver disponível:
Request enhance(Request request) {
StringBuilder group = new StringBuilder();
request.onRequestBegin(theRequest -> {
// append request url and method to group
});
request.onRequestHeaders(theRequest -> {
for (HttpField header : theRequest.getHeaders()) {
// append request headers to group
}
});
request.onRequestContent((theRequest, content) -> {
// append content to group
});
request.onRequestSuccess(theRequest -> {
log.debug(group.toString());
group.delete(0, group.length());
});
group.append("\n");
request.onResponseBegin(theResponse -> {
// append response status to group
});
request.onResponseHeaders(theResponse -> {
for (HttpField header : theResponse.getHeaders()) {
// append response headers to group
}
});
request.onResponseContent((theResponse, content) -> {
// append content to group
});
request.onResponseSuccess(theResponse -> {
log.debug(group.toString());
});
return request;
}
Finalmente, temos que construir a instânciaWebClient:
WebClient
.builder()
.clientConnector(new JettyClientHttpConnector(httpClient))
.build()
Claro, como fizemos antes, precisaremos definir o nível de log deRequestLogEnhancer paraDEBUG.
4.2. Registrando com NettyHttpClient
Primeiro, vamos criar um NettyHttpClient:
HttpClient httpClient = HttpClient
.create()
.wiretap(true)
Depois de ativar a escuta telefônica, cada solicitação e resposta serão registradas com todos os detalhes.
Em seguida, temos que definir o nível de registro do pacote do cliente Nettyreactor.netty.http.client paraDEBUG:
logging.level.reactor.netty.http.client=DEBUG
Agora, vamos construir oWebClient:
WebClient
.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build()
NossoWebClient registrará cada solicitação e resposta em detalhes completos,but the default format of Netty built-in logger contains both Hex and Text representation of bodies e também muitos dados sobre eventos de solicitação e resposta.
Portanto, se precisarmos de um logger personalizado para Netty, podemos configurar oHttpClient:
HttpClient httpClient = HttpClient
.create()
.tcpConfiguration(
tc -> tc.bootstrap(
b -> BootstrapHandlers.updateLogSupport(b, new CustomLogger(HttpClient.class))))
.build()
Por último, vamos implementar nossoCustomLogger que estendeLoggingHandler:
public class CustomLogger extends LoggingHandler {
public CustomLogger(Class> clazz) {
super(clazz);
}
@Override
protected String format(ChannelHandlerContext ctx, String event, Object arg) {
if (arg instanceof ByteBuf) {
ByteBuf msg = (ByteBuf) arg;
return decode(
msg, msg.readerIndex(), msg.readableBytes(), defaultCharset());
}
return super.format(ctx, event, arg);
}
// further code omitted for brevity
}
5. Conclusão
Neste tutorial, usamos várias técnicas para registrar dados de solicitação e resposta ao usar SpringWebClient.
Como sempre, o código está disponívelover on GitHub.