Skip to content

Commit

Permalink
Use custom implementation with patch from HTTPCLIENT-2288. Catch `Soc…
Browse files Browse the repository at this point in the history
…ketException` and retry with next resolved address. Fix #16723.
  • Loading branch information
dkocher committed Dec 29, 2024
1 parent 99a9529 commit 7963efd
Show file tree
Hide file tree
Showing 2 changed files with 170 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
package ch.cyberduck.core.http;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpHost;
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.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.impl.conn.DefaultSchemePortResolver;
import org.apache.http.impl.conn.SystemDefaultDnsResolver;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.Args;

import java.io.IOException;
import java.net.ConnectException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;

public class CustomHttpClientConnectionOperator implements HttpClientConnectionOperator {

static final String SOCKET_FACTORY_REGISTRY = "http.socket-factory-registry";

private final Log log = LogFactory.getLog(getClass());

private final Lookup<ConnectionSocketFactory> socketFactoryRegistry;
private final SchemePortResolver schemePortResolver;
private final DnsResolver dnsResolver;

public CustomHttpClientConnectionOperator(
final Lookup<ConnectionSocketFactory> 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;
}

private Lookup<ConnectionSocketFactory> getSocketFactoryRegistry(final HttpContext context) {
Lookup<ConnectionSocketFactory> reg = (Lookup<ConnectionSocketFactory>) 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<ConnectionSocketFactory> 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 = host.getAddress() != null ?
new InetAddress[]{host.getAddress()} : 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.setSoTimeout(socketConfig.getSoTimeout());
sock.setReuseAddress(socketConfig.isSoReuseAddress());
sock.setTcpNoDelay(socketConfig.isTcpNoDelay());
sock.setKeepAlive(socketConfig.isSoKeepAlive());
if(socketConfig.getRcvBufSize() > 0) {
sock.setReceiveBufferSize(socketConfig.getRcvBufSize());
}
if(socketConfig.getSndBufSize() > 0) {
sock.setSendBufferSize(socketConfig.getSndBufSize());
}

final int linger = socketConfig.getSoLinger();
if(linger >= 0) {
sock.setSoLinger(true, linger);
}
conn.bind(sock);

final InetSocketAddress remoteAddress = new InetSocketAddress(address, port);
if(this.log.isDebugEnabled()) {
this.log.debug("Connecting to " + remoteAddress);
}
try {
sock = sf.connectSocket(
connectTimeout, sock, host, remoteAddress, localAddress, context);
conn.bind(sock);
if(this.log.isDebugEnabled()) {
this.log.debug("Connection established " + conn);
}
return;
}
catch(final SocketTimeoutException ex) {
if(last) {
throw new ConnectTimeoutException(ex, host, addresses);
}
}
catch(final ConnectException ex) {
if(last) {
final String msg = ex.getMessage();
throw "Connection timed out".equals(msg)
? new ConnectTimeoutException(ex, host, addresses)
: new HttpHostConnectException(ex, host, addresses);
}
}
catch(final SocketException 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");
}
}
}

@Override
public void upgrade(
final ManagedHttpClientConnection conn,
final HttpHost host,
final HttpContext context) throws IOException {
final HttpClientContext clientContext = HttpClientContext.adapt(context);
final Lookup<ConnectionSocketFactory> 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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
import org.apache.http.impl.client.WinHttpClients;
import org.apache.http.impl.conn.DefaultRoutePlanner;
import org.apache.http.impl.conn.DefaultSchemePortResolver;
import org.apache.http.impl.conn.ManagedHttpClientConnectionFactory;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.protocol.HttpContext;
import org.apache.logging.log4j.LogManager;
Expand All @@ -68,6 +69,7 @@
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.charset.Charset;
import java.util.concurrent.TimeUnit;

public class HttpConnectionPoolBuilder {
private static final Logger log = LogManager.getLogger(HttpConnectionPoolBuilder.class);
Expand Down Expand Up @@ -139,9 +141,9 @@ public HttpConnectionPoolBuilder(final Host host,
}

/**
* @param proxyfinder Proxy configuration
* @param listener Log listener
* @param prompt Prompt for proxy credentials
* @param proxyfinder Proxy configuration
* @param listener Log listener
* @param prompt Prompt for proxy credentials
* @return Builder for HTTP client
*/
public HttpClientBuilder build(final ProxyFinder proxyfinder, final TranscriptListener listener, final LoginCallback prompt) {
Expand Down Expand Up @@ -238,7 +240,9 @@ public Registry<ConnectionSocketFactory> createRegistry() {

public PoolingHttpClientConnectionManager createConnectionManager(final Registry<ConnectionSocketFactory> registry) {
log.debug("Setup connection pool with registry {}", registry);
final PoolingHttpClientConnectionManager manager = new PoolingHttpClientConnectionManager(registry, new CustomDnsResolver());
final PoolingHttpClientConnectionManager manager = new PoolingHttpClientConnectionManager(
new CustomHttpClientConnectionOperator(registry, DefaultSchemePortResolver.INSTANCE, new CustomDnsResolver()),
ManagedHttpClientConnectionFactory.INSTANCE, -1, TimeUnit.MILLISECONDS);
manager.setMaxTotal(new HostPreferences(host).getInteger("http.connections.total"));
manager.setDefaultMaxPerRoute(new HostPreferences(host).getInteger("http.connections.route"));
// Detect connections that have become stale (half-closed) while kept inactive in the pool
Expand Down

0 comments on commit 7963efd

Please sign in to comment.