后端 Spring Spring-RestTemplate cmyang 2022-02-03 2024-06-06 1. RestTemplate
RestTemplate是Spring提供的用于访问Rest服务的客户端
底层通过使用java.net包下的实现创建HTTP 请求
通过使用ClientHttpRequestFactory指定不同的HTTP请求方式,主要提供了两种实现方式
SimpleClientHttpRequestFactory(默认)
底层使用J2SE提供的方式,既java.net包提供的方式,创建底层的Http请求连接
主要createRequest 方法( 断点调试),每次都会创建一个新的连接,每次都创建连接会造成极大的资源浪费,而且若连接不能及时释放,会因为无法建立新的连接导致后面的请求阻塞
HttpComponentsClientHttpRequestFactory
底层使用HttpClient访问远程的Http服务
2. RestTemplate报错 2.1 Broken pipe
服务端向前端socket连接管道写返回数据时链接(pipe)已经断开了
从应用角度分析,这是因为客户端等待返回超时了,主动断开了与服务端链接
连接数设置太小,并发量增加后,造成大量请求排队等待
网络延迟,是否有丢包
内存是否足够多支持对应的并发量
3. 问题解决思路
客户端每次请求都要和服务端建立新的连接,即三次握手将会非常耗时
通过http连接池可以减少连接建立与释放的时间,提升http请求的性能
Spring的restTemplate是对httpclient进行了封装, 而httpclient是支持池化机制
对httpclient进行封装的有:Apache的Fluent、es的restHighLevelClient、spring的restTemplate等
4. 编码实践 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 @Configuration public class RestTemplateConfig { @Bean("customRestTemplate") public RestTemplate restTemplate () { return new RestTemplate (httpComponentsClientHttpRequestFactory()); } private ClientHttpRequestFactory httpComponentsClientHttpRequestFactory () { return new HttpComponentsClientHttpRequestFactory (httpClient()); } private HttpClient httpClient () { Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create() .register("http" , PlainConnectionSocketFactory.getSocketFactory()) .register("https" , SSLConnectionSocketFactory.getSocketFactory()) .build(); PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager (registry); connectionManager.setMaxTotal(500 ); connectionManager.setDefaultMaxPerRoute(300 ); RequestConfig requestConfig = RequestConfig.custom() .setSocketTimeout(30000 ) .setConnectTimeout(10000 ) .setConnectionRequestTimeout(1000 ) .build(); return HttpClientBuilder.create() .setDefaultRequestConfig(requestConfig) .setConnectionManager(connectionManager) .build(); } }
5. 请求https证书报错
1 sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
简单处理,可以通过忽略证书方式
创建一个忽略ssl证书的bean:unSSLRestTemplate
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 package com.xxx.web.cloud.platform.eip.config;import org.apache.http.client.config.RequestConfig;import org.apache.http.config.Registry;import org.apache.http.config.RegistryBuilder;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.conn.ssl.TrustStrategy;import org.apache.http.impl.client.CloseableHttpClient;import org.apache.http.impl.client.HttpClientBuilder;import org.apache.http.impl.client.HttpClients;import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;import org.apache.http.ssl.SSLContexts;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;import org.springframework.web.client.RestTemplate;import javax.net.ssl.SSLContext;@Configuration public class RestTemplateConfig { @Bean("unSSLRestTemplate") public RestTemplate unSSLRestTemplate () throws Exception { return new RestTemplate (generateUnSSLHttpRequestFactory()); } private HttpComponentsClientHttpRequestFactory generateUnSSLHttpRequestFactory () throws Exception{ TrustStrategy acceptingTrustStrategy = ((x509Certificates, authType) -> true ); SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(null , acceptingTrustStrategy).build(); SSLConnectionSocketFactory connectionSocketFactory = new SSLConnectionSocketFactory (sslContext, new NoopHostnameVerifier ()); Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create() .register("http" , PlainConnectionSocketFactory.getSocketFactory()) .register("https" , connectionSocketFactory) .build(); PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager (registry); connectionManager.setMaxTotal(500 ); connectionManager.setDefaultMaxPerRoute(300 ); RequestConfig requestConfig = RequestConfig.custom() .setSocketTimeout(30000 ) .setConnectTimeout(10000 ) .setConnectionRequestTimeout(1000 ) .build(); HttpClientBuilder httpClientBuilder = HttpClients.custom(); httpClientBuilder.setDefaultRequestConfig(requestConfig); httpClientBuilder.setConnectionManager(connectionManager); CloseableHttpClient httpClient = httpClientBuilder.build(); HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory (); factory.setHttpClient(httpClient); return factory; } }
6. 发送请求参数带有+特殊字符 当请求参数中有特殊字符加号(+)时,会被替换成空格,导致服务请求报错
解决方案:
将带有+号的参数值,使用URLEncode进行编码
String param = URLEncoder.encode(paramStr, StandardCharsets.UTF_8.name());
在发送请求时,通过URI的方式包装一下,URI.create(url)
String result = restTemplate.getForObject(URI.create(url), String.class);