如何复制不支持标记的流?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
相关产品推荐
相关产品推荐

