Skip to content

Commit fbe6051

Browse files
committed
Upgrade HttpComponentsHIRE to HttpComponents 4.3
Previously, HttpComponentsHttpInvokerRequestExecutor was not compatible with the new API of HttpComponents 4.3. Specifically, it is not possible to update the socket and read timeouts on the HttpClient itself anymore. We actually already updated HttpComponentsClientHttpRequestFactory for a similar problem in SPR-11442: if we detect an older HttpClient implementation, we update the timeout directly on the client. If that's not the case, we keep the value in the factory itself and use it when a new HttpRequest needs to be created. This commit also uses the new API to create a default HttpClient and therefore requires HttpComponents 4.3. As mentioned above, it is still possible to use deprecated HttpClient instances against this executor. Issue: SPR-11113
1 parent abc3cc4 commit fbe6051

File tree

2 files changed

+122
-12
lines changed

2 files changed

+122
-12
lines changed

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

Lines changed: 79 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2014 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.
@@ -27,8 +27,16 @@
2727
import org.apache.http.NoHttpResponseException;
2828
import org.apache.http.StatusLine;
2929
import org.apache.http.client.HttpClient;
30+
import org.apache.http.client.config.RequestConfig;
3031
import org.apache.http.client.methods.HttpPost;
32+
import org.apache.http.config.Registry;
33+
import org.apache.http.config.RegistryBuilder;
34+
import org.apache.http.conn.socket.ConnectionSocketFactory;
35+
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
36+
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
3137
import org.apache.http.entity.ByteArrayEntity;
38+
import org.apache.http.impl.client.HttpClientBuilder;
39+
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
3240

3341
import org.springframework.context.i18n.LocaleContext;
3442
import org.springframework.context.i18n.LocaleContextHolder;
@@ -45,13 +53,12 @@
4553
* instance, potentially with authentication, HTTP connection pooling, etc.
4654
* Also designed for easy subclassing, providing specific template methods.
4755
*
48-
* <p>As of Spring 3.2, this request executor requires Apache HttpComponents 4.2 or higher.
56+
* <p>As of Spring 4.1, this request executor requires Apache HttpComponents 4.3 or higher.
4957
*
5058
* @author Juergen Hoeller
5159
* @since 3.1
5260
* @see org.springframework.remoting.httpinvoker.SimpleHttpInvokerRequestExecutor
5361
*/
54-
@SuppressWarnings("deprecation")
5562
public class HttpComponentsHttpInvokerRequestExecutor extends AbstractHttpInvokerRequestExecutor {
5663

5764
private static final int DEFAULT_MAX_TOTAL_CONNECTIONS = 100;
@@ -61,24 +68,26 @@ public class HttpComponentsHttpInvokerRequestExecutor extends AbstractHttpInvoke
6168
private static final int DEFAULT_READ_TIMEOUT_MILLISECONDS = (60 * 1000);
6269

6370
private HttpClient httpClient;
71+
private int connectionTimeout = 0;
72+
private int readTimeout = DEFAULT_READ_TIMEOUT_MILLISECONDS;
6473

6574

6675
/**
6776
* Create a new instance of the HttpComponentsHttpInvokerRequestExecutor with a default
6877
* {@link HttpClient} that uses a default {@code org.apache.http.impl.conn.PoolingClientConnectionManager}.
6978
*/
7079
public HttpComponentsHttpInvokerRequestExecutor() {
71-
org.apache.http.conn.scheme.SchemeRegistry schemeRegistry = new org.apache.http.conn.scheme.SchemeRegistry();
72-
schemeRegistry.register(new org.apache.http.conn.scheme.Scheme("http", 80, org.apache.http.conn.scheme.PlainSocketFactory.getSocketFactory()));
73-
schemeRegistry.register(new org.apache.http.conn.scheme.Scheme("https", 443, org.apache.http.conn.ssl.SSLSocketFactory.getSocketFactory()));
80+
Registry<ConnectionSocketFactory> schemeRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
81+
.register("http", PlainConnectionSocketFactory.getSocketFactory())
82+
.register("https", SSLConnectionSocketFactory.getSocketFactory())
83+
.build();
7484

75-
org.apache.http.impl.conn.PoolingClientConnectionManager connectionManager =
76-
new org.apache.http.impl.conn.PoolingClientConnectionManager(schemeRegistry);
85+
PoolingHttpClientConnectionManager connectionManager
86+
= new PoolingHttpClientConnectionManager(schemeRegistry);
7787
connectionManager.setMaxTotal(DEFAULT_MAX_TOTAL_CONNECTIONS);
7888
connectionManager.setDefaultMaxPerRoute(DEFAULT_MAX_CONNECTIONS_PER_ROUTE);
7989

80-
this.httpClient = new org.apache.http.impl.client.DefaultHttpClient(connectionManager);
81-
setReadTimeout(DEFAULT_READ_TIMEOUT_MILLISECONDS);
90+
this.httpClient = HttpClientBuilder.create().setConnectionManager(connectionManager).build();
8291
}
8392

8493
/**
@@ -112,7 +121,30 @@ public HttpClient getHttpClient() {
112121
*/
113122
public void setConnectTimeout(int timeout) {
114123
Assert.isTrue(timeout >= 0, "Timeout must be a non-negative value");
115-
getHttpClient().getParams().setIntParameter(org.apache.http.params.CoreConnectionPNames.CONNECTION_TIMEOUT, timeout);
124+
this.connectionTimeout = timeout;
125+
setLegacyConnectionTimeout(getHttpClient(), timeout);
126+
}
127+
128+
/**
129+
* Apply the specified connection timeout to deprecated {@link HttpClient}
130+
* implementations.
131+
* <p>As of HttpClient 4.3, default parameters have to be exposed through a
132+
* {@link RequestConfig} instance instead of setting the parameters on the
133+
* client. Unfortunately, this behavior is not backward-compatible and older
134+
* {@link HttpClient} implementations will ignore the {@link RequestConfig}
135+
* object set in the context.
136+
* <p>If the specified client is an older implementation, we set the custom
137+
* connection timeout through the deprecated API. Otherwise, we just return
138+
* as it is set through {@link RequestConfig} with newer clients.
139+
* @param client the client to configure
140+
* @param timeout the custom connection timeout
141+
*/
142+
@SuppressWarnings("deprecation")
143+
private void setLegacyConnectionTimeout(HttpClient client, int timeout) {
144+
if (org.apache.http.impl.client.AbstractHttpClient.class.isInstance(client)) {
145+
client.getParams().setIntParameter(
146+
org.apache.http.params.CoreConnectionPNames.CONNECTION_TIMEOUT, timeout);
147+
}
116148
}
117149

118150
/**
@@ -123,9 +155,24 @@ public void setConnectTimeout(int timeout) {
123155
*/
124156
public void setReadTimeout(int timeout) {
125157
Assert.isTrue(timeout >= 0, "Timeout must be a non-negative value");
126-
getHttpClient().getParams().setIntParameter(org.apache.http.params.CoreConnectionPNames.SO_TIMEOUT, timeout);
158+
this.readTimeout = timeout;
159+
setLegacySocketTimeout(getHttpClient(), timeout);
127160
}
128161

162+
/**
163+
* Apply the specified socket timeout to deprecated {@link HttpClient}
164+
* implementations. See {@link #setLegacyConnectionTimeout}.
165+
* @param client the client to configure
166+
* @param timeout the custom socket timeout
167+
* @see #setLegacyConnectionTimeout
168+
*/
169+
@SuppressWarnings("deprecation")
170+
private void setLegacySocketTimeout(HttpClient client, int timeout) {
171+
if (org.apache.http.impl.client.AbstractHttpClient.class.isInstance(client)) {
172+
client.getParams().setIntParameter(
173+
org.apache.http.params.CoreConnectionPNames.SO_TIMEOUT, timeout);
174+
}
175+
}
129176

130177
/**
131178
* Execute the given request through the HttpClient.
@@ -166,6 +213,7 @@ protected RemoteInvocationResult doExecuteRequest(
166213
*/
167214
protected HttpPost createHttpPost(HttpInvokerClientConfiguration config) throws IOException {
168215
HttpPost httpPost = new HttpPost(config.getServiceUrl());
216+
httpPost.setConfig(createRequestConfig(config, httpPost));
169217
LocaleContext localeContext = LocaleContextHolder.getLocaleContext();
170218
if (localeContext != null) {
171219
Locale locale = localeContext.getLocale();
@@ -179,6 +227,25 @@ protected HttpPost createHttpPost(HttpInvokerClientConfiguration config) throws
179227
return httpPost;
180228
}
181229

230+
/**
231+
* Create a {@link RequestConfig} for the given configuration and {@link HttpPost}.
232+
* @param config the HTTP invoker configuration that specifies the
233+
* target service
234+
* @param httpPost the HttpPost instance
235+
* @return the RequestConfig to use for that HttpPost
236+
*/
237+
protected RequestConfig createRequestConfig(HttpInvokerClientConfiguration config, HttpPost httpPost) {
238+
if (this.connectionTimeout > 0 || this.readTimeout > 0) {
239+
return RequestConfig.custom()
240+
.setConnectTimeout(this.connectionTimeout)
241+
.setSocketTimeout(this.readTimeout)
242+
.build();
243+
}
244+
else {
245+
return RequestConfig.DEFAULT;
246+
}
247+
}
248+
182249
/**
183250
* Set the given serialized remote invocation as request body.
184251
* <p>The default implementation simply sets the serialized invocation as the
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package org.springframework.remoting.httpinvoker;
2+
3+
import java.io.IOException;
4+
5+
import org.apache.http.client.methods.HttpPost;
6+
import org.junit.Test;
7+
8+
import static org.junit.Assert.*;
9+
import static org.mockito.Mockito.*;
10+
11+
/**
12+
*
13+
* @author Stephane Nicoll
14+
*/
15+
public class HttpComponentsHttpInvokerRequestExecutorTests {
16+
17+
@Test
18+
public void customizeConnectionTimeout() throws IOException {
19+
HttpComponentsHttpInvokerRequestExecutor executor = new HttpComponentsHttpInvokerRequestExecutor();
20+
executor.setConnectTimeout(5000);
21+
22+
HttpInvokerClientConfiguration config = mockHttpInvokerClientConfiguration("http://fake-service");
23+
HttpPost httpPost = executor.createHttpPost(config);
24+
assertEquals(5000, httpPost.getConfig().getConnectTimeout());
25+
}
26+
27+
@Test
28+
public void customizeReadTimeout() throws IOException {
29+
HttpComponentsHttpInvokerRequestExecutor executor = new HttpComponentsHttpInvokerRequestExecutor();
30+
executor.setReadTimeout(10000);
31+
32+
HttpInvokerClientConfiguration config = mockHttpInvokerClientConfiguration("http://fake-service");
33+
HttpPost httpPost = executor.createHttpPost(config);
34+
assertEquals(10000, httpPost.getConfig().getSocketTimeout());
35+
}
36+
37+
private HttpInvokerClientConfiguration mockHttpInvokerClientConfiguration(String serviceUrl) {
38+
HttpInvokerClientConfiguration config = mock(HttpInvokerClientConfiguration.class);
39+
when(config.getServiceUrl()).thenReturn(serviceUrl);
40+
return config;
41+
}
42+
43+
}

0 commit comments

Comments
 (0)