Spring RestTemplate不支持持久的HTTPS连接。

4

我想在使用Spring RestTemplate访问https的REST API时利用持久化的http连接。然而,我不能使其工作,因为每个请求都会创建一个新的连接并进行SSL握手。 是否可以在RestTemplate中使用可重用的https连接,如果可以,如何配置它?

我设置了一个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的execute方法中,首先创建一个新的请求,然后执行该请求并返回结果。
            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 context:


    @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主体则从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每次都会从头开始进行新的请求,因此用户令牌会丢失,PoolingHttpClientConnectionManager无法找到可重用的连接,因此每次都会创建一个新的连接。

我希望RestTemplate可以创建一个可重用该连接的请求,而不是每次都创建一个新的连接。

1个回答

1
我试图达到相同的目的,我所能想到的唯一方法是扩展HttpComponentsClientHttpRequestFactory以设置UserToken,在这种情况下为证书的主体cert.getSubjectDN(),然后覆盖createHttpContext(HttpMethod httpMethod, URI uri)函数。
@Override
protected HttpContext createHttpContext(HttpMethod httpMethod, URI uri) {
    HttpClientContext context = HttpClientContext.create();
    context.setUserToken(userToken);
    return context;
}

尝试过这样做,但没有改变任何事情。 SSL握手仍然在每个交易中发生。 - ArtOfWarfare

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接