• 使用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了)
    • 静态代理的代码有点啰嗦,动态代理又有点不够味
  • 相关阅读:
    [数字证书] 怎么打开windows的数字证书管理器
    [RF] 安装好Robot Framework之后怎样让启动的界面后面不带命令行窗口,且图片以机器人显示
    [RF]怎样用Robot Framework写好Test Case?
    iptables的疑问
    centos6.5安装jenkins文档部署全过程
    haproxy+keepalived以及haproxy的原理特点
    rhel6.5安装ansible
    客户端执行rsync出现的错误
    LVS_DR 安装后无法转发真实服务器,但是配置其他方面都检查的没有问题了。就剩在realserver这边没有在lo口上绑定VIP了
    架构设计:负载均衡层设计方案(1)——负载场景和解决方式
  • 原文地址:https://www.cnblogs.com/pi-laoban/p/14071279.html
Copyright © 2020-2023  润新知