RestTemplate详细配置及日志打印

1. RestTemplate配置类

  • 包括连接池,超时时间,拦截器,异常处理,字符集,response多次读取
  • 使用httpclient进行配置,引入依赖包
implementation group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5.10'
package com.example.handlerinterceptor.config;

import com.example.handlerinterceptor.interceptor.RestTemplateLog;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HeaderElement;
import org.apache.http.HeaderElementIterator;
import org.apache.http.HttpHost;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeaderElementIterator;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.BufferingClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

@Slf4j
@Configuration
public class RestTemplateConfig {

    /**
     * 官方推荐使用检查永久链接的可用性,而不推荐每次请求的时候才去检查 (milliseconds 毫秒) 
     */
    private int connectionValidateAfterInactivityMs = 10 * 1000;

    /**
     * 连接池的最大连接数
     */
    private int maxTotalConnect = 50;

    /**
     * 同路由的并发数 每个路由的最大连接数,如果只调用一个地址,可以将其设置为最大连接数
     */
    private int maxConnectPerRoute = 10;

    /**
     * 客户端和服务器建立连接超时,默认15s 指客户端和服务器建立连接的超时时间,ms , 最大约21秒,因为内部tcp在进行三次握手建立连接时,默认tcp超时时间是20秒
     */
    private int connectTimeout = 20 * 1000;

    /**
     * 指客户端从服务器读取数据包的间隔超时时间,不是总读取时间,默认20s
     */
    private int readTimeout = 20 * 1000;

    /**
     * 重试次数,默认2次
     */
    private int retryTimes = 2;

    /**
     * 从连接池获取连接的超时时间,不宜过长,单位ms
     */
    private int connectionRequestTimout = 200;

    /**
     * 针对不同的地址,特别设置不同的长连接保持时间,单位s,如果是频繁而持续的请求,可以设置小一点,不建议设置过大,避免大量无用连接占用内存资源
     */
    private static Map<String, Integer> keepAliveTargetHost = Map.of("127.0.0.1", 20, "www.baidu.com", 30);

    /**
     * 长连接保持时间 单位s,不宜过长
     */
    private int keepAliveTime = 10;

    @Resource
    private RestTemplateLog restTemplateLog;

    /**
     * 设置httpclient参数
     */
    @Bean
    public HttpClient httpClient(ConnectionKeepAliveStrategy connectionKeepAliveStrategy) {
        HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
        // 使用Httpclient连接池的方式配置(推荐),同时支持netty,okHttp以及其他http框架
        PoolingHttpClientConnectionManager poolingHttpClientConnectionManager =
            new PoolingHttpClientConnectionManager();
        // 最大连接数
        poolingHttpClientConnectionManager.setMaxTotal(maxTotalConnect);
        // 同路由并发数
        poolingHttpClientConnectionManager.setDefaultMaxPerRoute(maxConnectPerRoute);
        // 官方推荐使用检查永久链接的可用性,而不推荐每次请求的时候才去检查 (milliseconds 毫秒)
        poolingHttpClientConnectionManager.setValidateAfterInactivity(connectionValidateAfterInactivityMs);

        // 配置连接池
        httpClientBuilder.setConnectionManager(poolingHttpClientConnectionManager);

		// 重试次数
        httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(retryTimes, true, new ArrayList<>()) {
            @Override
            public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
                return super.retryRequest(exception, executionCount, context);
            }
        });

        // 设置长连接保持策略
        httpClientBuilder.setKeepAliveStrategy(connectionKeepAliveStrategy);

        /**
         * 处理Invalid cookie header和Cookie rejected错误,配置Cookie策略为ignoreCookies
         * cookieSpec:即cookie策略。参数为cookiespecs的一些字段。作用: 1、如果网站header中有set-cookie字段时,采用默认方式可能会被cookie
         * reject,无法写入cookie。将此属性设置成CookieSpecs.STANDARD_STRICT可避免此情况。
         * 2、如果要想忽略cookie访问,则将此属性设置成CookieSpecs.IGNORE_COOKIES。
         */
        httpClientBuilder
            .setDefaultRequestConfig(RequestConfig.custom().setCookieSpec(CookieSpecs.IGNORE_COOKIES).build());
        return httpClientBuilder.build();
    }

    /**
     * 创建HTTP客户端工厂
     */
    @Bean
    public ClientHttpRequestFactory clientHttpRequestFactory(HttpClient httpClient) {
        HttpComponentsClientHttpRequestFactory clientHttpRequestFactory =
            new HttpComponentsClientHttpRequestFactory(httpClient);
        // 连接超时
        clientHttpRequestFactory.setConnectTimeout(connectTimeout);
        // 数据读取超时时间,即SocketTimeout
        clientHttpRequestFactory.setReadTimeout(readTimeout);
        // 从连接池获取请求连接的超时时间,不宜过长,必须设置,比如连接不够用时,时间过长将是灾难性的
        clientHttpRequestFactory.setConnectionRequestTimeout(connectionRequestTimout);
        return clientHttpRequestFactory;
    }

    /**
     * 设置长连接保持策略
     */
    @Bean
    public ConnectionKeepAliveStrategy connectionKeepAliveStrategy() {
        return (response, context) -> {
            // response的header中存在keep-alive字段使用timeout指定的时间
            HeaderElementIterator it = new BasicHeaderElementIterator(response.headerIterator(HTTP.CONN_KEEP_ALIVE));
            while (it.hasNext()) {
                HeaderElement he = it.nextElement();
                String param = he.getName();
                String value = he.getValue();
                if (value != null && "timeout".equalsIgnoreCase(param)) {
                    try {
                        return Long.parseLong(value) * 1000;
                    } catch (NumberFormatException ignore) {
                        log.error("解析长连接过期时间异常 {}", ignore);
                    }
                }
            }
            // response没有指定keep-alive时长,使用配置的时长,如果不在配置的ip,使用默认时长
            HttpHost target = (HttpHost)context.getAttribute(HttpClientContext.HTTP_TARGET_HOST);
            // 如果请求目标地址,单独配置了长连接保持时间,使用该配置
            Optional<Map.Entry<String, Integer>> any = Optional.ofNullable(keepAliveTargetHost).orElseGet(HashMap::new)
                .entrySet().stream().filter(e -> e.getKey().equalsIgnoreCase(target.getHostName())).findAny();
            // 否则使用默认长连接保持时间
            Long aLong = any.map(en -> en.getValue() * 1000L).orElse(keepAliveTime * 1000L);
            return aLong;
        };
    }

    @Bean
    public RestTemplate restTemplate(ClientHttpRequestFactory clientHttpRequestFactory) {
        SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
        // 解决401报错时,报java.net.HttpRetryException: cannot retry due to server authentication, in streaming mode
        requestFactory.setOutputStreaming(false);
        RestTemplate restTemplate = new RestTemplate(requestFactory);

        // 添加拦截器
        List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
        interceptors.add(restTemplateLog);
        restTemplate.setInterceptors(interceptors);

        // 提供对传出/传入流的缓冲,可以让响应body多次读取(如果不配置,拦截器读取了Response流,再响应数据时会返回body=null)
        restTemplate.setRequestFactory(new BufferingClientHttpRequestFactory(clientHttpRequestFactory));

        // 我们采用RestTemplate内部的MessageConverter
        // 重新设置StringHttpMessageConverter字符集为UTF-8,解决中文乱码问题
        modifyDefaultCharset(restTemplate);

        // 请求失败异常处理,如果不重写hasError方法,抛出异常,无法执行后续代码
        restTemplate.setErrorHandler(new DefaultResponseErrorHandler() {
            @Override
            public boolean hasError(ClientHttpResponse response) {
                return false;
            }
        });
        return restTemplate;
    }

    private void modifyDefaultCharset(RestTemplate restTemplate) {
        List<HttpMessageConverter<?>> converterList = restTemplate.getMessageConverters();
        HttpMessageConverter<?> converterTarget = null;
        for (HttpMessageConverter<?> item : converterList) {
            if (StringHttpMessageConverter.class == item.getClass()) {
                converterTarget = item;
                break;
            }
        }
        if (null != converterTarget) {
            converterList.remove(converterTarget);
        }
        Charset defaultCharset = StandardCharsets.UTF_8;
        converterList.add(1, new StringHttpMessageConverter(defaultCharset));
    }

}

2. RestTemplateLog拦截器类

  • 打印请求参数和返回结果
package com.example.handlerinterceptor.interceptor;

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

@Slf4j
@Component
public class RestTemplateLog implements ClientHttpRequestInterceptor {

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
        throws IOException {
        traceRequest(request, body);
        ClientHttpResponse response = execution.execute(request, body);
        traceResponse(response);
        return response;
    }

    private void traceRequest(HttpRequest request, byte[] body) {
        Map<String, String> map = new HashMap<>(16);
        map.put("requestUri", request.getURI().getPath());
        map.put("headers", String.valueOf(request.getHeaders()));
        map.put("parameter", new String(body, StandardCharsets.UTF_8));
        log.info("request: {}", map);
    }

    private void traceResponse(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();
            }
        }
        Map<String, Object> map = new HashMap<>(16);
        map.put("httpCode", response.getStatusCode().value());
        map.put("responseBody", String.valueOf(inputStringBuilder));
        log.info("response: {}", map);
    }

}

3. 使用

package com.example.handlerinterceptor.sysuser.controller;

import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;

@RestController
public class Test {
    @Resource
    private RestTemplate restTemplate;

    @GetMapping("/test")
    public void test() {
        MultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<>();
        paramMap.add("name", "Fisher3652");
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        HttpEntity<MultiValueMap<String, Object>> entity = new HttpEntity<>(paramMap, headers);
        ResponseEntity<String> responseEntity =
                restTemplate.postForEntity("http://127.0.0.1:8081/test", entity, String.class);
        HttpHeaders responseHeaders = responseEntity.getHeaders();
        System.out.println(responseEntity.getBody());
        System.out.println(responseHeaders);
    }

}

  • 打印结果
2021-12-08 16:31:19.985  INFO 6043 --- [-nio-80-exec-10] c.e.h.interceptor.RestTemplateLog        : request: {headers=[Accept:"text/plain, application/json, application/*+json, */*", Content-Type:"application/x-www-form-urlencoded;charset=UTF-8", Content-Length:"15"], parameter=name=Fisher3652, requestUri=/test}
2021-12-08 16:31:19.990  INFO 6043 --- [-nio-80-exec-10] c.e.h.interceptor.RestTemplateLog        : response: {responseBody={"code":0,"msg":"Success","data":"Hello Fisher3652"}
, httpCode=200}
{"code":0,"msg":"Success","data":"Hello Fisher3652"}
[Content-Type:"application/json", Transfer-Encoding:"chunked", Date:"Wed, 08 Dec 2021 08:31:19 GMT", Keep-Alive:"timeout=60", Connection:"keep-alive"]

在这里插入图片描述

4. httpclient5

  • 如果使用httpclient5和jdk21,参考下面的写法
    // http-client
    implementation 'org.apache.httpcomponents.client5:httpclient5:5.2.3'
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;

import org.apache.hc.client5.http.config.ConnectionConfig;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.DefaultHttpRequestRetryStrategy;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.HttpResponse;
import org.apache.hc.core5.http.protocol.HttpContext;
import org.apache.hc.core5.util.TimeValue;
import org.apache.hc.core5.util.Timeout;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.BufferingClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.RestTemplate;

import jakarta.annotation.Resource;

@Configuration
public class RestTemplateConfig {

   private static final int CLOSED_IDLE_TIME = 15;

   private static int CONNECT_TIMEOUT = 5 * 1000;

   private static int READ_TIMEOUT = 6 * 1000;

   private static int connectionRequestTimeout = 500;
   @Resource
   ClientHttpRequestInterceptor restTemplateInterceptor;

   @Bean
   public RestTemplate restTemplate() {
       RestTemplate restTemplate = new RestTemplate();
       restTemplate.setRequestFactory(new BufferingClientHttpRequestFactory(clientHttpRequestFactory()));
       restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
       restTemplate.setInterceptors(Arrays.asList(restTemplateInterceptor));
       restTemplate.setErrorHandler(new DefaultResponseErrorHandler() {
           @Override
           public boolean hasError(ClientHttpResponse response) throws IOException {
               return false;
           }
       });
       return restTemplate;
   }

