北京列举网 > 教育培训 > 电脑/网络 > 拉勾教育使用RestTemplate显示请求信息响应信息
北京
[切换城市]

拉勾教育使用RestTemplate显示请求信息响应信息

更新时间:2020-12-03 13:54:03 浏览次数:121次
区域: 北京 > 海淀 > 中关村
类别:软件工程师培训
地址:创业大街
使用RestTemplate,显示请求信息,响应信息
这里不讲怎么用RestTemplate具体细节用法,就是一个学习中的过程记录 拉勾IT课小编为大家分解
一个简单的例子
public class App {
  public static void main(String[] args) {
    String url = "https://api.***/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.***/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.***/hitokoto/get": stream is closed; nested exception is jav***.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.***/hitokoto/get": mark/reset not supported; nested exception is jav***.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 = Stream***pyToByteArray(this.clientHttpResponse.getBody());
        }
        return new ByteArrayInputStream(this.body == null ? new byte[0] : this.body);
    }

    @Override
    public HttpHeaders getHeaders() {
        return this.clientHttpResponse.getHeaders();
    }
  }
}
运行效果:

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

public class App {
  public static void main(String[] args) {
    String url = "https://api.***/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 = Stream***pyToByteArray(this.clientHttpResponse.getBody());
          }
          return new ByteArrayInputStream(this.body == null ? new byte[0] : this.body);
        }
        return m***voke(this.clientHttpResponse, args);
    }
  }
}
运行结果:

总结
•      使用RestTemplate想要显示详细请求信息,和响应信息
•      添加拦截器
•      拦截器中操作InputSteam导致流关闭,不能重复读Response body
•      尝试不关闭流,重置流的方案失败
•      使用代理解决 Response body 不能读第二次读的问题
o      静态代理(可以重复读Response body了)
o      动态代理(可以重复读Response body了)

北京电脑/网络相关信息
办公软件培训
平谷-平谷城区
9月2日
4月23日
办公软件培训
平谷-平谷城区
4月19日
办公软件培训
平谷-平谷城区
4月11日
办公软件培训
平谷-平谷城区
4月9日
注册时间:2020年08月20日
UID:706755
---------- 认证信息 ----------
手机已认证
查看用户主页