还是老规矩,话不多说,直接上代码:
依赖:
<dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.7</version> </dependency> <!--为了使用@ConfigurationProperties,还需要这个依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> <version>${springboot.version}</version> </dependency>
yml文件:
spring:
restTemplate: #客户端和服务端建立连接的超时时间,这里最大只能是20s,因为linux操作系统的tcp进行三次握手的默认超时时间是20s,,即便设置100s也是在20s后报错(毫秒) connectTimeout: 5_000 #即socketTime,数据响应读取超时时间,指的是两个相邻的数据包的间隔超时时间(毫秒) readTimeout: 120_000 #连接不够用时从连接池获取连接的等待时间,必须设置。不宜过长,连接不够用时,等待时间过长将是灾难性的(毫秒) connectionRequestTimeout: 200 #RestTemplate默认依赖jdk的HTTP连接工具,也支持使用Httpclient、OkHttp和Netty httpclient: #httpclient连接池 最大tcp连接数 0代表不限;如果取0,需要考虑连接泄露导致系统崩溃的后果 maxTotal: 2000 #设置每一个路由的最大连接数,这里的路由是指IP+PORT,例如连接池大小(MaxTotal)设置为2000,路由连接数设置为1500(DefaultMaxPerRoute), #对于www.a.com与www.b.com两个路由来说,发起服务的主机连接到每个路由的最大连接数(并发数)不能超过1500,两个路由的总连接数不能超过2000。 maxConnectPerRoute: 1000 #是否允许失败重试 enableRetry: true #连接IO异常时重试次数,默认为3次 retryTimes: 3 #长连接保持时间, http1.1都是默认开启长连接的,如果不配置这个时间,连接池会默认永久保持连接.如果是不频繁访问的服务端,长期保持一个无用的连接也会大大占用资源 #对于需要频繁调用的服务端,我们可以开启长连接,然后将这个保持时间设置小一些,能保证相邻两次请求的长连接都还在就可以了.(毫秒) keepAliveTime: 10_000
# 针对不同的请求地址,可以单独设置不同的长连接存活时间 keepAliveTargetHosts: "{'127.0.0.1': 5000, '127.0.0.2': 3000}"
配置类:
import org.apache.commons.io.IOUtils; 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.protocol.HttpClientContext; import org.apache.http.config.Registry; import org.apache.http.config.RegistryBuilder; import org.apache.http.conn.ConnectionKeepAliveStrategy; import org.apache.http.conn.socket.ConnectionSocketFactory; import org.apache.http.conn.socket.PlainConnectionSocketFactory; import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; 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.ssl.SSLContexts; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpRequest; import org.springframework.http.client.*; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.RestTemplate; import javax.net.ssl.SSLContext; import java.io.*; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.security.KeyManagementException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.cert.X509Certificate; import java.util.*; /** * @version v1.0 * @description: RestTemplate配置类 * @author: 47 */ @Configuration public class RestTemplateConfig { private final static Logger logger = LoggerFactory.getLogger(RestTemplateConfig.class); //==============Resttemplate相关参数==================== @Value("${spring.restTemplate.readTimeout}") private int readTimeout; @Value("${spring.restTemplate.connectTimeout}") private int connectTimeout; @Value("${spring.restTemplate.connectionRequestTimeout}") private int connectionRequestTimeout; //==============Httpclient连接池相关参数==================== @Value("${spring.restTemplate.httpclient.maxTotal}") private int maxTotal; @Value("${spring.restTemplate.httpclient.maxConnectPerRoute}") private int maxConnectPerRoute; @Value("${spring.restTemplate.httpclient.enableRetry}") private boolean enableRetry; @Value("${spring.restTemplate.httpclient.retryTimes}") private int retryTimes; @Value("#{${spring.restTemplate.httpclient.keepAliveTargetHosts}}") private Map<String, Long> keepAliveTargetHosts; @Value("${spring.restTemplate.httpclient.keepAliveTime}") private long keepAliveTime; /** * 配置RestTemplate * * @param clientHttpRequestFactory * @return */ @Bean public RestTemplate restTemplate(ClientHttpRequestFactory clientHttpRequestFactory) { RestTemplate restTemplate = new RestTemplate(clientHttpRequestFactory); //默认使用ISO-8859-1编码,此处修改为UTF-8 List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters(); Iterator<HttpMessageConverter<?>> iterator = messageConverters.iterator(); while (iterator.hasNext()) { HttpMessageConverter<?> converter = iterator.next(); if (converter instanceof StringHttpMessageConverter) { ((StringHttpMessageConverter) converter).setDefaultCharset(Charset.forName("UTF-8")); } } //设置自定义异常处理 restTemplate.setErrorHandler(new MyErrorHandler()); //设置日志拦截器 restTemplate.getInterceptors().add(new LoggingInterceptor()); return restTemplate; } /** * 配置httpclient工厂 * * @param httpClient * @return */ @Bean public ClientHttpRequestFactory clientHttpRequestFactory(HttpClient httpClient) { HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(); factory.setHttpClient(httpClient); //客户端和服务端建立连接的超时时间,这里最大只能是20s,因为linux操作系统的tcp进行三次握手的默认超时时间是20s,,即便设置100s也是在20s后报错 factory.setConnectTimeout(connectTimeout); //毫秒 //即socketTime,数据响应读取超时时间,指的是两个相邻的数据包的间隔超时时间 factory.setReadTimeout(readTimeout); //毫秒 //连接不够用时从连接池获取连接的等待时间,必须设置。不宜过长,连接不够用时,等待时间过长将是灾难性的 factory.setConnectionRequestTimeout(connectionRequestTimeout); //毫秒 //必须使用BufferingClientHttpRequestFactory这个包装类,否则默认实现只允许读取一次响应流,日志输出那里读取之后,请求调用处再读取时会报错 BufferingClientHttpRequestFactory bufferingClientHttpRequestFactory = new BufferingClientHttpRequestFactory(factory); return bufferingClientHttpRequestFactory; } /** * 配置httpclient * * @return */ @Bean public HttpClient httpClient(ConnectionKeepAliveStrategy connectionKeepAliveStrategy) { HttpClientBuilder httpClientBuilder = HttpClientBuilder.create(); SSLContext sslContext = null; try { sslContext = SSLContexts.custom().loadTrustMaterial(null, (X509Certificate[] var1, String var2) -> true).build(); } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) { logger.error(e.getMessage()); } // httpClientBuilder.setSSLContext(sslContext); SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE); Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create() // 注册http和https请求 .register("http", PlainConnectionSocketFactory.getSocketFactory()) .register("https", sslConnectionSocketFactory) .build(); //使用Httpclient连接池的方式配置(推荐),同时支持netty,okHttp以及其他http框架 PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry); // 最大tcp连接数 poolingHttpClientConnectionManager.setMaxTotal(maxTotal); // 同路由最大tcp连接数 poolingHttpClientConnectionManager.setDefaultMaxPerRoute(maxConnectPerRoute); //配置连接池 httpClientBuilder.setConnectionManager(poolingHttpClientConnectionManager); // 是否允许重试和重试次数 httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(retryTimes, enableRetry)); //设置默认请求头 // List<Header> headers = getDefaultHeaders(); // httpClientBuilder.setDefaultHeaders(headers); //设置长连接保持策略 httpClientBuilder.setKeepAliveStrategy(connectionKeepAliveStrategy); return httpClientBuilder.build(); } /** * 配置长连接策略 * * @return */ @Bean public ConnectionKeepAliveStrategy connectionKeepAliveStrategy() { return (response, context) -> { // Honor 'keep-alive' header HeaderElementIterator it = new BasicHeaderElementIterator(response.headerIterator(HTTP.CONN_KEEP_ALIVE)); logger.debug("HeaderElement:{}", it); while (it.hasNext()) { HeaderElement he = it.nextElement(); String param = he.getName(); String value = he.getValue(); if (value != null && "timeout".equalsIgnoreCase(param)) { try { //1.服务器有时候会告诉客户端长连接的超时时间,如果有则设置为服务器的返回值 return Long.parseLong(value) * 1000; } catch (NumberFormatException ignore) { logger.error("解析长连接过期时间异常", ignore); } } } //2.如果服务器没有返回超时时间则采用配置的时间 //a.如果请求目标地址,单独配置了长连接保持时间,使用该配置 b.否则使用配置的的默认长连接保持时间keepAliveTime HttpHost target = (HttpHost) context.getAttribute(HttpClientContext.HTTP_TARGET_HOST); Optional<Map.Entry<String, Long>> any = Optional.ofNullable(keepAliveTargetHosts).orElseGet(HashMap::new) .entrySet().stream().filter(e -> e.getKey().equalsIgnoreCase(target.getHostName())).findAny(); return any.map(e -> e.getValue()).orElse(keepAliveTime); }; } /** * 自定义异常处理 */ private class MyErrorHandler implements ResponseErrorHandler { @Override public boolean hasError(ClientHttpResponse response) throws IOException { return !response.getStatusCode().is2xxSuccessful(); } @Override public void handleError(ClientHttpResponse response) throws IOException { //响应状态码错误时不抛出异常 throw new HttpClientErrorException(response.getStatusCode(), response.getStatusText(), response.getHeaders(), IOUtils.toByteArray(response.getBody()), Charset.forName("UTF-8")); } } /** * 日志拦截器 */ private class LoggingInterceptor implements ClientHttpRequestInterceptor { @Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { traceRequest(request, body); long start = System.currentTimeMillis(); ClientHttpResponse response = execution.execute(request, body); long end = System.currentTimeMillis(); traceResponse(body, request, response, (end - start) / 1000.0f); return response; } private void traceRequest(HttpRequest request, byte[] body) { StringBuilder log = new StringBuilder(); log.append(" ===========================request begin=========================== ") .append("URI : ").append(request.getURI()).append(" ") .append("Method : ").append(request.getMethod()).append(" ") .append("Headers : ").append(request.getHeaders()).append(" ") .append("Request body: ").append(new String(body, Charset.forName("UTF-8"))).append(" ") .append("===========================request end=============================="); logger.debug(log.toString()); } private void traceResponse(byte[] body, HttpRequest request, ClientHttpResponse response, float time) throws IOException { StringBuilder inputStringBuilder = new StringBuilder(); try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(response.getBody(), "UTF-8"))) { String line = bufferedReader.readLine(); while (line != null) { inputStringBuilder.append(line); line = bufferedReader.readLine(); } } StringBuilder log = new StringBuilder(); if (response.getStatusCode().is2xxSuccessful()) { log.append(" ===========================response begin=========================== ") .append("TIME : ").append(time).append("s").append(" ") .append("URI : ").append(request.getURI()).append(" ") .append("Method : ").append(request.getMethod()).append(" ") .append("Status code : ").append(response.getStatusCode()).append(" ") .append("Headers : ").append(response.getHeaders()).append(" ") .append("Response body: ").append(inputStringBuilder.toString()).append(" ") .append("===========================response end=============================="); logger.debug(log.toString()); } else { log.append(" ===========================response begin=========================== ") .append("TIME : ").append(time).append("s").append(" ") .append("URI : ").append(request.getURI()).append(" ") .append("Method : ").append(request.getMethod()).append(" ") .append("Headers : ").append(request.getHeaders()).append(" ") .append("Request body : ").append(new String(body, Charset.forName("UTF-8"))).append(" ") .append("Status code : ").append(response.getStatusCode()).append(" ") .append("Headers : ").append(response.getHeaders()).append(" ") .append("Response body: ").append(inputStringBuilder.toString()).append(" ") .append("===========================response end=============================="); logger.error(log.toString()); } } private String getLogRules() { String rule = ""; String path = System.getProperty("user.dir"); StringBuffer sb = new StringBuffer(); String logRulesPath = sb.append(path) .append(File.separator) .append("config") .append(File.separator) .append("log-rules") .append(File.separator).append("log-print-rules.json").toString(); FileInputStream fileInputStream = null; try { fileInputStream = new FileInputStream(new File(logRulesPath)); rule = IOUtils.toString(fileInputStream, StandardCharsets.UTF_8.name()); } catch (IOException e) { logger.error("Load rules file error.", e); } finally { if (fileInputStream != null) { try { fileInputStream.close(); } catch (IOException e) { logger.error("Closing stream error.", e); } } } return rule; } } }
Util类:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponentsBuilder; import javax.annotation.PostConstruct; import java.net.URI; import java.util.HashMap; import java.util.Map; import java.util.Objects; /** * @version v1.0 * @description: * @author: 47 */ @Component public class RestUtils { private static RestTemplate restTemplate; @Autowired private RestTemplate restTemplateValue; @PostConstruct public void init() { restTemplate = restTemplateValue; } public static String buildUrl(String hostAddress, String uri) { return hostAddress + uri; } public static String buildUrl(String scheme, String host, int port, String uri) { return scheme + "://" + host + ":" + port + uri; } public static <T> ResponseEntity<T> get(String uri, Map<String, String> headers, Map<String, Object> uriVariable, Map<String, Object> queryVariable, Class<T> clazz) { return execute(uri, HttpMethod.GET, headers, uriVariable, queryVariable, null, clazz); } public static <T> ResponseEntity<T> get(String uri, Map<String, Object> uriVariable, Map<String, Object> queryVariable, Class<T> clazz) { return execute(uri, HttpMethod.GET, null, uriVariable, queryVariable, null, clazz); } public static <T> ResponseEntity<T> get(String uri, String token, Map<String, Object> queryVariable, Class<T> clazz) { Map headers = new HashMap<String, String>(2); headers.put("X-Auth-Token", token); return execute(uri, HttpMethod.GET, headers, null, queryVariable, null, clazz); } public static <T> ResponseEntity<T> get(String uri, Map<String, Object> queryVariable, Class<T> clazz) { return execute(uri, HttpMethod.GET, null, null, queryVariable, null, clazz); } public static <T> ResponseEntity<T> get(String uri, String token, Class<T> clazz) { Map headers = new HashMap<String, String>(2); headers.put("X-Auth-Token", token); return execute(uri, HttpMethod.GET, headers, null, null, null, clazz); } public static <T> ResponseEntity<T> get(String uri, Class<T> clazz) { return execute(uri, HttpMethod.GET, null, null, null, null, clazz); } public static <T> ResponseEntity<T> post(String uri, Map<String, String> headers, Map<String, Object> uriVariable, Map<String, Object> queryVariable, Object body, Class<T> clazz) { return execute(uri, HttpMethod.POST, headers, uriVariable, queryVariable, body, clazz); } public static <T> ResponseEntity<T> post(String uri, Map<String, Object> uriVariable, Object body, Class<T> clazz) { return execute(uri, HttpMethod.POST, null, uriVariable, null, body, clazz); } public static <T> ResponseEntity<T> post(String uri, Object body, Class<T> clazz) { return execute(uri, HttpMethod.POST, null, null, null, body, clazz); } public static <T> ResponseEntity<T> post(String uri, String token, Map<String, Object> uriVariable, Object body, Class<T> clazz) { Map headers = new HashMap<String, String>(2); headers.put("X-Auth-Token", token); return execute(uri, HttpMethod.POST, headers, uriVariable, null, body, clazz); } public static <T> ResponseEntity<T> post(String uri, String token, Object body, Class<T> clazz) { Map headers = new HashMap<String, String>(2); headers.put("X-Auth-Token", token); return execute(uri, HttpMethod.POST, headers, null, null, body, clazz); } public static <T> ResponseEntity<T> put(String uri, Map<String, String> headers, Map<String, Object> uriVariable, Map<String, Object> queryVariable, Object body, Class<T> clazz) { return execute(uri, HttpMethod.PUT, headers, uriVariable, queryVariable, body, clazz); } public static <T> ResponseEntity<T> put(String uri, Map<String, Object> uriVariable, Object body, Class<T> clazz) { return execute(uri, HttpMethod.PUT, null, uriVariable, null, body, clazz); } public static <T> ResponseEntity<T> put(String uri, String token, Map<String, Object> uriVariable, Object body, Class<T> clazz) { Map headers = new HashMap<String, String>(2); headers.put("X-Auth-Token", token); return execute(uri, HttpMethod.PUT, headers, uriVariable, null, body, clazz); } public static <T> ResponseEntity<T> put(String uri, Object body, Class<T> clazz) { return execute(uri, HttpMethod.PUT, null, null, null, body, clazz); } public static <T> ResponseEntity<T> put(String uri, String token, Object body, Class<T> clazz) { Map headers = new HashMap<String, String>(2); headers.put("X-Auth-Token", token); return execute(uri, HttpMethod.PUT, headers, null, null, body, clazz); } public static <T> ResponseEntity<T> patch(String uri, Map<String, String> headers, Map<String, Object> uriVariable, Map<String, Object> queryVariable, Object body, Class<T> clazz) { return execute(uri, HttpMethod.PATCH, headers, uriVariable, queryVariable, body, clazz); } public static <T> ResponseEntity<T> patch(String uri, Map<String, Object> uriVariable, Object body, Class<T> clazz) { return execute(uri, HttpMethod.PATCH, null, uriVariable, null, body, clazz); } public static <T> ResponseEntity<T> patch(String uri, String token, Map<String, Object> uriVariable, Object body, Class<T> clazz) { Map headers = new HashMap<String, String>(2); headers.put("X-Auth-Token", token); return execute(uri, HttpMethod.PATCH, headers, uriVariable, null, body, clazz); } public static <T> ResponseEntity<T> patch(String uri, Object body, Class<T> clazz) { return execute(uri, HttpMethod.PATCH, null, null, null, body, clazz); } public static <T> ResponseEntity<T> patch(String uri, String token, Object body, Class<T> clazz) { Map headers = new HashMap<String, String>(2); headers.put("X-Auth-Token", token); return execute(uri, HttpMethod.PATCH, headers, null, null, body, clazz); } public static <T> ResponseEntity<T> delete(String uri, Map<String, String> headers, Map<String, Object> uriVariable, Map<String, Object> queryVariable, Class<T> clazz) { return execute(uri, HttpMethod.DELETE, headers, uriVariable, queryVariable, null, clazz); } public static <T> ResponseEntity<T> delete(String uri, Map<String, Object> uriVariable, Class<T> clazz) { return execute(uri, HttpMethod.DELETE, null, uriVariable, null, null, clazz); } public static <T> ResponseEntity<T> delete(String uri, Map<String, Object> uriVariable, Object body, Class<T> clazz) { return execute(uri, HttpMethod.DELETE, null, uriVariable, null, body, clazz); } /** * HTTP/HTTPS请求 * * @param uri 请求地址 * @param httpMethod 请求方法 * @param headers 请求头 * @param uriVariables 路径参数 * @param queryVariable 查询参数 * @param body 请求体 * @param clazz 返回值类型 * @return */ private static <T> ResponseEntity<T> execute(String uri, HttpMethod httpMethod, Map<String, String> headers, Map<String, Object> uriVariables, Map<String, Object> queryVariable, Object body, Class<T> clazz) { HttpEntity httpEntity = new HttpEntity(body, buildHeader(headers)); ResponseEntity<T> responseEntity = restTemplate.exchange(buildUri(uri, uriVariables, queryVariable), httpMethod, httpEntity, clazz); return responseEntity; } /** * 组装请求头 * * @param headers * @return */ private static HttpHeaders buildHeader(Map<String, String> headers) { HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.add("Content-Type", "application/json; charset=UTF-8"); httpHeaders.add("Accept-Charset", "UTF-8"); httpHeaders.add("Accept", "application/json; charset=UTF-8"); //设置请求头 if (headers != null) { for (Map.Entry<String, String> entry : headers.entrySet()) { httpHeaders.set(entry.getKey(), entry.getValue()); } } return httpHeaders; } /** * 组装查询参数 * * @param uri * @param queryVariable * @return */ private static URI buildUri(String uri, Map<String, Object> uriVariables, Map<String, Object> queryVariable) { UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUriString(uri); if (Objects.nonNull(uriVariables)) { uriComponentsBuilder.uriVariables(uriVariables); } if (queryVariable != null) { for (Map.Entry<String, Object> entry : queryVariable.entrySet()) { uriComponentsBuilder.queryParam(entry.getKey(), entry.getValue()); } } return uriComponentsBuilder.build().encode().toUri(); } }