You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

实现ExchangeFilterFunction时如何捕获ClientRequest的请求体

捕获WebClient请求体的简化方案

直接通过读取ClientRequest的BodyInserter并重新包装请求的方式,无需额外类即可实现请求体的捕获,修改后的filter方法如下:

import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.client.reactive.ClientRequest;
import org.springframework.http.client.reactive.ClientResponse;
import org.springframework.http.client.reactive.ExchangeFilterFunction;
import org.springframework.http.client.reactive.ExchangeFunction;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.web.reactive.function.BodyInserters;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;

@Override
public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
    // 读取请求体内容,处理无请求体的情况(如GET请求)
    Mono<String> requestBodyMono = request.body()
            .insert(new MockServerHttpRequest(request.method(), request.url()), new DefaultDataBufferFactory())
            .map(dataBuffer -> {
                byte[] bytes = new byte[dataBuffer.readableByteCount()];
                dataBuffer.read(bytes);
                DataBufferUtils.release(dataBuffer);
                return new String(bytes, StandardCharsets.UTF_8);
            })
            .defaultIfEmpty("");

    return requestBodyMono.flatMap(requestBody -> {
        // 创建新请求,重新设置请求体(原BodyInserter已被消费,无法重复使用)
        ClientRequest newRequest = ClientRequest.from(request)
                .body(BodyInserters.fromValue(requestBody))
                .build();

        // 继续执行请求并处理响应日志
        return next.exchange(newRequest)
                .flatMap(response -> response.bodyToMono(String.class)
                        .flatMap(responseBody -> {
                            ClientResponse clientResponse = ClientResponse.create(response.statusCode())
                                    .headers(httpResponse -> httpResponse.addAll(response.headers().asHttpHeaders()))
                                    .body(responseBody)
                                    .build();

                            log.atInfo()
                                    .addKeyValue("requestHeaders", request.headers())
                                    .addKeyValue("responseHeaders", response.headers().asHttpHeaders())
                                    .addKeyValue("requestBody", requestBody)
                                    .addKeyValue("responseBody", parseResponseBody(responseBody))
                                    .log();

                            return Mono.just(clientResponse);
                        })
                );
    });
}

private Object parseResponseBody(String json) {
    try {
        return objectMapper.readValue(json, Object.class);
    } catch (Exception e) {
        log.error("Failed to parse JSON", e);
        return json;
    }
}

关键逻辑说明

  • 读取请求体:通过request.body().insert(...)触发BodyInserter的执行,借助MockServerHttpRequest模拟请求上下文,将请求体的DataBuffer转换为字符串。
  • 重新包装请求:由于BodyInserter只能被消费一次,读取后必须创建新的ClientRequest并重新设置请求体,确保后续请求能正常发送。
  • 兼容无请求体场景:用defaultIfEmpty("")处理GET等无请求体的请求,避免日志出现空值。

注意事项

  • 字符集默认使用UTF-8,如果请求体使用其他编码,可从request.headers().getContentType()中提取charset进行转换。
  • 针对大文件等超大请求体,建议添加截断逻辑,避免内存占用过高。

内容的提问来源于stack exchange,提问作者DonutQuan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 20:35:14