文章目录
1. HTTP协议的长连接和短连接
HTTP协议的长连接和短连接,本质上是TCP协议的长连接和短连接。
长连接(持久连接)和短连接(非持久连接)的区别在于:
长连接:连接建立后,保持打开状态,可以复用同一连接进行多个请求和响应。减少了频繁的连接建立和断开开销,适用于需要频繁通信的场景,如 HTTP/1.1 和 WebSocket。
短连接:每次请求后,连接都会关闭。每次新的请求都需要重新建立连接,适用于通信频率较低的场景,如 HTTP/1.0。
在HTTP/1.0中默认使用短连接。也就是说,客户端和服务器每进行一次HTTP操作,就建立一次连接,任务结束就中断连接。
从HTTP/1.1起,默认使用长连接,用以保持连接特性。使用长连接的HTTP协议,会在响应头加入:Connection:keep-alive。在使用长连接的情况下,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,客户端再次访问这个服务器时,会继续使用这一条已经建立的连接。Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件中设定这个时间。
2. RestTemplate的长连接和短连接
2.1 RestTemplate的构造方法:
RestTemplate的构造方法:
RestTemplate()
RestTemplate(ClientHttpRequestFactory requestFactory)
RestTemplate(List<HttpMessageConverter<?>> messageConverters)
ClientHttpRequestFactory接口的实现很多,其中最常用的有以下两种:
SimpleClientHttpRequestFactory(封装URLConnection)
HttpComponentsClientHttpRequestFactory(封装HttpClient)
RestTemplate默认使用短连接,也就是每次发送请求都会建立一个新的TCP连接。这是因为RestTemplate的默认请求工厂是SimpleClientHttpRequestFactory,它没有连接池的概念。
如果需要使用长连接,可以使用HttpComponentsClientHttpRequestFactory,它支持连接池,并且可以通过setMaxTotal()和setDefaultMaxPerRoute()方法设置最大连接数和每个路由的最大连接数。
另外,需要注意的是,长连接并不是在所有情况下都能带来性能提升。在高并发场景下,使用长连接可能会导致连接池中的连接被占满,从而导致新的请求被阻塞。因此,在选择使用长连接时,需要根据实际情况进行评估和测试,以确定最合适的连接方式。
2.2 验证RestTemplate的默认请求工厂:
RestTemplate restTemplate = new RestTemplate();
System.out.println(restTemplate.getRequestFactory());
打印结果:
org.springframework.http.client.SimpleClientHttpRequestFactory@5d740a0f
代码追踪:
在RestTemplate的默认构造方法中并没有显示指明为SimpleClientHttpRequestFactory。
public RestTemplate() {
this.messageConverters = new ArrayList();
this.errorHandler = new DefaultResponseErrorHandler();
this.headersExtractor = new RestTemplate.HeadersExtractor();
this.messageConverters.add(new ByteArrayHttpMessageConverter());
this.messageConverters.add(new StringHttpMessageConverter());
this.messageConverters.add(new ResourceHttpMessageConverter(false));
try {
this.messageConverters.add(new SourceHttpMessageConverter());
} catch (Error var2) {
}
this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
if (romePresent) {
this.messageConverters.add(new AtomFeedHttpMessageConverter());
this.messageConverters.add(new RssChannelHttpMessageConverter());
}
if (jackson2XmlPresent) {
this.messageConverters.add(new MappingJackson2XmlHttpMessageConverter());
} else if (jaxb2Present) {
this.messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
}
if (jackson2Present) {
this.messageConverters.add(new MappingJackson2HttpMessageConverter());
} else if (gsonPresent) {
this.messageConverters.add(new GsonHttpMessageConverter());
} else if (jsonbPresent) {
this.messageConverters.add(new JsonbHttpMessageConverter());
}
if (jackson2SmilePresent) {
this.messageConverters.add(new MappingJackson2SmileHttpMessageConverter());
}
if (jackson2CborPresent) {
this.messageConverters.add(new MappingJackson2CborHttpMessageConverter());
}
this.uriTemplateHandler = initUriTemplateHandler();
}
发现:RestTemplate extends InterceptingHttpAccessor implements RestOperations
然后查看InterceptingHttpAccessor ,发现:InterceptingHttpAccessor extends HttpAccessor
然后查看HttpAccessor,发现定义了成员变量private ClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
所以从代码角度验证了默认请求工厂为SimpleClientHttpRequestFactory。
不知道怎么追踪源码的时候,可以打上断点,然后一步一步深入到方法里面,然后去看变量是哪一步被赋值的。
2.3 验证SimpleClientHttpRequestFactory每次请求开启新连接:
示例代码如下:
RestTemplate restTemplate = new RestTemplate();
System.out.println(restTemplate.getRequestFactory());
ResponseEntity<String> response = restTemplate.getForEntity("https://blog.csdn.net", String.class);
HttpHeaders headers = response.getHeaders();
System.out.println("Response Headers: " + headers);
代码打印包含:Connection:“keep-alive”。
代码追踪:
restTemplate.getForEntity->this.execute->this.doExecute->this.createRequest->this.getRequestFactory().createRequest
可以看到先获取了请求工厂然后才建立连接的。追踪一下SimpleClientHttpRequestFactory的实现
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
HttpURLConnection connection = this.openConnection(uri.toURL(), this.proxy);
this.prepareConnection(connection, httpMethod.name());
return (ClientHttpRequest)(this.bufferRequestBody ? new SimpleBufferingClientHttpRequest(connection, this.outputStreaming) : new SimpleStreamingClientHttpRequest(connection, this.chunkSize, this.outputStreaming));
}
openConnection 方法:
这个方法创建一个新的 HttpURLConnection 实例。openConnection 通常会调用 URL.openConnection() 方法,这实际上是创建一个新的连接对象。该连接是通过 HttpURLConnection 类建立的,它负责处理 TCP 连接的创建和管理。
prepareConnection 方法:
在这一步,prepareConnection 方法配置 HttpURLConnection 对象,例如设置请求方法(GET、POST 等)、请求头以及其他必要的配置。
SimpleBufferingClientHttpRequest 和 SimpleStreamingClientHttpRequest:
根据 bufferRequestBody 的值,返回一个适当的 ClientHttpRequest 实例。SimpleBufferingClientHttpRequest 和 SimpleStreamingClientHttpRequest 都是对 HttpURLConnection 的封装,负责处理请求的具体细节。
总结:
SimpleClientHttpRequestFactory 在每次调用 createRequest 时都会创建一个新的 HttpURLConnection 实例。虽然 HttpURLConnection 可以复用连接(通过 HTTP/1.1 的 Keep-Alive 特性),但 SimpleClientHttpRequestFactory 本身不管理连接池或长期连接的复用。
3. RestTemplate使用长连接
简单实现:
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
public static void main(String[] args) {
CloseableHttpClient httpClient = HttpClients.custom()
.setMaxConnTotal(100)
.setMaxConnPerRoute(20)
.build();
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
RestTemplate restTemplate = new RestTemplate(factory);
System.out.println(restTemplate.getRequestFactory());
ResponseEntity<String> response = restTemplate.getForEntity("https://blog.csdn.net", String.class);
HttpHeaders headers = response.getHeaders();
System.out.println("Response Headers: " + headers);
}
复杂实现:
HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
try {
// 设置信任ssl访问
SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, (arg0, arg1) -> true).build();
httpClientBuilder.setSSLContext(sslContext);
HostnameVerifier hostnameVerifier = NoopHostnameVerifier.INSTANCE;
SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext, hostnameVerifier);
Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
// 注册http和https请求
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", sslConnectionSocketFactory).build();
// 使用Httpclient连接池的方式配置
PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
poolingHttpClientConnectionManager.setMaxTotal(200);
poolingHttpClientConnectionManager.setDefaultMaxPerRoute(20);
httpClientBuilder.setConnectionManager(poolingHttpClientConnectionManager);
CloseableHttpClient httpClient = httpClientBuilder.build();
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
RestTemplate restTemplate = new RestTemplate(factory);
} catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
logger.error("初始化Httpclient连接池出错", e);
}
return null;
建议:
如果你的应用有特定的安全要求或协议需求(比如使用特定的证书、主机名验证规则等),建议你使用自定义的 SSLConnectionSocketFactory 和 Registry,即复杂实现。如果你的应用场景对默认配置没有特殊要求,可以使用默认配置,即简单实现。
具体区别如下:
HTTPS 连接的 SSL/TLS 配置:
SSL/TLS 设置:如果你省略了 SSLConnectionSocketFactory 的设置,HTTPS 连接将使用默认的 SSL/TLS 配置。这可能会导致你无法使用自定义的信任策略或证书设置。
主机名验证:省略 HostnameVerifier 相关的设置将导致默认的主机名验证机制被应用,可能会有不同的验证标准。
连接工厂注册:
注册表配置:省略 Registry 的设置,将会失去对 HTTP 和 HTTPS 连接工厂的自定义管理。这可能会导致连接池无法正确处理不同协议的连接,从而影响连接的创建和管理。
连接池行为:
连接工厂选择:如果你不提供自定义的 Registry,连接池可能无法正确区分和使用 HTTP 和 HTTPS 连接工厂,从而可能导致连接创建或复用的问题。
默认配置影响:
安全性:默认的 SSL 配置可能不符合你特定的安全要求。如果你需要特定的证书信任策略或加密设置,省略自定义配置会导致无法满足这些要求。
兼容性:如果你与特定的服务或API有特定的兼容性需求,默认配置可能不满足这些需求。