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

如何复制不支持标记的流?RestTemplate拦截器重复读响应体

问题:如何在RestTemplate拦截器与客户端代码中同时读取响应体?

我尝试构建一个集成拦截器(用于记录出入站请求)的HTTP客户端,但遇到了问题:在拦截器中读取响应体后,客户端代码再读取就会返回null——因为响应体流不支持标记功能,只能被读取一次。

客户端代码

...
RestTemplateBuilder rtb = new RestTemplateBuilder();    
RestTemplate rt = rtb.build();
HttpHeaders hh = new HttpHeaders();
HttpEntity<String> he = new HttpEntity<>("123", hh);
rt.setInterceptors(Collections.singletonList(new MyInterceptor()));
ResponseEntity<String> re = rt.exchange("my.url", HttpMethod.POST, he, String.class);
// 此处需要获取响应体,但返回null
System.out.println(re.getBody());   // 为null - 因为响应体流已在拦截器中被读取
....

拦截器代码

public class MyInterceptor implements ClientHttpRequestInterceptor
{
  @Override
  public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException
  {                    
    // ...
    ClientHttpResponse response = execution.execute(request, body);

    String respbody = new String(response.getBody().readAllBytes());
    // 此处可以正常读取响应体,但客户端第二次读取时内容为空/为null
    return response;
  }
}

解决方案

核心思路是包装原始的ClientHttpResponse,将响应体流缓存为可重复读取的字节数组,让拦截器和后续客户端代码都能读取完整的响应体。具体实现分两步:

1. 实现可重复读取的ClientHttpResponse包装类

创建包装类,把原始响应的流读取到字节数组中,后续每次调用getBody()都返回基于该字节数组的新输入流:

import org.springframework.http.client.ClientHttpResponse;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;

public class RepeatableClientHttpResponse implements ClientHttpResponse {

    private final ClientHttpResponse originalResponse;
    private byte[] responseBody;

    public RepeatableClientHttpResponse(ClientHttpResponse originalResponse) throws IOException {
        this.originalResponse = originalResponse;
        // 读取原始响应体到字节数组缓存
        this.responseBody = originalResponse.getBody().readAllBytes();
    }

    @Override
    public InputStream getBody() throws IOException {
        // 每次返回新的字节数组输入流,支持重复读取
        return new ByteArrayInputStream(responseBody);
    }

    // 其他方法直接委托给原始响应
    @Override
    public HttpHeaders getHeaders() {
        return originalResponse.getHeaders();
    }

    @Override
    public int getRawStatusCode() throws IOException {
        return originalResponse.getRawStatusCode();
    }

    @Override
    public String getStatusText() throws IOException {
        return originalResponse.getStatusText();
    }

    @Override
    public void close() {
        originalResponse.close();
    }
}

2. 修改拦截器代码

在拦截器中用包装类替换原始响应,确保拦截器读取后,客户端代码仍能重新读取:

public class MyInterceptor implements ClientHttpRequestInterceptor
{
  @Override
  public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException
  {                    
    // ...
    ClientHttpResponse originalResponse = execution.execute(request, body);
    // 包装原始响应,实现可重复读取
    RepeatableClientHttpResponse repeatableResponse = new RepeatableClientHttpResponse(originalResponse);

    // 拦截器中读取响应体(可正常处理)
    String respbody = new String(repeatableResponse.getBody().readAllBytes());
    // 此处可执行日志记录等业务操作

    return repeatableResponse;
  }
}

修改完成后,客户端代码中的re.getBody()就能正常获取响应体了——包装类每次都会返回新的输入流,不会因之前被读取过而失效。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 18:35:07