使用RestTemplate,显示请求信息,响应信息

这里不讲怎么用RestTemplate具体细节用法,就是一个学习中的过程记录

一个简单的例子

public class App {
public static void main(String[] args) {
String url = "https://api.uixsj.cn/hitokoto/get";
RestTemplate restTemplate = new RestTemplate();
String body = restTemplate.getForObject(url, String.class);
System.out.println(body);
}
}

运行结果:

:现在我想看看他的请求头,请求参数,响应头,响应体的详细信息是怎么样子的,这样也方便以后检查请求参数是否完整,响应正确与否。

经过搜集资料发现ClientHttpRequestInterceptor满足需求,于是就有了下面的代码

打印请求头/响应头

public class App {
public static void main(String[] args) {
String url = "https://api.uixsj.cn/hitokoto/get";
RestTemplate restTemplate = new RestTemplate();
// 加上拦截器打印将请求请求,响应信息打印出来
restTemplate.setInterceptors(Collections.singletonList(new LoggingInterceptor()));
String body = restTemplate.getForObject(url, String.class);
System.out.println(body);
}
} @Slf4j
class LoggingInterceptor implements ClientHttpRequestInterceptor { @Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
displayRequest(request, body);
ClientHttpResponse response = execution.execute(request, body);
displayResponse(response);
return response;
} /**
* 显示请求相关信息
* @param request
* @param body
*/
private void displayRequest(HttpRequest request, byte[] body) {
log.debug("====request info====");
log.debug("URI : {}", request.getURI());
log.debug("Method : {}", request.getMethod());
log.debug("Req Headers : {}", this.headersToString(request.getHeaders()));
log.debug("Request body: {}", body == null ? "" : new String(body, StandardCharsets.UTF_8));
} /**
* 显示响应相关信息
* @param response
* @throws IOException
*/
private void displayResponse(ClientHttpResponse response) throws IOException {
StringBuilder inputStringBuilder = new StringBuilder();
try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(response.getBody(), StandardCharsets.UTF_8))) {
String line = bufferedReader.readLine();
while (line != null) {
inputStringBuilder.append(line);
inputStringBuilder.append('\n');
line = bufferedReader.readLine();
}
}
log.debug("====response info====");
log.debug("Status code : {}", response.getStatusCode());
log.debug("Status text : {}", response.getStatusText());
log.debug("Resp Headers : {}", headersToString(response.getHeaders()));
log.debug("Response body: {}", inputStringBuilder.toString());
} /**
* 将Http头信息格式化处理
* @param httpHeaders
* @return
*/
private String headersToString(HttpHeaders httpHeaders) {
if (Objects.isNull(httpHeaders)) {
return "[]";
}
return httpHeaders.entrySet().stream()
.map(entry -> {
List<String> values = entry.getValue();
return "\t" + entry.getKey() + ":" + (values.size() == 1 ?
"\"" + values.get(0) + "\"" :
values.stream().map(s -> "\"" + s + "\"").collect(Collectors.joining(", ")));
})
.collect(Collectors.joining(", \n", "\n[\n", "\n]\n"));
}
}

运行结果:

执行过程中会报错,具体错误信息是

Exception in thread "main" org.springframework.web.client.ResourceAccessException: I/O error on GET request for "https://api.uixsj.cn/hitokoto/get": stream is closed; nested exception is java.io.IOException: stream is closed

这里报错信息是流已关闭,报错是在添加LoggingInterceptor后出现的,那就是新加代码引起的。在看看LoggingInterceptor的实现,什么地方操作了流,并且关闭了流。

LoggingInterceptor.displayResponse这个方法里面,为了读取响应体操作了流response.getBody()

try (...) {
}
// 这个try块结束后就把流给关了

注释掉代码中流操作相关代码,再次运行没有错误信息。因该是在拦截器后,RestTemplate也需要操作了response.getBody()的流(废话)。

Response body 不能读第二次这个很要命呀

