Spring-RestTemplate

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);
//MaxPerRoute路由是对maxTotal的细分,每个主机的并发最大是300,这里route指的是域名
//MaxPerRoute所有路由的并发数加起来不会超过最大连接数
connectionManager.setDefaultMaxPerRoute(300);

RequestConfig requestConfig = RequestConfig.custom()
//返回数据的超时时间
.setSocketTimeout(30000)
//连接上服务器的超时时间
.setConnectTimeout(10000)
//从连接池中获取连接的超时时间
.setConnectionRequestTimeout(1000)
.build();

return HttpClientBuilder.create()
.setDefaultRequestConfig(requestConfig)
.setConnectionManager(connectionManager)
.build();
}
}

5. 请求https证书报错

  • 当请求非正式的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;

/**
* @author iseven.yang
* @date 2022/3/21 14:45
*/
@Configuration
public class RestTemplateConfig {

/**
* 忽略Https证书认证
*/
@Bean("unSSLRestTemplate")
public RestTemplate unSSLRestTemplate() throws Exception {
return new RestTemplate(generateUnSSLHttpRequestFactory());
}

private HttpComponentsClientHttpRequestFactory generateUnSSLHttpRequestFactory() throws Exception{
// 忽略SSL证书
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);
//MaxPerRoute路由是对maxTotal的细分,每个主机的并发最大是300,这里route指的是域名
//MaxPerRoute所有路由的并发数加起来不会超过最大连接数
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);