From 8f93c7f0f33c4cf00b53a0752145bdfbdff72e58 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Thu, 30 Jan 2014 11:22:54 +0100 Subject: [PATCH] HTTPCLIENT-1454: (re)Introducing HttpClientConnectionOperator in public API In some edge cases, possibility to provide own implementation of HttpClientConnectionOperator is desired, but in general, HttpClient users should not mangle it, as the default provided by client is completely satisfying. Dropped the old HttpClientConnectionOperator as it was package private already. --- .../http/conn/HttpClientConnectionOperator.java | 59 +++++++ .../conn/BasicHttpClientConnectionManager.java | 26 ++- .../conn/DefaultHttpClientConnectionOperator.java | 182 +++++++++++++++++++++ .../impl/conn/HttpClientConnectionOperator.java | 171 ------------------- .../conn/PoolingHttpClientConnectionManager.java | 31 +++- .../conn/TestHttpClientConnectionOperator.java | 4 +- 6 files changed, 286 insertions(+), 187 deletions(-) create mode 100644 httpclient/src/main/java/org/apache/http/conn/HttpClientConnectionOperator.java create mode 100644 httpclient/src/main/java/org/apache/http/impl/conn/DefaultHttpClientConnectionOperator.java delete mode 100644 httpclient/src/main/java/org/apache/http/impl/conn/HttpClientConnectionOperator.java diff --git a/httpclient/src/main/java/org/apache/http/conn/HttpClientConnectionOperator.java b/httpclient/src/main/java/org/apache/http/conn/HttpClientConnectionOperator.java new file mode 100644 index 0000000..dabebaa --- /dev/null +++ b/httpclient/src/main/java/org/apache/http/conn/HttpClientConnectionOperator.java @@ -0,0 +1,59 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.http.conn; + +import java.io.IOException; +import java.net.InetSocketAddress; + +import org.apache.http.HttpHost; +import org.apache.http.config.SocketConfig; +import org.apache.http.protocol.HttpContext; + +/** + * Connection operator that performs connecting and upgrading of the connections. Usually, components participating in + * these operations are registry of {@link org.apache.http.conn.socket.ConnectionSocketFactory}, {@link + * SchemePortResolver} and {@link DnsResolver}. In general, HTTP client user should not provide implementations of this + * interface, as Client will use the default one that covers most of the cases needed for majority of users. + * + * @since 4.4 + */ +public interface HttpClientConnectionOperator +{ + void connect( + final ManagedHttpClientConnection conn, + final HttpHost host, + final InetSocketAddress localAddress, + final int connectTimeout, + final SocketConfig socketConfig, + final HttpContext context) throws IOException; + + void upgrade( + final ManagedHttpClientConnection conn, + final HttpHost host, + final HttpContext context) throws IOException; +} diff --git a/httpclient/src/main/java/org/apache/http/impl/conn/BasicHttpClientConnectionManager.java b/httpclient/src/main/java/org/apache/http/impl/conn/BasicHttpClientConnectionManager.java index aa0dd54..ba3aac2 100644 --- a/httpclient/src/main/java/org/apache/http/impl/conn/BasicHttpClientConnectionManager.java +++ b/httpclient/src/main/java/org/apache/http/impl/conn/BasicHttpClientConnectionManager.java @@ -47,6 +47,7 @@ import org.apache.http.conn.ConnectionRequest; import org.apache.http.conn.DnsResolver; import org.apache.http.conn.HttpClientConnectionManager; +import org.apache.http.conn.HttpClientConnectionOperator; import org.apache.http.conn.HttpConnectionFactory; import org.apache.http.conn.SchemePortResolver; import org.apache.http.conn.ManagedHttpClientConnection; @@ -80,7 +81,7 @@ private final Log log = LogFactory.getLog(getClass()); - private final HttpClientConnectionOperator connectionOperator; + private final org.apache.http.conn.HttpClientConnectionOperator connectionOperator; private final HttpConnectionFactory connFactory; @GuardedBy("this") @@ -118,13 +119,24 @@ } public BasicHttpClientConnectionManager( - final Lookup socketFactoryRegistry, - final HttpConnectionFactory connFactory, - final SchemePortResolver schemePortResolver, - final DnsResolver dnsResolver) { + final Lookup socketFactoryRegistry, + final HttpConnectionFactory connFactory, + final SchemePortResolver schemePortResolver, + final DnsResolver dnsResolver) { + this( + new DefaultHttpClientConnectionOperator(socketFactoryRegistry, schemePortResolver, dnsResolver), + connFactory + ); + } + + /** + * @since 4.4 + */ + public BasicHttpClientConnectionManager( + final HttpClientConnectionOperator httpClientConnectionOperator, + final HttpConnectionFactory connFactory) { super(); - this.connectionOperator = new HttpClientConnectionOperator( - socketFactoryRegistry, schemePortResolver, dnsResolver); + this.connectionOperator = Args.notNull(httpClientConnectionOperator, "HttpClientConnectionOperator"); this.connFactory = connFactory != null ? connFactory : ManagedHttpClientConnectionFactory.INSTANCE; this.expiry = Long.MAX_VALUE; this.socketConfig = SocketConfig.DEFAULT; diff --git a/httpclient/src/main/java/org/apache/http/impl/conn/DefaultHttpClientConnectionOperator.java b/httpclient/src/main/java/org/apache/http/impl/conn/DefaultHttpClientConnectionOperator.java new file mode 100644 index 0000000..d0930b7 --- /dev/null +++ b/httpclient/src/main/java/org/apache/http/impl/conn/DefaultHttpClientConnectionOperator.java @@ -0,0 +1,182 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.http.impl.conn; + +import java.io.IOException; +import java.net.ConnectException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketTimeoutException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.HttpHost; +import org.apache.http.annotation.Immutable; +import org.apache.http.client.protocol.HttpClientContext; +import org.apache.http.config.Lookup; +import org.apache.http.config.SocketConfig; +import org.apache.http.conn.ConnectTimeoutException; +import org.apache.http.conn.DnsResolver; +import org.apache.http.conn.HttpClientConnectionManager; +import org.apache.http.conn.HttpClientConnectionOperator; +import org.apache.http.conn.HttpHostConnectException; +import org.apache.http.conn.ManagedHttpClientConnection; +import org.apache.http.conn.SchemePortResolver; +import org.apache.http.conn.UnsupportedSchemeException; +import org.apache.http.conn.socket.ConnectionSocketFactory; +import org.apache.http.conn.socket.LayeredConnectionSocketFactory; +import org.apache.http.protocol.HttpContext; +import org.apache.http.util.Args; + +/** + * Default implementation of {@link HttpClientConnectionOperator} used as default in Http client, + * when no instance provided by user to {@link BasicHttpClientConnectionManager} or {@link + * PoolingHttpClientConnectionManager} constructor. + * + * @since 4.4 + */ +@Immutable +public class DefaultHttpClientConnectionOperator implements HttpClientConnectionOperator +{ + + static final String SOCKET_FACTORY_REGISTRY = "http.socket-factory-registry"; + + private final Log log = LogFactory.getLog(HttpClientConnectionManager.class); + + private final Lookup socketFactoryRegistry; + private final SchemePortResolver schemePortResolver; + private final DnsResolver dnsResolver; + + DefaultHttpClientConnectionOperator( + final Lookup socketFactoryRegistry, + final SchemePortResolver schemePortResolver, + final DnsResolver dnsResolver) { + super(); + Args.notNull(socketFactoryRegistry, "Socket factory registry"); + this.socketFactoryRegistry = socketFactoryRegistry; + this.schemePortResolver = schemePortResolver != null ? schemePortResolver : + DefaultSchemePortResolver.INSTANCE; + this.dnsResolver = dnsResolver != null ? dnsResolver : + SystemDefaultDnsResolver.INSTANCE; + } + + @SuppressWarnings("unchecked") + private Lookup getSocketFactoryRegistry(final HttpContext context) { + Lookup reg = (Lookup) context.getAttribute( + SOCKET_FACTORY_REGISTRY); + if (reg == null) { + reg = this.socketFactoryRegistry; + } + return reg; + } + + @Override + public void connect( + final ManagedHttpClientConnection conn, + final HttpHost host, + final InetSocketAddress localAddress, + final int connectTimeout, + final SocketConfig socketConfig, + final HttpContext context) throws IOException { + final Lookup registry = getSocketFactoryRegistry(context); + final ConnectionSocketFactory sf = registry.lookup(host.getSchemeName()); + if (sf == null) { + throw new UnsupportedSchemeException(host.getSchemeName() + + " protocol is not supported"); + } + final InetAddress[] addresses = this.dnsResolver.resolve(host.getHostName()); + final int port = this.schemePortResolver.resolve(host); + for (int i = 0; i < addresses.length; i++) { + final InetAddress address = addresses[i]; + final boolean last = i == addresses.length - 1; + + Socket sock = sf.createSocket(context); + sock.setReuseAddress(socketConfig.isSoReuseAddress()); + conn.bind(sock); + + final InetSocketAddress remoteAddress = new InetSocketAddress(address, port); + if (this.log.isDebugEnabled()) { + this.log.debug("Connecting to " + remoteAddress); + } + try { + sock.setSoTimeout(socketConfig.getSoTimeout()); + sock = sf.connectSocket( + connectTimeout, sock, host, remoteAddress, localAddress, context); + sock.setTcpNoDelay(socketConfig.isTcpNoDelay()); + sock.setKeepAlive(socketConfig.isSoKeepAlive()); + final int linger = socketConfig.getSoLinger(); + if (linger >= 0) { + sock.setSoLinger(linger > 0, linger); + } + conn.bind(sock); + return; + } catch (final SocketTimeoutException ex) { + if (last) { + throw new ConnectTimeoutException(ex, host, addresses); + } + } catch (final ConnectException ex) { + if (last) { + final String msg = ex.getMessage(); + if ("Connection timed out".equals(msg)) { + throw new ConnectTimeoutException(ex, host, addresses); + } else { + throw new HttpHostConnectException(ex, host, addresses); + } + } + } + if (this.log.isDebugEnabled()) { + this.log.debug("Connect to " + remoteAddress + " timed out. " + + "Connection will be retried using another IP address"); + } + } + } + + @Override + public void upgrade( + final ManagedHttpClientConnection conn, + final HttpHost host, + final HttpContext context) throws IOException { + final HttpClientContext clientContext = HttpClientContext.adapt(context); + final Lookup registry = getSocketFactoryRegistry(clientContext); + final ConnectionSocketFactory sf = registry.lookup(host.getSchemeName()); + if (sf == null) { + throw new UnsupportedSchemeException(host.getSchemeName() + + " protocol is not supported"); + } + if (!(sf instanceof LayeredConnectionSocketFactory)) { + throw new UnsupportedSchemeException(host.getSchemeName() + + " protocol does not support connection upgrade"); + } + final LayeredConnectionSocketFactory lsf = (LayeredConnectionSocketFactory) sf; + Socket sock = conn.getSocket(); + final int port = this.schemePortResolver.resolve(host); + sock = lsf.createLayeredSocket(sock, host.getHostName(), port, context); + conn.bind(sock); + } + +} diff --git a/httpclient/src/main/java/org/apache/http/impl/conn/HttpClientConnectionOperator.java b/httpclient/src/main/java/org/apache/http/impl/conn/HttpClientConnectionOperator.java deleted file mode 100644 index 3a5b33d..0000000 --- a/httpclient/src/main/java/org/apache/http/impl/conn/HttpClientConnectionOperator.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * ==================================================================== - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * ==================================================================== - * - * This software consists of voluntary contributions made by many - * individuals on behalf of the Apache Software Foundation. For more - * information on the Apache Software Foundation, please see - * . - * - */ -package org.apache.http.impl.conn; - -import java.io.IOException; -import java.net.ConnectException; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.Socket; -import java.net.SocketTimeoutException; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.apache.http.HttpHost; -import org.apache.http.annotation.Immutable; -import org.apache.http.client.protocol.HttpClientContext; -import org.apache.http.config.Lookup; -import org.apache.http.config.SocketConfig; -import org.apache.http.conn.ConnectTimeoutException; -import org.apache.http.conn.DnsResolver; -import org.apache.http.conn.HttpClientConnectionManager; -import org.apache.http.conn.HttpHostConnectException; -import org.apache.http.conn.ManagedHttpClientConnection; -import org.apache.http.conn.SchemePortResolver; -import org.apache.http.conn.UnsupportedSchemeException; -import org.apache.http.conn.socket.ConnectionSocketFactory; -import org.apache.http.conn.socket.LayeredConnectionSocketFactory; -import org.apache.http.protocol.HttpContext; -import org.apache.http.util.Args; - -@Immutable -class HttpClientConnectionOperator { - - static final String SOCKET_FACTORY_REGISTRY = "http.socket-factory-registry"; - - private final Log log = LogFactory.getLog(HttpClientConnectionManager.class); - - private final Lookup socketFactoryRegistry; - private final SchemePortResolver schemePortResolver; - private final DnsResolver dnsResolver; - - HttpClientConnectionOperator( - final Lookup socketFactoryRegistry, - final SchemePortResolver schemePortResolver, - final DnsResolver dnsResolver) { - super(); - Args.notNull(socketFactoryRegistry, "Socket factory registry"); - this.socketFactoryRegistry = socketFactoryRegistry; - this.schemePortResolver = schemePortResolver != null ? schemePortResolver : - DefaultSchemePortResolver.INSTANCE; - this.dnsResolver = dnsResolver != null ? dnsResolver : - SystemDefaultDnsResolver.INSTANCE; - } - - @SuppressWarnings("unchecked") - private Lookup getSocketFactoryRegistry(final HttpContext context) { - Lookup reg = (Lookup) context.getAttribute( - SOCKET_FACTORY_REGISTRY); - if (reg == null) { - reg = this.socketFactoryRegistry; - } - return reg; - } - - public void connect( - final ManagedHttpClientConnection conn, - final HttpHost host, - final InetSocketAddress localAddress, - final int connectTimeout, - final SocketConfig socketConfig, - final HttpContext context) throws IOException { - final Lookup registry = getSocketFactoryRegistry(context); - final ConnectionSocketFactory sf = registry.lookup(host.getSchemeName()); - if (sf == null) { - throw new UnsupportedSchemeException(host.getSchemeName() + - " protocol is not supported"); - } - final InetAddress[] addresses = this.dnsResolver.resolve(host.getHostName()); - final int port = this.schemePortResolver.resolve(host); - for (int i = 0; i < addresses.length; i++) { - final InetAddress address = addresses[i]; - final boolean last = i == addresses.length - 1; - - Socket sock = sf.createSocket(context); - sock.setReuseAddress(socketConfig.isSoReuseAddress()); - conn.bind(sock); - - final InetSocketAddress remoteAddress = new InetSocketAddress(address, port); - if (this.log.isDebugEnabled()) { - this.log.debug("Connecting to " + remoteAddress); - } - try { - sock.setSoTimeout(socketConfig.getSoTimeout()); - sock = sf.connectSocket( - connectTimeout, sock, host, remoteAddress, localAddress, context); - sock.setTcpNoDelay(socketConfig.isTcpNoDelay()); - sock.setKeepAlive(socketConfig.isSoKeepAlive()); - final int linger = socketConfig.getSoLinger(); - if (linger >= 0) { - sock.setSoLinger(linger > 0, linger); - } - conn.bind(sock); - return; - } catch (final SocketTimeoutException ex) { - if (last) { - throw new ConnectTimeoutException(ex, host, addresses); - } - } catch (final ConnectException ex) { - if (last) { - final String msg = ex.getMessage(); - if ("Connection timed out".equals(msg)) { - throw new ConnectTimeoutException(ex, host, addresses); - } else { - throw new HttpHostConnectException(ex, host, addresses); - } - } - } - if (this.log.isDebugEnabled()) { - this.log.debug("Connect to " + remoteAddress + " timed out. " + - "Connection will be retried using another IP address"); - } - } - } - - public void upgrade( - final ManagedHttpClientConnection conn, - final HttpHost host, - final HttpContext context) throws IOException { - final HttpClientContext clientContext = HttpClientContext.adapt(context); - final Lookup registry = getSocketFactoryRegistry(clientContext); - final ConnectionSocketFactory sf = registry.lookup(host.getSchemeName()); - if (sf == null) { - throw new UnsupportedSchemeException(host.getSchemeName() + - " protocol is not supported"); - } - if (!(sf instanceof LayeredConnectionSocketFactory)) { - throw new UnsupportedSchemeException(host.getSchemeName() + - " protocol does not support connection upgrade"); - } - final LayeredConnectionSocketFactory lsf = (LayeredConnectionSocketFactory) sf; - Socket sock = conn.getSocket(); - final int port = this.schemePortResolver.resolve(host); - sock = lsf.createLayeredSocket(sock, host.getHostName(), port, context); - conn.bind(sock); - } - -} diff --git a/httpclient/src/main/java/org/apache/http/impl/conn/PoolingHttpClientConnectionManager.java b/httpclient/src/main/java/org/apache/http/impl/conn/PoolingHttpClientConnectionManager.java index ab00600..1f7c73b 100644 --- a/httpclient/src/main/java/org/apache/http/impl/conn/PoolingHttpClientConnectionManager.java +++ b/httpclient/src/main/java/org/apache/http/impl/conn/PoolingHttpClientConnectionManager.java @@ -50,6 +50,7 @@ import org.apache.http.conn.ConnectionRequest; import org.apache.http.conn.DnsResolver; import org.apache.http.conn.HttpClientConnectionManager; +import org.apache.http.conn.HttpClientConnectionOperator; import org.apache.http.conn.HttpConnectionFactory; import org.apache.http.conn.SchemePortResolver; import org.apache.http.conn.ManagedHttpClientConnection; @@ -142,14 +143,30 @@ public PoolingHttpClientConnectionManager( final SchemePortResolver schemePortResolver, final DnsResolver dnsResolver, final long timeToLive, final TimeUnit tunit) { - super(); - this.configData = new ConfigData(); - this.pool = new CPool( - new InternalConnectionFactory(this.configData, connFactory), 2, 20, timeToLive, tunit); - this.connectionOperator = new HttpClientConnectionOperator( - socketFactoryRegistry, schemePortResolver, dnsResolver); + this( + new DefaultHttpClientConnectionOperator(socketFactoryRegistry, schemePortResolver, dnsResolver), + connFactory, + timeToLive, tunit + ); } + /** + * @since 4.4 + */ + public PoolingHttpClientConnectionManager( + final org.apache.http.conn.HttpClientConnectionOperator httpClientConnectionOperator, + final HttpConnectionFactory connFactory, + final long timeToLive, final TimeUnit tunit) { + super(); + this.configData = new ConfigData(); + this.pool = new CPool( + new InternalConnectionFactory(this.configData, connFactory), 2, 20, timeToLive, tunit); + this.connectionOperator = Args.notNull(httpClientConnectionOperator, "HttpClientConnectionOperator"); + } + + /** + * Visible for test. + */ PoolingHttpClientConnectionManager( final CPool pool, final Lookup socketFactoryRegistry, @@ -158,7 +175,7 @@ public PoolingHttpClientConnectionManager( super(); this.configData = new ConfigData(); this.pool = pool; - this.connectionOperator = new HttpClientConnectionOperator( + this.connectionOperator = new DefaultHttpClientConnectionOperator( socketFactoryRegistry, schemePortResolver, dnsResolver); } diff --git a/httpclient/src/test/java/org/apache/http/impl/conn/TestHttpClientConnectionOperator.java b/httpclient/src/test/java/org/apache/http/impl/conn/TestHttpClientConnectionOperator.java index 3e70547..4621c12 100644 --- a/httpclient/src/test/java/org/apache/http/impl/conn/TestHttpClientConnectionOperator.java +++ b/httpclient/src/test/java/org/apache/http/impl/conn/TestHttpClientConnectionOperator.java @@ -59,7 +59,7 @@ private Lookup socketFactoryRegistry; private SchemePortResolver schemePortResolver; private DnsResolver dnsResolver; - private HttpClientConnectionOperator connectionOperator; + private DefaultHttpClientConnectionOperator connectionOperator; @SuppressWarnings("unchecked") @Before @@ -71,7 +71,7 @@ public void setup() throws Exception { socketFactoryRegistry = Mockito.mock(Lookup.class); schemePortResolver = Mockito.mock(SchemePortResolver.class); dnsResolver = Mockito.mock(DnsResolver.class); - connectionOperator = new HttpClientConnectionOperator( + connectionOperator = new DefaultHttpClientConnectionOperator( socketFactoryRegistry, schemePortResolver, dnsResolver); } -- 1.8.5.1