问题找到了,初步的想到了几种解决

  1. 改写代码,不close流,读取完之后再reset
  2. 代理一下ClientHttpResponse每次调用getBody都返回一个新的输入流

解决不能重复读Response body

方法一:读取完后不关闭流

// 略...
InputStream responseBody = response.getBody();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(responseBody, StandardCharsets.UTF_8));
String line = bufferedReader.readLine();
while (line != null) {
inputStringBuilder.append(line);
inputStringBuilder.append('\n');
line = bufferedReader.readLine();
}
responseBody.reset();
// 略...

很遗憾,执行后依旧有错误

Exception in thread "main" org.springframework.web.client.ResourceAccessException: I/O error on GET request for "https://api.uixsj.cn/hitokoto/get": mark/reset not supported; nested exception is java.io.IOException: mark/reset not supported

说的很清楚,不支持mark/reset方法。很明显了它不允许随意修改读取定位。没办法只转为第二种方法了。

方法二:代理,每次都返回一个新的流

  1. 静态代理实现ClientHttpResponse接口,好在ClientHttpResponse实现的接口数量不多,实现的代码如下。
@Slf4j
class LoggingInterceptor implements ClientHttpRequestInterceptor { @Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
displayRequest(request, body);
ClientHttpResponse response = execution.execute(request, body);
// 包装代理一下
response = new ClientHttpResponseWrapper(response);
displayResponse(response);
return response;
} /**
* 显示请求相关信息
* @param request
* @param body
*/
private void displayRequest(HttpRequest request, byte[] body) {
// 略...
} /**
* 显示响应相关信息
* @param response
* @throws IOException
*/
private void displayResponse(ClientHttpResponse response) throws IOException {
StringBuilder inputStringBuilder = new StringBuilder();
try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(response.getBody(), StandardCharsets.UTF_8))) {
String line = bufferedReader.readLine();
while (line != null) {
inputStringBuilder.append(line);
inputStringBuilder.append('\n');
line = bufferedReader.readLine();
}
}
// 略...
} /**
* 将Http头信息格式化处理
* @param httpHeaders
* @return
*/
private String headersToString(HttpHeaders httpHeaders) {
// 略...
} private class ClientHttpResponseWrapper implements ClientHttpResponse {
private ClientHttpResponse clientHttpResponse;
private byte[] body; public ClientHttpResponseWrapper(ClientHttpResponse clientHttpResponse) {
this.clientHttpResponse = clientHttpResponse;
} @Override
public HttpStatus getStatusCode() throws IOException {
return this.clientHttpResponse.getStatusCode();
} @Override
public int getRawStatusCode() throws IOException {
return this.clientHttpResponse.getRawStatusCode();
} @Override
public String getStatusText() throws IOException {
return this.clientHttpResponse.getStatusText();
} @Override
public void close() {
this.clientHttpResponse.close();
} /**
* 缓存body每次返回一个新的输入流
* @return
* @throws IOException
*/
@Override
public InputStream getBody() throws IOException {
if (Objects.isNull(this.body)) {
this.body = StreamUtils.copyToByteArray(this.clientHttpResponse.getBody());
}
return new ByteArrayInputStream(this.body == null ? new byte[0] : this.body);
} @Override
public HttpHeaders getHeaders() {
return this.clientHttpResponse.getHeaders();
}
}
}

运行效果:

代码运行没问题,但是总感觉代码写出来笨笨的,要重写这么多用不着的方法,看着不舒服,换个写法。

  1. 动态代理

