Как распечатать необработанный HTTP-запрос и HTTP-ответ с помощью Spring 5 Webclient?

Spring MVC позволяет вести журнал тела запроса и ответа, чтобы упростить отладку и проверку содержимого сообщения. Это необходимо для моего проекта в целях аудита, сообщения журнала ДОЛЖНЫ содержать полное тело запроса и ответа.

Как с помощью Spring Web Reactive и Webclient регистрировать тело запроса и ответа без шестнадцатеричных значений?

Требуемый формат - запрос RAW HTTP, Ex.

PUT /api/v1/target/{id}

HTTP/1.1
Host: https://testsite.com:8080
Authorization: Bearer myToken
Content-Type: application/json

{
  "test": "test
}

В настоящее время другие ответы предоставляют только приблизительный вывод hex + msg с использованием ведения журнала отладки Reactor Netty ИЛИ выводят только заголовки HTTP для запроса. Бывший. текущей реализации:

         +-------------------------------------------------+
     |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 48 54 54 50 2f 31 2e 31 20 32 30 30 20 4f 4b 0d |HTTP/1.1 200 OK.|
|00000010| 0a 53 65 72 76 65 72 3a 20 6e 67 69 6e 78 2f 31 |.Server: nginx/1|
|00000020| 2e 31 36 2e 31 0d 0a 44 61 74 65 3a 20 4d 6f 6e |.16.1..Date: Mon|
|00000030| 2c 20 30 35 20 4f 63 74 20 32 30 32 30 20 31 33 |, 05 Oct 2020 13|
|00000040| 3a 35 39 3a 33 36 20 47 4d 54 0d 0a 43 6f 6e 74 |:59:36 GMT..Cont|
|00000050| 65 6e 74 2d 54 79 70 65 3a 20 61 70 70 6c 69 63 |ent-Type: applic|
|00000060| 61 74 69 6f 6e 2f 6a 73 6f 6e 3b 20 63 68 61 72 |ation/json; char|
|00000070| 73 65 74 3d 75 74 66 2d 38 0d 0a 43 6f 6e 74 65 |set=utf-8..Conte|
|00000080| 6e 74 2d 4c 65 6e 67 74 68 3a 20 31 30 37 38 0d |nt-Length: 1078.|
|00000090| 0a 43 6f 6e 6e 65 63 74 69 6f 6e 3a 20 6b 65 65 |.Connection: kee|
|000000a0| 70 2d 61 6c 69 76 65 0d 0a 58 2d 50 6f 77 65 72 |p-alive..----|
.....
.....

Это гораздо труднее читать, а аудиторам копировать и вставлять.

Есть ли способ изменить этот формат? Как просто распечатать тело запроса.

Я согласен с нарушением рекомендации по неблокированию в Spring Webflux. RestTemplate указан как устаревший, что означает, что Webclient будет использоваться для блокировки операций. Аудит для этого проекта важнее производительности.


person StevenPG    schedule 05.10.2020    source источник
comment
пробовали ли вы этот baeldung.com/spring-log-webclient-calls   -  person Toerktumlare    schedule 06.10.2020
comment
Моя текущая реализация использует 4.2. Я пробовал использовать 4.1 в прошлом, но не смог заставить его работать. Я собираюсь снова попробовать 4.1 и добавить к вопросу, если я выясню, почему он не работал раньше.   -  person StevenPG    schedule 06.10.2020


Ответы (1)


Отвечаю здесь после тестирования всех опций во включенных комментариях.

Чтобы вытащить соответствующие зависимости

# Gradle    
implementation group: 'org.eclipse.jetty', name: 'jetty-reactive-httpclient', version: '1.1.4'

# Maven
<dependency>
    <groupId>org.eclipse.jetty</groupId>
    <artifactId>jetty-reactive-httpclient</artifactId>
    <version>1.1.4</version>
</dependency>

Создайте метод, который будет выполняться во время потока веб-клиента. Используется в определении веб-клиента:

// org.eclipse.jetty.client.api.Request
private Request enhance(Request inboundRequest) {
    StringBuilder log = new StringBuilder();
    // Request Logging
    inboundRequest.onRequestBegin(request ->
            log.append("Request: \n")
            .append("URI: ")
            .append(request.getURI())
            .append("\n")
            .append("Method: ")
            .append(request.getMethod()));
    inboundRequest.onRequestHeaders(request -> {
        log.append("\nHeaders:\n");
        for (HttpField header : request.getHeaders()) {
            log.append("\t\t" + header.getName() + " : " + header.getValue() + "\n");
        }
    });
    inboundRequest.onRequestContent((request, content) ->
            log.append("Body: \n\t")
            .append(content.toString()));
    log.append("\n");

    // Response Logging
    inboundRequest.onResponseBegin(response ->
            log.append("Response:\n")
            .append("Status: ")
            .append(response.getStatus())
            .append("\n"));
    inboundRequest.onResponseHeaders(response -> {
       log.append("Headers:\n");
       for (HttpField header : response.getHeaders()) {
           log.append("\t\t" + header.getName() + " : " + header.getValue() + "\n");
       }
    });
    inboundRequest.onResponseContent(((response, content) -> {
        var bufferAsString = StandardCharsets.UTF_8.decode(content).toString();
        log.append("Response Body:\n" + bufferAsString);
    }));

    // Add actual log invocation
    logger.info("HTTP ->\n");
    inboundRequest.onRequestSuccess(request -> logger.info(log.toString()));
    inboundRequest.onResponseSuccess(response -> logger.info(log.toString()));

    // Return original request
    return inboundRequest;
}

Определите веб-клиент, используя следующий метод (включая пример определения веб-клиента как bean-компонента, который будет внедряться по мере необходимости):

@Bean
public WebClient jettyHttpClient() {
    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);
        }
    };
    return WebClient.builder().clientConnector(new JettyClientHttpConnector(httpClient)).build();
}
person StevenPG    schedule 13.10.2020