Skip to content

Commit 71783c5

Browse files
committed
Allow default settings of a custom HttpClient to apply
Previously the default settings of a custom HttpClient were always ignored since a RequestConfig instance was always set no matter if some customizations were applied or not. This commit keeps an internal RequestConfig object instance that is only initialized if the user applies a customization. If he does not, the default settings of the HttpClient are used as it should. Note that if the HttpComponents API exposed the default RequestConfig of a given HttpClient, we would be able to merge our customizations with the one specified by the client. Unfortunately, such API does not exist and the "defaultSettingsOfHttpClientLostOnExecutorCustomization" test illustrates that limitation. Issue: SPR-12540
1 parent 961574b commit 71783c5

File tree

4 files changed

+152
-49
lines changed

4 files changed

+152
-49
lines changed

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

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,7 @@ public class HttpComponentsClientHttpRequestFactory implements ClientHttpRequest
5959

6060
private CloseableHttpClient httpClient;
6161

62-
private int connectTimeout;
63-
64-
private int connectionRequestTimeout;
65-
66-
private int socketTimeout;
62+
private RequestConfig requestConfig;
6763

6864
private boolean bufferRequestBody = true;
6965

@@ -111,11 +107,15 @@ public HttpClient getHttpClient() {
111107
/**
112108
* Set the connection timeout for the underlying HttpClient.
113109
* A timeout value of 0 specifies an infinite timeout.
110+
* <p>Additional properties can be configured by specifying a
111+
* {@link RequestConfig} instance on a custom {@link HttpClient}.
114112
* @param timeout the timeout value in milliseconds
113+
* @see RequestConfig#getConnectTimeout()
115114
*/
116115
public void setConnectTimeout(int timeout) {
117116
Assert.isTrue(timeout >= 0, "Timeout must be a non-negative value");
118-
this.connectTimeout = timeout;
117+
this.requestConfig = cloneRequestConfig()
118+
.setConnectTimeout(timeout).build();
119119
setLegacyConnectionTimeout(getHttpClient(), timeout);
120120
}
121121

@@ -145,20 +145,28 @@ private void setLegacyConnectionTimeout(HttpClient client, int timeout) {
145145
* Set the timeout in milliseconds used when requesting a connection from the connection
146146
* manager using the underlying HttpClient.
147147
* A timeout value of 0 specifies an infinite timeout.
148+
* <p>Additional properties can be configured by specifying a
149+
* {@link RequestConfig} instance on a custom {@link HttpClient}.
148150
* @param connectionRequestTimeout the timeout value to request a connection in milliseconds
151+
* @see RequestConfig#getConnectionRequestTimeout()
149152
*/
150153
public void setConnectionRequestTimeout(int connectionRequestTimeout) {
151-
this.connectionRequestTimeout = connectionRequestTimeout;
154+
this.requestConfig = cloneRequestConfig()
155+
.setConnectionRequestTimeout(connectionRequestTimeout).build();
152156
}
153157

154158
/**
155159
* Set the socket read timeout for the underlying HttpClient.
156160
* A timeout value of 0 specifies an infinite timeout.
161+
* <p>Additional properties can be configured by specifying a
162+
* {@link RequestConfig} instance on a custom {@link HttpClient}.
157163
* @param timeout the timeout value in milliseconds
164+
* @see RequestConfig#getSocketTimeout()
158165
*/
159166
public void setReadTimeout(int timeout) {
160167
Assert.isTrue(timeout >= 0, "Timeout must be a non-negative value");
161-
this.socketTimeout = timeout;
168+
this.requestConfig = cloneRequestConfig()
169+
.setSocketTimeout(timeout).build();
162170
setLegacySocketTimeout(getHttpClient(), timeout);
163171
}
164172

@@ -177,6 +185,10 @@ private void setLegacySocketTimeout(HttpClient client, int timeout) {
177185
}
178186
}
179187

188+
private RequestConfig.Builder cloneRequestConfig() {
189+
return this.requestConfig != null ? RequestConfig.copy(this.requestConfig) : RequestConfig.custom();
190+
}
191+
180192
/**
181193
* Indicates whether this request factory should buffer the request body internally.
182194
* <p>Default is {@code true}. When sending large amounts of data via POST or PUT, it is
@@ -205,18 +217,11 @@ public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IO
205217
config = ((Configurable) httpRequest).getConfig();
206218
}
207219
if (config == null) {
208-
if (this.connectTimeout > 0 || this.connectionRequestTimeout > 0 || this.socketTimeout > 0) {
209-
config = RequestConfig.custom()
210-
.setConnectTimeout(this.connectTimeout)
211-
.setConnectionRequestTimeout(this.connectionRequestTimeout)
212-
.setSocketTimeout(this.socketTimeout)
213-
.build();
214-
}
215-
else {
216-
config = RequestConfig.DEFAULT;
217-
}
220+
config = this.requestConfig;
221+
}
222+
if (config != null) {
223+
context.setAttribute(HttpClientContext.REQUEST_CONFIG, config);
218224
}
219-
context.setAttribute(HttpClientContext.REQUEST_CONFIG, config);
220225
}
221226
if (this.bufferRequestBody) {
222227
return new HttpComponentsClientHttpRequest(client, httpRequest, context);

spring-web/src/main/java/org/springframework/remoting/httpinvoker/HttpComponentsHttpInvokerRequestExecutor.java

Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -69,27 +69,30 @@ public class HttpComponentsHttpInvokerRequestExecutor extends AbstractHttpInvoke
6969
private static final int DEFAULT_READ_TIMEOUT_MILLISECONDS = (60 * 1000);
7070

7171
private HttpClient httpClient;
72-
private int connectionTimeout = 0;
73-
private int connectionRequestTimeout = 0;
74-
private int readTimeout = DEFAULT_READ_TIMEOUT_MILLISECONDS;
7572

73+
private RequestConfig requestConfig;
7674

7775
/**
7876
* Create a new instance of the HttpComponentsHttpInvokerRequestExecutor with a default
7977
* {@link HttpClient} that uses a default {@code org.apache.http.impl.conn.PoolingClientConnectionManager}.
8078
*/
8179
public HttpComponentsHttpInvokerRequestExecutor() {
80+
this(createDefaultHttpClient(), RequestConfig.custom()
81+
.setSocketTimeout(DEFAULT_READ_TIMEOUT_MILLISECONDS).build());
82+
}
83+
84+
private static HttpClient createDefaultHttpClient() {
8285
Registry<ConnectionSocketFactory> schemeRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
83-
.register("http", PlainConnectionSocketFactory.getSocketFactory())
84-
.register("https", SSLConnectionSocketFactory.getSocketFactory())
85-
.build();
86+
.register("http", PlainConnectionSocketFactory.getSocketFactory())
87+
.register("https", SSLConnectionSocketFactory.getSocketFactory())
88+
.build();
8689

8790
PoolingHttpClientConnectionManager connectionManager
8891
= new PoolingHttpClientConnectionManager(schemeRegistry);
8992
connectionManager.setMaxTotal(DEFAULT_MAX_TOTAL_CONNECTIONS);
9093
connectionManager.setDefaultMaxPerRoute(DEFAULT_MAX_CONNECTIONS_PER_ROUTE);
9194

92-
this.httpClient = HttpClientBuilder.create().setConnectionManager(connectionManager).build();
95+
return HttpClientBuilder.create().setConnectionManager(connectionManager).build();
9396
}
9497

9598
/**
@@ -98,9 +101,13 @@ public HttpComponentsHttpInvokerRequestExecutor() {
98101
* @param httpClient the HttpClient instance to use for this request executor
99102
*/
100103
public HttpComponentsHttpInvokerRequestExecutor(HttpClient httpClient) {
101-
this.httpClient = httpClient;
104+
this(httpClient, null);
102105
}
103106

107+
private HttpComponentsHttpInvokerRequestExecutor(HttpClient httpClient, RequestConfig requestConfig) {
108+
this.httpClient = httpClient;
109+
this.requestConfig = requestConfig;
110+
}
104111

105112
/**
106113
* Set the {@link HttpClient} instance to use for this request executor.
@@ -119,11 +126,15 @@ public HttpClient getHttpClient() {
119126
/**
120127
* Set the connection timeout for the underlying HttpClient.
121128
* A timeout value of 0 specifies an infinite timeout.
129+
* <p>Additional properties can be configured by specifying a
130+
* {@link RequestConfig} instance on a custom {@link HttpClient}.
122131
* @param timeout the timeout value in milliseconds
132+
* @see RequestConfig#getConnectTimeout()
123133
*/
124134
public void setConnectTimeout(int timeout) {
125135
Assert.isTrue(timeout >= 0, "Timeout must be a non-negative value");
126-
this.connectionTimeout = timeout;
136+
this.requestConfig = cloneRequestConfig()
137+
.setConnectTimeout(timeout).build();
127138
setLegacyConnectionTimeout(getHttpClient(), timeout);
128139
}
129140

@@ -153,21 +164,29 @@ private void setLegacyConnectionTimeout(HttpClient client, int timeout) {
153164
* Set the timeout in milliseconds used when requesting a connection from the connection
154165
* manager using the underlying HttpClient.
155166
* A timeout value of 0 specifies an infinite timeout.
167+
* <p>Additional properties can be configured by specifying a
168+
* {@link RequestConfig} instance on a custom {@link HttpClient}.
156169
* @param connectionRequestTimeout the timeout value to request a connection in milliseconds
170+
* @see RequestConfig#getConnectionRequestTimeout()
157171
*/
158172
public void setConnectionRequestTimeout(int connectionRequestTimeout) {
159-
this.connectionRequestTimeout = connectionRequestTimeout;
173+
this.requestConfig = cloneRequestConfig()
174+
.setConnectionRequestTimeout(connectionRequestTimeout).build();
160175
}
161176

162177
/**
163178
* Set the socket read timeout for the underlying HttpClient.
164179
* A timeout value of 0 specifies an infinite timeout.
180+
* <p>Additional properties can be configured by specifying a
181+
* {@link RequestConfig} instance on a custom {@link HttpClient}.
165182
* @param timeout the timeout value in milliseconds
166183
* @see #DEFAULT_READ_TIMEOUT_MILLISECONDS
184+
* @see RequestConfig#getSocketTimeout()
167185
*/
168186
public void setReadTimeout(int timeout) {
169187
Assert.isTrue(timeout >= 0, "Timeout must be a non-negative value");
170-
this.readTimeout = timeout;
188+
this.requestConfig = cloneRequestConfig()
189+
.setSocketTimeout(timeout).build();
171190
setLegacySocketTimeout(getHttpClient(), timeout);
172191
}
173192

@@ -186,6 +205,10 @@ private void setLegacySocketTimeout(HttpClient client, int timeout) {
186205
}
187206
}
188207

208+
private RequestConfig.Builder cloneRequestConfig() {
209+
return this.requestConfig != null ? RequestConfig.copy(this.requestConfig) : RequestConfig.custom();
210+
}
211+
189212
/**
190213
* Execute the given request through the HttpClient.
191214
* <p>This method implements the basic processing workflow:
@@ -251,16 +274,7 @@ protected HttpPost createHttpPost(HttpInvokerClientConfiguration config) throws
251274
* @return the RequestConfig to use
252275
*/
253276
protected RequestConfig createRequestConfig(HttpInvokerClientConfiguration config) {
254-
if (this.connectionTimeout > 0 || this.connectionRequestTimeout > 0 || this.readTimeout > 0) {
255-
return RequestConfig.custom()
256-
.setConnectTimeout(this.connectionTimeout)
257-
.setConnectionRequestTimeout(this.connectionRequestTimeout)
258-
.setSocketTimeout(this.readTimeout)
259-
.build();
260-
}
261-
else {
262-
return RequestConfig.DEFAULT;
263-
}
277+
return (this.requestConfig != null ? this.requestConfig : null);
264278
}
265279

266280
/**

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

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,20 @@
1616

1717
package org.springframework.http.client;
1818

19-
import static org.junit.Assert.assertEquals;
20-
import static org.junit.Assert.assertNotNull;
21-
import static org.junit.Assert.assertTrue;
22-
2319
import java.net.URI;
2420

2521
import org.apache.http.HttpEntityEnclosingRequest;
2622
import org.apache.http.client.HttpClient;
2723
import org.apache.http.client.config.RequestConfig;
2824
import org.apache.http.client.methods.HttpUriRequest;
2925
import org.apache.http.client.protocol.HttpClientContext;
30-
import org.apache.http.impl.client.DefaultHttpClient;
26+
import org.apache.http.impl.client.CloseableHttpClient;
3127
import org.apache.http.impl.client.HttpClientBuilder;
32-
import org.apache.http.params.CoreConnectionPNames;
3328
import org.junit.Test;
3429
import org.springframework.http.HttpMethod;
3530

31+
import static org.junit.Assert.*;
32+
3633
public class HttpComponentsClientHttpRequestFactoryTests extends AbstractHttpRequestFactoryTestCase {
3734

3835
@Override
@@ -49,13 +46,15 @@ public void httpMethods() throws Exception {
4946
@SuppressWarnings("deprecation")
5047
@Test
5148
public void assertLegacyCustomConfig() {
52-
HttpClient httpClient = new DefaultHttpClient(); // Does not support RequestConfig
49+
HttpClient httpClient = new org.apache.http.impl.client.DefaultHttpClient(); // Does not support RequestConfig
5350
HttpComponentsClientHttpRequestFactory hrf = new HttpComponentsClientHttpRequestFactory(httpClient);
5451
hrf.setConnectTimeout(1234);
55-
assertEquals(1234, httpClient.getParams().getIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 0));
52+
assertEquals(1234, httpClient.getParams().getIntParameter(
53+
org.apache.http.params.CoreConnectionPNames.CONNECTION_TIMEOUT, 0));
5654

5755
hrf.setReadTimeout(4567);
58-
assertEquals(4567, httpClient.getParams().getIntParameter(CoreConnectionPNames.SO_TIMEOUT, 0));
56+
assertEquals(4567, httpClient.getParams().getIntParameter(
57+
org.apache.http.params.CoreConnectionPNames.SO_TIMEOUT, 0));
5958
}
6059

6160
@Test
@@ -80,6 +79,45 @@ public void assertCustomConfig() throws Exception {
8079
assertEquals("Wrong custom socket timeout", 4567, requestConfig.getSocketTimeout());
8180
}
8281

82+
@Test
83+
public void customHttpClientUsesItsDefault() throws Exception {
84+
HttpComponentsClientHttpRequestFactory hrf =
85+
new HttpComponentsClientHttpRequestFactory(HttpClientBuilder.create().build());
86+
87+
URI uri = new URI(baseUrl + "/status/ok");
88+
HttpComponentsClientHttpRequest request = (HttpComponentsClientHttpRequest)
89+
hrf.createRequest(uri, HttpMethod.GET);
90+
91+
assertNull("No custom config should be set with a custom HttpClient",
92+
request.getHttpContext().getAttribute(HttpClientContext.REQUEST_CONFIG));
93+
}
94+
95+
@Test
96+
public void defaultSettingsOfHttpClientLostOnExecutorCustomization() throws Exception {
97+
CloseableHttpClient client = HttpClientBuilder.create()
98+
.setDefaultRequestConfig(RequestConfig.custom().setConnectTimeout(1234).build())
99+
.build();
100+
HttpComponentsClientHttpRequestFactory hrf = new HttpComponentsClientHttpRequestFactory(client);
101+
102+
URI uri = new URI(baseUrl + "/status/ok");
103+
HttpComponentsClientHttpRequest request = (HttpComponentsClientHttpRequest)
104+
hrf.createRequest(uri, HttpMethod.GET);
105+
106+
assertNull("No custom config should be set with a custom HttpClient",
107+
request.getHttpContext().getAttribute(HttpClientContext.REQUEST_CONFIG));
108+
109+
hrf.setConnectionRequestTimeout(4567);
110+
HttpComponentsClientHttpRequest request2 = (HttpComponentsClientHttpRequest)
111+
hrf.createRequest(uri, HttpMethod.GET);
112+
Object requestConfigAttribute = request2.getHttpContext().getAttribute(HttpClientContext.REQUEST_CONFIG);
113+
assertNotNull(requestConfigAttribute);
114+
RequestConfig requestConfig = (RequestConfig) requestConfigAttribute;
115+
116+
assertEquals(4567, requestConfig.getConnectionRequestTimeout());
117+
// No way to access the request config of the HTTP client so no way to "merge" our customizations
118+
assertEquals(-1, requestConfig.getConnectTimeout());
119+
}
120+
83121
@Test
84122
public void createHttpUriRequest() throws Exception {
85123
URI uri = new URI("http://example.com");

spring-web/src/test/java/org/springframework/remoting/httpinvoker/HttpComponentsHttpInvokerRequestExecutorTests.java

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
1+
/*
2+
* Copyright 2002-2014 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
117
package org.springframework.remoting.httpinvoker;
218

319
import java.io.IOException;
@@ -63,6 +79,36 @@ public void customizeReadTimeout() throws IOException {
6379
assertEquals(10000, httpPost.getConfig().getSocketTimeout());
6480
}
6581

82+
@Test
83+
public void customHttpClientUsesItsDefault() throws IOException {
84+
HttpComponentsHttpInvokerRequestExecutor executor =
85+
new HttpComponentsHttpInvokerRequestExecutor(HttpClientBuilder.create().build());
86+
87+
HttpInvokerClientConfiguration config = mockHttpInvokerClientConfiguration("http://fake-service");
88+
HttpPost httpPost = executor.createHttpPost(config);
89+
assertNull("No custom config should be set with a custom HttpClient", httpPost.getConfig());
90+
}
91+
92+
@Test
93+
public void defaultSettingsOfHttpClientLostOnExecutorCustomization() throws IOException {
94+
CloseableHttpClient client = HttpClientBuilder.create()
95+
.setDefaultRequestConfig(RequestConfig.custom().setConnectTimeout(1234).build())
96+
.build();
97+
HttpComponentsHttpInvokerRequestExecutor executor =
98+
new HttpComponentsHttpInvokerRequestExecutor(client);
99+
100+
HttpInvokerClientConfiguration config = mockHttpInvokerClientConfiguration("http://fake-service");
101+
HttpPost httpPost = executor.createHttpPost(config);
102+
assertNull("No custom config should be set with a custom HttpClient", httpPost.getConfig());
103+
104+
executor.setConnectionRequestTimeout(4567);
105+
HttpPost httpPost2 = executor.createHttpPost(config);
106+
assertNotNull(httpPost2.getConfig());
107+
assertEquals(4567, httpPost2.getConfig().getConnectionRequestTimeout());
108+
// No way to access the request config of the HTTP client so no way to "merge" our customizations
109+
assertEquals(-1, httpPost2.getConfig().getConnectTimeout());
110+
}
111+
66112
@Test
67113
public void ignoreFactorySettings() throws IOException {
68114
CloseableHttpClient httpClient = HttpClientBuilder.create().build();

0 commit comments

Comments
 (0)