Redesign of classic (blocking) connection management APIs

git-svn-id: https://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk@1784138 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Oleg Kalnichevski 2017-02-23 14:32:24 +00:00
parent 7637761238
commit 564537aebc
62 changed files with 1523 additions and 2760 deletions

View File

@ -34,7 +34,6 @@
import org.apache.hc.client5.http.osgi.services.ProxyConfiguration; import org.apache.hc.client5.http.osgi.services.ProxyConfiguration;
import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpException;
import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.protocol.HttpContext;
/** /**
@ -53,7 +52,7 @@ public OSGiHttpRoutePlanner(final List<ProxyConfiguration> proxyConfigurations)
* {@inheritDoc} * {@inheritDoc}
*/ */
@Override @Override
protected HttpHost determineProxy(final HttpHost target, final HttpRequest request, final HttpContext context) throws HttpException { protected HttpHost determineProxy(final HttpHost target, final HttpContext context) throws HttpException {
HttpHost proxyHost = null; HttpHost proxyHost = null;
for (final ProxyConfiguration proxyConfiguration : proxyConfigurations) { for (final ProxyConfiguration proxyConfiguration : proxyConfigurations) {
if (proxyConfiguration.isEnabled()) { if (proxyConfiguration.isEnabled()) {

View File

@ -51,41 +51,41 @@ public class TestOSGiHttpRoutePlanner {
public void testDeterminProxy() throws Exception { public void testDeterminProxy() throws Exception {
OSGiHttpRoutePlanner planner = new OSGiHttpRoutePlanner(singletonList(pc1)); OSGiHttpRoutePlanner planner = new OSGiHttpRoutePlanner(singletonList(pc1));
HttpHost proxy = planner.determineProxy(new HttpHost("localhost", 8090), null, null); HttpHost proxy = planner.determineProxy(new HttpHost("localhost", 8090), null);
assertNull(proxy); assertNull(proxy);
proxy = planner.determineProxy(new HttpHost("there", 9090), null, null); proxy = planner.determineProxy(new HttpHost("there", 9090), null);
assertNotNull(proxy); assertNotNull(proxy);
assertTrue(proxy.getHostName().equals("proxy1")); assertTrue(proxy.getHostName().equals("proxy1"));
proxy = planner.determineProxy(new HttpHost("10.2.144.23", 4554), null, null); proxy = planner.determineProxy(new HttpHost("10.2.144.23", 4554), null);
assertNotNull(proxy); assertNotNull(proxy);
assertTrue(proxy.getHostName().equals("proxy1")); assertTrue(proxy.getHostName().equals("proxy1"));
final InetAddress addr = InetAddress.getByName("localhost"); final InetAddress addr = InetAddress.getByName("localhost");
proxy = planner.determineProxy(new HttpHost(addr, 4554), null, null); proxy = planner.determineProxy(new HttpHost(addr, 4554), null);
assertNull(proxy); assertNull(proxy);
proxy = planner.determineProxy(new HttpHost("hc.apache.org", 4554), null, null); proxy = planner.determineProxy(new HttpHost("hc.apache.org", 4554), null);
assertNull(proxy); assertNull(proxy);
// test with more than one registration of proxyConfiguration // test with more than one registration of proxyConfiguration
planner = new OSGiHttpRoutePlanner(asList(pc1, pc2)); planner = new OSGiHttpRoutePlanner(asList(pc1, pc2));
proxy = planner.determineProxy(new HttpHost("localhost", 8090), null, null); proxy = planner.determineProxy(new HttpHost("localhost", 8090), null);
assertNull(proxy); assertNull(proxy);
proxy = planner.determineProxy(new HttpHost("there", 9090), null, null); proxy = planner.determineProxy(new HttpHost("there", 9090), null);
assertNotNull(proxy); assertNotNull(proxy);
assertTrue(proxy.getHostName().equals("proxy1")); // the first one assertTrue(proxy.getHostName().equals("proxy1")); // the first one
proxy = planner.determineProxy(new HttpHost(addr, 4554), null, null); proxy = planner.determineProxy(new HttpHost(addr, 4554), null);
assertNull(proxy); assertNull(proxy);
proxy = planner.determineProxy(new HttpHost("hc.apache.org", 4554), null, null); proxy = planner.determineProxy(new HttpHost("hc.apache.org", 4554), null);
assertNull(proxy); assertNull(proxy);
proxy = planner.determineProxy(new HttpHost("docs.oracle.com", 4554), null, null); proxy = planner.determineProxy(new HttpHost("docs.oracle.com", 4554), null);
assertNull(proxy); assertNull(proxy);
} }
@ -93,11 +93,11 @@ public void testDeterminProxy() throws Exception {
public void testMasking() throws Exception { public void testMasking() throws Exception {
final OSGiHttpRoutePlanner planner = new OSGiHttpRoutePlanner(singletonList(pc2)); final OSGiHttpRoutePlanner planner = new OSGiHttpRoutePlanner(singletonList(pc2));
HttpHost proxy = planner.determineProxy(new HttpHost("12.34.34.2", 4554), null, null); HttpHost proxy = planner.determineProxy(new HttpHost("12.34.34.2", 4554), null);
assertNotNull(proxy); assertNotNull(proxy);
assertTrue(proxy.getHostName().equals("proxy2")); assertTrue(proxy.getHostName().equals("proxy2"));
proxy = planner.determineProxy(new HttpHost("12.34.34.8", 4554), null, null); proxy = planner.determineProxy(new HttpHost("12.34.34.8", 4554), null);
assertNotNull(proxy); assertNotNull(proxy);
} }

View File

@ -36,7 +36,6 @@
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLContext;
import org.apache.hc.client5.http.DnsResolver; import org.apache.hc.client5.http.DnsResolver;
import org.apache.hc.client5.http.HttpConnectionFactory;
import org.apache.hc.client5.http.HttpRoute; import org.apache.hc.client5.http.HttpRoute;
import org.apache.hc.client5.http.SystemDefaultDnsResolver; import org.apache.hc.client5.http.SystemDefaultDnsResolver;
import org.apache.hc.client5.http.auth.CredentialsProvider; import org.apache.hc.client5.http.auth.CredentialsProvider;
@ -45,15 +44,13 @@
import org.apache.hc.client5.http.config.RequestConfig; import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.cookie.BasicCookieStore; import org.apache.hc.client5.http.cookie.BasicCookieStore;
import org.apache.hc.client5.http.cookie.CookieStore; import org.apache.hc.client5.http.cookie.CookieStore;
import org.apache.hc.client5.http.impl.io.DefaultHttpResponseParserFactory;
import org.apache.hc.client5.http.impl.io.LenientHttpResponseParser;
import org.apache.hc.client5.http.impl.io.ManagedHttpClientConnectionFactory; import org.apache.hc.client5.http.impl.io.ManagedHttpClientConnectionFactory;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager; import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.impl.sync.BasicCredentialsProvider; import org.apache.hc.client5.http.impl.sync.BasicCredentialsProvider;
import org.apache.hc.client5.http.impl.sync.CloseableHttpClient; import org.apache.hc.client5.http.impl.sync.CloseableHttpClient;
import org.apache.hc.client5.http.impl.sync.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.sync.HttpClients; import org.apache.hc.client5.http.impl.sync.HttpClients;
import org.apache.hc.client5.http.io.ManagedHttpClientConnection; import org.apache.hc.client5.http.io.ManagedHttpClientConnection;
import org.apache.hc.client5.http.impl.sync.CloseableHttpResponse;
import org.apache.hc.client5.http.methods.HttpGet; import org.apache.hc.client5.http.methods.HttpGet;
import org.apache.hc.client5.http.protocol.HttpClientContext; import org.apache.hc.client5.http.protocol.HttpClientContext;
import org.apache.hc.client5.http.socket.ConnectionSocketFactory; import org.apache.hc.client5.http.socket.ConnectionSocketFactory;
@ -71,6 +68,9 @@
import org.apache.hc.core5.http.config.SocketConfig; import org.apache.hc.core5.http.config.SocketConfig;
import org.apache.hc.core5.http.impl.io.DefaultClassicHttpResponseFactory; import org.apache.hc.core5.http.impl.io.DefaultClassicHttpResponseFactory;
import org.apache.hc.core5.http.impl.io.DefaultHttpRequestWriterFactory; import org.apache.hc.core5.http.impl.io.DefaultHttpRequestWriterFactory;
import org.apache.hc.core5.http.impl.io.DefaultHttpResponseParser;
import org.apache.hc.core5.http.impl.io.DefaultHttpResponseParserFactory;
import org.apache.hc.core5.http.io.HttpConnectionFactory;
import org.apache.hc.core5.http.io.HttpMessageParser; import org.apache.hc.core5.http.io.HttpMessageParser;
import org.apache.hc.core5.http.io.HttpMessageParserFactory; import org.apache.hc.core5.http.io.HttpMessageParserFactory;
import org.apache.hc.core5.http.io.HttpMessageWriterFactory; import org.apache.hc.core5.http.io.HttpMessageWriterFactory;
@ -107,18 +107,30 @@ public Header parseHeader(final CharArrayBuffer buffer) {
} }
}; };
return new LenientHttpResponseParser(lineParser, DefaultClassicHttpResponseFactory.INSTANCE, h1Config); return new DefaultHttpResponseParser(lineParser, DefaultClassicHttpResponseFactory.INSTANCE, h1Config);
} }
}; };
HttpMessageWriterFactory<ClassicHttpRequest> requestWriterFactory = new DefaultHttpRequestWriterFactory(); HttpMessageWriterFactory<ClassicHttpRequest> requestWriterFactory = new DefaultHttpRequestWriterFactory();
// Create HTTP/1.1 protocol configuration
H1Config h1Config = H1Config.custom()
.setMaxHeaderCount(200)
.setMaxLineLength(2000)
.build();
// Create connection configuration
ConnectionConfig connectionConfig = ConnectionConfig.custom()
.setMalformedInputAction(CodingErrorAction.IGNORE)
.setUnmappableInputAction(CodingErrorAction.IGNORE)
.setCharset(StandardCharsets.UTF_8)
.build();
// Use a custom connection factory to customize the process of // Use a custom connection factory to customize the process of
// initialization of outgoing HTTP connections. Beside standard connection // initialization of outgoing HTTP connections. Beside standard connection
// configuration parameters HTTP connection factory can define message // configuration parameters HTTP connection factory can define message
// parser / writer routines to be employed by individual connections. // parser / writer routines to be employed by individual connections.
HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory = new ManagedHttpClientConnectionFactory( HttpConnectionFactory<ManagedHttpClientConnection> connFactory = new ManagedHttpClientConnectionFactory(
H1Config.DEFAULT, requestWriterFactory, responseParserFactory); h1Config, connectionConfig, requestWriterFactory, responseParserFactory);
// Client HTTP connection objects when fully initialized can be bound to // Client HTTP connection objects when fully initialized can be bound to
// an arbitrary network socket. The process of network socket initialization, // an arbitrary network socket. The process of network socket initialization,
@ -161,26 +173,9 @@ public InetAddress[] resolve(final String host) throws UnknownHostException {
// Configure the connection manager to use socket configuration either // Configure the connection manager to use socket configuration either
// by default or for a specific host. // by default or for a specific host.
connManager.setDefaultSocketConfig(socketConfig); connManager.setDefaultSocketConfig(socketConfig);
connManager.setSocketConfig(new HttpHost("somehost", 80), socketConfig);
// Validate connections after 1 sec of inactivity // Validate connections after 1 sec of inactivity
connManager.setValidateAfterInactivity(1000); connManager.setValidateAfterInactivity(1000);
// Create message constraints
H1Config messageConstraints = H1Config.custom()
.setMaxHeaderCount(200)
.setMaxLineLength(2000)
.build();
// Create connection configuration
ConnectionConfig connectionConfig = ConnectionConfig.custom()
.setMalformedInputAction(CodingErrorAction.IGNORE)
.setUnmappableInputAction(CodingErrorAction.IGNORE)
.setCharset(StandardCharsets.UTF_8)
.build();
// Configure the connection manager to use connection configuration either
// by default or for a specific host.
connManager.setDefaultConnectionConfig(connectionConfig);
connManager.setConnectionConfig(new HttpHost("somehost", 80), ConnectionConfig.DEFAULT);
// Configure total max or per route limits for persistent connections // Configure total max or per route limits for persistent connections
// that can be kept in the pool or leased by the connection manager. // that can be kept in the pool or leased by the connection manager.
connManager.setMaxTotal(100); connManager.setMaxTotal(100);

View File

@ -0,0 +1,70 @@
/*
* ====================================================================
* 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
* <http://www.apache.org/>.
*
*/
package org.apache.hc.client5.http.impl;
import org.apache.hc.client5.http.HttpRoute;
import org.apache.hc.client5.http.utils.Identifiable;
import org.apache.hc.core5.pool.ConnPoolControl;
import org.apache.hc.core5.pool.PoolStats;
public final class ConnPoolSupport {
public static String getId(final Object object) {
if (object == null) {
return null;
}
if (object instanceof Identifiable) {
return ((Identifiable) object).getId();
} else {
return Integer.toHexString(System.identityHashCode(object));
}
}
public static String formatStats(
final Object object,
final HttpRoute route,
final Object state,
final ConnPoolControl<HttpRoute> connPool) {
final StringBuilder buf = new StringBuilder();
if (object != null) {
buf.append("[id: ").append(getId(object)).append("]");
}
buf.append("[route: ").append(route).append("]");
if (state != null) {
buf.append("[state: ").append(state).append("]");
}
final PoolStats totals = connPool.getTotalStats();
final PoolStats stats = connPool.getStats(route);
buf.append("[total kept alive: ").append(totals.getAvailable()).append("; ");
buf.append("route allocated: ").append(stats.getLeased() + stats.getAvailable());
buf.append(" of ").append(stats.getMax()).append("; ");
buf.append("total allocated: ").append(totals.getLeased() + totals.getAvailable());
buf.append(" of ").append(totals.getMax()).append("]");
return buf.toString();
}
}

View File

@ -25,7 +25,7 @@
* *
*/ */
package org.apache.hc.client5.http.impl.io; package org.apache.hc.client5.http.impl;
/** /**
* Signals that the connection has been shut down or released back to the * Signals that the connection has been shut down or released back to the

View File

@ -0,0 +1,64 @@
/*
* ====================================================================
* 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
* <http://www.apache.org/>.
*
*/
package org.apache.hc.client5.http.impl;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicLong;
/**
* @since 5.0
*/
public class DefaultThreadFactory implements ThreadFactory {
private final String namePrefix;
private final ThreadGroup group;
private final AtomicLong count;
private final boolean daemon;
public DefaultThreadFactory(final String namePrefix, final ThreadGroup group, final boolean daemon) {
this.namePrefix = namePrefix;
this.group = group;
this.count = new AtomicLong();
this.daemon = daemon;
}
public DefaultThreadFactory(final String namePrefix) {
this(namePrefix, null, false);
}
public DefaultThreadFactory(final String namePrefix, final boolean daemon) {
this(namePrefix, null, daemon);
}
@Override
public Thread newThread(final Runnable target) {
final Thread thread = new Thread(this.group, target, this.namePrefix + "-" + this.count.incrementAndGet());
thread.setDaemon(this.daemon);
return thread;
}
}

View File

@ -27,33 +27,38 @@
package org.apache.hc.client5.http.impl.io; package org.apache.hc.client5.http.impl.io;
import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.Date; import java.util.Date;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.hc.client5.http.DnsResolver; import org.apache.hc.client5.http.DnsResolver;
import org.apache.hc.client5.http.HttpConnectionFactory;
import org.apache.hc.client5.http.HttpRoute; import org.apache.hc.client5.http.HttpRoute;
import org.apache.hc.client5.http.SchemePortResolver; import org.apache.hc.client5.http.SchemePortResolver;
import org.apache.hc.client5.http.io.ConnectionRequest; import org.apache.hc.client5.http.impl.ConnectionShutdownException;
import org.apache.hc.client5.http.io.ConnectionEndpoint;
import org.apache.hc.client5.http.io.HttpClientConnectionManager; import org.apache.hc.client5.http.io.HttpClientConnectionManager;
import org.apache.hc.client5.http.io.HttpClientConnectionOperator; import org.apache.hc.client5.http.io.HttpClientConnectionOperator;
import org.apache.hc.client5.http.io.LeaseRequest;
import org.apache.hc.client5.http.io.ManagedHttpClientConnection; import org.apache.hc.client5.http.io.ManagedHttpClientConnection;
import org.apache.hc.client5.http.socket.ConnectionSocketFactory; import org.apache.hc.client5.http.socket.ConnectionSocketFactory;
import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory; import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.Contract;
import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.annotation.ThreadingBehavior;
import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.HttpException;
import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.config.ConnectionConfig;
import org.apache.hc.core5.http.config.Lookup; import org.apache.hc.core5.http.config.Lookup;
import org.apache.hc.core5.http.config.Registry; import org.apache.hc.core5.http.config.Registry;
import org.apache.hc.core5.http.config.RegistryBuilder; import org.apache.hc.core5.http.config.RegistryBuilder;
import org.apache.hc.core5.http.config.SocketConfig; import org.apache.hc.core5.http.config.SocketConfig;
import org.apache.hc.core5.http.io.HttpClientConnection; import org.apache.hc.core5.http.impl.io.HttpRequestExecutor;
import org.apache.hc.core5.http.io.HttpConnectionFactory;
import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.protocol.HttpContext;
import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Args;
import org.apache.hc.core5.util.Asserts; import org.apache.hc.core5.util.Asserts;
@ -80,12 +85,12 @@
* @since 4.3 * @since 4.3
*/ */
@Contract(threading = ThreadingBehavior.SAFE) @Contract(threading = ThreadingBehavior.SAFE)
public class BasicHttpClientConnectionManager implements HttpClientConnectionManager, Closeable { public class BasicHttpClientConnectionManager implements HttpClientConnectionManager {
private final Logger log = LogManager.getLogger(getClass()); private final Logger log = LogManager.getLogger(getClass());
private final HttpClientConnectionOperator connectionOperator; private final HttpClientConnectionOperator connectionOperator;
private final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory; private final HttpConnectionFactory<ManagedHttpClientConnection> connFactory;
private ManagedHttpClientConnection conn; private ManagedHttpClientConnection conn;
private HttpRoute route; private HttpRoute route;
@ -94,9 +99,8 @@ public class BasicHttpClientConnectionManager implements HttpClientConnectionMan
private long expiry; private long expiry;
private boolean leased; private boolean leased;
private SocketConfig socketConfig; private SocketConfig socketConfig;
private ConnectionConfig connConfig;
private final AtomicBoolean isShutdown; private final AtomicBoolean closed;
private static Registry<ConnectionSocketFactory> getDefaultRegistry() { private static Registry<ConnectionSocketFactory> getDefaultRegistry() {
return RegistryBuilder.<ConnectionSocketFactory>create() return RegistryBuilder.<ConnectionSocketFactory>create()
@ -107,13 +111,11 @@ private static Registry<ConnectionSocketFactory> getDefaultRegistry() {
public BasicHttpClientConnectionManager( public BasicHttpClientConnectionManager(
final Lookup<ConnectionSocketFactory> socketFactoryRegistry, final Lookup<ConnectionSocketFactory> socketFactoryRegistry,
final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory, final HttpConnectionFactory<ManagedHttpClientConnection> connFactory,
final SchemePortResolver schemePortResolver, final SchemePortResolver schemePortResolver,
final DnsResolver dnsResolver) { final DnsResolver dnsResolver) {
this( this(new DefaultHttpClientConnectionOperator(
new DefaultHttpClientConnectionOperator(socketFactoryRegistry, schemePortResolver, dnsResolver), socketFactoryRegistry, schemePortResolver, dnsResolver), connFactory);
connFactory
);
} }
/** /**
@ -121,19 +123,18 @@ public BasicHttpClientConnectionManager(
*/ */
public BasicHttpClientConnectionManager( public BasicHttpClientConnectionManager(
final HttpClientConnectionOperator httpClientConnectionOperator, final HttpClientConnectionOperator httpClientConnectionOperator,
final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory) { final HttpConnectionFactory<ManagedHttpClientConnection> connFactory) {
super(); super();
this.connectionOperator = Args.notNull(httpClientConnectionOperator, "Connection operator"); this.connectionOperator = Args.notNull(httpClientConnectionOperator, "Connection operator");
this.connFactory = connFactory != null ? connFactory : ManagedHttpClientConnectionFactory.INSTANCE; this.connFactory = connFactory != null ? connFactory : ManagedHttpClientConnectionFactory.INSTANCE;
this.expiry = Long.MAX_VALUE; this.expiry = Long.MAX_VALUE;
this.socketConfig = SocketConfig.DEFAULT; this.socketConfig = SocketConfig.DEFAULT;
this.connConfig = ConnectionConfig.DEFAULT; this.closed = new AtomicBoolean(false);
this.isShutdown = new AtomicBoolean(false);
} }
public BasicHttpClientConnectionManager( public BasicHttpClientConnectionManager(
final Lookup<ConnectionSocketFactory> socketFactoryRegistry, final Lookup<ConnectionSocketFactory> socketFactoryRegistry,
final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory) { final HttpConnectionFactory<ManagedHttpClientConnection> connFactory) {
this(socketFactoryRegistry, connFactory, null, null); this(socketFactoryRegistry, connFactory, null, null);
} }
@ -149,7 +150,7 @@ public BasicHttpClientConnectionManager() {
@Override @Override
protected void finalize() throws Throwable { protected void finalize() throws Throwable {
try { try {
shutdown(); close();
} finally { // Make sure we call overridden method even if shutdown barfs } finally { // Make sure we call overridden method even if shutdown barfs
super.finalize(); super.finalize();
} }
@ -157,7 +158,9 @@ protected void finalize() throws Throwable {
@Override @Override
public void close() { public void close() {
shutdown(); if (this.closed.compareAndSet(false, true)) {
shutdownConnection();
}
} }
HttpRoute getRoute() { HttpRoute getRoute() {
@ -176,37 +179,30 @@ public synchronized void setSocketConfig(final SocketConfig socketConfig) {
this.socketConfig = socketConfig != null ? socketConfig : SocketConfig.DEFAULT; this.socketConfig = socketConfig != null ? socketConfig : SocketConfig.DEFAULT;
} }
public synchronized ConnectionConfig getConnectionConfig() { @Override
return connConfig; public LeaseRequest lease(final HttpRoute route, final Object state) {
} return new LeaseRequest() {
public synchronized void setConnectionConfig(final ConnectionConfig connConfig) {
this.connConfig = connConfig != null ? connConfig : ConnectionConfig.DEFAULT;
}
@Override @Override
public final ConnectionRequest requestConnection( public ConnectionEndpoint get(
final HttpRoute route, final long timeout,
final Object state) { final TimeUnit tunit) throws InterruptedException, ExecutionException, TimeoutException {
Args.notNull(route, "Route"); try {
return new ConnectionRequest() { return new InternalConnectionEndpoint(route, getConnection(route, state));
} catch (IOException ex) {
throw new ExecutionException(ex.getMessage(), ex);
}
}
@Override @Override
public boolean cancel() { public boolean cancel() {
// Nothing to abort, since requests are immediate.
return false; return false;
} }
@Override
public HttpClientConnection get(final long timeout, final TimeUnit tunit) {
return BasicHttpClientConnectionManager.this.getConnection(
route, state);
}
}; };
} }
private void closeConnection() { private synchronized void closeConnection() {
if (this.conn != null) { if (this.conn != null) {
this.log.debug("Closing connection"); this.log.debug("Closing connection");
try { try {
@ -220,7 +216,7 @@ private void closeConnection() {
} }
} }
private void shutdownConnection() { private synchronized void shutdownConnection() {
if (this.conn != null) { if (this.conn != null) {
this.log.debug("Shutting down connection"); this.log.debug("Shutting down connection");
try { try {
@ -243,8 +239,8 @@ private void checkExpiry() {
} }
} }
synchronized HttpClientConnection getConnection(final HttpRoute route, final Object state) { synchronized ManagedHttpClientConnection getConnection(final HttpRoute route, final Object state) throws IOException {
Asserts.check(!this.isShutdown.get(), "Connection manager has been shut down"); Asserts.check(!this.closed.get(), "Connection manager has been shut down");
if (this.log.isDebugEnabled()) { if (this.log.isDebugEnabled()) {
this.log.debug("Get connection for route " + route); this.log.debug("Get connection for route " + route);
} }
@ -256,23 +252,32 @@ synchronized HttpClientConnection getConnection(final HttpRoute route, final Obj
this.state = state; this.state = state;
checkExpiry(); checkExpiry();
if (this.conn == null) { if (this.conn == null) {
this.conn = this.connFactory.create(route, this.connConfig); this.conn = this.connFactory.createConnection(null);
} }
this.leased = true; this.leased = true;
return this.conn; return this.conn;
} }
private InternalConnectionEndpoint cast(final ConnectionEndpoint endpoint) {
if (endpoint instanceof InternalConnectionEndpoint) {
return (InternalConnectionEndpoint) endpoint;
} else {
throw new IllegalStateException("Unexpected endpoint class: " + endpoint.getClass());
}
}
@Override @Override
public synchronized void releaseConnection( public synchronized void release(
final HttpClientConnection conn, final ConnectionEndpoint endpoint,
final Object state, final Object state,
final long keepalive, final TimeUnit tunit) { final long keepalive, final TimeUnit tunit) {
Args.notNull(conn, "Connection"); Args.notNull(endpoint, "Endpoint");
Asserts.check(conn == this.conn, "Connection not obtained from this manager"); final InternalConnectionEndpoint internalEndpoint = cast(endpoint);
if (this.log.isDebugEnabled()) { final ManagedHttpClientConnection conn = internalEndpoint.detach();
if (conn != null && this.log.isDebugEnabled()) {
this.log.debug("Releasing connection " + conn); this.log.debug("Releasing connection " + conn);
} }
if (this.isShutdown.get()) { if (this.closed.get()) {
return; return;
} }
try { try {
@ -306,45 +311,47 @@ public synchronized void releaseConnection(
@Override @Override
public void connect( public void connect(
final HttpClientConnection conn, final ConnectionEndpoint endpoint,
final HttpRoute route, final long connectTimeout,
final int connectTimeout, final TimeUnit timeUnit,
final HttpContext context) throws IOException { final HttpContext context) throws IOException {
Args.notNull(conn, "Connection"); Args.notNull(endpoint, "Endpoint");
Args.notNull(route, "HTTP route");
Asserts.check(conn == this.conn, "Connection not obtained from this manager"); final InternalConnectionEndpoint internalEndpoint = cast(endpoint);
if (internalEndpoint.isConnected()) {
return;
}
final HttpRoute route = internalEndpoint.getRoute();
final HttpHost host; final HttpHost host;
if (route.getProxyHost() != null) { if (route.getProxyHost() != null) {
host = route.getProxyHost(); host = route.getProxyHost();
} else { } else {
host = route.getTargetHost(); host = route.getTargetHost();
} }
final InetSocketAddress localAddress = route.getLocalSocketAddress(); this.connectionOperator.connect(
this.connectionOperator.connect(this.conn, host, localAddress, internalEndpoint.getConnection(),
connectTimeout, this.socketConfig, context); host,
route.getLocalSocketAddress(),
(int) (timeUnit != null ? timeUnit : TimeUnit.MILLISECONDS).toMillis(connectTimeout),
this.socketConfig,
context);
} }
@Override @Override
public void upgrade( public void upgrade(
final HttpClientConnection conn, final ConnectionEndpoint endpoint,
final HttpRoute route,
final HttpContext context) throws IOException { final HttpContext context) throws IOException {
Args.notNull(conn, "Connection"); Args.notNull(endpoint, "Endpoint");
Args.notNull(route, "HTTP route"); Args.notNull(route, "HTTP route");
Asserts.check(conn == this.conn, "Connection not obtained from this manager"); final InternalConnectionEndpoint internalEndpoint = cast(endpoint);
this.connectionOperator.upgrade(this.conn, route.getTargetHost(), context); this.connectionOperator.upgrade(
internalEndpoint.getConnection(),
internalEndpoint.getRoute().getTargetHost(),
context);
} }
@Override
public void routeComplete(
final HttpClientConnection conn,
final HttpRoute route,
final HttpContext context) throws IOException {
}
@Override
public synchronized void closeExpired() { public synchronized void closeExpired() {
if (this.isShutdown.get()) { if (this.closed.get()) {
return; return;
} }
if (!this.leased) { if (!this.leased) {
@ -352,10 +359,9 @@ public synchronized void closeExpired() {
} }
} }
@Override
public synchronized void closeIdle(final long idletime, final TimeUnit tunit) { public synchronized void closeIdle(final long idletime, final TimeUnit tunit) {
Args.notNull(tunit, "Time unit"); Args.notNull(tunit, "Time unit");
if (this.isShutdown.get()) { if (this.closed.get()) {
return; return;
} }
if (!this.leased) { if (!this.leased) {
@ -370,11 +376,75 @@ public synchronized void closeIdle(final long idletime, final TimeUnit tunit) {
} }
} }
class InternalConnectionEndpoint extends ConnectionEndpoint {
private final HttpRoute route;
private final AtomicReference<ManagedHttpClientConnection> connRef;
public InternalConnectionEndpoint(final HttpRoute route, final ManagedHttpClientConnection conn) {
this.route = route;
this.connRef = new AtomicReference<>(conn);
}
HttpRoute getRoute() {
return route;
}
ManagedHttpClientConnection getConnection() {
final ManagedHttpClientConnection conn = this.connRef.get();
if (conn == null) {
throw new ConnectionShutdownException();
}
return conn;
}
ManagedHttpClientConnection getValidatedConnection() {
final ManagedHttpClientConnection conn = getConnection();
Asserts.check(conn.isOpen(), "Endpoint is not connected");
return conn;
}
ManagedHttpClientConnection detach() {
return this.connRef.getAndSet(null);
}
@Override @Override
public synchronized void shutdown() { public boolean isConnected() {
if (this.isShutdown.compareAndSet(false, true)) { final ManagedHttpClientConnection conn = getConnection();
shutdownConnection(); return conn != null && conn.isOpen();
}
@Override
public void shutdown() throws IOException {
final ManagedHttpClientConnection conn = detach();
if (conn != null) {
conn.shutdown();
} }
} }
@Override
public void close() throws IOException {
final ManagedHttpClientConnection conn = detach();
if (conn != null) {
conn.close();
}
}
@Override
public void setSocketTimeout(final int timeout) {
getValidatedConnection().setSocketTimeout(timeout);
}
@Override
public ClassicHttpResponse execute(
final ClassicHttpRequest request,
final HttpRequestExecutor requestExecutor,
final HttpContext context) throws IOException, HttpException {
Args.notNull(request, "HTTP request");
Args.notNull(requestExecutor, "Request executor");
return requestExecutor.execute(request, getValidatedConnection(), context);
}
}
} }

View File

@ -1,283 +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
* <http://www.apache.org/>.
*
*/
package org.apache.hc.client5.http.impl.io;
import java.io.IOException;
import java.net.Socket;
import java.net.SocketAddress;
import javax.net.ssl.SSLSession;
import org.apache.hc.client5.http.HttpRoute;
import org.apache.hc.client5.http.io.ManagedHttpClientConnection;
import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.HttpConnectionMetrics;
import org.apache.hc.core5.http.HttpException;
import org.apache.hc.core5.http.ProtocolVersion;
import org.apache.hc.core5.http.io.HttpClientConnection;
import org.apache.hc.core5.http.protocol.HttpContext;
import org.apache.hc.core5.pool.PoolEntry;
/**
* @since 4.3
*/
class CPoolProxy implements ManagedHttpClientConnection, HttpContext {
private volatile PoolEntry<HttpRoute, ManagedHttpClientConnection> poolEntry;
private volatile boolean routeComplete;
CPoolProxy(final PoolEntry<HttpRoute, ManagedHttpClientConnection> entry) {
super();
this.poolEntry = entry;
}
PoolEntry<HttpRoute, ManagedHttpClientConnection> getPoolEntry() {
return this.poolEntry;
}
boolean isDetached() {
return this.poolEntry == null;
}
PoolEntry<HttpRoute, ManagedHttpClientConnection> detach() {
final PoolEntry<HttpRoute, ManagedHttpClientConnection> local = this.poolEntry;
this.poolEntry = null;
return local;
}
public void markRouteComplete() {
this.routeComplete = true;
}
public boolean isRouteComplete() {
return this.routeComplete;
}
ManagedHttpClientConnection getConnection() {
final PoolEntry<HttpRoute, ManagedHttpClientConnection> local = this.poolEntry;
if (local == null) {
return null;
}
return local.getConnection();
}
ManagedHttpClientConnection getValidConnection() {
final ManagedHttpClientConnection conn = getConnection();
if (conn == null) {
throw new ConnectionShutdownException();
}
return conn;
}
@Override
public void close() throws IOException {
final PoolEntry<HttpRoute, ManagedHttpClientConnection> local = this.poolEntry;
if (local != null) {
final ManagedHttpClientConnection conn = local.getConnection();
if (conn != null) {
conn.close();
}
}
}
@Override
public void shutdown() throws IOException {
final PoolEntry<HttpRoute, ManagedHttpClientConnection> local = this.poolEntry;
if (local != null) {
final ManagedHttpClientConnection conn = local.getConnection();
if (conn != null) {
conn.close();
}
}
}
@Override
public boolean isOpen() {
final PoolEntry<HttpRoute, ManagedHttpClientConnection> local = this.poolEntry;
if (local != null) {
final ManagedHttpClientConnection conn = local.getConnection();
if (conn != null) {
return conn.isOpen();
}
}
return false;
}
@Override
public boolean isStale() throws IOException {
final HttpClientConnection conn = getConnection();
if (conn != null) {
return conn.isStale();
} else {
return true;
}
}
@Override
public ProtocolVersion getProtocolVersion() {
return getValidConnection().getProtocolVersion();
}
@Override
public void setSocketTimeout(final int timeout) {
getValidConnection().setSocketTimeout(timeout);
}
@Override
public int getSocketTimeout() {
return getValidConnection().getSocketTimeout();
}
@Override
public String getId() {
return getValidConnection().getId();
}
@Override
public void bind(final Socket socket) throws IOException {
getValidConnection().bind(socket);
}
@Override
public Socket getSocket() {
return getValidConnection().getSocket();
}
@Override
public SSLSession getSSLSession() {
return getValidConnection().getSSLSession();
}
@Override
public boolean isConsistent() {
return getValidConnection().isConsistent();
}
@Override
public void terminateRequest(final ClassicHttpRequest request) throws HttpException, IOException {
getValidConnection().terminateRequest(request);
}
@Override
public boolean isDataAvailable(final int timeout) throws IOException {
return getValidConnection().isDataAvailable(timeout);
}
@Override
public void sendRequestHeader(final ClassicHttpRequest request) throws HttpException, IOException {
getValidConnection().sendRequestHeader(request);
}
@Override
public void sendRequestEntity(final ClassicHttpRequest request) throws HttpException, IOException {
getValidConnection().sendRequestEntity(request);
}
@Override
public ClassicHttpResponse receiveResponseHeader() throws HttpException, IOException {
return getValidConnection().receiveResponseHeader();
}
@Override
public void receiveResponseEntity(final ClassicHttpResponse response) throws HttpException, IOException {
getValidConnection().receiveResponseEntity(response);
}
@Override
public void flush() throws IOException {
getValidConnection().flush();
}
@Override
public HttpConnectionMetrics getMetrics() {
return getValidConnection().getMetrics();
}
@Override
public SocketAddress getLocalAddress() {
return getValidConnection().getLocalAddress();
}
@Override
public SocketAddress getRemoteAddress() {
return getValidConnection().getRemoteAddress();
}
@Override
public Object getAttribute(final String id) {
final ManagedHttpClientConnection conn = getValidConnection();
if (conn instanceof HttpContext) {
return ((HttpContext) conn).getAttribute(id);
} else {
return null;
}
}
@Override
public void setAttribute(final String id, final Object obj) {
final ManagedHttpClientConnection conn = getValidConnection();
if (conn instanceof HttpContext) {
((HttpContext) conn).setAttribute(id, obj);
}
}
@Override
public Object removeAttribute(final String id) {
final ManagedHttpClientConnection conn = getValidConnection();
if (conn instanceof HttpContext) {
return ((HttpContext) conn).removeAttribute(id);
} else {
return null;
}
}
@Override
public void setProtocolVersion(final ProtocolVersion version) {
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("CPoolProxy{");
final ManagedHttpClientConnection conn = getConnection();
if (conn != null) {
sb.append(conn);
} else {
sb.append("detached");
}
sb.append('}');
return sb.toString();
}
static CPoolProxy getProxy(final HttpClientConnection conn) {
if (!CPoolProxy.class.isInstance(conn)) {
throw new IllegalStateException("Unexpected connection proxy class: " + conn.getClass());
}
return CPoolProxy.class.cast(conn);
}
}

View File

@ -48,6 +48,7 @@
import org.apache.hc.client5.http.socket.LayeredConnectionSocketFactory; import org.apache.hc.client5.http.socket.LayeredConnectionSocketFactory;
import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.Contract;
import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.annotation.ThreadingBehavior;
import org.apache.hc.core5.http.ConnectionClosedException;
import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.config.Lookup; import org.apache.hc.core5.http.config.Lookup;
import org.apache.hc.core5.http.config.SocketConfig; import org.apache.hc.core5.http.config.SocketConfig;
@ -191,6 +192,9 @@ public void upgrade(
} }
final LayeredConnectionSocketFactory lsf = (LayeredConnectionSocketFactory) sf; final LayeredConnectionSocketFactory lsf = (LayeredConnectionSocketFactory) sf;
Socket sock = conn.getSocket(); Socket sock = conn.getSocket();
if (sock == null) {
throw new ConnectionClosedException("Connection is closed");
}
final int port = this.schemePortResolver.resolve(host); final int port = this.schemePortResolver.resolve(host);
sock = lsf.createLayeredSocket(sock, host.getHostName(), port, context); sock = lsf.createLayeredSocket(sock, host.getHostName(), port, context);
conn.bind(sock); conn.bind(sock);

View File

@ -32,35 +32,41 @@
import java.net.Socket; import java.net.Socket;
import java.nio.charset.CharsetDecoder; import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder; import java.nio.charset.CharsetEncoder;
import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.ConcurrentHashMap;
import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocket;
import org.apache.hc.client5.http.impl.logging.LoggingSocketHolder;
import org.apache.hc.client5.http.io.ManagedHttpClientConnection; import org.apache.hc.client5.http.io.ManagedHttpClientConnection;
import org.apache.hc.client5.http.utils.Identifiable;
import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.ContentLengthStrategy; import org.apache.hc.core5.http.ContentLengthStrategy;
import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.config.H1Config; import org.apache.hc.core5.http.config.H1Config;
import org.apache.hc.core5.http.impl.io.DefaultBHttpClientConnection; import org.apache.hc.core5.http.impl.io.DefaultBHttpClientConnection;
import org.apache.hc.core5.http.impl.io.SocketHolder; import org.apache.hc.core5.http.impl.io.SocketHolder;
import org.apache.hc.core5.http.io.HttpMessageParserFactory; import org.apache.hc.core5.http.io.HttpMessageParserFactory;
import org.apache.hc.core5.http.io.HttpMessageWriterFactory; import org.apache.hc.core5.http.io.HttpMessageWriterFactory;
import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.message.RequestLine;
import org.apache.hc.core5.http.message.StatusLine;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/** /**
* Default {@link ManagedHttpClientConnection} implementation. * Default {@link ManagedHttpClientConnection} implementation.
* @since 4.3 * @since 4.3
*/ */
public class DefaultManagedHttpClientConnection extends DefaultBHttpClientConnection public class DefaultManagedHttpClientConnection
implements ManagedHttpClientConnection, HttpContext { extends DefaultBHttpClientConnection implements ManagedHttpClientConnection, Identifiable {
private final Logger log = LogManager.getLogger(DefaultManagedHttpClientConnection.class);
private final Logger headerlog = LogManager.getLogger("org.apache.hc.client5.http.headers");
private final Logger wirelog = LogManager.getLogger("org.apache.hc.client5.http.wire");
private final String id; private final String id;
private final Map<String, Object> attributes; private final AtomicBoolean closed;
private volatile boolean shutdown;
public DefaultManagedHttpClientConnection( public DefaultManagedHttpClientConnection(
final String id, final String id,
@ -75,7 +81,7 @@ public DefaultManagedHttpClientConnection(
super(buffersize, chardecoder, charencoder, h1Config, incomingContentStrategy, outgoingContentStrategy, super(buffersize, chardecoder, charencoder, h1Config, incomingContentStrategy, outgoingContentStrategy,
requestWriterFactory, responseParserFactory); requestWriterFactory, responseParserFactory);
this.id = id; this.id = id;
this.attributes = new ConcurrentHashMap<>(); this.closed = new AtomicBoolean();
} }
public DefaultManagedHttpClientConnection( public DefaultManagedHttpClientConnection(
@ -89,15 +95,9 @@ public String getId() {
return this.id; return this.id;
} }
@Override
public void shutdown() throws IOException {
this.shutdown = true;
super.shutdown();
}
@Override @Override
public void bind(final SocketHolder socketHolder) throws IOException { public void bind(final SocketHolder socketHolder) throws IOException {
if (this.shutdown) { if (this.closed.get()) {
final Socket socket = socketHolder.getSocket(); final Socket socket = socketHolder.getSocket();
socket.close(); // allow this to throw... socket.close(); // allow this to throw...
// ...but if it doesn't, explicitly throw one ourselves. // ...but if it doesn't, explicitly throw one ourselves.
@ -123,22 +123,58 @@ public SSLSession getSSLSession() {
} }
@Override @Override
public void setProtocolVersion(final ProtocolVersion version) { public void close() throws IOException {
if (this.closed.compareAndSet(false, true)) {
if (this.log.isDebugEnabled()) {
this.log.debug(this.id + ": Close connection");
}
super.close();
}
} }
@Override @Override
public Object getAttribute(final String id) { public void setSocketTimeout(final int timeout) {
return this.attributes.get(id); if (this.log.isDebugEnabled()) {
this.log.debug(this.id + ": set socket timeout to " + timeout);
}
super.setSocketTimeout(timeout);
} }
@Override @Override
public void setAttribute(final String id, final Object obj) { public void shutdown() throws IOException {
this.attributes.put(id, obj); if (this.closed.compareAndSet(false, true)) {
if (this.log.isDebugEnabled()) {
this.log.debug(this.id + ": Shutdown connection");
}
super.shutdown();
}
} }
@Override @Override
public Object removeAttribute(final String id) { public void bind(final Socket socket) throws IOException {
return this.attributes.remove(id); super.bind(this.wirelog.isDebugEnabled() ? new LoggingSocketHolder(socket, this.id, this.wirelog) : new SocketHolder(socket));
}
@Override
protected void onResponseReceived(final ClassicHttpResponse response) {
if (response != null && this.headerlog.isDebugEnabled()) {
this.headerlog.debug(this.id + " << " + new StatusLine(response));
final Header[] headers = response.getAllHeaders();
for (final Header header : headers) {
this.headerlog.debug(this.id + " << " + header.toString());
}
}
}
@Override
protected void onRequestSubmitted(final ClassicHttpRequest request) {
if (request != null && this.headerlog.isDebugEnabled()) {
this.headerlog.debug(this.id + " >> " + new RequestLine(request));
final Header[] headers = request.getAllHeaders();
for (final Header header : headers) {
this.headerlog.debug(this.id + " >> " + header.toString());
}
}
} }
} }

View File

@ -1,127 +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
* <http://www.apache.org/>.
*
*/
package org.apache.hc.client5.http.impl.io;
import java.io.IOException;
import java.net.Socket;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.ContentLengthStrategy;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.config.H1Config;
import org.apache.hc.core5.http.impl.io.SocketHolder;
import org.apache.hc.core5.http.io.HttpMessageParserFactory;
import org.apache.hc.core5.http.io.HttpMessageWriterFactory;
import org.apache.hc.core5.http.message.RequestLine;
import org.apache.hc.core5.http.message.StatusLine;
import org.apache.logging.log4j.Logger;
class LoggingManagedHttpClientConnection extends DefaultManagedHttpClientConnection {
private final Logger log;
private final Logger headerlog;
private final Wire wire;
public LoggingManagedHttpClientConnection(
final String id,
final Logger log,
final Logger headerlog,
final Logger wirelog,
final int buffersize,
final CharsetDecoder chardecoder,
final CharsetEncoder charencoder,
final H1Config h1Config,
final ContentLengthStrategy incomingContentStrategy,
final ContentLengthStrategy outgoingContentStrategy,
final HttpMessageWriterFactory<ClassicHttpRequest> requestWriterFactory,
final HttpMessageParserFactory<ClassicHttpResponse> responseParserFactory) {
super(id, buffersize, chardecoder, charencoder, h1Config, incomingContentStrategy, outgoingContentStrategy,
requestWriterFactory, responseParserFactory);
this.log = log;
this.headerlog = headerlog;
this.wire = new Wire(wirelog, id);
}
@Override
public void close() throws IOException {
if (super.isOpen()) {
if (this.log.isDebugEnabled()) {
this.log.debug(getId() + ": Close connection");
}
super.close();
}
}
@Override
public void setSocketTimeout(final int timeout) {
if (this.log.isDebugEnabled()) {
this.log.debug(getId() + ": set socket timeout to " + timeout);
}
super.setSocketTimeout(timeout);
}
@Override
public void shutdown() throws IOException {
if (this.log.isDebugEnabled()) {
this.log.debug(getId() + ": Shutdown connection");
}
super.shutdown();
}
@Override
public void bind(final Socket socket) throws IOException {
super.bind(this.wire.enabled() ? new LoggingSocketHolder(socket, this.wire) : new SocketHolder(socket));
}
@Override
protected void onResponseReceived(final ClassicHttpResponse response) {
if (response != null && this.headerlog.isDebugEnabled()) {
this.headerlog.debug(getId() + " << " + new StatusLine(response));
final Header[] headers = response.getAllHeaders();
for (final Header header : headers) {
this.headerlog.debug(getId() + " << " + header.toString());
}
}
}
@Override
protected void onRequestSubmitted(final ClassicHttpRequest request) {
if (request != null && this.headerlog.isDebugEnabled()) {
this.headerlog.debug(getId() + " >> " + new RequestLine(request));
final Header[] headers = request.getAllHeaders();
for (final Header header : headers) {
this.headerlog.debug(getId() + " >> " + header.toString());
}
}
}
}

View File

@ -27,14 +27,14 @@
package org.apache.hc.client5.http.impl.io; package org.apache.hc.client5.http.impl.io;
import java.io.IOException;
import java.net.Socket;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder; import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder; import java.nio.charset.CharsetEncoder;
import java.nio.charset.CodingErrorAction; import java.nio.charset.CodingErrorAction;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
import org.apache.hc.client5.http.HttpConnectionFactory;
import org.apache.hc.client5.http.HttpRoute;
import org.apache.hc.client5.http.io.ManagedHttpClientConnection; import org.apache.hc.client5.http.io.ManagedHttpClientConnection;
import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.Contract;
import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.annotation.ThreadingBehavior;
@ -45,43 +45,38 @@
import org.apache.hc.core5.http.config.H1Config; import org.apache.hc.core5.http.config.H1Config;
import org.apache.hc.core5.http.impl.DefaultContentLengthStrategy; import org.apache.hc.core5.http.impl.DefaultContentLengthStrategy;
import org.apache.hc.core5.http.impl.io.DefaultHttpRequestWriterFactory; import org.apache.hc.core5.http.impl.io.DefaultHttpRequestWriterFactory;
import org.apache.hc.core5.http.io.HttpConnectionFactory;
import org.apache.hc.core5.http.io.HttpMessageParserFactory; import org.apache.hc.core5.http.io.HttpMessageParserFactory;
import org.apache.hc.core5.http.io.HttpMessageWriterFactory; import org.apache.hc.core5.http.io.HttpMessageWriterFactory;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/** /**
* Factory for {@link ManagedHttpClientConnection} instances. * Factory for {@link ManagedHttpClientConnection} instances.
* @since 4.3 * @since 4.3
*/ */
@Contract(threading = ThreadingBehavior.IMMUTABLE) @Contract(threading = ThreadingBehavior.IMMUTABLE)
public class ManagedHttpClientConnectionFactory implements HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> { public class ManagedHttpClientConnectionFactory implements HttpConnectionFactory<ManagedHttpClientConnection> {
private static final AtomicLong COUNTER = new AtomicLong(); private static final AtomicLong COUNTER = new AtomicLong();
public static final ManagedHttpClientConnectionFactory INSTANCE = new ManagedHttpClientConnectionFactory(); public static final ManagedHttpClientConnectionFactory INSTANCE = new ManagedHttpClientConnectionFactory();
private final Logger log = LogManager.getLogger(DefaultManagedHttpClientConnection.class);
private final Logger headerlog = LogManager.getLogger("org.apache.hc.client5.http.headers");
private final Logger wirelog = LogManager.getLogger("org.apache.hc.client5.http.wire");
private final H1Config h1Config; private final H1Config h1Config;
private final ConnectionConfig connectionConfig;
private final HttpMessageWriterFactory<ClassicHttpRequest> requestWriterFactory; private final HttpMessageWriterFactory<ClassicHttpRequest> requestWriterFactory;
private final HttpMessageParserFactory<ClassicHttpResponse> responseParserFactory; private final HttpMessageParserFactory<ClassicHttpResponse> responseParserFactory;
private final ContentLengthStrategy incomingContentStrategy; private final ContentLengthStrategy incomingContentStrategy;
private final ContentLengthStrategy outgoingContentStrategy; private final ContentLengthStrategy outgoingContentStrategy;
/**
* @since 4.4
*/
public ManagedHttpClientConnectionFactory( public ManagedHttpClientConnectionFactory(
final H1Config h1Config, final H1Config h1Config,
final ConnectionConfig connectionConfig,
final HttpMessageWriterFactory<ClassicHttpRequest> requestWriterFactory, final HttpMessageWriterFactory<ClassicHttpRequest> requestWriterFactory,
final HttpMessageParserFactory<ClassicHttpResponse> responseParserFactory, final HttpMessageParserFactory<ClassicHttpResponse> responseParserFactory,
final ContentLengthStrategy incomingContentStrategy, final ContentLengthStrategy incomingContentStrategy,
final ContentLengthStrategy outgoingContentStrategy) { final ContentLengthStrategy outgoingContentStrategy) {
super(); super();
this.h1Config = h1Config != null ? h1Config : H1Config.DEFAULT; this.h1Config = h1Config != null ? h1Config : H1Config.DEFAULT;
this.connectionConfig = connectionConfig != null ? connectionConfig : ConnectionConfig.DEFAULT;
this.requestWriterFactory = requestWriterFactory != null ? requestWriterFactory : this.requestWriterFactory = requestWriterFactory != null ? requestWriterFactory :
DefaultHttpRequestWriterFactory.INSTANCE; DefaultHttpRequestWriterFactory.INSTANCE;
this.responseParserFactory = responseParserFactory != null ? responseParserFactory : this.responseParserFactory = responseParserFactory != null ? responseParserFactory :
@ -94,31 +89,32 @@ public ManagedHttpClientConnectionFactory(
public ManagedHttpClientConnectionFactory( public ManagedHttpClientConnectionFactory(
final H1Config h1Config, final H1Config h1Config,
final ConnectionConfig connectionConfig,
final HttpMessageWriterFactory<ClassicHttpRequest> requestWriterFactory, final HttpMessageWriterFactory<ClassicHttpRequest> requestWriterFactory,
final HttpMessageParserFactory<ClassicHttpResponse> responseParserFactory) { final HttpMessageParserFactory<ClassicHttpResponse> responseParserFactory) {
this(h1Config, requestWriterFactory, responseParserFactory, null, null); this(h1Config, connectionConfig, requestWriterFactory, responseParserFactory, null, null);
} }
public ManagedHttpClientConnectionFactory( public ManagedHttpClientConnectionFactory(
final H1Config h1Config, final H1Config h1Config,
final ConnectionConfig connectionConfig,
final HttpMessageParserFactory<ClassicHttpResponse> responseParserFactory) { final HttpMessageParserFactory<ClassicHttpResponse> responseParserFactory) {
this(h1Config, null, responseParserFactory); this(h1Config, connectionConfig, null, responseParserFactory);
} }
public ManagedHttpClientConnectionFactory() { public ManagedHttpClientConnectionFactory() {
this(null, null); this(null, null, null);
} }
@Override @Override
public ManagedHttpClientConnection create(final HttpRoute route, final ConnectionConfig config) { public ManagedHttpClientConnection createConnection(final Socket socket) throws IOException {
final ConnectionConfig cconfig = config != null ? config : ConnectionConfig.DEFAULT;
CharsetDecoder chardecoder = null; CharsetDecoder chardecoder = null;
CharsetEncoder charencoder = null; CharsetEncoder charencoder = null;
final Charset charset = cconfig.getCharset(); final Charset charset = this.connectionConfig.getCharset();
final CodingErrorAction malformedInputAction = cconfig.getMalformedInputAction() != null ? final CodingErrorAction malformedInputAction = this.connectionConfig.getMalformedInputAction() != null ?
cconfig.getMalformedInputAction() : CodingErrorAction.REPORT; this.connectionConfig.getMalformedInputAction() : CodingErrorAction.REPORT;
final CodingErrorAction unmappableInputAction = cconfig.getUnmappableInputAction() != null ? final CodingErrorAction unmappableInputAction = this.connectionConfig.getUnmappableInputAction() != null ?
cconfig.getUnmappableInputAction() : CodingErrorAction.REPORT; this.connectionConfig.getUnmappableInputAction() : CodingErrorAction.REPORT;
if (charset != null) { if (charset != null) {
chardecoder = charset.newDecoder(); chardecoder = charset.newDecoder();
chardecoder.onMalformedInput(malformedInputAction); chardecoder.onMalformedInput(malformedInputAction);
@ -128,12 +124,9 @@ public ManagedHttpClientConnection create(final HttpRoute route, final Connectio
charencoder.onUnmappableCharacter(unmappableInputAction); charencoder.onUnmappableCharacter(unmappableInputAction);
} }
final String id = "http-outgoing-" + Long.toString(COUNTER.getAndIncrement()); final String id = "http-outgoing-" + Long.toString(COUNTER.getAndIncrement());
return new LoggingManagedHttpClientConnection( final DefaultManagedHttpClientConnection conn = new DefaultManagedHttpClientConnection(
id, id,
log, connectionConfig.getBufferSize(),
headerlog,
wirelog,
cconfig.getBufferSize(),
chardecoder, chardecoder,
charencoder, charencoder,
h1Config, h1Config,
@ -141,6 +134,10 @@ public ManagedHttpClientConnection create(final HttpRoute route, final Connectio
outgoingContentStrategy, outgoingContentStrategy,
requestWriterFactory, requestWriterFactory,
responseParserFactory); responseParserFactory);
if (socket != null) {
conn.bind(socket);
}
return conn;
} }
} }

View File

@ -26,9 +26,7 @@
*/ */
package org.apache.hc.client5.http.impl.io; package org.apache.hc.client5.http.impl.io;
import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@ -37,15 +35,17 @@
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.hc.client5.http.ConnectionPoolTimeoutException;
import org.apache.hc.client5.http.DnsResolver; import org.apache.hc.client5.http.DnsResolver;
import org.apache.hc.client5.http.HttpConnectionFactory;
import org.apache.hc.client5.http.HttpRoute; import org.apache.hc.client5.http.HttpRoute;
import org.apache.hc.client5.http.SchemePortResolver; import org.apache.hc.client5.http.SchemePortResolver;
import org.apache.hc.client5.http.io.ConnectionRequest; import org.apache.hc.client5.http.impl.ConnPoolSupport;
import org.apache.hc.client5.http.impl.ConnectionShutdownException;
import org.apache.hc.client5.http.io.ConnectionEndpoint;
import org.apache.hc.client5.http.io.HttpClientConnectionManager; import org.apache.hc.client5.http.io.HttpClientConnectionManager;
import org.apache.hc.client5.http.io.HttpClientConnectionOperator; import org.apache.hc.client5.http.io.HttpClientConnectionOperator;
import org.apache.hc.client5.http.io.LeaseRequest;
import org.apache.hc.client5.http.io.ManagedHttpClientConnection; import org.apache.hc.client5.http.io.ManagedHttpClientConnection;
import org.apache.hc.client5.http.socket.ConnectionSocketFactory; import org.apache.hc.client5.http.socket.ConnectionSocketFactory;
import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory; import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory;
@ -53,70 +53,68 @@
import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.Contract;
import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.annotation.ThreadingBehavior;
import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.function.Callback;
import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.HttpException;
import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.config.ConnectionConfig;
import org.apache.hc.core5.http.config.Lookup; import org.apache.hc.core5.http.config.Lookup;
import org.apache.hc.core5.http.config.Registry; import org.apache.hc.core5.http.config.Registry;
import org.apache.hc.core5.http.config.RegistryBuilder; import org.apache.hc.core5.http.config.RegistryBuilder;
import org.apache.hc.core5.http.config.SocketConfig; import org.apache.hc.core5.http.config.SocketConfig;
import org.apache.hc.core5.http.impl.io.HttpRequestExecutor;
import org.apache.hc.core5.http.io.HttpClientConnection; import org.apache.hc.core5.http.io.HttpClientConnection;
import org.apache.hc.core5.http.io.HttpConnectionFactory;
import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.protocol.HttpContext;
import org.apache.hc.core5.pool.ConnPoolControl; import org.apache.hc.core5.pool.ConnPoolControl;
import org.apache.hc.core5.pool.ConnPoolListener;
import org.apache.hc.core5.pool.ConnPoolPolicy; import org.apache.hc.core5.pool.ConnPoolPolicy;
import org.apache.hc.core5.pool.PoolEntry; import org.apache.hc.core5.pool.PoolEntry;
import org.apache.hc.core5.pool.PoolStats; import org.apache.hc.core5.pool.PoolStats;
import org.apache.hc.core5.pool.StrictConnPool; import org.apache.hc.core5.pool.StrictConnPool;
import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Args;
import org.apache.hc.core5.util.Asserts;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
/** /**
* {@code ClientConnectionPoolManager} maintains a pool of * {@code ClientConnectionPoolManager} maintains a pool of
* {@link HttpClientConnection}s and is able to service connection requests * {@link ManagedHttpClientConnection}s and is able to service connection requests
* from multiple execution threads. Connections are pooled on a per route * from multiple execution threads. Connections are pooled on a per route
* basis. A request for a route which already the manager has persistent * basis. A request for a route which already the manager has persistent
* connections for available in the pool will be services by leasing * connections for available in the pool will be services by leasing
* a connection from the pool rather than creating a brand new connection. * a connection from the pool rather than creating a new connection.
* <p> * <p>
* {@code ClientConnectionPoolManager} maintains a maximum limit of connection * {@code ClientConnectionPoolManager} maintains a maximum limit of connection
* on a per route basis and in total. Per default this implementation will * on a per route basis and in total. Connection limits, however, can be adjusted
* create no more than than 2 concurrent connections per given route * using {@link ConnPoolControl} methods.
* and no more 20 connections in total. For many real-world applications
* these limits may prove too constraining, especially if they use HTTP
* as a transport protocol for their services. Connection limits, however,
* can be adjusted using {@link ConnPoolControl} methods.
* </p>
* <p> * <p>
* Total time to live (TTL) set at construction time defines maximum life span * Total time to live (TTL) set at construction time defines maximum life span
* of persistent connections regardless of their expiration setting. No persistent * of persistent connections regardless of their expiration setting. No persistent
* connection will be re-used past its TTL value. * connection will be re-used past its TTL value.
* </p>
* <p> * <p>
* The handling of stale connections was changed in version 4.4. * The handling of stale connections was changed in version 4.4.
* Previously, the code would check every connection by default before re-using it. * Previously, the code would check every connection by default before re-using it.
* The code now only checks the connection if the elapsed time since * The code now only checks the connection if the elapsed time since
* the last use of the connection exceeds the timeout that has been set. * the last use of the connection exceeds the timeout that has been set.
* The default timeout is set to 5000ms - see * The default timeout is set to 5000ms.
* {@link #PoolingHttpClientConnectionManager(HttpClientConnectionOperator, HttpConnectionFactory, long, TimeUnit)}
* </p>
* *
* @since 4.3 * @since 4.3
*/ */
@Contract(threading = ThreadingBehavior.SAFE_CONDITIONAL) @Contract(threading = ThreadingBehavior.SAFE_CONDITIONAL)
public class PoolingHttpClientConnectionManager public class PoolingHttpClientConnectionManager
implements HttpClientConnectionManager, ConnPoolControl<HttpRoute>, Closeable { implements HttpClientConnectionManager, ConnPoolControl<HttpRoute> {
private final Logger log = LogManager.getLogger(getClass()); private final Logger log = LogManager.getLogger(getClass());
public static final int DEFAULT_MAX_TOTAL_CONNECTIONS = 25; public static final int DEFAULT_MAX_TOTAL_CONNECTIONS = 25;
public static final int DEFAULT_MAX_CONNECTIONS_PER_ROUTE = 5; public static final int DEFAULT_MAX_CONNECTIONS_PER_ROUTE = 5;
private final ConfigData configData;
private final StrictConnPool<HttpRoute, ManagedHttpClientConnection> pool; private final StrictConnPool<HttpRoute, ManagedHttpClientConnection> pool;
private final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory; private final HttpConnectionFactory<ManagedHttpClientConnection> connFactory;
private final HttpClientConnectionOperator connectionOperator; private final HttpClientConnectionOperator connectionOperator;
private final AtomicBoolean isShutDown; private final AtomicBoolean closed;
private volatile SocketConfig defaultSocketConfig;
private volatile int validateAfterInactivity; private volatile int validateAfterInactivity;
private static Registry<ConnectionSocketFactory> getDefaultRegistry() { private static Registry<ConnectionSocketFactory> getDefaultRegistry() {
@ -130,8 +128,9 @@ public PoolingHttpClientConnectionManager() {
this(getDefaultRegistry()); this(getDefaultRegistry());
} }
public PoolingHttpClientConnectionManager(final long timeToLive, final TimeUnit tunit) { public PoolingHttpClientConnectionManager(
this(getDefaultRegistry(), null, null ,null, timeToLive, tunit); final long timeToLive, final TimeUnit tunit) {
this(getDefaultRegistry(), null, null ,null, ConnPoolPolicy.LIFO, null, timeToLive, tunit);
} }
public PoolingHttpClientConnectionManager( public PoolingHttpClientConnectionManager(
@ -147,49 +146,46 @@ public PoolingHttpClientConnectionManager(
public PoolingHttpClientConnectionManager( public PoolingHttpClientConnectionManager(
final Registry<ConnectionSocketFactory> socketFactoryRegistry, final Registry<ConnectionSocketFactory> socketFactoryRegistry,
final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory) { final HttpConnectionFactory<ManagedHttpClientConnection> connFactory) {
this(socketFactoryRegistry, connFactory, null); this(socketFactoryRegistry, connFactory, null);
} }
public PoolingHttpClientConnectionManager( public PoolingHttpClientConnectionManager(
final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory) { final HttpConnectionFactory<ManagedHttpClientConnection> connFactory) {
this(getDefaultRegistry(), connFactory, null); this(getDefaultRegistry(), connFactory, null);
} }
public PoolingHttpClientConnectionManager( public PoolingHttpClientConnectionManager(
final Registry<ConnectionSocketFactory> socketFactoryRegistry, final Registry<ConnectionSocketFactory> socketFactoryRegistry,
final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory, final HttpConnectionFactory<ManagedHttpClientConnection> connFactory,
final DnsResolver dnsResolver) { final DnsResolver dnsResolver) {
this(socketFactoryRegistry, connFactory, null, dnsResolver, -1, TimeUnit.MILLISECONDS); this(socketFactoryRegistry, connFactory, null, dnsResolver, ConnPoolPolicy.LIFO, null, -1, TimeUnit.MILLISECONDS);
} }
public PoolingHttpClientConnectionManager( public PoolingHttpClientConnectionManager(
final Registry<ConnectionSocketFactory> socketFactoryRegistry, final Registry<ConnectionSocketFactory> socketFactoryRegistry,
final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory, final HttpConnectionFactory<ManagedHttpClientConnection> connFactory,
final SchemePortResolver schemePortResolver, final SchemePortResolver schemePortResolver,
final DnsResolver dnsResolver, final DnsResolver dnsResolver,
final ConnPoolPolicy connPoolPolicy,
final ConnPoolListener<HttpRoute> connPoolListener,
final long timeToLive, final TimeUnit tunit) { final long timeToLive, final TimeUnit tunit) {
this( this(new DefaultHttpClientConnectionOperator(socketFactoryRegistry, schemePortResolver, dnsResolver),
new DefaultHttpClientConnectionOperator(socketFactoryRegistry, schemePortResolver, dnsResolver), connFactory, connPoolPolicy, connPoolListener, timeToLive, tunit);
connFactory,
timeToLive, tunit
);
} }
/**
* @since 4.4
*/
public PoolingHttpClientConnectionManager( public PoolingHttpClientConnectionManager(
final HttpClientConnectionOperator httpClientConnectionOperator, final HttpClientConnectionOperator httpClientConnectionOperator,
final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory, final HttpConnectionFactory<ManagedHttpClientConnection> connFactory,
final ConnPoolPolicy connPoolPolicy,
final ConnPoolListener<HttpRoute> connPoolListener,
final long timeToLive, final TimeUnit tunit) { final long timeToLive, final TimeUnit tunit) {
super(); super();
this.connectionOperator = Args.notNull(httpClientConnectionOperator, "Connection operator"); this.connectionOperator = Args.notNull(httpClientConnectionOperator, "Connection operator");
this.connFactory = connFactory != null ? connFactory : ManagedHttpClientConnectionFactory.INSTANCE; this.connFactory = connFactory != null ? connFactory : ManagedHttpClientConnectionFactory.INSTANCE;
this.configData = new ConfigData();
this.pool = new StrictConnPool<>( this.pool = new StrictConnPool<>(
DEFAULT_MAX_CONNECTIONS_PER_ROUTE, DEFAULT_MAX_TOTAL_CONNECTIONS, timeToLive, tunit, ConnPoolPolicy.LIFO, null); DEFAULT_MAX_CONNECTIONS_PER_ROUTE, DEFAULT_MAX_TOTAL_CONNECTIONS, timeToLive, tunit, connPoolPolicy, connPoolListener);
this.isShutDown = new AtomicBoolean(false); this.closed = new AtomicBoolean(false);
} }
/** /**
@ -204,15 +200,14 @@ public PoolingHttpClientConnectionManager(
this.connectionOperator = new DefaultHttpClientConnectionOperator( this.connectionOperator = new DefaultHttpClientConnectionOperator(
socketFactoryRegistry, schemePortResolver, dnsResolver); socketFactoryRegistry, schemePortResolver, dnsResolver);
this.connFactory = ManagedHttpClientConnectionFactory.INSTANCE; this.connFactory = ManagedHttpClientConnectionFactory.INSTANCE;
this.configData = new ConfigData();
this.pool = pool; this.pool = pool;
this.isShutDown = new AtomicBoolean(false); this.closed = new AtomicBoolean(false);
} }
@Override @Override
protected void finalize() throws Throwable { protected void finalize() throws Throwable {
try { try {
shutdown(); close();
} finally { } finally {
super.finalize(); super.finalize();
} }
@ -220,139 +215,112 @@ protected void finalize() throws Throwable {
@Override @Override
public void close() { public void close() {
shutdown(); if (this.closed.compareAndSet(false, true)) {
this.log.debug("Connection manager is shutting down");
this.pool.shutdown();
this.log.debug("Connection manager shut down");
}
} }
private String format(final HttpRoute route, final Object state) { private InternalConnectionEndpoint cast(final ConnectionEndpoint endpoint) {
final StringBuilder buf = new StringBuilder(); if (endpoint instanceof InternalConnectionEndpoint) {
buf.append("[route: ").append(route).append("]"); return (InternalConnectionEndpoint) endpoint;
if (state != null) { } else {
buf.append("[state: ").append(state).append("]"); throw new IllegalStateException("Unexpected endpoint class: " + endpoint.getClass());
} }
return buf.toString();
}
private String formatStats(final HttpRoute route) {
final StringBuilder buf = new StringBuilder();
final PoolStats totals = this.pool.getTotalStats();
final PoolStats stats = this.pool.getStats(route);
buf.append("[total kept alive: ").append(totals.getAvailable()).append("; ");
buf.append("route allocated: ").append(stats.getLeased() + stats.getAvailable());
buf.append(" of ").append(stats.getMax()).append("; ");
buf.append("total allocated: ").append(totals.getLeased() + totals.getAvailable());
buf.append(" of ").append(totals.getMax()).append("]");
return buf.toString();
}
private String format(final PoolEntry<HttpRoute, ManagedHttpClientConnection> entry) {
final StringBuilder buf = new StringBuilder();
final ManagedHttpClientConnection conn = entry.getConnection();
buf.append("[id: ").append(conn != null ? conn.getId() : "unknown").append("]");
buf.append("[route: ").append(entry.getRoute()).append("]");
final Object state = entry.getState();
if (state != null) {
buf.append("[state: ").append(state).append("]");
}
return buf.toString();
} }
@Override @Override
public ConnectionRequest requestConnection( public LeaseRequest lease(
final HttpRoute route, final HttpRoute route,
final Object state) { final Object state) {
Args.notNull(route, "HTTP route"); Args.notNull(route, "HTTP route");
if (this.log.isDebugEnabled()) { if (this.log.isDebugEnabled()) {
this.log.debug("Connection request: " + format(route, state) + formatStats(route)); this.log.debug("Connection request: " + ConnPoolSupport.formatStats(null, route, state, this.pool));
}
final Future<PoolEntry<HttpRoute, ManagedHttpClientConnection>> leaseFuture = this.pool.lease(route, state, null);
return new LeaseRequest() {
private volatile ConnectionEndpoint endpoint;
@Override
public synchronized ConnectionEndpoint get(
final long timeout,
final TimeUnit tunit) throws InterruptedException, ExecutionException, TimeoutException {
if (this.endpoint != null) {
return this.endpoint;
}
final PoolEntry<HttpRoute, ManagedHttpClientConnection> poolEntry;
try {
poolEntry = leaseFuture.get(timeout, tunit);
if (poolEntry == null || leaseFuture.isCancelled()) {
throw new InterruptedException();
}
} catch (final TimeoutException ex) {
leaseFuture.cancel(true);
throw ex;
}
try {
if (validateAfterInactivity > 0) {
final ManagedHttpClientConnection conn = poolEntry.getConnection();
if (conn != null
&& poolEntry.getUpdated() + validateAfterInactivity <= System.currentTimeMillis()) {
boolean stale;
try {
stale = conn.isStale();
} catch (IOException ignore) {
stale = true;
}
if (stale) {
if (log.isDebugEnabled()) {
log.debug("Connection " + ConnPoolSupport.getId(conn) + " is stale");
}
poolEntry.discardConnection();
}
}
}
if (!poolEntry.hasConnection()) {
poolEntry.assignConnection(connFactory.createConnection(null));
}
if (log.isDebugEnabled()) {
log.debug("Connection leased: " + ConnPoolSupport.formatStats(
poolEntry.getConnection(), route, state, pool));
}
if (leaseFuture.isCancelled()) {
pool.release(poolEntry, false);
} else {
this.endpoint = new InternalConnectionEndpoint(poolEntry);
}
return this.endpoint;
} catch (Exception ex) {
pool.release(poolEntry, false);
throw new ExecutionException(ex.getMessage(), ex);
}
} }
final Future<PoolEntry<HttpRoute, ManagedHttpClientConnection>> future = this.pool.lease(route, state, null);
return new ConnectionRequest() {
@Override @Override
public boolean cancel() { public boolean cancel() {
return future.cancel(true); return leaseFuture.cancel(true);
}
@Override
public HttpClientConnection get(
final long timeout,
final TimeUnit tunit) throws InterruptedException, ExecutionException, ConnectionPoolTimeoutException {
return leaseConnection(future, timeout, tunit);
} }
}; };
} }
protected HttpClientConnection leaseConnection(
final Future<PoolEntry<HttpRoute, ManagedHttpClientConnection>> future,
final long timeout,
final TimeUnit tunit) throws InterruptedException, ExecutionException, ConnectionPoolTimeoutException {
final PoolEntry<HttpRoute, ManagedHttpClientConnection> entry;
try {
entry = future.get(timeout, tunit);
if (entry == null || future.isCancelled()) {
throw new InterruptedException();
}
} catch (final TimeoutException ex) {
future.cancel(true);
throw new ConnectionPoolTimeoutException("Timeout waiting for connection from pool");
}
if (this.validateAfterInactivity > 0) {
final ManagedHttpClientConnection connection = entry.getConnection();
if (connection != null
&& entry.getUpdated() + this.validateAfterInactivity <= System.currentTimeMillis()) {
boolean stale;
try {
stale = connection.isStale();
} catch (IOException ignore) {
stale = true;
}
if (stale) {
entry.discardConnection();
}
}
}
final HttpRoute route = entry.getRoute();
final CPoolProxy poolProxy = new CPoolProxy(entry);
if (entry.hasConnection()) {
poolProxy.markRouteComplete();
} else {
ConnectionConfig config = null;
if (route.getProxyHost() != null) {
config = this.configData.getConnectionConfig(route.getProxyHost());
}
if (config == null) {
config = this.configData.getConnectionConfig(route.getTargetHost());
}
if (config == null) {
config = this.configData.getDefaultConnectionConfig();
}
if (config == null) {
config = ConnectionConfig.DEFAULT;
}
entry.assignConnection(this.connFactory.create(route, config));
}
if (this.log.isDebugEnabled()) {
this.log.debug("Connection leased: " + format(entry) + formatStats(route));
}
return poolProxy;
}
@Override @Override
public void releaseConnection( public void release(
final HttpClientConnection managedConn, final ConnectionEndpoint endpoint,
final Object state, final Object state,
final long keepAlive, final TimeUnit timeUnit) { final long keepAlive, final TimeUnit timeUnit) {
Args.notNull(managedConn, "Managed connection"); Args.notNull(endpoint, "Managed endpoint");
synchronized (managedConn) { final PoolEntry<HttpRoute, ManagedHttpClientConnection> entry = cast(endpoint).detach();
final CPoolProxy poolProxy = CPoolProxy.getProxy(managedConn); if (entry == null) {
if (poolProxy.isDetached()) {
return; return;
} }
final PoolEntry<HttpRoute, ManagedHttpClientConnection> entry = poolProxy.detach();
try {
final ManagedHttpClientConnection conn = entry.getConnection(); final ManagedHttpClientConnection conn = entry.getConnection();
if (conn.isOpen()) { boolean reusable = conn != null && conn.isOpen();
try {
if (reusable) {
final TimeUnit effectiveUnit = timeUnit != null ? timeUnit : TimeUnit.MILLISECONDS; final TimeUnit effectiveUnit = timeUnit != null ? timeUnit : TimeUnit.MILLISECONDS;
entry.updateConnection(keepAlive, effectiveUnit, state); entry.updateConnection(keepAlive, effectiveUnit, state);
if (this.log.isDebugEnabled()) { if (this.log.isDebugEnabled()) {
@ -362,85 +330,61 @@ public void releaseConnection(
} else { } else {
s = "indefinitely"; s = "indefinitely";
} }
this.log.debug("Connection " + format(entry) + " can be kept alive " + s); this.log.debug("Connection " + ConnPoolSupport.getId(conn) + " can be kept alive " + s);
} }
} }
} catch (RuntimeException ex) {
reusable = false;
throw ex;
} finally { } finally {
final ManagedHttpClientConnection conn = entry.getConnection(); this.pool.release(entry, reusable);
this.pool.release(entry, conn.isOpen() && poolProxy.isRouteComplete());
if (this.log.isDebugEnabled()) { if (this.log.isDebugEnabled()) {
this.log.debug("Connection released: " + format(entry) + formatStats(entry.getRoute())); this.log.debug("Connection released: " + ConnPoolSupport.formatStats(
} conn, entry.getRoute(), entry.getState(), pool));
} }
} }
} }
@Override @Override
public void connect( public void connect(
final HttpClientConnection managedConn, final ConnectionEndpoint endpoint,
final HttpRoute route, final long connectTimeout,
final int connectTimeout, final TimeUnit timeUnit,
final HttpContext context) throws IOException { final HttpContext context) throws IOException {
Args.notNull(managedConn, "Managed Connection"); Args.notNull(endpoint, "Managed endpoint");
Args.notNull(route, "HTTP route"); final InternalConnectionEndpoint internalEndpoint = cast(endpoint);
final ManagedHttpClientConnection conn; if (internalEndpoint.isConnected()) {
synchronized (managedConn) { return;
final CPoolProxy poolProxy = CPoolProxy.getProxy(managedConn);
conn = poolProxy.getConnection();
} }
final PoolEntry<HttpRoute, ManagedHttpClientConnection> poolEntry = internalEndpoint.getPoolEntry();
if (!poolEntry.hasConnection()) {
poolEntry.assignConnection(connFactory.createConnection(null));
}
final HttpRoute route = poolEntry.getRoute();
final HttpHost host; final HttpHost host;
if (route.getProxyHost() != null) { if (route.getProxyHost() != null) {
host = route.getProxyHost(); host = route.getProxyHost();
} else { } else {
host = route.getTargetHost(); host = route.getTargetHost();
} }
final InetSocketAddress localAddress = route.getLocalSocketAddress();
SocketConfig socketConfig = this.configData.getSocketConfig(host);
if (socketConfig == null) {
socketConfig = this.configData.getDefaultSocketConfig();
}
if (socketConfig == null) {
socketConfig = SocketConfig.DEFAULT;
}
this.connectionOperator.connect( this.connectionOperator.connect(
conn, host, localAddress, connectTimeout, socketConfig, context); poolEntry.getConnection(),
host,
route.getLocalSocketAddress(),
(int) (timeUnit != null ? timeUnit : TimeUnit.MILLISECONDS).toMillis(connectTimeout),
this.defaultSocketConfig != null ? this.defaultSocketConfig : SocketConfig.DEFAULT,
context);
} }
@Override @Override
public void upgrade( public void upgrade(
final HttpClientConnection managedConn, final ConnectionEndpoint endpoint,
final HttpRoute route,
final HttpContext context) throws IOException { final HttpContext context) throws IOException {
Args.notNull(managedConn, "Managed Connection"); Args.notNull(endpoint, "Managed endpoint");
Args.notNull(route, "HTTP route"); final InternalConnectionEndpoint internalEndpoint = cast(endpoint);
final ManagedHttpClientConnection conn; final PoolEntry<HttpRoute, ManagedHttpClientConnection> poolEntry = internalEndpoint.getValidatedPoolEntry();
synchronized (managedConn) { final HttpRoute route = poolEntry.getRoute();
final CPoolProxy poolProxy = CPoolProxy.getProxy(managedConn); this.connectionOperator.upgrade(poolEntry.getConnection(), route.getTargetHost(), context);
conn = poolProxy.getConnection();
}
this.connectionOperator.upgrade(conn, route.getTargetHost(), context);
}
@Override
public void routeComplete(
final HttpClientConnection managedConn,
final HttpRoute route,
final HttpContext context) throws IOException {
Args.notNull(managedConn, "Managed Connection");
Args.notNull(route, "HTTP route");
synchronized (managedConn) {
final CPoolProxy poolProxy = CPoolProxy.getProxy(managedConn);
poolProxy.markRouteComplete();
}
}
@Override
public void shutdown() {
if (this.isShutDown.compareAndSet(false, true)) {
this.log.debug("Connection manager is shutting down");
this.pool.shutdown();
this.log.debug("Connection manager shut down");
}
} }
@Override @Override
@ -513,35 +457,11 @@ public Set<HttpRoute> getRoutes() {
} }
public SocketConfig getDefaultSocketConfig() { public SocketConfig getDefaultSocketConfig() {
return this.configData.getDefaultSocketConfig(); return this.defaultSocketConfig;
} }
public void setDefaultSocketConfig(final SocketConfig defaultSocketConfig) { public void setDefaultSocketConfig(final SocketConfig defaultSocketConfig) {
this.configData.setDefaultSocketConfig(defaultSocketConfig); this.defaultSocketConfig = defaultSocketConfig;
}
public ConnectionConfig getDefaultConnectionConfig() {
return this.configData.getDefaultConnectionConfig();
}
public void setDefaultConnectionConfig(final ConnectionConfig defaultConnectionConfig) {
this.configData.setDefaultConnectionConfig(defaultConnectionConfig);
}
public SocketConfig getSocketConfig(final HttpHost host) {
return this.configData.getSocketConfig(host);
}
public void setSocketConfig(final HttpHost host, final SocketConfig socketConfig) {
this.configData.setSocketConfig(host, socketConfig);
}
public ConnectionConfig getConnectionConfig(final HttpHost host) {
return this.configData.getConnectionConfig(host);
}
public void setConnectionConfig(final HttpHost host, final ConnectionConfig connectionConfig) {
this.configData.setConnectionConfig(host, connectionConfig);
} }
/** /**
@ -555,12 +475,11 @@ public int getValidateAfterInactivity() {
/** /**
* Defines period of inactivity in milliseconds after which persistent connections must * Defines period of inactivity in milliseconds after which persistent connections must
* be re-validated prior to being {@link #leaseConnection(java.util.concurrent.Future, * be re-validated prior to being {@link #lease(HttpRoute, Object)} leased} to the consumer.
* long, java.util.concurrent.TimeUnit) leased} to the consumer. Non-positive value passed * Non-positive value passed to this method disables connection validation. This check helps
* to this method disables connection validation. This check helps detect connections * detect connections that have become stale (half-closed) while kept inactive in the pool.
* that have become stale (half-closed) while kept inactive in the pool.
* *
* @see #leaseConnection(java.util.concurrent.Future, long, java.util.concurrent.TimeUnit) * @see #lease(HttpRoute, Object)
* *
* @since 4.4 * @since 4.4
*/ */
@ -571,14 +490,11 @@ public void setValidateAfterInactivity(final int ms) {
static class ConfigData { static class ConfigData {
private final Map<HttpHost, SocketConfig> socketConfigMap; private final Map<HttpHost, SocketConfig> socketConfigMap;
private final Map<HttpHost, ConnectionConfig> connectionConfigMap;
private volatile SocketConfig defaultSocketConfig; private volatile SocketConfig defaultSocketConfig;
private volatile ConnectionConfig defaultConnectionConfig;
ConfigData() { ConfigData() {
super(); super();
this.socketConfigMap = new ConcurrentHashMap<>(); this.socketConfigMap = new ConcurrentHashMap<>();
this.connectionConfigMap = new ConcurrentHashMap<>();
} }
public SocketConfig getDefaultSocketConfig() { public SocketConfig getDefaultSocketConfig() {
@ -589,14 +505,6 @@ public void setDefaultSocketConfig(final SocketConfig defaultSocketConfig) {
this.defaultSocketConfig = defaultSocketConfig; this.defaultSocketConfig = defaultSocketConfig;
} }
public ConnectionConfig getDefaultConnectionConfig() {
return this.defaultConnectionConfig;
}
public void setDefaultConnectionConfig(final ConnectionConfig defaultConnectionConfig) {
this.defaultConnectionConfig = defaultConnectionConfig;
}
public SocketConfig getSocketConfig(final HttpHost host) { public SocketConfig getSocketConfig(final HttpHost host) {
return this.socketConfigMap.get(host); return this.socketConfigMap.get(host);
} }
@ -605,12 +513,76 @@ public void setSocketConfig(final HttpHost host, final SocketConfig socketConfig
this.socketConfigMap.put(host, socketConfig); this.socketConfigMap.put(host, socketConfig);
} }
public ConnectionConfig getConnectionConfig(final HttpHost host) {
return this.connectionConfigMap.get(host);
} }
public void setConnectionConfig(final HttpHost host, final ConnectionConfig connectionConfig) { class InternalConnectionEndpoint extends ConnectionEndpoint {
this.connectionConfigMap.put(host, connectionConfig);
private final AtomicReference<PoolEntry<HttpRoute, ManagedHttpClientConnection>> poolEntryRef;
InternalConnectionEndpoint(
final PoolEntry<HttpRoute, ManagedHttpClientConnection> poolEntry) {
this.poolEntryRef = new AtomicReference<>(poolEntry);
}
PoolEntry<HttpRoute, ManagedHttpClientConnection> getPoolEntry() {
final PoolEntry<HttpRoute, ManagedHttpClientConnection> poolEntry = poolEntryRef.get();
if (poolEntry == null) {
throw new ConnectionShutdownException();
}
return poolEntry;
}
PoolEntry<HttpRoute, ManagedHttpClientConnection> getValidatedPoolEntry() {
final PoolEntry<HttpRoute, ManagedHttpClientConnection> poolEntry = getPoolEntry();
final ManagedHttpClientConnection connection = poolEntry.getConnection();
Asserts.check(connection != null && connection.isOpen(), "Endpoint is not connected");
return poolEntry;
}
PoolEntry<HttpRoute, ManagedHttpClientConnection> detach() {
return poolEntryRef.getAndSet(null);
}
@Override
public void shutdown() throws IOException {
final PoolEntry<HttpRoute, ManagedHttpClientConnection> poolEntry = poolEntryRef.get();
if (poolEntry != null) {
final HttpClientConnection connection = poolEntry.getConnection();
poolEntry.discardConnection();
if (connection != null) {
connection.shutdown();
}
}
}
@Override
public void close() throws IOException {
final PoolEntry<HttpRoute, ManagedHttpClientConnection> poolEntry = poolEntryRef.get();
if (poolEntry != null) {
poolEntry.discardConnection();
}
}
@Override
public boolean isConnected() {
final PoolEntry<HttpRoute, ManagedHttpClientConnection> poolEntry = getPoolEntry();
final ManagedHttpClientConnection connection = poolEntry.getConnection();
return connection != null && connection.isOpen();
}
@Override
public void setSocketTimeout(final int timeout) {
getValidatedPoolEntry().getConnection().setSocketTimeout(timeout);
}
@Override
public ClassicHttpResponse execute(
final ClassicHttpRequest request,
final HttpRequestExecutor requestExecutor,
final HttpContext context) throws IOException, HttpException {
Args.notNull(request, "HTTP request");
Args.notNull(requestExecutor, "Request executor");
return requestExecutor.execute(request, getValidatedPoolEntry().getConnection(), context);
} }
} }

View File

@ -30,14 +30,18 @@
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.apache.hc.client5.http.DnsResolver; import org.apache.hc.client5.http.DnsResolver;
import org.apache.hc.client5.http.HttpRoute;
import org.apache.hc.client5.http.SchemePortResolver;
import org.apache.hc.client5.http.io.ManagedHttpClientConnection;
import org.apache.hc.client5.http.socket.ConnectionSocketFactory; import org.apache.hc.client5.http.socket.ConnectionSocketFactory;
import org.apache.hc.client5.http.socket.LayeredConnectionSocketFactory; import org.apache.hc.client5.http.socket.LayeredConnectionSocketFactory;
import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory; import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
import org.apache.hc.core5.http.config.ConnectionConfig;
import org.apache.hc.core5.http.config.RegistryBuilder; import org.apache.hc.core5.http.config.RegistryBuilder;
import org.apache.hc.core5.http.config.SocketConfig; import org.apache.hc.core5.http.config.SocketConfig;
import org.apache.hc.core5.util.TextUtils; import org.apache.hc.core5.http.io.HttpConnectionFactory;
import org.apache.hc.core5.pool.ConnPoolListener;
import org.apache.hc.core5.pool.ConnPoolPolicy;
/** /**
* Builder for {@link PoolingHttpClientConnectionManager} instances. * Builder for {@link PoolingHttpClientConnectionManager} instances.
@ -67,11 +71,13 @@
*/ */
public class PoolingHttpClientConnectionManagerBuilder { public class PoolingHttpClientConnectionManagerBuilder {
private HttpConnectionFactory<ManagedHttpClientConnection> connectionFactory;
private LayeredConnectionSocketFactory sslSocketFactory; private LayeredConnectionSocketFactory sslSocketFactory;
private SchemePortResolver schemePortResolver;
private DnsResolver dnsResolver; private DnsResolver dnsResolver;
private ConnPoolPolicy connPoolPolicy;
private ConnPoolListener<HttpRoute> connPoolListener;
private SocketConfig defaultSocketConfig; private SocketConfig defaultSocketConfig;
private ConnectionConfig defaultConnectionConfig;
private boolean systemProperties; private boolean systemProperties;
@ -91,7 +97,16 @@ public static PoolingHttpClientConnectionManagerBuilder create() {
} }
/** /**
* Assigns {@link LayeredConnectionSocketFactory} instance for SSL connections. * Assigns {@link HttpConnectionFactory} instance.
*/
public final PoolingHttpClientConnectionManagerBuilder setConnectionFactory(
final HttpConnectionFactory<ManagedHttpClientConnection> connectionFactory) {
this.connectionFactory = connectionFactory;
return this;
}
/**
* Assigns {@link LayeredConnectionSocketFactory} instance.
*/ */
public final PoolingHttpClientConnectionManagerBuilder setSSLSocketFactory( public final PoolingHttpClientConnectionManagerBuilder setSSLSocketFactory(
final LayeredConnectionSocketFactory sslSocketFactory) { final LayeredConnectionSocketFactory sslSocketFactory) {
@ -99,6 +114,38 @@ public final PoolingHttpClientConnectionManagerBuilder setSSLSocketFactory(
return this; return this;
} }
/**
* Assigns {@link DnsResolver} instance.
*/
public final PoolingHttpClientConnectionManagerBuilder setDnsResolver(final DnsResolver dnsResolver) {
this.dnsResolver = dnsResolver;
return this;
}
/**
* Assigns {@link SchemePortResolver} instance.
*/
public final PoolingHttpClientConnectionManagerBuilder setSchemePortResolver(final SchemePortResolver schemePortResolver) {
this.schemePortResolver = schemePortResolver;
return this;
}
/**
* Assigns {@link ConnPoolPolicy} value.
*/
public final PoolingHttpClientConnectionManagerBuilder setConnPoolPolicy(final ConnPoolPolicy connPoolPolicy) {
this.connPoolPolicy = connPoolPolicy;
return this;
}
/**
* Assigns {@link ConnPoolListener} instance.
*/
public final PoolingHttpClientConnectionManagerBuilder setConnPoolListener(final ConnPoolListener<HttpRoute> connPoolListener) {
this.connPoolListener = connPoolListener;
return this;
}
/** /**
* Assigns maximum total connection value. * Assigns maximum total connection value.
*/ */
@ -123,18 +170,8 @@ public final PoolingHttpClientConnectionManagerBuilder setDefaultSocketConfig(fi
return this; return this;
} }
/**
* Assigns default {@link ConnectionConfig}.
*/
public final PoolingHttpClientConnectionManagerBuilder setDefaultConnectionConfig(final ConnectionConfig config) {
this.defaultConnectionConfig = config;
return this;
}
/** /**
* Sets maximum time to live for persistent connections * Sets maximum time to live for persistent connections
*
* @since 4.4
*/ */
public final PoolingHttpClientConnectionManagerBuilder setConnectionTimeToLive(final long connTimeToLive, final TimeUnit connTimeToLiveTimeUnit) { public final PoolingHttpClientConnectionManagerBuilder setConnectionTimeToLive(final long connTimeToLive, final TimeUnit connTimeToLiveTimeUnit) {
this.connTimeToLive = connTimeToLive; this.connTimeToLive = connTimeToLive;
@ -153,14 +190,6 @@ public final PoolingHttpClientConnectionManagerBuilder setValidateAfterInactivit
return this; return this;
} }
/**
* Assigns {@link DnsResolver} instance.
*/
public final PoolingHttpClientConnectionManagerBuilder setDnsResolver(final DnsResolver dnsResolver) {
this.dnsResolver = dnsResolver;
return this;
}
/** /**
* Use system properties when creating and configuring default * Use system properties when creating and configuring default
* implementations. * implementations.
@ -170,13 +199,6 @@ public final PoolingHttpClientConnectionManagerBuilder useSystemProperties() {
return this; return this;
} }
private static String[] split(final String s) {
if (TextUtils.isBlank(s)) {
return null;
}
return s.split(" *, *");
}
public PoolingHttpClientConnectionManager build() { public PoolingHttpClientConnectionManager build() {
@SuppressWarnings("resource") @SuppressWarnings("resource")
final PoolingHttpClientConnectionManager poolingmgr = new PoolingHttpClientConnectionManager( final PoolingHttpClientConnectionManager poolingmgr = new PoolingHttpClientConnectionManager(
@ -187,18 +209,17 @@ public PoolingHttpClientConnectionManager build() {
SSLConnectionSocketFactory.getSystemSocketFactory() : SSLConnectionSocketFactory.getSystemSocketFactory() :
SSLConnectionSocketFactory.getSocketFactory())) SSLConnectionSocketFactory.getSocketFactory()))
.build(), .build(),
null, connectionFactory,
null, schemePortResolver,
dnsResolver, dnsResolver,
connPoolPolicy,
connPoolListener,
connTimeToLive, connTimeToLive,
connTimeToLiveTimeUnit != null ? connTimeToLiveTimeUnit : TimeUnit.MILLISECONDS); connTimeToLiveTimeUnit != null ? connTimeToLiveTimeUnit : TimeUnit.MILLISECONDS);
poolingmgr.setValidateAfterInactivity(this.validateAfterInactivity); poolingmgr.setValidateAfterInactivity(this.validateAfterInactivity);
if (defaultSocketConfig != null) { if (defaultSocketConfig != null) {
poolingmgr.setDefaultSocketConfig(defaultSocketConfig); poolingmgr.setDefaultSocketConfig(defaultSocketConfig);
} }
if (defaultConnectionConfig != null) {
poolingmgr.setDefaultConnectionConfig(defaultConnectionConfig);
}
if (maxConnTotal > 0) { if (maxConnTotal > 0) {
poolingmgr.setMaxTotal(maxConnTotal); poolingmgr.setMaxTotal(maxConnTotal);
} }

View File

@ -25,16 +25,11 @@
* *
*/ */
package org.apache.hc.client5.http.impl.io; package org.apache.hc.client5.http.impl.logging;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
/**
* Internal class.
*
* @since 4.3
*/
class LoggingInputStream extends InputStream { class LoggingInputStream extends InputStream {
private final InputStream in; private final InputStream in;

View File

@ -25,7 +25,7 @@
* *
*/ */
package org.apache.hc.client5.http.impl.io; package org.apache.hc.client5.http.impl.logging;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
@ -49,6 +49,7 @@ public LoggingOutputStream(final OutputStream out, final Wire wire) {
@Override @Override
public void write(final int b) throws IOException { public void write(final int b) throws IOException {
try { try {
out.write(b);
wire.output(b); wire.output(b);
} catch (final IOException ex) { } catch (final IOException ex) {
wire.output("[write] I/O error: " + ex.getMessage()); wire.output("[write] I/O error: " + ex.getMessage());

View File

@ -25,7 +25,7 @@
* *
*/ */
package org.apache.hc.client5.http.impl.io; package org.apache.hc.client5.http.impl.logging;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -33,14 +33,15 @@
import java.net.Socket; import java.net.Socket;
import org.apache.hc.core5.http.impl.io.SocketHolder; import org.apache.hc.core5.http.impl.io.SocketHolder;
import org.apache.logging.log4j.Logger;
class LoggingSocketHolder extends SocketHolder { public class LoggingSocketHolder extends SocketHolder {
private final Wire wire; private final Wire wire;
LoggingSocketHolder(final Socket socket, final Wire wire) { public LoggingSocketHolder(final Socket socket, final String id, final Logger log) {
super(socket); super(socket);
this.wire = wire; this.wire = new Wire(log, id);
} }
@Override @Override

View File

@ -24,52 +24,36 @@
* <http://www.apache.org/>. * <http://www.apache.org/>.
* *
*/ */
package org.apache.hc.client5.http.impl.io;
import java.io.ByteArrayInputStream; package org.apache.hc.client5.http.impl.logging;
import java.io.IOException;
import java.io.InputStream; import java.nio.ByteBuffer;
import org.apache.hc.core5.annotation.Contract;
import org.apache.hc.core5.annotation.ThreadingBehavior;
import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Args;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
/**
* Logs data to the wire LOG.
*
* @since 4.0
*/
@Contract(threading = ThreadingBehavior.IMMUTABLE)
class Wire { class Wire {
private final Logger log; private final Logger log;
private final String id; private final String id;
/** Wire(final Logger log, final String id) {
* @since 4.3 super();
*/
public Wire(final Logger log, final String id) {
this.log = log; this.log = log;
this.id = id; this.id = id;
} }
public Wire(final Logger log) { private void wire(final String header, final byte[] b, final int pos, final int off) {
this(log, "");
}
private void wire(final String header, final InputStream instream)
throws IOException {
final StringBuilder buffer = new StringBuilder(); final StringBuilder buffer = new StringBuilder();
int ch; for (int i = 0; i < off; i++) {
while ((ch = instream.read()) != -1) { final int ch = b[pos + i];
if (ch == 13) { if (ch == 13) {
buffer.append("[\\r]"); buffer.append("[\\r]");
} else if (ch == 10) { } else if (ch == 10) {
buffer.append("[\\n]\""); buffer.append("[\\n]\"");
buffer.insert(0, "\""); buffer.insert(0, "\"");
buffer.insert(0, header); buffer.insert(0, header);
log.debug(id + " " + buffer.toString()); this.log.debug(this.id + " " + buffer.toString());
buffer.setLength(0); buffer.setLength(0);
} else if ((ch < 32) || (ch > 127)) { } else if ((ch < 32) || (ch > 127)) {
buffer.append("[0x"); buffer.append("[0x");
@ -83,70 +67,73 @@ private void wire(final String header, final InputStream instream)
buffer.append('\"'); buffer.append('\"');
buffer.insert(0, '\"'); buffer.insert(0, '\"');
buffer.insert(0, header); buffer.insert(0, header);
log.debug(id + " " + buffer.toString()); this.log.debug(this.id + " " + buffer.toString());
} }
} }
public boolean enabled() { public boolean isEnabled() {
return log.isDebugEnabled(); return this.log.isDebugEnabled();
} }
public void output(final InputStream outstream) public void output(final byte[] b, final int pos, final int off) {
throws IOException {
Args.notNull(outstream, "Output");
wire(">> ", outstream);
}
public void input(final InputStream instream)
throws IOException {
Args.notNull(instream, "Input");
wire("<< ", instream);
}
public void output(final byte[] b, final int off, final int len)
throws IOException {
Args.notNull(b, "Output"); Args.notNull(b, "Output");
wire(">> ", new ByteArrayInputStream(b, off, len)); wire(">> ", b, pos, off);
} }
public void input(final byte[] b, final int off, final int len) public void input(final byte[] b, final int pos, final int off) {
throws IOException {
Args.notNull(b, "Input"); Args.notNull(b, "Input");
wire("<< ", new ByteArrayInputStream(b, off, len)); wire("<< ", b, pos, off);
} }
public void output(final byte[] b) public void output(final byte[] b) {
throws IOException {
Args.notNull(b, "Output"); Args.notNull(b, "Output");
wire(">> ", new ByteArrayInputStream(b)); output(b, 0, b.length);
} }
public void input(final byte[] b) public void input(final byte[] b) {
throws IOException {
Args.notNull(b, "Input"); Args.notNull(b, "Input");
wire("<< ", new ByteArrayInputStream(b)); input(b, 0, b.length);
} }
public void output(final int b) public void output(final int b) {
throws IOException {
output(new byte[] {(byte) b}); output(new byte[] {(byte) b});
} }
public void input(final int b) public void input(final int b) {
throws IOException {
input(new byte[] {(byte) b}); input(new byte[] {(byte) b});
} }
public void output(final String s) public void output(final String s) {
throws IOException {
Args.notNull(s, "Output"); Args.notNull(s, "Output");
output(s.getBytes()); output(s.getBytes());
} }
public void input(final String s) public void input(final String s) {
throws IOException {
Args.notNull(s, "Input"); Args.notNull(s, "Input");
input(s.getBytes()); input(s.getBytes());
} }
public void output(final ByteBuffer b) {
Args.notNull(b, "Output");
if (b.hasArray()) {
output(b.array(), b.arrayOffset() + b.position(), b.remaining());
} else {
final byte[] tmp = new byte[b.remaining()];
b.get(tmp);
output(tmp);
}
}
public void input(final ByteBuffer b) {
Args.notNull(b, "Input");
if (b.hasArray()) {
input(b.array(), b.arrayOffset() + b.position(), b.remaining());
} else {
final byte[] tmp = new byte[b.remaining()];
b.get(tmp);
input(tmp);
}
}
} }

View File

@ -32,7 +32,6 @@
import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.annotation.ThreadingBehavior;
import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpException;
import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.protocol.HttpContext;
import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Args;
@ -59,7 +58,6 @@ public DefaultProxyRoutePlanner(final HttpHost proxy) {
@Override @Override
protected HttpHost determineProxy( protected HttpHost determineProxy(
final HttpHost target, final HttpHost target,
final HttpRequest request,
final HttpContext context) throws HttpException { final HttpContext context) throws HttpException {
return proxy; return proxy;
} }

View File

@ -43,7 +43,7 @@
import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.http.ProtocolException;
import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.protocol.HttpContext;
import org.apache.hc.core5.util.Args; import org.apache.hc.core5.net.URIAuthority;
/** /**
* Default implementation of an {@link HttpRoutePlanner}. It will not make use of * Default implementation of an {@link HttpRoutePlanner}. It will not make use of
@ -63,11 +63,7 @@ public DefaultRoutePlanner(final SchemePortResolver schemePortResolver) {
} }
@Override @Override
public HttpRoute determineRoute( public HttpRoute determineRoute(final HttpHost host, final HttpContext context) throws HttpException {
final HttpHost host,
final HttpRequest request,
final HttpContext context) throws HttpException {
Args.notNull(request, "Request");
if (host == null) { if (host == null) {
throw new ProtocolException("Target host is not specified"); throw new ProtocolException("Target host is not specified");
} }
@ -76,7 +72,7 @@ public HttpRoute determineRoute(
final InetAddress local = config.getLocalAddress(); final InetAddress local = config.getLocalAddress();
HttpHost proxy = config.getProxy(); HttpHost proxy = config.getProxy();
if (proxy == null) { if (proxy == null) {
proxy = determineProxy(host, request, context); proxy = determineProxy(host, context);
} }
final HttpHost target; final HttpHost target;
@ -100,6 +96,20 @@ public HttpRoute determineRoute(
} }
} }
@Override
public HttpHost determineTargetHost(final HttpRequest request, final HttpContext context) throws HttpException {
final URIAuthority authority = request.getAuthority();
if (authority != null) {
final String scheme = request.getScheme();
if (scheme == null) {
throw new ProtocolException("Protocol scheme is not specified");
}
return new HttpHost(authority, scheme);
} else {
return null;
}
}
/** /**
* This implementation returns null. * This implementation returns null.
* *
@ -107,7 +117,6 @@ public HttpRoute determineRoute(
*/ */
protected HttpHost determineProxy( protected HttpHost determineProxy(
final HttpHost target, final HttpHost target,
final HttpRequest request,
final HttpContext context) throws HttpException { final HttpContext context) throws HttpException {
return null; return null;
} }

View File

@ -39,7 +39,6 @@
import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.annotation.ThreadingBehavior;
import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpException;
import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.protocol.HttpContext;
/** /**
@ -73,10 +72,7 @@ public SystemDefaultRoutePlanner(final ProxySelector proxySelector) {
} }
@Override @Override
protected HttpHost determineProxy( protected HttpHost determineProxy(final HttpHost target, final HttpContext context) throws HttpException {
final HttpHost target,
final HttpRequest request,
final HttpContext context) throws HttpException {
final URI targetURI; final URI targetURI;
try { try {
targetURI = new URI(target.toURI()); targetURI = new URI(target.toURI());

View File

@ -25,11 +25,10 @@
* *
*/ */
package org.apache.hc.client5.http.impl; package org.apache.hc.client5.http.impl.sync;
import java.io.IOException; import java.io.IOException;
import org.apache.hc.client5.http.impl.sync.AbstractResponseHandler;
import org.apache.hc.client5.http.protocol.ClientProtocolException; import org.apache.hc.client5.http.protocol.ClientProtocolException;
import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.Contract;
import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.annotation.ThreadingBehavior;
@ -39,13 +38,13 @@
import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.hc.core5.http.io.entity.EntityUtils;
/** /**
* A {@link org.apache.hc.client5.http.sync.ResponseHandler} that returns the response body as a String * A {@link org.apache.hc.core5.http.io.ResponseHandler} that returns the response body as a String
* for successful (2xx) responses. If the response code was &gt;= 300, the response * for successful (2xx) responses. If the response code was &gt;= 300, the response
* body is consumed and an {@link org.apache.hc.client5.http.protocol.HttpResponseException} is thrown. * body is consumed and an {@link org.apache.hc.client5.http.protocol.HttpResponseException} is thrown.
* <p> * <p>
* If this is used with * If this is used with
* {@link org.apache.hc.client5.http.sync.HttpClient#execute( * {@link org.apache.hc.client5.http.sync.HttpClient#execute(
* org.apache.hc.core5.http.ClassicHttpRequest, org.apache.hc.client5.http.sync.ResponseHandler)}, * org.apache.hc.core5.http.ClassicHttpRequest, org.apache.hc.core5.http.io.ResponseHandler)},
* HttpClient may handle redirects (3xx responses) internally. * HttpClient may handle redirects (3xx responses) internally.
* </p> * </p>
* *

View File

@ -32,12 +32,11 @@
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.hc.client5.http.io.ConnectionReleaseTrigger; import org.apache.hc.client5.http.io.ConnectionEndpoint;
import org.apache.hc.client5.http.io.HttpClientConnectionManager; import org.apache.hc.client5.http.io.HttpClientConnectionManager;
import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.Contract;
import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.annotation.ThreadingBehavior;
import org.apache.hc.core5.concurrent.Cancellable; import org.apache.hc.core5.concurrent.Cancellable;
import org.apache.hc.core5.http.io.HttpClientConnection;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
/** /**
@ -46,26 +45,25 @@
* @since 4.3 * @since 4.3
*/ */
@Contract(threading = ThreadingBehavior.SAFE_CONDITIONAL) @Contract(threading = ThreadingBehavior.SAFE_CONDITIONAL)
class ConnectionHolder implements ConnectionReleaseTrigger, Cancellable, Closeable { class EndpointHolder implements Cancellable, Closeable {
private final Logger log; private final Logger log;
private final HttpClientConnectionManager manager; private final HttpClientConnectionManager manager;
private final HttpClientConnection managedConn; private final ConnectionEndpoint endpoint;
private final AtomicBoolean released; private final AtomicBoolean released;
private volatile boolean reusable; private volatile boolean reusable;
private volatile Object state; private volatile Object state;
private volatile long validDuration; private volatile long validDuration;
private volatile TimeUnit tunit;
public ConnectionHolder( public EndpointHolder(
final Logger log, final Logger log,
final HttpClientConnectionManager manager, final HttpClientConnectionManager manager,
final HttpClientConnection managedConn) { final ConnectionEndpoint endpoint) {
super(); super();
this.log = log; this.log = log;
this.manager = manager; this.manager = manager;
this.managedConn = managedConn; this.endpoint = endpoint;
this.released = new AtomicBoolean(false); this.released = new AtomicBoolean(false);
} }
@ -86,54 +84,46 @@ public void setState(final Object state) {
} }
public void setValidFor(final long duration, final TimeUnit tunit) { public void setValidFor(final long duration, final TimeUnit tunit) {
synchronized (this.managedConn) { this.validDuration = (tunit != null ? tunit : TimeUnit.MILLISECONDS).toMillis(duration);
this.validDuration = duration;
this.tunit = tunit;
}
} }
private void releaseConnection(final boolean reusable) { private void releaseConnection(final boolean reusable) {
if (this.released.compareAndSet(false, true)) { if (this.released.compareAndSet(false, true)) {
synchronized (this.managedConn) { synchronized (this.endpoint) {
if (reusable) { if (reusable) {
this.manager.releaseConnection(this.managedConn, this.manager.release(this.endpoint, this.state, this.validDuration, TimeUnit.MILLISECONDS);
this.state, this.validDuration, this.tunit);
} else { } else {
try { try {
this.managedConn.close(); this.endpoint.close();
log.debug("Connection discarded"); log.debug("Connection discarded");
} catch (final IOException ex) { } catch (final IOException ex) {
if (this.log.isDebugEnabled()) { if (this.log.isDebugEnabled()) {
this.log.debug(ex.getMessage(), ex); this.log.debug(ex.getMessage(), ex);
} }
} finally { } finally {
this.manager.releaseConnection( this.manager.release(this.endpoint, null, 0, TimeUnit.MILLISECONDS);
this.managedConn, null, 0, TimeUnit.MILLISECONDS);
} }
} }
} }
} }
} }
@Override
public void releaseConnection() { public void releaseConnection() {
releaseConnection(this.reusable); releaseConnection(this.reusable);
} }
@Override
public void abortConnection() { public void abortConnection() {
if (this.released.compareAndSet(false, true)) { if (this.released.compareAndSet(false, true)) {
synchronized (this.managedConn) { synchronized (this.endpoint) {
try { try {
this.managedConn.shutdown(); this.endpoint.shutdown();
log.debug("Connection discarded"); log.debug("Connection discarded");
} catch (final IOException ex) { } catch (final IOException ex) {
if (this.log.isDebugEnabled()) { if (this.log.isDebugEnabled()) {
this.log.debug(ex.getMessage(), ex); this.log.debug(ex.getMessage(), ex);
} }
} finally { } finally {
this.manager.releaseConnection( this.manager.release(this.endpoint, null, 0, TimeUnit.MILLISECONDS);
this.managedConn, null, 0, TimeUnit.MILLISECONDS);
} }
} }
} }

View File

@ -99,7 +99,7 @@
import org.apache.hc.core5.http.protocol.RequestContent; import org.apache.hc.core5.http.protocol.RequestContent;
import org.apache.hc.core5.http.protocol.RequestTargetHost; import org.apache.hc.core5.http.protocol.RequestTargetHost;
import org.apache.hc.core5.http.protocol.RequestUserAgent; import org.apache.hc.core5.http.protocol.RequestUserAgent;
import org.apache.hc.core5.util.TextUtils; import org.apache.hc.core5.pool.ConnPoolControl;
import org.apache.hc.core5.util.VersionInfo; import org.apache.hc.core5.util.VersionInfo;
/** /**
@ -547,14 +547,11 @@ public final HttpClientBuilder useSystemProperties() {
* One MUST explicitly close HttpClient with {@link CloseableHttpClient#close()} in order * One MUST explicitly close HttpClient with {@link CloseableHttpClient#close()} in order
* to stop and release the background thread. * to stop and release the background thread.
* <p> * <p>
* Please note this method has no effect if the instance of HttpClient is configuted to * Please note this method has no effect if the instance of HttpClient is configured to
* use a shared connection manager. * use a shared connection manager.
* <p>
* Please note this method may not be used when the instance of HttpClient is created
* inside an EJB container.
* *
* @see #setConnectionManagerShared(boolean) * @see #setConnectionManagerShared(boolean)
* @see HttpClientConnectionManager#closeExpired() * @see ConnPoolControl#closeExpired()
* *
* @since 4.4 * @since 4.4
*/ */
@ -570,14 +567,11 @@ public final HttpClientBuilder evictExpiredConnections() {
* One MUST explicitly close HttpClient with {@link CloseableHttpClient#close()} in order * One MUST explicitly close HttpClient with {@link CloseableHttpClient#close()} in order
* to stop and release the background thread. * to stop and release the background thread.
* <p> * <p>
* Please note this method has no effect if the instance of HttpClient is configuted to * Please note this method has no effect if the instance of HttpClient is configured to
* use a shared connection manager. * use a shared connection manager.
* <p>
* Please note this method may not be used when the instance of HttpClient is created
* inside an EJB container.
* *
* @see #setConnectionManagerShared(boolean) * @see #setConnectionManagerShared(boolean)
* @see HttpClientConnectionManager#closeExpired() * @see ConnPoolControl#closeIdle(long, TimeUnit)
* *
* @param maxIdleTime maximum time persistent connections can stay idle while kept alive * @param maxIdleTime maximum time persistent connections can stay idle while kept alive
* in the connection pool. Connections whose inactivity period exceeds this value will * in the connection pool. Connections whose inactivity period exceeds this value will
@ -652,13 +646,6 @@ protected void addCloseable(final Closeable closeable) {
closeables.add(closeable); closeables.add(closeable);
} }
private static String[] split(final String s) {
if (TextUtils.isBlank(s)) {
return null;
}
return s.split(" *, *");
}
public CloseableHttpClient build() { public CloseableHttpClient build() {
// Create main request executor // Create main request executor
// We copy the instance fields to avoid changing them, and rename to avoid accidental use of the wrong version // We copy the instance fields to avoid changing them, and rename to avoid accidental use of the wrong version
@ -873,10 +860,9 @@ public boolean keepAlive(
if (closeablesCopy == null) { if (closeablesCopy == null) {
closeablesCopy = new ArrayList<>(1); closeablesCopy = new ArrayList<>(1);
} }
final HttpClientConnectionManager cm = connManagerCopy;
if (evictExpiredConnections || evictIdleConnections) { if (evictExpiredConnections || evictIdleConnections) {
final IdleConnectionEvictor connectionEvictor = new IdleConnectionEvictor(cm, if (connManagerCopy instanceof ConnPoolControl) {
final IdleConnectionEvictor connectionEvictor = new IdleConnectionEvictor((ConnPoolControl<?>) connManagerCopy,
maxIdleTime > 0 ? maxIdleTime : 10, maxIdleTimeUnit != null ? maxIdleTimeUnit : TimeUnit.SECONDS); maxIdleTime > 0 ? maxIdleTime : 10, maxIdleTimeUnit != null ? maxIdleTimeUnit : TimeUnit.SECONDS);
closeablesCopy.add(new Closeable() { closeablesCopy.add(new Closeable() {
@ -888,19 +874,12 @@ public void close() throws IOException {
}); });
connectionEvictor.start(); connectionEvictor.start();
} }
closeablesCopy.add(new Closeable() {
@Override
public void close() throws IOException {
cm.shutdown();
} }
closeablesCopy.add(connManagerCopy);
});
} }
return new InternalHttpClient( return new InternalHttpClient(
execChain, execChain,
connManagerCopy,
routePlannerCopy, routePlannerCopy,
cookieSpecRegistryCopy, cookieSpecRegistryCopy,
authSchemeRegistryCopy, authSchemeRegistryCopy,

View File

@ -30,7 +30,8 @@
import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.apache.hc.client5.http.io.HttpClientConnectionManager; import org.apache.hc.client5.http.impl.DefaultThreadFactory;
import org.apache.hc.core5.pool.ConnPoolControl;
import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Args;
/** /**
@ -41,7 +42,6 @@
*/ */
public final class IdleConnectionEvictor { public final class IdleConnectionEvictor {
private final HttpClientConnectionManager connectionManager;
private final ThreadFactory threadFactory; private final ThreadFactory threadFactory;
private final Thread thread; private final Thread thread;
private final long sleepTimeMs; private final long sleepTimeMs;
@ -50,12 +50,12 @@ public final class IdleConnectionEvictor {
private volatile Exception exception; private volatile Exception exception;
public IdleConnectionEvictor( public IdleConnectionEvictor(
final HttpClientConnectionManager connectionManager, final ConnPoolControl<?> connectionManager,
final ThreadFactory threadFactory, final ThreadFactory threadFactory,
final long sleepTime, final TimeUnit sleepTimeUnit, final long sleepTime, final TimeUnit sleepTimeUnit,
final long maxIdleTime, final TimeUnit maxIdleTimeUnit) { final long maxIdleTime, final TimeUnit maxIdleTimeUnit) {
this.connectionManager = Args.notNull(connectionManager, "Connection manager"); Args.notNull(connectionManager, "Connection manager");
this.threadFactory = threadFactory != null ? threadFactory : new DefaultThreadFactory(); this.threadFactory = threadFactory != null ? threadFactory : new DefaultThreadFactory("idle-connection-evictor", true);
this.sleepTimeMs = sleepTimeUnit != null ? sleepTimeUnit.toMillis(sleepTime) : sleepTime; this.sleepTimeMs = sleepTimeUnit != null ? sleepTimeUnit.toMillis(sleepTime) : sleepTime;
this.maxIdleTimeMs = maxIdleTimeUnit != null ? maxIdleTimeUnit.toMillis(maxIdleTime) : maxIdleTime; this.maxIdleTimeMs = maxIdleTimeUnit != null ? maxIdleTimeUnit.toMillis(maxIdleTime) : maxIdleTime;
this.thread = this.threadFactory.newThread(new Runnable() { this.thread = this.threadFactory.newThread(new Runnable() {
@ -78,14 +78,14 @@ public void run() {
} }
public IdleConnectionEvictor( public IdleConnectionEvictor(
final HttpClientConnectionManager connectionManager, final ConnPoolControl<?> connectionManager,
final long sleepTime, final TimeUnit sleepTimeUnit, final long sleepTime, final TimeUnit sleepTimeUnit,
final long maxIdleTime, final TimeUnit maxIdleTimeUnit) { final long maxIdleTime, final TimeUnit maxIdleTimeUnit) {
this(connectionManager, null, sleepTime, sleepTimeUnit, maxIdleTime, maxIdleTimeUnit); this(connectionManager, null, sleepTime, sleepTimeUnit, maxIdleTime, maxIdleTimeUnit);
} }
public IdleConnectionEvictor( public IdleConnectionEvictor(
final HttpClientConnectionManager connectionManager, final ConnPoolControl<?> connectionManager,
final long maxIdleTime, final TimeUnit maxIdleTimeUnit) { final long maxIdleTime, final TimeUnit maxIdleTimeUnit) {
this(connectionManager, null, this(connectionManager, null,
maxIdleTime > 0 ? maxIdleTime : 5, maxIdleTimeUnit != null ? maxIdleTimeUnit : TimeUnit.SECONDS, maxIdleTime > 0 ? maxIdleTime : 5, maxIdleTimeUnit != null ? maxIdleTimeUnit : TimeUnit.SECONDS,
@ -108,16 +108,4 @@ public void awaitTermination(final long time, final TimeUnit tunit) throws Inter
thread.join((tunit != null ? tunit : TimeUnit.MILLISECONDS).toMillis(time)); thread.join((tunit != null ? tunit : TimeUnit.MILLISECONDS).toMillis(time));
} }
static class DefaultThreadFactory implements ThreadFactory {
@Override
public Thread newThread(final Runnable r) {
final Thread t = new Thread(r, "Connection evictor");
t.setDaemon(true);
return t;
}
}
} }

View File

@ -37,7 +37,6 @@
import org.apache.hc.client5.http.config.RequestConfig; import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.cookie.CookieSpecProvider; import org.apache.hc.client5.http.cookie.CookieSpecProvider;
import org.apache.hc.client5.http.cookie.CookieStore; import org.apache.hc.client5.http.cookie.CookieStore;
import org.apache.hc.client5.http.io.HttpClientConnectionManager;
import org.apache.hc.client5.http.methods.Configurable; import org.apache.hc.client5.http.methods.Configurable;
import org.apache.hc.client5.http.methods.HttpExecutionAware; import org.apache.hc.client5.http.methods.HttpExecutionAware;
import org.apache.hc.client5.http.methods.RoutedHttpRequest; import org.apache.hc.client5.http.methods.RoutedHttpRequest;
@ -70,7 +69,6 @@ class InternalHttpClient extends CloseableHttpClient implements Configurable {
private final Logger log = LogManager.getLogger(getClass()); private final Logger log = LogManager.getLogger(getClass());
private final ClientExecChain execChain; private final ClientExecChain execChain;
private final HttpClientConnectionManager connManager;
private final HttpRoutePlanner routePlanner; private final HttpRoutePlanner routePlanner;
private final Lookup<CookieSpecProvider> cookieSpecRegistry; private final Lookup<CookieSpecProvider> cookieSpecRegistry;
private final Lookup<AuthSchemeProvider> authSchemeRegistry; private final Lookup<AuthSchemeProvider> authSchemeRegistry;
@ -81,7 +79,6 @@ class InternalHttpClient extends CloseableHttpClient implements Configurable {
public InternalHttpClient( public InternalHttpClient(
final ClientExecChain execChain, final ClientExecChain execChain,
final HttpClientConnectionManager connManager,
final HttpRoutePlanner routePlanner, final HttpRoutePlanner routePlanner,
final Lookup<CookieSpecProvider> cookieSpecRegistry, final Lookup<CookieSpecProvider> cookieSpecRegistry,
final Lookup<AuthSchemeProvider> authSchemeRegistry, final Lookup<AuthSchemeProvider> authSchemeRegistry,
@ -91,10 +88,8 @@ public InternalHttpClient(
final List<Closeable> closeables) { final List<Closeable> closeables) {
super(); super();
Args.notNull(execChain, "HTTP client exec chain"); Args.notNull(execChain, "HTTP client exec chain");
Args.notNull(connManager, "HTTP connection manager");
Args.notNull(routePlanner, "HTTP route planner"); Args.notNull(routePlanner, "HTTP route planner");
this.execChain = execChain; this.execChain = execChain;
this.connManager = connManager;
this.routePlanner = routePlanner; this.routePlanner = routePlanner;
this.cookieSpecRegistry = cookieSpecRegistry; this.cookieSpecRegistry = cookieSpecRegistry;
this.authSchemeRegistry = authSchemeRegistry; this.authSchemeRegistry = authSchemeRegistry;
@ -105,10 +100,11 @@ public InternalHttpClient(
} }
private HttpRoute determineRoute( private HttpRoute determineRoute(
final HttpHost target, final HttpHost host,
final HttpRequest request, final HttpRequest request,
final HttpContext context) throws HttpException { final HttpContext context) throws HttpException {
return this.routePlanner.determineRoute(target, request, context); final HttpHost target = host != null ? host : this.routePlanner.determineTargetHost(request, context);
return this.routePlanner.determineRoute(target, context);
} }
private void setupContext(final HttpClientContext context) { private void setupContext(final HttpClientContext context) {

View File

@ -31,6 +31,7 @@
import java.io.InterruptedIOException; import java.io.InterruptedIOException;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.apache.hc.client5.http.ConnectionKeepAliveStrategy; import org.apache.hc.client5.http.ConnectionKeepAliveStrategy;
import org.apache.hc.client5.http.HttpRoute; import org.apache.hc.client5.http.HttpRoute;
@ -39,10 +40,11 @@
import org.apache.hc.client5.http.auth.ChallengeType; import org.apache.hc.client5.http.auth.ChallengeType;
import org.apache.hc.client5.http.config.RequestConfig; import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.auth.HttpAuthenticator; import org.apache.hc.client5.http.impl.auth.HttpAuthenticator;
import org.apache.hc.client5.http.impl.io.ConnectionShutdownException; import org.apache.hc.client5.http.impl.ConnectionShutdownException;
import org.apache.hc.client5.http.impl.routing.BasicRouteDirector; import org.apache.hc.client5.http.impl.routing.BasicRouteDirector;
import org.apache.hc.client5.http.io.ConnectionRequest; import org.apache.hc.client5.http.io.ConnectionEndpoint;
import org.apache.hc.client5.http.io.HttpClientConnectionManager; import org.apache.hc.client5.http.io.HttpClientConnectionManager;
import org.apache.hc.client5.http.io.LeaseRequest;
import org.apache.hc.client5.http.methods.HttpExecutionAware; import org.apache.hc.client5.http.methods.HttpExecutionAware;
import org.apache.hc.client5.http.methods.RoutedHttpRequest; import org.apache.hc.client5.http.methods.RoutedHttpRequest;
import org.apache.hc.client5.http.protocol.AuthenticationStrategy; import org.apache.hc.client5.http.protocol.AuthenticationStrategy;
@ -54,6 +56,7 @@
import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.annotation.ThreadingBehavior;
import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.ConnectionRequestTimeoutException;
import org.apache.hc.core5.http.ConnectionReuseStrategy; import org.apache.hc.core5.http.ConnectionReuseStrategy;
import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpException;
@ -64,7 +67,6 @@
import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.HttpStatus;
import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.HttpVersion;
import org.apache.hc.core5.http.impl.io.HttpRequestExecutor; import org.apache.hc.core5.http.impl.io.HttpRequestExecutor;
import org.apache.hc.core5.http.io.HttpClientConnection;
import org.apache.hc.core5.http.io.entity.BufferedHttpEntity; import org.apache.hc.core5.http.io.entity.BufferedHttpEntity;
import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.message.BasicClassicHttpRequest; import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
@ -162,22 +164,24 @@ public ClassicHttpResponse execute(
Object userToken = context.getUserToken(); Object userToken = context.getUserToken();
final ConnectionRequest connRequest = connManager.requestConnection(route, userToken); final LeaseRequest leaseRequest = connManager.lease(route, userToken);
if (execAware != null) { if (execAware != null) {
if (execAware.isAborted()) { if (execAware.isAborted()) {
connRequest.cancel(); leaseRequest.cancel();
throw new RequestAbortedException("Request aborted"); throw new RequestAbortedException("Request aborted");
} else { } else {
execAware.setCancellable(connRequest); execAware.setCancellable(leaseRequest);
} }
} }
final RequestConfig config = context.getRequestConfig(); final RequestConfig config = context.getRequestConfig();
final HttpClientConnection managedConn; final ConnectionEndpoint endpoint;
try { try {
final int timeout = config.getConnectionRequestTimeout(); final int timeout = config.getConnectionRequestTimeout();
managedConn = connRequest.get(timeout > 0 ? timeout : 0, TimeUnit.MILLISECONDS); endpoint = leaseRequest.get(timeout > 0 ? timeout : 0, TimeUnit.MILLISECONDS);
} catch(final TimeoutException ex) {
throw new ConnectionRequestTimeoutException(ex.getMessage());
} catch(final InterruptedException interrupted) { } catch(final InterruptedException interrupted) {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
throw new RequestAbortedException("Request aborted", interrupted); throw new RequestAbortedException("Request aborted", interrupted);
@ -189,12 +193,12 @@ public ClassicHttpResponse execute(
throw new RequestAbortedException("Request execution failed", cause); throw new RequestAbortedException("Request execution failed", cause);
} }
context.setAttribute(HttpCoreContext.HTTP_CONNECTION, managedConn); context.setAttribute(HttpCoreContext.HTTP_CONNECTION, endpoint);
final ConnectionHolder connHolder = new ConnectionHolder(this.log, this.connManager, managedConn); final EndpointHolder endpointHolder = new EndpointHolder(this.log, this.connManager, endpoint);
try { try {
if (execAware != null) { if (execAware != null) {
execAware.setCancellable(connHolder); execAware.setCancellable(endpointHolder);
} }
final AuthExchange targetAuthExchange = context.getAuthExchange(route.getTargetHost()); final AuthExchange targetAuthExchange = context.getAuthExchange(route.getTargetHost());
@ -213,10 +217,12 @@ public ClassicHttpResponse execute(
throw new RequestAbortedException("Request aborted"); throw new RequestAbortedException("Request aborted");
} }
if (!managedConn.isOpen()) { if (!endpoint.isConnected()) {
endpointHolder.markNonReusable();
this.log.debug("Opening connection " + route); this.log.debug("Opening connection " + route);
try { try {
establishRoute(managedConn, route, request, context); establishRoute(endpoint, route, request, context);
endpointHolder.markReusable();
} catch (final TunnelRefusedException ex) { } catch (final TunnelRefusedException ex) {
if (this.log.isDebugEnabled()) { if (this.log.isDebugEnabled()) {
this.log.debug(ex.getMessage()); this.log.debug(ex.getMessage());
@ -227,7 +233,7 @@ public ClassicHttpResponse execute(
} }
final int timeout = config.getSocketTimeout(); final int timeout = config.getSocketTimeout();
if (timeout >= 0) { if (timeout >= 0) {
managedConn.setSocketTimeout(timeout); endpoint.setSocketTimeout(timeout);
} }
if (execAware != null && execAware.isAborted()) { if (execAware != null && execAware.isAborted()) {
@ -253,7 +259,7 @@ public ClassicHttpResponse execute(
route.getProxyHost(), ChallengeType.PROXY, request, proxyAuthExchange, context); route.getProxyHost(), ChallengeType.PROXY, request, proxyAuthExchange, context);
} }
response = requestExecutor.execute(request, managedConn, context); response = endpoint.execute(request, requestExecutor, context);
// The connection is in or can be brought to a re-usable state. // The connection is in or can be brought to a re-usable state.
if (reuseStrategy.keepAlive(request, response, context)) { if (reuseStrategy.keepAlive(request, response, context)) {
@ -268,10 +274,10 @@ public ClassicHttpResponse execute(
} }
this.log.debug("Connection can be kept alive " + s); this.log.debug("Connection can be kept alive " + s);
} }
connHolder.setValidFor(duration, TimeUnit.MILLISECONDS); endpointHolder.setValidFor(duration, TimeUnit.MILLISECONDS);
connHolder.markReusable(); endpointHolder.markReusable();
} else { } else {
connHolder.markNonReusable(); endpointHolder.markNonReusable();
} }
if (request.getMethod().equalsIgnoreCase("TRACE")) { if (request.getMethod().equalsIgnoreCase("TRACE")) {
@ -283,10 +289,10 @@ public ClassicHttpResponse execute(
targetAuthExchange, proxyAuthExchange, route, request, response, context)) { targetAuthExchange, proxyAuthExchange, route, request, response, context)) {
// Make sure the response body is fully consumed, if present // Make sure the response body is fully consumed, if present
final HttpEntity entity = response.getEntity(); final HttpEntity entity = response.getEntity();
if (connHolder.isReusable()) { if (endpointHolder.isReusable()) {
EntityUtils.consume(entity); EntityUtils.consume(entity);
} else { } else {
managedConn.close(); endpoint.close();
if (proxyAuthExchange.getState() == AuthExchange.State.SUCCESS if (proxyAuthExchange.getState() == AuthExchange.State.SUCCESS
&& proxyAuthExchange.getAuthScheme() != null && proxyAuthExchange.getAuthScheme() != null
&& proxyAuthExchange.getAuthScheme().isConnectionBased()) { && proxyAuthExchange.getAuthScheme().isConnectionBased()) {
@ -318,18 +324,18 @@ public ClassicHttpResponse execute(
context.setAttribute(HttpClientContext.USER_TOKEN, userToken); context.setAttribute(HttpClientContext.USER_TOKEN, userToken);
} }
if (userToken != null) { if (userToken != null) {
connHolder.setState(userToken); endpointHolder.setState(userToken);
} }
// check for entity, release connection if possible // check for entity, release connection if possible
final HttpEntity entity = response.getEntity(); final HttpEntity entity = response.getEntity();
if (entity == null || !entity.isStreaming()) { if (entity == null || !entity.isStreaming()) {
// connection not needed and (assumed to be) in re-usable state // connection not needed and (assumed to be) in re-usable state
connHolder.releaseConnection(); endpointHolder.releaseConnection();
return new CloseableHttpResponse(response, null); return new CloseableHttpResponse(response, null);
} else { } else {
ResponseEntityProxy.enchance(response, connHolder); ResponseEntityProxy.enchance(response, endpointHolder);
return new CloseableHttpResponse(response, connHolder); return new CloseableHttpResponse(response, endpointHolder);
} }
} catch (final ConnectionShutdownException ex) { } catch (final ConnectionShutdownException ex) {
final InterruptedIOException ioex = new InterruptedIOException( final InterruptedIOException ioex = new InterruptedIOException(
@ -337,7 +343,7 @@ public ClassicHttpResponse execute(
ioex.initCause(ex); ioex.initCause(ex);
throw ioex; throw ioex;
} catch (final HttpException | RuntimeException | IOException ex) { } catch (final HttpException | RuntimeException | IOException ex) {
connHolder.abortConnection(); endpointHolder.abortConnection();
throw ex; throw ex;
} }
} }
@ -346,7 +352,7 @@ public ClassicHttpResponse execute(
* Establishes the target route. * Establishes the target route.
*/ */
void establishRoute( void establishRoute(
final HttpClientConnection managedConn, final ConnectionEndpoint endpoint,
final HttpRoute route, final HttpRoute route,
final HttpRequest request, final HttpRequest request,
final HttpClientContext context) throws HttpException, IOException { final HttpClientContext context) throws HttpException, IOException {
@ -362,23 +368,23 @@ void establishRoute(
case HttpRouteDirector.CONNECT_TARGET: case HttpRouteDirector.CONNECT_TARGET:
this.connManager.connect( this.connManager.connect(
managedConn, endpoint,
route,
timeout > 0 ? timeout : 0, timeout > 0 ? timeout : 0,
TimeUnit.MILLISECONDS,
context); context);
tracker.connectTarget(route.isSecure()); tracker.connectTarget(route.isSecure());
break; break;
case HttpRouteDirector.CONNECT_PROXY: case HttpRouteDirector.CONNECT_PROXY:
this.connManager.connect( this.connManager.connect(
managedConn, endpoint,
route,
timeout > 0 ? timeout : 0, timeout > 0 ? timeout : 0,
TimeUnit.MILLISECONDS,
context); context);
final HttpHost proxy = route.getProxyHost(); final HttpHost proxy = route.getProxyHost();
tracker.connectProxy(proxy, false); tracker.connectProxy(proxy, false);
break; break;
case HttpRouteDirector.TUNNEL_TARGET: { case HttpRouteDirector.TUNNEL_TARGET: {
final boolean secure = createTunnelToTarget(managedConn, route, request, context); final boolean secure = createTunnelToTarget(endpoint, route, request, context);
this.log.debug("Tunnel to target created."); this.log.debug("Tunnel to target created.");
tracker.tunnelTarget(secure); tracker.tunnelTarget(secure);
} break; } break;
@ -395,7 +401,7 @@ void establishRoute(
} break; } break;
case HttpRouteDirector.LAYER_PROTOCOL: case HttpRouteDirector.LAYER_PROTOCOL:
this.connManager.upgrade(managedConn, route, context); this.connManager.upgrade(endpoint, context);
tracker.layerProtocol(route.isSecure()); tracker.layerProtocol(route.isSecure());
break; break;
@ -403,7 +409,6 @@ void establishRoute(
throw new HttpException("Unable to establish route: " + throw new HttpException("Unable to establish route: " +
"planned = " + route + "; current = " + fact); "planned = " + route + "; current = " + fact);
case HttpRouteDirector.COMPLETE: case HttpRouteDirector.COMPLETE:
this.connManager.routeComplete(managedConn, route, context);
break; break;
default: default:
throw new IllegalStateException("Unknown step indicator " throw new IllegalStateException("Unknown step indicator "
@ -422,7 +427,7 @@ void establishRoute(
* information about the tunnel, that is left to the caller. * information about the tunnel, that is left to the caller.
*/ */
private boolean createTunnelToTarget( private boolean createTunnelToTarget(
final HttpClientConnection managedConn, final ConnectionEndpoint endpoint,
final HttpRoute route, final HttpRoute route,
final HttpRequest request, final HttpRequest request,
final HttpClientContext context) throws HttpException, IOException { final HttpClientContext context) throws HttpException, IOException {
@ -442,18 +447,18 @@ private boolean createTunnelToTarget(
this.requestExecutor.preProcess(connect, this.proxyHttpProcessor, context); this.requestExecutor.preProcess(connect, this.proxyHttpProcessor, context);
while (response == null) { while (response == null) {
if (!managedConn.isOpen()) { if (!endpoint.isConnected()) {
this.connManager.connect( this.connManager.connect(
managedConn, endpoint,
route,
timeout > 0 ? timeout : 0, timeout > 0 ? timeout : 0,
TimeUnit.MILLISECONDS,
context); context);
} }
connect.removeHeaders(HttpHeaders.PROXY_AUTHORIZATION); connect.removeHeaders(HttpHeaders.PROXY_AUTHORIZATION);
this.authenticator.addAuthResponse(proxy, ChallengeType.PROXY, connect, proxyAuthExchange, context); this.authenticator.addAuthResponse(proxy, ChallengeType.PROXY, connect, proxyAuthExchange, context);
response = this.requestExecutor.execute(connect, managedConn, context); response = endpoint.execute(connect, this.requestExecutor, context);
final int status = response.getCode(); final int status = response.getCode();
if (status < HttpStatus.SC_SUCCESS) { if (status < HttpStatus.SC_SUCCESS) {
@ -472,7 +477,7 @@ private boolean createTunnelToTarget(
final HttpEntity entity = response.getEntity(); final HttpEntity entity = response.getEntity();
EntityUtils.consume(entity); EntityUtils.consume(entity);
} else { } else {
managedConn.close(); endpoint.close();
} }
response = null; response = null;
} }
@ -489,7 +494,7 @@ private boolean createTunnelToTarget(
response.setEntity(new BufferedHttpEntity(entity)); response.setEntity(new BufferedHttpEntity(entity));
} }
managedConn.close(); endpoint.close();
throw new TunnelRefusedException("CONNECT refused by proxy: " + throw new TunnelRefusedException("CONNECT refused by proxy: " +
new StatusLine(response), response); new StatusLine(response), response);
} }

View File

@ -31,13 +31,16 @@
import java.io.InterruptedIOException; import java.io.InterruptedIOException;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.apache.hc.client5.http.ConnectionKeepAliveStrategy; import org.apache.hc.client5.http.ConnectionKeepAliveStrategy;
import org.apache.hc.client5.http.HttpRoute; import org.apache.hc.client5.http.HttpRoute;
import org.apache.hc.client5.http.config.RequestConfig; import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.io.ConnectionShutdownException; import org.apache.hc.client5.http.impl.DefaultConnectionKeepAliveStrategy;
import org.apache.hc.client5.http.io.ConnectionRequest; import org.apache.hc.client5.http.impl.ConnectionShutdownException;
import org.apache.hc.client5.http.io.ConnectionEndpoint;
import org.apache.hc.client5.http.io.HttpClientConnectionManager; import org.apache.hc.client5.http.io.HttpClientConnectionManager;
import org.apache.hc.client5.http.io.LeaseRequest;
import org.apache.hc.client5.http.methods.HttpExecutionAware; import org.apache.hc.client5.http.methods.HttpExecutionAware;
import org.apache.hc.client5.http.methods.RoutedHttpRequest; import org.apache.hc.client5.http.methods.RoutedHttpRequest;
import org.apache.hc.client5.http.protocol.HttpClientContext; import org.apache.hc.client5.http.protocol.HttpClientContext;
@ -45,11 +48,12 @@
import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.Contract;
import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.annotation.ThreadingBehavior;
import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.ConnectionRequestTimeoutException;
import org.apache.hc.core5.http.ConnectionReuseStrategy; import org.apache.hc.core5.http.ConnectionReuseStrategy;
import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpException;
import org.apache.hc.core5.http.impl.DefaultConnectionReuseStrategy;
import org.apache.hc.core5.http.impl.io.HttpRequestExecutor; import org.apache.hc.core5.http.impl.io.HttpRequestExecutor;
import org.apache.hc.core5.http.io.HttpClientConnection;
import org.apache.hc.core5.http.protocol.DefaultHttpProcessor; import org.apache.hc.core5.http.protocol.DefaultHttpProcessor;
import org.apache.hc.core5.http.protocol.HttpCoreContext; import org.apache.hc.core5.http.protocol.HttpCoreContext;
import org.apache.hc.core5.http.protocol.HttpProcessor; import org.apache.hc.core5.http.protocol.HttpProcessor;
@ -86,20 +90,16 @@ public MinimalClientExec(
final HttpClientConnectionManager connManager, final HttpClientConnectionManager connManager,
final ConnectionReuseStrategy reuseStrategy, final ConnectionReuseStrategy reuseStrategy,
final ConnectionKeepAliveStrategy keepAliveStrategy) { final ConnectionKeepAliveStrategy keepAliveStrategy) {
Args.notNull(requestExecutor, "HTTP request executor"); this.requestExecutor = Args.notNull(requestExecutor, "Request executor");
Args.notNull(connManager, "Client connection manager"); this.connManager = Args.notNull(connManager, "Connection manager");
Args.notNull(reuseStrategy, "Connection reuse strategy"); this.reuseStrategy = reuseStrategy != null ? reuseStrategy : DefaultConnectionReuseStrategy.INSTANCE;
Args.notNull(keepAliveStrategy, "Connection keep alive strategy"); this.keepAliveStrategy = keepAliveStrategy != null ? keepAliveStrategy : DefaultConnectionKeepAliveStrategy.INSTANCE;
this.httpProcessor = new DefaultHttpProcessor( this.httpProcessor = new DefaultHttpProcessor(
new RequestContent(), new RequestContent(),
new RequestTargetHost(), new RequestTargetHost(),
new RequestClientConnControl(), new RequestClientConnControl(),
new RequestUserAgent(VersionInfo.getSoftwareInfo( new RequestUserAgent(VersionInfo.getSoftwareInfo(
"Apache-HttpClient", "org.apache.hc.client5", getClass()))); "Apache-HttpClient", "org.apache.hc.client5", getClass())));
this.requestExecutor = requestExecutor;
this.connManager = connManager;
this.reuseStrategy = reuseStrategy;
this.keepAliveStrategy = keepAliveStrategy;
} }
@Override @Override
@ -111,7 +111,7 @@ public ClassicHttpResponse execute(
Args.notNull(context, "HTTP context"); Args.notNull(context, "HTTP context");
final HttpRoute route = request.getRoute(); final HttpRoute route = request.getRoute();
final ConnectionRequest connRequest = connManager.requestConnection(route, null); final LeaseRequest connRequest = connManager.lease(route, null);
if (execAware != null) { if (execAware != null) {
if (execAware.isAborted()) { if (execAware.isAborted()) {
connRequest.cancel(); connRequest.cancel();
@ -122,10 +122,12 @@ public ClassicHttpResponse execute(
final RequestConfig config = context.getRequestConfig(); final RequestConfig config = context.getRequestConfig();
final HttpClientConnection managedConn; final ConnectionEndpoint endpoint;
try { try {
final int timeout = config.getConnectionRequestTimeout(); final int timeout = config.getConnectionRequestTimeout();
managedConn = connRequest.get(timeout > 0 ? timeout : 0, TimeUnit.MILLISECONDS); endpoint = connRequest.get(timeout > 0 ? timeout : 0, TimeUnit.MILLISECONDS);
} catch(final TimeoutException ex) {
throw new ConnectionRequestTimeoutException(ex.getMessage());
} catch(final InterruptedException interrupted) { } catch(final InterruptedException interrupted) {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
throw new RequestAbortedException("Request aborted", interrupted); throw new RequestAbortedException("Request aborted", interrupted);
@ -137,57 +139,54 @@ public ClassicHttpResponse execute(
throw new RequestAbortedException("Request execution failed", cause); throw new RequestAbortedException("Request execution failed", cause);
} }
final ConnectionHolder connHolder = new ConnectionHolder(log, connManager, managedConn); final EndpointHolder endpointHolder = new EndpointHolder(log, connManager, endpoint);
try { try {
if (execAware != null) { if (execAware != null) {
if (execAware.isAborted()) { if (execAware.isAborted()) {
connHolder.close(); endpointHolder.close();
throw new RequestAbortedException("Request aborted"); throw new RequestAbortedException("Request aborted");
} }
execAware.setCancellable(connHolder); execAware.setCancellable(endpointHolder);
} }
if (!endpoint.isConnected()) {
if (!managedConn.isOpen()) {
final int timeout = config.getConnectTimeout(); final int timeout = config.getConnectTimeout();
this.connManager.connect( this.connManager.connect(
managedConn, endpoint,
route,
timeout > 0 ? timeout : 0, timeout > 0 ? timeout : 0,
TimeUnit.MILLISECONDS,
context); context);
this.connManager.routeComplete(managedConn, route, context);
} }
final int timeout = config.getSocketTimeout(); final int timeout = config.getSocketTimeout();
if (timeout >= 0) { if (timeout >= 0) {
managedConn.setSocketTimeout(timeout); endpoint.setSocketTimeout(timeout);
} }
context.setAttribute(HttpCoreContext.HTTP_REQUEST, request); context.setAttribute(HttpCoreContext.HTTP_REQUEST, request);
context.setAttribute(HttpCoreContext.HTTP_CONNECTION, managedConn);
context.setAttribute(HttpClientContext.HTTP_ROUTE, route); context.setAttribute(HttpClientContext.HTTP_ROUTE, route);
httpProcessor.process(request, request.getEntity(), context); httpProcessor.process(request, request.getEntity(), context);
final ClassicHttpResponse response = requestExecutor.execute(request, managedConn, context); final ClassicHttpResponse response = endpoint.execute(request, requestExecutor, context);
httpProcessor.process(response, response.getEntity(), context); httpProcessor.process(response, response.getEntity(), context);
// The connection is in or can be brought to a re-usable state. // The connection is in or can be brought to a re-usable state.
if (reuseStrategy.keepAlive(request, response, context)) { if (reuseStrategy.keepAlive(request, response, context)) {
// Set the idle duration of this connection // Set the idle duration of this connection
final long duration = keepAliveStrategy.getKeepAliveDuration(response, context); final long duration = keepAliveStrategy.getKeepAliveDuration(response, context);
connHolder.setValidFor(duration, TimeUnit.MILLISECONDS); endpointHolder.setValidFor(duration, TimeUnit.MILLISECONDS);
connHolder.markReusable(); endpointHolder.markReusable();
} else { } else {
connHolder.markNonReusable(); endpointHolder.markNonReusable();
} }
// check for entity, release connection if possible // check for entity, release connection if possible
final HttpEntity entity = response.getEntity(); final HttpEntity entity = response.getEntity();
if (entity == null || !entity.isStreaming()) { if (entity == null || !entity.isStreaming()) {
// connection not needed and (assumed to be) in re-usable state // connection not needed and (assumed to be) in re-usable state
connHolder.releaseConnection(); endpointHolder.releaseConnection();
return new CloseableHttpResponse(response, null); return new CloseableHttpResponse(response, null);
} else { } else {
ResponseEntityProxy.enchance(response, connHolder); ResponseEntityProxy.enchance(response, endpointHolder);
return new CloseableHttpResponse(response, connHolder); return new CloseableHttpResponse(response, endpointHolder);
} }
} catch (final ConnectionShutdownException ex) { } catch (final ConnectionShutdownException ex) {
final InterruptedIOException ioex = new InterruptedIOException( final InterruptedIOException ioex = new InterruptedIOException(
@ -195,7 +194,7 @@ public ClassicHttpResponse execute(
ioex.initCause(ex); ioex.initCause(ex);
throw ioex; throw ioex;
} catch (final HttpException | RuntimeException | IOException ex) { } catch (final HttpException | RuntimeException | IOException ex) {
connHolder.abortConnection(); endpointHolder.abortConnection();
throw ex; throw ex;
} }
} }

View File

@ -110,8 +110,8 @@ protected CloseableHttpResponse doExecute(
} }
@Override @Override
public void close() { public void close() throws IOException {
this.connManager.shutdown(); this.connManager.close();
} }
} }

View File

@ -30,7 +30,6 @@
import java.io.IOException; import java.io.IOException;
import java.net.Socket; import java.net.Socket;
import org.apache.hc.client5.http.HttpConnectionFactory;
import org.apache.hc.client5.http.HttpRoute; import org.apache.hc.client5.http.HttpRoute;
import org.apache.hc.client5.http.RouteInfo.LayerType; import org.apache.hc.client5.http.RouteInfo.LayerType;
import org.apache.hc.client5.http.RouteInfo.TunnelType; import org.apache.hc.client5.http.RouteInfo.TunnelType;
@ -66,6 +65,7 @@
import org.apache.hc.core5.http.config.RegistryBuilder; import org.apache.hc.core5.http.config.RegistryBuilder;
import org.apache.hc.core5.http.impl.DefaultConnectionReuseStrategy; import org.apache.hc.core5.http.impl.DefaultConnectionReuseStrategy;
import org.apache.hc.core5.http.impl.io.HttpRequestExecutor; import org.apache.hc.core5.http.impl.io.HttpRequestExecutor;
import org.apache.hc.core5.http.io.HttpConnectionFactory;
import org.apache.hc.core5.http.io.entity.BufferedHttpEntity; import org.apache.hc.core5.http.io.entity.BufferedHttpEntity;
import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.message.BasicClassicHttpRequest; import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
@ -83,7 +83,7 @@
*/ */
public class ProxyClient { public class ProxyClient {
private final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory; private final HttpConnectionFactory<ManagedHttpClientConnection> connFactory;
private final ConnectionConfig connectionConfig; private final ConnectionConfig connectionConfig;
private final RequestConfig requestConfig; private final RequestConfig requestConfig;
private final HttpProcessor httpProcessor; private final HttpProcessor httpProcessor;
@ -98,7 +98,7 @@ public class ProxyClient {
* @since 4.3 * @since 4.3
*/ */
public ProxyClient( public ProxyClient(
final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory, final HttpConnectionFactory<ManagedHttpClientConnection> connFactory,
final ConnectionConfig connectionConfig, final ConnectionConfig connectionConfig,
final RequestConfig requestConfig) { final RequestConfig requestConfig) {
super(); super();
@ -148,8 +148,7 @@ public Socket tunnel(
this.requestConfig.getLocalAddress(), this.requestConfig.getLocalAddress(),
proxy, false, TunnelType.TUNNELLED, LayerType.PLAIN); proxy, false, TunnelType.TUNNELLED, LayerType.PLAIN);
final ManagedHttpClientConnection conn = this.connFactory.create( final ManagedHttpClientConnection conn = this.connFactory.createConnection(null);
route, this.connectionConfig);
final HttpContext context = new BasicHttpContext(); final HttpContext context = new BasicHttpContext();
ClassicHttpResponse response; ClassicHttpResponse response;

View File

@ -146,7 +146,7 @@ public ClassicHttpResponse execute(
} }
} }
currentRoute = this.routePlanner.determineRoute(newTarget, redirect, context); currentRoute = this.routePlanner.determineRoute(newTarget, context);
if (this.log.isDebugEnabled()) { if (this.log.isDebugEnabled()) {
this.log.debug("Redirecting to '" + redirectUri + "' via " + currentRoute); this.log.debug("Redirecting to '" + redirectUri + "' via " + currentRoute);
} }

View File

@ -45,35 +45,35 @@
*/ */
class ResponseEntityProxy extends HttpEntityWrapper implements EofSensorWatcher { class ResponseEntityProxy extends HttpEntityWrapper implements EofSensorWatcher {
private final ConnectionHolder connHolder; private final EndpointHolder endpointHolder;
public static void enchance(final ClassicHttpResponse response, final ConnectionHolder connHolder) { public static void enchance(final ClassicHttpResponse response, final EndpointHolder connHolder) {
final HttpEntity entity = response.getEntity(); final HttpEntity entity = response.getEntity();
if (entity != null && entity.isStreaming() && connHolder != null) { if (entity != null && entity.isStreaming() && connHolder != null) {
response.setEntity(new ResponseEntityProxy(entity, connHolder)); response.setEntity(new ResponseEntityProxy(entity, connHolder));
} }
} }
ResponseEntityProxy(final HttpEntity entity, final ConnectionHolder connHolder) { ResponseEntityProxy(final HttpEntity entity, final EndpointHolder endpointHolder) {
super(entity); super(entity);
this.connHolder = connHolder; this.endpointHolder = endpointHolder;
} }
private void cleanup() throws IOException { private void cleanup() throws IOException {
if (this.connHolder != null) { if (this.endpointHolder != null) {
this.connHolder.close(); this.endpointHolder.close();
} }
} }
private void abortConnection() { private void abortConnection() {
if (this.connHolder != null) { if (this.endpointHolder != null) {
this.connHolder.abortConnection(); this.endpointHolder.abortConnection();
} }
} }
public void releaseConnection() { public void releaseConnection() {
if (this.connHolder != null) { if (this.endpointHolder != null) {
this.connHolder.releaseConnection(); this.endpointHolder.releaseConnection();
} }
} }
@ -123,7 +123,7 @@ public boolean eofDetected(final InputStream wrapped) throws IOException {
@Override @Override
public boolean streamClosed(final InputStream wrapped) throws IOException { public boolean streamClosed(final InputStream wrapped) throws IOException {
try { try {
final boolean open = connHolder != null && !connHolder.isReleased(); final boolean open = endpointHolder != null && !endpointHolder.isReleased();
// this assumes that closing the stream will // this assumes that closing the stream will
// consume the remainder of the response body: // consume the remainder of the response body:
try { try {

View File

@ -25,33 +25,36 @@
* *
*/ */
package org.apache.hc.client5.http; package org.apache.hc.client5.http.io;
import java.io.Closeable;
import java.io.IOException;
import org.apache.hc.core5.annotation.Contract;
import org.apache.hc.core5.annotation.ThreadingBehavior;
import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.HttpException;
import org.apache.hc.core5.http.impl.io.HttpRequestExecutor;
import org.apache.hc.core5.http.protocol.HttpContext;
/** /**
* A timeout while waiting for an available connection * Client connection endpoint that can be used to execute message exchanges.
* from a connection manager.
* *
* * @since 5.0
* @since 4.0
*/ */
public class ConnectionPoolTimeoutException extends ConnectTimeoutException { @Contract(threading = ThreadingBehavior.SAFE)
public abstract class ConnectionEndpoint implements Closeable {
private static final long serialVersionUID = -7898874842020245128L; public abstract ClassicHttpResponse execute(
ClassicHttpRequest request,
HttpRequestExecutor executor,
HttpContext context) throws IOException, HttpException;
/** public abstract boolean isConnected();
* Creates a ConnectTimeoutException with a {@code null} detail message.
*/
public ConnectionPoolTimeoutException() {
super();
}
/** public abstract void setSocketTimeout(int timeout);
* Creates a ConnectTimeoutException with the specified detail message.
* public abstract void shutdown() throws IOException;
* @param message The exception detail message
*/
public ConnectionPoolTimeoutException(final String message) {
super(message);
}
} }

View File

@ -1,70 +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
* <http://www.apache.org/>.
*
*/
package org.apache.hc.client5.http.io;
import java.io.IOException;
/**
* Interface for releasing a connection. This can be implemented by various
* "trigger" objects which are associated with a leased connection, for example
* a {@link org.apache.hc.core5.http.io.EofSensorInputStream}
* or the {@link ManagedHttpClientConnection} itself.
* <p>
* The methods in this interface can safely be called multiple times.
* The first invocation releases the connection, subsequent calls
* are ignored.
*
* @since 4.0
*/
public interface ConnectionReleaseTrigger {
/**
* Releases the connection with the option of keep-alive. This is a
* "graceful" release and may cause IO operations for consuming the
* remainder of a response entity. Use
* {@link #abortConnection abortConnection} for a hard release. The
* connection may be reused as specified by the duration.
*
* @throws IOException
* in case of an IO problem. The connection will be released
* anyway.
*/
void releaseConnection()
throws IOException;
/**
* Releases the connection without the option of keep-alive.
* This is a "hard" release that implies a shutdown of the connection.
* Use {@link #releaseConnection()} for a graceful release.
*
* @throws IOException in case of an IO problem.
* The connection will be released anyway.
*/
void abortConnection()
throws IOException;
}

View File

@ -26,11 +26,11 @@
*/ */
package org.apache.hc.client5.http.io; package org.apache.hc.client5.http.io;
import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.apache.hc.client5.http.HttpRoute; import org.apache.hc.client5.http.HttpRoute;
import org.apache.hc.core5.http.io.HttpClientConnection;
import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.protocol.HttpContext;
/** /**
@ -40,137 +40,77 @@
* HTTP connections, manage persistent connections and synchronize access to * HTTP connections, manage persistent connections and synchronize access to
* persistent connections making sure that only one thread of execution can * persistent connections making sure that only one thread of execution can
* have access to a connection at a time. * have access to a connection at a time.
* </p>
* <p> * <p>
* Implementations of this interface must be thread-safe. Access to shared * Implementations of this interface must be thread-safe. Access to shared
* data must be synchronized as methods of this interface may be executed * data must be synchronized as methods of this interface may be executed
* from multiple threads. * from multiple threads.
* </p>
* *
* @since 4.3 * @since 4.3
*/ */
public interface HttpClientConnectionManager { public interface HttpClientConnectionManager extends Closeable {
/** /**
* Returns a new {@link ConnectionRequest}, from which a * Returns a {@link LeaseRequest} object which can be used to obtain
* {@link HttpClientConnection} can be obtained or the request can be * a {@link ConnectionEndpoint} to cancel the request by calling
* aborted. * {@link LeaseRequest#cancel()}.
* <p> * <p>
* Please note that newly allocated connections can be returned * Please note that newly allocated endpoints can be leased
* in the closed state. The consumer of that connection is responsible * {@link ConnectionEndpoint#isConnected() disconnected}. The consumer of the endpoint
* for fully establishing the route the to the connection target * is responsible for fully establishing the route to the endpoint target
* by calling {@link #connect(HttpClientConnection, HttpRoute, int, HttpContext)} connect} in order to connect * by calling {@link #connect(ConnectionEndpoint, long, TimeUnit, HttpContext)}
* directly to the target or to the first proxy hop, optionally calling * in order to connect directly to the target or to the first proxy hop,
* {@link #upgrade(HttpClientConnection, HttpRoute, HttpContext)} upgrade} method to upgrade * and optionally calling {@link #upgrade(ConnectionEndpoint, HttpContext)} method
* the connection after having executed {@code CONNECT} method to * to upgrade the underlying transport to Transport Layer Security after having
* all intermediate proxy hops and and finally calling {@link #routeComplete(HttpClientConnection, HttpRoute, * executed a {@code CONNECT} method to all intermediate proxy hops.
* HttpContext)} routeComplete} to mark the route
* as fully completed.
* </p>
* *
* @param route HTTP route of the requested connection. * @param route HTTP route of the requested connection.
* @param state expected state of the connection or {@code null} * @param state expected state of the connection or {@code null}
* if the connection is not expected to carry any state. * if the connection is not expected to carry any state.
*/ */
ConnectionRequest requestConnection(HttpRoute route, Object state); LeaseRequest lease(HttpRoute route, Object state);
/** /**
* Releases the connection back to the manager making it potentially * Releases the endpoint back to the manager making it potentially
* re-usable by other consumers. Optionally, the maximum period * re-usable by other consumers. Optionally, the maximum period
* of how long the manager should keep the connection alive can be * of how long the manager should keep the connection alive can be
* defined using {@code validDuration} and {@code timeUnit} * defined using {@code validDuration} and {@code timeUnit}
* parameters. * parameters.
* *
* @param conn the managed connection to release. * @param endpoint the managed endpoint.
* @param newState the new connection state of {@code null} if state-less.
* @param validDuration the duration of time this connection is valid for reuse. * @param validDuration the duration of time this connection is valid for reuse.
* @param timeUnit the time unit. * @param timeUnit the time unit.
* *
* @see #closeExpired() * @see #closeExpired()
*/ */
void releaseConnection( void release(ConnectionEndpoint endpoint, Object newState, long validDuration, TimeUnit timeUnit);
HttpClientConnection conn, Object newState, long validDuration, TimeUnit timeUnit);
/** /**
* Connects the underlying connection socket to the connection target in case * Connects the endpoint to the initial hop (connection target in case
* of a direct route or to the first proxy hop in case of a route via a proxy * of a direct route or to the first proxy hop in case of a route via a proxy
* (or multiple proxies). * or multiple proxies).
* *
* @param conn the managed connection. * @param endpoint the managed endpoint.
* @param route the route of the connection. * @param connectTimeout connect timeout.
* @param connectTimeout connect timeout in milliseconds. * @param timeUnit the time unit.
* @param context the actual HTTP context. * @param context the actual HTTP context.
* @throws IOException * @throws IOException
*/ */
void connect( void connect(
HttpClientConnection conn, ConnectionEndpoint endpoint,
HttpRoute route, long connectTimeout,
int connectTimeout, TimeUnit timeUnit,
HttpContext context) throws IOException; HttpContext context) throws IOException;
/** /**
* Upgrades the underlying connection socket to TLS/SSL (or another layering * Upgrades the endpoint's underlying transport to Transport Layer Security.
* protocol) after having executed {@code CONNECT} method to all
* intermediate proxy hops
* *
* @param conn the managed connection. * @param endpoint the managed endpoint.
* @param route the route of the connection.
* @param context the actual HTTP context. * @param context the actual HTTP context.
* @throws IOException * @throws IOException
*/ */
void upgrade( void upgrade(
HttpClientConnection conn, ConnectionEndpoint endpoint,
HttpRoute route,
HttpContext context) throws IOException; HttpContext context) throws IOException;
/**
* Marks the connection as fully established with all its intermediate
* hops completed.
*
* @param conn the managed connection.
* @param route the route of the connection.
* @param context the actual HTTP context.
* @throws IOException
*/
void routeComplete(
HttpClientConnection conn,
HttpRoute route,
HttpContext context) throws IOException;
/**
* Closes idle connections in the pool.
* <p>
* Open connections in the pool that have not been used for the
* timespan given by the argument will be closed.
* Currently allocated connections are not subject to this method.
* Times will be checked with milliseconds precision
* </p>
* <p>
* All expired connections will also be closed.
* </p>
*
* @param idletime the idle time of connections to be closed
* @param tunit the unit for the {@code idletime}
*
* @see #closeExpired()
*/
void closeIdle(long idletime, TimeUnit tunit);
/**
* Closes all expired connections in the pool.
* <p>
* Open connections in the pool that have not been used for
* the timespan defined when the connection was released will be closed.
* Currently allocated connections are not subject to this method.
* Times will be checked with milliseconds precision.
* </p>
*/
void closeExpired();
/**
* Shuts down this connection manager and releases allocated resources.
* This includes closing all connections, whether they are currently
* used or not.
*/
void shutdown();
} }

View File

@ -29,21 +29,20 @@
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.apache.hc.client5.http.ConnectionPoolTimeoutException;
import org.apache.hc.core5.concurrent.Cancellable; import org.apache.hc.core5.concurrent.Cancellable;
import org.apache.hc.core5.http.io.HttpClientConnection;
/** /**
* Represents a request for a {@link HttpClientConnection} whose life cycle * Represents a request for a {@link ConnectionEndpoint} whose life cycle
* is managed by a connection manager. * is managed by a connection manager.
* *
* @since 4.3 * @since 5.0
*/ */
public interface ConnectionRequest extends Cancellable { public interface LeaseRequest extends Cancellable {
/** /**
* Obtains a connection within a given time. * Returns {@link ConnectionEndpoint} within a given time.
* This method will block until a connection becomes available, * This method will block until a connection becomes available,
* the timeout expires, or the connection manager is shut down. * the timeout expires, or the connection manager is shut down.
* Timeouts are handled with millisecond precision. * Timeouts are handled with millisecond precision.
@ -59,12 +58,12 @@ public interface ConnectionRequest extends Cancellable {
* @return a connection that can be used to communicate * @return a connection that can be used to communicate
* along the given route * along the given route
* *
* @throws ConnectionPoolTimeoutException * @throws TimeoutException
* in case of a timeout * in case of a timeout
* @throws InterruptedException * @throws InterruptedException
* if the calling thread is interrupted while waiting * if the calling thread is interrupted while waiting
*/ */
HttpClientConnection get(long timeout, TimeUnit tunit) ConnectionEndpoint get(long timeout, TimeUnit tunit)
throws InterruptedException, ExecutionException, ConnectionPoolTimeoutException; throws InterruptedException, ExecutionException, TimeoutException;
} }

View File

@ -44,12 +44,6 @@
*/ */
public interface ManagedHttpClientConnection extends HttpClientConnection { public interface ManagedHttpClientConnection extends HttpClientConnection {
/**
* Returns connection ID which is expected to be unique
* for the life span of the connection manager.
*/
String getId();
/** /**
* Binds this connection to the given socket. The connection * Binds this connection to the given socket. The connection
* is considered open if it is bound and the underlying socket * is considered open if it is bound and the underlying socket

View File

@ -48,13 +48,9 @@
public interface HttpRoutePlanner { public interface HttpRoutePlanner {
/** /**
* Determines the route for a request. * Determines the route for the given host.
* *
* @param target the target host for the request. * @param target the target host for the request.
* Implementations may accept {@code null}
* if they can still determine a route, for example
* to a default target or by inspecting the request.
* @param request the request to execute
* @param context the context to use for the subsequent execution. * @param context the context to use for the subsequent execution.
* Implementations may accept {@code null}. * Implementations may accept {@code null}.
* *
@ -62,8 +58,19 @@ public interface HttpRoutePlanner {
* *
* @throws HttpException in case of a problem * @throws HttpException in case of a problem
*/ */
HttpRoute determineRoute(HttpHost target, HttpRoute determineRoute(HttpHost target, HttpContext context) throws HttpException;
HttpRequest request,
HttpContext context) throws HttpException; /**
* Determines the target host for the given request.
*
* @param request the request to be executed
* @param context the context to use for the subsequent execution.
* Implementations may accept {@code null}.
*
* @return the route that the request should take
*
* @throws HttpException in case of a problem
*/
HttpHost determineTargetHost(HttpRequest request, HttpContext context) throws HttpException;
} }

View File

@ -24,18 +24,16 @@
* <http://www.apache.org/>. * <http://www.apache.org/>.
* *
*/ */
package org.apache.hc.client5.http;
import org.apache.hc.core5.http.HttpConnection; package org.apache.hc.client5.http.utils;
import org.apache.hc.core5.http.config.ConnectionConfig;
/** /**
* Generic {@link HttpConnection} factory. * Object with a unique identifier.
* *
* @since 4.3 * @since 5.0
*/ */
public interface HttpConnectionFactory<T, C extends HttpConnection> { public interface Identifiable {
C create(T route, ConnectionConfig config); String getId();
} }

View File

@ -115,15 +115,4 @@ public void testHttpHostConnectExceptionFromCauseHostAndRemoteAddress() throws E
Assert.assertEquals("Connect to localhost [/1.2.3.4, /5.6.7.8] refused", ctx.getMessage()); Assert.assertEquals("Connect to localhost [/1.2.3.4, /5.6.7.8] refused", ctx.getMessage());
} }
@Test
public void testConnectionPoolTimeoutException() {
final String msg = "sample exception message";
ConnectionPoolTimeoutException cptx = new ConnectionPoolTimeoutException(msg);
Assert.assertFalse(!cptx.toString().contains(msg));
Assert.assertSame(msg, cptx.getMessage());
cptx = new ConnectionPoolTimeoutException();
Assert.assertNull(cptx.getMessage());
}
} }

View File

@ -31,6 +31,7 @@
import java.io.InputStream; import java.io.InputStream;
import org.apache.hc.client5.http.impl.sync.AbstractResponseHandler; import org.apache.hc.client5.http.impl.sync.AbstractResponseHandler;
import org.apache.hc.client5.http.impl.sync.BasicResponseHandler;
import org.apache.hc.client5.http.protocol.HttpResponseException; import org.apache.hc.client5.http.protocol.HttpResponseException;
import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.HttpEntity;

View File

@ -29,6 +29,7 @@
import java.io.InputStream; import java.io.InputStream;
import org.apache.hc.client5.http.impl.sync.BasicResponseHandler;
import org.apache.hc.client5.http.protocol.HttpResponseException; import org.apache.hc.client5.http.protocol.HttpResponseException;
import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.HttpEntity;

View File

@ -1,533 +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
* <http://www.apache.org/>.
*
*/
package org.apache.hc.client5.http.impl.integration;
import java.io.IOException;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.hc.client5.http.ConnectionPoolTimeoutException;
import org.apache.hc.client5.http.HttpRoute;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.impl.sync.CloseableHttpClient;
import org.apache.hc.client5.http.impl.sync.HttpClients;
import org.apache.hc.client5.http.io.ConnectionRequest;
import org.apache.hc.client5.http.io.HttpClientConnectionManager;
import org.apache.hc.client5.http.localserver.LocalServerTestBase;
import org.apache.hc.client5.http.methods.HttpGet;
import org.apache.hc.client5.http.protocol.HttpClientContext;
import org.apache.hc.core5.concurrent.Cancellable;
import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.HttpException;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.HttpStatus;
import org.apache.hc.core5.http.io.HttpClientConnection;
import org.apache.hc.core5.http.io.HttpRequestHandler;
import org.apache.hc.core5.http.io.UriHttpRequestHandlerMapper;
import org.apache.hc.core5.http.io.entity.StringEntity;
import org.apache.hc.core5.http.message.BasicHeader;
import org.apache.hc.core5.http.protocol.BasicHttpContext;
import org.apache.hc.core5.http.protocol.HttpContext;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.Mockito;
/**
* Tests for Abort handling.
*/
@SuppressWarnings("static-access") // test code
public class TestAbortHandling extends LocalServerTestBase {
@Test
public void testAbortRetry_HTTPCLIENT_1120() throws Exception {
final CountDownLatch wait = new CountDownLatch(1);
this.serverBootstrap.registerHandler("*", new HttpRequestHandler() {
@Override
public void handle(
final ClassicHttpRequest request,
final ClassicHttpResponse response,
final HttpContext context) throws HttpException, IOException {
try {
wait.countDown(); // trigger abort
Thread.sleep(2000); // allow time for abort to happen
response.setCode(HttpStatus.SC_OK);
final StringEntity entity = new StringEntity("Whatever");
response.setEntity(entity);
} catch (final Exception e) {
response.setCode(HttpStatus.SC_REQUEST_TIMEOUT);
}
}
});
final HttpHost target = start();
final HttpGet httpget = new HttpGet("/");
final Thread t = new Thread() {
@Override
public void run(){
try {
wait.await();
} catch (final InterruptedException e) {
}
httpget.abort();
}
};
t.start();
final HttpClientContext context = HttpClientContext.create();
try {
this.httpclient.execute(target, httpget, context);
} catch (final IllegalStateException | IOException e) {
}
final HttpRequest reqWrapper = context.getRequest();
Assert.assertNotNull("Request should exist",reqWrapper);
}
@Test
public void testAbortInAllocate() throws Exception {
final CountDownLatch connLatch = new CountDownLatch(1);
final CountDownLatch awaitLatch = new CountDownLatch(1);
final ConMan conMan = new ConMan(connLatch, awaitLatch);
final AtomicReference<Throwable> throwableRef = new AtomicReference<>();
final CountDownLatch getLatch = new CountDownLatch(1);
this.clientBuilder.setConnectionManager(conMan);
final HttpContext context = new BasicHttpContext();
final HttpGet httpget = new HttpGet("http://www.example.com/a");
start();
new Thread(new Runnable() {
@Override
public void run() {
try {
httpclient.execute(httpget, context);
} catch(final Throwable t) {
throwableRef.set(t);
} finally {
getLatch.countDown();
}
}
}).start();
Assert.assertTrue("should have tried to get a connection", connLatch.await(1, TimeUnit.SECONDS));
httpget.abort();
Assert.assertTrue("should have finished get request", getLatch.await(1, TimeUnit.SECONDS));
Assert.assertTrue("should be instanceof IOException, was: " + throwableRef.get(),
throwableRef.get() instanceof IOException);
Assert.assertTrue("cause should be InterruptedException, was: " + throwableRef.get().getCause(),
throwableRef.get().getCause() instanceof InterruptedException);
}
/**
* Tests that an abort called after the connection has been retrieved
* but before a release trigger is set does still abort the request.
*/
@Test
public void testAbortAfterAllocateBeforeRequest() throws Exception {
this.serverBootstrap.registerHandler("*", new BasicService());
final CountDownLatch releaseLatch = new CountDownLatch(1);
final AtomicReference<Throwable> throwableRef = new AtomicReference<>();
final CountDownLatch getLatch = new CountDownLatch(1);
final HttpContext context = new BasicHttpContext();
final HttpGet httpget = new CustomGet("a", releaseLatch);
final HttpHost target = start();
new Thread(new Runnable() {
@Override
public void run() {
try {
httpclient.execute(target, httpget, context);
} catch(final Throwable t) {
throwableRef.set(t);
} finally {
getLatch.countDown();
}
}
}).start();
Thread.sleep(100); // Give it a little time to proceed to release...
httpget.abort();
releaseLatch.countDown();
Assert.assertTrue("should have finished get request", getLatch.await(1, TimeUnit.SECONDS));
Assert.assertTrue("should be instanceof IOException, was: " + throwableRef.get(),
throwableRef.get() instanceof IOException);
}
/**
* Tests that an abort called completely before execute
* still aborts the request.
*/
@Test
public void testAbortBeforeExecute() throws Exception {
this.serverBootstrap.registerHandler("*", new BasicService());
final AtomicReference<Throwable> throwableRef = new AtomicReference<>();
final CountDownLatch getLatch = new CountDownLatch(1);
final CountDownLatch startLatch = new CountDownLatch(1);
final HttpContext context = new BasicHttpContext();
final HttpGet httpget = new HttpGet("a");
final HttpHost target = start();
new Thread(new Runnable() {
@Override
public void run() {
try {
try {
if(!startLatch.await(1, TimeUnit.SECONDS)) {
throw new RuntimeException("Took too long to start!");
}
} catch(final InterruptedException interrupted) {
throw new RuntimeException("Never started!", interrupted);
}
httpclient.execute(target, httpget, context);
} catch(final Throwable t) {
throwableRef.set(t);
} finally {
getLatch.countDown();
}
}
}).start();
httpget.abort();
startLatch.countDown();
Assert.assertTrue("should have finished get request", getLatch.await(1, TimeUnit.SECONDS));
Assert.assertTrue("should be instanceof IOException, was: " + throwableRef.get(),
throwableRef.get() instanceof IOException);
}
/**
* Tests that an abort called after a redirect has found a new host
* still aborts in the correct place (while trying to get the new
* host's route, not while doing the subsequent request).
*/
@Test
public void testAbortAfterRedirectedRoute() throws Exception {
final UriHttpRequestHandlerMapper reqistry = new UriHttpRequestHandlerMapper();
this.serverBootstrap.setHandlerMapper(reqistry);
final CountDownLatch connLatch = new CountDownLatch(1);
final CountDownLatch awaitLatch = new CountDownLatch(1);
final ConnMan4 conMan = new ConnMan4(connLatch, awaitLatch);
final AtomicReference<Throwable> throwableRef = new AtomicReference<>();
final CountDownLatch getLatch = new CountDownLatch(1);
this.clientBuilder.setConnectionManager(conMan);
final HttpContext context = new BasicHttpContext();
final HttpGet httpget = new HttpGet("a");
final HttpHost target = start();
reqistry.register("*", new BasicRedirectService(target.getPort()));
new Thread(new Runnable() {
@Override
public void run() {
try {
final HttpHost host = new HttpHost("127.0.0.1", target.getPort());
httpclient.execute(host, httpget, context);
} catch(final Throwable t) {
throwableRef.set(t);
} finally {
getLatch.countDown();
}
}
}).start();
Assert.assertTrue("should have tried to get a connection", connLatch.await(1, TimeUnit.SECONDS));
httpget.abort();
Assert.assertTrue("should have finished get request", getLatch.await(1, TimeUnit.SECONDS));
Assert.assertTrue("should be instanceof IOException, was: " + throwableRef.get(),
throwableRef.get() instanceof IOException);
Assert.assertTrue("cause should be InterruptedException, was: " + throwableRef.get().getCause(),
throwableRef.get().getCause() instanceof InterruptedException);
}
/**
* Tests that if a socket fails to connect, the allocated connection is
* properly released back to the connection manager.
*/
@Test
public void testSocketConnectFailureReleasesConnection() throws Exception {
final HttpClientConnection conn = Mockito.mock(HttpClientConnection.class);
final ConnectionRequest connrequest = Mockito.mock(ConnectionRequest.class);
Mockito.when(connrequest.get(
Mockito.anyInt(), Mockito.any(TimeUnit.class))).thenReturn(conn);
final HttpClientConnectionManager connmgr = Mockito.mock(HttpClientConnectionManager.class);
Mockito.doThrow(new ConnectException()).when(connmgr).connect(
Mockito.any(HttpClientConnection.class),
Mockito.any(HttpRoute.class),
Mockito.anyInt(),
Mockito.any(HttpContext.class));
Mockito.when(connmgr.requestConnection(
Mockito.any(HttpRoute.class), Mockito.any())).thenReturn(connrequest);
final CloseableHttpClient client = HttpClients.custom().setConnectionManager(connmgr).build();
final HttpContext context = new BasicHttpContext();
final HttpGet httpget = new HttpGet("http://www.example.com/a");
try {
client.execute(httpget, context);
Assert.fail("expected IOException");
} catch(final IOException expected) {}
Mockito.verify(connmgr).releaseConnection(conn, null, 0, TimeUnit.MILLISECONDS);
}
private static class BasicService implements HttpRequestHandler {
@Override
public void handle(
final ClassicHttpRequest request,
final ClassicHttpResponse response,
final HttpContext context) throws HttpException, IOException {
response.setCode(200);
response.setEntity(new StringEntity("Hello World"));
}
}
private static class BasicRedirectService implements HttpRequestHandler {
private final int statuscode = HttpStatus.SC_SEE_OTHER;
private final int port;
public BasicRedirectService(final int port) {
this.port = port;
}
@Override
public void handle(
final ClassicHttpRequest request,
final ClassicHttpResponse response,
final HttpContext context) throws HttpException, IOException {
response.setCode(this.statuscode);
response.addHeader(new BasicHeader("Location", "http://localhost:"
+ this.port + "/newlocation/"));
response.addHeader(new BasicHeader("Connection", "close"));
}
}
private static class ConnMan4 extends PoolingHttpClientConnectionManager {
private final CountDownLatch connLatch;
private final CountDownLatch awaitLatch;
public ConnMan4(final CountDownLatch connLatch, final CountDownLatch awaitLatch) {
super();
this.connLatch = connLatch;
this.awaitLatch = awaitLatch;
}
@Override
public ConnectionRequest requestConnection(final HttpRoute route, final Object state) {
// If this is the redirect route, stub the return value
// so-as to pretend the host is waiting on a slot...
if(route.getTargetHost().getHostName().equals("localhost")) {
final Thread currentThread = Thread.currentThread();
return new ConnectionRequest() {
@Override
public boolean cancel() {
currentThread.interrupt();
return true;
}
@Override
public HttpClientConnection get(
final long timeout,
final TimeUnit tunit) throws InterruptedException, ConnectionPoolTimeoutException {
connLatch.countDown(); // notify waiter that we're getting a connection
// zero usually means sleep forever, but CountDownLatch doesn't interpret it that way.
if(!awaitLatch.await(timeout > 0 ? timeout : Integer.MAX_VALUE, tunit)) {
throw new ConnectionPoolTimeoutException();
}
return Mockito.mock(HttpClientConnection.class);
}
};
} else {
return super.requestConnection(route, state);
}
}
}
static class ConMan implements HttpClientConnectionManager {
private final CountDownLatch connLatch;
private final CountDownLatch awaitLatch;
public ConMan(final CountDownLatch connLatch, final CountDownLatch awaitLatch) {
this.connLatch = connLatch;
this.awaitLatch = awaitLatch;
}
@Override
public void closeIdle(final long idletime, final TimeUnit tunit) {
throw new UnsupportedOperationException("just a mockup");
}
@Override
public void closeExpired() {
throw new UnsupportedOperationException("just a mockup");
}
public HttpClientConnection getConnection(final HttpRoute route,
final long timeout, final TimeUnit tunit) {
throw new UnsupportedOperationException("just a mockup");
}
@Override
public ConnectionRequest requestConnection(
final HttpRoute route,
final Object state) {
final Thread currentThread = Thread.currentThread();
return new ConnectionRequest() {
@Override
public boolean cancel() {
currentThread.interrupt();
return true;
}
@Override
public HttpClientConnection get(
final long timeout,
final TimeUnit tunit) throws InterruptedException, ConnectionPoolTimeoutException {
connLatch.countDown(); // notify waiter that we're getting a connection
// zero usually means sleep forever, but CountDownLatch doesn't interpret it that way.
if(!awaitLatch.await(timeout > 0 ? timeout : Integer.MAX_VALUE, tunit)) {
throw new ConnectionPoolTimeoutException();
}
return Mockito.mock(HttpClientConnection.class);
}
};
}
@Override
public void shutdown() {
}
public void close() {
}
@Override
public void releaseConnection(
final HttpClientConnection conn,
final Object newState,
final long validDuration, final TimeUnit timeUnit) {
throw new UnsupportedOperationException("just a mockup");
}
@Override
public void connect(
final HttpClientConnection conn,
final HttpRoute route,
final int connectTimeout,
final HttpContext context) throws IOException {
throw new UnsupportedOperationException("just a mockup");
}
@Override
public void upgrade(
final HttpClientConnection conn,
final HttpRoute route,
final HttpContext context) throws IOException {
throw new UnsupportedOperationException("just a mockup");
}
@Override
public void routeComplete(
final HttpClientConnection conn,
final HttpRoute route,
final HttpContext context) throws IOException {
throw new UnsupportedOperationException("just a mockup");
}
public void connect(
final HttpClientConnection conn,
final HttpHost host,
final InetSocketAddress localAddress,
final int connectTimeout,
final HttpContext context) {
throw new UnsupportedOperationException("just a mockup");
}
public void upgrade(
final HttpClientConnection conn,
final HttpHost host,
final HttpContext context) {
throw new UnsupportedOperationException("just a mockup");
}
}
private static class CustomGet extends HttpGet {
private static final long serialVersionUID = 1L;
private final CountDownLatch releaseTriggerLatch;
public CustomGet(final String uri, final CountDownLatch releaseTriggerLatch) {
super(uri);
this.releaseTriggerLatch = releaseTriggerLatch;
}
@Override
public void setCancellable(final Cancellable cancellable) {
try {
if(!releaseTriggerLatch.await(1, TimeUnit.SECONDS)) {
throw new RuntimeException("Waited too long...");
}
} catch(final InterruptedException ie) {
throw new RuntimeException(ie);
}
super.setCancellable(cancellable);
}
}
}

View File

@ -31,10 +31,11 @@
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.apache.hc.client5.http.ConnectionPoolTimeoutException;
import org.apache.hc.client5.http.HttpRoute; import org.apache.hc.client5.http.HttpRoute;
import org.apache.hc.client5.http.io.ConnectionRequest; import org.apache.hc.client5.http.io.ConnectionEndpoint;
import org.apache.hc.client5.http.io.LeaseRequest;
import org.apache.hc.client5.http.localserver.LocalServerTestBase; import org.apache.hc.client5.http.localserver.LocalServerTestBase;
import org.apache.hc.client5.http.methods.HttpGet; import org.apache.hc.client5.http.methods.HttpGet;
import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpRequest;
@ -44,13 +45,11 @@
import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.MalformedChunkCodingException; import org.apache.hc.core5.http.MalformedChunkCodingException;
import org.apache.hc.core5.http.impl.io.DefaultBHttpServerConnection; import org.apache.hc.core5.http.impl.io.DefaultBHttpServerConnection;
import org.apache.hc.core5.http.io.HttpClientConnection;
import org.apache.hc.core5.http.io.HttpRequestHandler; import org.apache.hc.core5.http.io.HttpRequestHandler;
import org.apache.hc.core5.http.io.entity.BasicHttpEntity; import org.apache.hc.core5.http.io.entity.BasicHttpEntity;
import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.protocol.HttpContext;
import org.apache.hc.core5.http.protocol.HttpCoreContext; import org.apache.hc.core5.http.protocol.HttpCoreContext;
import org.apache.hc.core5.pool.PoolStats;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
@ -62,19 +61,18 @@ public void testReleaseOnEntityConsumeContent() throws Exception {
this.connManager.setMaxTotal(1); this.connManager.setMaxTotal(1);
// Zero connections in the pool // Zero connections in the pool
PoolStats stats = this.connManager.getTotalStats(); Assert.assertEquals(0, this.connManager.getTotalStats().getAvailable());
Assert.assertEquals(0, stats.getAvailable());
final HttpHost target = start(); final HttpHost target = start();
// Get some random data // Get some random data
final HttpGet httpget = new HttpGet("/random/20000"); final HttpGet httpget = new HttpGet("/random/20000");
final ClassicHttpResponse response = this.httpclient.execute(target, httpget); final ClassicHttpResponse response = this.httpclient.execute(target, httpget);
ConnectionRequest connreq = this.connManager.requestConnection(new HttpRoute(target), null); final LeaseRequest connreq1 = this.connManager.lease(new HttpRoute(target), null);
try { try {
connreq.get(250, TimeUnit.MILLISECONDS); connreq1.get(250, TimeUnit.MILLISECONDS);
Assert.fail("ConnectionPoolTimeoutException should have been thrown"); Assert.fail("ConnectionPoolTimeoutException should have been thrown");
} catch (final ConnectionPoolTimeoutException expected) { } catch (final TimeoutException expected) {
} }
final HttpEntity e = response.getEntity(); final HttpEntity e = response.getEntity();
@ -82,14 +80,13 @@ public void testReleaseOnEntityConsumeContent() throws Exception {
EntityUtils.consume(e); EntityUtils.consume(e);
// Expect one connection in the pool // Expect one connection in the pool
stats = this.connManager.getTotalStats(); Assert.assertEquals(1, this.connManager.getTotalStats().getAvailable());
Assert.assertEquals(1, stats.getAvailable());
// Make sure one connection is available // Make sure one connection is available
connreq = this.connManager.requestConnection(new HttpRoute(target), null); final LeaseRequest connreq2 = this.connManager.lease(new HttpRoute(target), null);
final HttpClientConnection conn = connreq.get(250, TimeUnit.MILLISECONDS); final ConnectionEndpoint endpoint = connreq2.get(250, TimeUnit.MILLISECONDS);
this.connManager.releaseConnection(conn, null, -1, null); this.connManager.release(endpoint, null, -1, null);
} }
@Test @Test
@ -98,19 +95,18 @@ public void testReleaseOnEntityWriteTo() throws Exception {
this.connManager.setMaxTotal(1); this.connManager.setMaxTotal(1);
// Zero connections in the pool // Zero connections in the pool
PoolStats stats = this.connManager.getTotalStats(); Assert.assertEquals(0, this.connManager.getTotalStats().getAvailable());
Assert.assertEquals(0, stats.getAvailable());
final HttpHost target = start(); final HttpHost target = start();
// Get some random data // Get some random data
final HttpGet httpget = new HttpGet("/random/20000"); final HttpGet httpget = new HttpGet("/random/20000");
final ClassicHttpResponse response = this.httpclient.execute(target, httpget); final ClassicHttpResponse response = this.httpclient.execute(target, httpget);
ConnectionRequest connreq = this.connManager.requestConnection(new HttpRoute(target), null); final LeaseRequest connreq1 = this.connManager.lease(new HttpRoute(target), null);
try { try {
connreq.get(250, TimeUnit.MILLISECONDS); connreq1.get(250, TimeUnit.MILLISECONDS);
Assert.fail("ConnectionPoolTimeoutException should have been thrown"); Assert.fail("ConnectionPoolTimeoutException should have been thrown");
} catch (final ConnectionPoolTimeoutException expected) { } catch (final TimeoutException expected) {
} }
final HttpEntity e = response.getEntity(); final HttpEntity e = response.getEntity();
@ -119,14 +115,13 @@ public void testReleaseOnEntityWriteTo() throws Exception {
e.writeTo(outsteam); e.writeTo(outsteam);
// Expect one connection in the pool // Expect one connection in the pool
stats = this.connManager.getTotalStats(); Assert.assertEquals(1, this.connManager.getTotalStats().getAvailable());
Assert.assertEquals(1, stats.getAvailable());
// Make sure one connection is available // Make sure one connection is available
connreq = this.connManager.requestConnection(new HttpRoute(target), null); final LeaseRequest connreq2 = this.connManager.lease(new HttpRoute(target), null);
final HttpClientConnection conn = connreq.get(250, TimeUnit.MILLISECONDS); final ConnectionEndpoint endpoint = connreq2.get(250, TimeUnit.MILLISECONDS);
this.connManager.releaseConnection(conn, null, -1, null); this.connManager.release(endpoint, null, -1, null);
} }
@Test @Test
@ -135,8 +130,7 @@ public void testReleaseOnAbort() throws Exception {
this.connManager.setMaxTotal(1); this.connManager.setMaxTotal(1);
// Zero connections in the pool // Zero connections in the pool
final PoolStats stats = this.connManager.getTotalStats(); Assert.assertEquals(0, this.connManager.getTotalStats().getAvailable());
Assert.assertEquals(0, stats.getAvailable());
final HttpHost target = start(); final HttpHost target = start();
@ -144,11 +138,11 @@ public void testReleaseOnAbort() throws Exception {
final HttpGet httpget = new HttpGet("/random/20000"); final HttpGet httpget = new HttpGet("/random/20000");
final ClassicHttpResponse response = this.httpclient.execute(target, httpget); final ClassicHttpResponse response = this.httpclient.execute(target, httpget);
ConnectionRequest connreq = this.connManager.requestConnection(new HttpRoute(target), null); final LeaseRequest connreq1 = this.connManager.lease(new HttpRoute(target), null);
try { try {
connreq.get(250, TimeUnit.MILLISECONDS); connreq1.get(250, TimeUnit.MILLISECONDS);
Assert.fail("ConnectionPoolTimeoutException should have been thrown"); Assert.fail("ConnectionPoolTimeoutException should have been thrown");
} catch (final ConnectionPoolTimeoutException expected) { } catch (final TimeoutException expected) {
} }
final HttpEntity e = response.getEntity(); final HttpEntity e = response.getEntity();
@ -159,10 +153,10 @@ public void testReleaseOnAbort() throws Exception {
Assert.assertEquals(0, this.connManager.getTotalStats().getAvailable()); Assert.assertEquals(0, this.connManager.getTotalStats().getAvailable());
// Make sure one connection is available // Make sure one connection is available
connreq = this.connManager.requestConnection(new HttpRoute(target), null); final LeaseRequest connreq2 = this.connManager.lease(new HttpRoute(target), null);
final HttpClientConnection conn = connreq.get(250, TimeUnit.MILLISECONDS); final ConnectionEndpoint endpoint = connreq2.get(250, TimeUnit.MILLISECONDS);
this.connManager.releaseConnection(conn, null, -1, null); this.connManager.release(endpoint, null, -1, null);
} }
@Test @Test
@ -212,11 +206,11 @@ public void writeTo(
final HttpGet httpget = new HttpGet("/dropdead"); final HttpGet httpget = new HttpGet("/dropdead");
final ClassicHttpResponse response = this.httpclient.execute(target, httpget); final ClassicHttpResponse response = this.httpclient.execute(target, httpget);
ConnectionRequest connreq = this.connManager.requestConnection(new HttpRoute(target), null); final LeaseRequest connreq1 = this.connManager.lease(new HttpRoute(target), null);
try { try {
connreq.get(250, TimeUnit.MILLISECONDS); connreq1.get(250, TimeUnit.MILLISECONDS);
Assert.fail("ConnectionPoolTimeoutException should have been thrown"); Assert.fail("ConnectionPoolTimeoutException should have been thrown");
} catch (final ConnectionPoolTimeoutException expected) { } catch (final TimeoutException expected) {
} }
final HttpEntity e = response.getEntity(); final HttpEntity e = response.getEntity();
@ -233,10 +227,10 @@ public void writeTo(
Assert.assertEquals(0, this.connManager.getTotalStats().getAvailable()); Assert.assertEquals(0, this.connManager.getTotalStats().getAvailable());
// Make sure one connection is available // Make sure one connection is available
connreq = this.connManager.requestConnection(new HttpRoute(target), null); final LeaseRequest connreq2 = this.connManager.lease(new HttpRoute(target), null);
final HttpClientConnection conn = connreq.get(250, TimeUnit.MILLISECONDS); final ConnectionEndpoint endpoint = connreq2.get(250, TimeUnit.MILLISECONDS);
this.connManager.releaseConnection(conn, null, -1, null); this.connManager.release(endpoint, null, -1, null);
} }
} }

View File

@ -28,27 +28,23 @@
package org.apache.hc.client5.http.impl.integration; package org.apache.hc.client5.http.impl.integration;
import java.util.Collections; import java.util.Collections;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.apache.hc.client5.http.ConnectionPoolTimeoutException;
import org.apache.hc.client5.http.HttpRoute; import org.apache.hc.client5.http.HttpRoute;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager; import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.io.ConnectionRequest; import org.apache.hc.client5.http.io.ConnectionEndpoint;
import org.apache.hc.client5.http.io.HttpClientConnectionManager; import org.apache.hc.client5.http.io.LeaseRequest;
import org.apache.hc.client5.http.localserver.LocalServerTestBase; import org.apache.hc.client5.http.localserver.LocalServerTestBase;
import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.HttpStatus;
import org.apache.hc.core5.http.impl.io.HttpRequestExecutor; import org.apache.hc.core5.http.impl.io.HttpRequestExecutor;
import org.apache.hc.core5.http.io.HttpClientConnection;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.message.BasicClassicHttpRequest; import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
import org.apache.hc.core5.http.protocol.BasicHttpContext; import org.apache.hc.core5.http.protocol.BasicHttpContext;
import org.apache.hc.core5.http.protocol.DefaultHttpProcessor; import org.apache.hc.core5.http.protocol.DefaultHttpProcessor;
import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.protocol.HttpContext;
import org.apache.hc.core5.http.protocol.HttpCoreContext;
import org.apache.hc.core5.http.protocol.HttpProcessor; import org.apache.hc.core5.http.protocol.HttpProcessor;
import org.apache.hc.core5.http.protocol.RequestConnControl; import org.apache.hc.core5.http.protocol.RequestConnControl;
import org.apache.hc.core5.http.protocol.RequestContent; import org.apache.hc.core5.http.protocol.RequestContent;
@ -62,22 +58,6 @@
*/ */
public class TestConnectionManagement extends LocalServerTestBase { public class TestConnectionManagement extends LocalServerTestBase {
private static HttpClientConnection getConnection(
final HttpClientConnectionManager mgr,
final HttpRoute route,
final long timeout,
final TimeUnit unit) throws ConnectionPoolTimeoutException, ExecutionException, InterruptedException {
final ConnectionRequest connRequest = mgr.requestConnection(route, null);
return connRequest.get(timeout, unit);
}
private static HttpClientConnection getConnection(
final HttpClientConnectionManager mgr,
final HttpRoute route) throws ConnectionPoolTimeoutException, ExecutionException, InterruptedException {
final ConnectionRequest connRequest = mgr.requestConnection(route, null);
return connRequest.get(0, TimeUnit.MILLISECONDS);
}
/** /**
* Tests releasing and re-using a connection after a response is read. * Tests releasing and re-using a connection after a response is read.
*/ */
@ -91,79 +71,60 @@ public void testReleaseConnection() throws Exception {
final int rsplen = 8; final int rsplen = 8;
final String uri = "/random/" + rsplen; final String uri = "/random/" + rsplen;
final ClassicHttpRequest request = new BasicClassicHttpRequest("GET", uri); final ClassicHttpRequest request = new BasicClassicHttpRequest("GET", target, uri);
final HttpContext context = new BasicHttpContext(); final HttpContext context = new BasicHttpContext();
HttpClientConnection conn = getConnection(this.connManager, route); final LeaseRequest leaseRequest1 = this.connManager.lease(route, null);
this.connManager.connect(conn, route, 0, context); final ConnectionEndpoint endpoint1 = leaseRequest1.get(0, TimeUnit.MILLISECONDS);
this.connManager.routeComplete(conn, route, context);
context.setAttribute(HttpCoreContext.HTTP_CONNECTION, conn); this.connManager.connect(endpoint1, 0, TimeUnit.MILLISECONDS, context);
final HttpProcessor httpProcessor = new DefaultHttpProcessor( final HttpProcessor httpProcessor = new DefaultHttpProcessor(
new RequestTargetHost(), new RequestContent(), new RequestConnControl()); new RequestTargetHost(), new RequestContent(), new RequestConnControl());
final HttpRequestExecutor exec = new HttpRequestExecutor(); final HttpRequestExecutor exec = new HttpRequestExecutor();
exec.preProcess(request, httpProcessor, context); exec.preProcess(request, httpProcessor, context);
ClassicHttpResponse response = exec.execute(request, conn, context); try (final ClassicHttpResponse response1 = endpoint1.execute(request, exec, context)) {
Assert.assertEquals(HttpStatus.SC_OK, response1.getCode());
Assert.assertEquals("wrong status in first response", }
HttpStatus.SC_OK,
response.getCode());
byte[] data = EntityUtils.toByteArray(response.getEntity());
Assert.assertEquals("wrong length of first response entity",
rsplen, data.length);
// ignore data, but it must be read
// check that there is no auto-release by default // check that there is no auto-release by default
try { try {
// this should fail quickly, connection has not been released // this should fail quickly, connection has not been released
getConnection(this.connManager, route, 10L, TimeUnit.MILLISECONDS); final LeaseRequest leaseRequest2 = this.connManager.lease(route, null);
Assert.fail("ConnectionPoolTimeoutException should have been thrown"); leaseRequest2.get(10, TimeUnit.MILLISECONDS);
} catch (final ConnectionPoolTimeoutException e) { Assert.fail("TimeoutException expected");
} catch (final TimeoutException ex) {
// expected // expected
} }
conn.close(); endpoint1.close();
this.connManager.releaseConnection(conn, null, -1, null); this.connManager.release(endpoint1, null, -1, null);
conn = getConnection(this.connManager, route); final LeaseRequest leaseRequest2 = this.connManager.lease(route, null);
Assert.assertFalse("connection should have been closed", conn.isOpen()); final ConnectionEndpoint endpoint2 = leaseRequest2.get(0, TimeUnit.MILLISECONDS);
Assert.assertFalse(endpoint2.isConnected());
this.connManager.connect(conn, route, 0, context); this.connManager.connect(endpoint2, 0, TimeUnit.MILLISECONDS, context);
this.connManager.routeComplete(conn, route, context);
// repeat the communication, no need to prepare the request again try (final ClassicHttpResponse response2 = endpoint2.execute(request, exec, context)) {
context.setAttribute(HttpCoreContext.HTTP_CONNECTION, conn); Assert.assertEquals(HttpStatus.SC_OK, response2.getCode());
response = exec.execute(request, conn, context); }
Assert.assertEquals("wrong status in second response",
HttpStatus.SC_OK,
response.getCode());
data = EntityUtils.toByteArray(response.getEntity());
Assert.assertEquals("wrong length of second response entity",
rsplen, data.length);
// ignore data, but it must be read
// release connection after marking it for re-use // release connection after marking it for re-use
// expect the next connection obtained to be open // expect the next connection obtained to be open
this.connManager.releaseConnection(conn, null, -1, null); this.connManager.release(endpoint2, null, -1, null);
conn = getConnection(this.connManager, route);
Assert.assertTrue("connection should have been open", conn.isOpen()); final LeaseRequest leaseRequest3 = this.connManager.lease(route, null);
final ConnectionEndpoint endpoint3 = leaseRequest3.get(0, TimeUnit.MILLISECONDS);
Assert.assertTrue(endpoint3.isConnected());
// repeat the communication, no need to prepare the request again // repeat the communication, no need to prepare the request again
context.setAttribute(HttpCoreContext.HTTP_CONNECTION, conn); try (final ClassicHttpResponse response3 = endpoint3.execute(request, exec, context)) {
response = exec.execute(request, conn, context); Assert.assertEquals(HttpStatus.SC_OK, response3.getCode());
}
Assert.assertEquals("wrong status in third response", this.connManager.release(endpoint3, null, -1, null);
HttpStatus.SC_OK, this.connManager.close();
response.getCode());
data = EntityUtils.toByteArray(response.getEntity());
Assert.assertEquals("wrong length of third response entity",
rsplen, data.length);
// ignore data, but it must be read
this.connManager.releaseConnection(conn, null, -1, null);
this.connManager.shutdown();
} }
/** /**
@ -179,96 +140,71 @@ public void testReleaseConnectionWithTimeLimits() throws Exception {
final int rsplen = 8; final int rsplen = 8;
final String uri = "/random/" + rsplen; final String uri = "/random/" + rsplen;
final ClassicHttpRequest request = new BasicClassicHttpRequest("GET", uri); final ClassicHttpRequest request = new BasicClassicHttpRequest("GET", target, uri);
final HttpContext context = new BasicHttpContext(); final HttpContext context = new BasicHttpContext();
HttpClientConnection conn = getConnection(this.connManager, route); final LeaseRequest leaseRequest1 = this.connManager.lease(route, null);
this.connManager.connect(conn, route, 0, context); final ConnectionEndpoint endpoint1 = leaseRequest1.get(0, TimeUnit.MILLISECONDS);
this.connManager.routeComplete(conn, route, context); this.connManager.connect(endpoint1, 0, TimeUnit.MILLISECONDS, context);
context.setAttribute(HttpCoreContext.HTTP_CONNECTION, conn);
final HttpProcessor httpProcessor = new DefaultHttpProcessor( final HttpProcessor httpProcessor = new DefaultHttpProcessor(
new RequestTargetHost(), new RequestContent(), new RequestConnControl()); new RequestTargetHost(), new RequestContent(), new RequestConnControl());
final HttpRequestExecutor exec = new HttpRequestExecutor(); final HttpRequestExecutor exec = new HttpRequestExecutor();
exec.preProcess(request, httpProcessor, context); exec.preProcess(request, httpProcessor, context);
ClassicHttpResponse response = exec.execute(request, conn, context); try (final ClassicHttpResponse response1 = endpoint1.execute(request, exec, context)) {
Assert.assertEquals(HttpStatus.SC_OK, response1.getCode());
Assert.assertEquals("wrong status in first response", }
HttpStatus.SC_OK,
response.getCode());
byte[] data = EntityUtils.toByteArray(response.getEntity());
Assert.assertEquals("wrong length of first response entity",
rsplen, data.length);
// ignore data, but it must be read
// check that there is no auto-release by default // check that there is no auto-release by default
try { try {
// this should fail quickly, connection has not been released // this should fail quickly, connection has not been released
getConnection(this.connManager, route, 10L, TimeUnit.MILLISECONDS); final LeaseRequest leaseRequest2 = this.connManager.lease(route, null);
Assert.fail("ConnectionPoolTimeoutException should have been thrown"); leaseRequest2.get(10, TimeUnit.MILLISECONDS);
} catch (final ConnectionPoolTimeoutException e) { Assert.fail("TimeoutException expected");
} catch (final TimeoutException ex) {
// expected // expected
} }
conn.close(); endpoint1.close();
this.connManager.releaseConnection(conn, null, 100, TimeUnit.MILLISECONDS); this.connManager.release(endpoint1, null, 100, TimeUnit.MILLISECONDS);
conn = getConnection(this.connManager, route);
Assert.assertFalse("connection should have been closed", conn.isOpen()); final LeaseRequest leaseRequest2 = this.connManager.lease(route, null);
final ConnectionEndpoint endpoint2 = leaseRequest2.get(0, TimeUnit.MILLISECONDS);
Assert.assertFalse(endpoint2.isConnected());
this.connManager.connect(endpoint2, 0, TimeUnit.MILLISECONDS, context);
try (final ClassicHttpResponse response2 = endpoint2.execute(request, exec, context)) {
Assert.assertEquals(HttpStatus.SC_OK, response2.getCode());
}
this.connManager.release(endpoint2, null, 100, TimeUnit.MILLISECONDS);
final LeaseRequest leaseRequest3 = this.connManager.lease(route, null);
final ConnectionEndpoint endpoint3 = leaseRequest3.get(0, TimeUnit.MILLISECONDS);
Assert.assertTrue(endpoint3.isConnected());
// repeat the communication, no need to prepare the request again // repeat the communication, no need to prepare the request again
this.connManager.connect(conn, route, 0, context); try (final ClassicHttpResponse response3 = endpoint3.execute(request, exec, context)) {
this.connManager.routeComplete(conn, route, context); Assert.assertEquals(HttpStatus.SC_OK, response3.getCode());
}
context.setAttribute(HttpCoreContext.HTTP_CONNECTION, conn); this.connManager.release(endpoint3, null, 100, TimeUnit.MILLISECONDS);
response = exec.execute(request, conn, context);
Assert.assertEquals("wrong status in second response",
HttpStatus.SC_OK,
response.getCode());
data = EntityUtils.toByteArray(response.getEntity());
Assert.assertEquals("wrong length of second response entity",
rsplen, data.length);
// ignore data, but it must be read
this.connManager.releaseConnection(conn, null, 100, TimeUnit.MILLISECONDS);
conn = getConnection(this.connManager, route);
Assert.assertTrue("connection should have been open", conn.isOpen());
// repeat the communication, no need to prepare the request again
context.setAttribute(HttpCoreContext.HTTP_CONNECTION, conn);
response = exec.execute(request, conn, context);
Assert.assertEquals("wrong status in third response",
HttpStatus.SC_OK,
response.getCode());
data = EntityUtils.toByteArray(response.getEntity());
Assert.assertEquals("wrong length of third response entity",
rsplen, data.length);
// ignore data, but it must be read
this.connManager.releaseConnection(conn, null, 100, TimeUnit.MILLISECONDS);
Thread.sleep(150); Thread.sleep(150);
conn = getConnection(this.connManager, route);
Assert.assertTrue("connection should have been closed", !conn.isOpen()); final LeaseRequest leaseRequest4 = this.connManager.lease(route, null);
final ConnectionEndpoint endpoint4 = leaseRequest4.get(0, TimeUnit.MILLISECONDS);
Assert.assertFalse(endpoint4.isConnected());
// repeat the communication, no need to prepare the request again // repeat the communication, no need to prepare the request again
this.connManager.connect(conn, route, 0, context); this.connManager.connect(endpoint4, 0, TimeUnit.MILLISECONDS, context);
this.connManager.routeComplete(conn, route, context);
context.setAttribute(HttpCoreContext.HTTP_CONNECTION, conn); try (final ClassicHttpResponse response4 = endpoint4.execute(request, exec, context)) {
response = exec.execute(request, conn, context); Assert.assertEquals(HttpStatus.SC_OK, response4.getCode());
}
Assert.assertEquals("wrong status in third response", this.connManager.close();
HttpStatus.SC_OK,
response.getCode());
data = EntityUtils.toByteArray(response.getEntity());
Assert.assertEquals("wrong length of fourth response entity",
rsplen, data.length);
// ignore data, but it must be read
this.connManager.shutdown();
} }
@Test @Test
@ -280,15 +216,15 @@ public void testCloseExpiredIdleConnections() throws Exception {
final HttpRoute route = new HttpRoute(target, null, false); final HttpRoute route = new HttpRoute(target, null, false);
final HttpContext context = new BasicHttpContext(); final HttpContext context = new BasicHttpContext();
final HttpClientConnection conn = getConnection(this.connManager, route); final LeaseRequest leaseRequest1 = this.connManager.lease(route, null);
this.connManager.connect(conn, route, 0, context); final ConnectionEndpoint endpoint1 = leaseRequest1.get(0, TimeUnit.MILLISECONDS);
this.connManager.routeComplete(conn, route, context); this.connManager.connect(endpoint1, 0, TimeUnit.MILLISECONDS, context);
Assert.assertEquals(Collections.singleton(route), this.connManager.getRoutes()); Assert.assertEquals(Collections.singleton(route), this.connManager.getRoutes());
Assert.assertEquals(1, this.connManager.getTotalStats().getLeased()); Assert.assertEquals(1, this.connManager.getTotalStats().getLeased());
Assert.assertEquals(1, this.connManager.getStats(route).getLeased()); Assert.assertEquals(1, this.connManager.getStats(route).getLeased());
this.connManager.releaseConnection(conn, null, 100, TimeUnit.MILLISECONDS); this.connManager.release(endpoint1, null, 100, TimeUnit.MILLISECONDS);
// Released, still active. // Released, still active.
Assert.assertEquals(Collections.singleton(route), this.connManager.getRoutes()); Assert.assertEquals(Collections.singleton(route), this.connManager.getRoutes());
@ -311,7 +247,7 @@ public void testCloseExpiredIdleConnections() throws Exception {
Assert.assertEquals(0, this.connManager.getTotalStats().getAvailable()); Assert.assertEquals(0, this.connManager.getTotalStats().getAvailable());
Assert.assertEquals(0, this.connManager.getStats(route).getAvailable()); Assert.assertEquals(0, this.connManager.getStats(route).getAvailable());
this.connManager.shutdown(); this.connManager.close();
} }
@Test @Test
@ -327,15 +263,15 @@ public void testCloseExpiredTTLConnections() throws Exception {
final HttpRoute route = new HttpRoute(target, null, false); final HttpRoute route = new HttpRoute(target, null, false);
final HttpContext context = new BasicHttpContext(); final HttpContext context = new BasicHttpContext();
final HttpClientConnection conn = getConnection(this.connManager, route); final LeaseRequest leaseRequest1 = this.connManager.lease(route, null);
this.connManager.connect(conn, route, 0, context); final ConnectionEndpoint endpoint1 = leaseRequest1.get(0, TimeUnit.MILLISECONDS);
this.connManager.routeComplete(conn, route, context); this.connManager.connect(endpoint1, 0, TimeUnit.MILLISECONDS, context);
Assert.assertEquals(Collections.singleton(route), this.connManager.getRoutes()); Assert.assertEquals(Collections.singleton(route), this.connManager.getRoutes());
Assert.assertEquals(1, this.connManager.getTotalStats().getLeased()); Assert.assertEquals(1, this.connManager.getTotalStats().getLeased());
Assert.assertEquals(1, this.connManager.getStats(route).getLeased()); Assert.assertEquals(1, this.connManager.getStats(route).getLeased());
// Release, let remain idle for forever // Release, let remain idle for forever
this.connManager.releaseConnection(conn, null, -1, TimeUnit.MILLISECONDS); this.connManager.release(endpoint1, null, 0, TimeUnit.MILLISECONDS);
// Released, still active. // Released, still active.
Assert.assertEquals(Collections.singleton(route), this.connManager.getRoutes()); Assert.assertEquals(Collections.singleton(route), this.connManager.getRoutes());
@ -358,64 +294,7 @@ public void testCloseExpiredTTLConnections() throws Exception {
Assert.assertEquals(0, this.connManager.getTotalStats().getAvailable()); Assert.assertEquals(0, this.connManager.getTotalStats().getAvailable());
Assert.assertEquals(0, this.connManager.getStats(route).getAvailable()); Assert.assertEquals(0, this.connManager.getStats(route).getAvailable());
this.connManager.shutdown(); this.connManager.close();
}
/**
* Tests releasing connection from #abort method called from the
* main execution thread while there is no blocking I/O operation.
*/
@Test
public void testReleaseConnectionOnAbort() throws Exception {
this.connManager.setMaxTotal(1);
final HttpHost target = start();
final HttpRoute route = new HttpRoute(target, null, false);
final int rsplen = 8;
final String uri = "/random/" + rsplen;
final HttpContext context = new BasicHttpContext();
final ClassicHttpRequest request =
new BasicClassicHttpRequest("GET", uri);
HttpClientConnection conn = getConnection(this.connManager, route);
this.connManager.connect(conn, route, 0, context);
this.connManager.routeComplete(conn, route, context);
context.setAttribute(HttpCoreContext.HTTP_CONNECTION, conn);
final HttpProcessor httpProcessor = new DefaultHttpProcessor(
new RequestTargetHost(), new RequestContent(), new RequestConnControl());
final HttpRequestExecutor exec = new HttpRequestExecutor();
exec.preProcess(request, httpProcessor, context);
final ClassicHttpResponse response = exec.execute(request, conn, context);
Assert.assertEquals("wrong status in first response",
HttpStatus.SC_OK,
response.getCode());
// check that there are no connections available
try {
// this should fail quickly, connection has not been released
getConnection(this.connManager, route, 100L, TimeUnit.MILLISECONDS);
Assert.fail("ConnectionPoolTimeoutException should have been thrown");
} catch (final ConnectionPoolTimeoutException e) {
// expected
}
// abort the connection
Assert.assertTrue(conn instanceof HttpClientConnection);
conn.shutdown();
this.connManager.releaseConnection(conn, null, -1, null);
// the connection is expected to be released back to the manager
conn = getConnection(this.connManager, route, 5L, TimeUnit.SECONDS);
Assert.assertFalse("connection should have been closed", conn.isOpen());
this.connManager.releaseConnection(conn, null, -1, null);
this.connManager.shutdown();
} }
} }

View File

@ -41,7 +41,7 @@
import java.util.zip.Deflater; import java.util.zip.Deflater;
import java.util.zip.GZIPOutputStream; import java.util.zip.GZIPOutputStream;
import org.apache.hc.client5.http.impl.BasicResponseHandler; import org.apache.hc.client5.http.impl.sync.BasicResponseHandler;
import org.apache.hc.client5.http.impl.sync.CloseableHttpClient; import org.apache.hc.client5.http.impl.sync.CloseableHttpClient;
import org.apache.hc.client5.http.localserver.LocalServerTestBase; import org.apache.hc.client5.http.localserver.LocalServerTestBase;
import org.apache.hc.client5.http.methods.HttpGet; import org.apache.hc.client5.http.methods.HttpGet;

View File

@ -36,10 +36,10 @@
import org.apache.hc.client5.http.protocol.UserTokenHandler; import org.apache.hc.client5.http.protocol.UserTokenHandler;
import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.HttpConnection;
import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpException;
import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.HttpStatus;
import org.apache.hc.core5.http.io.HttpClientConnection;
import org.apache.hc.core5.http.io.HttpRequestHandler; import org.apache.hc.core5.http.io.HttpRequestHandler;
import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.io.entity.StringEntity; import org.apache.hc.core5.http.io.entity.StringEntity;
@ -119,12 +119,10 @@ public Object getUserToken(final HttpRoute route, final HttpContext context) {
} }
for (final HttpContext context : contexts) { for (final HttpContext context : contexts) {
final String uid = (String) context.getAttribute("user"); final String state0 = (String) context.getAttribute("r0");
Assert.assertNotNull(state0);
for (int r = 0; r < requestCount; r++) { for (int r = 1; r < requestCount; r++) {
final String state = (String) context.getAttribute("r" + r); Assert.assertEquals(state0, (String) context.getAttribute("r" + r));
Assert.assertNotNull(state);
Assert.assertEquals(uid, state);
} }
} }
@ -176,13 +174,8 @@ public void run() {
this.context); this.context);
this.count++; this.count++;
final HttpClientConnection conn = this.context.getConnection(HttpClientConnection.class); final HttpConnection conn = this.context.getConnection();
final HttpContext connContext = (HttpContext) conn; final String connuid = Integer.toHexString(System.identityHashCode(conn));
String connuid = (String) connContext.getAttribute("user");
if (connuid == null) {
connContext.setAttribute("user", this.uid);
connuid = this.uid;
}
this.context.setAttribute("r" + r, connuid); this.context.setAttribute("r" + r, connuid);
EntityUtils.consume(response.getEntity()); EntityUtils.consume(response.getEntity());
} }

View File

@ -33,19 +33,18 @@
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.apache.hc.client5.http.DnsResolver; import org.apache.hc.client5.http.DnsResolver;
import org.apache.hc.client5.http.HttpConnectionFactory;
import org.apache.hc.client5.http.HttpRoute; import org.apache.hc.client5.http.HttpRoute;
import org.apache.hc.client5.http.SchemePortResolver; import org.apache.hc.client5.http.SchemePortResolver;
import org.apache.hc.client5.http.io.ConnectionRequest; import org.apache.hc.client5.http.io.ConnectionEndpoint;
import org.apache.hc.client5.http.io.LeaseRequest;
import org.apache.hc.client5.http.io.ManagedHttpClientConnection; import org.apache.hc.client5.http.io.ManagedHttpClientConnection;
import org.apache.hc.client5.http.protocol.HttpClientContext; import org.apache.hc.client5.http.protocol.HttpClientContext;
import org.apache.hc.client5.http.socket.ConnectionSocketFactory; import org.apache.hc.client5.http.socket.ConnectionSocketFactory;
import org.apache.hc.client5.http.socket.LayeredConnectionSocketFactory; import org.apache.hc.client5.http.socket.LayeredConnectionSocketFactory;
import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.config.ConnectionConfig;
import org.apache.hc.core5.http.config.Lookup; import org.apache.hc.core5.http.config.Lookup;
import org.apache.hc.core5.http.config.SocketConfig; import org.apache.hc.core5.http.config.SocketConfig;
import org.apache.hc.core5.http.io.HttpClientConnection; import org.apache.hc.core5.http.io.HttpConnectionFactory;
import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.protocol.HttpContext;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
@ -60,7 +59,7 @@ public class TestBasicHttpClientConnectionManager {
@Mock @Mock
private ManagedHttpClientConnection conn; private ManagedHttpClientConnection conn;
@Mock @Mock
private HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory; private HttpConnectionFactory<ManagedHttpClientConnection> connFactory;
@Mock @Mock
private Lookup<ConnectionSocketFactory> socketFactoryRegistry; private Lookup<ConnectionSocketFactory> socketFactoryRegistry;
@Mock @Mock
@ -88,26 +87,24 @@ public void testLeaseReleaseNonReusable() throws Exception {
final HttpHost target = new HttpHost("localhost", 80); final HttpHost target = new HttpHost("localhost", 80);
final HttpRoute route = new HttpRoute(target); final HttpRoute route = new HttpRoute(target);
Mockito.when(connFactory.create( Mockito.when(connFactory.createConnection(Mockito.<Socket>any())).thenReturn(conn);
Mockito.eq(route), Mockito.<ConnectionConfig>any())).thenReturn(conn);
final ConnectionRequest connRequest1 = mgr.requestConnection(route, null); final LeaseRequest connRequest1 = mgr.lease(route, null);
final HttpClientConnection conn1 = connRequest1.get(0, TimeUnit.MILLISECONDS); final ConnectionEndpoint endpoint1 = connRequest1.get(0, TimeUnit.MILLISECONDS);
Assert.assertNotNull(conn1); Assert.assertNotNull(endpoint1);
Assert.assertFalse(conn1.isOpen()); Assert.assertFalse(endpoint1.isConnected());
mgr.releaseConnection(conn1, null, 100, TimeUnit.MILLISECONDS); mgr.release(endpoint1, null, 100, TimeUnit.MILLISECONDS);
Assert.assertNull(mgr.getRoute()); Assert.assertNull(mgr.getRoute());
Assert.assertNull(mgr.getState()); Assert.assertNull(mgr.getState());
final ConnectionRequest connRequest2 = mgr.requestConnection(route, null); final LeaseRequest connRequest2 = mgr.lease(route, null);
final HttpClientConnection conn2 = connRequest2.get(0, TimeUnit.MILLISECONDS); final ConnectionEndpoint conn2 = connRequest2.get(0, TimeUnit.MILLISECONDS);
Assert.assertNotNull(conn2); Assert.assertNotNull(conn2);
Assert.assertFalse(conn2.isOpen()); Assert.assertFalse(conn2.isConnected());
Mockito.verify(connFactory, Mockito.times(2)).create( Mockito.verify(connFactory, Mockito.times(2)).createConnection(Mockito.<Socket>any());
Mockito.eq(route), Mockito.<ConnectionConfig>any());
} }
@Test @Test
@ -115,29 +112,27 @@ public void testLeaseReleaseReusable() throws Exception {
final HttpHost target = new HttpHost("somehost", 80); final HttpHost target = new HttpHost("somehost", 80);
final HttpRoute route = new HttpRoute(target); final HttpRoute route = new HttpRoute(target);
Mockito.when(connFactory.create(Mockito.eq(route), Mockito.<ConnectionConfig>any())).thenReturn(conn); Mockito.when(connFactory.createConnection(Mockito.<Socket>any())).thenReturn(conn);
final ConnectionRequest connRequest1 = mgr.requestConnection(route, null); final LeaseRequest connRequest1 = mgr.lease(route, null);
final HttpClientConnection conn1 = connRequest1.get(0, TimeUnit.MILLISECONDS); final ConnectionEndpoint endpoint1 = connRequest1.get(0, TimeUnit.MILLISECONDS);
Assert.assertNotNull(conn1); Assert.assertNotNull(endpoint1);
Mockito.verify(connFactory, Mockito.times(1)).create( Mockito.verify(connFactory, Mockito.times(1)).createConnection(Mockito.<Socket>any());
Mockito.eq(route), Mockito.<ConnectionConfig>any());
Mockito.when(conn.isOpen()).thenReturn(Boolean.TRUE); Mockito.when(conn.isOpen()).thenReturn(Boolean.TRUE);
mgr.releaseConnection(conn1, null, 10000, TimeUnit.MILLISECONDS); mgr.release(endpoint1, null, 10000, TimeUnit.MILLISECONDS);
Assert.assertEquals(route, mgr.getRoute()); Assert.assertEquals(route, mgr.getRoute());
Assert.assertEquals(null, mgr.getState()); Assert.assertEquals(null, mgr.getState());
final ConnectionRequest connRequest2 = mgr.requestConnection(route, null); final LeaseRequest connRequest2 = mgr.lease(route, null);
final HttpClientConnection conn2 = connRequest2.get(0, TimeUnit.MILLISECONDS); final ConnectionEndpoint conn2 = connRequest2.get(0, TimeUnit.MILLISECONDS);
Assert.assertNotNull(conn2); Assert.assertNotNull(conn2);
Assert.assertTrue(conn2.isOpen()); Assert.assertTrue(conn2.isConnected());
Mockito.verify(connFactory, Mockito.times(1)).create( Mockito.verify(connFactory, Mockito.times(1)).createConnection(Mockito.<Socket>any());
Mockito.eq(route), Mockito.<ConnectionConfig>any());
} }
@Test @Test
@ -145,30 +140,27 @@ public void testLeaseReleaseReusableWithState() throws Exception {
final HttpHost target = new HttpHost("somehost", 80); final HttpHost target = new HttpHost("somehost", 80);
final HttpRoute route = new HttpRoute(target); final HttpRoute route = new HttpRoute(target);
Mockito.when(connFactory.create( Mockito.when(connFactory.createConnection(Mockito.<Socket>any())).thenReturn(conn);
Mockito.eq(route), Mockito.<ConnectionConfig>any())).thenReturn(conn);
final ConnectionRequest connRequest1 = mgr.requestConnection(route, "some state"); final LeaseRequest connRequest1 = mgr.lease(route, "some state");
final HttpClientConnection conn1 = connRequest1.get(0, TimeUnit.MILLISECONDS); final ConnectionEndpoint endpoint1 = connRequest1.get(0, TimeUnit.MILLISECONDS);
Assert.assertNotNull(conn1); Assert.assertNotNull(endpoint1);
Mockito.verify(connFactory, Mockito.times(1)).create( Mockito.verify(connFactory, Mockito.times(1)).createConnection(Mockito.<Socket>any());
Mockito.eq(route), Mockito.<ConnectionConfig>any());
Mockito.when(conn.isOpen()).thenReturn(Boolean.TRUE); Mockito.when(conn.isOpen()).thenReturn(Boolean.TRUE);
mgr.releaseConnection(conn1, "some other state", 10000, TimeUnit.MILLISECONDS); mgr.release(endpoint1, "some other state", 10000, TimeUnit.MILLISECONDS);
Assert.assertEquals(route, mgr.getRoute()); Assert.assertEquals(route, mgr.getRoute());
Assert.assertEquals("some other state", mgr.getState()); Assert.assertEquals("some other state", mgr.getState());
final ConnectionRequest connRequest2 = mgr.requestConnection(route, "some other state"); final LeaseRequest connRequest2 = mgr.lease(route, "some other state");
final HttpClientConnection conn2 = connRequest2.get(0, TimeUnit.MILLISECONDS); final ConnectionEndpoint conn2 = connRequest2.get(0, TimeUnit.MILLISECONDS);
Assert.assertNotNull(conn2); Assert.assertNotNull(conn2);
Assert.assertTrue(conn2.isOpen()); Assert.assertTrue(conn2.isConnected());
Mockito.verify(connFactory, Mockito.times(1)).create( Mockito.verify(connFactory, Mockito.times(1)).createConnection(Mockito.<Socket>any());
Mockito.eq(route), Mockito.<ConnectionConfig>any());
} }
@Test @Test
@ -176,35 +168,30 @@ public void testLeaseDifferentRoute() throws Exception {
final HttpHost target1 = new HttpHost("somehost", 80); final HttpHost target1 = new HttpHost("somehost", 80);
final HttpRoute route1 = new HttpRoute(target1); final HttpRoute route1 = new HttpRoute(target1);
Mockito.when(connFactory.create( Mockito.when(connFactory.createConnection(Mockito.<Socket>any())).thenReturn(conn);
Mockito.<HttpRoute>any(), Mockito.<ConnectionConfig>any())).thenReturn(conn);
final ConnectionRequest connRequest1 = mgr.requestConnection(route1, null); final LeaseRequest connRequest1 = mgr.lease(route1, null);
final HttpClientConnection conn1 = connRequest1.get(0, TimeUnit.MILLISECONDS); final ConnectionEndpoint endpoint1 = connRequest1.get(0, TimeUnit.MILLISECONDS);
Assert.assertNotNull(conn1); Assert.assertNotNull(endpoint1);
Mockito.verify(connFactory, Mockito.times(1)).create( Mockito.verify(connFactory, Mockito.times(1)).createConnection(Mockito.<Socket>any());
Mockito.eq(route1), Mockito.<ConnectionConfig>any());
Mockito.when(conn.isOpen()).thenReturn(Boolean.TRUE, Boolean.FALSE); Mockito.when(conn.isOpen()).thenReturn(Boolean.TRUE, Boolean.FALSE);
mgr.releaseConnection(conn1, null, 0, TimeUnit.MILLISECONDS); mgr.release(endpoint1, null, 0, TimeUnit.MILLISECONDS);
Assert.assertEquals(route1, mgr.getRoute()); Assert.assertEquals(route1, mgr.getRoute());
Assert.assertEquals(null, mgr.getState()); Assert.assertEquals(null, mgr.getState());
final HttpHost target2 = new HttpHost("otherhost", 80); final HttpHost target2 = new HttpHost("otherhost", 80);
final HttpRoute route2 = new HttpRoute(target2); final HttpRoute route2 = new HttpRoute(target2);
final ConnectionRequest connRequest2 = mgr.requestConnection(route2, null); final LeaseRequest connRequest2 = mgr.lease(route2, null);
final HttpClientConnection conn2 = connRequest2.get(0, TimeUnit.MILLISECONDS); final ConnectionEndpoint conn2 = connRequest2.get(0, TimeUnit.MILLISECONDS);
Assert.assertNotNull(conn2); Assert.assertNotNull(conn2);
Assert.assertFalse(conn2.isOpen()); Assert.assertFalse(conn2.isConnected());
Mockito.verify(conn).close(); Mockito.verify(conn).close();
Mockito.verify(connFactory, Mockito.times(1)).create( Mockito.verify(connFactory, Mockito.times(2)).createConnection(Mockito.<Socket>any());
Mockito.eq(route1), Mockito.<ConnectionConfig>any());
Mockito.verify(connFactory, Mockito.times(1)).create(
Mockito.eq(route2), Mockito.<ConnectionConfig>any());
} }
@Test @Test
@ -212,49 +199,41 @@ public void testLeaseExpired() throws Exception {
final HttpHost target = new HttpHost("somehost", 80); final HttpHost target = new HttpHost("somehost", 80);
final HttpRoute route = new HttpRoute(target); final HttpRoute route = new HttpRoute(target);
Mockito.when(connFactory.create( Mockito.when(connFactory.createConnection(Mockito.<Socket>any())).thenReturn(conn);
Mockito.eq(route), Mockito.<ConnectionConfig>any())).thenReturn(conn);
final ConnectionRequest connRequest1 = mgr.requestConnection(route, null); final LeaseRequest connRequest1 = mgr.lease(route, null);
final HttpClientConnection conn1 = connRequest1.get(0, TimeUnit.MILLISECONDS); final ConnectionEndpoint endpoint1 = connRequest1.get(0, TimeUnit.MILLISECONDS);
Assert.assertNotNull(conn1); Assert.assertNotNull(endpoint1);
Mockito.verify(connFactory, Mockito.times(1)).create( Mockito.verify(connFactory, Mockito.times(1)).createConnection(Mockito.<Socket>any());
Mockito.eq(route), Mockito.<ConnectionConfig>any());
Mockito.when(conn.isOpen()).thenReturn(Boolean.TRUE, Boolean.FALSE); Mockito.when(conn.isOpen()).thenReturn(Boolean.TRUE, Boolean.FALSE);
mgr.releaseConnection(conn1, null, 10, TimeUnit.MILLISECONDS); mgr.release(endpoint1, null, 10, TimeUnit.MILLISECONDS);
Assert.assertEquals(route, mgr.getRoute()); Assert.assertEquals(route, mgr.getRoute());
Assert.assertEquals(null, mgr.getState()); Assert.assertEquals(null, mgr.getState());
Thread.sleep(50); Thread.sleep(50);
final ConnectionRequest connRequest2 = mgr.requestConnection(route, null); final LeaseRequest connRequest2 = mgr.lease(route, null);
final HttpClientConnection conn2 = connRequest2.get(0, TimeUnit.MILLISECONDS); final ConnectionEndpoint conn2 = connRequest2.get(0, TimeUnit.MILLISECONDS);
Assert.assertNotNull(conn2); Assert.assertNotNull(conn2);
Assert.assertFalse(conn2.isOpen()); Assert.assertFalse(conn2.isConnected());
Mockito.verify(conn).close(); Mockito.verify(conn).close();
Mockito.verify(connFactory, Mockito.times(2)).create( Mockito.verify(connFactory, Mockito.times(2)).createConnection(Mockito.<Socket>any());
Mockito.eq(route), Mockito.<ConnectionConfig>any());
}
@Test(expected=IllegalArgumentException.class)
public void testLeaseInvalidArg() throws Exception {
mgr.requestConnection(null, null);
} }
@Test(expected=IllegalArgumentException.class) @Test(expected=IllegalArgumentException.class)
public void testReleaseInvalidArg() throws Exception { public void testReleaseInvalidArg() throws Exception {
mgr.releaseConnection(null, null, 0, TimeUnit.MILLISECONDS); mgr.release(null, null, 0, TimeUnit.MILLISECONDS);
} }
@Test(expected=IllegalStateException.class) @Test(expected=IllegalStateException.class)
public void testReleaseAnotherConnection() throws Exception { public void testReleaseAnotherConnection() throws Exception {
final HttpClientConnection wrongCon = Mockito.mock(HttpClientConnection.class); final ConnectionEndpoint wrongCon = Mockito.mock(ConnectionEndpoint.class);
mgr.releaseConnection(wrongCon, null, 0, TimeUnit.MILLISECONDS); mgr.release(wrongCon, null, 0, TimeUnit.MILLISECONDS);
} }
@Test @Test
@ -262,26 +241,24 @@ public void testShutdown() throws Exception {
final HttpHost target = new HttpHost("somehost", 80); final HttpHost target = new HttpHost("somehost", 80);
final HttpRoute route = new HttpRoute(target); final HttpRoute route = new HttpRoute(target);
Mockito.when(connFactory.create( Mockito.when(connFactory.createConnection(Mockito.<Socket>any())).thenReturn(conn);
Mockito.eq(route), Mockito.<ConnectionConfig>any())).thenReturn(conn);
final ConnectionRequest connRequest1 = mgr.requestConnection(route, null); final LeaseRequest connRequest1 = mgr.lease(route, null);
final HttpClientConnection conn1 = connRequest1.get(0, TimeUnit.MILLISECONDS); final ConnectionEndpoint endpoint1 = connRequest1.get(0, TimeUnit.MILLISECONDS);
Assert.assertNotNull(conn1); Assert.assertNotNull(endpoint1);
Mockito.verify(connFactory, Mockito.times(1)).create( Mockito.verify(connFactory, Mockito.times(1)).createConnection(Mockito.<Socket>any());
Mockito.eq(route), Mockito.<ConnectionConfig>any());
Mockito.when(conn.isOpen()).thenReturn(Boolean.TRUE); Mockito.when(conn.isOpen()).thenReturn(Boolean.TRUE);
mgr.releaseConnection(conn1, null, 0, TimeUnit.MILLISECONDS); mgr.release(endpoint1, null, 0, TimeUnit.MILLISECONDS);
mgr.shutdown(); mgr.close();
Mockito.verify(conn, Mockito.times(1)).shutdown(); Mockito.verify(conn, Mockito.times(1)).shutdown();
try { try {
final ConnectionRequest connRequest2 = mgr.requestConnection(route, null); final LeaseRequest connRequest2 = mgr.lease(route, null);
connRequest2.get(0, TimeUnit.MILLISECONDS); connRequest2.get(0, TimeUnit.MILLISECONDS);
Assert.fail("IllegalStateException expected"); Assert.fail("IllegalStateException expected");
} catch (final IllegalStateException ex) { } catch (final IllegalStateException ex) {
@ -290,7 +267,7 @@ public void testShutdown() throws Exception {
// Should have no effect // Should have no effect
mgr.closeExpired(); mgr.closeExpired();
mgr.closeIdle(0L, TimeUnit.MILLISECONDS); mgr.closeIdle(0L, TimeUnit.MILLISECONDS);
mgr.shutdown(); mgr.close();
Mockito.verify(conn, Mockito.times(1)).shutdown(); Mockito.verify(conn, Mockito.times(1)).shutdown();
} }
@ -300,19 +277,17 @@ public void testCloseExpired() throws Exception {
final HttpHost target = new HttpHost("somehost", 80); final HttpHost target = new HttpHost("somehost", 80);
final HttpRoute route = new HttpRoute(target); final HttpRoute route = new HttpRoute(target);
Mockito.when(connFactory.create( Mockito.when(connFactory.createConnection(Mockito.<Socket>any())).thenReturn(conn);
Mockito.eq(route), Mockito.<ConnectionConfig>any())).thenReturn(conn);
final ConnectionRequest connRequest1 = mgr.requestConnection(route, null); final LeaseRequest connRequest1 = mgr.lease(route, null);
final HttpClientConnection conn1 = connRequest1.get(0, TimeUnit.MILLISECONDS); final ConnectionEndpoint endpoint1 = connRequest1.get(0, TimeUnit.MILLISECONDS);
Assert.assertNotNull(conn1); Assert.assertNotNull(endpoint1);
Mockito.verify(connFactory, Mockito.times(1)).create( Mockito.verify(connFactory, Mockito.times(1)).createConnection(Mockito.<Socket>any());
Mockito.eq(route), Mockito.<ConnectionConfig>any());
Mockito.when(conn.isOpen()).thenReturn(Boolean.TRUE, Boolean.FALSE); Mockito.when(conn.isOpen()).thenReturn(Boolean.TRUE, Boolean.FALSE);
mgr.releaseConnection(conn1, null, 10, TimeUnit.MILLISECONDS); mgr.release(endpoint1, null, 10, TimeUnit.MILLISECONDS);
Assert.assertEquals(route, mgr.getRoute()); Assert.assertEquals(route, mgr.getRoute());
Assert.assertEquals(null, mgr.getState()); Assert.assertEquals(null, mgr.getState());
@ -329,19 +304,17 @@ public void testCloseIdle() throws Exception {
final HttpHost target = new HttpHost("somehost", 80); final HttpHost target = new HttpHost("somehost", 80);
final HttpRoute route = new HttpRoute(target); final HttpRoute route = new HttpRoute(target);
Mockito.when(connFactory.create( Mockito.when(connFactory.createConnection(Mockito.<Socket>any())).thenReturn(conn);
Mockito.eq(route), Mockito.<ConnectionConfig>any())).thenReturn(conn);
final ConnectionRequest connRequest1 = mgr.requestConnection(route, null); final LeaseRequest connRequest1 = mgr.lease(route, null);
final HttpClientConnection conn1 = connRequest1.get(0, TimeUnit.MILLISECONDS); final ConnectionEndpoint endpoint1 = connRequest1.get(0, TimeUnit.MILLISECONDS);
Assert.assertNotNull(conn1); Assert.assertNotNull(endpoint1);
Mockito.verify(connFactory, Mockito.times(1)).create( Mockito.verify(connFactory, Mockito.times(1)).createConnection(Mockito.<Socket>any());
Mockito.eq(route), Mockito.<ConnectionConfig>any());
Mockito.when(conn.isOpen()).thenReturn(Boolean.TRUE, Boolean.FALSE); Mockito.when(conn.isOpen()).thenReturn(Boolean.TRUE, Boolean.FALSE);
mgr.releaseConnection(conn1, null, 0, TimeUnit.MILLISECONDS); mgr.release(endpoint1, null, 0, TimeUnit.MILLISECONDS);
Assert.assertEquals(route, mgr.getRoute()); Assert.assertEquals(route, mgr.getRoute());
Assert.assertEquals(null, mgr.getState()); Assert.assertEquals(null, mgr.getState());
@ -358,13 +331,12 @@ public void testAlreadyLeased() throws Exception {
final HttpHost target = new HttpHost("somehost", 80); final HttpHost target = new HttpHost("somehost", 80);
final HttpRoute route = new HttpRoute(target); final HttpRoute route = new HttpRoute(target);
Mockito.when(connFactory.create( Mockito.when(connFactory.createConnection(Mockito.<Socket>any())).thenReturn(conn);
Mockito.eq(route), Mockito.<ConnectionConfig>any())).thenReturn(conn);
final ConnectionRequest connRequest1 = mgr.requestConnection(route, null); final LeaseRequest connRequest1 = mgr.lease(route, null);
final HttpClientConnection conn1 = connRequest1.get(0, TimeUnit.MILLISECONDS); final ConnectionEndpoint endpoint1 = connRequest1.get(0, TimeUnit.MILLISECONDS);
Assert.assertNotNull(conn1); Assert.assertNotNull(endpoint1);
mgr.releaseConnection(conn1, null, 100, TimeUnit.MILLISECONDS); mgr.release(endpoint1, null, 100, TimeUnit.MILLISECONDS);
mgr.getConnection(route, null); mgr.getConnection(route, null);
mgr.getConnection(route, null); mgr.getConnection(route, null);
@ -377,12 +349,11 @@ public void testTargetConnect() throws Exception {
final InetAddress local = InetAddress.getByAddress(new byte[] {127, 0, 0, 1}); final InetAddress local = InetAddress.getByAddress(new byte[] {127, 0, 0, 1});
final HttpRoute route = new HttpRoute(target, local, true); final HttpRoute route = new HttpRoute(target, local, true);
Mockito.when(connFactory.create( Mockito.when(connFactory.createConnection(Mockito.<Socket>any())).thenReturn(conn);
Mockito.eq(route), Mockito.<ConnectionConfig>any())).thenReturn(conn);
final ConnectionRequest connRequest1 = mgr.requestConnection(route, null); final LeaseRequest connRequest1 = mgr.lease(route, null);
final HttpClientConnection conn1 = connRequest1.get(0, TimeUnit.MILLISECONDS); final ConnectionEndpoint endpoint1 = connRequest1.get(0, TimeUnit.MILLISECONDS);
Assert.assertNotNull(conn1); Assert.assertNotNull(endpoint1);
final HttpClientContext context = HttpClientContext.create(); final HttpClientContext context = HttpClientContext.create();
final SocketConfig sconfig = SocketConfig.custom().build(); final SocketConfig sconfig = SocketConfig.custom().build();
@ -401,7 +372,7 @@ public void testTargetConnect() throws Exception {
Mockito.<InetSocketAddress>any(), Mockito.<InetSocketAddress>any(),
Mockito.<HttpContext>any())).thenReturn(socket); Mockito.<HttpContext>any())).thenReturn(socket);
mgr.connect(conn1, route, 123, context); mgr.connect(endpoint1, 123, TimeUnit.MILLISECONDS, context);
Mockito.verify(dnsResolver, Mockito.times(1)).resolve("somehost"); Mockito.verify(dnsResolver, Mockito.times(1)).resolve("somehost");
Mockito.verify(schemePortResolver, Mockito.times(1)).resolve(target); Mockito.verify(schemePortResolver, Mockito.times(1)).resolve(target);
@ -409,8 +380,6 @@ public void testTargetConnect() throws Exception {
Mockito.verify(plainSocketFactory, Mockito.times(1)).connectSocket(123, socket, target, Mockito.verify(plainSocketFactory, Mockito.times(1)).connectSocket(123, socket, target,
new InetSocketAddress(remote, 8443), new InetSocketAddress(remote, 8443),
new InetSocketAddress(local, 0), context); new InetSocketAddress(local, 0), context);
mgr.routeComplete(conn1, route, context);
} }
@Test @Test
@ -421,12 +390,11 @@ public void testProxyConnectAndUpgrade() throws Exception {
final InetAddress local = InetAddress.getByAddress(new byte[] {127, 0, 0, 1}); final InetAddress local = InetAddress.getByAddress(new byte[] {127, 0, 0, 1});
final HttpRoute route = new HttpRoute(target, local, proxy, true); final HttpRoute route = new HttpRoute(target, local, proxy, true);
Mockito.when(connFactory.create( Mockito.when(connFactory.createConnection(Mockito.<Socket>any())).thenReturn(conn);
Mockito.eq(route), Mockito.<ConnectionConfig>any())).thenReturn(conn);
final ConnectionRequest connRequest1 = mgr.requestConnection(route, null); final LeaseRequest connRequest1 = mgr.lease(route, null);
final HttpClientConnection conn1 = connRequest1.get(0, TimeUnit.MILLISECONDS); final ConnectionEndpoint endpoint1 = connRequest1.get(0, TimeUnit.MILLISECONDS);
Assert.assertNotNull(conn1); Assert.assertNotNull(endpoint1);
final HttpClientContext context = HttpClientContext.create(); final HttpClientContext context = HttpClientContext.create();
final SocketConfig sconfig = SocketConfig.custom().build(); final SocketConfig sconfig = SocketConfig.custom().build();
@ -447,7 +415,7 @@ public void testProxyConnectAndUpgrade() throws Exception {
Mockito.<InetSocketAddress>any(), Mockito.<InetSocketAddress>any(),
Mockito.<HttpContext>any())).thenReturn(socket); Mockito.<HttpContext>any())).thenReturn(socket);
mgr.connect(conn1, route, 123, context); mgr.connect(endpoint1, 123, TimeUnit.MILLISECONDS, context);
Mockito.verify(dnsResolver, Mockito.times(1)).resolve("someproxy"); Mockito.verify(dnsResolver, Mockito.times(1)).resolve("someproxy");
Mockito.verify(schemePortResolver, Mockito.times(1)).resolve(proxy); Mockito.verify(schemePortResolver, Mockito.times(1)).resolve(proxy);
@ -458,13 +426,11 @@ public void testProxyConnectAndUpgrade() throws Exception {
Mockito.when(conn.getSocket()).thenReturn(socket); Mockito.when(conn.getSocket()).thenReturn(socket);
mgr.upgrade(conn1, route, context); mgr.upgrade(endpoint1, context);
Mockito.verify(schemePortResolver, Mockito.times(1)).resolve(target); Mockito.verify(schemePortResolver, Mockito.times(1)).resolve(target);
Mockito.verify(sslSocketFactory, Mockito.times(1)).createLayeredSocket( Mockito.verify(sslSocketFactory, Mockito.times(1)).createLayeredSocket(
socket, "somehost", 8443, context); socket, "somehost", 8443, context);
mgr.routeComplete(conn1, route, context);
} }
} }

View File

@ -1,115 +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
* <http://www.apache.org/>.
*
*/
package org.apache.hc.client5.http.impl.io;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpResponse;
import org.apache.hc.core5.http.HttpVersion;
import org.apache.hc.core5.http.MessageConstraintException;
import org.apache.hc.core5.http.NoHttpResponseException;
import org.apache.hc.core5.http.config.H1Config;
import org.apache.hc.core5.http.impl.io.SessionInputBufferImpl;
import org.apache.hc.core5.http.io.HttpMessageParser;
import org.apache.hc.core5.http.io.SessionInputBuffer;
import org.junit.Assert;
import org.junit.Test;
/**
* Tests for {@link LenientHttpResponseParser}.
*/
public class TestDefaultHttpResponseParser {
@Test
public void testResponseParsingWithSomeGarbage() throws Exception {
final String s =
"garbage\r\n" +
"garbage\r\n" +
"more garbage\r\n" +
"HTTP/1.1 200 OK\r\n" +
"header1: value1\r\n" +
"header2: value2\r\n" +
"\r\n";
final SessionInputBuffer inbuffer = new SessionInputBufferImpl(1024);
final H1Config h1Config = H1Config.custom().setMaxEmptyLineCount(Integer.MAX_VALUE).build();
final HttpMessageParser<ClassicHttpResponse> parser = new LenientHttpResponseParser(h1Config);
final HttpResponse response = parser.parse(inbuffer,
new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII)));
Assert.assertNotNull(response);
Assert.assertEquals(HttpVersion.HTTP_1_1, response.getVersion());
Assert.assertEquals(200, response.getCode());
final Header[] headers = response.getAllHeaders();
Assert.assertNotNull(headers);
Assert.assertEquals(2, headers.length);
Assert.assertEquals("header1", headers[0].getName());
Assert.assertEquals("header2", headers[1].getName());
}
@Test(expected=MessageConstraintException.class)
public void testResponseParsingWithTooMuchGarbage() throws Exception {
final String s =
"garbage\r\n" +
"garbage\r\n" +
"more garbage\r\n" +
"HTTP/1.1 200 OK\r\n" +
"header1: value1\r\n" +
"header2: value2\r\n" +
"\r\n";
final SessionInputBuffer inbuffer = new SessionInputBufferImpl(1024);
final H1Config h1Config = H1Config.custom().setMaxEmptyLineCount(2).build();
final HttpMessageParser<ClassicHttpResponse> parser = new LenientHttpResponseParser(h1Config);
parser.parse(inbuffer, new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII)));
}
@Test(expected=NoHttpResponseException.class)
public void testResponseParsingNoResponse() throws Exception {
final SessionInputBuffer inbuffer = new SessionInputBufferImpl(1024);
final HttpMessageParser<ClassicHttpResponse> parser = new LenientHttpResponseParser(H1Config.DEFAULT);
parser.parse(inbuffer, new ByteArrayInputStream(new byte[]{}));
}
@Test(expected=NoHttpResponseException.class)
public void testResponseParsingOnlyGarbage() throws Exception {
final String s =
"garbage\r\n" +
"garbage\r\n" +
"more garbage\r\n" +
"a lot more garbage\r\n";
final SessionInputBuffer inbuffer = new SessionInputBufferImpl(1024);
final HttpMessageParser<ClassicHttpResponse> parser = new LenientHttpResponseParser(H1Config.DEFAULT);
parser.parse(inbuffer, new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII)));
}
}

View File

@ -243,6 +243,8 @@ public void testUpgrade() throws Exception {
final HttpContext context = new BasicHttpContext(); final HttpContext context = new BasicHttpContext();
final HttpHost host = new HttpHost("somehost", -1, "https"); final HttpHost host = new HttpHost("somehost", -1, "https");
Mockito.when(conn.isOpen()).thenReturn(true);
Mockito.when(conn.getSocket()).thenReturn(socket);
Mockito.when(socketFactoryRegistry.lookup("https")).thenReturn(sslSocketFactory); Mockito.when(socketFactoryRegistry.lookup("https")).thenReturn(sslSocketFactory);
Mockito.when(schemePortResolver.resolve(host)).thenReturn(443); Mockito.when(schemePortResolver.resolve(host)).thenReturn(443);
Mockito.when(sslSocketFactory.createSocket(Mockito.<HttpContext>any())).thenReturn(socket); Mockito.when(sslSocketFactory.createSocket(Mockito.<HttpContext>any())).thenReturn(socket);

View File

@ -34,20 +34,18 @@
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import org.apache.hc.client5.http.ConnectionPoolTimeoutException;
import org.apache.hc.client5.http.DnsResolver; import org.apache.hc.client5.http.DnsResolver;
import org.apache.hc.client5.http.HttpRoute; import org.apache.hc.client5.http.HttpRoute;
import org.apache.hc.client5.http.SchemePortResolver; import org.apache.hc.client5.http.SchemePortResolver;
import org.apache.hc.client5.http.io.ConnectionRequest; import org.apache.hc.client5.http.io.ConnectionEndpoint;
import org.apache.hc.client5.http.io.LeaseRequest;
import org.apache.hc.client5.http.io.ManagedHttpClientConnection; import org.apache.hc.client5.http.io.ManagedHttpClientConnection;
import org.apache.hc.client5.http.protocol.HttpClientContext; import org.apache.hc.client5.http.protocol.HttpClientContext;
import org.apache.hc.client5.http.socket.ConnectionSocketFactory; import org.apache.hc.client5.http.socket.ConnectionSocketFactory;
import org.apache.hc.client5.http.socket.LayeredConnectionSocketFactory; import org.apache.hc.client5.http.socket.LayeredConnectionSocketFactory;
import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.config.ConnectionConfig;
import org.apache.hc.core5.http.config.Lookup; import org.apache.hc.core5.http.config.Lookup;
import org.apache.hc.core5.http.config.SocketConfig; import org.apache.hc.core5.http.config.SocketConfig;
import org.apache.hc.core5.http.io.HttpClientConnection;
import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.protocol.HttpContext;
import org.apache.hc.core5.pool.PoolEntry; import org.apache.hc.core5.pool.PoolEntry;
import org.apache.hc.core5.pool.StrictConnPool; import org.apache.hc.core5.pool.StrictConnPool;
@ -109,14 +107,12 @@ public void testLeaseRelease() throws Exception {
Mockito.when(future.get(1, TimeUnit.SECONDS)).thenReturn(entry); Mockito.when(future.get(1, TimeUnit.SECONDS)).thenReturn(entry);
Mockito.when(pool.lease(route, null, null)).thenReturn(future); Mockito.when(pool.lease(route, null, null)).thenReturn(future);
final ConnectionRequest connRequest1 = mgr.requestConnection(route, null); final LeaseRequest connRequest1 = mgr.lease(route, null);
final HttpClientConnection conn1 = connRequest1.get(1, TimeUnit.SECONDS); final ConnectionEndpoint endpoint1 = connRequest1.get(1, TimeUnit.SECONDS);
Assert.assertNotNull(conn1); Assert.assertNotNull(endpoint1);
Assert.assertNotSame(conn, conn1); Assert.assertNotSame(conn, endpoint1);
final CPoolProxy poolProxy = CPoolProxy.getProxy(conn1);
poolProxy.markRouteComplete();
mgr.releaseConnection(conn1, null, 0, TimeUnit.MILLISECONDS); mgr.release(endpoint1, null, 0, TimeUnit.MILLISECONDS);
Mockito.verify(pool).release(entry, true); Mockito.verify(pool).release(entry, true);
} }
@ -138,12 +134,12 @@ public void testReleaseRouteIncomplete() throws Exception {
Mockito.when(future.get(1, TimeUnit.SECONDS)).thenReturn(entry); Mockito.when(future.get(1, TimeUnit.SECONDS)).thenReturn(entry);
Mockito.when(pool.lease(route, null, null)).thenReturn(future); Mockito.when(pool.lease(route, null, null)).thenReturn(future);
final ConnectionRequest connRequest1 = mgr.requestConnection(route, null); final LeaseRequest connRequest1 = mgr.lease(route, null);
final HttpClientConnection conn1 = connRequest1.get(1, TimeUnit.SECONDS); final ConnectionEndpoint endpoint1 = connRequest1.get(1, TimeUnit.SECONDS);
Assert.assertNotNull(conn1); Assert.assertNotNull(endpoint1);
Assert.assertNotSame(conn, conn1); Assert.assertNotSame(conn, endpoint1);
mgr.releaseConnection(conn1, null, 0, TimeUnit.MILLISECONDS); mgr.release(endpoint1, null, 0, TimeUnit.MILLISECONDS);
Mockito.verify(pool).release(entry, false); Mockito.verify(pool).release(entry, false);
} }
@ -160,11 +156,11 @@ public void testLeaseFutureCancelled() throws Exception {
Mockito.when(future.get(1, TimeUnit.SECONDS)).thenReturn(entry); Mockito.when(future.get(1, TimeUnit.SECONDS)).thenReturn(entry);
Mockito.when(pool.lease(route, null, null)).thenReturn(future); Mockito.when(pool.lease(route, null, null)).thenReturn(future);
final ConnectionRequest connRequest1 = mgr.requestConnection(route, null); final LeaseRequest connRequest1 = mgr.lease(route, null);
connRequest1.get(1, TimeUnit.SECONDS); connRequest1.get(1, TimeUnit.SECONDS);
} }
@Test(expected=ConnectionPoolTimeoutException.class) @Test(expected=TimeoutException.class)
public void testLeaseFutureTimeout() throws Exception { public void testLeaseFutureTimeout() throws Exception {
final HttpHost target = new HttpHost("localhost", 80); final HttpHost target = new HttpHost("localhost", 80);
final HttpRoute route = new HttpRoute(target); final HttpRoute route = new HttpRoute(target);
@ -173,7 +169,7 @@ public void testLeaseFutureTimeout() throws Exception {
Mockito.when(future.get(1, TimeUnit.SECONDS)).thenThrow(new TimeoutException()); Mockito.when(future.get(1, TimeUnit.SECONDS)).thenThrow(new TimeoutException());
Mockito.when(pool.lease(route, null, null)).thenReturn(future); Mockito.when(pool.lease(route, null, null)).thenReturn(future);
final ConnectionRequest connRequest1 = mgr.requestConnection(route, null); final LeaseRequest connRequest1 = mgr.lease(route, null);
connRequest1.get(1, TimeUnit.SECONDS); connRequest1.get(1, TimeUnit.SECONDS);
} }
@ -190,14 +186,12 @@ public void testReleaseReusable() throws Exception {
Mockito.when(pool.lease(route, null, null)).thenReturn(future); Mockito.when(pool.lease(route, null, null)).thenReturn(future);
Mockito.when(conn.isOpen()).thenReturn(Boolean.TRUE); Mockito.when(conn.isOpen()).thenReturn(Boolean.TRUE);
final ConnectionRequest connRequest1 = mgr.requestConnection(route, null); final LeaseRequest connRequest1 = mgr.lease(route, null);
final HttpClientConnection conn1 = connRequest1.get(1, TimeUnit.SECONDS); final ConnectionEndpoint endpoint1 = connRequest1.get(1, TimeUnit.SECONDS);
Assert.assertNotNull(conn1); Assert.assertNotNull(endpoint1);
Assert.assertTrue(conn1.isOpen()); Assert.assertTrue(endpoint1.isConnected());
final CPoolProxy poolProxy = CPoolProxy.getProxy(conn1);
poolProxy.markRouteComplete();
mgr.releaseConnection(conn1, "some state", 10, TimeUnit.MILLISECONDS); mgr.release(endpoint1, "some state", 10, TimeUnit.MILLISECONDS);
Mockito.verify(pool).release(entry, true); Mockito.verify(pool).release(entry, true);
Assert.assertEquals("some state", entry.getState()); Assert.assertEquals("some state", entry.getState());
@ -216,14 +210,12 @@ public void testReleaseNonReusable() throws Exception {
Mockito.when(pool.lease(route, null, null)).thenReturn(future); Mockito.when(pool.lease(route, null, null)).thenReturn(future);
Mockito.when(conn.isOpen()).thenReturn(Boolean.FALSE); Mockito.when(conn.isOpen()).thenReturn(Boolean.FALSE);
final ConnectionRequest connRequest1 = mgr.requestConnection(route, null); final LeaseRequest connRequest1 = mgr.lease(route, null);
final HttpClientConnection conn1 = connRequest1.get(1, TimeUnit.SECONDS); final ConnectionEndpoint endpoint1 = connRequest1.get(1, TimeUnit.SECONDS);
Assert.assertNotNull(conn1); Assert.assertNotNull(endpoint1);
Assert.assertFalse(conn1.isOpen()); Assert.assertFalse(endpoint1.isConnected());
final CPoolProxy poolProxy = CPoolProxy.getProxy(conn1);
poolProxy.markRouteComplete();
mgr.releaseConnection(conn1, "some state", 0, TimeUnit.MILLISECONDS); mgr.release(endpoint1, "some state", 0, TimeUnit.MILLISECONDS);
Mockito.verify(pool).release(entry, false); Mockito.verify(pool).release(entry, false);
Assert.assertEquals(null, entry.getState()); Assert.assertEquals(null, entry.getState());
@ -240,16 +232,14 @@ public void testTargetConnect() throws Exception {
entry.assignConnection(conn); entry.assignConnection(conn);
Mockito.when(future.isCancelled()).thenReturn(Boolean.FALSE); Mockito.when(future.isCancelled()).thenReturn(Boolean.FALSE);
Mockito.when(conn.isOpen()).thenReturn(true); Mockito.when(conn.isOpen()).thenReturn(false);
Mockito.when(future.isCancelled()).thenReturn(false); Mockito.when(future.isCancelled()).thenReturn(false);
Mockito.when(future.get(1, TimeUnit.SECONDS)).thenReturn(entry); Mockito.when(future.get(1, TimeUnit.SECONDS)).thenReturn(entry);
Mockito.when(pool.lease(route, null, null)).thenReturn(future); Mockito.when(pool.lease(route, null, null)).thenReturn(future);
final ConnectionRequest connRequest1 = mgr.requestConnection(route, null); final LeaseRequest connRequest1 = mgr.lease(route, null);
final HttpClientConnection conn1 = connRequest1.get(1, TimeUnit.SECONDS); final ConnectionEndpoint endpoint1 = connRequest1.get(1, TimeUnit.SECONDS);
Assert.assertNotNull(conn1); Assert.assertNotNull(endpoint1);
final CPoolProxy poolProxy = CPoolProxy.getProxy(conn1);
poolProxy.markRouteComplete();
final HttpClientContext context = HttpClientContext.create(); final HttpClientContext context = HttpClientContext.create();
final SocketConfig sconfig = SocketConfig.custom().build(); final SocketConfig sconfig = SocketConfig.custom().build();
@ -268,7 +258,7 @@ public void testTargetConnect() throws Exception {
Mockito.<InetSocketAddress>any(), Mockito.<InetSocketAddress>any(),
Mockito.<HttpContext>any())).thenReturn(socket); Mockito.<HttpContext>any())).thenReturn(socket);
mgr.connect(conn1, route, 123, context); mgr.connect(endpoint1, 123, TimeUnit.MILLISECONDS, context);
Mockito.verify(dnsResolver, Mockito.times(1)).resolve("somehost"); Mockito.verify(dnsResolver, Mockito.times(1)).resolve("somehost");
Mockito.verify(schemePortResolver, Mockito.times(1)).resolve(target); Mockito.verify(schemePortResolver, Mockito.times(1)).resolve(target);
@ -276,8 +266,6 @@ public void testTargetConnect() throws Exception {
Mockito.verify(plainSocketFactory, Mockito.times(1)).connectSocket(123, socket, target, Mockito.verify(plainSocketFactory, Mockito.times(1)).connectSocket(123, socket, target,
new InetSocketAddress(remote, 8443), new InetSocketAddress(remote, 8443),
new InetSocketAddress(local, 0), context); new InetSocketAddress(local, 0), context);
mgr.routeComplete(conn1, route, context);
} }
@Test @Test
@ -292,24 +280,22 @@ public void testProxyConnectAndUpgrade() throws Exception {
entry.assignConnection(conn); entry.assignConnection(conn);
Mockito.when(future.isCancelled()).thenReturn(Boolean.FALSE); Mockito.when(future.isCancelled()).thenReturn(Boolean.FALSE);
Mockito.when(conn.isOpen()).thenReturn(true); Mockito.when(conn.isOpen()).thenReturn(false);
Mockito.when(future.isCancelled()).thenReturn(false); Mockito.when(future.isCancelled()).thenReturn(false);
Mockito.when(future.get(1, TimeUnit.SECONDS)).thenReturn(entry); Mockito.when(future.get(1, TimeUnit.SECONDS)).thenReturn(entry);
Mockito.when(pool.lease(route, null, null)).thenReturn(future); Mockito.when(pool.lease(route, null, null)).thenReturn(future);
final ConnectionRequest connRequest1 = mgr.requestConnection(route, null); final LeaseRequest connRequest1 = mgr.lease(route, null);
final HttpClientConnection conn1 = connRequest1.get(1, TimeUnit.SECONDS); final ConnectionEndpoint endpoint1 = connRequest1.get(1, TimeUnit.SECONDS);
Assert.assertNotNull(conn1); Assert.assertNotNull(endpoint1);
final ConnectionSocketFactory plainsf = Mockito.mock(ConnectionSocketFactory.class); final ConnectionSocketFactory plainsf = Mockito.mock(ConnectionSocketFactory.class);
final LayeredConnectionSocketFactory sslsf = Mockito.mock(LayeredConnectionSocketFactory.class); final LayeredConnectionSocketFactory sslsf = Mockito.mock(LayeredConnectionSocketFactory.class);
final Socket mockSock = Mockito.mock(Socket.class); final Socket mockSock = Mockito.mock(Socket.class);
final HttpClientContext context = HttpClientContext.create(); final HttpClientContext context = HttpClientContext.create();
final SocketConfig sconfig = SocketConfig.custom().build(); final SocketConfig sconfig = SocketConfig.custom().build();
final ConnectionConfig cconfig = ConnectionConfig.custom().build();
mgr.setDefaultSocketConfig(sconfig); mgr.setDefaultSocketConfig(sconfig);
mgr.setDefaultConnectionConfig(cconfig);
Mockito.when(dnsResolver.resolve("someproxy")).thenReturn(new InetAddress[] {remote}); Mockito.when(dnsResolver.resolve("someproxy")).thenReturn(new InetAddress[] {remote});
Mockito.when(schemePortResolver.resolve(proxy)).thenReturn(8080); Mockito.when(schemePortResolver.resolve(proxy)).thenReturn(8080);
@ -325,7 +311,7 @@ public void testProxyConnectAndUpgrade() throws Exception {
Mockito.<InetSocketAddress>any(), Mockito.<InetSocketAddress>any(),
Mockito.<HttpContext>any())).thenReturn(mockSock); Mockito.<HttpContext>any())).thenReturn(mockSock);
mgr.connect(conn1, route, 123, context); mgr.connect(endpoint1, 123, TimeUnit.MILLISECONDS, context);
Mockito.verify(dnsResolver, Mockito.times(1)).resolve("someproxy"); Mockito.verify(dnsResolver, Mockito.times(1)).resolve("someproxy");
Mockito.verify(schemePortResolver, Mockito.times(1)).resolve(proxy); Mockito.verify(schemePortResolver, Mockito.times(1)).resolve(proxy);
@ -334,15 +320,14 @@ public void testProxyConnectAndUpgrade() throws Exception {
new InetSocketAddress(remote, 8080), new InetSocketAddress(remote, 8080),
new InetSocketAddress(local, 0), context); new InetSocketAddress(local, 0), context);
Mockito.when(conn.isOpen()).thenReturn(true);
Mockito.when(conn.getSocket()).thenReturn(mockSock); Mockito.when(conn.getSocket()).thenReturn(mockSock);
mgr.upgrade(conn1, route, context); mgr.upgrade(endpoint1, context);
Mockito.verify(schemePortResolver, Mockito.times(1)).resolve(target); Mockito.verify(schemePortResolver, Mockito.times(1)).resolve(target);
Mockito.verify(sslsf, Mockito.times(1)).createLayeredSocket( Mockito.verify(sslsf, Mockito.times(1)).createLayeredSocket(
mockSock, "somehost", 8443, context); mockSock, "somehost", 8443, context);
mgr.routeComplete(conn1, route, context);
} }
} }

View File

@ -61,10 +61,9 @@ public void setup() {
@Test @Test
public void testDefaultProxyDirect() throws Exception { public void testDefaultProxyDirect() throws Exception {
final HttpHost target = new HttpHost("somehost", 80, "http"); final HttpHost target = new HttpHost("somehost", 80, "http");
final HttpRequest request = new BasicHttpRequest("GET", "/");
final HttpContext context = new BasicHttpContext(); final HttpContext context = new BasicHttpContext();
final HttpRoute route = routePlanner.determineRoute(target, request, context); final HttpRoute route = routePlanner.determineRoute(target, context);
Assert.assertEquals(target, route.getTargetHost()); Assert.assertEquals(target, route.getTargetHost());
Assert.assertEquals(defaultProxy, route.getProxyHost()); Assert.assertEquals(defaultProxy, route.getProxyHost());
@ -80,7 +79,7 @@ public void testViaProxy() throws Exception {
final HttpClientContext context = HttpClientContext.create(); final HttpClientContext context = HttpClientContext.create();
context.setRequestConfig(RequestConfig.custom().setProxy(proxy).build()); context.setRequestConfig(RequestConfig.custom().setProxy(proxy).build());
final HttpRoute route = routePlanner.determineRoute(target, request, context); final HttpRoute route = routePlanner.determineRoute(target, context);
Assert.assertEquals(target, route.getTargetHost()); Assert.assertEquals(target, route.getTargetHost());
Assert.assertEquals(proxy, route.getProxyHost()); Assert.assertEquals(proxy, route.getProxyHost());

View File

@ -27,6 +27,8 @@
package org.apache.hc.client5.http.impl.routing; package org.apache.hc.client5.http.impl.routing;
import java.net.URI;
import org.apache.hc.client5.http.HttpRoute; import org.apache.hc.client5.http.HttpRoute;
import org.apache.hc.client5.http.SchemePortResolver; import org.apache.hc.client5.http.SchemePortResolver;
import org.apache.hc.client5.http.config.RequestConfig; import org.apache.hc.client5.http.config.RequestConfig;
@ -37,6 +39,8 @@
import org.apache.hc.core5.http.message.BasicHttpRequest; import org.apache.hc.core5.http.message.BasicHttpRequest;
import org.apache.hc.core5.http.protocol.BasicHttpContext; import org.apache.hc.core5.http.protocol.BasicHttpContext;
import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.protocol.HttpContext;
import org.apache.hc.core5.net.URIAuthority;
import org.hamcrest.CoreMatchers;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -60,10 +64,9 @@ public void setup() {
@Test @Test
public void testDirect() throws Exception { public void testDirect() throws Exception {
final HttpHost target = new HttpHost("somehost", 80, "http"); final HttpHost target = new HttpHost("somehost", 80, "http");
final HttpRequest request = new BasicHttpRequest("GET", "/");
final HttpContext context = new BasicHttpContext(); final HttpContext context = new BasicHttpContext();
final HttpRoute route = routePlanner.determineRoute(target, request, context); final HttpRoute route = routePlanner.determineRoute(target, context);
Assert.assertEquals(target, route.getTargetHost()); Assert.assertEquals(target, route.getTargetHost());
Assert.assertEquals(1, route.getHopCount()); Assert.assertEquals(1, route.getHopCount());
@ -75,10 +78,9 @@ public void testDirect() throws Exception {
public void testDirectDefaultPort() throws Exception { public void testDirectDefaultPort() throws Exception {
final HttpHost target = new HttpHost("somehost", -1, "https"); final HttpHost target = new HttpHost("somehost", -1, "https");
Mockito.when(schemePortResolver.resolve(target)).thenReturn(443); Mockito.when(schemePortResolver.resolve(target)).thenReturn(443);
final HttpRequest request = new BasicHttpRequest("GET", "/");
final HttpContext context = new BasicHttpContext(); final HttpContext context = new BasicHttpContext();
final HttpRoute route = routePlanner.determineRoute(target, request, context); final HttpRoute route = routePlanner.determineRoute(target, context);
Assert.assertEquals(new HttpHost("somehost", 443, "https"), route.getTargetHost()); Assert.assertEquals(new HttpHost("somehost", 443, "https"), route.getTargetHost());
Assert.assertEquals(1, route.getHopCount()); Assert.assertEquals(1, route.getHopCount());
@ -89,11 +91,10 @@ public void testDirectDefaultPort() throws Exception {
public void testViaProxy() throws Exception { public void testViaProxy() throws Exception {
final HttpHost target = new HttpHost("somehost", 80, "http"); final HttpHost target = new HttpHost("somehost", 80, "http");
final HttpHost proxy = new HttpHost("proxy", 8080); final HttpHost proxy = new HttpHost("proxy", 8080);
final HttpRequest request = new BasicHttpRequest("GET", "/");
final HttpClientContext context = HttpClientContext.create(); final HttpClientContext context = HttpClientContext.create();
context.setRequestConfig(RequestConfig.custom().setProxy(proxy).build()); context.setRequestConfig(RequestConfig.custom().setProxy(proxy).build());
final HttpRoute route = routePlanner.determineRoute(target, request, context); final HttpRoute route = routePlanner.determineRoute(target, context);
Assert.assertEquals(target, route.getTargetHost()); Assert.assertEquals(target, route.getTargetHost());
Assert.assertEquals(proxy, route.getProxyHost()); Assert.assertEquals(proxy, route.getProxyHost());
@ -104,10 +105,28 @@ public void testViaProxy() throws Exception {
@Test(expected= ProtocolException.class) @Test(expected= ProtocolException.class)
public void testNullTarget() throws Exception { public void testNullTarget() throws Exception {
final HttpRequest request = new BasicHttpRequest("GET", "/");
final HttpContext context = new BasicHttpContext(); final HttpContext context = new BasicHttpContext();
routePlanner.determineRoute(null, request, context); routePlanner.determineRoute(null, context);
}
@Test
public void testDetermineHost() throws Exception {
final HttpContext context = new BasicHttpContext();
final HttpRequest request1 = new BasicHttpRequest("GET", "/");
final HttpHost host1 = routePlanner.determineTargetHost(request1, context);
Assert.assertThat(host1, CoreMatchers.nullValue());
final HttpRequest request2 = new BasicHttpRequest("GET", new URI("https://somehost:8443/"));
final HttpHost host2 = routePlanner.determineTargetHost(request2, context);
Assert.assertThat(host2, CoreMatchers.equalTo(new HttpHost("somehost", 8443, "https")));
}
@Test(expected = ProtocolException.class)
public void testDetermineHostMissingScheme() throws Exception {
final HttpContext context = new BasicHttpContext();
final HttpRequest request1 = new BasicHttpRequest("GET", "/");
request1.setAuthority(new URIAuthority("host"));
routePlanner.determineTargetHost(request1, context);
} }
} }

View File

@ -38,8 +38,6 @@
import org.apache.hc.client5.http.HttpRoute; import org.apache.hc.client5.http.HttpRoute;
import org.apache.hc.client5.http.SchemePortResolver; import org.apache.hc.client5.http.SchemePortResolver;
import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.message.BasicHttpRequest;
import org.apache.hc.core5.http.protocol.BasicHttpContext; import org.apache.hc.core5.http.protocol.BasicHttpContext;
import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.protocol.HttpContext;
import org.junit.Assert; import org.junit.Assert;
@ -68,10 +66,9 @@ public void setup() {
@Test @Test
public void testDirect() throws Exception { public void testDirect() throws Exception {
final HttpHost target = new HttpHost("somehost", 80, "http"); final HttpHost target = new HttpHost("somehost", 80, "http");
final HttpRequest request = new BasicHttpRequest("GET", "/");
final HttpContext context = new BasicHttpContext(); final HttpContext context = new BasicHttpContext();
final HttpRoute route = routePlanner.determineRoute(target, request, context); final HttpRoute route = routePlanner.determineRoute(target, context);
Assert.assertEquals(target, route.getTargetHost()); Assert.assertEquals(target, route.getTargetHost());
Assert.assertEquals(1, route.getHopCount()); Assert.assertEquals(1, route.getHopCount());
@ -83,10 +80,9 @@ public void testDirect() throws Exception {
public void testDirectDefaultPort() throws Exception { public void testDirectDefaultPort() throws Exception {
final HttpHost target = new HttpHost("somehost", -1, "https"); final HttpHost target = new HttpHost("somehost", -1, "https");
Mockito.when(schemePortResolver.resolve(target)).thenReturn(443); Mockito.when(schemePortResolver.resolve(target)).thenReturn(443);
final HttpRequest request = new BasicHttpRequest("GET", "/");
final HttpContext context = new BasicHttpContext(); final HttpContext context = new BasicHttpContext();
final HttpRoute route = routePlanner.determineRoute(target, request, context); final HttpRoute route = routePlanner.determineRoute(target, context);
Assert.assertEquals(new HttpHost("somehost", 443, "https"), route.getTargetHost()); Assert.assertEquals(new HttpHost("somehost", 443, "https"), route.getTargetHost());
Assert.assertEquals(1, route.getHopCount()); Assert.assertEquals(1, route.getHopCount());
@ -109,11 +105,9 @@ public void testProxy() throws Exception {
Mockito.when(proxySelector.select(new URI("http://somehost:80"))).thenReturn(proxies); Mockito.when(proxySelector.select(new URI("http://somehost:80"))).thenReturn(proxies);
final HttpHost target = new HttpHost("somehost", 80, "http"); final HttpHost target = new HttpHost("somehost", 80, "http");
final HttpRequest request =
new BasicHttpRequest("GET", "/");
final HttpContext context = new BasicHttpContext(); final HttpContext context = new BasicHttpContext();
final HttpRoute route = routePlanner.determineRoute(target, request, context); final HttpRoute route = routePlanner.determineRoute(target, context);
Assert.assertEquals(target, route.getTargetHost()); Assert.assertEquals(target, route.getTargetHost());
Assert.assertEquals(2, route.getHopCount()); Assert.assertEquals(2, route.getHopCount());

View File

@ -32,8 +32,7 @@
import java.net.ConnectException; import java.net.ConnectException;
import java.net.SocketTimeoutException; import java.net.SocketTimeoutException;
import org.apache.hc.client5.http.ConnectionPoolTimeoutException; import org.apache.hc.core5.http.ConnectionRequestTimeoutException;
import org.apache.hc.client5.http.sync.ConnectionBackoffStrategy;
import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpResponse;
import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.HttpStatus;
import org.apache.hc.core5.http.message.BasicHttpResponse; import org.apache.hc.core5.http.message.BasicHttpResponse;
@ -50,11 +49,6 @@ public void setUp() {
impl = new DefaultBackoffStrategy(); impl = new DefaultBackoffStrategy();
} }
@Test
public void isABackoffStrategy() {
assertTrue(impl instanceof ConnectionBackoffStrategy);
}
@Test @Test
public void backsOffForSocketTimeouts() { public void backsOffForSocketTimeouts() {
assertTrue(impl.shouldBackoff(new SocketTimeoutException())); assertTrue(impl.shouldBackoff(new SocketTimeoutException()));
@ -67,7 +61,7 @@ public void backsOffForConnectionTimeouts() {
@Test @Test
public void doesNotBackOffForConnectionManagerTimeout() { public void doesNotBackOffForConnectionManagerTimeout() {
assertFalse(impl.shouldBackoff(new ConnectionPoolTimeoutException())); assertFalse(impl.shouldBackoff(new ConnectionRequestTimeoutException()));
} }
@Test @Test

View File

@ -29,28 +29,32 @@
import java.io.IOException; import java.io.IOException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.apache.hc.client5.http.io.ConnectionEndpoint;
import org.apache.hc.client5.http.io.HttpClientConnectionManager; import org.apache.hc.client5.http.io.HttpClientConnectionManager;
import org.apache.hc.core5.http.io.HttpClientConnection;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.mockito.Mock;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
@SuppressWarnings({"static-access"}) // test code @SuppressWarnings({"static-access"}) // test code
public class TestConnectionHolder { public class TestEndpointHolder {
@Mock
private Logger log; private Logger log;
@Mock
private HttpClientConnectionManager mgr; private HttpClientConnectionManager mgr;
private HttpClientConnection conn; @Mock
private ConnectionHolder connHolder; private ConnectionEndpoint endpoint;
private EndpointHolder connHolder;
@Before @Before
public void setup() { public void setup() {
log = Mockito.mock(Logger.class); MockitoAnnotations.initMocks(this);
mgr = Mockito.mock(HttpClientConnectionManager.class); connHolder = new EndpointHolder(log, mgr, endpoint);
conn = Mockito.mock(HttpClientConnection.class);
connHolder = new ConnectionHolder(log, mgr, conn);
} }
@Test @Test
@ -59,14 +63,14 @@ public void testAbortConnection() throws Exception {
Assert.assertTrue(connHolder.isReleased()); Assert.assertTrue(connHolder.isReleased());
Mockito.verify(conn).shutdown(); Mockito.verify(endpoint).shutdown();
Mockito.verify(mgr).releaseConnection(conn, null, 0, TimeUnit.MILLISECONDS); Mockito.verify(mgr).release(endpoint, null, 0, TimeUnit.MILLISECONDS);
connHolder.abortConnection(); connHolder.abortConnection();
Mockito.verify(conn, Mockito.times(1)).shutdown(); Mockito.verify(endpoint, Mockito.times(1)).shutdown();
Mockito.verify(mgr, Mockito.times(1)).releaseConnection( Mockito.verify(mgr, Mockito.times(1)).release(
Mockito.<HttpClientConnection>any(), Mockito.<ConnectionEndpoint>any(),
Mockito.anyObject(), Mockito.anyObject(),
Mockito.anyLong(), Mockito.anyLong(),
Mockito.<TimeUnit>any()); Mockito.<TimeUnit>any());
@ -74,14 +78,14 @@ public void testAbortConnection() throws Exception {
@Test @Test
public void testAbortConnectionIOError() throws Exception { public void testAbortConnectionIOError() throws Exception {
Mockito.doThrow(new IOException()).when(conn).shutdown(); Mockito.doThrow(new IOException()).when(endpoint).shutdown();
connHolder.abortConnection(); connHolder.abortConnection();
Assert.assertTrue(connHolder.isReleased()); Assert.assertTrue(connHolder.isReleased());
Mockito.verify(conn).shutdown(); Mockito.verify(endpoint).shutdown();
Mockito.verify(mgr).releaseConnection(conn, null, 0, TimeUnit.MILLISECONDS); Mockito.verify(mgr).release(endpoint, null, 0, TimeUnit.MILLISECONDS);
} }
@Test @Test
@ -90,14 +94,14 @@ public void testCancell() throws Exception {
Assert.assertTrue(connHolder.isReleased()); Assert.assertTrue(connHolder.isReleased());
Mockito.verify(conn).shutdown(); Mockito.verify(endpoint).shutdown();
Mockito.verify(mgr).releaseConnection(conn, null, 0, TimeUnit.MILLISECONDS); Mockito.verify(mgr).release(endpoint, null, 0, TimeUnit.MILLISECONDS);
Assert.assertFalse(connHolder.cancel()); Assert.assertFalse(connHolder.cancel());
Mockito.verify(conn, Mockito.times(1)).shutdown(); Mockito.verify(endpoint, Mockito.times(1)).shutdown();
Mockito.verify(mgr, Mockito.times(1)).releaseConnection( Mockito.verify(mgr, Mockito.times(1)).release(
Mockito.<HttpClientConnection>any(), Mockito.<ConnectionEndpoint>any(),
Mockito.anyObject(), Mockito.anyObject(),
Mockito.anyLong(), Mockito.anyLong(),
Mockito.<TimeUnit>any()); Mockito.<TimeUnit>any());
@ -113,13 +117,13 @@ public void testReleaseConnectionReusable() throws Exception {
Assert.assertTrue(connHolder.isReleased()); Assert.assertTrue(connHolder.isReleased());
Mockito.verify(conn, Mockito.never()).close(); Mockito.verify(endpoint, Mockito.never()).close();
Mockito.verify(mgr).releaseConnection(conn, "some state", 100, TimeUnit.SECONDS); Mockito.verify(mgr).release(endpoint, "some state", 100000, TimeUnit.MILLISECONDS);
connHolder.releaseConnection(); connHolder.releaseConnection();
Mockito.verify(mgr, Mockito.times(1)).releaseConnection( Mockito.verify(mgr, Mockito.times(1)).release(
Mockito.<HttpClientConnection>any(), Mockito.<ConnectionEndpoint>any(),
Mockito.anyObject(), Mockito.anyObject(),
Mockito.anyLong(), Mockito.anyLong(),
Mockito.<TimeUnit>any()); Mockito.<TimeUnit>any());
@ -135,13 +139,13 @@ public void testReleaseConnectionNonReusable() throws Exception {
Assert.assertTrue(connHolder.isReleased()); Assert.assertTrue(connHolder.isReleased());
Mockito.verify(conn, Mockito.times(1)).close(); Mockito.verify(endpoint, Mockito.times(1)).close();
Mockito.verify(mgr).releaseConnection(conn, null, 0, TimeUnit.MILLISECONDS); Mockito.verify(mgr).release(endpoint, null, 0, TimeUnit.MILLISECONDS);
connHolder.releaseConnection(); connHolder.releaseConnection();
Mockito.verify(mgr, Mockito.times(1)).releaseConnection( Mockito.verify(mgr, Mockito.times(1)).release(
Mockito.<HttpClientConnection>any(), Mockito.<ConnectionEndpoint>any(),
Mockito.anyObject(), Mockito.anyObject(),
Mockito.anyLong(), Mockito.anyLong(),
Mockito.<TimeUnit>any()); Mockito.<TimeUnit>any());

View File

@ -29,7 +29,7 @@
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.apache.hc.client5.http.io.HttpClientConnectionManager; import org.apache.hc.core5.pool.ConnPoolControl;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import org.mockito.Mockito; import org.mockito.Mockito;
@ -41,7 +41,7 @@ public class TestIdleConnectionEvictor {
@Test @Test
public void testEvictExpiredAndIdle() throws Exception { public void testEvictExpiredAndIdle() throws Exception {
final HttpClientConnectionManager cm = Mockito.mock(HttpClientConnectionManager.class); final ConnPoolControl<?> cm = Mockito.mock(ConnPoolControl.class);
final IdleConnectionEvictor connectionEvictor = new IdleConnectionEvictor(cm, final IdleConnectionEvictor connectionEvictor = new IdleConnectionEvictor(cm,
500, TimeUnit.MILLISECONDS, 3, TimeUnit.SECONDS); 500, TimeUnit.MILLISECONDS, 3, TimeUnit.SECONDS);
connectionEvictor.start(); connectionEvictor.start();
@ -60,7 +60,7 @@ public void testEvictExpiredAndIdle() throws Exception {
@Test @Test
public void testEvictExpiredOnly() throws Exception { public void testEvictExpiredOnly() throws Exception {
final HttpClientConnectionManager cm = Mockito.mock(HttpClientConnectionManager.class); final ConnPoolControl<?> cm = Mockito.mock(ConnPoolControl.class);
final IdleConnectionEvictor connectionEvictor = new IdleConnectionEvictor(cm, final IdleConnectionEvictor connectionEvictor = new IdleConnectionEvictor(cm,
500, TimeUnit.MILLISECONDS, 0, TimeUnit.SECONDS); 500, TimeUnit.MILLISECONDS, 0, TimeUnit.SECONDS);
connectionEvictor.start(); connectionEvictor.start();

View File

@ -36,7 +36,6 @@
import org.apache.hc.client5.http.config.RequestConfig; import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.cookie.CookieSpecProvider; import org.apache.hc.client5.http.cookie.CookieSpecProvider;
import org.apache.hc.client5.http.cookie.CookieStore; import org.apache.hc.client5.http.cookie.CookieStore;
import org.apache.hc.client5.http.io.HttpClientConnectionManager;
import org.apache.hc.client5.http.methods.HttpGet; import org.apache.hc.client5.http.methods.HttpGet;
import org.apache.hc.client5.http.methods.RoutedHttpRequest; import org.apache.hc.client5.http.methods.RoutedHttpRequest;
import org.apache.hc.client5.http.protocol.ClientProtocolException; import org.apache.hc.client5.http.protocol.ClientProtocolException;
@ -44,12 +43,10 @@
import org.apache.hc.client5.http.routing.HttpRoutePlanner; import org.apache.hc.client5.http.routing.HttpRoutePlanner;
import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpException;
import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.config.Lookup; import org.apache.hc.core5.http.config.Lookup;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito; import org.mockito.Mockito;
/** /**
@ -59,7 +56,6 @@
public class TestInternalHttpClient { public class TestInternalHttpClient {
private ClientExecChain execChain; private ClientExecChain execChain;
private HttpClientConnectionManager connManager;
private HttpRoutePlanner routePlanner; private HttpRoutePlanner routePlanner;
private Lookup<CookieSpecProvider> cookieSpecRegistry; private Lookup<CookieSpecProvider> cookieSpecRegistry;
private Lookup<AuthSchemeProvider> authSchemeRegistry; private Lookup<AuthSchemeProvider> authSchemeRegistry;
@ -75,7 +71,6 @@ public class TestInternalHttpClient {
@Before @Before
public void setup() throws Exception { public void setup() throws Exception {
execChain = Mockito.mock(ClientExecChain.class); execChain = Mockito.mock(ClientExecChain.class);
connManager = Mockito.mock(HttpClientConnectionManager.class);
routePlanner = Mockito.mock(HttpRoutePlanner.class); routePlanner = Mockito.mock(HttpRoutePlanner.class);
cookieSpecRegistry = Mockito.mock(Lookup.class); cookieSpecRegistry = Mockito.mock(Lookup.class);
authSchemeRegistry = Mockito.mock(Lookup.class); authSchemeRegistry = Mockito.mock(Lookup.class);
@ -85,7 +80,7 @@ public void setup() throws Exception {
closeable1 = Mockito.mock(Closeable.class); closeable1 = Mockito.mock(Closeable.class);
closeable2 = Mockito.mock(Closeable.class); closeable2 = Mockito.mock(Closeable.class);
client = new InternalHttpClient(execChain, connManager, routePlanner, client = new InternalHttpClient(execChain, routePlanner,
cookieSpecRegistry, authSchemeRegistry, cookieStore, credentialsProvider, cookieSpecRegistry, authSchemeRegistry, cookieStore, credentialsProvider,
defaultConfig, Arrays.asList(closeable1, closeable2)); defaultConfig, Arrays.asList(closeable1, closeable2));
@ -96,17 +91,12 @@ public void testExecute() throws Exception {
final HttpGet httpget = new HttpGet("http://somehost/stuff"); final HttpGet httpget = new HttpGet("http://somehost/stuff");
final HttpRoute route = new HttpRoute(new HttpHost("somehost", 80)); final HttpRoute route = new HttpRoute(new HttpHost("somehost", 80));
final ArgumentCaptor<HttpRequest> argcap = ArgumentCaptor.forClass(HttpRequest.class);
Mockito.when(routePlanner.determineRoute( Mockito.when(routePlanner.determineRoute(
Mockito.eq(new HttpHost("somehost")), Mockito.eq(new HttpHost("somehost")),
argcap.capture(),
Mockito.<HttpClientContext>any())).thenReturn(route); Mockito.<HttpClientContext>any())).thenReturn(route);
client.execute(httpget); client.execute(httpget);
Assert.assertNotNull(argcap.getValue());
Assert.assertSame(httpget, argcap.getValue());
Mockito.verify(execChain).execute( Mockito.verify(execChain).execute(
Mockito.<RoutedHttpRequest>any(), Mockito.<RoutedHttpRequest>any(),
Mockito.<HttpClientContext>any(), Mockito.<HttpClientContext>any(),
@ -120,7 +110,6 @@ public void testExecuteHttpException() throws Exception {
Mockito.when(routePlanner.determineRoute( Mockito.when(routePlanner.determineRoute(
Mockito.eq(new HttpHost("somehost")), Mockito.eq(new HttpHost("somehost")),
Mockito.<RoutedHttpRequest>any(),
Mockito.<HttpClientContext>any())).thenReturn(route); Mockito.<HttpClientContext>any())).thenReturn(route);
Mockito.when(execChain.execute( Mockito.when(execChain.execute(
Mockito.<RoutedHttpRequest>any(), Mockito.<RoutedHttpRequest>any(),
@ -137,7 +126,6 @@ public void testExecuteDefaultContext() throws Exception {
Mockito.when(routePlanner.determineRoute( Mockito.when(routePlanner.determineRoute(
Mockito.eq(new HttpHost("somehost")), Mockito.eq(new HttpHost("somehost")),
Mockito.<RoutedHttpRequest>any(),
Mockito.<HttpClientContext>any())).thenReturn(route); Mockito.<HttpClientContext>any())).thenReturn(route);
final HttpClientContext context = HttpClientContext.create(); final HttpClientContext context = HttpClientContext.create();
@ -157,7 +145,6 @@ public void testExecuteRequestConfig() throws Exception {
Mockito.when(routePlanner.determineRoute( Mockito.when(routePlanner.determineRoute(
Mockito.eq(new HttpHost("somehost")), Mockito.eq(new HttpHost("somehost")),
Mockito.<RoutedHttpRequest>any(),
Mockito.<HttpClientContext>any())).thenReturn(route); Mockito.<HttpClientContext>any())).thenReturn(route);
final RequestConfig config = RequestConfig.custom().build(); final RequestConfig config = RequestConfig.custom().build();
@ -176,7 +163,6 @@ public void testExecuteLocalContext() throws Exception {
Mockito.when(routePlanner.determineRoute( Mockito.when(routePlanner.determineRoute(
Mockito.eq(new HttpHost("somehost")), Mockito.eq(new HttpHost("somehost")),
Mockito.<RoutedHttpRequest>any(),
Mockito.<HttpClientContext>any())).thenReturn(route); Mockito.<HttpClientContext>any())).thenReturn(route);
final HttpClientContext context = HttpClientContext.create(); final HttpClientContext context = HttpClientContext.create();

View File

@ -49,9 +49,10 @@
import org.apache.hc.client5.http.entity.EntityBuilder; import org.apache.hc.client5.http.entity.EntityBuilder;
import org.apache.hc.client5.http.impl.auth.BasicScheme; import org.apache.hc.client5.http.impl.auth.BasicScheme;
import org.apache.hc.client5.http.impl.auth.NTLMScheme; import org.apache.hc.client5.http.impl.auth.NTLMScheme;
import org.apache.hc.client5.http.impl.io.ConnectionShutdownException; import org.apache.hc.client5.http.impl.ConnectionShutdownException;
import org.apache.hc.client5.http.io.ConnectionRequest; import org.apache.hc.client5.http.io.ConnectionEndpoint;
import org.apache.hc.client5.http.io.HttpClientConnectionManager; import org.apache.hc.client5.http.io.HttpClientConnectionManager;
import org.apache.hc.client5.http.io.LeaseRequest;
import org.apache.hc.client5.http.methods.HttpExecutionAware; import org.apache.hc.client5.http.methods.HttpExecutionAware;
import org.apache.hc.client5.http.methods.HttpGet; import org.apache.hc.client5.http.methods.HttpGet;
import org.apache.hc.client5.http.methods.HttpPost; import org.apache.hc.client5.http.methods.HttpPost;
@ -109,9 +110,9 @@ public class TestMainClientExec {
@Mock @Mock
private HttpExecutionAware execAware; private HttpExecutionAware execAware;
@Mock @Mock
private ConnectionRequest connRequest; private LeaseRequest connRequest;
@Mock @Mock
private HttpClientConnection managedConn; private ConnectionEndpoint endpoint;
private MainClientExec mainClientExec; private MainClientExec mainClientExec;
private HttpHost target; private HttpHost target;
@ -125,10 +126,10 @@ public void setup() throws Exception {
target = new HttpHost("foo", 80); target = new HttpHost("foo", 80);
proxy = new HttpHost("bar", 8888); proxy = new HttpHost("bar", 8888);
Mockito.when(connManager.requestConnection( Mockito.when(connManager.lease(
Mockito.<HttpRoute>any(), Mockito.any())).thenReturn(connRequest); Mockito.<HttpRoute>any(), Mockito.any())).thenReturn(connRequest);
Mockito.when(connRequest.get( Mockito.when(connRequest.get(
Mockito.anyLong(), Mockito.<TimeUnit>any())).thenReturn(managedConn); Mockito.anyLong(), Mockito.<TimeUnit>any())).thenReturn(endpoint);
} }
@Test @Test
@ -143,24 +144,22 @@ public void testExecRequestNonPersistentConnection() throws Exception {
context.setRequestConfig(config); context.setRequestConfig(config);
final RoutedHttpRequest request = RoutedHttpRequest.adapt(new HttpGet("http://bar/test"), route); final RoutedHttpRequest request = RoutedHttpRequest.adapt(new HttpGet("http://bar/test"), route);
final ClassicHttpResponse response = new BasicClassicHttpResponse(200, "OK"); final ClassicHttpResponse response = new BasicClassicHttpResponse(200, "OK");
Mockito.when(requestExecutor.execute( Mockito.when(endpoint.execute(
Mockito.same(request), Mockito.same(request),
Mockito.<HttpClientConnection>any(), Mockito.<HttpRequestExecutor>any(),
Mockito.<HttpClientContext>any())).thenReturn(response); Mockito.<HttpClientContext>any())).thenReturn(response);
final ClassicHttpResponse finalResponse = mainClientExec.execute(request, context, execAware); final ClassicHttpResponse finalResponse = mainClientExec.execute(request, context, execAware);
Mockito.verify(connManager).requestConnection(route, null); Mockito.verify(connManager).lease(route, null);
Mockito.verify(connRequest).get(345, TimeUnit.MILLISECONDS); Mockito.verify(connRequest).get(345, TimeUnit.MILLISECONDS);
Mockito.verify(execAware, Mockito.times(1)).setCancellable(connRequest); Mockito.verify(execAware, Mockito.times(1)).setCancellable(connRequest);
Mockito.verify(execAware, Mockito.times(2)).setCancellable(Mockito.<Cancellable>any()); Mockito.verify(execAware, Mockito.times(2)).setCancellable(Mockito.<Cancellable>any());
Mockito.verify(connManager).connect(managedConn, route, 123, context); Mockito.verify(connManager).connect(endpoint, 123, TimeUnit.MILLISECONDS, context);
Mockito.verify(connManager).routeComplete(managedConn, route, context); Mockito.verify(endpoint).setSocketTimeout(234);
Mockito.verify(managedConn).setSocketTimeout(234); Mockito.verify(endpoint, Mockito.times(1)).execute(request, requestExecutor, context);
Mockito.verify(requestExecutor, Mockito.times(1)).execute(request, managedConn, context); Mockito.verify(endpoint, Mockito.times(1)).close();
Mockito.verify(managedConn, Mockito.times(1)).close(); Mockito.verify(connManager).release(endpoint, null, 0, TimeUnit.MILLISECONDS);
Mockito.verify(connManager).releaseConnection(managedConn, null, 0, TimeUnit.MILLISECONDS);
Assert.assertSame(managedConn, context.getConnection());
Assert.assertNull(context.getUserToken()); Assert.assertNull(context.getUserToken());
Assert.assertNotNull(finalResponse); Assert.assertNotNull(finalResponse);
Assert.assertTrue(finalResponse instanceof CloseableHttpResponse); Assert.assertTrue(finalResponse instanceof CloseableHttpResponse);
@ -177,11 +176,10 @@ public void testExecRequestPersistentConnection() throws Exception {
final RoutedHttpRequest request = RoutedHttpRequest.adapt(new HttpGet("http://bar/test"), route); final RoutedHttpRequest request = RoutedHttpRequest.adapt(new HttpGet("http://bar/test"), route);
final ClassicHttpResponse response = new BasicClassicHttpResponse(200, "OK"); final ClassicHttpResponse response = new BasicClassicHttpResponse(200, "OK");
Mockito.when(managedConn.isOpen()).thenReturn(Boolean.TRUE); Mockito.when(endpoint.isConnected()).thenReturn(Boolean.TRUE);
Mockito.when(managedConn.isStale()).thenReturn(Boolean.FALSE); Mockito.when(endpoint.execute(
Mockito.when(requestExecutor.execute(
Mockito.same(request), Mockito.same(request),
Mockito.<HttpClientConnection>any(), Mockito.<HttpRequestExecutor>any(),
Mockito.<HttpClientContext>any())).thenReturn(response); Mockito.<HttpClientContext>any())).thenReturn(response);
Mockito.when(reuseStrategy.keepAlive( Mockito.when(reuseStrategy.keepAlive(
Mockito.same(request), Mockito.same(request),
@ -192,11 +190,11 @@ public void testExecRequestPersistentConnection() throws Exception {
Mockito.<HttpClientContext>any())).thenReturn(678L); Mockito.<HttpClientContext>any())).thenReturn(678L);
final ClassicHttpResponse finalResponse = mainClientExec.execute(request, context, execAware); final ClassicHttpResponse finalResponse = mainClientExec.execute(request, context, execAware);
Mockito.verify(connManager).requestConnection(route, null); Mockito.verify(connManager).lease(route, null);
Mockito.verify(connRequest).get(345, TimeUnit.MILLISECONDS); Mockito.verify(connRequest).get(345, TimeUnit.MILLISECONDS);
Mockito.verify(requestExecutor, Mockito.times(1)).execute(request, managedConn, context); Mockito.verify(endpoint, Mockito.times(1)).execute(request, requestExecutor, context);
Mockito.verify(connManager).releaseConnection(managedConn, null, 678L, TimeUnit.MILLISECONDS); Mockito.verify(connManager).release(endpoint, null, 678L, TimeUnit.MILLISECONDS);
Mockito.verify(managedConn, Mockito.never()).close(); Mockito.verify(endpoint, Mockito.never()).close();
Assert.assertNotNull(finalResponse); Assert.assertNotNull(finalResponse);
Assert.assertTrue(finalResponse instanceof CloseableHttpResponse); Assert.assertTrue(finalResponse instanceof CloseableHttpResponse);
@ -213,11 +211,10 @@ public void testExecRequestPersistentStatefulConnection() throws Exception {
final RoutedHttpRequest request = RoutedHttpRequest.adapt(new HttpGet("http://bar/test"), route); final RoutedHttpRequest request = RoutedHttpRequest.adapt(new HttpGet("http://bar/test"), route);
final ClassicHttpResponse response = new BasicClassicHttpResponse(200, "OK"); final ClassicHttpResponse response = new BasicClassicHttpResponse(200, "OK");
Mockito.when(managedConn.isOpen()).thenReturn(Boolean.TRUE); Mockito.when(endpoint.isConnected()).thenReturn(Boolean.TRUE);
Mockito.when(managedConn.isStale()).thenReturn(Boolean.FALSE); Mockito.when(endpoint.execute(
Mockito.when(requestExecutor.execute(
Mockito.same(request), Mockito.same(request),
Mockito.<HttpClientConnection>any(), Mockito.<HttpRequestExecutor>any(),
Mockito.<HttpClientContext>any())).thenReturn(response); Mockito.<HttpClientContext>any())).thenReturn(response);
Mockito.when(reuseStrategy.keepAlive( Mockito.when(reuseStrategy.keepAlive(
Mockito.same(request), Mockito.same(request),
@ -228,11 +225,11 @@ public void testExecRequestPersistentStatefulConnection() throws Exception {
Mockito.<HttpClientContext>any())).thenReturn("this and that"); Mockito.<HttpClientContext>any())).thenReturn("this and that");
mainClientExec.execute(request, context, execAware); mainClientExec.execute(request, context, execAware);
Mockito.verify(connManager).requestConnection(route, null); Mockito.verify(connManager).lease(route, null);
Mockito.verify(connRequest).get(345, TimeUnit.MILLISECONDS); Mockito.verify(connRequest).get(345, TimeUnit.MILLISECONDS);
Mockito.verify(requestExecutor, Mockito.times(1)).execute(request, managedConn, context); Mockito.verify(endpoint, Mockito.times(1)).execute(request, requestExecutor, context);
Mockito.verify(connManager).releaseConnection(managedConn, "this and that", 0, TimeUnit.MILLISECONDS); Mockito.verify(connManager).release(endpoint, "this and that", 0, TimeUnit.MILLISECONDS);
Mockito.verify(managedConn, Mockito.never()).close(); Mockito.verify(endpoint, Mockito.never()).close();
Assert.assertEquals("this and that", context.getUserToken()); Assert.assertEquals("this and that", context.getUserToken());
} }
@ -252,11 +249,10 @@ public void testExecRequestConnectionRelease() throws Exception {
.setStream(new ByteArrayInputStream(new byte[]{})) .setStream(new ByteArrayInputStream(new byte[]{}))
.build()); .build());
Mockito.when(managedConn.isOpen()).thenReturn(Boolean.TRUE); Mockito.when(endpoint.isConnected()).thenReturn(Boolean.TRUE);
Mockito.when(managedConn.isStale()).thenReturn(Boolean.FALSE); Mockito.when(endpoint.execute(
Mockito.when(requestExecutor.execute(
Mockito.same(request), Mockito.same(request),
Mockito.<HttpClientConnection>any(), Mockito.<HttpRequestExecutor>any(),
Mockito.<HttpClientContext>any())).thenReturn(response); Mockito.<HttpClientContext>any())).thenReturn(response);
Mockito.when(reuseStrategy.keepAlive( Mockito.when(reuseStrategy.keepAlive(
Mockito.same(request), Mockito.same(request),
@ -264,23 +260,23 @@ public void testExecRequestConnectionRelease() throws Exception {
Mockito.<HttpClientContext>any())).thenReturn(Boolean.FALSE); Mockito.<HttpClientContext>any())).thenReturn(Boolean.FALSE);
final ClassicHttpResponse finalResponse = mainClientExec.execute(request, context, execAware); final ClassicHttpResponse finalResponse = mainClientExec.execute(request, context, execAware);
Mockito.verify(connManager).requestConnection(route, null); Mockito.verify(connManager).lease(route, null);
Mockito.verify(connRequest).get(345, TimeUnit.MILLISECONDS); Mockito.verify(connRequest).get(345, TimeUnit.MILLISECONDS);
Mockito.verify(requestExecutor, Mockito.times(1)).execute(request, managedConn, context); Mockito.verify(endpoint, Mockito.times(1)).execute(request, requestExecutor, context);
Mockito.verify(connManager, Mockito.never()).releaseConnection( Mockito.verify(connManager, Mockito.never()).release(
Mockito.same(managedConn), Mockito.same(endpoint),
Mockito.any(), Mockito.any(),
Mockito.anyInt(), Mockito.anyInt(),
Mockito.<TimeUnit>any()); Mockito.<TimeUnit>any());
Mockito.verify(managedConn, Mockito.never()).close(); Mockito.verify(endpoint, Mockito.never()).close();
Assert.assertNotNull(finalResponse); Assert.assertNotNull(finalResponse);
Assert.assertTrue(finalResponse instanceof CloseableHttpResponse); Assert.assertTrue(finalResponse instanceof CloseableHttpResponse);
finalResponse.close(); finalResponse.close();
Mockito.verify(connManager, Mockito.times(1)).releaseConnection( Mockito.verify(connManager, Mockito.times(1)).release(
managedConn, null, 0, TimeUnit.MILLISECONDS); endpoint, null, 0, TimeUnit.MILLISECONDS);
Mockito.verify(managedConn, Mockito.times(1)).close(); Mockito.verify(endpoint, Mockito.times(1)).close();
} }
@Test @Test
@ -291,14 +287,14 @@ public void testSocketTimeoutExistingConnection() throws Exception {
final RoutedHttpRequest request = RoutedHttpRequest.adapt(new HttpGet("http://bar/test"), route); final RoutedHttpRequest request = RoutedHttpRequest.adapt(new HttpGet("http://bar/test"), route);
context.setRequestConfig(config); context.setRequestConfig(config);
final ClassicHttpResponse response = new BasicClassicHttpResponse(200, "OK"); final ClassicHttpResponse response = new BasicClassicHttpResponse(200, "OK");
Mockito.when(managedConn.isOpen()).thenReturn(true); Mockito.when(endpoint.isConnected()).thenReturn(true);
Mockito.when(requestExecutor.execute( Mockito.when(endpoint.execute(
Mockito.same(request), Mockito.same(request),
Mockito.<HttpClientConnection>any(), Mockito.<HttpRequestExecutor>any(),
Mockito.<HttpClientContext>any())).thenReturn(response); Mockito.<HttpClientContext>any())).thenReturn(response);
mainClientExec.execute(request, context, execAware); mainClientExec.execute(request, context, execAware);
Mockito.verify(managedConn).setSocketTimeout(3000); Mockito.verify(endpoint).setSocketTimeout(3000);
} }
@Test @Test
@ -307,14 +303,14 @@ public void testSocketTimeoutReset() throws Exception {
final HttpClientContext context = new HttpClientContext(); final HttpClientContext context = new HttpClientContext();
final RoutedHttpRequest request = RoutedHttpRequest.adapt(new HttpGet("http://bar/test"), route); final RoutedHttpRequest request = RoutedHttpRequest.adapt(new HttpGet("http://bar/test"), route);
final ClassicHttpResponse response = new BasicClassicHttpResponse(200, "OK"); final ClassicHttpResponse response = new BasicClassicHttpResponse(200, "OK");
Mockito.when(managedConn.isOpen()).thenReturn(Boolean.TRUE); Mockito.when(endpoint.isConnected()).thenReturn(Boolean.TRUE);
Mockito.when(requestExecutor.execute( Mockito.when(endpoint.execute(
Mockito.same(request), Mockito.same(request),
Mockito.<HttpClientConnection>any(), Mockito.<HttpRequestExecutor>any(),
Mockito.<HttpClientContext>any())).thenReturn(response); Mockito.<HttpClientContext>any())).thenReturn(response);
mainClientExec.execute(request, context, execAware); mainClientExec.execute(request, context, execAware);
Mockito.verify(managedConn, Mockito.never()).setSocketTimeout(Mockito.anyInt()); Mockito.verify(endpoint, Mockito.never()).setSocketTimeout(Mockito.anyInt());
} }
@Test(expected=RequestAbortedException.class) @Test(expected=RequestAbortedException.class)
@ -323,7 +319,7 @@ public void testExecAbortedPriorToConnectionLease() throws Exception {
final HttpClientContext context = new HttpClientContext(); final HttpClientContext context = new HttpClientContext();
final RoutedHttpRequest request = RoutedHttpRequest.adapt(new HttpGet("http://bar/test"), route); final RoutedHttpRequest request = RoutedHttpRequest.adapt(new HttpGet("http://bar/test"), route);
Mockito.when(managedConn.isOpen()).thenReturn(Boolean.FALSE); Mockito.when(endpoint.isConnected()).thenReturn(Boolean.FALSE);
Mockito.when(execAware.isAborted()).thenReturn(Boolean.TRUE); Mockito.when(execAware.isAborted()).thenReturn(Boolean.TRUE);
try { try {
mainClientExec.execute(request, context, execAware); mainClientExec.execute(request, context, execAware);
@ -343,7 +339,7 @@ public void testExecAbortedPriorToConnectionSetup() throws Exception {
context.setRequestConfig(config); context.setRequestConfig(config);
final RoutedHttpRequest request = RoutedHttpRequest.adapt(new HttpGet("http://bar/test"), route); final RoutedHttpRequest request = RoutedHttpRequest.adapt(new HttpGet("http://bar/test"), route);
Mockito.when(managedConn.isOpen()).thenReturn(Boolean.FALSE); Mockito.when(endpoint.isConnected()).thenReturn(Boolean.FALSE);
Mockito.when(execAware.isAborted()).thenReturn(Boolean.FALSE, Boolean.TRUE); Mockito.when(execAware.isAborted()).thenReturn(Boolean.FALSE, Boolean.TRUE);
try { try {
mainClientExec.execute(request, context, execAware); mainClientExec.execute(request, context, execAware);
@ -351,9 +347,9 @@ public void testExecAbortedPriorToConnectionSetup() throws Exception {
Mockito.verify(connRequest, Mockito.times(1)).get(345, TimeUnit.MILLISECONDS); Mockito.verify(connRequest, Mockito.times(1)).get(345, TimeUnit.MILLISECONDS);
Mockito.verify(execAware, Mockito.times(2)).setCancellable(Mockito.<Cancellable>any()); Mockito.verify(execAware, Mockito.times(2)).setCancellable(Mockito.<Cancellable>any());
Mockito.verify(connManager, Mockito.never()).connect( Mockito.verify(connManager, Mockito.never()).connect(
Mockito.same(managedConn), Mockito.same(endpoint),
Mockito.<HttpRoute>any(),
Mockito.anyInt(), Mockito.anyInt(),
Mockito.<TimeUnit>any(),
Mockito.<HttpContext>any()); Mockito.<HttpContext>any());
throw ex; throw ex;
} }
@ -370,13 +366,13 @@ public void testExecAbortedPriorToRequestExecution() throws Exception {
context.setRequestConfig(config); context.setRequestConfig(config);
final RoutedHttpRequest request = RoutedHttpRequest.adapt(new HttpGet("http://bar/test"), route); final RoutedHttpRequest request = RoutedHttpRequest.adapt(new HttpGet("http://bar/test"), route);
Mockito.when(managedConn.isOpen()).thenReturn(Boolean.FALSE); Mockito.when(endpoint.isConnected()).thenReturn(Boolean.FALSE);
Mockito.when(execAware.isAborted()).thenReturn(Boolean.FALSE, Boolean.FALSE, Boolean.TRUE); Mockito.when(execAware.isAborted()).thenReturn(Boolean.FALSE, Boolean.FALSE, Boolean.TRUE);
try { try {
mainClientExec.execute(request, context, execAware); mainClientExec.execute(request, context, execAware);
} catch (final IOException ex) { } catch (final IOException ex) {
Mockito.verify(connRequest, Mockito.times(1)).get(345, TimeUnit.MILLISECONDS); Mockito.verify(connRequest, Mockito.times(1)).get(345, TimeUnit.MILLISECONDS);
Mockito.verify(connManager, Mockito.times(1)).connect(managedConn, route, 567, context); Mockito.verify(connManager, Mockito.times(1)).connect(endpoint, 567, TimeUnit.MILLISECONDS, context);
Mockito.verify(requestExecutor, Mockito.never()).execute( Mockito.verify(requestExecutor, Mockito.never()).execute(
Mockito.same(request), Mockito.same(request),
Mockito.<HttpClientConnection>any(), Mockito.<HttpClientConnection>any(),
@ -417,11 +413,10 @@ public void testExecRequestRetryOnAuthChallenge() throws Exception {
credentialsProvider.setCredentials(new AuthScope(target), new UsernamePasswordCredentials("user", "pass".toCharArray())); credentialsProvider.setCredentials(new AuthScope(target), new UsernamePasswordCredentials("user", "pass".toCharArray()));
context.setCredentialsProvider(credentialsProvider); context.setCredentialsProvider(credentialsProvider);
Mockito.when(managedConn.isOpen()).thenReturn(Boolean.TRUE); Mockito.when(endpoint.isConnected()).thenReturn(Boolean.TRUE);
Mockito.when(managedConn.isStale()).thenReturn(Boolean.FALSE); Mockito.when(endpoint.execute(
Mockito.when(requestExecutor.execute(
Mockito.same(request), Mockito.same(request),
Mockito.<HttpClientConnection>any(), Mockito.<HttpRequestExecutor>any(),
Mockito.<HttpClientContext>any())).thenReturn(response1, response2); Mockito.<HttpClientContext>any())).thenReturn(response1, response2);
Mockito.when(reuseStrategy.keepAlive( Mockito.when(reuseStrategy.keepAlive(
Mockito.same(request), Mockito.same(request),
@ -434,7 +429,7 @@ public void testExecRequestRetryOnAuthChallenge() throws Exception {
final ClassicHttpResponse finalResponse = mainClientExec.execute( final ClassicHttpResponse finalResponse = mainClientExec.execute(
request, context, execAware); request, context, execAware);
Mockito.verify(requestExecutor, Mockito.times(2)).execute(request, managedConn, context); Mockito.verify(endpoint, Mockito.times(2)).execute(request, requestExecutor, context);
Mockito.verify(instream1).close(); Mockito.verify(instream1).close();
Mockito.verify(instream2, Mockito.never()).close(); Mockito.verify(instream2, Mockito.never()).close();
@ -469,11 +464,10 @@ public void testExecEntityEnclosingRequestRetryOnAuthChallenge() throws Exceptio
credentialsProvider.setCredentials(new AuthScope(target), new UsernamePasswordCredentials("user", "pass".toCharArray())); credentialsProvider.setCredentials(new AuthScope(target), new UsernamePasswordCredentials("user", "pass".toCharArray()));
context.setCredentialsProvider(credentialsProvider); context.setCredentialsProvider(credentialsProvider);
Mockito.when(managedConn.isOpen()).thenReturn(Boolean.TRUE); Mockito.when(endpoint.isConnected()).thenReturn(Boolean.TRUE);
Mockito.when(managedConn.isStale()).thenReturn(Boolean.FALSE); Mockito.when(endpoint.execute(
Mockito.when(requestExecutor.execute(
Mockito.same(request), Mockito.same(request),
Mockito.<HttpClientConnection>any(), Mockito.<HttpRequestExecutor>any(),
Mockito.<HttpClientContext>any())).thenReturn(response1, response2); Mockito.<HttpClientContext>any())).thenReturn(response1, response2);
Mockito.when(reuseStrategy.keepAlive( Mockito.when(reuseStrategy.keepAlive(
Mockito.same(request), Mockito.same(request),
@ -487,8 +481,8 @@ public void testExecEntityEnclosingRequestRetryOnAuthChallenge() throws Exceptio
final ClassicHttpResponse finalResponse = mainClientExec.execute( final ClassicHttpResponse finalResponse = mainClientExec.execute(
request, context, execAware); request, context, execAware);
Mockito.verify(requestExecutor, Mockito.times(2)).execute(request, managedConn, context); Mockito.verify(endpoint, Mockito.times(2)).execute(request, requestExecutor, context);
Mockito.verify(managedConn).close(); Mockito.verify(endpoint).close();
Mockito.verify(instream2, Mockito.never()).close(); Mockito.verify(instream2, Mockito.never()).close();
Assert.assertNotNull(finalResponse); Assert.assertNotNull(finalResponse);
@ -518,11 +512,10 @@ public void testExecEntityEnclosingRequest() throws Exception {
credentialsProvider.setCredentials(new AuthScope(target), new UsernamePasswordCredentials("user", "pass".toCharArray())); credentialsProvider.setCredentials(new AuthScope(target), new UsernamePasswordCredentials("user", "pass".toCharArray()));
context.setCredentialsProvider(credentialsProvider); context.setCredentialsProvider(credentialsProvider);
Mockito.when(managedConn.isOpen()).thenReturn(Boolean.TRUE); Mockito.when(endpoint.isConnected()).thenReturn(Boolean.TRUE);
Mockito.when(managedConn.isStale()).thenReturn(Boolean.FALSE); Mockito.when(endpoint.execute(
Mockito.when(requestExecutor.execute(
Mockito.same(request), Mockito.same(request),
Mockito.<HttpClientConnection>any(), Mockito.<HttpRequestExecutor>any(),
Mockito.<HttpClientContext>any())).thenAnswer(new Answer<HttpResponse>() { Mockito.<HttpClientContext>any())).thenAnswer(new Answer<HttpResponse>() {
@Override @Override
@ -553,9 +546,9 @@ public void testExecConnectionShutDown() throws Exception {
final HttpClientContext context = new HttpClientContext(); final HttpClientContext context = new HttpClientContext();
final RoutedHttpRequest request = RoutedHttpRequest.adapt(new HttpGet("http://bar/test"), route); final RoutedHttpRequest request = RoutedHttpRequest.adapt(new HttpGet("http://bar/test"), route);
Mockito.when(requestExecutor.execute( Mockito.when(endpoint.execute(
Mockito.<ClassicHttpRequest>any(), Mockito.same(request),
Mockito.<HttpClientConnection>any(), Mockito.<HttpRequestExecutor>any(),
Mockito.<HttpClientContext>any())).thenThrow(new ConnectionShutdownException()); Mockito.<HttpClientContext>any())).thenThrow(new ConnectionShutdownException());
mainClientExec.execute(request, context, execAware); mainClientExec.execute(request, context, execAware);
@ -567,15 +560,15 @@ public void testExecRuntimeException() throws Exception {
final HttpClientContext context = new HttpClientContext(); final HttpClientContext context = new HttpClientContext();
final RoutedHttpRequest request = RoutedHttpRequest.adapt(new HttpGet("http://bar/test"), route); final RoutedHttpRequest request = RoutedHttpRequest.adapt(new HttpGet("http://bar/test"), route);
Mockito.when(requestExecutor.execute( Mockito.when(endpoint.execute(
Mockito.<ClassicHttpRequest>any(), Mockito.same(request),
Mockito.<HttpClientConnection>any(), Mockito.<HttpRequestExecutor>any(),
Mockito.<HttpClientContext>any())).thenThrow(new RuntimeException("Ka-boom")); Mockito.<HttpClientContext>any())).thenThrow(new RuntimeException("Ka-boom"));
try { try {
mainClientExec.execute(request, context, execAware); mainClientExec.execute(request, context, execAware);
} catch (final Exception ex) { } catch (final Exception ex) {
Mockito.verify(connManager).releaseConnection(managedConn, null, 0, TimeUnit.MILLISECONDS); Mockito.verify(connManager).release(endpoint, null, 0, TimeUnit.MILLISECONDS);
throw ex; throw ex;
} }
@ -587,15 +580,15 @@ public void testExecHttpException() throws Exception {
final HttpClientContext context = new HttpClientContext(); final HttpClientContext context = new HttpClientContext();
final RoutedHttpRequest request = RoutedHttpRequest.adapt(new HttpGet("http://bar/test"), route); final RoutedHttpRequest request = RoutedHttpRequest.adapt(new HttpGet("http://bar/test"), route);
Mockito.when(requestExecutor.execute( Mockito.when(endpoint.execute(
Mockito.<ClassicHttpRequest>any(), Mockito.same(request),
Mockito.<HttpClientConnection>any(), Mockito.<HttpRequestExecutor>any(),
Mockito.<HttpClientContext>any())).thenThrow(new HttpException("Ka-boom")); Mockito.<HttpClientContext>any())).thenThrow(new HttpException("Ka-boom"));
try { try {
mainClientExec.execute(request, context, execAware); mainClientExec.execute(request, context, execAware);
} catch (final Exception ex) { } catch (final Exception ex) {
Mockito.verify(connManager).releaseConnection(managedConn, null, 0, TimeUnit.MILLISECONDS); Mockito.verify(connManager).release(endpoint, null, 0, TimeUnit.MILLISECONDS);
throw ex; throw ex;
} }
@ -607,15 +600,15 @@ public void testExecIOException() throws Exception {
final HttpClientContext context = new HttpClientContext(); final HttpClientContext context = new HttpClientContext();
final RoutedHttpRequest request = RoutedHttpRequest.adapt(new HttpGet("http://bar/test"), route); final RoutedHttpRequest request = RoutedHttpRequest.adapt(new HttpGet("http://bar/test"), route);
Mockito.when(requestExecutor.execute( Mockito.when(endpoint.execute(
Mockito.<ClassicHttpRequest>any(), Mockito.same(request),
Mockito.<HttpClientConnection>any(), Mockito.<HttpRequestExecutor>any(),
Mockito.<HttpClientContext>any())).thenThrow(new IOException("Ka-boom")); Mockito.<HttpClientContext>any())).thenThrow(new IOException("Ka-boom"));
try { try {
mainClientExec.execute(request, context, execAware); mainClientExec.execute(request, context, execAware);
} catch (final Exception ex) { } catch (final Exception ex) {
Mockito.verify(connManager).releaseConnection(managedConn, null, 0, TimeUnit.MILLISECONDS); Mockito.verify(connManager).release(endpoint, null, 0, TimeUnit.MILLISECONDS);
throw ex; throw ex;
} }
@ -631,12 +624,11 @@ public void testEstablishDirectRoute() throws Exception {
context.setRequestConfig(config); context.setRequestConfig(config);
final RoutedHttpRequest request = RoutedHttpRequest.adapt(new HttpGet("http://bar/test"), route); final RoutedHttpRequest request = RoutedHttpRequest.adapt(new HttpGet("http://bar/test"), route);
Mockito.when(managedConn.isOpen()).thenReturn(Boolean.TRUE); Mockito.when(endpoint.isConnected()).thenReturn(Boolean.TRUE);
mainClientExec.establishRoute(managedConn, route, request, context); mainClientExec.establishRoute(endpoint, route, request, context);
Mockito.verify(connManager).connect(managedConn, route, 567, context); Mockito.verify(connManager).connect(endpoint, 567, TimeUnit.MILLISECONDS, context);
Mockito.verify(connManager).routeComplete(managedConn, route, context);
} }
@Test @Test
@ -649,12 +641,11 @@ public void testEstablishRouteDirectProxy() throws Exception {
context.setRequestConfig(config); context.setRequestConfig(config);
final RoutedHttpRequest request = RoutedHttpRequest.adapt(new HttpGet("http://bar/test"), route); final RoutedHttpRequest request = RoutedHttpRequest.adapt(new HttpGet("http://bar/test"), route);
Mockito.when(managedConn.isOpen()).thenReturn(Boolean.TRUE); Mockito.when(endpoint.isConnected()).thenReturn(Boolean.TRUE);
mainClientExec.establishRoute(managedConn, route, request, context); mainClientExec.establishRoute(endpoint, route, request, context);
Mockito.verify(connManager).connect(managedConn, route, 567, context); Mockito.verify(connManager).connect(endpoint, 567, TimeUnit.MILLISECONDS, context);
Mockito.verify(connManager).routeComplete(managedConn, route, context);
} }
@Test @Test
@ -668,20 +659,19 @@ public void testEstablishRouteViaProxyTunnel() throws Exception {
final RoutedHttpRequest request = RoutedHttpRequest.adapt(new HttpGet("http://bar/test"), route); final RoutedHttpRequest request = RoutedHttpRequest.adapt(new HttpGet("http://bar/test"), route);
final ClassicHttpResponse response = new BasicClassicHttpResponse(200, "OK"); final ClassicHttpResponse response = new BasicClassicHttpResponse(200, "OK");
Mockito.when(managedConn.isOpen()).thenReturn(Boolean.TRUE); Mockito.when(endpoint.isConnected()).thenReturn(Boolean.TRUE);
Mockito.when(requestExecutor.execute( Mockito.when(endpoint.execute(
Mockito.<ClassicHttpRequest>any(), Mockito.<ClassicHttpRequest>any(),
Mockito.<HttpClientConnection>any(), Mockito.<HttpRequestExecutor>any(),
Mockito.<HttpClientContext>any())).thenReturn(response); Mockito.<HttpClientContext>any())).thenReturn(response);
mainClientExec.establishRoute(managedConn, route, request, context); mainClientExec.establishRoute(endpoint, route, request, context);
Mockito.verify(connManager).connect(managedConn, route, 321, context); Mockito.verify(connManager).connect(endpoint, 321, TimeUnit.MILLISECONDS, context);
Mockito.verify(connManager).routeComplete(managedConn, route, context);
final ArgumentCaptor<ClassicHttpRequest> reqCaptor = ArgumentCaptor.forClass(ClassicHttpRequest.class); final ArgumentCaptor<ClassicHttpRequest> reqCaptor = ArgumentCaptor.forClass(ClassicHttpRequest.class);
Mockito.verify(requestExecutor).execute( Mockito.verify(endpoint).execute(
reqCaptor.capture(), reqCaptor.capture(),
Mockito.same(managedConn), Mockito.same(requestExecutor),
Mockito.same(context)); Mockito.same(context));
final HttpRequest connect = reqCaptor.getValue(); final HttpRequest connect = reqCaptor.getValue();
Assert.assertNotNull(connect); Assert.assertNotNull(connect);
@ -697,13 +687,13 @@ public void testEstablishRouteViaProxyTunnelUnexpectedResponse() throws Exceptio
final RoutedHttpRequest request = RoutedHttpRequest.adapt(new HttpGet("http://bar/test"), route); final RoutedHttpRequest request = RoutedHttpRequest.adapt(new HttpGet("http://bar/test"), route);
final ClassicHttpResponse response = new BasicClassicHttpResponse(101, "Lost"); final ClassicHttpResponse response = new BasicClassicHttpResponse(101, "Lost");
Mockito.when(managedConn.isOpen()).thenReturn(Boolean.TRUE); Mockito.when(endpoint.isConnected()).thenReturn(Boolean.TRUE);
Mockito.when(requestExecutor.execute( Mockito.when(endpoint.execute(
Mockito.<ClassicHttpRequest>any(), Mockito.<ClassicHttpRequest>any(),
Mockito.<HttpClientConnection>any(), Mockito.<HttpRequestExecutor>any(),
Mockito.<HttpClientContext>any())).thenReturn(response); Mockito.<HttpClientContext>any())).thenReturn(response);
mainClientExec.establishRoute(managedConn, route, request, context); mainClientExec.establishRoute(endpoint, route, request, context);
} }
@Test(expected = HttpException.class) @Test(expected = HttpException.class)
@ -714,20 +704,18 @@ public void testEstablishRouteViaProxyTunnelFailure() throws Exception {
final ClassicHttpResponse response = new BasicClassicHttpResponse(500, "Boom"); final ClassicHttpResponse response = new BasicClassicHttpResponse(500, "Boom");
response.setEntity(new StringEntity("Ka-boom")); response.setEntity(new StringEntity("Ka-boom"));
Mockito.when(managedConn.isOpen()).thenReturn(Boolean.TRUE); Mockito.when(endpoint.isConnected()).thenReturn(Boolean.TRUE);
Mockito.when(requestExecutor.execute( Mockito.when(endpoint.execute(
Mockito.<ClassicHttpRequest>any(), Mockito.<ClassicHttpRequest>any(),
Mockito.<HttpClientConnection>any(), Mockito.<HttpRequestExecutor>any(),
Mockito.<HttpClientContext>any())).thenReturn(response); Mockito.<HttpClientContext>any())).thenReturn(response);
try { try {
mainClientExec.establishRoute(managedConn, route, request, context); mainClientExec.establishRoute(endpoint, route, request, context);
} catch (final TunnelRefusedException ex) { } catch (final TunnelRefusedException ex) {
final ClassicHttpResponse r = ex.getResponse(); final ClassicHttpResponse r = ex.getResponse();
Assert.assertEquals("Ka-boom", EntityUtils.toString(r.getEntity())); Assert.assertEquals("Ka-boom", EntityUtils.toString(r.getEntity()));
Mockito.verify(endpoint).close();
Mockito.verify(managedConn).close();
throw ex; throw ex;
} }
} }
@ -753,14 +741,14 @@ public void testEstablishRouteViaProxyTunnelRetryOnAuthChallengePersistentConnec
credentialsProvider.setCredentials(new AuthScope(proxy), new UsernamePasswordCredentials("user", "pass".toCharArray())); credentialsProvider.setCredentials(new AuthScope(proxy), new UsernamePasswordCredentials("user", "pass".toCharArray()));
context.setCredentialsProvider(credentialsProvider); context.setCredentialsProvider(credentialsProvider);
Mockito.when(managedConn.isOpen()).thenReturn(Boolean.TRUE); Mockito.when(endpoint.isConnected()).thenReturn(Boolean.TRUE);
Mockito.when(reuseStrategy.keepAlive( Mockito.when(reuseStrategy.keepAlive(
Mockito.same(request), Mockito.same(request),
Mockito.<HttpResponse>any(), Mockito.<HttpResponse>any(),
Mockito.<HttpClientContext>any())).thenReturn(Boolean.TRUE); Mockito.<HttpClientContext>any())).thenReturn(Boolean.TRUE);
Mockito.when(requestExecutor.execute( Mockito.when(endpoint.execute(
Mockito.<ClassicHttpRequest>any(), Mockito.<ClassicHttpRequest>any(),
Mockito.<HttpClientConnection>any(), Mockito.<HttpRequestExecutor>any(),
Mockito.<HttpClientContext>any())).thenReturn(response1, response2); Mockito.<HttpClientContext>any())).thenReturn(response1, response2);
Mockito.when(proxyAuthStrategy.select( Mockito.when(proxyAuthStrategy.select(
@ -768,10 +756,9 @@ public void testEstablishRouteViaProxyTunnelRetryOnAuthChallengePersistentConnec
Mockito.<Map<String, AuthChallenge>>any(), Mockito.<Map<String, AuthChallenge>>any(),
Mockito.<HttpClientContext>any())).thenReturn(Collections.<AuthScheme>singletonList(new BasicScheme())); Mockito.<HttpClientContext>any())).thenReturn(Collections.<AuthScheme>singletonList(new BasicScheme()));
mainClientExec.establishRoute(managedConn, route, request, context); mainClientExec.establishRoute(endpoint, route, request, context);
Mockito.verify(connManager).connect(managedConn, route, 567, context); Mockito.verify(connManager).connect(endpoint, 567, TimeUnit.MILLISECONDS, context);
Mockito.verify(connManager).routeComplete(managedConn, route, context);
Mockito.verify(instream1).close(); Mockito.verify(instream1).close();
} }
@ -796,14 +783,14 @@ public void testEstablishRouteViaProxyTunnelRetryOnAuthChallengeNonPersistentCon
credentialsProvider.setCredentials(new AuthScope(proxy), new UsernamePasswordCredentials("user", "pass".toCharArray())); credentialsProvider.setCredentials(new AuthScope(proxy), new UsernamePasswordCredentials("user", "pass".toCharArray()));
context.setCredentialsProvider(credentialsProvider); context.setCredentialsProvider(credentialsProvider);
Mockito.when(managedConn.isOpen()).thenReturn(Boolean.TRUE); Mockito.when(endpoint.isConnected()).thenReturn(Boolean.TRUE);
Mockito.when(reuseStrategy.keepAlive( Mockito.when(reuseStrategy.keepAlive(
Mockito.same(request), Mockito.same(request),
Mockito.<HttpResponse>any(), Mockito.<HttpResponse>any(),
Mockito.<HttpClientContext>any())).thenReturn(Boolean.FALSE); Mockito.<HttpClientContext>any())).thenReturn(Boolean.FALSE);
Mockito.when(requestExecutor.execute( Mockito.when(endpoint.execute(
Mockito.<ClassicHttpRequest>any(), Mockito.<ClassicHttpRequest>any(),
Mockito.<HttpClientConnection>any(), Mockito.<HttpRequestExecutor>any(),
Mockito.<HttpClientContext>any())).thenReturn(response1, response2); Mockito.<HttpClientContext>any())).thenReturn(response1, response2);
Mockito.when(proxyAuthStrategy.select( Mockito.when(proxyAuthStrategy.select(
@ -811,12 +798,11 @@ public void testEstablishRouteViaProxyTunnelRetryOnAuthChallengeNonPersistentCon
Mockito.<Map<String, AuthChallenge>>any(), Mockito.<Map<String, AuthChallenge>>any(),
Mockito.<HttpClientContext>any())).thenReturn(Collections.<AuthScheme>singletonList(new BasicScheme())); Mockito.<HttpClientContext>any())).thenReturn(Collections.<AuthScheme>singletonList(new BasicScheme()));
mainClientExec.establishRoute(managedConn, route, request, context); mainClientExec.establishRoute(endpoint, route, request, context);
Mockito.verify(connManager).connect(managedConn, route, 567, context); Mockito.verify(connManager).connect(endpoint, 567, TimeUnit.MILLISECONDS, context);
Mockito.verify(connManager).routeComplete(managedConn, route, context);
Mockito.verify(instream1, Mockito.never()).close(); Mockito.verify(instream1, Mockito.never()).close();
Mockito.verify(managedConn).close(); Mockito.verify(endpoint).close();
} }
@Test(expected = HttpException.class) @Test(expected = HttpException.class)
@ -828,9 +814,9 @@ public void testEstablishRouteViaProxyTunnelMultipleHops() throws Exception {
final HttpClientContext context = new HttpClientContext(); final HttpClientContext context = new HttpClientContext();
final RoutedHttpRequest request = RoutedHttpRequest.adapt(new HttpGet("http://bar/test"), route); final RoutedHttpRequest request = RoutedHttpRequest.adapt(new HttpGet("http://bar/test"), route);
Mockito.when(managedConn.isOpen()).thenReturn(Boolean.TRUE); Mockito.when(endpoint.isConnected()).thenReturn(Boolean.TRUE);
mainClientExec.establishRoute(managedConn, route, request, context); mainClientExec.establishRoute(endpoint, route, request, context);
} }
} }

View File

@ -36,9 +36,10 @@
import org.apache.hc.client5.http.HttpRoute; import org.apache.hc.client5.http.HttpRoute;
import org.apache.hc.client5.http.config.RequestConfig; import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.entity.EntityBuilder; import org.apache.hc.client5.http.entity.EntityBuilder;
import org.apache.hc.client5.http.impl.io.ConnectionShutdownException; import org.apache.hc.client5.http.impl.ConnectionShutdownException;
import org.apache.hc.client5.http.io.ConnectionRequest; import org.apache.hc.client5.http.io.ConnectionEndpoint;
import org.apache.hc.client5.http.io.HttpClientConnectionManager; import org.apache.hc.client5.http.io.HttpClientConnectionManager;
import org.apache.hc.client5.http.io.LeaseRequest;
import org.apache.hc.client5.http.methods.HttpExecutionAware; import org.apache.hc.client5.http.methods.HttpExecutionAware;
import org.apache.hc.client5.http.methods.HttpGet; import org.apache.hc.client5.http.methods.HttpGet;
import org.apache.hc.client5.http.methods.RoutedHttpRequest; import org.apache.hc.client5.http.methods.RoutedHttpRequest;
@ -50,7 +51,6 @@
import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpException;
import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.impl.io.HttpRequestExecutor; import org.apache.hc.core5.http.impl.io.HttpRequestExecutor;
import org.apache.hc.core5.http.io.HttpClientConnection;
import org.apache.hc.core5.http.message.BasicClassicHttpResponse; import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
@ -74,9 +74,9 @@ public class TestMinimalClientExec {
@Mock @Mock
private HttpExecutionAware execAware; private HttpExecutionAware execAware;
@Mock @Mock
private ConnectionRequest connRequest; private LeaseRequest connRequest;
@Mock @Mock
private HttpClientConnection managedConn; private ConnectionEndpoint endpoint;
private MinimalClientExec minimalClientExec; private MinimalClientExec minimalClientExec;
private HttpHost target; private HttpHost target;
@ -88,10 +88,10 @@ public void setup() throws Exception {
requestExecutor, connManager, reuseStrategy, keepAliveStrategy); requestExecutor, connManager, reuseStrategy, keepAliveStrategy);
target = new HttpHost("foo", 80); target = new HttpHost("foo", 80);
Mockito.when(connManager.requestConnection( Mockito.when(connManager.lease(
Mockito.<HttpRoute>any(), Mockito.any())).thenReturn(connRequest); Mockito.<HttpRoute>any(), Mockito.any())).thenReturn(connRequest);
Mockito.when(connRequest.get( Mockito.when(connRequest.get(
Mockito.anyLong(), Mockito.<TimeUnit>any())).thenReturn(managedConn); Mockito.anyLong(), Mockito.<TimeUnit>any())).thenReturn(endpoint);
} }
@Test @Test
@ -106,24 +106,22 @@ public void testExecRequestNonPersistentConnection() throws Exception {
context.setRequestConfig(config); context.setRequestConfig(config);
final RoutedHttpRequest request = RoutedHttpRequest.adapt(new HttpGet("http://bar/test"), route); final RoutedHttpRequest request = RoutedHttpRequest.adapt(new HttpGet("http://bar/test"), route);
final ClassicHttpResponse response = new BasicClassicHttpResponse(200, "OK"); final ClassicHttpResponse response = new BasicClassicHttpResponse(200, "OK");
Mockito.when(requestExecutor.execute( Mockito.when(endpoint.execute(
Mockito.same(request), Mockito.same(request),
Mockito.<HttpClientConnection>any(), Mockito.<HttpRequestExecutor>any(),
Mockito.<HttpClientContext>any())).thenReturn(response); Mockito.<HttpClientContext>any())).thenReturn(response);
final ClassicHttpResponse finalResponse = minimalClientExec.execute(request, context, execAware); final ClassicHttpResponse finalResponse = minimalClientExec.execute(request, context, execAware);
Mockito.verify(connManager).requestConnection(route, null); Mockito.verify(connManager).lease(route, null);
Mockito.verify(connRequest).get(345, TimeUnit.MILLISECONDS); Mockito.verify(connRequest).get(345, TimeUnit.MILLISECONDS);
Mockito.verify(execAware, Mockito.times(1)).setCancellable(connRequest); Mockito.verify(execAware, Mockito.times(1)).setCancellable(connRequest);
Mockito.verify(execAware, Mockito.times(2)).setCancellable(Mockito.<Cancellable>any()); Mockito.verify(execAware, Mockito.times(2)).setCancellable(Mockito.<Cancellable>any());
Mockito.verify(connManager).connect(managedConn, route, 123, context); Mockito.verify(connManager).connect(endpoint, 123, TimeUnit.MILLISECONDS, context);
Mockito.verify(connManager).routeComplete(managedConn, route, context); Mockito.verify(endpoint).setSocketTimeout(234);
Mockito.verify(managedConn).setSocketTimeout(234); Mockito.verify(endpoint, Mockito.times(1)).execute(request, requestExecutor, context);
Mockito.verify(requestExecutor, Mockito.times(1)).execute(request, managedConn, context); Mockito.verify(endpoint, Mockito.times(1)).close();
Mockito.verify(managedConn, Mockito.times(1)).close(); Mockito.verify(connManager).release(endpoint, null, 0, TimeUnit.MILLISECONDS);
Mockito.verify(connManager).releaseConnection(managedConn, null, 0, TimeUnit.MILLISECONDS);
Assert.assertSame(managedConn, context.getConnection());
Assert.assertNotNull(finalResponse); Assert.assertNotNull(finalResponse);
Assert.assertTrue(finalResponse instanceof CloseableHttpResponse); Assert.assertTrue(finalResponse instanceof CloseableHttpResponse);
} }
@ -141,11 +139,10 @@ public void testExecRequestPersistentConnection() throws Exception {
final RoutedHttpRequest request = RoutedHttpRequest.adapt(new HttpGet("http://bar/test"), route); final RoutedHttpRequest request = RoutedHttpRequest.adapt(new HttpGet("http://bar/test"), route);
final ClassicHttpResponse response = new BasicClassicHttpResponse(200, "OK"); final ClassicHttpResponse response = new BasicClassicHttpResponse(200, "OK");
Mockito.when(managedConn.isOpen()).thenReturn(Boolean.TRUE); Mockito.when(endpoint.isConnected()).thenReturn(Boolean.TRUE);
Mockito.when(managedConn.isStale()).thenReturn(Boolean.FALSE); Mockito.when(endpoint.execute(
Mockito.when(requestExecutor.execute(
Mockito.same(request), Mockito.same(request),
Mockito.<HttpClientConnection>any(), Mockito.<HttpRequestExecutor>any(),
Mockito.<HttpClientContext>any())).thenReturn(response); Mockito.<HttpClientContext>any())).thenReturn(response);
Mockito.when(reuseStrategy.keepAlive( Mockito.when(reuseStrategy.keepAlive(
Mockito.same(request), Mockito.same(request),
@ -156,11 +153,11 @@ public void testExecRequestPersistentConnection() throws Exception {
Mockito.<HttpClientContext>any())).thenReturn(678L); Mockito.<HttpClientContext>any())).thenReturn(678L);
final ClassicHttpResponse finalResponse = minimalClientExec.execute(request, context, execAware); final ClassicHttpResponse finalResponse = minimalClientExec.execute(request, context, execAware);
Mockito.verify(connManager).requestConnection(route, null); Mockito.verify(connManager).lease(route, null);
Mockito.verify(connRequest).get(345, TimeUnit.MILLISECONDS); Mockito.verify(connRequest).get(345, TimeUnit.MILLISECONDS);
Mockito.verify(requestExecutor, Mockito.times(1)).execute(request, managedConn, context); Mockito.verify(endpoint, Mockito.times(1)).execute(request, requestExecutor, context);
Mockito.verify(connManager).releaseConnection(managedConn, null, 678L, TimeUnit.MILLISECONDS); Mockito.verify(connManager).release(endpoint, null, 678L, TimeUnit.MILLISECONDS);
Mockito.verify(managedConn, Mockito.never()).close(); Mockito.verify(endpoint, Mockito.never()).close();
Assert.assertNotNull(finalResponse); Assert.assertNotNull(finalResponse);
Assert.assertTrue(finalResponse instanceof CloseableHttpResponse); Assert.assertTrue(finalResponse instanceof CloseableHttpResponse);
@ -183,11 +180,10 @@ public void testExecRequestConnectionRelease() throws Exception {
.setStream(new ByteArrayInputStream(new byte[]{})) .setStream(new ByteArrayInputStream(new byte[]{}))
.build()); .build());
Mockito.when(managedConn.isOpen()).thenReturn(Boolean.TRUE); Mockito.when(endpoint.isConnected()).thenReturn(Boolean.TRUE);
Mockito.when(managedConn.isStale()).thenReturn(Boolean.FALSE); Mockito.when(endpoint.execute(
Mockito.when(requestExecutor.execute(
Mockito.same(request), Mockito.same(request),
Mockito.<HttpClientConnection>any(), Mockito.<HttpRequestExecutor>any(),
Mockito.<HttpClientContext>any())).thenReturn(response); Mockito.<HttpClientContext>any())).thenReturn(response);
Mockito.when(reuseStrategy.keepAlive( Mockito.when(reuseStrategy.keepAlive(
Mockito.same(request), Mockito.same(request),
@ -195,23 +191,23 @@ public void testExecRequestConnectionRelease() throws Exception {
Mockito.<HttpClientContext>any())).thenReturn(Boolean.FALSE); Mockito.<HttpClientContext>any())).thenReturn(Boolean.FALSE);
final ClassicHttpResponse finalResponse = minimalClientExec.execute(request, context, execAware); final ClassicHttpResponse finalResponse = minimalClientExec.execute(request, context, execAware);
Mockito.verify(connManager).requestConnection(route, null); Mockito.verify(connManager).lease(route, null);
Mockito.verify(connRequest).get(345, TimeUnit.MILLISECONDS); Mockito.verify(connRequest).get(345, TimeUnit.MILLISECONDS);
Mockito.verify(requestExecutor, Mockito.times(1)).execute(request, managedConn, context); Mockito.verify(endpoint, Mockito.times(1)).execute(request, requestExecutor, context);
Mockito.verify(connManager, Mockito.never()).releaseConnection( Mockito.verify(connManager, Mockito.never()).release(
Mockito.same(managedConn), Mockito.same(endpoint),
Mockito.any(), Mockito.any(),
Mockito.anyInt(), Mockito.anyInt(),
Mockito.<TimeUnit>any()); Mockito.<TimeUnit>any());
Mockito.verify(managedConn, Mockito.never()).close(); Mockito.verify(endpoint, Mockito.never()).close();
Assert.assertNotNull(finalResponse); Assert.assertNotNull(finalResponse);
Assert.assertTrue(finalResponse instanceof CloseableHttpResponse); Assert.assertTrue(finalResponse instanceof CloseableHttpResponse);
finalResponse.close(); finalResponse.close();
Mockito.verify(connManager, Mockito.times(1)).releaseConnection( Mockito.verify(connManager, Mockito.times(1)).release(
managedConn, null, 0, TimeUnit.MILLISECONDS); endpoint, null, 0, TimeUnit.MILLISECONDS);
Mockito.verify(managedConn, Mockito.times(1)).close(); Mockito.verify(endpoint, Mockito.times(1)).close();
} }
@Test @Test
@ -222,14 +218,14 @@ public void testSocketTimeoutExistingConnection() throws Exception {
final RoutedHttpRequest request = RoutedHttpRequest.adapt(new HttpGet("http://bar/test"), route); final RoutedHttpRequest request = RoutedHttpRequest.adapt(new HttpGet("http://bar/test"), route);
context.setRequestConfig(config); context.setRequestConfig(config);
final ClassicHttpResponse response = new BasicClassicHttpResponse(200, "OK"); final ClassicHttpResponse response = new BasicClassicHttpResponse(200, "OK");
Mockito.when(managedConn.isOpen()).thenReturn(true); Mockito.when(endpoint.isConnected()).thenReturn(true);
Mockito.when(requestExecutor.execute( Mockito.when(endpoint.execute(
Mockito.same(request), Mockito.same(request),
Mockito.<HttpClientConnection>any(), Mockito.<HttpRequestExecutor>any(),
Mockito.<HttpClientContext>any())).thenReturn(response); Mockito.<HttpClientContext>any())).thenReturn(response);
minimalClientExec.execute(request, context, execAware); minimalClientExec.execute(request, context, execAware);
Mockito.verify(managedConn).setSocketTimeout(3000); Mockito.verify(endpoint).setSocketTimeout(3000);
} }
@Test @Test
@ -238,14 +234,14 @@ public void testSocketTimeoutReset() throws Exception {
final HttpClientContext context = new HttpClientContext(); final HttpClientContext context = new HttpClientContext();
final RoutedHttpRequest request = RoutedHttpRequest.adapt(new HttpGet("http://bar/test"), route); final RoutedHttpRequest request = RoutedHttpRequest.adapt(new HttpGet("http://bar/test"), route);
final ClassicHttpResponse response = new BasicClassicHttpResponse(200, "OK"); final ClassicHttpResponse response = new BasicClassicHttpResponse(200, "OK");
Mockito.when(managedConn.isOpen()).thenReturn(Boolean.TRUE); Mockito.when(endpoint.isConnected()).thenReturn(Boolean.TRUE);
Mockito.when(requestExecutor.execute( Mockito.when(endpoint.execute(
Mockito.same(request), Mockito.same(request),
Mockito.<HttpClientConnection>any(), Mockito.<HttpRequestExecutor>any(),
Mockito.<HttpClientContext>any())).thenReturn(response); Mockito.<HttpClientContext>any())).thenReturn(response);
minimalClientExec.execute(request, context, execAware); minimalClientExec.execute(request, context, execAware);
Mockito.verify(managedConn, Mockito.never()).setSocketTimeout(Mockito.anyInt()); Mockito.verify(endpoint, Mockito.never()).setSocketTimeout(Mockito.anyInt());
} }
@Test(expected=RequestAbortedException.class) @Test(expected=RequestAbortedException.class)
@ -254,7 +250,7 @@ public void testExecAbortedPriorToConnectionLease() throws Exception {
final HttpClientContext context = new HttpClientContext(); final HttpClientContext context = new HttpClientContext();
final RoutedHttpRequest request = RoutedHttpRequest.adapt(new HttpGet("http://bar/test"), route); final RoutedHttpRequest request = RoutedHttpRequest.adapt(new HttpGet("http://bar/test"), route);
Mockito.when(managedConn.isOpen()).thenReturn(Boolean.FALSE); Mockito.when(endpoint.isConnected()).thenReturn(Boolean.FALSE);
Mockito.when(execAware.isAborted()).thenReturn(Boolean.TRUE); Mockito.when(execAware.isAborted()).thenReturn(Boolean.TRUE);
try { try {
minimalClientExec.execute(request, context, execAware); minimalClientExec.execute(request, context, execAware);
@ -281,9 +277,9 @@ public void testExecConnectionShutDown() throws Exception {
final HttpClientContext context = new HttpClientContext(); final HttpClientContext context = new HttpClientContext();
final RoutedHttpRequest request = RoutedHttpRequest.adapt(new HttpGet("http://bar/test"), route); final RoutedHttpRequest request = RoutedHttpRequest.adapt(new HttpGet("http://bar/test"), route);
Mockito.when(requestExecutor.execute( Mockito.when(endpoint.execute(
Mockito.<ClassicHttpRequest>any(), Mockito.same(request),
Mockito.<HttpClientConnection>any(), Mockito.<HttpRequestExecutor>any(),
Mockito.<HttpClientContext>any())).thenThrow(new ConnectionShutdownException()); Mockito.<HttpClientContext>any())).thenThrow(new ConnectionShutdownException());
minimalClientExec.execute(request, context, execAware); minimalClientExec.execute(request, context, execAware);
@ -295,16 +291,15 @@ public void testExecRuntimeException() throws Exception {
final HttpClientContext context = new HttpClientContext(); final HttpClientContext context = new HttpClientContext();
final RoutedHttpRequest request = RoutedHttpRequest.adapt(new HttpGet("http://bar/test"), route); final RoutedHttpRequest request = RoutedHttpRequest.adapt(new HttpGet("http://bar/test"), route);
Mockito.when(requestExecutor.execute( Mockito.when(endpoint.execute(
Mockito.<ClassicHttpRequest>any(), Mockito.same(request),
Mockito.<HttpClientConnection>any(), Mockito.<HttpRequestExecutor>any(),
Mockito.<HttpClientContext>any())).thenThrow(new RuntimeException("Ka-boom")); Mockito.<HttpClientContext>any())).thenThrow(new RuntimeException("Ka-boom"));
try { try {
minimalClientExec.execute(request, context, execAware); minimalClientExec.execute(request, context, execAware);
} catch (final Exception ex) { } catch (final Exception ex) {
Mockito.verify(connManager).releaseConnection(managedConn, null, 0, TimeUnit.MILLISECONDS); Mockito.verify(connManager).release(endpoint, null, 0, TimeUnit.MILLISECONDS);
throw ex; throw ex;
} }
} }
@ -315,16 +310,15 @@ public void testExecHttpException() throws Exception {
final HttpClientContext context = new HttpClientContext(); final HttpClientContext context = new HttpClientContext();
final RoutedHttpRequest request = RoutedHttpRequest.adapt(new HttpGet("http://bar/test"), route); final RoutedHttpRequest request = RoutedHttpRequest.adapt(new HttpGet("http://bar/test"), route);
Mockito.when(requestExecutor.execute( Mockito.when(endpoint.execute(
Mockito.<ClassicHttpRequest>any(), Mockito.same(request),
Mockito.<HttpClientConnection>any(), Mockito.<HttpRequestExecutor>any(),
Mockito.<HttpClientContext>any())).thenThrow(new HttpException("Ka-boom")); Mockito.<HttpClientContext>any())).thenThrow(new HttpException("Ka-boom"));
try { try {
minimalClientExec.execute(request, context, execAware); minimalClientExec.execute(request, context, execAware);
} catch (final Exception ex) { } catch (final Exception ex) {
Mockito.verify(connManager).releaseConnection(managedConn, null, 0, TimeUnit.MILLISECONDS); Mockito.verify(connManager).release(endpoint, null, 0, TimeUnit.MILLISECONDS);
throw ex; throw ex;
} }
} }
@ -335,16 +329,15 @@ public void testExecIOException() throws Exception {
final HttpClientContext context = new HttpClientContext(); final HttpClientContext context = new HttpClientContext();
final RoutedHttpRequest request = RoutedHttpRequest.adapt(new HttpGet("http://bar/test"), route); final RoutedHttpRequest request = RoutedHttpRequest.adapt(new HttpGet("http://bar/test"), route);
Mockito.when(requestExecutor.execute( Mockito.when(endpoint.execute(
Mockito.<ClassicHttpRequest>any(), Mockito.same(request),
Mockito.<HttpClientConnection>any(), Mockito.<HttpRequestExecutor>any(),
Mockito.<HttpClientContext>any())).thenThrow(new IOException("Ka-boom")); Mockito.<HttpClientContext>any())).thenThrow(new IOException("Ka-boom"));
try { try {
minimalClientExec.execute(request, context, execAware); minimalClientExec.execute(request, context, execAware);
} catch (final Exception ex) { } catch (final Exception ex) {
Mockito.verify(connManager).releaseConnection(managedConn, null, 0, TimeUnit.MILLISECONDS); Mockito.verify(connManager).release(endpoint, null, 0, TimeUnit.MILLISECONDS);
throw ex; throw ex;
} }
} }
@ -356,15 +349,15 @@ public void absoluteUriIsRewrittenToRelativeBeforeBeingPassedInRequestLine() thr
final RoutedHttpRequest request = RoutedHttpRequest.adapt(new HttpGet("http://bar/test"), route); final RoutedHttpRequest request = RoutedHttpRequest.adapt(new HttpGet("http://bar/test"), route);
final ClassicHttpResponse response = Mockito.mock(ClassicHttpResponse.class); final ClassicHttpResponse response = Mockito.mock(ClassicHttpResponse.class);
Mockito.when(requestExecutor.execute( Mockito.when(endpoint.execute(
Mockito.<ClassicHttpRequest>any(), Mockito.same(request),
Mockito.<HttpClientConnection>any(), Mockito.<HttpRequestExecutor>any(),
Mockito.<HttpClientContext>any())).thenReturn(response); Mockito.<HttpClientContext>any())).thenReturn(response);
minimalClientExec.execute(request, context, execAware); minimalClientExec.execute(request, context, execAware);
final ArgumentCaptor<ClassicHttpRequest> reqCaptor = ArgumentCaptor.forClass(ClassicHttpRequest.class); final ArgumentCaptor<ClassicHttpRequest> reqCaptor = ArgumentCaptor.forClass(ClassicHttpRequest.class);
Mockito.verify(requestExecutor).execute(reqCaptor.capture(), Mockito.<HttpClientConnection>any(), Mockito.<HttpClientContext>any()); Mockito.verify(endpoint).execute(reqCaptor.capture(), Mockito.<HttpRequestExecutor>any(), Mockito.<HttpClientContext>any());
Assert.assertEquals("/test", reqCaptor.getValue().getRequestUri()); Assert.assertEquals("/test", reqCaptor.getValue().getRequestUri());
} }

View File

@ -121,7 +121,6 @@ public void testFundamentals() throws Exception {
Mockito.<HttpClientContext>any())).thenReturn(redirect); Mockito.<HttpClientContext>any())).thenReturn(redirect);
Mockito.when(httpRoutePlanner.determineRoute( Mockito.when(httpRoutePlanner.determineRoute(
Mockito.eq(target), Mockito.eq(target),
Mockito.<RoutedHttpRequest>any(),
Mockito.<HttpClientContext>any())).thenReturn(route); Mockito.<HttpClientContext>any())).thenReturn(route);
redirectExec.execute(request, context, execAware); redirectExec.execute(request, context, execAware);
@ -173,7 +172,6 @@ public void testMaxRedirect() throws Exception {
Mockito.<HttpClientContext>any())).thenReturn(redirect); Mockito.<HttpClientContext>any())).thenReturn(redirect);
Mockito.when(httpRoutePlanner.determineRoute( Mockito.when(httpRoutePlanner.determineRoute(
Mockito.eq(target), Mockito.eq(target),
Mockito.<RoutedHttpRequest>any(),
Mockito.<HttpClientContext>any())).thenReturn(route); Mockito.<HttpClientContext>any())).thenReturn(route);
redirectExec.execute(request, context, execAware); redirectExec.execute(request, context, execAware);
@ -207,7 +205,6 @@ public void testRelativeRedirect() throws Exception {
Mockito.<HttpClientContext>any())).thenReturn(redirect); Mockito.<HttpClientContext>any())).thenReturn(redirect);
Mockito.when(httpRoutePlanner.determineRoute( Mockito.when(httpRoutePlanner.determineRoute(
Mockito.eq(target), Mockito.eq(target),
Mockito.<RoutedHttpRequest>any(),
Mockito.<HttpClientContext>any())).thenReturn(route); Mockito.<HttpClientContext>any())).thenReturn(route);
redirectExec.execute(request, context, execAware); redirectExec.execute(request, context, execAware);
@ -253,11 +250,9 @@ public void testCrossSiteRedirect() throws Exception {
Mockito.<HttpClientContext>any())).thenReturn(redirect); Mockito.<HttpClientContext>any())).thenReturn(redirect);
Mockito.when(httpRoutePlanner.determineRoute( Mockito.when(httpRoutePlanner.determineRoute(
Mockito.eq(target), Mockito.eq(target),
Mockito.<RoutedHttpRequest>any(),
Mockito.<HttpClientContext>any())).thenReturn(new HttpRoute(target)); Mockito.<HttpClientContext>any())).thenReturn(new HttpRoute(target));
Mockito.when(httpRoutePlanner.determineRoute( Mockito.when(httpRoutePlanner.determineRoute(
Mockito.eq(otherHost), Mockito.eq(otherHost),
Mockito.<RoutedHttpRequest>any(),
Mockito.<HttpClientContext>any())).thenReturn(new HttpRoute(otherHost)); Mockito.<HttpClientContext>any())).thenReturn(new HttpRoute(otherHost));
redirectExec.execute(request, context, execAware); redirectExec.execute(request, context, execAware);

View File

@ -43,7 +43,7 @@ public class TestResponseEntityWrapper {
private InputStream instream; private InputStream instream;
private HttpEntity entity; private HttpEntity entity;
private ConnectionHolder connHolder; private EndpointHolder endpointHolder;
private ResponseEntityProxy wrapper; private ResponseEntityProxy wrapper;
@Before @Before
@ -51,83 +51,83 @@ public void setup() throws Exception {
instream = Mockito.mock(InputStream.class); instream = Mockito.mock(InputStream.class);
entity = Mockito.mock(HttpEntity.class); entity = Mockito.mock(HttpEntity.class);
Mockito.when(entity.getContent()).thenReturn(instream); Mockito.when(entity.getContent()).thenReturn(instream);
connHolder = Mockito.mock(ConnectionHolder.class); endpointHolder = Mockito.mock(EndpointHolder.class);
wrapper = new ResponseEntityProxy(entity, connHolder); wrapper = new ResponseEntityProxy(entity, endpointHolder);
} }
@Test @Test
public void testReusableEntityStreamClosed() throws Exception { public void testReusableEntityStreamClosed() throws Exception {
Mockito.when(entity.isStreaming()).thenReturn(true); Mockito.when(entity.isStreaming()).thenReturn(true);
Mockito.when(connHolder.isReusable()).thenReturn(true); Mockito.when(endpointHolder.isReusable()).thenReturn(true);
EntityUtils.consume(wrapper); EntityUtils.consume(wrapper);
Mockito.verify(instream, Mockito.times(1)).close(); Mockito.verify(instream, Mockito.times(1)).close();
Mockito.verify(connHolder).releaseConnection(); Mockito.verify(endpointHolder).releaseConnection();
} }
@Test @Test
public void testReusableEntityStreamClosedIOError() throws Exception { public void testReusableEntityStreamClosedIOError() throws Exception {
Mockito.when(entity.isStreaming()).thenReturn(true); Mockito.when(entity.isStreaming()).thenReturn(true);
Mockito.when(connHolder.isReusable()).thenReturn(true); Mockito.when(endpointHolder.isReusable()).thenReturn(true);
Mockito.doThrow(new IOException()).when(instream).close(); Mockito.doThrow(new IOException()).when(instream).close();
try { try {
EntityUtils.consume(wrapper); EntityUtils.consume(wrapper);
Assert.fail("IOException expected"); Assert.fail("IOException expected");
} catch (final IOException ex) { } catch (final IOException ex) {
} }
Mockito.verify(connHolder).abortConnection(); Mockito.verify(endpointHolder).abortConnection();
} }
@Test @Test
public void testEntityStreamClosedIOErrorAlreadyReleased() throws Exception { public void testEntityStreamClosedIOErrorAlreadyReleased() throws Exception {
Mockito.when(entity.isStreaming()).thenReturn(true); Mockito.when(entity.isStreaming()).thenReturn(true);
Mockito.when(connHolder.isReusable()).thenReturn(true); Mockito.when(endpointHolder.isReusable()).thenReturn(true);
Mockito.when(connHolder.isReleased()).thenReturn(true); Mockito.when(endpointHolder.isReleased()).thenReturn(true);
Mockito.doThrow(new SocketException()).when(instream).close(); Mockito.doThrow(new SocketException()).when(instream).close();
EntityUtils.consume(wrapper); EntityUtils.consume(wrapper);
Mockito.verify(connHolder).close(); Mockito.verify(endpointHolder).close();
} }
@Test @Test
public void testReusableEntityWriteTo() throws Exception { public void testReusableEntityWriteTo() throws Exception {
final OutputStream outstream = Mockito.mock(OutputStream.class); final OutputStream outstream = Mockito.mock(OutputStream.class);
Mockito.when(entity.isStreaming()).thenReturn(true); Mockito.when(entity.isStreaming()).thenReturn(true);
Mockito.when(connHolder.isReusable()).thenReturn(true); Mockito.when(endpointHolder.isReusable()).thenReturn(true);
wrapper.writeTo(outstream); wrapper.writeTo(outstream);
Mockito.verify(connHolder).releaseConnection(); Mockito.verify(endpointHolder).releaseConnection();
} }
@Test @Test
public void testReusableEntityWriteToIOError() throws Exception { public void testReusableEntityWriteToIOError() throws Exception {
final OutputStream outstream = Mockito.mock(OutputStream.class); final OutputStream outstream = Mockito.mock(OutputStream.class);
Mockito.when(entity.isStreaming()).thenReturn(true); Mockito.when(entity.isStreaming()).thenReturn(true);
Mockito.when(connHolder.isReusable()).thenReturn(true); Mockito.when(endpointHolder.isReusable()).thenReturn(true);
Mockito.doThrow(new IOException()).when(entity).writeTo(outstream); Mockito.doThrow(new IOException()).when(entity).writeTo(outstream);
try { try {
wrapper.writeTo(outstream); wrapper.writeTo(outstream);
Assert.fail("IOException expected"); Assert.fail("IOException expected");
} catch (final IOException ex) { } catch (final IOException ex) {
} }
Mockito.verify(connHolder, Mockito.never()).releaseConnection(); Mockito.verify(endpointHolder, Mockito.never()).releaseConnection();
Mockito.verify(connHolder).abortConnection(); Mockito.verify(endpointHolder).abortConnection();
} }
@Test @Test
public void testReusableEntityEndOfStream() throws Exception { public void testReusableEntityEndOfStream() throws Exception {
Mockito.when(instream.read()).thenReturn(-1); Mockito.when(instream.read()).thenReturn(-1);
Mockito.when(entity.isStreaming()).thenReturn(true); Mockito.when(entity.isStreaming()).thenReturn(true);
Mockito.when(connHolder.isReusable()).thenReturn(true); Mockito.when(endpointHolder.isReusable()).thenReturn(true);
final InputStream content = wrapper.getContent(); final InputStream content = wrapper.getContent();
Assert.assertEquals(-1, content.read()); Assert.assertEquals(-1, content.read());
Mockito.verify(instream).close(); Mockito.verify(instream).close();
Mockito.verify(connHolder).releaseConnection(); Mockito.verify(endpointHolder).releaseConnection();
} }
@Test @Test
public void testReusableEntityEndOfStreamIOError() throws Exception { public void testReusableEntityEndOfStreamIOError() throws Exception {
Mockito.when(instream.read()).thenReturn(-1); Mockito.when(instream.read()).thenReturn(-1);
Mockito.when(entity.isStreaming()).thenReturn(true); Mockito.when(entity.isStreaming()).thenReturn(true);
Mockito.when(connHolder.isReusable()).thenReturn(true); Mockito.when(endpointHolder.isReusable()).thenReturn(true);
Mockito.doThrow(new IOException()).when(instream).close(); Mockito.doThrow(new IOException()).when(instream).close();
final InputStream content = wrapper.getContent(); final InputStream content = wrapper.getContent();
try { try {
@ -135,7 +135,7 @@ public void testReusableEntityEndOfStreamIOError() throws Exception {
Assert.fail("IOException expected"); Assert.fail("IOException expected");
} catch (final IOException ex) { } catch (final IOException ex) {
} }
Mockito.verify(connHolder).abortConnection(); Mockito.verify(endpointHolder).abortConnection();
} }
} }