Skip to content

Commit 19d991a

Browse files
committed
Revisit resource cleanup in ClientHttpRequestFactory
Prior to this commit, resource management around `ClientHttpRequestFactory` and `RestTemplate` was unclear. Some factories implementation were implementing a `DisposableBean` and other contracts were not managing request factory resources. In the meantime, neither `ClientHttpRequestFactory` nor `RestTemplate` are typically meant to be contributed as beans to the application context. Most often, they're instantiated within beans and their lifecycle should be managed by those. This commit makes all `ClientHttpRequestFactory` `Closeable` and ensures that all existing implementations have a similar behavior between `dispose()` and `close()`. Since `RestTemplate` (actually `HttpAccessor`) can instantiate factories on its own, they also now extend `Closeable` to properly close those resources, if not externally managed. Closes gh-29010
1 parent 71f9a84 commit 19d991a

File tree

6 files changed

+58
-34
lines changed

6 files changed

+58
-34
lines changed

spring-web/src/main/java/org/springframework/http/client/ClientHttpRequestFactory.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.http.client;
1818

19+
import java.io.Closeable;
1920
import java.io.IOException;
2021
import java.net.URI;
2122

@@ -29,7 +30,7 @@
2930
* @since 3.0
3031
*/
3132
@FunctionalInterface
32-
public interface ClientHttpRequestFactory {
33+
public interface ClientHttpRequestFactory extends Closeable {
3334

3435
/**
3536
* Create a new {@link ClientHttpRequest} for the specified URI and HTTP method.
@@ -42,4 +43,8 @@ public interface ClientHttpRequestFactory {
4243
*/
4344
ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException;
4445

46+
@Override
47+
default void close() throws IOException {
48+
49+
}
4550
}

spring-web/src/main/java/org/springframework/http/client/HttpComponentsClientHttpRequestFactory.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -329,13 +329,17 @@ protected HttpContext createHttpContext(HttpMethod httpMethod, URI uri) {
329329
*/
330330
@Override
331331
public void destroy() throws Exception {
332+
close();
333+
}
334+
335+
@Override
336+
public void close() throws IOException {
332337
HttpClient httpClient = getHttpClient();
333338
if (httpClient instanceof Closeable) {
334339
((Closeable) httpClient).close();
335340
}
336341
}
337342

338-
339343
/**
340344
* An alternative to {@link org.apache.http.client.methods.HttpDelete} that
341345
* extends {@link org.apache.http.client.methods.HttpEntityEnclosingRequestBase}

spring-web/src/main/java/org/springframework/http/client/OkHttp3ClientHttpRequestFactory.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -107,6 +107,11 @@ public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) {
107107

108108
@Override
109109
public void destroy() throws IOException {
110+
close();
111+
}
112+
113+
@Override
114+
public void close() throws IOException {
110115
if (this.defaultClient) {
111116
// Clean up the client if we created it in the constructor
112117
Cache cache = this.client.cache();
@@ -118,7 +123,6 @@ public void destroy() throws IOException {
118123
}
119124
}
120125

121-
122126
static Request buildRequest(HttpHeaders headers, byte[] content, URI uri, HttpMethod method)
123127
throws MalformedURLException {
124128

spring-web/src/main/java/org/springframework/http/client/support/HttpAccessor.java

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.http.client.support;
1818

19+
import java.io.Closeable;
1920
import java.io.IOException;
2021
import java.net.URI;
2122
import java.util.ArrayList;
@@ -48,7 +49,7 @@
4849
* @see ClientHttpRequestFactory
4950
* @see org.springframework.web.client.RestTemplate
5051
*/
51-
public abstract class HttpAccessor {
52+
public abstract class HttpAccessor implements Closeable {
5253

5354
/** Logger available to subclasses. */
5455
protected final Log logger = HttpLogging.forLogName(getClass());
@@ -66,7 +67,7 @@ public abstract class HttpAccessor {
6667
* Configure the Apache HttpComponents or OkHttp request factory to enable PATCH.</b>
6768
* @see #createRequest(URI, HttpMethod)
6869
* @see SimpleClientHttpRequestFactory
69-
* @see org.springframework.http.client.HttpComponentsAsyncClientHttpRequestFactory
70+
* @see org.springframework.http.client.HttpComponentsClientHttpRequestFactory
7071
* @see org.springframework.http.client.OkHttp3ClientHttpRequestFactory
7172
*/
7273
public void setRequestFactory(ClientHttpRequestFactory requestFactory) {
@@ -133,4 +134,15 @@ private void initialize(ClientHttpRequest request) {
133134
this.clientHttpRequestInitializers.forEach(initializer -> initializer.initialize(request));
134135
}
135136

137+
/**
138+
* Close the underlying {@link ClientHttpRequestFactory}.
139+
* <p>This should not be called if the factory has been {@link #setRequestFactory(ClientHttpRequestFactory) set}
140+
* and is externally managed.
141+
* @throws IOException if the request factory cannot be closed properly
142+
*/
143+
@Override
144+
public void close() throws IOException {
145+
this.requestFactory.close();
146+
}
147+
136148
}

spring-web/src/test/java/org/springframework/http/client/AbstractHttpRequestFactoryTests.java

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -25,7 +25,6 @@
2525
import org.junit.jupiter.api.BeforeEach;
2626
import org.junit.jupiter.api.Test;
2727

28-
import org.springframework.beans.factory.DisposableBean;
2928
import org.springframework.beans.factory.InitializingBean;
3029
import org.springframework.http.HttpMethod;
3130
import org.springframework.http.HttpStatus;
@@ -47,17 +46,15 @@ abstract class AbstractHttpRequestFactoryTests extends AbstractMockWebServerTest
4746

4847
@BeforeEach
4948
final void createFactory() throws Exception {
50-
factory = createRequestFactory();
51-
if (factory instanceof InitializingBean) {
52-
((InitializingBean) factory).afterPropertiesSet();
49+
this.factory = createRequestFactory();
50+
if (this.factory instanceof InitializingBean) {
51+
((InitializingBean) this.factory).afterPropertiesSet();
5352
}
5453
}
5554

5655
@AfterEach
5756
final void destroyFactory() throws Exception {
58-
if (factory instanceof DisposableBean) {
59-
((DisposableBean) factory).destroy();
60-
}
57+
this.factory.close();
6158
}
6259

6360

spring-web/src/test/java/org/springframework/http/client/HttpComponentsClientHttpRequestFactoryTests.java

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -55,22 +55,24 @@ public void httpMethods() throws Exception {
5555
@Test
5656
public void assertCustomConfig() throws Exception {
5757
HttpClient httpClient = HttpClientBuilder.create().build();
58-
HttpComponentsClientHttpRequestFactory hrf = new HttpComponentsClientHttpRequestFactory(httpClient);
59-
hrf.setConnectTimeout(1234);
60-
hrf.setConnectionRequestTimeout(4321);
61-
hrf.setReadTimeout(4567);
62-
63-
URI uri = new URI(baseUrl + "/status/ok");
64-
HttpComponentsClientHttpRequest request = (HttpComponentsClientHttpRequest)
65-
hrf.createRequest(uri, HttpMethod.GET);
66-
67-
Object config = request.getHttpContext().getAttribute(HttpClientContext.REQUEST_CONFIG);
68-
assertThat(config).as("Request config should be set").isNotNull();
69-
assertThat(config).as("Wrong request config type " + config.getClass().getName()).isInstanceOf(RequestConfig.class);
70-
RequestConfig requestConfig = (RequestConfig) config;
71-
assertThat(requestConfig.getConnectTimeout()).as("Wrong custom connection timeout").isEqualTo(1234);
72-
assertThat(requestConfig.getConnectionRequestTimeout()).as("Wrong custom connection request timeout").isEqualTo(4321);
73-
assertThat(requestConfig.getSocketTimeout()).as("Wrong custom socket timeout").isEqualTo(4567);
58+
HttpComponentsClientHttpRequest request;
59+
try (HttpComponentsClientHttpRequestFactory hrf = new HttpComponentsClientHttpRequestFactory(httpClient)) {
60+
hrf.setConnectTimeout(1234);
61+
hrf.setConnectionRequestTimeout(4321);
62+
hrf.setReadTimeout(4567);
63+
64+
URI uri = new URI(baseUrl + "/status/ok");
65+
request = (HttpComponentsClientHttpRequest)
66+
hrf.createRequest(uri, HttpMethod.GET);
67+
68+
Object config = request.getHttpContext().getAttribute(HttpClientContext.REQUEST_CONFIG);
69+
assertThat(config).as("Request config should be set").isNotNull();
70+
assertThat(config).as("Wrong request config type " + config.getClass().getName()).isInstanceOf(RequestConfig.class);
71+
RequestConfig requestConfig = (RequestConfig) config;
72+
assertThat(requestConfig.getConnectTimeout()).as("Wrong custom connection timeout").isEqualTo(1234);
73+
assertThat(requestConfig.getConnectionRequestTimeout()).as("Wrong custom connection request timeout").isEqualTo(4321);
74+
assertThat(requestConfig.getSocketTimeout()).as("Wrong custom socket timeout").isEqualTo(4567);
75+
}
7476
}
7577

7678
@Test

0 commit comments

Comments
 (0)