這裏不講怎麼用RestTemplate具體細節用法,就是一個學習中的過程記錄java
一個簡單的例子web
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); } }
運行結果:spring
❓:如今我想看看他的請求頭,請求參數,響應頭,響應體的詳細信息是怎麼樣子的,這樣也方便之後檢查請求參數是否完整,響應正確與否。api
通過蒐集資料發現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")); } }
運行結果:app
執行過程當中會報錯,具體錯誤信息是ide
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()
,ui
try (...) { } // 這個try塊結束後就把流給關了
註釋掉代碼中流操做相關代碼,再次運行沒有錯誤信息。因該是在攔截器後,RestTemplate也須要操做了response.getBody()
的流(廢話)。this
Response body 不能讀第二次這個很要命呀
問題找到了,初步的想到了幾種解決
close
流,讀取完以後再reset
流ClientHttpResponse
每次調用getBody
都返回一個新的輸入流// 略... 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
方法。很明顯了它不容許隨意修改讀取定位。沒辦法只轉爲第二種方法了。
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(); } } }
運行效果:
代碼運行沒問題,可是總感受代碼寫出來笨笨的,要重寫這麼多用不着的方法,看着不舒服,換個寫法。
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); } } }
運行結果: