diff --git a/spring-web/src/main/java/org/springframework/remoting/httpinvoker/HttpComponentsHttpInvokerRequestExecutor.java b/spring-web/src/main/java/org/springframework/remoting/httpinvoker/HttpComponentsHttpInvokerRequestExecutor.java index a94039aeb95..1d020a15eff 100644 --- a/spring-web/src/main/java/org/springframework/remoting/httpinvoker/HttpComponentsHttpInvokerRequestExecutor.java +++ b/spring-web/src/main/java/org/springframework/remoting/httpinvoker/HttpComponentsHttpInvokerRequestExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,8 +27,16 @@ import org.apache.http.HttpResponse; import org.apache.http.NoHttpResponseException; import org.apache.http.StatusLine; import org.apache.http.client.HttpClient; +import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.HttpPost; +import org.apache.http.config.Registry; +import org.apache.http.config.RegistryBuilder; +import org.apache.http.conn.socket.ConnectionSocketFactory; +import org.apache.http.conn.socket.PlainConnectionSocketFactory; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.entity.ByteArrayEntity; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.springframework.context.i18n.LocaleContext; import org.springframework.context.i18n.LocaleContextHolder; @@ -45,13 +53,12 @@ import org.springframework.util.StringUtils; * instance, potentially with authentication, HTTP connection pooling, etc. * Also designed for easy subclassing, providing specific template methods. * - *
As of Spring 3.2, this request executor requires Apache HttpComponents 4.2 or higher. + *
As of Spring 4.1, this request executor requires Apache HttpComponents 4.3 or higher.
*
* @author Juergen Hoeller
* @since 3.1
* @see org.springframework.remoting.httpinvoker.SimpleHttpInvokerRequestExecutor
*/
-@SuppressWarnings("deprecation")
public class HttpComponentsHttpInvokerRequestExecutor extends AbstractHttpInvokerRequestExecutor {
private static final int DEFAULT_MAX_TOTAL_CONNECTIONS = 100;
@@ -61,6 +68,8 @@ public class HttpComponentsHttpInvokerRequestExecutor extends AbstractHttpInvoke
private static final int DEFAULT_READ_TIMEOUT_MILLISECONDS = (60 * 1000);
private HttpClient httpClient;
+ private int connectionTimeout = 0;
+ private int readTimeout = DEFAULT_READ_TIMEOUT_MILLISECONDS;
/**
@@ -68,17 +77,17 @@ public class HttpComponentsHttpInvokerRequestExecutor extends AbstractHttpInvoke
* {@link HttpClient} that uses a default {@code org.apache.http.impl.conn.PoolingClientConnectionManager}.
*/
public HttpComponentsHttpInvokerRequestExecutor() {
- org.apache.http.conn.scheme.SchemeRegistry schemeRegistry = new org.apache.http.conn.scheme.SchemeRegistry();
- schemeRegistry.register(new org.apache.http.conn.scheme.Scheme("http", 80, org.apache.http.conn.scheme.PlainSocketFactory.getSocketFactory()));
- schemeRegistry.register(new org.apache.http.conn.scheme.Scheme("https", 443, org.apache.http.conn.ssl.SSLSocketFactory.getSocketFactory()));
+ Registry As of HttpClient 4.3, default parameters have to be exposed through a
+ * {@link RequestConfig} instance instead of setting the parameters on the
+ * client. Unfortunately, this behavior is not backward-compatible and older
+ * {@link HttpClient} implementations will ignore the {@link RequestConfig}
+ * object set in the context.
+ * If the specified client is an older implementation, we set the custom
+ * connection timeout through the deprecated API. Otherwise, we just return
+ * as it is set through {@link RequestConfig} with newer clients.
+ * @param client the client to configure
+ * @param timeout the custom connection timeout
+ */
+ @SuppressWarnings("deprecation")
+ private void setLegacyConnectionTimeout(HttpClient client, int timeout) {
+ if (org.apache.http.impl.client.AbstractHttpClient.class.isInstance(client)) {
+ client.getParams().setIntParameter(
+ org.apache.http.params.CoreConnectionPNames.CONNECTION_TIMEOUT, timeout);
+ }
}
/**
@@ -123,9 +155,24 @@ public class HttpComponentsHttpInvokerRequestExecutor extends AbstractHttpInvoke
*/
public void setReadTimeout(int timeout) {
Assert.isTrue(timeout >= 0, "Timeout must be a non-negative value");
- getHttpClient().getParams().setIntParameter(org.apache.http.params.CoreConnectionPNames.SO_TIMEOUT, timeout);
+ this.readTimeout = timeout;
+ setLegacySocketTimeout(getHttpClient(), timeout);
}
+ /**
+ * Apply the specified socket timeout to deprecated {@link HttpClient}
+ * implementations. See {@link #setLegacyConnectionTimeout}.
+ * @param client the client to configure
+ * @param timeout the custom socket timeout
+ * @see #setLegacyConnectionTimeout
+ */
+ @SuppressWarnings("deprecation")
+ private void setLegacySocketTimeout(HttpClient client, int timeout) {
+ if (org.apache.http.impl.client.AbstractHttpClient.class.isInstance(client)) {
+ client.getParams().setIntParameter(
+ org.apache.http.params.CoreConnectionPNames.SO_TIMEOUT, timeout);
+ }
+ }
/**
* Execute the given request through the HttpClient.
@@ -166,6 +213,7 @@ public class HttpComponentsHttpInvokerRequestExecutor extends AbstractHttpInvoke
*/
protected HttpPost createHttpPost(HttpInvokerClientConfiguration config) throws IOException {
HttpPost httpPost = new HttpPost(config.getServiceUrl());
+ httpPost.setConfig(createRequestConfig(config, httpPost));
LocaleContext localeContext = LocaleContextHolder.getLocaleContext();
if (localeContext != null) {
Locale locale = localeContext.getLocale();
@@ -179,6 +227,25 @@ public class HttpComponentsHttpInvokerRequestExecutor extends AbstractHttpInvoke
return httpPost;
}
+ /**
+ * Create a {@link RequestConfig} for the given configuration and {@link HttpPost}.
+ * @param config the HTTP invoker configuration that specifies the
+ * target service
+ * @param httpPost the HttpPost instance
+ * @return the RequestConfig to use for that HttpPost
+ */
+ protected RequestConfig createRequestConfig(HttpInvokerClientConfiguration config, HttpPost httpPost) {
+ if (this.connectionTimeout > 0 || this.readTimeout > 0) {
+ return RequestConfig.custom()
+ .setConnectTimeout(this.connectionTimeout)
+ .setSocketTimeout(this.readTimeout)
+ .build();
+ }
+ else {
+ return RequestConfig.DEFAULT;
+ }
+ }
+
/**
* Set the given serialized remote invocation as request body.
* The default implementation simply sets the serialized invocation as the
diff --git a/spring-web/src/test/java/org/springframework/remoting/httpinvoker/HttpComponentsHttpInvokerRequestExecutorTests.java b/spring-web/src/test/java/org/springframework/remoting/httpinvoker/HttpComponentsHttpInvokerRequestExecutorTests.java
new file mode 100644
index 00000000000..e5f52958b04
--- /dev/null
+++ b/spring-web/src/test/java/org/springframework/remoting/httpinvoker/HttpComponentsHttpInvokerRequestExecutorTests.java
@@ -0,0 +1,43 @@
+package org.springframework.remoting.httpinvoker;
+
+import java.io.IOException;
+
+import org.apache.http.client.methods.HttpPost;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+/**
+ *
+ * @author Stephane Nicoll
+ */
+public class HttpComponentsHttpInvokerRequestExecutorTests {
+
+ @Test
+ public void customizeConnectionTimeout() throws IOException {
+ HttpComponentsHttpInvokerRequestExecutor executor = new HttpComponentsHttpInvokerRequestExecutor();
+ executor.setConnectTimeout(5000);
+
+ HttpInvokerClientConfiguration config = mockHttpInvokerClientConfiguration("http://fake-service");
+ HttpPost httpPost = executor.createHttpPost(config);
+ assertEquals(5000, httpPost.getConfig().getConnectTimeout());
+ }
+
+ @Test
+ public void customizeReadTimeout() throws IOException {
+ HttpComponentsHttpInvokerRequestExecutor executor = new HttpComponentsHttpInvokerRequestExecutor();
+ executor.setReadTimeout(10000);
+
+ HttpInvokerClientConfiguration config = mockHttpInvokerClientConfiguration("http://fake-service");
+ HttpPost httpPost = executor.createHttpPost(config);
+ assertEquals(10000, httpPost.getConfig().getSocketTimeout());
+ }
+
+ private HttpInvokerClientConfiguration mockHttpInvokerClientConfiguration(String serviceUrl) {
+ HttpInvokerClientConfiguration config = mock(HttpInvokerClientConfiguration.class);
+ when(config.getServiceUrl()).thenReturn(serviceUrl);
+ return config;
+ }
+
+}