目录
- 1. RestTemplate配置类
- 2. RestTemplateLog拦截器类
- 3. 使用
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.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.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 {
private int connectionValidateAfterInactivityMs = 10 * 1000;
private int maxTotalConnect = 50;
private int maxConnectPerRoute = 10;
private int connectTimeout = 20 * 1000;
private int readTimeout = 20 * 1000;
private int retryTimes = 2;
private int connectionRequestTimout = 200;
private static Map keepAliveTargetHost = Map.of("127.0.0.1", 20, "www.baidu.com", 30);
private int keepAliveTime = 10;
@Resource
private RestTemplateLog restTemplateLog;
@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));
// 设置长连接保持策略
httpClientBuilder.setKeepAliveStrategy(connectionKeepAliveStrategy);
httpClientBuilder
.setDefaultRequestConfig(RequestConfig.custom().setcookieSpec(cookieSpecs.IGNORE_cookieS).build());
return httpClientBuilder.build();
}
@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> 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 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> 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 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 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 paramMap = new linkedMultiValueMap<>();
paramMap.add("name", "Fisher3652");
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
HttpEntity> entity = new HttpEntity<>(paramMap, headers);
ResponseEntity 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*", 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"]