【发布时间】:2019-12-03 16:48:41
【问题描述】:
我想在通过 https 访问 REST api 时使用 Spring RestTemplate 使用持久的 http 连接。我不能让它工作;为每个请求创建一个新连接,并且每次都进行 SSL 握手。 是否可以通过 https 与 RestTemplate 建立可重用连接,如果可以,如何配置?
我设置了一个 RestTemplate 来通过 https 发出请求。这工作正常。 但是,我在日志中注意到每个请求都会发生新的 SSL 握手。
我在测试中设置了一个 RestTemplate 如下:
@Before
public void setupPersistentHttpConnectionBackedRestTemplate() {
final SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(
sslContext,
new String[] { "TLSv1.2" },
null,
SSLConnectionSocketFactory.getDefaultHostnameVerifier());
final Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("https", sslSocketFactory)
.build();
final PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(registry);
final CloseableHttpClient httpClient = HttpClients.custom()
.setSSLSocketFactory(sslSocketFactory)
.setConnectionManager(connectionManager)
.build();
final HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
restTemplate.getRestTemplate().setRequestFactory(requestFactory);
}
然后我像这样使用这个 RestTemplate 进行几次调用:
ResponseEntity<String> response = restTemplate.exchange("/tomcat/sleep?millis={millis}", HttpMethod.GET, HttpEntity.EMPTY, String.class, SLEEP_DURATION);
我研究了 spring-mvc 和 apache 的代码,并注意到以下内容。 在 Spring RestTemplate 的执行方法中,创建一个新的请求,然后执行请求并返回结果。
ClientHttpRequest request = createRequest(url, method);
if (requestCallback != null) {
requestCallback.doWithRequest(request);
}
response = request.execute();
handleResponse(url, method, response);
return (responseExtractor != null ? responseExtractor.extractData(response) : null);
这反过来又以调用 HttpComponentsClientHttpRequestFactory 结束,每次都会创建一个新的 http 上下文:
@Override
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
HttpClient client = getHttpClient();
HttpUriRequest httpRequest = createHttpUriRequest(httpMethod, uri);
postProcessHttpRequest(httpRequest);
HttpContext context = createHttpContext(httpMethod, uri);
if (context == null) {
context = HttpClientContext.create();
}
...
在请求执行调用期间跟踪调用链时,我最终进入 apache MainClientExec。它在那里尝试重用基于路由和上下文用户令牌的连接。请求执行后,从上下文中检索用户令牌并存储以供进一步查找。
@Override
public CloseableHttpResponse execute(
final HttpRoute route,
final HttpRequestWrapper request,
final HttpClientContext context,
final HttpExecutionAware execAware) throws IOException, HttpException {
...
Object userToken = context.getUserToken();
final ConnectionRequest connRequest = connManager.requestConnection(route, userToken);
...
if (userToken == null) {
userToken = userTokenHandler.getUserToken(context);
context.setAttribute(HttpClientContext.USER_TOKEN, userToken);
}
if (userToken != null) {
connHolder.setState(userToken);
}
...
在 https 连接的情况下,用户令牌从 SSL 主体中检索,然后从 SSL 证书中获取:
@Override
public Object getUserToken(final HttpContext context) {
...
if (userPrincipal == null) {
final HttpConnection conn = clientContext.getConnection();
if (conn.isOpen() && conn instanceof ManagedHttpClientConnection) {
final SSLSession sslsession = ((ManagedHttpClientConnection) conn).getSSLSession();
if (sslsession != null) {
userPrincipal = sslsession.getLocalPrincipal();
}
}
}
public Principal getLocalPrincipal() {
if (this.cipherSuite.keyExchange != KeyExchange.K_KRB5 && this.cipherSuite.keyExchange != KeyExchange.K_KRB5_EXPORT) {
return this.localCerts == null ? null : this.localCerts[0].getSubjectX500Principal();
} else {
return this.localPrincipal == null ? null : this.localPrincipal;
}
}
PoolingHttpClientConnectionManager 尝试根据路由和状态(存储用户令牌的位置)返回可重用连接。
但由于 RestTemplate 每次都以新的请求和新的上下文开始,因此 uset 令牌丢失并且 PoolingHttpClientConnectionManager 找不到可重用的连接,因此每次都会创建一个新的。
我希望 RestRemplate 可以创建一个重新使用该连接的请求,而不是每次都创建一个新连接。
【问题讨论】:
标签: spring-mvc ssl https resttemplate keep-alive