使用spring webclient对http请求进行可读的调试日志记录

时间:2017-12-01 15:29:45

标签: spring webclient project-reactor spring-webflux reactor-netty

我使用Spring反应式WebClient向http服务器发送请求。为了查看基本请求&amp;发送的响应,我启用了 Species rank_Sepal_Length Sepal.Length <fctr> <dbl> <dbl> 1 setosa 3 5.1 2 setosa 2 4.9 3 setosa 1 4.7 4 versicolor 3 7.0 5 versicolor 1 6.4 6 versicolor 2 6.9 7 virginica 2 6.3 8 virginica 1 5.8 9 virginica 3 7.1 包的调试日志记录。

可以正常查看传出请求的标头。

我发送&amp;通过http接收纯文本,该日志包含请求&amp;以下格式的回复(是十六进制?)

我不确定如何以易于理解的方式查看记录的数据。更好的是记录请求&amp;以可理解的方式回应

以下是记录数据的片段

reactor.ipc.netty

由于存在相同的库,我们发现了一个无法回答的问题:Reading a HttpContent that has a PooledUnsafeDirectByteBuf

提出问题here

似乎有一种正统观点认为无功客户端不需要调试。这是一个毫无意义的论点,因为我们使用 +-------------------------------------------------+ | 0 1 2 3 4 5 6 7 8 9 a b c d e f | +--------+-------------------------------------------------+----------------+ |00000000| 47 45 54 20 2f 53 65 61 72 63 68 5f 47 43 2e 61 |GET /Search_GC.a| |00000010| 73 70 78 20 48 54 54 50 2f 31 2e 31 0d 0a 75 73 |spx HTTP/1.1..us| |00000020| 65 72 2d 61 67 65 6e 74 3a 20 52 65 61 63 74 6f |er-agent: Reacto| |00000030| 72 4e 65 74 74 79 2f 30 2e 37 2e 32 2e 52 45 4c |rNetty/0.7.2.REL| |00000040| 45 41 53 45 0d 0a 68 6f 73 74 3a 20 63 65 6f 6b |EASE..host: ceok| |00000050| 61 72 6e 61 74 61 6b 61 2e 6b 61 72 2e 6e 69 63 |arnataka.kar.nic| |00000060| 2e 69 6e 0d 0a 61 63 63 65 70 74 3a 20 2a 2f 2a |.in..accept: */*| |00000070| 0d 0a 61 63 63 65 70 74 2d 65 6e 63 6f 64 69 6e |..accept-encodin| |00000080| 67 3a 20 67 7a 69 70 0d 0a 63 6f 6e 74 65 6e 74 |g: gzip..content| |00000090| 2d 6c 65 6e 67 74 68 3a 20 30 0d 0a 0d 0a |-length: 0.... | +--------+-------------------------------------------------+----------------+ rest clientpostmancurl等工具。其他人发送请求和查看回复

4 个答案:

答案 0 :(得分:1)

他们更改了 reactor.netty.http.client.HttpClient 类,在我升级到 io.projectreactor.netty:reactor-netty-http:1.0.5 之后,以下代码是可编译的并且可以执行您的期望。 (我不确定哪个是 最小 版本,我从旧版本升级,但我猜它是 1.0.0。它是一个传递依赖项,我从 {{} 升级了 spring-boot-starter-webflux 1}} 到 2.3.4.RELEASE。)

关键部分是wiretap()的调用:

2.4.4

它还记录请求和响应的标头和正文。

整个上下文是这样的:

wiretap("reactor.netty.http.client.HttpClient", LogLevel.DEBUG, AdvancedByteBufFormat.TEXTUAL, StandardCharsets.UTF_8)

当您要求时,记录的输出是人类可读的:

package com.example;

import io.netty.handler.logging.LogLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import reactor.netty.http.client.HttpClient;
import reactor.netty.transport.logging.AdvancedByteBufFormat;

import java.nio.charset.StandardCharsets;

@Slf4j
class RestClientTest {
    private WebClient createWebClient() {
        final HttpClient httpClient = HttpClient.create()
                .wiretap(HttpClient.class.getCanonicalName(), LogLevel.DEBUG, AdvancedByteBufFormat.TEXTUAL, StandardCharsets.UTF_8);
        return WebClient.builder()
                .clientConnector(new ReactorClientHttpConnector(httpClient))
                .build();
    }

    @Data
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    private static class User {
        int id;
        int userId;
        String title;
        String body;
    }

    @Test
    void createUsersReactive() {
        final WebClient webClient = createWebClient();
        final String url = "http://jsonplaceholder.typicode.com/posts";
        final Mono<User> userMono = webClient.method(HttpMethod.POST)
                .uri(url)
                .contentType(MediaType.APPLICATION_JSON_UTF8)
                .header("X-My-Header", "MyValue1", "MyValue2")
                .body(BodyInserters.fromValue(User.builder().userId(1).title("foo").body("bar").build()))
                .retrieve()
                .bodyToMono(User.class);
        final User user = userMono.block();
        log.info("Created user: " + user);
    }
}

答案 1 :(得分:0)

如果您将DataBuffer用作读取器,则可以使用doOnNext()来做到这一点:

public Mono<ServerResponse> selectByPost(ServerRequest request) {
  Flux<DataBuffer> requestBodyFlux = request.bodyToFlux(DataBuffer.class)
    .doOnNext(dataBuffer -> {
      if (debug ) {
        log.debug(new String(dataBuffer.asByteBuffer().array()));
      }
      Scannable.from(dataBuffer).tags().forEach(System.out::println);
    });
}

这可能不是最好的方法,如果netty提供了记录有效负载的不同方法,那么这当然是一个不错的功能。十六进制确实有其优点,这取决于您需要调试什么。

答案 2 :(得分:-1)

这是一个非常主观的问题。

你没有发现这种格式可读/有用,但我认为相反的原因有几个。十六进制在这里非常有用,因为HTTP可能很棘手:

  • 在日志(字符编码等)中读取特殊字符并不总是很容易
  • 服务器/客户端可以以不同方式解释/解析标头
  • 在没有
  • 的情况下涉及HTTP控制字符时,很难找到问题

这一切归结为真正了解通过网络发送/接收的内容,这通常是您在查看特定问题时应该看到的内容。

但我同意这一详细程度不应该是第一个也是唯一可用于调试的信息。应该有一个中间级别,您可以在不查看原始十六进制数据的情况下获得有关HTTP交换的基础知识。

有关详情,请点击the dedicated issue on Reactor Netty

答案 3 :(得分:-3)

似乎响应服务器正在返回gzip压缩内容,因此您无法阅读它。

如果确实想要在原始HTTP级别进行拦截,请确保您的请求标头未指定它可以接受GZipped内容(accept-encoding: gzip)。

另一种选择可能是将请求记录在另一层,当它已经从原始数据流解压缩但尚未由您的应用程序代码处理时 - 不确定这在Reactive webclient中如何工作; )