public class App {
public static void main(String[] args) {
String url = "https://api.uixsj.cn/hitokoto/get";
RestTemplate restTemplate = new RestTemplate();
restTemplate.setInterceptors(Collections.singletonList(new LoggingInterceptor()));
String body = restTemplate.getForObject(url, String.class);
System.out.println(body);
} } @Slf4j
class LoggingInterceptor implements ClientHttpRequestInterceptor { @Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
displayRequest(request, body);
ClientHttpResponse response = execution.execute(request, body);
// 包装代理一下
response = (ClientHttpResponse) Proxy.newProxyInstance(response.getClass().getClassLoader(), new Class[]{ClientHttpResponse.class}, new ClientHttpResponseHandler(response));
displayResponse(response);
return response;
} /**
* 显示请求相关信息
* @param request
* @param body
*/
private void displayRequest(HttpRequest request, byte[] body) {
// 略......
} /**
* 显示响应相关信息
* @param response
* @throws IOException
*/
private void displayResponse(ClientHttpResponse response) throws IOException {
StringBuilder inputStringBuilder = new StringBuilder();
try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(response.getBody(), StandardCharsets.UTF_8))) {
String line = bufferedReader.readLine();
while (line != null) {
inputStringBuilder.append(line);
inputStringBuilder.append('\n');
line = bufferedReader.readLine();
}
}
// 略......
} /**
* 将Http头信息格式化处理
* @param httpHeaders
* @return
*/
private String headersToString(HttpHeaders httpHeaders) {
// 略......
} private static class ClientHttpResponseHandler implements InvocationHandler {
private static final String methodName = "getBody";
private ClientHttpResponse clientHttpResponse;
private byte[] body; ClientHttpResponseHandler(ClientHttpResponse clientHttpResponse) {
this.clientHttpResponse = clientHttpResponse;
} @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (StringUtils.equals(methodName, method.getName())) {
if (Objects.isNull(this.body)) {
this.body = StreamUtils.copyToByteArray(this.clientHttpResponse.getBody());
}
return new ByteArrayInputStream(this.body == null ? new byte[0] : this.body);
}
return method.invoke(this.clientHttpResponse, args);
}
}
}

运行结果:

总结

  • 使用RestTemplate想要显示详细请求信息,和响应信息
  • 添加拦截器
  • 拦截器中操作InputSteam导致流关闭,不能重复读Response body
  • 尝试不关闭流,重置流的方案失败
  • 使用代理解决 Response body 不能读第二次读的问题
    • 静态代理(可以重复读Response body了)
    • 动态代理(可以重复读Response body了)
  • 静态代理的代码有点啰嗦,动态代理又有点不够味,看来『茴』字不好写呀

最新文章

  1. CIDR详解和ip最长地址前缀匹配
  2. Entity Framework 实体框架的形成之旅--界面操作的几个典型的处理(8)
  3. 调用WebService 实现在线双向翻译
  4. :first // :last
  5. xiaocms 关于搜索功能 添加搜索字段
  6. undefined index : HTTP_RAW_POST_DATA
  7. linux服务端的网络编程
  8. poj2242
  9. php单例设计模式
  10. c# 官方命名规则
  11. SysLog简介和java操作实例
  12. iOS切圆角的几个方法
  13. 网络安全实验室_注入关writeup
  14. Hystrix 熔断机制原理
  15. Hadoop化繁为简(三)—探索Mapreduce简要原理与实践
  16. 【Linux_Unix系统编程】Chapter4 文件IO
  17. 2018.09.16 bzoj1176: [Balkan2007]Mokia(cdq分治)
  18. 分析Ethernet标准和Ieee802.3标准规定的MAC层帧结构
  19. Unity3D — —存读档【转载】
  20. file.getPath() getAbsolutePath() getCanonicalPath()区别

热门文章

  1. Python爬虫之线程池
  2. 关于H5页面在微信浏览器中音视频播放的问题
  3. Inception系列之Batch-Normalization
  4. Spark架构与原理这一篇就够了
  5. 容器场景要选择什么 Linux 版本?
  6. 学习笔记——make项目中克隆GitHub目录失败的解决
  7. 使用webhook watch pod
  8. Android 架构组件-Lifecycle、LiveData、ViewModel
  9. centos6 安装 terminator
  10. 聊一聊Token