zoukankan      html  css  js  c++  java
  • 使用RestTemplate,显示请求信息,响应信息

    使用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);
        }
    }
    

    运行结果:

    image-20201130123152314

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

    经过搜集资料发现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('
    ');
                    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 "	" + entry.getKey() + ":" + (values.size() == 1 ?
                                """ + values.get(0) + """ :
                                values.stream().map(s -> """ + s + """).collect(Collectors.joining(", ")));
                    })
                    .collect(Collectors.joining(", 
    ", "
    [
    ", "
    ]
    "));
        }
    }
    

    运行结果:

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

    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('
    ');
        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('
    ');
                    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();
            }
        }
    }
    

    运行效果:

    image-20201130132734043

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

    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('
    ');
                    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);
            }
        }
    }
    

    运行结果:

    image-20201130140437927

    总结

    • 使用RestTemplate想要显示详细请求信息,和响应信息
    • 添加拦截器
    • 拦截器中操作InputSteam导致流关闭,不能重复读Response body
    • 尝试不关闭流,重置流的方案失败
    • 使用代理解决 Response body 不能读第二次读的问题
      • 静态代理(可以重复读Response body了)
      • 动态代理(可以重复读Response body了)
    • 静态代理的代码有点啰嗦,动态代理又有点不够味
  • 相关阅读:
    C#
    C#
    ssh学习笔记
    (已解决)Could not open '/var/lib/nova/mnt/*/volume-*': Permission denied
    RPCVersionCapError: Requested message version, 4.17 is incompatible. It needs to be equal in major version and less than or equal in minor version as the specified version cap 4.11.
    如何在linux下安装idea
    The system has no LUN copy license
    调整mysql数据库最大连接数
    mysql数据库编码问题
    cinder支持nfs快照
  • 原文地址:https://www.cnblogs.com/pi-laoban/p/14071279.html
Copyright © 2011-2022 走看看