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 {
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<String, Integer> 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();
PoolingHttpClientConnectionManager poolingHttpClientConnectionManager =
new PoolingHttpClientConnectionManager();
poolingHttpClientConnectionManager.setMaxTotal(maxTotalConnect);
poolingHttpClientConnectionManager.setDefaultMaxPerRoute(maxConnectPerRoute);
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);
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);
clientHttpRequestFactory.setReadTimeout(readTimeout);
clientHttpRequestFactory.setConnectionRequestTimeout(connectionRequestTimout);
return clientHttpRequestFactory;
}
@Bean
public ConnectionKeepAliveStrategy connectionKeepAliveStrategy() {
return (response, context) -> {
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);
}
}
}
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();
requestFactory.setOutputStreaming(false);
RestTemplate restTemplate = new RestTemplate(requestFactory);
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
interceptors.add(restTemplateLog);
restTemplate.setInterceptors(interceptors);
restTemplate.setRequestFactory(new BufferingClientHttpRequestFactory(clientHttpRequestFactory));
modifyDefaultCharset(restTemplate);
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);
}
}