   private ClientHttpRequestFactory clientHttpRequestFactory() {
       PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
       connectionManager.setMaxTotal(50);
       connectionManager.setDefaultMaxPerRoute(25);

       ConnectionConfig defaultConnectionConfig = ConnectionConfig.custom().setSocketTimeout(Timeout.ofSeconds(3))
           .setConnectTimeout(Timeout.ofMilliseconds(CONNECT_TIMEOUT)).build();
       connectionManager.setConnectionConfigResolver(httpRoute -> defaultConnectionConfig);

       connectionManager.closeExpired();
       connectionManager.closeIdle(TimeValue.ofSeconds(CLOSED_IDLE_TIME));

       CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(connectionManager)
           .setDefaultRequestConfig(RequestConfig.custom().setCookieSpec("ignoreCookies")
               .setRedirectsEnabled(Boolean.FALSE).setResponseTimeout(READ_TIMEOUT, TimeUnit.MILLISECONDS)
               .setConnectionRequestTimeout(connectionRequestTimeout, TimeUnit.MILLISECONDS).build())
           .setRetryStrategy(new DefaultHttpRequestRetryStrategy(3, TimeValue.ofMilliseconds(0)) {
               @Override
               public boolean retryRequest(HttpRequest request, IOException exception, int execCount,
                   HttpContext context) {
                   return super.retryRequest(request, exception, execCount, context);
               }

               @Override
               public boolean retryRequest(HttpResponse response, int execCount, HttpContext context) {
                   return super.retryRequest(response, execCount, context);
               }
           }).build();
       return new HttpComponentsClientHttpRequestFactory(httpClient);
   }

}

  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot RestTemplate 是一个 HTTP 客户端库,用于向 RESTful API 发送请求和接收响应。它可以与大多数 HTTP 请求和响应库一起使用,如 Apache HttpClient、OkHttp 等。 以下是 Spring Boot RestTemplate配置和使用步骤: 1. 引入 RestTemplate 依赖 在 Spring Boot 项目的 pom.xml 文件中添加以下依赖: ``` <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web-services</artifactId> </dependency> ``` 这些依赖包含了 RestTemplate 所需的所有类和配置。 2. 创建 RestTemplate Bean 在 Spring Boot 中,可以使用 @Bean 注解创建 RestTemplate Bean。在创建 RestTemplate Bean 时,可以配置一些属性,如连接超时时间、读取超时时间等。 以下是一个基本的 RestTemplate 配置: ``` @Configuration public class RestTemplateConfig { @Bean public RestTemplate restTemplate() { return new RestTemplateBuilder() .setConnectTimeout(Duration.ofSeconds(10)) .setReadTimeout(Duration.ofSeconds(10)) .build(); } } ``` 3. 发送 GET 请求 使用 RestTemplate 发送 GET 请求的示例代码如下: ``` @Autowired private RestTemplate restTemplate; public void sendGetRequest() { String url = "https://jsonplaceholder.typicode.com/posts/1"; ResponseEntity<String> response = restTemplate.getForEntity(url, String.class); System.out.println(response.getBody()); } ``` 在这个示例中,我们使用 restTemplate.getForEntity() 方法发送一个 GET 请求,并将响应映射为一个 String 类型的实体。然后,我们打印出响应体。 4. 发送 POST 请求 使用 RestTemplate 发送 POST 请求的示例代码如下: ``` @Autowired private RestTemplate restTemplate; public void sendPostRequest() { String url = "https://jsonplaceholder.typicode.com/posts"; HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); String requestBody = "{\"title\": \"foo\", \"body\": \"bar\", \"userId\": 1}"; HttpEntity<String> requestEntity = new HttpEntity<>(requestBody, headers); ResponseEntity<String> response = restTemplate.postForEntity(url, requestEntity, String.class); System.out.println(response.getBody()); } ``` 在这个示例中,我们使用 restTemplate.postForEntity() 方法发送一个 POST 请求,并将请求体设置为一个 JSON 字符串。然后,我们打印出响应体。 总结: 以上就是 Spring Boot RestTemplate配置和使用步骤。使用 RestTemplate 可以方便地向 RESTful API 发送请求和接收响应。在使用 RestTemplate 时,可以根据需要配置一些属性,如超时时间等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值