Registrando chamadas do Spring WebClient

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.