diff --git a/httpclient/src/main/java/org/apache/http/impl/conn/DefaultClientConnectionOperator.java b/httpclient/src/main/java/org/apache/http/impl/conn/DefaultClientConnectionOperator.java
index 7286ecbb0..eb33d4b4d 100644
--- a/httpclient/src/main/java/org/apache/http/impl/conn/DefaultClientConnectionOperator.java
+++ b/httpclient/src/main/java/org/apache/http/impl/conn/DefaultClientConnectionOperator.java
@@ -32,7 +32,10 @@ import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.InetAddress;
+import java.net.UnknownHostException;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
import org.apache.http.annotation.ThreadSafe;
import org.apache.http.HttpHost;
@@ -40,6 +43,7 @@ import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.protocol.HttpContext;
+import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.HttpHostConnectException;
import org.apache.http.conn.OperatedClientConnection;
import org.apache.http.conn.ClientConnectionOperator;
@@ -49,8 +53,18 @@ import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.scheme.SchemeSocketFactory;
/**
- * Default implementation of a {@link ClientConnectionOperator}. It uses
- * a {@link SchemeRegistry} to look up {@link SchemeSocketFactory} objects.
+ * Default implementation of a {@link ClientConnectionOperator}. It uses a {@link SchemeRegistry}
+ * to look up {@link SchemeSocketFactory} objects.
+ *
+ * This connection operator is multihome network aware and will attempt to retry failed connects
+ * against all known IP addresses sequentially until the connect is successful or all known
+ * addresses fail to respond. Please note the same
+ * {@link org.apache.http.params.CoreConnectionPNames#CONNECTION_TIMEOUT} value will be used
+ * for each connection attempt, so in the worst case the total elapsed time before timeout
+ * can be CONNECTION_TIMEOUT * n
where n
is the number of IP addresses
+ * of the given host. One can disable multihome support by overriding
+ * the {@link #resolveHostname(String)} method and returning only one IP address for the given
+ * host name.
*
* The following parameters can be used to customize the behavior of this
* class:
@@ -58,6 +72,7 @@ import org.apache.http.conn.scheme.SchemeSocketFactory;
*
{@link org.apache.http.params.CoreProtocolPNames#HTTP_ELEMENT_CHARSET}
* {@link org.apache.http.params.CoreConnectionPNames#TCP_NODELAY}
* {@link org.apache.http.params.CoreConnectionPNames#SO_TIMEOUT}
+ * {@link org.apache.http.params.CoreConnectionPNames#CONNECTION_TIMEOUT}
* {@link org.apache.http.params.CoreConnectionPNames#SO_LINGER}
* {@link org.apache.http.params.CoreConnectionPNames#SOCKET_BUFFER_SIZE}
* {@link org.apache.http.params.CoreConnectionPNames#MAX_LINE_LENGTH}
@@ -68,6 +83,8 @@ import org.apache.http.conn.scheme.SchemeSocketFactory;
@ThreadSafe
public class DefaultClientConnectionOperator implements ClientConnectionOperator {
+ private final Log log = LogFactory.getLog(getClass());
+
/** The scheme registry for looking up socket factories. */
protected final SchemeRegistry schemeRegistry; // @ThreadSafe
@@ -76,92 +93,97 @@ public class DefaultClientConnectionOperator implements ClientConnectionOperator
*
* @param schemes the scheme registry
*/
- public DefaultClientConnectionOperator(SchemeRegistry schemes) {
+ public DefaultClientConnectionOperator(final SchemeRegistry schemes) {
if (schemes == null) {
- throw new IllegalArgumentException
- ("Scheme registry must not be null.");
+ throw new IllegalArgumentException("Scheme registry amy not be null");
}
- schemeRegistry = schemes;
+ this.schemeRegistry = schemes;
}
public OperatedClientConnection createConnection() {
return new DefaultClientConnection();
}
- public void openConnection(OperatedClientConnection conn,
- HttpHost target,
- InetAddress local,
- HttpContext context,
- HttpParams params)
- throws IOException {
-
+ public void openConnection(
+ final OperatedClientConnection conn,
+ final HttpHost target,
+ final InetAddress local,
+ final HttpContext context,
+ final HttpParams params) throws IOException {
if (conn == null) {
- throw new IllegalArgumentException
- ("Connection must not be null.");
+ throw new IllegalArgumentException("Connection may not be null");
}
if (target == null) {
- throw new IllegalArgumentException
- ("Target host must not be null.");
+ throw new IllegalArgumentException("Target host may not be null");
}
- // local address may be null
- //@@@ is context allowed to be null?
if (params == null) {
- throw new IllegalArgumentException
- ("Parameters must not be null.");
+ throw new IllegalArgumentException("Parameters may not be null");
}
if (conn.isOpen()) {
- throw new IllegalArgumentException
- ("Connection must not be open.");
+ throw new IllegalStateException("Connection must not be open");
}
Scheme schm = schemeRegistry.getScheme(target.getSchemeName());
SchemeSocketFactory sf = schm.getSchemeSocketFactory();
- Socket sock = sf.createSocket();
- conn.opening(sock, target);
-
- InetAddress address = InetAddress.getByName(target.getHostName());
+ InetAddress[] addresses = resolveHostname(target.getHostName());
int port = schm.resolvePort(target.getPort());
- InetSocketAddress remoteAddress = new InetSocketAddress(address, port);
- InetSocketAddress localAddress = null;
- if (local != null) {
- localAddress = new InetSocketAddress(local, 0);
- }
- try {
- Socket connsock = sf.connectSocket(sock, remoteAddress, localAddress, params);
- if (sock != connsock) {
- sock = connsock;
- conn.opening(sock, target);
+ for (int i = 0; i < addresses.length; i++) {
+ InetAddress address = addresses[i];
+ boolean last = i == addresses.length;
+
+ Socket sock = sf.createSocket();
+ conn.opening(sock, target);
+
+ InetSocketAddress remoteAddress = new InetSocketAddress(address, port);
+ InetSocketAddress localAddress = null;
+ if (local != null) {
+ localAddress = new InetSocketAddress(local, 0);
+ }
+ if (this.log.isDebugEnabled()) {
+ this.log.debug("Connecting to " + remoteAddress);
+ }
+ try {
+ Socket connsock = sf.connectSocket(sock, remoteAddress, localAddress, params);
+ if (sock != connsock) {
+ sock = connsock;
+ conn.opening(sock, target);
+ }
+ prepareSocket(sock, context, params);
+ conn.openCompleted(sf.isSecure(sock), params);
+ return;
+ } catch (ConnectException ex) {
+ if (last) {
+ throw new HttpHostConnectException(target, ex);
+ }
+ } catch (ConnectTimeoutException ex) {
+ if (last) {
+ throw ex;
+ }
+ }
+ if (this.log.isDebugEnabled()) {
+ this.log.debug("Connect to " + remoteAddress + " timed out. " +
+ "Connection will be retried using another IP address");
}
- } catch (ConnectException ex) {
- throw new HttpHostConnectException(target, ex);
}
- prepareSocket(sock, context, params);
- conn.openCompleted(sf.isSecure(sock), params);
}
- public void updateSecureConnection(OperatedClientConnection conn,
- HttpHost target,
- HttpContext context,
- HttpParams params)
- throws IOException {
-
-
+ public void updateSecureConnection(
+ final OperatedClientConnection conn,
+ final HttpHost target,
+ final HttpContext context,
+ final HttpParams params) throws IOException {
if (conn == null) {
- throw new IllegalArgumentException
- ("Connection must not be null.");
+ throw new IllegalArgumentException("Connection may not be null");
}
if (target == null) {
- throw new IllegalArgumentException
- ("Target host must not be null.");
+ throw new IllegalArgumentException("Target host may not be null");
}
if (params == null) {
- throw new IllegalArgumentException
- ("Parameters must not be null.");
+ throw new IllegalArgumentException("Parameters may not be null");
}
if (!conn.isOpen()) {
- throw new IllegalArgumentException
- ("Connection must be open.");
+ throw new IllegalStateException("Connection must be open");
}
final Scheme schm = schemeRegistry.getScheme(target.getSchemeName());
@@ -192,10 +214,10 @@ public class DefaultClientConnectionOperator implements ClientConnectionOperator
*
* @throws IOException in case of an IO problem
*/
- protected void prepareSocket(Socket sock, HttpContext context,
- HttpParams params)
- throws IOException {
-
+ protected void prepareSocket(
+ final Socket sock,
+ final HttpContext context,
+ final HttpParams params) throws IOException {
sock.setTcpNoDelay(HttpConnectionParams.getTcpNoDelay(params));
sock.setSoTimeout(HttpConnectionParams.getSoTimeout(params));
@@ -205,5 +227,19 @@ public class DefaultClientConnectionOperator implements ClientConnectionOperator
}
}
+ /**
+ * Resolves the given host name to an array of corresponding IP addresses, based on the
+ * configured name service on the system.
+ *
+ * @param host host name to resolve
+ * @return array of IP addresses
+ * @exception UnknownHostException if no IP address for the host could be determined.
+ *
+ * @since 4.1
+ */
+ protected InetAddress[] resolveHostname(final String host) throws UnknownHostException {
+ return InetAddress.getAllByName(host);
+ }
+
}