Spring 6 WebClient HttpInterface日志拦截问题:无法获取请求响应体
Spring 6 WebClient HttpInterface 日志过滤器:完整记录请求/响应 Header 与 Body
针对你遇到的只能拦截Header、无法获取请求/响应Body的问题,核心原因是请求体的BodyInserter是函数式封装,直接读取会影响原请求发送;响应体读取后会被消费,导致后续业务无法获取。以下是完整的解决方案:
一、实现日志过滤器
通过ExchangeFilterFunction分别处理请求体和响应体的捕获,同时保证原请求/响应流程不受影响:
import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferUtils; import org.springframework.http.HttpMethod; import org.springframework.http.client.reactive.ClientHttpRequest; import org.springframework.http.client.reactive.ClientHttpRequestDecorator; import org.springframework.http.client.reactive.ExchangeFilterFunction; import org.springframework.web.reactive.function.BodyInserter; import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.client.ClientResponse; import reactor.core.publisher.Mono; import java.nio.charset.StandardCharsets; import java.util.function.Supplier; public class WebClientLoggingFilter { public static ExchangeFilterFunction logRequestResponse() { // 拦截请求,捕获请求体 ExchangeFilterFunction requestFilter = ExchangeFilterFunction.ofRequestProcessor(clientRequest -> { return captureRequestBody(clientRequest) .doOnNext(request -> { // 按需打印请求日志,可替换为SLF4J等日志框架 System.out.println("=== 请求详情 ==="); System.out.println("请求方法: " + request.method()); System.out.println("请求URI: " + request.url()); System.out.println("请求Headers: " + request.headers()); System.out.println("请求Body: " + request.attributes().get("requestBody")); }); }); // 拦截响应,捕获响应体 ExchangeFilterFunction responseFilter = ExchangeFilterFunction.ofResponseProcessor(clientResponse -> { return captureResponseBody(clientResponse) .doOnNext(response -> { // 按需打印响应日志,可替换为SLF4J等日志框架 System.out.println("=== 响应详情 ==="); System.out.println("响应状态码: " + response.statusCode()); System.out.println("响应Headers: " + response.headers().asHttpHeaders()); System.out.println("响应Body: " + response.attributes().get("responseBody")); }); }); return requestFilter.andThen(responseFilter); } private static Mono<ClientRequest> captureRequestBody(ClientRequest clientRequest) { // GET/DELETE方法通常无请求体,直接返回 if (HttpMethod.GET.equals(clientRequest.method()) || HttpMethod.DELETE.equals(clientRequest.method())) { return Mono.just(clientRequest); } // 缓存请求体内容到请求属性中 Supplier<Mono<String>> bodySupplier = () -> { BodyInserter<?, ? super ClientHttpRequest> body = clientRequest.body(); DataBuffer buffer = clientRequest.exchangeStrategies().bufferFactory().allocateBuffer(); return BodyInserters.fromPublisher(body.insert(new ClientHttpRequestDecorator(clientRequest.createRequest(null)) { @Override public Mono<Void> writeWith(Publisher<? extends DataBuffer> bodyPublisher) { return DataBufferUtils.join(bodyPublisher) .doOnNext(dataBuffer -> { byte[] bytes = new byte[dataBuffer.readableByteCount()]; dataBuffer.read(bytes); DataBufferUtils.release(dataBuffer); String bodyStr = new String(bytes, StandardCharsets.UTF_8); clientRequest.attributes().put("requestBody", bodyStr); }) .then(Mono.empty()); } }, clientRequest.exchangeStrategies()), String.class); }; return bodySupplier.get().thenReturn(clientRequest); } private static Mono<ClientResponse> captureResponseBody(ClientResponse clientResponse) { return clientResponse.bodyToMono(String.class) .flatMap(bodyStr -> { // 重新构建响应体,保证后续业务能正常读取 ClientResponse mutatedResponse = clientResponse.mutate() .body(BodyInserters.fromValue(bodyStr)) .build(); // 将响应体存入响应属性用于日志 mutatedResponse.attributes().put("responseBody", bodyStr); return Mono.just(mutatedResponse); }); } }
二、配置WebClient与HttpInterface
将过滤器添加到WebClient中,再通过HttpServiceProxyFactory生成HttpInterface代理:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.reactive.function.client.support.WebClientAdapter; import org.springframework.web.service.invoker.HttpServiceProxyFactory; @Configuration public class WebClientConfig { @Bean public WebClient webClient() { return WebClient.builder() .baseUrl("http://your-target-api-base-url") // 替换为实际API地址 .filter(WebClientLoggingFilter.logRequestResponse()) // 注入日志过滤器 .build(); } @Bean public YourHttpInterface yourHttpInterface(WebClient webClient) { HttpServiceProxyFactory proxyFactory = HttpServiceProxyFactory.builder(WebClientAdapter.forClient(webClient)).build(); return proxyFactory.createClient(YourHttpInterface.class); } }
关键说明
- 请求体捕获:通过装饰
ClientHttpRequest,在写入请求体时读取DataBuffer内容并缓存到请求属性中,不会干扰原请求的发送流程。 - 响应体捕获:读取响应体后,必须通过
clientResponse.mutate()重新构建响应体,否则后续业务逻辑会因响应体已被消费而无法获取数据。 - 适配场景:如果涉及二进制响应体(如文件下载),需修改
bodyToMono的类型(如byte[].class),避免转成String出现乱码;大请求/响应体可添加开关控制是否打印,防止内存溢出。
内容的提问来源于stack exchange,提问作者viglu
相关产品推荐
相关产品推荐

