背景
我在一次项目中,遇到和c++方的接口对接,但是发现对方的响应可能由于数据大小原因,其响应方式采用了分块响应的方式,及他的响应头设置为了 transfer-encoding=chunked的方式
我再用之前常规restTemplate调用http服务接口的方式,发现无论怎样都会拿不到响应体,但是在拦截器中确实又有响应数据,为什么呢?
经过简单的查看源码,发现针对chunked的这种响应,本来是针对浏览器的,在java端的处理会忽略掉content-length这一指标,而这个指标其实是restTemplate获取相应数据的依据,也就是说,默认的restTemplate配置会根据这个响应头参数来判断在你调用
responseEntity.getBody()时能否拿到响应
我的处理的话 思路是在拦截器中获取到响应体的大小,手动设置响应头content-length的长度,下面贴上部分代码:
public class RestClientChunkedInterceptor implements ClientHttpRequestInterceptor {
public static final String CHUNKED = "chunked";
@SuppressWarnings("NullableProblems")
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
ClientHttpResponse response = execution.execute(request, body);
if (needResetResponseContentLength(response)){
response = wrapperResponse(response);
}
return response;
}
private BufferingClientHttpResponseWrapper wrapperResponse(ClientHttpResponse response) throws IOException {
BufferingClientHttpResponseWrapper responseWrapper = new BufferingClientHttpResponseWrapper(response);
int available = responseWrapper.getBody().available();
response.getHeaders().setContentLength(available);
return responseWrapper;
}
/**
* 判断响应头是否包含transferEncoding=chunked 且contentType = application/octet-stream
* @param response rest请求的响应体
* @return true为重新设置content-length,false则原样返回
*/
private boolean needResetResponseContentLength(ClientHttpResponse response){
if (Objects.isNull(response)){
return false;
}
HttpHeaders headers = response.getHeaders();
List<String> transferEncodingList = headers.get("Transfer-Encoding");
if (CollectionUtils.isEmpty(transferEncodingList)){
return false;
}
MediaType contentType = headers.getContentType();
if (Objects.isNull(contentType)){
return false;
}
return transferEncodingList.contains(CHUNKED)
&& StringUtils.equals(MediaType.APPLICATION_OCTET_STREAM_VALUE, contentType.toString());
}
}
再写一个可以重复获取响应body的包装类
final class BufferingClientHttpResponseWrapper implements ClientHttpResponse {
private final ClientHttpResponse response;
private byte[] body;
BufferingClientHttpResponseWrapper(ClientHttpResponse response) {
this.response = response;
}
@Override
public HttpStatus getStatusCode() throws IOException {
return this.response.getStatusCode();
}
@Override
public int getRawStatusCode() throws IOException {
return this.response.getRawStatusCode();
}
@Override
public String getStatusText() throws IOException {
return this.response.getStatusText();
}
@Override
public HttpHeaders getHeaders() {
return this.response.getHeaders();
}
@Override
public InputStream getBody() throws IOException {
if (this.body == null) {
this.body = StreamUtils.copyToByteArray(this.response.getBody());
}
return new ByteArrayInputStream(this.body);
}
@Override
public void close() {
this.response.close();
}
}
这样子,在我们使用restTemplate时,就可以不关心这个类型的响应方式,也算一种临时解决方案
但是这里还有一个问题
当响应过大的时候会导致内存的溢出
针对这种情况,还有两种实现方式自己一直没有亲自去尝试
- 通过操作流的方式读取响应的数据
- 通过socket访问c++接口,解析数据
对以上方式,希望对看到这篇文章的你有所帮助,如果你有更好的解决方式,或思路,欢迎评论指教~