/*
* SoapUI, Copyright (C) 2004-2016 SmartBear Software
*
* Licensed under the EUPL, Version 1.1 or - as soon as they will be approved by the European Commission - subsequent
* versions of the EUPL (the "Licence");
* You may not use this work except in compliance with the Licence.
* You may obtain a copy of the Licence at:
*
* http://ec.europa.eu/idabc/eupl
*
* Unless required by applicable law or agreed to in writing, software distributed under the Licence is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the Licence for the specific language governing permissions and limitations
* under the Licence.
*/
package com.eviware.soapui.impl.wsdl.support.http;
import com.eviware.soapui.SoapUI;
import com.eviware.soapui.impl.wsdl.submit.transports.http.support.metrics.SoapUIMetrics;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.http.HttpHost;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.ClientConnectionOperator;
import org.apache.http.conn.ClientConnectionRequest;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.ConnectionPoolTimeoutException;
import org.apache.http.conn.HttpHostConnectException;
import org.apache.http.conn.ManagedClientConnection;
import org.apache.http.conn.OperatedClientConnection;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.scheme.SchemeSocketFactory;
import org.apache.http.impl.HttpConnectionMetricsImpl;
import org.apache.http.impl.conn.AbstractPoolEntry;
import org.apache.http.impl.conn.DefaultClientConnection;
import org.apache.http.impl.conn.DefaultClientConnectionOperator;
import org.apache.http.impl.conn.tsccm.BasicPoolEntry;
import org.apache.http.impl.conn.tsccm.BasicPooledConnAdapter;
import org.apache.http.impl.conn.tsccm.PoolEntryRequest;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.io.HttpTransportMetrics;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.HttpContext;
import org.apache.log4j.Logger;
import javax.net.ssl.SSLSocket;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.ConnectException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.concurrent.TimeUnit;
/**
* Manages a set of HttpConnections for various HostConfigurations. Modified to
* keep different pools for different keystores.
*/
public class SoapUIMultiThreadedHttpConnectionManager extends ThreadSafeClientConnManager {
/**
* Log object for this class.
*/
private static final Logger log = Logger.getLogger(SoapUIMultiThreadedHttpConnectionManager.class);
/**
* Connection eviction policy
*/
IdleConnectionMonitorThread idleConnectionHandler = new IdleConnectionMonitorThread(this);
public SoapUIMultiThreadedHttpConnectionManager(SchemeRegistry registry) {
super(registry);
idleConnectionHandler.start();
}
/**
* Hook for creating the connection operator. It is called by the
* constructor. Derived classes can override this method to change the
* instantiation of the operator. The default implementation here
* instantiates {@link DefaultClientConnectionOperator
* DefaultClientConnectionOperator}.
*
* @param schreg the scheme registry.
* @return the connection operator to use
*/
@Override
protected ClientConnectionOperator createConnectionOperator(SchemeRegistry schreg) {
return new SoapUIClientConnectionOperator(schreg);// @ThreadSafe
}
public static class IdleConnectionMonitorThread extends Thread {
private final ClientConnectionManager connMgr;
private volatile boolean shutdown;
public IdleConnectionMonitorThread(ClientConnectionManager connMgr) {
super();
this.connMgr = connMgr;
}
@Override
public void run() {
try {
while (!shutdown) {
synchronized (this) {
wait(5000);
// Close expired connections
connMgr.closeExpiredConnections();
// Optionally, close connections
// that have been idle longer than 30 sec
connMgr.closeIdleConnections(30, TimeUnit.SECONDS);
}
}
} catch (InterruptedException ex) {
// terminate
}
}
public void shutdown() {
shutdown = true;
synchronized (this) {
notifyAll();
}
}
}
public ClientConnectionRequest requestConnection(final HttpRoute route, final Object state) {
final PoolEntryRequest poolRequest = pool.requestPoolEntry(route, state);
return new ClientConnectionRequest() {
public void abortRequest() {
poolRequest.abortRequest();
}
public ManagedClientConnection getConnection(long timeout, TimeUnit tunit) throws InterruptedException,
ConnectionPoolTimeoutException {
if (route == null) {
throw new IllegalArgumentException("Route may not be null.");
}
if (log.isDebugEnabled()) {
log.debug("Get connection: " + route + ", timeout = " + timeout);
}
BasicPoolEntry entry = poolRequest.getPoolEntry(timeout, tunit);
SoapUIBasicPooledConnAdapter connAdapter = new SoapUIBasicPooledConnAdapter(
SoapUIMultiThreadedHttpConnectionManager.this, entry);
return connAdapter;
}
};
}
public void releaseConnection(ManagedClientConnection conn, long validDuration, TimeUnit timeUnit) {
if (!(conn instanceof SoapUIBasicPooledConnAdapter)) {
throw new IllegalArgumentException("Connection class mismatch, "
+ "connection not obtained from this manager.");
}
SoapUIBasicPooledConnAdapter hca = (SoapUIBasicPooledConnAdapter) conn;
if ((hca.getPoolEntry() != null) && (hca.getManager() != this)) {
throw new IllegalArgumentException("Connection not obtained from this manager.");
}
synchronized (hca) {
BasicPoolEntry entry = (BasicPoolEntry) hca.getPoolEntry();
if (entry == null) {
return;
}
try {
// make sure that the response has been read completely
if (hca.isOpen() && !hca.isMarkedReusable()) {
// In MTHCM, there would be a call to
// SimpleHttpConnectionManager.finishLastResponse(conn);
// Consuming the response is handled outside in 4.0.
// make sure this connection will not be re-used
// Shut down rather than close, we might have gotten here
// because of a shutdown trigger.
// Shutdown of the adapter also clears the tracked route.
hca.shutdown();
}
} catch (IOException iox) {
if (log.isDebugEnabled()) {
log.debug("Exception shutting down released connection.", iox);
}
} finally {
boolean reusable = hca.isMarkedReusable();
if (log.isDebugEnabled()) {
if (reusable) {
log.debug("Released connection is reusable.");
} else {
log.debug("Released connection is not reusable.");
}
}
hca.detach();
pool.freeEntry(entry, reusable, validDuration, timeUnit);
}
}
}
@Override
public void shutdown() {
super.shutdown(); //To change body of generated methods, choose Tools | Templates.
idleConnectionHandler.shutdown();
}
private class SoapUIClientConnectionOperator extends DefaultClientConnectionOperator {
public SoapUIClientConnectionOperator(SchemeRegistry schemes) {
super(schemes);
}
@Override
public OperatedClientConnection createConnection() {
SoapUIDefaultClientConnection connection = new SoapUIDefaultClientConnection();
return connection;
}
@Override
public void openConnection(final OperatedClientConnection conn, final HttpHost target, final InetAddress local,
final HttpContext context, final HttpParams params) throws IOException {
if (conn == null) {
throw new IllegalArgumentException("Connection may not be null");
}
if (target == null) {
throw new IllegalArgumentException("Target host may not be null");
}
if (params == null) {
throw new IllegalArgumentException("Parameters may not be null");
}
if (conn.isOpen()) {
throw new IllegalStateException("Connection must not be open");
}
Scheme schm = schemeRegistry.getScheme(target.getSchemeName());
SchemeSocketFactory sf = schm.getSchemeSocketFactory();
//long start = System.nanoTime();
long start = System.currentTimeMillis();
InetAddress[] addresses = resolveHostname(target.getHostName());
// long dnsEnd = System.nanoTime();
long dnsEnd = System.currentTimeMillis();
int port = schm.resolvePort(target.getPort());
for (int i = 0; i < addresses.length; i++) {
InetAddress address = addresses[i];
boolean last = i == addresses.length - 1;
Socket sock = sf.createSocket(params);
try {
// hostname is required by web server with virtual hosts and one IP (TLS-SNI)
if (sock instanceof SSLSocket) {
PropertyUtils.setProperty(sock, "host", target.getHostName());
}
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
SoapUI.logError(e);
}
conn.opening(sock, target);
InetSocketAddress remoteAddress = new InetSocketAddress(address, port);
InetSocketAddress localAddress = null;
if (local != null) {
localAddress = new InetSocketAddress(local, 0);
}
if (log.isDebugEnabled()) {
log.debug("Connecting to " + remoteAddress);
}
try {
Socket connsock = sf.connectSocket(sock, remoteAddress, localAddress, params);
if (sock != connsock) {
sock = connsock;
conn.opening(sock, target);
}
prepareSocket(sock, context, params);
conn.openCompleted(sf.isSecure(sock), params);
SoapUIMetrics metrics = (SoapUIMetrics) conn.getMetrics();
if (metrics != null) {
metrics.getDNSTimer().set(start, dnsEnd);
}
return;
} catch (ConnectException ex) {
if (last) {
throw new HttpHostConnectException(target, ex);
}
} catch (ConnectTimeoutException ex) {
if (last) {
throw ex;
}
}
if (log.isDebugEnabled()) {
log.debug("Connect to " + remoteAddress + " timed out. "
+ "Connection will be retried using another IP address");
}
}
}
}
private class SoapUIDefaultClientConnection extends DefaultClientConnection {
public SoapUIDefaultClientConnection() {
super();
}
@Override
/**
* @since 4.1
*/
protected HttpConnectionMetricsImpl createConnectionMetrics(final HttpTransportMetrics inTransportMetric,
final HttpTransportMetrics outTransportMetric) {
return new SoapUIMetrics(inTransportMetric, outTransportMetric);
}
}
static class SoapUIBasicPooledConnAdapter extends BasicPooledConnAdapter {
protected SoapUIBasicPooledConnAdapter(ThreadSafeClientConnManager tsccm, AbstractPoolEntry entry) {
super(tsccm, entry);
}
@Override
protected ClientConnectionManager getManager() {
// override needed only to make method visible in this package
return super.getManager();
}
@Override
protected AbstractPoolEntry getPoolEntry() {
// override needed only to make method visible in this package
return super.getPoolEntry();
}
@Override
protected void detach() {
// override needed only to make method visible in this package
super.detach();
}
}
}