417356 - Add SOCKS support to jetty client.
Big refactoring to allow for additional proxy schemes that work at a lower level than HTTP. Introduced client-side ConnectionFactory, and binding that to a HttpDestination, so that connections to that destination will use the same ConnectionFactory. The destination's ConnectionFactory is now initialized from the proxy configuration and the transport, which is now itself a ConnectionFactory. The proxy configuration has also changed becoming polymorphic by introducing a new ProxyConfiguration.Proxy abstract class, which is implemented as HTTPProxy and can be implemented in future as SOCKS4Proxy (and possibly others).
This commit is contained in:
parent
8eaedc4a5e
commit
25d9b8704f
|
@ -19,24 +19,20 @@
|
||||||
package org.eclipse.jetty.client;
|
package org.eclipse.jetty.client;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.ConnectException;
|
|
||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
import java.net.SocketException;
|
|
||||||
import java.nio.channels.SelectionKey;
|
import java.nio.channels.SelectionKey;
|
||||||
import java.nio.channels.SocketChannel;
|
import java.nio.channels.SocketChannel;
|
||||||
import javax.net.ssl.SSLEngine;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.eclipse.jetty.http.HttpScheme;
|
import org.eclipse.jetty.client.api.Connection;
|
||||||
import org.eclipse.jetty.io.Connection;
|
|
||||||
import org.eclipse.jetty.io.EndPoint;
|
import org.eclipse.jetty.io.EndPoint;
|
||||||
import org.eclipse.jetty.io.SelectChannelEndPoint;
|
import org.eclipse.jetty.io.SelectChannelEndPoint;
|
||||||
import org.eclipse.jetty.io.SelectorManager;
|
import org.eclipse.jetty.io.SelectorManager;
|
||||||
import org.eclipse.jetty.io.ssl.SslConnection;
|
import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
|
||||||
import org.eclipse.jetty.util.Promise;
|
import org.eclipse.jetty.util.Promise;
|
||||||
import org.eclipse.jetty.util.component.ContainerLifeCycle;
|
import org.eclipse.jetty.util.component.ContainerLifeCycle;
|
||||||
import org.eclipse.jetty.util.log.Log;
|
import org.eclipse.jetty.util.log.Log;
|
||||||
import org.eclipse.jetty.util.log.Logger;
|
import org.eclipse.jetty.util.log.Logger;
|
||||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
|
||||||
|
|
||||||
public abstract class AbstractHttpClientTransport extends ContainerLifeCycle implements HttpClientTransport
|
public abstract class AbstractHttpClientTransport extends ContainerLifeCycle implements HttpClientTransport
|
||||||
{
|
{
|
||||||
|
@ -72,12 +68,20 @@ public abstract class AbstractHttpClientTransport extends ContainerLifeCycle imp
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void connect(HttpDestination destination, SocketAddress address, Promise<org.eclipse.jetty.client.api.Connection> promise)
|
protected void doStop() throws Exception
|
||||||
|
{
|
||||||
|
super.doStop();
|
||||||
|
removeBean(selectorManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void connect(SocketAddress address, Map<String, Object> context)
|
||||||
{
|
{
|
||||||
SocketChannel channel = null;
|
SocketChannel channel = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
channel = SocketChannel.open();
|
channel = SocketChannel.open();
|
||||||
|
HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
|
||||||
HttpClient client = destination.getHttpClient();
|
HttpClient client = destination.getHttpClient();
|
||||||
SocketAddress bindAddress = client.getBindAddress();
|
SocketAddress bindAddress = client.getBindAddress();
|
||||||
if (bindAddress != null)
|
if (bindAddress != null)
|
||||||
|
@ -86,8 +90,9 @@ public abstract class AbstractHttpClientTransport extends ContainerLifeCycle imp
|
||||||
channel.configureBlocking(false);
|
channel.configureBlocking(false);
|
||||||
channel.connect(address);
|
channel.connect(address);
|
||||||
|
|
||||||
ConnectionCallback callback = new ConnectionCallback(destination, promise);
|
context.put(SslClientConnectionFactory.SSL_PEER_HOST_CONTEXT_KEY, destination.getHost());
|
||||||
selectorManager.connect(channel, callback);
|
context.put(SslClientConnectionFactory.SSL_PEER_PORT_CONTEXT_KEY, destination.getPort());
|
||||||
|
selectorManager.connect(channel, context);
|
||||||
}
|
}
|
||||||
// Must catch all exceptions, since some like
|
// Must catch all exceptions, since some like
|
||||||
// UnresolvedAddressException are not IOExceptions.
|
// UnresolvedAddressException are not IOExceptions.
|
||||||
|
@ -104,12 +109,14 @@ public abstract class AbstractHttpClientTransport extends ContainerLifeCycle imp
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Promise<Connection> promise = (Promise<Connection>)context.get(HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
|
||||||
promise.failed(x);
|
promise.failed(x);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void configure(HttpClient client, SocketChannel channel) throws SocketException
|
protected void configure(HttpClient client, SocketChannel channel) throws IOException
|
||||||
{
|
{
|
||||||
channel.socket().setTcpNoDelay(client.isTCPNoDelay());
|
channel.socket().setTcpNoDelay(client.isTCPNoDelay());
|
||||||
}
|
}
|
||||||
|
@ -119,40 +126,6 @@ public abstract class AbstractHttpClientTransport extends ContainerLifeCycle imp
|
||||||
return new ClientSelectorManager(client, selectors);
|
return new ClientSelectorManager(client, selectors);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected SslConnection createSslConnection(EndPoint endPoint, HttpDestination destination)
|
|
||||||
{
|
|
||||||
HttpClient httpClient = destination.getHttpClient();
|
|
||||||
SslContextFactory sslContextFactory = httpClient.getSslContextFactory();
|
|
||||||
SSLEngine engine = sslContextFactory.newSSLEngine(destination.getHost(), destination.getPort());
|
|
||||||
engine.setUseClientMode(true);
|
|
||||||
|
|
||||||
SslConnection sslConnection = newSslConnection(httpClient, endPoint, engine);
|
|
||||||
sslConnection.setRenegotiationAllowed(sslContextFactory.isRenegotiationAllowed());
|
|
||||||
endPoint.setConnection(sslConnection);
|
|
||||||
EndPoint appEndPoint = sslConnection.getDecryptedEndPoint();
|
|
||||||
Connection connection = newConnection(appEndPoint, destination);
|
|
||||||
appEndPoint.setConnection(connection);
|
|
||||||
|
|
||||||
return sslConnection;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected SslConnection newSslConnection(HttpClient httpClient, EndPoint endPoint, SSLEngine engine)
|
|
||||||
{
|
|
||||||
return new SslConnection(httpClient.getByteBufferPool(), httpClient.getExecutor(), endPoint, engine);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract Connection newConnection(EndPoint endPoint, HttpDestination destination);
|
|
||||||
|
|
||||||
protected org.eclipse.jetty.client.api.Connection tunnel(EndPoint endPoint, HttpDestination destination, org.eclipse.jetty.client.api.Connection connection)
|
|
||||||
{
|
|
||||||
SslConnection sslConnection = createSslConnection(endPoint, destination);
|
|
||||||
Connection result = sslConnection.getDecryptedEndPoint().getConnection();
|
|
||||||
selectorManager.connectionClosed((Connection)connection);
|
|
||||||
selectorManager.connectionOpened(sslConnection);
|
|
||||||
LOG.debug("Tunnelled {} over {}", connection, result);
|
|
||||||
return (org.eclipse.jetty.client.api.Connection)result;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected class ClientSelectorManager extends SelectorManager
|
protected class ClientSelectorManager extends SelectorManager
|
||||||
{
|
{
|
||||||
private final HttpClient client;
|
private final HttpClient client;
|
||||||
|
@ -170,63 +143,21 @@ public abstract class AbstractHttpClientTransport extends ContainerLifeCycle imp
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Connection newConnection(SocketChannel channel, EndPoint endPoint, Object attachment) throws IOException
|
public org.eclipse.jetty.io.Connection newConnection(SocketChannel channel, EndPoint endPoint, Object attachment) throws IOException
|
||||||
{
|
{
|
||||||
ConnectionCallback callback = (ConnectionCallback)attachment;
|
@SuppressWarnings("unchecked")
|
||||||
HttpDestination destination = callback.destination;
|
Map<String, Object> context = (Map<String, Object>)attachment;
|
||||||
|
HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
|
||||||
SslContextFactory sslContextFactory = client.getSslContextFactory();
|
return destination.getClientConnectionFactory().newConnection(endPoint, context);
|
||||||
if (!destination.isProxied() && HttpScheme.HTTPS.is(destination.getScheme()))
|
|
||||||
{
|
|
||||||
if (sslContextFactory == null)
|
|
||||||
{
|
|
||||||
IOException failure = new ConnectException("Missing " + SslContextFactory.class.getSimpleName() + " for " + destination.getScheme() + " requests");
|
|
||||||
callback.failed(failure);
|
|
||||||
throw failure;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
SslConnection sslConnection = createSslConnection(endPoint, destination);
|
|
||||||
callback.succeeded((org.eclipse.jetty.client.api.Connection)sslConnection.getDecryptedEndPoint().getConnection());
|
|
||||||
return sslConnection;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Connection connection = AbstractHttpClientTransport.this.newConnection(endPoint, destination);
|
|
||||||
callback.succeeded((org.eclipse.jetty.client.api.Connection)connection);
|
|
||||||
return connection;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void connectionFailed(SocketChannel channel, Throwable ex, Object attachment)
|
protected void connectionFailed(SocketChannel channel, Throwable x, Object attachment)
|
||||||
{
|
|
||||||
ConnectionCallback callback = (ConnectionCallback)attachment;
|
|
||||||
callback.failed(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ConnectionCallback implements Promise<org.eclipse.jetty.client.api.Connection>
|
|
||||||
{
|
|
||||||
private final HttpDestination destination;
|
|
||||||
private final Promise<org.eclipse.jetty.client.api.Connection> promise;
|
|
||||||
|
|
||||||
private ConnectionCallback(HttpDestination destination, Promise<org.eclipse.jetty.client.api.Connection> promise)
|
|
||||||
{
|
|
||||||
this.destination = destination;
|
|
||||||
this.promise = promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void succeeded(org.eclipse.jetty.client.api.Connection result)
|
|
||||||
{
|
|
||||||
promise.succeeded(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void failed(Throwable x)
|
|
||||||
{
|
{
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<String, Object> context = (Map<String, Object>)attachment;
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Promise<Connection> promise = (Promise<Connection>)context.get(HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
|
||||||
promise.failed(x);
|
promise.failed(x);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -109,16 +109,19 @@ public abstract class AuthenticationProtocolHandler implements ProtocolHandler
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
URI uri = getAuthenticationURI(request);
|
|
||||||
Authentication authentication = null;
|
Authentication authentication = null;
|
||||||
Authentication.HeaderInfo headerInfo = null;
|
Authentication.HeaderInfo headerInfo = null;
|
||||||
for (Authentication.HeaderInfo element : headerInfos)
|
URI uri = getAuthenticationURI(request);
|
||||||
|
if (uri != null)
|
||||||
{
|
{
|
||||||
authentication = client.getAuthenticationStore().findAuthentication(element.getType(), uri, element.getRealm());
|
for (Authentication.HeaderInfo element : headerInfos)
|
||||||
if (authentication != null)
|
|
||||||
{
|
{
|
||||||
headerInfo = element;
|
authentication = client.getAuthenticationStore().findAuthentication(element.getType(), uri, element.getRealm());
|
||||||
break;
|
if (authentication != null)
|
||||||
|
{
|
||||||
|
headerInfo = element;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (authentication == null)
|
if (authentication == null)
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
//
|
//
|
||||||
|
|
||||||
package org.eclipse.jetty.client.http;
|
package org.eclipse.jetty.client;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.concurrent.BlockingDeque;
|
import java.util.concurrent.BlockingDeque;
|
||||||
|
@ -33,9 +33,9 @@ import org.eclipse.jetty.util.component.Dumpable;
|
||||||
import org.eclipse.jetty.util.log.Log;
|
import org.eclipse.jetty.util.log.Log;
|
||||||
import org.eclipse.jetty.util.log.Logger;
|
import org.eclipse.jetty.util.log.Logger;
|
||||||
|
|
||||||
public class HttpConnectionPool implements Dumpable
|
public class ConnectionPool implements Dumpable
|
||||||
{
|
{
|
||||||
private static final Logger LOG = Log.getLogger(HttpConnectionPool.class);
|
private static final Logger LOG = Log.getLogger(ConnectionPool.class);
|
||||||
|
|
||||||
private final AtomicInteger connectionCount = new AtomicInteger();
|
private final AtomicInteger connectionCount = new AtomicInteger();
|
||||||
private final Destination destination;
|
private final Destination destination;
|
||||||
|
@ -44,7 +44,7 @@ public class HttpConnectionPool implements Dumpable
|
||||||
private final BlockingDeque<Connection> idleConnections;
|
private final BlockingDeque<Connection> idleConnections;
|
||||||
private final BlockingQueue<Connection> activeConnections;
|
private final BlockingQueue<Connection> activeConnections;
|
||||||
|
|
||||||
public HttpConnectionPool(Destination destination, int maxConnections, Promise<Connection> connectionPromise)
|
public ConnectionPool(Destination destination, int maxConnections, Promise<Connection> connectionPromise)
|
||||||
{
|
{
|
||||||
this.destination = destination;
|
this.destination = destination;
|
||||||
this.maxConnections = maxConnections;
|
this.maxConnections = maxConnections;
|
|
@ -27,10 +27,12 @@ import java.net.URI;
|
||||||
import java.nio.channels.SocketChannel;
|
import java.nio.channels.SocketChannel;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
@ -44,7 +46,6 @@ import org.eclipse.jetty.client.api.AuthenticationStore;
|
||||||
import org.eclipse.jetty.client.api.Connection;
|
import org.eclipse.jetty.client.api.Connection;
|
||||||
import org.eclipse.jetty.client.api.ContentResponse;
|
import org.eclipse.jetty.client.api.ContentResponse;
|
||||||
import org.eclipse.jetty.client.api.Destination;
|
import org.eclipse.jetty.client.api.Destination;
|
||||||
import org.eclipse.jetty.client.api.ProxyConfiguration;
|
|
||||||
import org.eclipse.jetty.client.api.Request;
|
import org.eclipse.jetty.client.api.Request;
|
||||||
import org.eclipse.jetty.client.api.Response;
|
import org.eclipse.jetty.client.api.Response;
|
||||||
import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
|
import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
|
||||||
|
@ -57,7 +58,6 @@ import org.eclipse.jetty.io.MappedByteBufferPool;
|
||||||
import org.eclipse.jetty.util.Jetty;
|
import org.eclipse.jetty.util.Jetty;
|
||||||
import org.eclipse.jetty.util.Promise;
|
import org.eclipse.jetty.util.Promise;
|
||||||
import org.eclipse.jetty.util.SocketAddressResolver;
|
import org.eclipse.jetty.util.SocketAddressResolver;
|
||||||
import org.eclipse.jetty.util.URIUtil;
|
|
||||||
import org.eclipse.jetty.util.component.ContainerLifeCycle;
|
import org.eclipse.jetty.util.component.ContainerLifeCycle;
|
||||||
import org.eclipse.jetty.util.log.Log;
|
import org.eclipse.jetty.util.log.Log;
|
||||||
import org.eclipse.jetty.util.log.Logger;
|
import org.eclipse.jetty.util.log.Logger;
|
||||||
|
@ -104,12 +104,13 @@ public class HttpClient extends ContainerLifeCycle
|
||||||
{
|
{
|
||||||
private static final Logger LOG = Log.getLogger(HttpClient.class);
|
private static final Logger LOG = Log.getLogger(HttpClient.class);
|
||||||
|
|
||||||
private final ConcurrentMap<String, HttpDestination> destinations = new ConcurrentHashMap<>();
|
private final ConcurrentMap<Origin, HttpDestination> destinations = new ConcurrentHashMap<>();
|
||||||
private final ConcurrentMap<Long, HttpConversation> conversations = new ConcurrentHashMap<>();
|
private final ConcurrentMap<Long, HttpConversation> conversations = new ConcurrentHashMap<>();
|
||||||
private final List<ProtocolHandler> handlers = new ArrayList<>();
|
private final List<ProtocolHandler> handlers = new ArrayList<>();
|
||||||
private final List<Request.Listener> requestListeners = new ArrayList<>();
|
private final List<Request.Listener> requestListeners = new ArrayList<>();
|
||||||
private final AuthenticationStore authenticationStore = new HttpAuthenticationStore();
|
private final AuthenticationStore authenticationStore = new HttpAuthenticationStore();
|
||||||
private final Set<ContentDecoder.Factory> decoderFactories = new ContentDecoderFactorySet();
|
private final Set<ContentDecoder.Factory> decoderFactories = new ContentDecoderFactorySet();
|
||||||
|
private final ProxyConfiguration proxyConfig = new ProxyConfiguration();
|
||||||
private final HttpClientTransport transport;
|
private final HttpClientTransport transport;
|
||||||
private final SslContextFactory sslContextFactory;
|
private final SslContextFactory sslContextFactory;
|
||||||
private volatile CookieManager cookieManager;
|
private volatile CookieManager cookieManager;
|
||||||
|
@ -132,7 +133,6 @@ public class HttpClient extends ContainerLifeCycle
|
||||||
private volatile boolean tcpNoDelay = true;
|
private volatile boolean tcpNoDelay = true;
|
||||||
private volatile boolean dispatchIO = true;
|
private volatile boolean dispatchIO = true;
|
||||||
private volatile boolean strictEventOrdering = false;
|
private volatile boolean strictEventOrdering = false;
|
||||||
private volatile ProxyConfiguration proxyConfig;
|
|
||||||
private volatile HttpField encodingField;
|
private volatile HttpField encodingField;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -359,7 +359,7 @@ public class HttpClient extends ContainerLifeCycle
|
||||||
*/
|
*/
|
||||||
public Request newRequest(String host, int port)
|
public Request newRequest(String host, int port)
|
||||||
{
|
{
|
||||||
return newRequest(address("http", host, port));
|
return newRequest(new Origin("http", host, port).asString());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -417,13 +417,6 @@ public class HttpClient extends ContainerLifeCycle
|
||||||
return newRequest;
|
return newRequest;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String address(String scheme, String host, int port)
|
|
||||||
{
|
|
||||||
StringBuilder result = new StringBuilder();
|
|
||||||
URIUtil.appendSchemeHostPort(result, scheme, host, port);
|
|
||||||
return result.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a {@link Destination} for the given scheme, host and port.
|
* Returns a {@link Destination} for the given scheme, host and port.
|
||||||
* Applications may use {@link Destination}s to create {@link Connection}s
|
* Applications may use {@link Destination}s to create {@link Connection}s
|
||||||
|
@ -446,20 +439,20 @@ public class HttpClient extends ContainerLifeCycle
|
||||||
{
|
{
|
||||||
port = normalizePort(scheme, port);
|
port = normalizePort(scheme, port);
|
||||||
|
|
||||||
String address = address(scheme, host, port);
|
Origin origin = new Origin(scheme, host, port);
|
||||||
HttpDestination destination = destinations.get(address);
|
HttpDestination destination = destinations.get(origin);
|
||||||
if (destination == null)
|
if (destination == null)
|
||||||
{
|
{
|
||||||
destination = transport.newHttpDestination(scheme, host, port);
|
destination = transport.newHttpDestination(origin);
|
||||||
if (isRunning())
|
if (isRunning())
|
||||||
{
|
{
|
||||||
HttpDestination existing = destinations.putIfAbsent(address, destination);
|
HttpDestination existing = destinations.putIfAbsent(origin, destination);
|
||||||
if (existing != null)
|
if (existing != null)
|
||||||
destination = existing;
|
destination = existing;
|
||||||
else
|
else
|
||||||
LOG.debug("Created {}", destination);
|
LOG.debug("Created {}", destination);
|
||||||
if (!isRunning())
|
if (!isRunning())
|
||||||
destinations.remove(address);
|
destinations.remove(origin);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -486,13 +479,16 @@ public class HttpClient extends ContainerLifeCycle
|
||||||
|
|
||||||
protected void newConnection(final HttpDestination destination, final Promise<Connection> promise)
|
protected void newConnection(final HttpDestination destination, final Promise<Connection> promise)
|
||||||
{
|
{
|
||||||
Destination.Address address = destination.getConnectAddress();
|
Origin.Address address = destination.getConnectAddress();
|
||||||
resolver.resolve(address.getHost(), address.getPort(), new Promise<SocketAddress>()
|
resolver.resolve(address.getHost(), address.getPort(), new Promise<SocketAddress>()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public void succeeded(SocketAddress socketAddress)
|
public void succeeded(SocketAddress socketAddress)
|
||||||
{
|
{
|
||||||
transport.connect(destination, socketAddress, promise);
|
Map<String, Object> context = new HashMap<>();
|
||||||
|
context.put(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY, destination);
|
||||||
|
context.put(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY, promise);
|
||||||
|
transport.connect(socketAddress, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -881,14 +877,6 @@ public class HttpClient extends ContainerLifeCycle
|
||||||
return proxyConfig;
|
return proxyConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param proxyConfig the forward proxy configuration
|
|
||||||
*/
|
|
||||||
public void setProxyConfiguration(ProxyConfiguration proxyConfig)
|
|
||||||
{
|
|
||||||
this.proxyConfig = proxyConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected HttpField getAcceptEncodingField()
|
protected HttpField getAcceptEncodingField()
|
||||||
{
|
{
|
||||||
return encodingField;
|
return encodingField;
|
||||||
|
|
|
@ -19,9 +19,9 @@
|
||||||
package org.eclipse.jetty.client;
|
package org.eclipse.jetty.client;
|
||||||
|
|
||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import org.eclipse.jetty.client.api.Connection;
|
import org.eclipse.jetty.io.ClientConnectionFactory;
|
||||||
import org.eclipse.jetty.util.Promise;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link HttpClientTransport} represents what transport implementations should provide
|
* {@link HttpClientTransport} represents what transport implementations should provide
|
||||||
|
@ -34,8 +34,11 @@ import org.eclipse.jetty.util.Promise;
|
||||||
* but the HTTP exchange may also be carried using the SPDY protocol or the FCGI protocol or, in future,
|
* but the HTTP exchange may also be carried using the SPDY protocol or the FCGI protocol or, in future,
|
||||||
* other protocols.
|
* other protocols.
|
||||||
*/
|
*/
|
||||||
public interface HttpClientTransport
|
public interface HttpClientTransport extends ClientConnectionFactory
|
||||||
{
|
{
|
||||||
|
public static final String HTTP_DESTINATION_CONTEXT_KEY = "http.destination";
|
||||||
|
public static final String HTTP_CONNECTION_PROMISE_CONTEXT_KEY = "http.connection.promise";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the {@link HttpClient} instance on this transport.
|
* Sets the {@link HttpClient} instance on this transport.
|
||||||
* <p />
|
* <p />
|
||||||
|
@ -53,27 +56,16 @@ public interface HttpClientTransport
|
||||||
* {@link HttpDestination} controls the destination-connection cardinality: protocols like
|
* {@link HttpDestination} controls the destination-connection cardinality: protocols like
|
||||||
* HTTP have 1-N cardinality, while multiplexed protocols like SPDY have a 1-1 cardinality.
|
* HTTP have 1-N cardinality, while multiplexed protocols like SPDY have a 1-1 cardinality.
|
||||||
*
|
*
|
||||||
* @param scheme the destination scheme
|
* @param origin the destination origin
|
||||||
* @param host the destination host
|
|
||||||
* @param port the destination port
|
|
||||||
* @return a new, transport-specific, {@link HttpDestination} object
|
* @return a new, transport-specific, {@link HttpDestination} object
|
||||||
*/
|
*/
|
||||||
public HttpDestination newHttpDestination(String scheme, String host, int port);
|
public HttpDestination newHttpDestination(Origin origin);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Establishes a physical connection to the given {@code address}.
|
* Establishes a physical connection to the given {@code address}.
|
||||||
*
|
*
|
||||||
* @param destination the destination
|
|
||||||
* @param address the address to connect to
|
* @param address the address to connect to
|
||||||
* @param promise the promise to notify when the connection succeeds or fails
|
* @param context the context information to establish the connection
|
||||||
*/
|
*/
|
||||||
public void connect(HttpDestination destination, SocketAddress address, Promise<Connection> promise);
|
public void connect(SocketAddress address, Map<String, Object> context);
|
||||||
|
|
||||||
/**
|
|
||||||
* Establishes an encrypted tunnel over the given {@code connection}
|
|
||||||
*
|
|
||||||
* @param connection the connection to tunnel
|
|
||||||
* @return the tunnelled connection
|
|
||||||
*/
|
|
||||||
public Connection tunnel(Connection connection);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,6 +83,7 @@ public abstract class HttpConnection implements Connection
|
||||||
HttpVersion version = request.getVersion();
|
HttpVersion version = request.getVersion();
|
||||||
HttpFields headers = request.getHeaders();
|
HttpFields headers = request.getHeaders();
|
||||||
ContentProvider content = request.getContent();
|
ContentProvider content = request.getContent();
|
||||||
|
ProxyConfiguration.Proxy proxy = destination.getProxy();
|
||||||
|
|
||||||
// Make sure the path is there
|
// Make sure the path is there
|
||||||
String path = request.getPath();
|
String path = request.getPath();
|
||||||
|
@ -91,7 +92,7 @@ public abstract class HttpConnection implements Connection
|
||||||
path = "/";
|
path = "/";
|
||||||
request.path(path);
|
request.path(path);
|
||||||
}
|
}
|
||||||
if (destination.isProxied() && !HttpMethod.CONNECT.is(method))
|
if (proxy != null && !HttpMethod.CONNECT.is(method))
|
||||||
{
|
{
|
||||||
path = request.getURI().toString();
|
path = request.getURI().toString();
|
||||||
request.path(path);
|
request.path(path);
|
||||||
|
@ -136,9 +137,12 @@ public abstract class HttpConnection implements Connection
|
||||||
request.header(HttpHeader.COOKIE.asString(), cookieString.toString());
|
request.header(HttpHeader.COOKIE.asString(), cookieString.toString());
|
||||||
|
|
||||||
// Authorization
|
// Authorization
|
||||||
URI authenticationURI = destination.isProxied() ? destination.getProxyURI() : request.getURI();
|
URI authenticationURI = proxy != null ? proxy.getURI() : request.getURI();
|
||||||
Authentication.Result authnResult = getHttpClient().getAuthenticationStore().findAuthenticationResult(authenticationURI);
|
if (authenticationURI != null)
|
||||||
if (authnResult != null)
|
{
|
||||||
authnResult.apply(request);
|
Authentication.Result authnResult = getHttpClient().getAuthenticationStore().findAuthenticationResult(authenticationURI);
|
||||||
|
if (authnResult != null)
|
||||||
|
authnResult.apply(request);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,51 +20,45 @@ package org.eclipse.jetty.client;
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
|
||||||
import java.nio.channels.AsynchronousCloseException;
|
import java.nio.channels.AsynchronousCloseException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.concurrent.RejectedExecutionException;
|
import java.util.concurrent.RejectedExecutionException;
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import org.eclipse.jetty.client.api.Connection;
|
import org.eclipse.jetty.client.api.Connection;
|
||||||
import org.eclipse.jetty.client.api.Destination;
|
import org.eclipse.jetty.client.api.Destination;
|
||||||
import org.eclipse.jetty.client.api.ProxyConfiguration;
|
|
||||||
import org.eclipse.jetty.client.api.Request;
|
import org.eclipse.jetty.client.api.Request;
|
||||||
import org.eclipse.jetty.client.api.Response;
|
import org.eclipse.jetty.client.api.Response;
|
||||||
import org.eclipse.jetty.client.api.Result;
|
import org.eclipse.jetty.client.api.Result;
|
||||||
import org.eclipse.jetty.http.HttpField;
|
import org.eclipse.jetty.http.HttpField;
|
||||||
import org.eclipse.jetty.http.HttpHeader;
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
import org.eclipse.jetty.http.HttpMethod;
|
|
||||||
import org.eclipse.jetty.http.HttpScheme;
|
import org.eclipse.jetty.http.HttpScheme;
|
||||||
|
import org.eclipse.jetty.io.ClientConnectionFactory;
|
||||||
|
import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
|
||||||
import org.eclipse.jetty.util.BlockingArrayQueue;
|
import org.eclipse.jetty.util.BlockingArrayQueue;
|
||||||
import org.eclipse.jetty.util.Promise;
|
import org.eclipse.jetty.util.Promise;
|
||||||
import org.eclipse.jetty.util.component.ContainerLifeCycle;
|
import org.eclipse.jetty.util.component.ContainerLifeCycle;
|
||||||
import org.eclipse.jetty.util.component.Dumpable;
|
import org.eclipse.jetty.util.component.Dumpable;
|
||||||
import org.eclipse.jetty.util.log.Log;
|
import org.eclipse.jetty.util.log.Log;
|
||||||
import org.eclipse.jetty.util.log.Logger;
|
import org.eclipse.jetty.util.log.Logger;
|
||||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
|
||||||
|
|
||||||
public abstract class HttpDestination implements Destination, Closeable, Dumpable
|
public abstract class HttpDestination implements Destination, Closeable, Dumpable
|
||||||
{
|
{
|
||||||
protected static final Logger LOG = Log.getLogger(HttpDestination.class);
|
protected static final Logger LOG = Log.getLogger(HttpDestination.class);
|
||||||
|
|
||||||
private final HttpClient client;
|
private final HttpClient client;
|
||||||
private final String scheme;
|
private final Origin origin;
|
||||||
private final String host;
|
|
||||||
private final Address address;
|
|
||||||
private final Queue<HttpExchange> exchanges;
|
private final Queue<HttpExchange> exchanges;
|
||||||
private final RequestNotifier requestNotifier;
|
private final RequestNotifier requestNotifier;
|
||||||
private final ResponseNotifier responseNotifier;
|
private final ResponseNotifier responseNotifier;
|
||||||
private final Address proxyAddress;
|
private final ProxyConfiguration.Proxy proxy;
|
||||||
|
private final ClientConnectionFactory connectionFactory;
|
||||||
private final HttpField hostField;
|
private final HttpField hostField;
|
||||||
|
|
||||||
public HttpDestination(HttpClient client, String scheme, String host, int port)
|
public HttpDestination(HttpClient client, Origin origin)
|
||||||
{
|
{
|
||||||
this.client = client;
|
this.client = client;
|
||||||
this.scheme = scheme;
|
this.origin = origin;
|
||||||
this.host = host;
|
|
||||||
this.address = new Address(host, port);
|
|
||||||
|
|
||||||
this.exchanges = new BlockingArrayQueue<>(client.getMaxRequestsQueuedPerDestination());
|
this.exchanges = new BlockingArrayQueue<>(client.getMaxRequestsQueuedPerDestination());
|
||||||
|
|
||||||
|
@ -72,19 +66,40 @@ public abstract class HttpDestination implements Destination, Closeable, Dumpabl
|
||||||
this.responseNotifier = new ResponseNotifier(client);
|
this.responseNotifier = new ResponseNotifier(client);
|
||||||
|
|
||||||
ProxyConfiguration proxyConfig = client.getProxyConfiguration();
|
ProxyConfiguration proxyConfig = client.getProxyConfiguration();
|
||||||
proxyAddress = proxyConfig != null && proxyConfig.matches(host, port) ?
|
proxy = proxyConfig.match(origin);
|
||||||
new Address(proxyConfig.getHost(), proxyConfig.getPort()) : null;
|
ClientConnectionFactory connectionFactory = client.getTransport();
|
||||||
|
if (proxy != null)
|
||||||
|
{
|
||||||
|
connectionFactory = proxy.newClientConnectionFactory(connectionFactory);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (HttpScheme.HTTPS.is(getScheme()))
|
||||||
|
connectionFactory = newSslClientConnectionFactory(connectionFactory);
|
||||||
|
}
|
||||||
|
this.connectionFactory = connectionFactory;
|
||||||
|
|
||||||
if (!client.isDefaultPort(scheme, port))
|
String host = getHost();
|
||||||
host += ":" + port;
|
if (!client.isDefaultPort(getScheme(), getPort()))
|
||||||
|
host += ":" + getPort();
|
||||||
hostField = new HttpField(HttpHeader.HOST, host);
|
hostField = new HttpField(HttpHeader.HOST, host);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected ClientConnectionFactory newSslClientConnectionFactory(ClientConnectionFactory connectionFactory)
|
||||||
|
{
|
||||||
|
return new SslClientConnectionFactory(client.getSslContextFactory(), client.getByteBufferPool(), client.getExecutor(), connectionFactory);
|
||||||
|
}
|
||||||
|
|
||||||
public HttpClient getHttpClient()
|
public HttpClient getHttpClient()
|
||||||
{
|
{
|
||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Origin getOrigin()
|
||||||
|
{
|
||||||
|
return origin;
|
||||||
|
}
|
||||||
|
|
||||||
public Queue<HttpExchange> getHttpExchanges()
|
public Queue<HttpExchange> getHttpExchanges()
|
||||||
{
|
{
|
||||||
return exchanges;
|
return exchanges;
|
||||||
|
@ -100,10 +115,20 @@ public abstract class HttpDestination implements Destination, Closeable, Dumpabl
|
||||||
return responseNotifier;
|
return responseNotifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ProxyConfiguration.Proxy getProxy()
|
||||||
|
{
|
||||||
|
return proxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientConnectionFactory getClientConnectionFactory()
|
||||||
|
{
|
||||||
|
return connectionFactory;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getScheme()
|
public String getScheme()
|
||||||
{
|
{
|
||||||
return scheme;
|
return origin.getScheme();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -111,32 +136,18 @@ public abstract class HttpDestination implements Destination, Closeable, Dumpabl
|
||||||
{
|
{
|
||||||
// InetSocketAddress.getHostString() transforms the host string
|
// InetSocketAddress.getHostString() transforms the host string
|
||||||
// in case of IPv6 addresses, so we return the original host string
|
// in case of IPv6 addresses, so we return the original host string
|
||||||
return host;
|
return origin.getAddress().getHost();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getPort()
|
public int getPort()
|
||||||
{
|
{
|
||||||
return address.getPort();
|
return origin.getAddress().getPort();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Address getConnectAddress()
|
public Origin.Address getConnectAddress()
|
||||||
{
|
{
|
||||||
return isProxied() ? proxyAddress : address;
|
return proxy == null ? origin.getAddress() : proxy.getAddress();
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isProxied()
|
|
||||||
{
|
|
||||||
return proxyAddress != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public URI getProxyURI()
|
|
||||||
{
|
|
||||||
ProxyConfiguration proxyConfiguration = client.getProxyConfiguration();
|
|
||||||
String uri = getScheme() + "://" + proxyConfiguration.getHost();
|
|
||||||
if (!client.isDefaultPort(getScheme(), proxyConfiguration.getPort()))
|
|
||||||
uri += ":" + proxyConfiguration.getPort();
|
|
||||||
return URI.create(uri);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public HttpField getHostField()
|
public HttpField getHostField()
|
||||||
|
@ -146,7 +157,7 @@ public abstract class HttpDestination implements Destination, Closeable, Dumpabl
|
||||||
|
|
||||||
protected void send(Request request, List<Response.ResponseListener> listeners)
|
protected void send(Request request, List<Response.ResponseListener> listeners)
|
||||||
{
|
{
|
||||||
if (!scheme.equals(request.getScheme()))
|
if (!getScheme().equals(request.getScheme()))
|
||||||
throw new IllegalArgumentException("Invalid request scheme " + request.getScheme() + " for destination " + this);
|
throw new IllegalArgumentException("Invalid request scheme " + request.getScheme() + " for destination " + this);
|
||||||
if (!getHost().equals(request.getHost()))
|
if (!getHost().equals(request.getHost()))
|
||||||
throw new IllegalArgumentException("Invalid request host " + request.getHost() + " for destination " + this);
|
throw new IllegalArgumentException("Invalid request host " + request.getHost() + " for destination " + this);
|
||||||
|
@ -188,7 +199,7 @@ public abstract class HttpDestination implements Destination, Closeable, Dumpabl
|
||||||
|
|
||||||
public void newConnection(Promise<Connection> promise)
|
public void newConnection(Promise<Connection> promise)
|
||||||
{
|
{
|
||||||
createConnection(new ProxyPromise(promise));
|
createConnection(promise);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void createConnection(Promise<Connection> promise)
|
protected void createConnection(Promise<Connection> promise)
|
||||||
|
@ -236,18 +247,6 @@ public abstract class HttpDestination implements Destination, Closeable, Dumpabl
|
||||||
getResponseNotifier().notifyComplete(listeners, new Result(request, cause, response, cause));
|
getResponseNotifier().notifyComplete(listeners, new Result(request, cause, response, cause));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void tunnelSucceeded(Connection connection, Promise<Connection> promise)
|
|
||||||
{
|
|
||||||
// Wrap the connection with TLS
|
|
||||||
promise.succeeded(client.getTransport().tunnel(connection));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void tunnelFailed(Connection connection, Promise<Connection> promise, Throwable failure)
|
|
||||||
{
|
|
||||||
promise.failed(failure);
|
|
||||||
connection.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String dump()
|
public String dump()
|
||||||
{
|
{
|
||||||
|
@ -262,7 +261,7 @@ public abstract class HttpDestination implements Destination, Closeable, Dumpabl
|
||||||
|
|
||||||
public String asString()
|
public String asString()
|
||||||
{
|
{
|
||||||
return client.address(getScheme(), getHost(), getPort());
|
return origin.asString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -271,84 +270,6 @@ public abstract class HttpDestination implements Destination, Closeable, Dumpabl
|
||||||
return String.format("%s(%s)%s",
|
return String.format("%s(%s)%s",
|
||||||
HttpDestination.class.getSimpleName(),
|
HttpDestination.class.getSimpleName(),
|
||||||
asString(),
|
asString(),
|
||||||
proxyAddress == null ? "" : " via " + proxyAddress.getHost() + ":" + proxyAddress.getPort());
|
proxy == null ? "" : " via " + proxy);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decides whether to establish a proxy tunnel using HTTP CONNECT.
|
|
||||||
* It is implemented as a promise because it needs to establish the tunnel
|
|
||||||
* when the TCP connection is succeeded, and needs to notify another
|
|
||||||
* promise when the tunnel is established (or failed).
|
|
||||||
*/
|
|
||||||
private class ProxyPromise implements Promise<Connection>
|
|
||||||
{
|
|
||||||
private final Promise<Connection> delegate;
|
|
||||||
|
|
||||||
private ProxyPromise(Promise<Connection> delegate)
|
|
||||||
{
|
|
||||||
this.delegate = delegate;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void succeeded(Connection connection)
|
|
||||||
{
|
|
||||||
if (isProxied() && HttpScheme.HTTPS.is(getScheme()))
|
|
||||||
{
|
|
||||||
if (client.getSslContextFactory() != null)
|
|
||||||
{
|
|
||||||
tunnel(connection);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
String message = String.format("Cannot perform requests over SSL, no %s in %s",
|
|
||||||
SslContextFactory.class.getSimpleName(), HttpClient.class.getSimpleName());
|
|
||||||
delegate.failed(new IllegalStateException(message));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
delegate.succeeded(connection);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void failed(Throwable x)
|
|
||||||
{
|
|
||||||
delegate.failed(x);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void tunnel(final Connection connection)
|
|
||||||
{
|
|
||||||
String target = address.getHost() + ":" + address.getPort();
|
|
||||||
Request connect = client.newRequest(proxyAddress.getHost(), proxyAddress.getPort())
|
|
||||||
.scheme(HttpScheme.HTTP.asString())
|
|
||||||
.method(HttpMethod.CONNECT)
|
|
||||||
.path(target)
|
|
||||||
.header(HttpHeader.HOST, target)
|
|
||||||
.timeout(client.getConnectTimeout(), TimeUnit.MILLISECONDS);
|
|
||||||
connection.send(connect, new Response.CompleteListener()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void onComplete(Result result)
|
|
||||||
{
|
|
||||||
if (result.isFailed())
|
|
||||||
{
|
|
||||||
tunnelFailed(connection, delegate, result.getFailure());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Response response = result.getResponse();
|
|
||||||
if (response.getStatus() == 200)
|
|
||||||
{
|
|
||||||
tunnelSucceeded(connection, delegate);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
tunnelFailed(connection, delegate, new HttpResponseException("Received " + response + " for " + result.getRequest(), response));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,200 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// All rights reserved. This program and the accompanying materials
|
||||||
|
// are made available under the terms of the Eclipse Public License v1.0
|
||||||
|
// and Apache License v2.0 which accompanies this distribution.
|
||||||
|
//
|
||||||
|
// The Eclipse Public License is available at
|
||||||
|
// http://www.eclipse.org/legal/epl-v10.html
|
||||||
|
//
|
||||||
|
// The Apache License v2.0 is available at
|
||||||
|
// http://www.opensource.org/licenses/apache2.0.php
|
||||||
|
//
|
||||||
|
// You may elect to redistribute this code under either of these licenses.
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
package org.eclipse.jetty.client;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.client.api.Connection;
|
||||||
|
import org.eclipse.jetty.client.api.Request;
|
||||||
|
import org.eclipse.jetty.client.api.Response;
|
||||||
|
import org.eclipse.jetty.client.api.Result;
|
||||||
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
|
import org.eclipse.jetty.http.HttpMethod;
|
||||||
|
import org.eclipse.jetty.http.HttpScheme;
|
||||||
|
import org.eclipse.jetty.io.ClientConnectionFactory;
|
||||||
|
import org.eclipse.jetty.io.EndPoint;
|
||||||
|
import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
|
||||||
|
import org.eclipse.jetty.util.Promise;
|
||||||
|
import org.eclipse.jetty.util.log.Log;
|
||||||
|
import org.eclipse.jetty.util.log.Logger;
|
||||||
|
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||||
|
|
||||||
|
public class HttpProxy extends ProxyConfiguration.Proxy
|
||||||
|
{
|
||||||
|
public HttpProxy(String host, int port)
|
||||||
|
{
|
||||||
|
this(new Origin.Address(host, port), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpProxy(Origin.Address address, boolean secure)
|
||||||
|
{
|
||||||
|
super(address, secure);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClientConnectionFactory newClientConnectionFactory(ClientConnectionFactory connectionFactory)
|
||||||
|
{
|
||||||
|
return new HttpProxyClientConnectionFactory(connectionFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public URI getURI()
|
||||||
|
{
|
||||||
|
String scheme = isSecure() ? HttpScheme.HTTPS.asString() : HttpScheme.HTTP.asString();
|
||||||
|
return URI.create(new Origin(scheme, getAddress()).asString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class HttpProxyClientConnectionFactory implements ClientConnectionFactory
|
||||||
|
{
|
||||||
|
private static final Logger LOG = Log.getLogger(HttpProxyClientConnectionFactory.class);
|
||||||
|
private final ClientConnectionFactory connectionFactory;
|
||||||
|
|
||||||
|
public HttpProxyClientConnectionFactory(ClientConnectionFactory connectionFactory)
|
||||||
|
{
|
||||||
|
this.connectionFactory = connectionFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
|
||||||
|
{
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Promise<Connection> promise = (Promise<Connection>)context.get(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
|
||||||
|
final ProxyPromise proxyPromise = new ProxyPromise(endPoint, promise, context);
|
||||||
|
// Replace the promise with the proxy one
|
||||||
|
context.put(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY, proxyPromise);
|
||||||
|
return connectionFactory.newConnection(endPoint, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decides whether to establish a proxy tunnel using HTTP CONNECT.
|
||||||
|
* It is implemented as a promise because it needs to establish the
|
||||||
|
* tunnel after the TCP connection is succeeded, and needs to notify
|
||||||
|
* the nested promise when the tunnel is established (or failed).
|
||||||
|
*/
|
||||||
|
private class ProxyPromise implements Promise<Connection>
|
||||||
|
{
|
||||||
|
private final EndPoint endPoint;
|
||||||
|
private final Promise<Connection> promise;
|
||||||
|
private final Map<String, Object> context;
|
||||||
|
|
||||||
|
private ProxyPromise(EndPoint endPoint, Promise<Connection> promise, Map<String, Object> context)
|
||||||
|
{
|
||||||
|
this.endPoint = endPoint;
|
||||||
|
this.promise = promise;
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void succeeded(Connection connection)
|
||||||
|
{
|
||||||
|
HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
|
||||||
|
if (HttpScheme.HTTPS.is(destination.getScheme()))
|
||||||
|
{
|
||||||
|
SslContextFactory sslContextFactory = destination.getHttpClient().getSslContextFactory();
|
||||||
|
if (sslContextFactory != null)
|
||||||
|
{
|
||||||
|
tunnel(destination, connection);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
String message = String.format("Cannot perform requests over SSL, no %s in %s",
|
||||||
|
SslContextFactory.class.getSimpleName(), HttpClient.class.getSimpleName());
|
||||||
|
promise.failed(new IllegalStateException(message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
promise.succeeded(connection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void failed(Throwable x)
|
||||||
|
{
|
||||||
|
promise.failed(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tunnel(HttpDestination destination, final Connection connection)
|
||||||
|
{
|
||||||
|
String target = destination.getOrigin().getAddress().asString();
|
||||||
|
Origin.Address proxyAddress = destination.getConnectAddress();
|
||||||
|
HttpClient httpClient = destination.getHttpClient();
|
||||||
|
Request connect = httpClient.newRequest(proxyAddress.getHost(), proxyAddress.getPort())
|
||||||
|
.scheme(HttpScheme.HTTP.asString())
|
||||||
|
.method(HttpMethod.CONNECT)
|
||||||
|
.path(target)
|
||||||
|
.header(HttpHeader.HOST, target)
|
||||||
|
.timeout(httpClient.getConnectTimeout(), TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
|
connection.send(connect, new Response.CompleteListener()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onComplete(Result result)
|
||||||
|
{
|
||||||
|
if (result.isFailed())
|
||||||
|
{
|
||||||
|
tunnelFailed(result.getFailure());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Response response = result.getResponse();
|
||||||
|
if (response.getStatus() == 200)
|
||||||
|
{
|
||||||
|
tunnelSucceeded();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tunnelFailed(new HttpResponseException("Received " + response + " for " + result.getRequest(), response));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tunnelSucceeded()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Replace the promise back with the original
|
||||||
|
context.put(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY, promise);
|
||||||
|
HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
|
||||||
|
HttpClient client = destination.getHttpClient();
|
||||||
|
ClientConnectionFactory sslConnectionFactory = new SslClientConnectionFactory(client.getSslContextFactory(), client.getByteBufferPool(), client.getExecutor(), connectionFactory);
|
||||||
|
org.eclipse.jetty.io.Connection oldConnection = endPoint.getConnection();
|
||||||
|
org.eclipse.jetty.io.Connection newConnection = sslConnectionFactory.newConnection(endPoint, context);
|
||||||
|
Helper.replaceConnection(oldConnection, newConnection);
|
||||||
|
LOG.debug("HTTP tunnel established: {} over {}", oldConnection, newConnection);
|
||||||
|
}
|
||||||
|
catch (Throwable x)
|
||||||
|
{
|
||||||
|
tunnelFailed(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tunnelFailed(Throwable failure)
|
||||||
|
{
|
||||||
|
endPoint.close();
|
||||||
|
failed(failure);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -589,7 +589,7 @@ public class HttpRequest implements Request
|
||||||
path += "?" + query;
|
path += "?" + query;
|
||||||
URI result = URI.create(path);
|
URI result = URI.create(path);
|
||||||
if (!result.isAbsolute() && !result.isOpaque())
|
if (!result.isAbsolute() && !result.isOpaque())
|
||||||
result = URI.create(client.address(getScheme(), getHost(), getPort()) + path);
|
result = URI.create(new Origin(getScheme(), getHost(), getPort()).asString() + path);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,9 +29,9 @@ public abstract class MultiplexHttpDestination<C extends Connection> extends Htt
|
||||||
private final AtomicReference<ConnectState> connect = new AtomicReference<>(ConnectState.DISCONNECTED);
|
private final AtomicReference<ConnectState> connect = new AtomicReference<>(ConnectState.DISCONNECTED);
|
||||||
private C connection;
|
private C connection;
|
||||||
|
|
||||||
protected MultiplexHttpDestination(HttpClient client, String scheme, String host, int port)
|
protected MultiplexHttpDestination(HttpClient client, Origin origin)
|
||||||
{
|
{
|
||||||
super(client, scheme, host, port);
|
super(client, origin);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -0,0 +1,122 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// All rights reserved. This program and the accompanying materials
|
||||||
|
// are made available under the terms of the Eclipse Public License v1.0
|
||||||
|
// and Apache License v2.0 which accompanies this distribution.
|
||||||
|
//
|
||||||
|
// The Eclipse Public License is available at
|
||||||
|
// http://www.eclipse.org/legal/epl-v10.html
|
||||||
|
//
|
||||||
|
// The Apache License v2.0 is available at
|
||||||
|
// http://www.opensource.org/licenses/apache2.0.php
|
||||||
|
//
|
||||||
|
// You may elect to redistribute this code under either of these licenses.
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
package org.eclipse.jetty.client;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.util.URIUtil;
|
||||||
|
|
||||||
|
public class Origin
|
||||||
|
{
|
||||||
|
private final String scheme;
|
||||||
|
private final Address address;
|
||||||
|
|
||||||
|
public Origin(String scheme, String host, int port)
|
||||||
|
{
|
||||||
|
this(scheme, new Address(host, port));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Origin(String scheme, Address address)
|
||||||
|
{
|
||||||
|
this.scheme = scheme;
|
||||||
|
this.address = address;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getScheme()
|
||||||
|
{
|
||||||
|
return scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Address getAddress()
|
||||||
|
{
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String asString()
|
||||||
|
{
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
URIUtil.appendSchemeHostPort(result, scheme, address.host, address.port);
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj)
|
||||||
|
{
|
||||||
|
if (this == obj) return true;
|
||||||
|
if (obj == null || getClass() != obj.getClass()) return false;
|
||||||
|
Origin that = (Origin)obj;
|
||||||
|
return scheme.equals(that.scheme) && address.equals(that.address);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode()
|
||||||
|
{
|
||||||
|
int result = scheme.hashCode();
|
||||||
|
result = 31 * result + address.hashCode();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Address
|
||||||
|
{
|
||||||
|
private final String host;
|
||||||
|
private final int port;
|
||||||
|
|
||||||
|
public Address(String host, int port)
|
||||||
|
{
|
||||||
|
this.host = host;
|
||||||
|
this.port = port;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHost()
|
||||||
|
{
|
||||||
|
return host;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPort()
|
||||||
|
{
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj)
|
||||||
|
{
|
||||||
|
if (this == obj) return true;
|
||||||
|
if (obj == null || getClass() != obj.getClass()) return false;
|
||||||
|
Address that = (Address)obj;
|
||||||
|
return host.equals(that.host) && port == that.port;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode()
|
||||||
|
{
|
||||||
|
int result = host.hashCode();
|
||||||
|
result = 31 * result + port;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String asString()
|
||||||
|
{
|
||||||
|
return String.format("%s:%d", host, port);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return asString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -59,6 +59,7 @@ public class ProxyAuthenticationProtocolHandler extends AuthenticationProtocolHa
|
||||||
protected URI getAuthenticationURI(Request request)
|
protected URI getAuthenticationURI(Request request)
|
||||||
{
|
{
|
||||||
HttpDestination destination = getHttpClient().destinationFor(request.getScheme(), request.getHost(), request.getPort());
|
HttpDestination destination = getHttpClient().destinationFor(request.getScheme(), request.getHost(), request.getPort());
|
||||||
return destination.isProxied() ? destination.getProxyURI() : request.getURI();
|
ProxyConfiguration.Proxy proxy = destination.getProxy();
|
||||||
|
return proxy != null ? proxy.getURI() : request.getURI();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,137 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// All rights reserved. This program and the accompanying materials
|
||||||
|
// are made available under the terms of the Eclipse Public License v1.0
|
||||||
|
// and Apache License v2.0 which accompanies this distribution.
|
||||||
|
//
|
||||||
|
// The Eclipse Public License is available at
|
||||||
|
// http://www.eclipse.org/legal/epl-v10.html
|
||||||
|
//
|
||||||
|
// The Apache License v2.0 is available at
|
||||||
|
// http://www.opensource.org/licenses/apache2.0.php
|
||||||
|
//
|
||||||
|
// You may elect to redistribute this code under either of these licenses.
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
package org.eclipse.jetty.client;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.io.ClientConnectionFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The configuration of the forward proxy to use with {@link org.eclipse.jetty.client.HttpClient}.
|
||||||
|
* <p />
|
||||||
|
* Applications add subclasses of {@link Proxy} to this configuration via:
|
||||||
|
* <pre>
|
||||||
|
* ProxyConfiguration proxyConfig = httpClient.getProxyConfiguration();
|
||||||
|
* proxyConfig.getProxies().add(new HttpProxy(proxyHost, 8080));
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @see HttpClient#getProxyConfiguration()
|
||||||
|
*/
|
||||||
|
public class ProxyConfiguration
|
||||||
|
{
|
||||||
|
private final List<Proxy> proxies = new ArrayList<>();
|
||||||
|
|
||||||
|
public List<Proxy> getProxies()
|
||||||
|
{
|
||||||
|
return proxies;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Proxy match(Origin origin)
|
||||||
|
{
|
||||||
|
for (Proxy proxy : getProxies())
|
||||||
|
{
|
||||||
|
if (proxy.matches(origin))
|
||||||
|
return proxy;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static abstract class Proxy
|
||||||
|
{
|
||||||
|
private final Set<Origin> included = new HashSet<>();
|
||||||
|
private final Set<Origin> excluded = new HashSet<>();
|
||||||
|
private final Origin.Address address;
|
||||||
|
private final boolean secure;
|
||||||
|
|
||||||
|
protected Proxy(Origin.Address address, boolean secure)
|
||||||
|
{
|
||||||
|
this.address = address;
|
||||||
|
this.secure = secure;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the address of this proxy
|
||||||
|
*/
|
||||||
|
public Origin.Address getAddress()
|
||||||
|
{
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return whether the connection to the proxy must be secured via TLS
|
||||||
|
*/
|
||||||
|
public boolean isSecure()
|
||||||
|
{
|
||||||
|
return secure;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the list of origins that must be proxied
|
||||||
|
*/
|
||||||
|
public Set<Origin> getIncludedOrigins()
|
||||||
|
{
|
||||||
|
return included;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the list of origins that must not be proxied.
|
||||||
|
*/
|
||||||
|
public Set<Origin> getExcludedOrigins()
|
||||||
|
{
|
||||||
|
return excluded;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return an URI representing this proxy, or null if no URI can represent this proxy
|
||||||
|
*/
|
||||||
|
public URI getURI()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Matches the given {@code origin} with the included and excluded origins,
|
||||||
|
* returning true if the given {@code origin} is to be proxied.
|
||||||
|
*
|
||||||
|
* @param origin the origin to test for proxying
|
||||||
|
* @return true if the origin must be proxied, false otherwise
|
||||||
|
*/
|
||||||
|
public boolean matches(Origin origin)
|
||||||
|
{
|
||||||
|
return included.contains(origin) || !excluded.contains(origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param connectionFactory the nested {@link ClientConnectionFactory}
|
||||||
|
* @return a new {@link ClientConnectionFactory} for this {@link Proxy}
|
||||||
|
*/
|
||||||
|
public abstract ClientConnectionFactory newClientConnectionFactory(ClientConnectionFactory connectionFactory);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return address.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,190 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// All rights reserved. This program and the accompanying materials
|
||||||
|
// are made available under the terms of the Eclipse Public License v1.0
|
||||||
|
// and Apache License v2.0 which accompanies this distribution.
|
||||||
|
//
|
||||||
|
// The Eclipse Public License is available at
|
||||||
|
// http://www.eclipse.org/legal/epl-v10.html
|
||||||
|
//
|
||||||
|
// The Apache License v2.0 is available at
|
||||||
|
// http://www.opensource.org/licenses/apache2.0.php
|
||||||
|
//
|
||||||
|
// You may elect to redistribute this code under either of these licenses.
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
package org.eclipse.jetty.client;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.client.api.Connection;
|
||||||
|
import org.eclipse.jetty.http.HttpScheme;
|
||||||
|
import org.eclipse.jetty.io.AbstractConnection;
|
||||||
|
import org.eclipse.jetty.io.ClientConnectionFactory;
|
||||||
|
import org.eclipse.jetty.io.EndPoint;
|
||||||
|
import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
|
||||||
|
import org.eclipse.jetty.util.BufferUtil;
|
||||||
|
import org.eclipse.jetty.util.Callback;
|
||||||
|
import org.eclipse.jetty.util.Promise;
|
||||||
|
import org.eclipse.jetty.util.log.Log;
|
||||||
|
import org.eclipse.jetty.util.log.Logger;
|
||||||
|
|
||||||
|
public class Socks4Proxy extends ProxyConfiguration.Proxy
|
||||||
|
{
|
||||||
|
public Socks4Proxy(String host, int port)
|
||||||
|
{
|
||||||
|
this(new Origin.Address(host, port), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Socks4Proxy(Origin.Address address, boolean secure)
|
||||||
|
{
|
||||||
|
super(address, secure);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClientConnectionFactory newClientConnectionFactory(ClientConnectionFactory connectionFactory)
|
||||||
|
{
|
||||||
|
return new Socks4ProxyClientConnectionFactory(connectionFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Socks4ProxyClientConnectionFactory implements ClientConnectionFactory
|
||||||
|
{
|
||||||
|
private final ClientConnectionFactory connectionFactory;
|
||||||
|
|
||||||
|
public Socks4ProxyClientConnectionFactory(ClientConnectionFactory connectionFactory)
|
||||||
|
{
|
||||||
|
this.connectionFactory = connectionFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
|
||||||
|
{
|
||||||
|
HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
|
||||||
|
Executor executor = destination.getHttpClient().getExecutor();
|
||||||
|
return new Socks4ProxyConnection(endPoint, executor, connectionFactory, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Socks4ProxyConnection extends AbstractConnection implements Callback
|
||||||
|
{
|
||||||
|
private static final Pattern IPv4_PATTERN = Pattern.compile("(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})");
|
||||||
|
private static final Logger LOG = Log.getLogger(Socks4ProxyConnection.class);
|
||||||
|
|
||||||
|
private final ClientConnectionFactory connectionFactory;
|
||||||
|
private final Map<String, Object> context;
|
||||||
|
|
||||||
|
public Socks4ProxyConnection(EndPoint endPoint, Executor executor, ClientConnectionFactory connectionFactory, Map<String, Object> context)
|
||||||
|
{
|
||||||
|
super(endPoint, executor);
|
||||||
|
this.connectionFactory = connectionFactory;
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onOpen()
|
||||||
|
{
|
||||||
|
super.onOpen();
|
||||||
|
writeSocks4Connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes the SOCKS "connect" bytes, differentiating between SOCKS 4 and 4A;
|
||||||
|
* the former sends an IPv4 address, the latter the full domain name.
|
||||||
|
*/
|
||||||
|
private void writeSocks4Connect()
|
||||||
|
{
|
||||||
|
HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
|
||||||
|
String host = destination.getHost();
|
||||||
|
short port = (short)destination.getPort();
|
||||||
|
Matcher matcher = IPv4_PATTERN.matcher(host);
|
||||||
|
if (matcher.matches())
|
||||||
|
{
|
||||||
|
// SOCKS 4
|
||||||
|
ByteBuffer buffer = ByteBuffer.allocate(9);
|
||||||
|
buffer.put((byte)4).put((byte)1).putShort(port);
|
||||||
|
for (int i = 1; i <= 4; ++i)
|
||||||
|
buffer.put((byte)Integer.parseInt(matcher.group(i)));
|
||||||
|
buffer.put((byte)0);
|
||||||
|
buffer.flip();
|
||||||
|
getEndPoint().write(this, buffer);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// SOCKS 4A
|
||||||
|
byte[] hostBytes = host.getBytes(Charset.forName("UTF-8"));
|
||||||
|
ByteBuffer buffer = ByteBuffer.allocate(9 + hostBytes.length + 1);
|
||||||
|
buffer.put((byte)4).put((byte)1).putShort(port);
|
||||||
|
buffer.put((byte)0).put((byte)0).put((byte)0).put((byte)1).put((byte)0);
|
||||||
|
buffer.put(hostBytes).put((byte)0);
|
||||||
|
buffer.flip();
|
||||||
|
getEndPoint().write(this, buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void succeeded()
|
||||||
|
{
|
||||||
|
LOG.debug("Written SOCKS4 connect request");
|
||||||
|
fillInterested();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void failed(Throwable x)
|
||||||
|
{
|
||||||
|
close();
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Promise<Connection> promise = (Promise<Connection>)context.get(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
|
||||||
|
promise.failed(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFillable()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ByteBuffer buffer = BufferUtil.allocate(8);
|
||||||
|
int filled = getEndPoint().fill(buffer);
|
||||||
|
LOG.debug("Read SOCKS4 connect response, {} bytes", filled);
|
||||||
|
if (filled != 8)
|
||||||
|
throw new IOException("Invalid response from SOCKS4 proxy");
|
||||||
|
int result = buffer.get(1);
|
||||||
|
if (result == 0x5A)
|
||||||
|
tunnel();
|
||||||
|
else
|
||||||
|
throw new IOException("SOCKS4 tunnel failed with code " + result);
|
||||||
|
}
|
||||||
|
catch (Throwable x)
|
||||||
|
{
|
||||||
|
failed(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tunnel()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
|
||||||
|
HttpClient client = destination.getHttpClient();
|
||||||
|
ClientConnectionFactory connectionFactory = this.connectionFactory;
|
||||||
|
if (HttpScheme.HTTPS.is(destination.getScheme()))
|
||||||
|
connectionFactory = new SslClientConnectionFactory(client.getSslContextFactory(), client.getByteBufferPool(), client.getExecutor(), connectionFactory);
|
||||||
|
org.eclipse.jetty.io.Connection connection = connectionFactory.newConnection(getEndPoint(), context);
|
||||||
|
ClientConnectionFactory.Helper.replaceConnection(this, connection);
|
||||||
|
LOG.debug("SOCKS4 tunnel established: {} over {}", this, connection);
|
||||||
|
}
|
||||||
|
catch (Throwable x)
|
||||||
|
{
|
||||||
|
failed(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -63,43 +63,4 @@ public interface Destination
|
||||||
* @param promise the promise of a new, unpooled, {@link Connection}
|
* @param promise the promise of a new, unpooled, {@link Connection}
|
||||||
*/
|
*/
|
||||||
void newConnection(Promise<Connection> promise);
|
void newConnection(Promise<Connection> promise);
|
||||||
|
|
||||||
public static class Address
|
|
||||||
{
|
|
||||||
private final String host;
|
|
||||||
private final int port;
|
|
||||||
|
|
||||||
public Address(String host, int port)
|
|
||||||
{
|
|
||||||
this.host = host;
|
|
||||||
this.port = port;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getHost()
|
|
||||||
{
|
|
||||||
return host;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getPort()
|
|
||||||
{
|
|
||||||
return port;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj)
|
|
||||||
{
|
|
||||||
if (this == obj) return true;
|
|
||||||
if (obj == null || getClass() != obj.getClass()) return false;
|
|
||||||
Address that = (Address)obj;
|
|
||||||
return host.equals(that.host) && port == that.port;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode()
|
|
||||||
{
|
|
||||||
int result = host.hashCode();
|
|
||||||
result = 31 * result + port;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,81 +0,0 @@
|
||||||
//
|
|
||||||
// ========================================================================
|
|
||||||
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// All rights reserved. This program and the accompanying materials
|
|
||||||
// are made available under the terms of the Eclipse Public License v1.0
|
|
||||||
// and Apache License v2.0 which accompanies this distribution.
|
|
||||||
//
|
|
||||||
// The Eclipse Public License is available at
|
|
||||||
// http://www.eclipse.org/legal/epl-v10.html
|
|
||||||
//
|
|
||||||
// The Apache License v2.0 is available at
|
|
||||||
// http://www.opensource.org/licenses/apache2.0.php
|
|
||||||
//
|
|
||||||
// You may elect to redistribute this code under either of these licenses.
|
|
||||||
// ========================================================================
|
|
||||||
//
|
|
||||||
|
|
||||||
package org.eclipse.jetty.client.api;
|
|
||||||
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The configuration of the forward proxy to use with {@link org.eclipse.jetty.client.HttpClient}.
|
|
||||||
* <p />
|
|
||||||
* Configuration parameters include the host and port of the forward proxy, and a list of
|
|
||||||
* {@link #getExcludedOrigins() origins} that are excluded from being proxied.
|
|
||||||
*
|
|
||||||
* @see org.eclipse.jetty.client.HttpClient#setProxyConfiguration(ProxyConfiguration)
|
|
||||||
*/
|
|
||||||
public class ProxyConfiguration
|
|
||||||
{
|
|
||||||
private final Set<String> excluded = new HashSet<>();
|
|
||||||
private final String host;
|
|
||||||
private final int port;
|
|
||||||
|
|
||||||
public ProxyConfiguration(String host, int port)
|
|
||||||
{
|
|
||||||
this.host = host;
|
|
||||||
this.port = port;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the host name of the forward proxy
|
|
||||||
*/
|
|
||||||
public String getHost()
|
|
||||||
{
|
|
||||||
return host;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the port of the forward proxy
|
|
||||||
*/
|
|
||||||
public int getPort()
|
|
||||||
{
|
|
||||||
return port;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Matches the given {@code host} and {@code port} with the list of excluded origins,
|
|
||||||
* returning true if the origin is to be proxied, false if it is excluded from proxying.
|
|
||||||
* @param host the host to match
|
|
||||||
* @param port the port to match
|
|
||||||
* @return true if the origin made of {@code host} and {@code port} is to be proxied,
|
|
||||||
* false if it is excluded from proxying.
|
|
||||||
*/
|
|
||||||
public boolean matches(String host, int port)
|
|
||||||
{
|
|
||||||
String hostPort = host + ":" + port;
|
|
||||||
return !getExcludedOrigins().contains(hostPort);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the list of origins to exclude from proxying, in the form "host:port".
|
|
||||||
*/
|
|
||||||
public Set<String> getExcludedOrigins()
|
|
||||||
{
|
|
||||||
return excluded;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -18,16 +18,21 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.client.http;
|
package org.eclipse.jetty.client.http;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import org.eclipse.jetty.client.AbstractHttpClientTransport;
|
import org.eclipse.jetty.client.AbstractHttpClientTransport;
|
||||||
import org.eclipse.jetty.client.HttpDestination;
|
import org.eclipse.jetty.client.HttpDestination;
|
||||||
import org.eclipse.jetty.io.Connection;
|
import org.eclipse.jetty.client.Origin;
|
||||||
|
import org.eclipse.jetty.client.api.Connection;
|
||||||
import org.eclipse.jetty.io.EndPoint;
|
import org.eclipse.jetty.io.EndPoint;
|
||||||
|
import org.eclipse.jetty.util.Promise;
|
||||||
|
|
||||||
public class HttpClientTransportOverHTTP extends AbstractHttpClientTransport
|
public class HttpClientTransportOverHTTP extends AbstractHttpClientTransport
|
||||||
{
|
{
|
||||||
public HttpClientTransportOverHTTP()
|
public HttpClientTransportOverHTTP()
|
||||||
{
|
{
|
||||||
this(Runtime.getRuntime().availableProcessors());
|
this(Math.max(1, Runtime.getRuntime().availableProcessors() / 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
public HttpClientTransportOverHTTP(int selectors)
|
public HttpClientTransportOverHTTP(int selectors)
|
||||||
|
@ -36,21 +41,20 @@ public class HttpClientTransportOverHTTP extends AbstractHttpClientTransport
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HttpDestination newHttpDestination(String scheme, String host, int port)
|
public HttpDestination newHttpDestination(Origin origin)
|
||||||
{
|
{
|
||||||
return new HttpDestinationOverHTTP(getHttpClient(), scheme, host, port);
|
return new HttpDestinationOverHTTP(getHttpClient(), origin);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Connection newConnection(EndPoint endPoint, HttpDestination destination)
|
public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
|
||||||
{
|
{
|
||||||
return new HttpConnectionOverHTTP(endPoint, destination);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
|
||||||
public org.eclipse.jetty.client.api.Connection tunnel(org.eclipse.jetty.client.api.Connection connection)
|
HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, destination);
|
||||||
{
|
@SuppressWarnings("unchecked")
|
||||||
HttpConnectionOverHTTP httpConnection = (HttpConnectionOverHTTP)connection;
|
Promise<Connection> promise = (Promise<Connection>)context.get(HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
|
||||||
return tunnel(httpConnection.getEndPoint(), httpConnection.getHttpDestination(), connection);
|
promise.succeeded(connection);
|
||||||
|
return connection;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,30 +21,32 @@ package org.eclipse.jetty.client.http;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.client.ConnectionPool;
|
||||||
import org.eclipse.jetty.client.HttpClient;
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
import org.eclipse.jetty.client.HttpDestination;
|
import org.eclipse.jetty.client.HttpDestination;
|
||||||
import org.eclipse.jetty.client.HttpExchange;
|
import org.eclipse.jetty.client.HttpExchange;
|
||||||
|
import org.eclipse.jetty.client.Origin;
|
||||||
import org.eclipse.jetty.client.api.Connection;
|
import org.eclipse.jetty.client.api.Connection;
|
||||||
import org.eclipse.jetty.client.api.Request;
|
import org.eclipse.jetty.client.api.Request;
|
||||||
import org.eclipse.jetty.util.Promise;
|
import org.eclipse.jetty.util.Promise;
|
||||||
import org.eclipse.jetty.util.component.ContainerLifeCycle;
|
import org.eclipse.jetty.util.component.ContainerLifeCycle;
|
||||||
|
|
||||||
public class HttpDestinationOverHTTP extends HttpDestination implements Promise<Connection>
|
public class HttpDestinationOverHTTP extends HttpDestination implements Promise<Connection>
|
||||||
{
|
{
|
||||||
private final HttpConnectionPool connectionPool;
|
private final ConnectionPool connectionPool;
|
||||||
|
|
||||||
public HttpDestinationOverHTTP(HttpClient client, String scheme, String host, int port)
|
public HttpDestinationOverHTTP(HttpClient client, Origin origin)
|
||||||
{
|
{
|
||||||
super(client, scheme, host, port);
|
super(client, origin);
|
||||||
this.connectionPool = newHttpConnectionPool(client);
|
this.connectionPool = newConnectionPool(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected HttpConnectionPool newHttpConnectionPool(HttpClient client)
|
protected ConnectionPool newConnectionPool(HttpClient client)
|
||||||
{
|
{
|
||||||
return new HttpConnectionPool(this, client.getMaxConnectionsPerDestination(), this);
|
return new ConnectionPool(this, client.getMaxConnectionsPerDestination(), this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public HttpConnectionPool getHttpConnectionPool()
|
public ConnectionPool getConnectionPool()
|
||||||
{
|
{
|
||||||
return connectionPool;
|
return connectionPool;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,245 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// All rights reserved. This program and the accompanying materials
|
||||||
|
// are made available under the terms of the Eclipse Public License v1.0
|
||||||
|
// and Apache License v2.0 which accompanies this distribution.
|
||||||
|
//
|
||||||
|
// The Eclipse Public License is available at
|
||||||
|
// http://www.eclipse.org/legal/epl-v10.html
|
||||||
|
//
|
||||||
|
// The Apache License v2.0 is available at
|
||||||
|
// http://www.opensource.org/licenses/apache2.0.php
|
||||||
|
//
|
||||||
|
// You may elect to redistribute this code under either of these licenses.
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
package org.eclipse.jetty.client;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.client.api.Connection;
|
||||||
|
import org.eclipse.jetty.client.api.ContentResponse;
|
||||||
|
import org.eclipse.jetty.http.HttpStatus;
|
||||||
|
import org.eclipse.jetty.io.AbstractConnection;
|
||||||
|
import org.eclipse.jetty.io.ClientConnectionFactory;
|
||||||
|
import org.eclipse.jetty.io.EndPoint;
|
||||||
|
import org.eclipse.jetty.server.AbstractConnectionFactory;
|
||||||
|
import org.eclipse.jetty.server.Connector;
|
||||||
|
import org.eclipse.jetty.server.Handler;
|
||||||
|
import org.eclipse.jetty.server.HttpConnectionFactory;
|
||||||
|
import org.eclipse.jetty.server.Server;
|
||||||
|
import org.eclipse.jetty.server.ServerConnector;
|
||||||
|
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||||
|
import org.eclipse.jetty.util.BufferUtil;
|
||||||
|
import org.eclipse.jetty.util.Callback;
|
||||||
|
import org.eclipse.jetty.util.Promise;
|
||||||
|
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class HttpClientCustomProxyTest
|
||||||
|
{
|
||||||
|
public static final byte[] CAFE_BABE = new byte[]{(byte)0xCA, (byte)0xFE, (byte)0xBA, (byte)0xBE};
|
||||||
|
|
||||||
|
private Server server;
|
||||||
|
private ServerConnector connector;
|
||||||
|
private HttpClient client;
|
||||||
|
|
||||||
|
public void prepare(Handler handler) throws Exception
|
||||||
|
{
|
||||||
|
server = new Server();
|
||||||
|
connector = new ServerConnector(server, new CAFEBABEServerConnectionFactory(new HttpConnectionFactory()));
|
||||||
|
server.addConnector(connector);
|
||||||
|
server.setHandler(handler);
|
||||||
|
server.start();
|
||||||
|
|
||||||
|
QueuedThreadPool executor = new QueuedThreadPool();
|
||||||
|
executor.setName(executor.getName() + "-client");
|
||||||
|
client = new HttpClient();
|
||||||
|
client.setExecutor(executor);
|
||||||
|
client.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void dispose() throws Exception
|
||||||
|
{
|
||||||
|
if (client != null)
|
||||||
|
client.stop();
|
||||||
|
if (server != null)
|
||||||
|
server.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCustomProxy() throws Exception
|
||||||
|
{
|
||||||
|
final String serverHost = "server";
|
||||||
|
final int status = HttpStatus.NO_CONTENT_204;
|
||||||
|
prepare(new AbstractHandler()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||||
|
{
|
||||||
|
baseRequest.setHandled(true);
|
||||||
|
if (!URI.create(baseRequest.getUri().toString()).isAbsolute())
|
||||||
|
response.setStatus(HttpServletResponse.SC_USE_PROXY);
|
||||||
|
else if (serverHost.equals(request.getServerName()))
|
||||||
|
response.setStatus(status);
|
||||||
|
else
|
||||||
|
response.setStatus(HttpServletResponse.SC_NOT_ACCEPTABLE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Setup the custom proxy
|
||||||
|
int proxyPort = connector.getLocalPort();
|
||||||
|
int serverPort = proxyPort + 1; // Any port will do for these tests - just not the same as the proxy
|
||||||
|
client.getProxyConfiguration().getProxies().add(new CAFEBABEProxy(new Origin.Address("localhost", proxyPort), false));
|
||||||
|
|
||||||
|
ContentResponse response = client.newRequest(serverHost, serverPort)
|
||||||
|
.timeout(5, TimeUnit.SECONDS)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
Assert.assertEquals(status, response.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CAFEBABEProxy extends ProxyConfiguration.Proxy
|
||||||
|
{
|
||||||
|
private CAFEBABEProxy(Origin.Address address, boolean secure)
|
||||||
|
{
|
||||||
|
super(address, secure);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClientConnectionFactory newClientConnectionFactory(ClientConnectionFactory connectionFactory)
|
||||||
|
{
|
||||||
|
return new CAFEBABEClientConnectionFactory(connectionFactory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CAFEBABEClientConnectionFactory implements ClientConnectionFactory
|
||||||
|
{
|
||||||
|
private final ClientConnectionFactory connectionFactory;
|
||||||
|
|
||||||
|
private CAFEBABEClientConnectionFactory(ClientConnectionFactory connectionFactory)
|
||||||
|
{
|
||||||
|
this.connectionFactory = connectionFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
|
||||||
|
{
|
||||||
|
HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
|
||||||
|
Executor executor = destination.getHttpClient().getExecutor();
|
||||||
|
return new CAFEBABEConnection(endPoint, executor, connectionFactory, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CAFEBABEConnection extends AbstractConnection
|
||||||
|
{
|
||||||
|
private final ClientConnectionFactory connectionFactory;
|
||||||
|
private final Map<String, Object> context;
|
||||||
|
|
||||||
|
public CAFEBABEConnection(EndPoint endPoint, Executor executor, ClientConnectionFactory connectionFactory, Map<String, Object> context)
|
||||||
|
{
|
||||||
|
super(endPoint, executor);
|
||||||
|
this.connectionFactory = connectionFactory;
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onOpen()
|
||||||
|
{
|
||||||
|
super.onOpen();
|
||||||
|
fillInterested();
|
||||||
|
getEndPoint().write(new Callback.Adapter(), ByteBuffer.wrap(CAFE_BABE));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFillable()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ByteBuffer buffer = BufferUtil.allocate(4);
|
||||||
|
int filled = getEndPoint().fill(buffer);
|
||||||
|
Assert.assertEquals(4, filled);
|
||||||
|
Assert.assertArrayEquals(CAFE_BABE, buffer.array());
|
||||||
|
|
||||||
|
// We are good, upgrade the connection
|
||||||
|
ClientConnectionFactory.Helper.replaceConnection(this, connectionFactory.newConnection(getEndPoint(), context));
|
||||||
|
}
|
||||||
|
catch (Throwable x)
|
||||||
|
{
|
||||||
|
close();
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Promise<Connection> promise = (Promise<Connection>)context.get(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
|
||||||
|
promise.failed(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CAFEBABEServerConnectionFactory extends AbstractConnectionFactory
|
||||||
|
{
|
||||||
|
private final org.eclipse.jetty.server.ConnectionFactory connectionFactory;
|
||||||
|
|
||||||
|
private CAFEBABEServerConnectionFactory(org.eclipse.jetty.server.ConnectionFactory connectionFactory)
|
||||||
|
{
|
||||||
|
super("cafebabe");
|
||||||
|
this.connectionFactory = connectionFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public org.eclipse.jetty.io.Connection newConnection(Connector connector, EndPoint endPoint)
|
||||||
|
{
|
||||||
|
return new CAFEBABEServerConnection(connector, endPoint, connectionFactory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CAFEBABEServerConnection extends AbstractConnection
|
||||||
|
{
|
||||||
|
private final org.eclipse.jetty.server.ConnectionFactory connectionFactory;
|
||||||
|
|
||||||
|
public CAFEBABEServerConnection(Connector connector, EndPoint endPoint, org.eclipse.jetty.server.ConnectionFactory connectionFactory)
|
||||||
|
{
|
||||||
|
super(endPoint, connector.getExecutor());
|
||||||
|
this.connectionFactory = connectionFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onOpen()
|
||||||
|
{
|
||||||
|
super.onOpen();
|
||||||
|
fillInterested();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFillable()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ByteBuffer buffer = BufferUtil.allocate(4);
|
||||||
|
int filled = getEndPoint().fill(buffer);
|
||||||
|
Assert.assertEquals(4, filled);
|
||||||
|
Assert.assertArrayEquals(CAFE_BABE, buffer.array());
|
||||||
|
getEndPoint().write(new Callback.Adapter(), buffer);
|
||||||
|
|
||||||
|
// We are good, upgrade the connection
|
||||||
|
ClientConnectionFactory.Helper.replaceConnection(this, connectionFactory.newConnection(connector, getEndPoint()));
|
||||||
|
}
|
||||||
|
catch (Throwable x)
|
||||||
|
{
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,7 +25,6 @@ import org.eclipse.jetty.client.api.ContentResponse;
|
||||||
import org.eclipse.jetty.client.api.Destination;
|
import org.eclipse.jetty.client.api.Destination;
|
||||||
import org.eclipse.jetty.client.api.Request;
|
import org.eclipse.jetty.client.api.Request;
|
||||||
import org.eclipse.jetty.client.http.HttpConnectionOverHTTP;
|
import org.eclipse.jetty.client.http.HttpConnectionOverHTTP;
|
||||||
import org.eclipse.jetty.client.http.HttpConnectionPool;
|
|
||||||
import org.eclipse.jetty.client.http.HttpDestinationOverHTTP;
|
import org.eclipse.jetty.client.http.HttpDestinationOverHTTP;
|
||||||
import org.eclipse.jetty.client.util.FutureResponseListener;
|
import org.eclipse.jetty.client.util.FutureResponseListener;
|
||||||
import org.eclipse.jetty.toolchain.test.annotation.Slow;
|
import org.eclipse.jetty.toolchain.test.annotation.Slow;
|
||||||
|
@ -60,7 +59,7 @@ public class HttpClientExplicitConnectionTest extends AbstractHttpClientServerTe
|
||||||
Assert.assertEquals(200, response.getStatus());
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
HttpDestinationOverHTTP httpDestination = (HttpDestinationOverHTTP)destination;
|
HttpDestinationOverHTTP httpDestination = (HttpDestinationOverHTTP)destination;
|
||||||
HttpConnectionPool connectionPool = httpDestination.getHttpConnectionPool();
|
ConnectionPool connectionPool = httpDestination.getConnectionPool();
|
||||||
Assert.assertTrue(connectionPool.getActiveConnections().isEmpty());
|
Assert.assertTrue(connectionPool.getActiveConnections().isEmpty());
|
||||||
Assert.assertTrue(connectionPool.getIdleConnections().isEmpty());
|
Assert.assertTrue(connectionPool.getIdleConnections().isEmpty());
|
||||||
}
|
}
|
||||||
|
@ -92,7 +91,7 @@ public class HttpClientExplicitConnectionTest extends AbstractHttpClientServerTe
|
||||||
Assert.assertFalse(httpConnection.getEndPoint().isOpen());
|
Assert.assertFalse(httpConnection.getEndPoint().isOpen());
|
||||||
|
|
||||||
HttpDestinationOverHTTP httpDestination = (HttpDestinationOverHTTP)destination;
|
HttpDestinationOverHTTP httpDestination = (HttpDestinationOverHTTP)destination;
|
||||||
HttpConnectionPool connectionPool = httpDestination.getHttpConnectionPool();
|
ConnectionPool connectionPool = httpDestination.getConnectionPool();
|
||||||
Assert.assertTrue(connectionPool.getActiveConnections().isEmpty());
|
Assert.assertTrue(connectionPool.getActiveConnections().isEmpty());
|
||||||
Assert.assertTrue(connectionPool.getIdleConnections().isEmpty());
|
Assert.assertTrue(connectionPool.getIdleConnections().isEmpty());
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,6 @@ import org.eclipse.jetty.client.api.Request;
|
||||||
import org.eclipse.jetty.client.api.Response;
|
import org.eclipse.jetty.client.api.Response;
|
||||||
import org.eclipse.jetty.client.api.Result;
|
import org.eclipse.jetty.client.api.Result;
|
||||||
import org.eclipse.jetty.client.http.HttpConnectionOverHTTP;
|
import org.eclipse.jetty.client.http.HttpConnectionOverHTTP;
|
||||||
import org.eclipse.jetty.client.http.HttpConnectionPool;
|
|
||||||
import org.eclipse.jetty.client.http.HttpDestinationOverHTTP;
|
import org.eclipse.jetty.client.http.HttpDestinationOverHTTP;
|
||||||
import org.eclipse.jetty.client.util.BytesContentProvider;
|
import org.eclipse.jetty.client.util.BytesContentProvider;
|
||||||
import org.eclipse.jetty.http.HttpHeader;
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
|
@ -85,7 +84,7 @@ public class HttpClientLoadTest extends AbstractHttpClientServerTest
|
||||||
}
|
}
|
||||||
|
|
||||||
// Re-run after warmup
|
// Re-run after warmup
|
||||||
iterations = 50_000;
|
iterations = 5_000;
|
||||||
for (int i = 0; i < runs; ++i)
|
for (int i = 0; i < runs; ++i)
|
||||||
{
|
{
|
||||||
run(random, iterations);
|
run(random, iterations);
|
||||||
|
@ -111,7 +110,7 @@ public class HttpClientLoadTest extends AbstractHttpClientServerTest
|
||||||
for (String host : Arrays.asList("localhost", "127.0.0.1"))
|
for (String host : Arrays.asList("localhost", "127.0.0.1"))
|
||||||
{
|
{
|
||||||
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, connector.getLocalPort());
|
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, connector.getLocalPort());
|
||||||
HttpConnectionPool connectionPool = destination.getHttpConnectionPool();
|
ConnectionPool connectionPool = destination.getConnectionPool();
|
||||||
for (Connection connection : new ArrayList<>(connectionPool.getActiveConnections()))
|
for (Connection connection : new ArrayList<>(connectionPool.getActiveConnections()))
|
||||||
{
|
{
|
||||||
HttpConnectionOverHTTP active = (HttpConnectionOverHTTP)connection;
|
HttpConnectionOverHTTP active = (HttpConnectionOverHTTP)connection;
|
||||||
|
|
|
@ -27,7 +27,6 @@ import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import org.eclipse.jetty.client.api.ContentResponse;
|
import org.eclipse.jetty.client.api.ContentResponse;
|
||||||
import org.eclipse.jetty.client.api.ProxyConfiguration;
|
|
||||||
import org.eclipse.jetty.client.api.Request;
|
import org.eclipse.jetty.client.api.Request;
|
||||||
import org.eclipse.jetty.client.util.BasicAuthentication;
|
import org.eclipse.jetty.client.util.BasicAuthentication;
|
||||||
import org.eclipse.jetty.http.HttpHeader;
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
|
@ -68,7 +67,7 @@ public class HttpClientProxyTest extends AbstractHttpClientServerTest
|
||||||
|
|
||||||
int proxyPort = connector.getLocalPort();
|
int proxyPort = connector.getLocalPort();
|
||||||
int serverPort = proxyPort + 1; // Any port will do for these tests - just not the same as the proxy
|
int serverPort = proxyPort + 1; // Any port will do for these tests - just not the same as the proxy
|
||||||
client.setProxyConfiguration(new ProxyConfiguration("localhost", proxyPort));
|
client.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyPort));
|
||||||
|
|
||||||
ContentResponse response = client.newRequest(serverHost, serverPort)
|
ContentResponse response = client.newRequest(serverHost, serverPort)
|
||||||
.scheme(scheme)
|
.scheme(scheme)
|
||||||
|
@ -115,7 +114,7 @@ public class HttpClientProxyTest extends AbstractHttpClientServerTest
|
||||||
String proxyHost = "localhost";
|
String proxyHost = "localhost";
|
||||||
int proxyPort = connector.getLocalPort();
|
int proxyPort = connector.getLocalPort();
|
||||||
int serverPort = proxyPort + 1; // Any port will do for these tests - just not the same as the proxy
|
int serverPort = proxyPort + 1; // Any port will do for these tests - just not the same as the proxy
|
||||||
client.setProxyConfiguration(new ProxyConfiguration(proxyHost, proxyPort));
|
client.getProxyConfiguration().getProxies().add(new HttpProxy(proxyHost, proxyPort));
|
||||||
|
|
||||||
ContentResponse response1 = client.newRequest(serverHost, serverPort)
|
ContentResponse response1 = client.newRequest(serverHost, serverPort)
|
||||||
.scheme(scheme)
|
.scheme(scheme)
|
||||||
|
|
|
@ -51,7 +51,6 @@ import org.eclipse.jetty.client.api.Request;
|
||||||
import org.eclipse.jetty.client.api.Response;
|
import org.eclipse.jetty.client.api.Response;
|
||||||
import org.eclipse.jetty.client.api.Result;
|
import org.eclipse.jetty.client.api.Result;
|
||||||
import org.eclipse.jetty.client.http.HttpConnectionOverHTTP;
|
import org.eclipse.jetty.client.http.HttpConnectionOverHTTP;
|
||||||
import org.eclipse.jetty.client.http.HttpConnectionPool;
|
|
||||||
import org.eclipse.jetty.client.http.HttpDestinationOverHTTP;
|
import org.eclipse.jetty.client.http.HttpDestinationOverHTTP;
|
||||||
import org.eclipse.jetty.client.util.BytesContentProvider;
|
import org.eclipse.jetty.client.util.BytesContentProvider;
|
||||||
import org.eclipse.jetty.http.HttpField;
|
import org.eclipse.jetty.http.HttpField;
|
||||||
|
@ -85,7 +84,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
||||||
Assert.assertEquals(200, response.getStatus());
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
|
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
|
||||||
HttpConnectionPool connectionPool = destination.getHttpConnectionPool();
|
ConnectionPool connectionPool = destination.getConnectionPool();
|
||||||
|
|
||||||
long start = System.nanoTime();
|
long start = System.nanoTime();
|
||||||
HttpConnectionOverHTTP connection = null;
|
HttpConnectionOverHTTP connection = null;
|
||||||
|
@ -637,7 +636,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
||||||
public void onBegin(Request request)
|
public void onBegin(Request request)
|
||||||
{
|
{
|
||||||
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
|
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
|
||||||
destination.getHttpConnectionPool().getActiveConnections().peek().close();
|
destination.getConnectionPool().getActiveConnections().peek().close();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.send(new Response.Listener.Adapter()
|
.send(new Response.Listener.Adapter()
|
||||||
|
|
|
@ -21,6 +21,7 @@ package org.eclipse.jetty.client;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
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;
|
||||||
|
@ -35,9 +36,13 @@ import org.eclipse.jetty.client.api.Request;
|
||||||
import org.eclipse.jetty.client.api.Response;
|
import org.eclipse.jetty.client.api.Response;
|
||||||
import org.eclipse.jetty.client.api.Result;
|
import org.eclipse.jetty.client.api.Result;
|
||||||
import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
|
import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
|
||||||
|
import org.eclipse.jetty.client.http.HttpDestinationOverHTTP;
|
||||||
import org.eclipse.jetty.client.util.BufferingResponseListener;
|
import org.eclipse.jetty.client.util.BufferingResponseListener;
|
||||||
import org.eclipse.jetty.client.util.InputStreamContentProvider;
|
import org.eclipse.jetty.client.util.InputStreamContentProvider;
|
||||||
|
import org.eclipse.jetty.io.ByteBufferPool;
|
||||||
|
import org.eclipse.jetty.io.ClientConnectionFactory;
|
||||||
import org.eclipse.jetty.io.EndPoint;
|
import org.eclipse.jetty.io.EndPoint;
|
||||||
|
import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
|
||||||
import org.eclipse.jetty.io.ssl.SslConnection;
|
import org.eclipse.jetty.io.ssl.SslConnection;
|
||||||
import org.eclipse.jetty.server.handler.AbstractHandler;
|
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||||
import org.eclipse.jetty.toolchain.test.annotation.Slow;
|
import org.eclipse.jetty.toolchain.test.annotation.Slow;
|
||||||
|
@ -244,15 +249,30 @@ public class HttpClientTimeoutTest extends AbstractHttpClientServerTest
|
||||||
client = new HttpClient(new HttpClientTransportOverHTTP()
|
client = new HttpClient(new HttpClientTransportOverHTTP()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
protected SslConnection newSslConnection(HttpClient httpClient, EndPoint endPoint, SSLEngine engine)
|
public HttpDestination newHttpDestination(Origin origin)
|
||||||
{
|
{
|
||||||
return new SslConnection(httpClient.getByteBufferPool(), httpClient.getExecutor(), endPoint, engine)
|
return new HttpDestinationOverHTTP(getHttpClient(), origin)
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
protected boolean onReadTimeout()
|
protected ClientConnectionFactory newSslClientConnectionFactory(ClientConnectionFactory connectionFactory)
|
||||||
{
|
{
|
||||||
sslIdle.set(true);
|
HttpClient client = getHttpClient();
|
||||||
return super.onReadTimeout();
|
return new SslClientConnectionFactory(client.getSslContextFactory(), client.getByteBufferPool(), client.getExecutor(), connectionFactory)
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected SslConnection newSslConnection(ByteBufferPool byteBufferPool, Executor executor, EndPoint endPoint, SSLEngine engine)
|
||||||
|
{
|
||||||
|
return new SslConnection(byteBufferPool, executor, endPoint, engine)
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected boolean onReadTimeout()
|
||||||
|
{
|
||||||
|
sslIdle.set(true);
|
||||||
|
return super.onReadTimeout();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,6 @@ import org.eclipse.jetty.client.api.ContentResponse;
|
||||||
import org.eclipse.jetty.client.api.Request;
|
import org.eclipse.jetty.client.api.Request;
|
||||||
import org.eclipse.jetty.client.api.Response;
|
import org.eclipse.jetty.client.api.Response;
|
||||||
import org.eclipse.jetty.client.api.Result;
|
import org.eclipse.jetty.client.api.Result;
|
||||||
import org.eclipse.jetty.client.http.HttpConnectionPool;
|
|
||||||
import org.eclipse.jetty.client.http.HttpDestinationOverHTTP;
|
import org.eclipse.jetty.client.http.HttpDestinationOverHTTP;
|
||||||
import org.eclipse.jetty.client.util.ByteBufferContentProvider;
|
import org.eclipse.jetty.client.util.ByteBufferContentProvider;
|
||||||
import org.eclipse.jetty.http.HttpHeader;
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
|
@ -68,7 +67,7 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
|
||||||
String host = "localhost";
|
String host = "localhost";
|
||||||
int port = connector.getLocalPort();
|
int port = connector.getLocalPort();
|
||||||
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
|
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
|
||||||
HttpConnectionPool connectionPool = destination.getHttpConnectionPool();
|
ConnectionPool connectionPool = destination.getConnectionPool();
|
||||||
|
|
||||||
final BlockingQueue<Connection> idleConnections = connectionPool.getIdleConnections();
|
final BlockingQueue<Connection> idleConnections = connectionPool.getIdleConnections();
|
||||||
Assert.assertEquals(0, idleConnections.size());
|
Assert.assertEquals(0, idleConnections.size());
|
||||||
|
@ -129,7 +128,7 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
|
||||||
String host = "localhost";
|
String host = "localhost";
|
||||||
int port = connector.getLocalPort();
|
int port = connector.getLocalPort();
|
||||||
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
|
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
|
||||||
HttpConnectionPool connectionPool = destination.getHttpConnectionPool();
|
ConnectionPool connectionPool = destination.getConnectionPool();
|
||||||
|
|
||||||
final BlockingQueue<Connection> idleConnections = connectionPool.getIdleConnections();
|
final BlockingQueue<Connection> idleConnections = connectionPool.getIdleConnections();
|
||||||
Assert.assertEquals(0, idleConnections.size());
|
Assert.assertEquals(0, idleConnections.size());
|
||||||
|
@ -180,7 +179,7 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
|
||||||
String host = "localhost";
|
String host = "localhost";
|
||||||
int port = connector.getLocalPort();
|
int port = connector.getLocalPort();
|
||||||
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
|
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
|
||||||
HttpConnectionPool connectionPool = destination.getHttpConnectionPool();
|
ConnectionPool connectionPool = destination.getConnectionPool();
|
||||||
|
|
||||||
final BlockingQueue<Connection> idleConnections = connectionPool.getIdleConnections();
|
final BlockingQueue<Connection> idleConnections = connectionPool.getIdleConnections();
|
||||||
Assert.assertEquals(0, idleConnections.size());
|
Assert.assertEquals(0, idleConnections.size());
|
||||||
|
@ -240,7 +239,7 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
|
||||||
String host = "localhost";
|
String host = "localhost";
|
||||||
int port = connector.getLocalPort();
|
int port = connector.getLocalPort();
|
||||||
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
|
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
|
||||||
HttpConnectionPool connectionPool = destination.getHttpConnectionPool();
|
ConnectionPool connectionPool = destination.getConnectionPool();
|
||||||
|
|
||||||
final BlockingQueue<Connection> idleConnections = connectionPool.getIdleConnections();
|
final BlockingQueue<Connection> idleConnections = connectionPool.getIdleConnections();
|
||||||
Assert.assertEquals(0, idleConnections.size());
|
Assert.assertEquals(0, idleConnections.size());
|
||||||
|
@ -313,7 +312,7 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
|
||||||
String host = "localhost";
|
String host = "localhost";
|
||||||
int port = connector.getLocalPort();
|
int port = connector.getLocalPort();
|
||||||
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
|
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
|
||||||
HttpConnectionPool connectionPool = destination.getHttpConnectionPool();
|
ConnectionPool connectionPool = destination.getConnectionPool();
|
||||||
|
|
||||||
final BlockingQueue<Connection> idleConnections = connectionPool.getIdleConnections();
|
final BlockingQueue<Connection> idleConnections = connectionPool.getIdleConnections();
|
||||||
Assert.assertEquals(0, idleConnections.size());
|
Assert.assertEquals(0, idleConnections.size());
|
||||||
|
@ -366,7 +365,7 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
|
||||||
String host = "localhost";
|
String host = "localhost";
|
||||||
int port = connector.getLocalPort();
|
int port = connector.getLocalPort();
|
||||||
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
|
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
|
||||||
HttpConnectionPool connectionPool = destination.getHttpConnectionPool();
|
ConnectionPool connectionPool = destination.getConnectionPool();
|
||||||
|
|
||||||
final BlockingQueue<Connection> idleConnections = connectionPool.getIdleConnections();
|
final BlockingQueue<Connection> idleConnections = connectionPool.getIdleConnections();
|
||||||
Assert.assertEquals(0, idleConnections.size());
|
Assert.assertEquals(0, idleConnections.size());
|
||||||
|
@ -416,7 +415,7 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
|
||||||
String host = "localhost";
|
String host = "localhost";
|
||||||
int port = connector.getLocalPort();
|
int port = connector.getLocalPort();
|
||||||
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
|
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
|
||||||
HttpConnectionPool connectionPool = destination.getHttpConnectionPool();
|
ConnectionPool connectionPool = destination.getConnectionPool();
|
||||||
|
|
||||||
final BlockingQueue<Connection> idleConnections = connectionPool.getIdleConnections();
|
final BlockingQueue<Connection> idleConnections = connectionPool.getIdleConnections();
|
||||||
Assert.assertEquals(0, idleConnections.size());
|
Assert.assertEquals(0, idleConnections.size());
|
||||||
|
@ -466,7 +465,7 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
|
||||||
String host = "localhost";
|
String host = "localhost";
|
||||||
int port = connector.getLocalPort();
|
int port = connector.getLocalPort();
|
||||||
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
|
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
|
||||||
HttpConnectionPool connectionPool = destination.getHttpConnectionPool();
|
ConnectionPool connectionPool = destination.getConnectionPool();
|
||||||
|
|
||||||
final BlockingQueue<Connection> idleConnections = connectionPool.getIdleConnections();
|
final BlockingQueue<Connection> idleConnections = connectionPool.getIdleConnections();
|
||||||
Assert.assertEquals(0, idleConnections.size());
|
Assert.assertEquals(0, idleConnections.size());
|
||||||
|
|
|
@ -24,6 +24,7 @@ import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import org.eclipse.jetty.client.AbstractHttpClientServerTest;
|
import org.eclipse.jetty.client.AbstractHttpClientServerTest;
|
||||||
import org.eclipse.jetty.client.EmptyServerHandler;
|
import org.eclipse.jetty.client.EmptyServerHandler;
|
||||||
|
import org.eclipse.jetty.client.Origin;
|
||||||
import org.eclipse.jetty.client.api.Connection;
|
import org.eclipse.jetty.client.api.Connection;
|
||||||
import org.eclipse.jetty.client.api.ContentResponse;
|
import org.eclipse.jetty.client.api.ContentResponse;
|
||||||
import org.eclipse.jetty.client.api.Request;
|
import org.eclipse.jetty.client.api.Request;
|
||||||
|
@ -52,12 +53,12 @@ public class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest
|
||||||
@Test
|
@Test
|
||||||
public void test_FirstAcquire_WithEmptyQueue() throws Exception
|
public void test_FirstAcquire_WithEmptyQueue() throws Exception
|
||||||
{
|
{
|
||||||
HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, "http", "localhost", connector.getLocalPort());
|
HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", connector.getLocalPort()));
|
||||||
Connection connection = destination.acquire();
|
Connection connection = destination.acquire();
|
||||||
if (connection == null)
|
if (connection == null)
|
||||||
{
|
{
|
||||||
// There are no queued requests, so the newly created connection will be idle
|
// There are no queued requests, so the newly created connection will be idle
|
||||||
connection = destination.getHttpConnectionPool().getIdleConnections().poll(5, TimeUnit.SECONDS);
|
connection = destination.getConnectionPool().getIdleConnections().poll(5, TimeUnit.SECONDS);
|
||||||
}
|
}
|
||||||
Assert.assertNotNull(connection);
|
Assert.assertNotNull(connection);
|
||||||
}
|
}
|
||||||
|
@ -65,7 +66,7 @@ public class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest
|
||||||
@Test
|
@Test
|
||||||
public void test_SecondAcquire_AfterFirstAcquire_WithEmptyQueue_ReturnsSameConnection() throws Exception
|
public void test_SecondAcquire_AfterFirstAcquire_WithEmptyQueue_ReturnsSameConnection() throws Exception
|
||||||
{
|
{
|
||||||
HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, "http", "localhost", connector.getLocalPort());
|
HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", connector.getLocalPort()));
|
||||||
Connection connection1 = destination.acquire();
|
Connection connection1 = destination.acquire();
|
||||||
if (connection1 == null)
|
if (connection1 == null)
|
||||||
{
|
{
|
||||||
|
@ -74,7 +75,7 @@ public class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest
|
||||||
while (connection1 == null && TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - start) < 5)
|
while (connection1 == null && TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - start) < 5)
|
||||||
{
|
{
|
||||||
TimeUnit.MILLISECONDS.sleep(50);
|
TimeUnit.MILLISECONDS.sleep(50);
|
||||||
connection1 = destination.getHttpConnectionPool().getIdleConnections().peek();
|
connection1 = destination.getConnectionPool().getIdleConnections().peek();
|
||||||
}
|
}
|
||||||
Assert.assertNotNull(connection1);
|
Assert.assertNotNull(connection1);
|
||||||
|
|
||||||
|
@ -87,7 +88,7 @@ public class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest
|
||||||
public void test_SecondAcquire_ConcurrentWithFirstAcquire_WithEmptyQueue_CreatesTwoConnections() throws Exception
|
public void test_SecondAcquire_ConcurrentWithFirstAcquire_WithEmptyQueue_CreatesTwoConnections() throws Exception
|
||||||
{
|
{
|
||||||
final CountDownLatch latch = new CountDownLatch(1);
|
final CountDownLatch latch = new CountDownLatch(1);
|
||||||
HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, "http", "localhost", connector.getLocalPort())
|
HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", connector.getLocalPort()))
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
protected void process(HttpConnectionOverHTTP connection, boolean dispatch)
|
protected void process(HttpConnectionOverHTTP connection, boolean dispatch)
|
||||||
|
@ -115,23 +116,23 @@ public class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest
|
||||||
latch.countDown();
|
latch.countDown();
|
||||||
|
|
||||||
// There must be 2 idle connections
|
// There must be 2 idle connections
|
||||||
Connection connection = destination.getHttpConnectionPool().getIdleConnections().poll(5, TimeUnit.SECONDS);
|
Connection connection = destination.getConnectionPool().getIdleConnections().poll(5, TimeUnit.SECONDS);
|
||||||
Assert.assertNotNull(connection);
|
Assert.assertNotNull(connection);
|
||||||
connection = destination.getHttpConnectionPool().getIdleConnections().poll(5, TimeUnit.SECONDS);
|
connection = destination.getConnectionPool().getIdleConnections().poll(5, TimeUnit.SECONDS);
|
||||||
Assert.assertNotNull(connection);
|
Assert.assertNotNull(connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test_Acquire_Process_Release_Acquire_ReturnsSameConnection() throws Exception
|
public void test_Acquire_Process_Release_Acquire_ReturnsSameConnection() throws Exception
|
||||||
{
|
{
|
||||||
HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, "http", "localhost", connector.getLocalPort());
|
HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", connector.getLocalPort()));
|
||||||
HttpConnectionOverHTTP connection1 = destination.acquire();
|
HttpConnectionOverHTTP connection1 = destination.acquire();
|
||||||
|
|
||||||
long start = System.nanoTime();
|
long start = System.nanoTime();
|
||||||
while (connection1 == null && TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - start) < 5)
|
while (connection1 == null && TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - start) < 5)
|
||||||
{
|
{
|
||||||
TimeUnit.MILLISECONDS.sleep(50);
|
TimeUnit.MILLISECONDS.sleep(50);
|
||||||
connection1 = (HttpConnectionOverHTTP)destination.getHttpConnectionPool().getIdleConnections().peek();
|
connection1 = (HttpConnectionOverHTTP)destination.getConnectionPool().getIdleConnections().peek();
|
||||||
}
|
}
|
||||||
Assert.assertNotNull(connection1);
|
Assert.assertNotNull(connection1);
|
||||||
|
|
||||||
|
@ -152,7 +153,7 @@ public class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest
|
||||||
long idleTimeout = 1000;
|
long idleTimeout = 1000;
|
||||||
client.setIdleTimeout(idleTimeout);
|
client.setIdleTimeout(idleTimeout);
|
||||||
|
|
||||||
HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, "http", "localhost", connector.getLocalPort());
|
HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", connector.getLocalPort()));
|
||||||
Connection connection1 = destination.acquire();
|
Connection connection1 = destination.acquire();
|
||||||
if (connection1 == null)
|
if (connection1 == null)
|
||||||
{
|
{
|
||||||
|
@ -161,13 +162,13 @@ public class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest
|
||||||
while (connection1 == null && TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - start) < 5)
|
while (connection1 == null && TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - start) < 5)
|
||||||
{
|
{
|
||||||
TimeUnit.MILLISECONDS.sleep(50);
|
TimeUnit.MILLISECONDS.sleep(50);
|
||||||
connection1 = destination.getHttpConnectionPool().getIdleConnections().peek();
|
connection1 = destination.getConnectionPool().getIdleConnections().peek();
|
||||||
}
|
}
|
||||||
Assert.assertNotNull(connection1);
|
Assert.assertNotNull(connection1);
|
||||||
|
|
||||||
TimeUnit.MILLISECONDS.sleep(2 * idleTimeout);
|
TimeUnit.MILLISECONDS.sleep(2 * idleTimeout);
|
||||||
|
|
||||||
connection1 = destination.getHttpConnectionPool().getIdleConnections().poll();
|
connection1 = destination.getConnectionPool().getIdleConnections().poll();
|
||||||
Assert.assertNull(connection1);
|
Assert.assertNull(connection1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// All rights reserved. This program and the accompanying materials
|
||||||
|
// are made available under the terms of the Eclipse Public License v1.0
|
||||||
|
// and Apache License v2.0 which accompanies this distribution.
|
||||||
|
//
|
||||||
|
// The Eclipse Public License is available at
|
||||||
|
// http://www.eclipse.org/legal/epl-v10.html
|
||||||
|
//
|
||||||
|
// The Apache License v2.0 is available at
|
||||||
|
// http://www.opensource.org/licenses/apache2.0.php
|
||||||
|
//
|
||||||
|
// You may elect to redistribute this code under either of these licenses.
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
package org.eclipse.jetty.io;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.util.log.Log;
|
||||||
|
import org.eclipse.jetty.util.log.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory for client-side {@link Connection} instances.
|
||||||
|
*/
|
||||||
|
public interface ClientConnectionFactory
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param endPoint the {@link org.eclipse.jetty.io.EndPoint} to link the newly created connection to
|
||||||
|
* @param context the context data to create the connection
|
||||||
|
* @return a new {@link Connection}
|
||||||
|
* @throws IOException if the connection cannot be created
|
||||||
|
*/
|
||||||
|
public Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException;
|
||||||
|
|
||||||
|
public static class Helper
|
||||||
|
{
|
||||||
|
private static Logger LOG = Log.getLogger(Helper.class);
|
||||||
|
|
||||||
|
private Helper()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replaces the given {@code oldConnection} with the given {@code newConnection} on the
|
||||||
|
* {@link EndPoint} associated with {@code oldConnection}, performing connection lifecycle management.
|
||||||
|
* <p />
|
||||||
|
* The {@code oldConnection} will be closed by invoking {@link org.eclipse.jetty.io.Connection#onClose()}
|
||||||
|
* and the {@code newConnection} will be opened by invoking {@link org.eclipse.jetty.io.Connection#onOpen()}.
|
||||||
|
* @param oldConnection the old connection to replace
|
||||||
|
* @param newConnection the new connection replacement
|
||||||
|
*/
|
||||||
|
public static void replaceConnection(Connection oldConnection, Connection newConnection)
|
||||||
|
{
|
||||||
|
close(oldConnection);
|
||||||
|
oldConnection.getEndPoint().setConnection(newConnection);
|
||||||
|
open(newConnection);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void open(Connection connection)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
connection.onOpen();
|
||||||
|
}
|
||||||
|
catch (Throwable x)
|
||||||
|
{
|
||||||
|
LOG.debug(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void close(Connection connection)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
connection.onClose();
|
||||||
|
}
|
||||||
|
catch (Throwable x)
|
||||||
|
{
|
||||||
|
LOG.debug(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// All rights reserved. This program and the accompanying materials
|
||||||
|
// are made available under the terms of the Eclipse Public License v1.0
|
||||||
|
// and Apache License v2.0 which accompanies this distribution.
|
||||||
|
//
|
||||||
|
// The Eclipse Public License is available at
|
||||||
|
// http://www.eclipse.org/legal/epl-v10.html
|
||||||
|
//
|
||||||
|
// The Apache License v2.0 is available at
|
||||||
|
// http://www.opensource.org/licenses/apache2.0.php
|
||||||
|
//
|
||||||
|
// You may elect to redistribute this code under either of these licenses.
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
package org.eclipse.jetty.io.ssl;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import javax.net.ssl.SSLEngine;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.io.ByteBufferPool;
|
||||||
|
import org.eclipse.jetty.io.ClientConnectionFactory;
|
||||||
|
import org.eclipse.jetty.io.EndPoint;
|
||||||
|
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||||
|
|
||||||
|
public class SslClientConnectionFactory implements ClientConnectionFactory
|
||||||
|
{
|
||||||
|
public static final String SSL_PEER_HOST_CONTEXT_KEY = "ssl.peer.host";
|
||||||
|
public static final String SSL_PEER_PORT_CONTEXT_KEY = "ssl.peer.port";
|
||||||
|
public static final String SSL_ENGINE_CONTEXT_KEY = "ssl.engine";
|
||||||
|
|
||||||
|
private final SslContextFactory sslContextFactory;
|
||||||
|
private final ByteBufferPool byteBufferPool;
|
||||||
|
private final Executor executor;
|
||||||
|
private final ClientConnectionFactory connectionFactory;
|
||||||
|
|
||||||
|
public SslClientConnectionFactory(SslContextFactory sslContextFactory, ByteBufferPool byteBufferPool, Executor executor, ClientConnectionFactory connectionFactory)
|
||||||
|
{
|
||||||
|
this.sslContextFactory = sslContextFactory;
|
||||||
|
this.byteBufferPool = byteBufferPool;
|
||||||
|
this.executor = executor;
|
||||||
|
this.connectionFactory = connectionFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
|
||||||
|
{
|
||||||
|
String host = (String)context.get(SSL_PEER_HOST_CONTEXT_KEY);
|
||||||
|
int port = (Integer)context.get(SSL_PEER_PORT_CONTEXT_KEY);
|
||||||
|
SSLEngine engine = sslContextFactory.newSSLEngine(host, port);
|
||||||
|
engine.setUseClientMode(true);
|
||||||
|
context.put(SSL_ENGINE_CONTEXT_KEY, engine);
|
||||||
|
|
||||||
|
SslConnection sslConnection = newSslConnection(byteBufferPool, executor, endPoint, engine);
|
||||||
|
sslConnection.setRenegotiationAllowed(sslContextFactory.isRenegotiationAllowed());
|
||||||
|
endPoint.setConnection(sslConnection);
|
||||||
|
EndPoint appEndPoint = sslConnection.getDecryptedEndPoint();
|
||||||
|
appEndPoint.setConnection(connectionFactory.newConnection(appEndPoint, context));
|
||||||
|
|
||||||
|
return sslConnection;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected SslConnection newSslConnection(ByteBufferPool byteBufferPool, Executor executor, EndPoint endPoint, SSLEngine engine)
|
||||||
|
{
|
||||||
|
return new SslConnection(byteBufferPool, executor, endPoint, engine);
|
||||||
|
}
|
||||||
|
}
|
|
@ -48,8 +48,9 @@ import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import org.eclipse.jetty.client.HttpClient;
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
import org.eclipse.jetty.client.HttpContentResponse;
|
import org.eclipse.jetty.client.HttpContentResponse;
|
||||||
|
import org.eclipse.jetty.client.HttpProxy;
|
||||||
|
import org.eclipse.jetty.client.Origin;
|
||||||
import org.eclipse.jetty.client.api.ContentResponse;
|
import org.eclipse.jetty.client.api.ContentResponse;
|
||||||
import org.eclipse.jetty.client.api.ProxyConfiguration;
|
|
||||||
import org.eclipse.jetty.client.api.Request;
|
import org.eclipse.jetty.client.api.Request;
|
||||||
import org.eclipse.jetty.client.api.Response;
|
import org.eclipse.jetty.client.api.Response;
|
||||||
import org.eclipse.jetty.client.api.Result;
|
import org.eclipse.jetty.client.api.Result;
|
||||||
|
@ -110,7 +111,7 @@ public class ProxyServletTest
|
||||||
private HttpClient prepareClient() throws Exception
|
private HttpClient prepareClient() throws Exception
|
||||||
{
|
{
|
||||||
HttpClient result = new HttpClient();
|
HttpClient result = new HttpClient();
|
||||||
result.setProxyConfiguration(new ProxyConfiguration("localhost", proxyConnector.getLocalPort()));
|
result.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyConnector.getLocalPort()));
|
||||||
result.start();
|
result.start();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -236,7 +237,7 @@ public class ProxyServletTest
|
||||||
prepareProxy(new ProxyServlet());
|
prepareProxy(new ProxyServlet());
|
||||||
|
|
||||||
HttpClient result = new HttpClient();
|
HttpClient result = new HttpClient();
|
||||||
result.setProxyConfiguration(new ProxyConfiguration("localhost", proxyConnector.getLocalPort()));
|
result.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyConnector.getLocalPort()));
|
||||||
QueuedThreadPool threadPool = new QueuedThreadPool();
|
QueuedThreadPool threadPool = new QueuedThreadPool();
|
||||||
threadPool.setName("foo");
|
threadPool.setName("foo");
|
||||||
threadPool.setMaxThreads(20);
|
threadPool.setMaxThreads(20);
|
||||||
|
@ -631,7 +632,7 @@ public class ProxyServletTest
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
int port = serverConnector.getLocalPort();
|
int port = serverConnector.getLocalPort();
|
||||||
client.getProxyConfiguration().getExcludedOrigins().add("127.0.0.1:" + port);
|
client.getProxyConfiguration().getProxies().get(0).getExcludedOrigins().add(new Origin("http", "127.0.0.1", port));
|
||||||
|
|
||||||
// Try with a proxied host
|
// Try with a proxied host
|
||||||
ContentResponse response = client.newRequest("localhost", port)
|
ContentResponse response = client.newRequest("localhost", port)
|
||||||
|
@ -865,7 +866,7 @@ public class ProxyServletTest
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
|
client.newRequest("localhost", serverConnector.getLocalPort())
|
||||||
.timeout(5, TimeUnit.SECONDS)
|
.timeout(5, TimeUnit.SECONDS)
|
||||||
.send();
|
.send();
|
||||||
|
|
||||||
|
|
|
@ -26,17 +26,16 @@ import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
import javax.servlet.ServletOutputStream;
|
import javax.servlet.ServletOutputStream;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import org.eclipse.jetty.client.HttpClient;
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
|
import org.eclipse.jetty.client.HttpProxy;
|
||||||
import org.eclipse.jetty.client.api.Connection;
|
import org.eclipse.jetty.client.api.Connection;
|
||||||
import org.eclipse.jetty.client.api.ContentResponse;
|
import org.eclipse.jetty.client.api.ContentResponse;
|
||||||
import org.eclipse.jetty.client.api.Destination;
|
import org.eclipse.jetty.client.api.Destination;
|
||||||
import org.eclipse.jetty.client.api.ProxyConfiguration;
|
|
||||||
import org.eclipse.jetty.client.util.FutureResponseListener;
|
import org.eclipse.jetty.client.util.FutureResponseListener;
|
||||||
import org.eclipse.jetty.client.util.StringContentProvider;
|
import org.eclipse.jetty.client.util.StringContentProvider;
|
||||||
import org.eclipse.jetty.http.HttpHeader;
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
|
@ -143,7 +142,7 @@ public class ProxyTunnellingTest
|
||||||
startProxy();
|
startProxy();
|
||||||
|
|
||||||
HttpClient httpClient = new HttpClient(sslContextFactory);
|
HttpClient httpClient = new HttpClient(sslContextFactory);
|
||||||
httpClient.setProxyConfiguration(new ProxyConfiguration("localhost", proxyPort()));
|
httpClient.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyPort()));
|
||||||
httpClient.start();
|
httpClient.start();
|
||||||
|
|
||||||
try
|
try
|
||||||
|
@ -172,7 +171,7 @@ public class ProxyTunnellingTest
|
||||||
startProxy();
|
startProxy();
|
||||||
|
|
||||||
HttpClient httpClient = new HttpClient(sslContextFactory);
|
HttpClient httpClient = new HttpClient(sslContextFactory);
|
||||||
httpClient.setProxyConfiguration(new ProxyConfiguration("localhost", proxyPort()));
|
httpClient.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyPort()));
|
||||||
httpClient.start();
|
httpClient.start();
|
||||||
|
|
||||||
try
|
try
|
||||||
|
@ -215,7 +214,7 @@ public class ProxyTunnellingTest
|
||||||
startProxy();
|
startProxy();
|
||||||
|
|
||||||
final HttpClient httpClient = new HttpClient(sslContextFactory);
|
final HttpClient httpClient = new HttpClient(sslContextFactory);
|
||||||
httpClient.setProxyConfiguration(new ProxyConfiguration("localhost", proxyPort()));
|
httpClient.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyPort()));
|
||||||
httpClient.start();
|
httpClient.start();
|
||||||
|
|
||||||
try
|
try
|
||||||
|
@ -285,7 +284,7 @@ public class ProxyTunnellingTest
|
||||||
stopProxy();
|
stopProxy();
|
||||||
|
|
||||||
HttpClient httpClient = new HttpClient(sslContextFactory);
|
HttpClient httpClient = new HttpClient(sslContextFactory);
|
||||||
httpClient.setProxyConfiguration(new ProxyConfiguration("localhost", proxyPort));
|
httpClient.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyPort));
|
||||||
httpClient.start();
|
httpClient.start();
|
||||||
|
|
||||||
try
|
try
|
||||||
|
@ -317,7 +316,7 @@ public class ProxyTunnellingTest
|
||||||
startProxy();
|
startProxy();
|
||||||
|
|
||||||
HttpClient httpClient = new HttpClient(sslContextFactory);
|
HttpClient httpClient = new HttpClient(sslContextFactory);
|
||||||
httpClient.setProxyConfiguration(new ProxyConfiguration("localhost", proxyPort()));
|
httpClient.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyPort()));
|
||||||
httpClient.start();
|
httpClient.start();
|
||||||
|
|
||||||
try
|
try
|
||||||
|
@ -354,7 +353,7 @@ public class ProxyTunnellingTest
|
||||||
});
|
});
|
||||||
|
|
||||||
HttpClient httpClient = new HttpClient(sslContextFactory);
|
HttpClient httpClient = new HttpClient(sslContextFactory);
|
||||||
httpClient.setProxyConfiguration(new ProxyConfiguration("localhost", proxyPort()));
|
httpClient.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyPort()));
|
||||||
httpClient.start();
|
httpClient.start();
|
||||||
|
|
||||||
try
|
try
|
||||||
|
@ -394,7 +393,7 @@ public class ProxyTunnellingTest
|
||||||
sslContextFactory.start();
|
sslContextFactory.start();
|
||||||
|
|
||||||
HttpClient httpClient = new HttpClient(sslContextFactory);
|
HttpClient httpClient = new HttpClient(sslContextFactory);
|
||||||
httpClient.setProxyConfiguration(new ProxyConfiguration(proxyHost, proxyPort));
|
httpClient.getProxyConfiguration().getProxies().add(new HttpProxy(proxyHost, proxyPort));
|
||||||
httpClient.start();
|
httpClient.start();
|
||||||
|
|
||||||
try
|
try
|
||||||
|
|
|
@ -20,10 +20,11 @@ package org.eclipse.jetty.spdy.client;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.Map;
|
||||||
import javax.net.ssl.SSLEngine;
|
import javax.net.ssl.SSLEngine;
|
||||||
|
|
||||||
import org.eclipse.jetty.io.AbstractConnection;
|
import org.eclipse.jetty.io.AbstractConnection;
|
||||||
|
import org.eclipse.jetty.io.ClientConnectionFactory;
|
||||||
import org.eclipse.jetty.io.Connection;
|
import org.eclipse.jetty.io.Connection;
|
||||||
import org.eclipse.jetty.io.EndPoint;
|
import org.eclipse.jetty.io.EndPoint;
|
||||||
import org.eclipse.jetty.io.RuntimeIOException;
|
import org.eclipse.jetty.io.RuntimeIOException;
|
||||||
|
@ -32,20 +33,22 @@ import org.eclipse.jetty.util.BufferUtil;
|
||||||
import org.eclipse.jetty.util.log.Log;
|
import org.eclipse.jetty.util.log.Log;
|
||||||
import org.eclipse.jetty.util.log.Logger;
|
import org.eclipse.jetty.util.log.Logger;
|
||||||
|
|
||||||
public class NextProtoNegoClientConnection extends AbstractConnection implements NextProtoNego.ClientProvider
|
public class NPNClientConnection extends AbstractConnection implements NextProtoNego.ClientProvider
|
||||||
{
|
{
|
||||||
private final Logger LOG = Log.getLogger(getClass());
|
private final Logger LOG = Log.getLogger(getClass());
|
||||||
private final SPDYClient client;
|
private final SPDYClient client;
|
||||||
private final Object attachment;
|
private final ClientConnectionFactory connectionFactory;
|
||||||
private final SSLEngine engine;
|
private final SSLEngine engine;
|
||||||
|
private final Map<String, Object> context;
|
||||||
private volatile boolean completed;
|
private volatile boolean completed;
|
||||||
|
|
||||||
public NextProtoNegoClientConnection(EndPoint endPoint, Executor executor, SPDYClient client, Object attachment, SSLEngine engine)
|
public NPNClientConnection(EndPoint endPoint, SPDYClient client, ClientConnectionFactory connectionFactory, SSLEngine sslEngine, Map<String, Object> context)
|
||||||
{
|
{
|
||||||
super(endPoint, executor);
|
super(endPoint, client.getFactory().getExecutor());
|
||||||
this.client = client;
|
this.client = client;
|
||||||
this.attachment = attachment;
|
this.connectionFactory = connectionFactory;
|
||||||
this.engine = engine;
|
this.engine = sslEngine;
|
||||||
|
this.context = context;
|
||||||
NextProtoNego.put(engine, this);
|
NextProtoNego.put(engine, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,7 +95,7 @@ public class NextProtoNegoClientConnection extends AbstractConnection implements
|
||||||
{
|
{
|
||||||
LOG.debug(x);
|
LOG.debug(x);
|
||||||
NextProtoNego.remove(engine);
|
NextProtoNego.remove(engine);
|
||||||
getEndPoint().close();
|
close();
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -115,16 +118,22 @@ public class NextProtoNegoClientConnection extends AbstractConnection implements
|
||||||
{
|
{
|
||||||
NextProtoNego.remove(engine);
|
NextProtoNego.remove(engine);
|
||||||
completed = true;
|
completed = true;
|
||||||
String protocol = client.selectProtocol(protocols);
|
return client.selectProtocol(protocols);
|
||||||
return protocol == null ? null : protocol;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void replaceConnection()
|
private void replaceConnection()
|
||||||
{
|
{
|
||||||
EndPoint endPoint = getEndPoint();
|
EndPoint endPoint = getEndPoint();
|
||||||
Connection connection = client.getConnectionFactory().newConnection(endPoint, attachment);
|
try
|
||||||
endPoint.getConnection().onClose();
|
{
|
||||||
endPoint.setConnection(connection);
|
Connection oldConnection = endPoint.getConnection();
|
||||||
connection.onOpen();
|
Connection newConnection = connectionFactory.newConnection(endPoint, context);
|
||||||
|
ClientConnectionFactory.Helper.replaceConnection(oldConnection, newConnection);
|
||||||
|
}
|
||||||
|
catch (Throwable x)
|
||||||
|
{
|
||||||
|
LOG.debug(x);
|
||||||
|
close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// All rights reserved. This program and the accompanying materials
|
||||||
|
// are made available under the terms of the Eclipse Public License v1.0
|
||||||
|
// and Apache License v2.0 which accompanies this distribution.
|
||||||
|
//
|
||||||
|
// The Eclipse Public License is available at
|
||||||
|
// http://www.eclipse.org/legal/epl-v10.html
|
||||||
|
//
|
||||||
|
// The Apache License v2.0 is available at
|
||||||
|
// http://www.opensource.org/licenses/apache2.0.php
|
||||||
|
//
|
||||||
|
// You may elect to redistribute this code under either of these licenses.
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
package org.eclipse.jetty.spdy.client;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Map;
|
||||||
|
import javax.net.ssl.SSLEngine;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.io.ClientConnectionFactory;
|
||||||
|
import org.eclipse.jetty.io.Connection;
|
||||||
|
import org.eclipse.jetty.io.EndPoint;
|
||||||
|
import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
|
||||||
|
|
||||||
|
public class NPNClientConnectionFactory implements ClientConnectionFactory
|
||||||
|
{
|
||||||
|
private final SPDYClient client;
|
||||||
|
private final ClientConnectionFactory connectionFactory;
|
||||||
|
|
||||||
|
public NPNClientConnectionFactory(SPDYClient client, ClientConnectionFactory connectionFactory)
|
||||||
|
{
|
||||||
|
this.client = client;
|
||||||
|
this.connectionFactory = connectionFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
|
||||||
|
{
|
||||||
|
return new NPNClientConnection(endPoint, client, connectionFactory,
|
||||||
|
(SSLEngine)context.get(SslClientConnectionFactory.SSL_ENGINE_CONTEXT_KEY), context);
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,26 +19,28 @@
|
||||||
package org.eclipse.jetty.spdy.client;
|
package org.eclipse.jetty.spdy.client;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
import java.nio.channels.SelectionKey;
|
import java.nio.channels.SelectionKey;
|
||||||
import java.nio.channels.SocketChannel;
|
import java.nio.channels.SocketChannel;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import javax.net.ssl.SSLEngine;
|
|
||||||
|
|
||||||
import org.eclipse.jetty.io.ByteBufferPool;
|
import org.eclipse.jetty.io.ByteBufferPool;
|
||||||
|
import org.eclipse.jetty.io.ClientConnectionFactory;
|
||||||
import org.eclipse.jetty.io.Connection;
|
import org.eclipse.jetty.io.Connection;
|
||||||
import org.eclipse.jetty.io.EndPoint;
|
import org.eclipse.jetty.io.EndPoint;
|
||||||
import org.eclipse.jetty.io.MappedByteBufferPool;
|
import org.eclipse.jetty.io.MappedByteBufferPool;
|
||||||
import org.eclipse.jetty.io.SelectChannelEndPoint;
|
import org.eclipse.jetty.io.SelectChannelEndPoint;
|
||||||
import org.eclipse.jetty.io.SelectorManager;
|
import org.eclipse.jetty.io.SelectorManager;
|
||||||
import org.eclipse.jetty.io.ssl.SslConnection;
|
import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
|
||||||
import org.eclipse.jetty.io.ssl.SslConnection.DecryptedEndPoint;
|
|
||||||
import org.eclipse.jetty.spdy.FlowControlStrategy;
|
import org.eclipse.jetty.spdy.FlowControlStrategy;
|
||||||
import org.eclipse.jetty.spdy.api.GoAwayInfo;
|
import org.eclipse.jetty.spdy.api.GoAwayInfo;
|
||||||
import org.eclipse.jetty.spdy.api.Session;
|
import org.eclipse.jetty.spdy.api.Session;
|
||||||
|
@ -55,7 +57,7 @@ import org.eclipse.jetty.util.thread.Scheduler;
|
||||||
/**
|
/**
|
||||||
* A {@link SPDYClient} allows applications to connect to one or more SPDY servers,
|
* A {@link SPDYClient} allows applications to connect to one or more SPDY servers,
|
||||||
* obtaining {@link Session} objects that can be used to send/receive SPDY frames.
|
* obtaining {@link Session} objects that can be used to send/receive SPDY frames.
|
||||||
* <p />
|
* <p/>
|
||||||
* {@link SPDYClient} instances are created through a {@link Factory}:
|
* {@link SPDYClient} instances are created through a {@link Factory}:
|
||||||
* <pre>
|
* <pre>
|
||||||
* SPDYClient.Factory factory = new SPDYClient.Factory();
|
* SPDYClient.Factory factory = new SPDYClient.Factory();
|
||||||
|
@ -76,6 +78,7 @@ public class SPDYClient
|
||||||
private volatile long idleTimeout = -1;
|
private volatile long idleTimeout = -1;
|
||||||
private volatile int initialWindowSize;
|
private volatile int initialWindowSize;
|
||||||
private volatile boolean dispatchIO;
|
private volatile boolean dispatchIO;
|
||||||
|
private volatile ClientConnectionFactory connectionFactory;
|
||||||
|
|
||||||
protected SPDYClient(short version, Factory factory)
|
protected SPDYClient(short version, Factory factory)
|
||||||
{
|
{
|
||||||
|
@ -83,6 +86,10 @@ public class SPDYClient
|
||||||
this.factory = factory;
|
this.factory = factory;
|
||||||
setInitialWindowSize(65536);
|
setInitialWindowSize(65536);
|
||||||
setDispatchIO(true);
|
setDispatchIO(true);
|
||||||
|
ClientConnectionFactory connectionFactory = new SPDYClientConnectionFactory();
|
||||||
|
if (factory.sslContextFactory != null)
|
||||||
|
connectionFactory = new SslClientConnectionFactory(factory.getSslContextFactory(), factory.getByteBufferPool(), factory.getExecutor(), new NPNClientConnectionFactory(this, connectionFactory));
|
||||||
|
setClientConnectionFactory(connectionFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
public short getVersion()
|
public short getVersion()
|
||||||
|
@ -95,6 +102,85 @@ public class SPDYClient
|
||||||
return factory;
|
return factory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Equivalent to:
|
||||||
|
* <pre>
|
||||||
|
* Future<Session> promise = new FuturePromise<>();
|
||||||
|
* connect(address, listener, promise);
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @param address the address to connect to
|
||||||
|
* @param listener the session listener that will be notified of session events
|
||||||
|
* @return a {@link Session} when connected
|
||||||
|
*/
|
||||||
|
public Session connect(SocketAddress address, SessionFrameListener listener) throws ExecutionException, InterruptedException
|
||||||
|
{
|
||||||
|
FuturePromise<Session> promise = new FuturePromise<>();
|
||||||
|
connect(address, listener, promise);
|
||||||
|
return promise.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Equivalent to:
|
||||||
|
* <pre>
|
||||||
|
* connect(address, listener, promise, null);
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @param address the address to connect to
|
||||||
|
* @param listener the session listener that will be notified of session events
|
||||||
|
* @param promise the promise notified of connection success/failure
|
||||||
|
*/
|
||||||
|
public void connect(SocketAddress address, SessionFrameListener listener, Promise<Session> promise)
|
||||||
|
{
|
||||||
|
connect(address, listener, promise, new HashMap<String, Object>());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connects to the given {@code address}, binding the given {@code listener} to session events,
|
||||||
|
* and notified the given {@code promise} of the connect result.
|
||||||
|
* <p/>
|
||||||
|
* If the connect operation is successful, the {@code promise} will be invoked with the {@link Session}
|
||||||
|
* object that applications can use to perform SPDY requests.
|
||||||
|
*
|
||||||
|
* @param address the address to connect to
|
||||||
|
* @param listener the session listener that will be notified of session events
|
||||||
|
* @param promise the promise notified of connection success/failure
|
||||||
|
* @param context a context object passed to the {@link #getClientConnectionFactory() ConnectionFactory}
|
||||||
|
* for the creation of the connection
|
||||||
|
*/
|
||||||
|
public void connect(final SocketAddress address, final SessionFrameListener listener, final Promise<Session> promise, Map<String, Object> context)
|
||||||
|
{
|
||||||
|
if (!factory.isStarted())
|
||||||
|
throw new IllegalStateException(Factory.class.getSimpleName() + " is not started");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
SocketChannel channel = SocketChannel.open();
|
||||||
|
if (bindAddress != null)
|
||||||
|
channel.bind(bindAddress);
|
||||||
|
configure(channel);
|
||||||
|
channel.configureBlocking(false);
|
||||||
|
channel.connect(address);
|
||||||
|
|
||||||
|
context.put(SslClientConnectionFactory.SSL_PEER_HOST_CONTEXT_KEY, ((InetSocketAddress)address).getHostString());
|
||||||
|
context.put(SslClientConnectionFactory.SSL_PEER_PORT_CONTEXT_KEY, ((InetSocketAddress)address).getPort());
|
||||||
|
context.put(SPDYClientConnectionFactory.SPDY_CLIENT_CONTEXT_KEY, this);
|
||||||
|
context.put(SPDYClientConnectionFactory.SPDY_SESSION_LISTENER_CONTEXT_KEY, listener);
|
||||||
|
context.put(SPDYClientConnectionFactory.SPDY_SESSION_PROMISE_CONTEXT_KEY, promise);
|
||||||
|
|
||||||
|
factory.selector.connect(channel, context);
|
||||||
|
}
|
||||||
|
catch (IOException x)
|
||||||
|
{
|
||||||
|
promise.failed(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void configure(SocketChannel channel) throws IOException
|
||||||
|
{
|
||||||
|
channel.socket().setTcpNoDelay(true);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the address to bind the socket channel to
|
* @return the address to bind the socket channel to
|
||||||
* @see #setBindAddress(SocketAddress)
|
* @see #setBindAddress(SocketAddress)
|
||||||
|
@ -113,58 +199,6 @@ public class SPDYClient
|
||||||
this.bindAddress = bindAddress;
|
this.bindAddress = bindAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Equivalent to:
|
|
||||||
* <pre>
|
|
||||||
* Future<Session> promise = new FuturePromise<>();
|
|
||||||
* connect(address, listener, promise);
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* @param address the address to connect to
|
|
||||||
* @param listener the session listener that will be notified of session events
|
|
||||||
* @return a {@link Session} when connected
|
|
||||||
*/
|
|
||||||
public Session connect(SocketAddress address, SessionFrameListener listener) throws ExecutionException, InterruptedException
|
|
||||||
{
|
|
||||||
FuturePromise<Session> promise = new FuturePromise<>();
|
|
||||||
connect(address, listener, promise);
|
|
||||||
return promise.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Connects to the given {@code address}, binding the given {@code listener} to session events,
|
|
||||||
* and notified the given {@code promise} of the connect result.
|
|
||||||
* <p />
|
|
||||||
* If the connect operation is successful, the {@code promise} will be invoked with the {@link Session}
|
|
||||||
* object that applications can use to perform SPDY requests.
|
|
||||||
*
|
|
||||||
* @param address the address to connect to
|
|
||||||
* @param listener the session listener that will be notified of session events
|
|
||||||
* @param promise the promise notified of connection success/failure
|
|
||||||
*/
|
|
||||||
public void connect(SocketAddress address, SessionFrameListener listener, Promise<Session> promise)
|
|
||||||
{
|
|
||||||
if (!factory.isStarted())
|
|
||||||
throw new IllegalStateException(Factory.class.getSimpleName() + " is not started");
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
SocketChannel channel = SocketChannel.open();
|
|
||||||
if (bindAddress != null)
|
|
||||||
channel.bind(bindAddress);
|
|
||||||
channel.socket().setTcpNoDelay(true);
|
|
||||||
channel.configureBlocking(false);
|
|
||||||
channel.connect(address);
|
|
||||||
|
|
||||||
SessionContext context = new SessionContext(this, listener, promise);
|
|
||||||
factory.selector.connect(channel, context);
|
|
||||||
}
|
|
||||||
catch (IOException x)
|
|
||||||
{
|
|
||||||
promise.failed(x);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getIdleTimeout()
|
public long getIdleTimeout()
|
||||||
{
|
{
|
||||||
return idleTimeout;
|
return idleTimeout;
|
||||||
|
@ -195,6 +229,16 @@ public class SPDYClient
|
||||||
this.dispatchIO = dispatchIO;
|
this.dispatchIO = dispatchIO;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ClientConnectionFactory getClientConnectionFactory()
|
||||||
|
{
|
||||||
|
return connectionFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClientConnectionFactory(ClientConnectionFactory connectionFactory)
|
||||||
|
{
|
||||||
|
this.connectionFactory = connectionFactory;
|
||||||
|
}
|
||||||
|
|
||||||
protected String selectProtocol(List<String> serverProtocols)
|
protected String selectProtocol(List<String> serverProtocols)
|
||||||
{
|
{
|
||||||
String protocol = "spdy/" + version;
|
String protocol = "spdy/" + version;
|
||||||
|
@ -206,20 +250,6 @@ public class SPDYClient
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SPDYClientConnectionFactory getConnectionFactory()
|
|
||||||
{
|
|
||||||
return factory.connectionFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected SSLEngine newSSLEngine(SslContextFactory sslContextFactory, SocketChannel channel)
|
|
||||||
{
|
|
||||||
String peerHost = channel.socket().getInetAddress().getHostName();
|
|
||||||
int peerPort = channel.socket().getPort();
|
|
||||||
SSLEngine engine = sslContextFactory.newSSLEngine(peerHost, peerPort);
|
|
||||||
engine.setUseClientMode(true);
|
|
||||||
return engine;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected FlowControlStrategy newFlowControlStrategy()
|
protected FlowControlStrategy newFlowControlStrategy()
|
||||||
{
|
{
|
||||||
return FlowControlStrategyFactory.newFlowControlStrategy(version);
|
return FlowControlStrategyFactory.newFlowControlStrategy(version);
|
||||||
|
@ -227,7 +257,6 @@ public class SPDYClient
|
||||||
|
|
||||||
public static class Factory extends ContainerLifeCycle
|
public static class Factory extends ContainerLifeCycle
|
||||||
{
|
{
|
||||||
private final SPDYClientConnectionFactory connectionFactory = new SPDYClientConnectionFactory();
|
|
||||||
private final Queue<Session> sessions = new ConcurrentLinkedQueue<>();
|
private final Queue<Session> sessions = new ConcurrentLinkedQueue<>();
|
||||||
private final ByteBufferPool bufferPool = new MappedByteBufferPool();
|
private final ByteBufferPool bufferPool = new MappedByteBufferPool();
|
||||||
private final Scheduler scheduler;
|
private final Scheduler scheduler;
|
||||||
|
@ -301,6 +330,11 @@ public class SPDYClient
|
||||||
return executor;
|
return executor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SslContextFactory getSslContextFactory()
|
||||||
|
{
|
||||||
|
return sslContextFactory;
|
||||||
|
}
|
||||||
|
|
||||||
public long getConnectTimeout()
|
public long getConnectTimeout()
|
||||||
{
|
{
|
||||||
return connectTimeout;
|
return connectTimeout;
|
||||||
|
@ -358,80 +392,33 @@ public class SPDYClient
|
||||||
@Override
|
@Override
|
||||||
protected EndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key) throws IOException
|
protected EndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key) throws IOException
|
||||||
{
|
{
|
||||||
SessionContext context = (SessionContext)key.attachment();
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<String, Object> context = (Map<String, Object>)key.attachment();
|
||||||
long clientIdleTimeout = context.client.getIdleTimeout();
|
SPDYClient client = (SPDYClient)context.get(SPDYClientConnectionFactory.SPDY_CLIENT_CONTEXT_KEY);
|
||||||
|
long clientIdleTimeout = client.getIdleTimeout();
|
||||||
if (clientIdleTimeout < 0)
|
if (clientIdleTimeout < 0)
|
||||||
clientIdleTimeout = idleTimeout;
|
clientIdleTimeout = idleTimeout;
|
||||||
|
|
||||||
return new SelectChannelEndPoint(channel, selectSet, key, getScheduler(), clientIdleTimeout);
|
return new SelectChannelEndPoint(channel, selectSet, key, getScheduler(), clientIdleTimeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Connection newConnection(final SocketChannel channel, EndPoint endPoint, final Object attachment)
|
public Connection newConnection(SocketChannel channel, EndPoint endPoint, Object attachment) throws IOException
|
||||||
{
|
{
|
||||||
SessionContext context = (SessionContext)attachment;
|
@SuppressWarnings("unchecked")
|
||||||
final SPDYClient client = context.client;
|
Map<String, Object> context = (Map<String, Object>)attachment;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (sslContextFactory != null)
|
SPDYClient client = (SPDYClient)context.get(SPDYClientConnectionFactory.SPDY_CLIENT_CONTEXT_KEY);
|
||||||
{
|
return client.getClientConnectionFactory().newConnection(endPoint, context);
|
||||||
final SSLEngine engine = client.newSSLEngine(sslContextFactory, channel);
|
|
||||||
SslConnection sslConnection = new SslConnection(bufferPool, getExecutor(), endPoint, engine);
|
|
||||||
sslConnection.setRenegotiationAllowed(sslContextFactory.isRenegotiationAllowed());
|
|
||||||
DecryptedEndPoint sslEndPoint = sslConnection.getDecryptedEndPoint();
|
|
||||||
NextProtoNegoClientConnection connection = new NextProtoNegoClientConnection(sslEndPoint, getExecutor(), client, attachment, engine);
|
|
||||||
sslEndPoint.setConnection(connection);
|
|
||||||
return sslConnection;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return connectionFactory.newConnection(endPoint, attachment);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (RuntimeException x)
|
catch (Throwable x)
|
||||||
{
|
{
|
||||||
context.failed(x);
|
@SuppressWarnings("unchecked")
|
||||||
|
Promise<Session> promise = (Promise<Session>)context.get(SPDYClientConnectionFactory.SPDY_SESSION_PROMISE_CONTEXT_KEY);
|
||||||
|
promise.failed(x);
|
||||||
throw x;
|
throw x;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static class SessionContext implements Promise<Session>
|
|
||||||
{
|
|
||||||
private final Promise<Session> promise;
|
|
||||||
private final SPDYClient client;
|
|
||||||
private final SessionFrameListener listener;
|
|
||||||
|
|
||||||
private SessionContext(SPDYClient client, SessionFrameListener listener, Promise<Session> promise)
|
|
||||||
{
|
|
||||||
this.client = client;
|
|
||||||
this.listener = listener;
|
|
||||||
this.promise = promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SPDYClient getSPDYClient()
|
|
||||||
{
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SessionFrameListener getSessionFrameListener()
|
|
||||||
{
|
|
||||||
return listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void succeeded(Session result)
|
|
||||||
{
|
|
||||||
promise.succeeded(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void failed(Throwable cause)
|
|
||||||
{
|
|
||||||
promise.failed(cause);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,44 +18,54 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.spdy.client;
|
package org.eclipse.jetty.spdy.client;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import org.eclipse.jetty.io.ByteBufferPool;
|
import org.eclipse.jetty.io.ByteBufferPool;
|
||||||
|
import org.eclipse.jetty.io.ClientConnectionFactory;
|
||||||
import org.eclipse.jetty.io.Connection;
|
import org.eclipse.jetty.io.Connection;
|
||||||
import org.eclipse.jetty.io.EndPoint;
|
import org.eclipse.jetty.io.EndPoint;
|
||||||
import org.eclipse.jetty.spdy.CompressionFactory;
|
import org.eclipse.jetty.spdy.CompressionFactory;
|
||||||
import org.eclipse.jetty.spdy.FlowControlStrategy;
|
import org.eclipse.jetty.spdy.FlowControlStrategy;
|
||||||
import org.eclipse.jetty.spdy.StandardCompressionFactory;
|
import org.eclipse.jetty.spdy.StandardCompressionFactory;
|
||||||
import org.eclipse.jetty.spdy.StandardSession;
|
import org.eclipse.jetty.spdy.StandardSession;
|
||||||
|
import org.eclipse.jetty.spdy.api.Session;
|
||||||
|
import org.eclipse.jetty.spdy.api.SessionFrameListener;
|
||||||
import org.eclipse.jetty.spdy.client.SPDYClient.Factory;
|
import org.eclipse.jetty.spdy.client.SPDYClient.Factory;
|
||||||
import org.eclipse.jetty.spdy.generator.Generator;
|
import org.eclipse.jetty.spdy.generator.Generator;
|
||||||
import org.eclipse.jetty.spdy.parser.Parser;
|
import org.eclipse.jetty.spdy.parser.Parser;
|
||||||
|
import org.eclipse.jetty.util.Promise;
|
||||||
|
|
||||||
public class SPDYClientConnectionFactory
|
public class SPDYClientConnectionFactory implements ClientConnectionFactory
|
||||||
{
|
{
|
||||||
public Connection newConnection(EndPoint endPoint, Object attachment)
|
public static final String SPDY_CLIENT_CONTEXT_KEY = "spdy.client";
|
||||||
{
|
public static final String SPDY_SESSION_LISTENER_CONTEXT_KEY = "spdy.session.listener";
|
||||||
SPDYClient.SessionContext context = (SPDYClient.SessionContext)attachment;
|
public static final String SPDY_SESSION_PROMISE_CONTEXT_KEY = "spdy.session.promise";
|
||||||
SPDYClient client = context.getSPDYClient();
|
|
||||||
Factory factory = client.getFactory();
|
|
||||||
ByteBufferPool bufferPool = factory.getByteBufferPool();
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
|
||||||
|
{
|
||||||
|
SPDYClient client = (SPDYClient)context.get(SPDY_CLIENT_CONTEXT_KEY);
|
||||||
|
SPDYClient.Factory factory = client.getFactory();
|
||||||
|
ByteBufferPool byteBufferPool = factory.getByteBufferPool();
|
||||||
CompressionFactory compressionFactory = new StandardCompressionFactory();
|
CompressionFactory compressionFactory = new StandardCompressionFactory();
|
||||||
Parser parser = new Parser(compressionFactory.newDecompressor());
|
Parser parser = new Parser(compressionFactory.newDecompressor());
|
||||||
Generator generator = new Generator(bufferPool, compressionFactory.newCompressor());
|
Generator generator = new Generator(byteBufferPool, compressionFactory.newCompressor());
|
||||||
|
|
||||||
SPDYConnection connection = new ClientSPDYConnection(endPoint, bufferPool, parser, factory, client.isDispatchIO());
|
SPDYConnection connection = new ClientSPDYConnection(endPoint, byteBufferPool, parser, factory, client.isDispatchIO());
|
||||||
|
|
||||||
FlowControlStrategy flowControlStrategy = client.newFlowControlStrategy();
|
FlowControlStrategy flowControlStrategy = client.newFlowControlStrategy();
|
||||||
|
|
||||||
StandardSession session = new StandardSession(client.getVersion(), bufferPool, factory.getExecutor(),
|
SessionFrameListener listener = (SessionFrameListener)context.get(SPDY_SESSION_LISTENER_CONTEXT_KEY);
|
||||||
factory.getScheduler(), connection, endPoint, connection, 1, context.getSessionFrameListener(),
|
StandardSession session = new StandardSession(client.getVersion(), byteBufferPool, factory.getExecutor(),
|
||||||
generator, flowControlStrategy);
|
factory.getScheduler(), connection, endPoint, connection, 1, listener, generator, flowControlStrategy);
|
||||||
session.setWindowSize(client.getInitialWindowSize());
|
session.setWindowSize(client.getInitialWindowSize());
|
||||||
parser.addListener(session);
|
parser.addListener(session);
|
||||||
connection.setSession(session);
|
connection.setSession(session);
|
||||||
|
|
||||||
/*connection = context.getConnectionProvider().decorateConnection(endPoint, connection);*/
|
@SuppressWarnings("unchecked")
|
||||||
|
Promise<Session> promise = (Promise<Session>)context.get(SPDY_SESSION_PROMISE_CONTEXT_KEY);
|
||||||
context.succeeded(session);
|
promise.succeeded(session);
|
||||||
|
|
||||||
return connection;
|
return connection;
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,12 +18,17 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.spdy.client.http;
|
package org.eclipse.jetty.spdy.client.http;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import org.eclipse.jetty.client.HttpClient;
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
import org.eclipse.jetty.client.HttpClientTransport;
|
import org.eclipse.jetty.client.HttpClientTransport;
|
||||||
import org.eclipse.jetty.client.HttpDestination;
|
import org.eclipse.jetty.client.HttpDestination;
|
||||||
|
import org.eclipse.jetty.client.Origin;
|
||||||
import org.eclipse.jetty.client.api.Connection;
|
import org.eclipse.jetty.client.api.Connection;
|
||||||
|
import org.eclipse.jetty.io.ClientConnectionFactory;
|
||||||
|
import org.eclipse.jetty.io.EndPoint;
|
||||||
import org.eclipse.jetty.spdy.api.Session;
|
import org.eclipse.jetty.spdy.api.Session;
|
||||||
import org.eclipse.jetty.spdy.api.SessionFrameListener;
|
import org.eclipse.jetty.spdy.api.SessionFrameListener;
|
||||||
import org.eclipse.jetty.spdy.client.SPDYClient;
|
import org.eclipse.jetty.spdy.client.SPDYClient;
|
||||||
|
@ -32,11 +37,22 @@ import org.eclipse.jetty.util.Promise;
|
||||||
public class HttpClientTransportOverSPDY implements HttpClientTransport
|
public class HttpClientTransportOverSPDY implements HttpClientTransport
|
||||||
{
|
{
|
||||||
private final SPDYClient client;
|
private final SPDYClient client;
|
||||||
private volatile HttpClient httpClient;
|
private final ClientConnectionFactory connectionFactory;
|
||||||
|
private HttpClient httpClient;
|
||||||
|
|
||||||
public HttpClientTransportOverSPDY(SPDYClient client)
|
public HttpClientTransportOverSPDY(SPDYClient client)
|
||||||
{
|
{
|
||||||
this.client = client;
|
this.client = client;
|
||||||
|
this.connectionFactory = client.getClientConnectionFactory();
|
||||||
|
client.setClientConnectionFactory(new ClientConnectionFactory()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
|
||||||
|
{
|
||||||
|
HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
|
||||||
|
return destination.getClientConnectionFactory().newConnection(endPoint, context);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -46,51 +62,46 @@ public class HttpClientTransportOverSPDY implements HttpClientTransport
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HttpDestination newHttpDestination(String scheme, String host, int port)
|
public HttpDestination newHttpDestination(Origin origin)
|
||||||
{
|
{
|
||||||
return new HttpDestinationOverSPDY(httpClient, scheme, host, port);
|
return new HttpDestinationOverSPDY(httpClient, origin);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void connect(final HttpDestination destination, SocketAddress address, final Promise<Connection> promise)
|
public void connect(SocketAddress address, Map<String, Object> context)
|
||||||
{
|
{
|
||||||
|
final HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
final Promise<Connection> promise = (Promise<Connection>)context.get(HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
|
||||||
|
|
||||||
SessionFrameListener.Adapter listener = new SessionFrameListener.Adapter()
|
SessionFrameListener.Adapter listener = new SessionFrameListener.Adapter()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public void onFailure(Session session, Throwable x)
|
public void onFailure(Session session, Throwable x)
|
||||||
{
|
{
|
||||||
// TODO: is this correct ?
|
|
||||||
// TODO: if I get a stream error (e.g. invalid response headers)
|
|
||||||
// TODO: I must abort the *current* exchange, while below I will abort
|
|
||||||
// TODO: the queued exchanges only.
|
|
||||||
// TODO: The problem is that a single destination/connection multiplexes
|
|
||||||
// TODO: several exchanges, so I would need to cancel them all,
|
|
||||||
// TODO: or only the one that failed ?
|
|
||||||
destination.abort(x);
|
destination.abort(x);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
client.connect(address, listener, new Promise<Session>()
|
client.connect(address, listener, new Promise<Session>()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public void succeeded(Session session)
|
public void succeeded(Session session)
|
||||||
{
|
{
|
||||||
Connection result = new HttpConnectionOverSPDY(destination, session);
|
promise.succeeded(new HttpConnectionOverSPDY(destination, session));
|
||||||
promise.succeeded(result);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void failed(Throwable x)
|
public void failed(Throwable x)
|
||||||
{
|
{
|
||||||
promise.failed(x);
|
promise.failed(x);
|
||||||
}
|
}
|
||||||
}
|
}, context);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Connection tunnel(Connection connection)
|
public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
|
||||||
{
|
{
|
||||||
throw new UnsupportedOperationException();
|
return connectionFactory.newConnection(endPoint, context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,12 +21,13 @@ package org.eclipse.jetty.spdy.client.http;
|
||||||
import org.eclipse.jetty.client.HttpClient;
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
import org.eclipse.jetty.client.HttpExchange;
|
import org.eclipse.jetty.client.HttpExchange;
|
||||||
import org.eclipse.jetty.client.MultiplexHttpDestination;
|
import org.eclipse.jetty.client.MultiplexHttpDestination;
|
||||||
|
import org.eclipse.jetty.client.Origin;
|
||||||
|
|
||||||
public class HttpDestinationOverSPDY extends MultiplexHttpDestination<HttpConnectionOverSPDY>
|
public class HttpDestinationOverSPDY extends MultiplexHttpDestination<HttpConnectionOverSPDY>
|
||||||
{
|
{
|
||||||
public HttpDestinationOverSPDY(HttpClient client, String scheme, String host, int port)
|
public HttpDestinationOverSPDY(HttpClient client, Origin origin)
|
||||||
{
|
{
|
||||||
super(client, scheme, host, port);
|
super(client, origin);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -34,4 +35,12 @@ public class HttpDestinationOverSPDY extends MultiplexHttpDestination<HttpConnec
|
||||||
{
|
{
|
||||||
connection.send(exchange);
|
connection.send(exchange);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void abort(Throwable cause)
|
||||||
|
{
|
||||||
|
// TODO: in case of connection failure, we need to abort also
|
||||||
|
// TODO: all pending exchanges, so we need to track them.
|
||||||
|
super.abort(cause);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,261 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// All rights reserved. This program and the accompanying materials
|
||||||
|
// are made available under the terms of the Eclipse Public License v1.0
|
||||||
|
// and Apache License v2.0 which accompanies this distribution.
|
||||||
|
//
|
||||||
|
// The Eclipse Public License is available at
|
||||||
|
// http://www.eclipse.org/legal/epl-v10.html
|
||||||
|
//
|
||||||
|
// The Apache License v2.0 is available at
|
||||||
|
// http://www.opensource.org/licenses/apache2.0.php
|
||||||
|
//
|
||||||
|
// You may elect to redistribute this code under either of these licenses.
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
package org.eclipse.jetty.spdy.client.http;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
|
import org.eclipse.jetty.client.HttpClientTransport;
|
||||||
|
import org.eclipse.jetty.client.HttpDestination;
|
||||||
|
import org.eclipse.jetty.client.Origin;
|
||||||
|
import org.eclipse.jetty.client.ProxyConfiguration;
|
||||||
|
import org.eclipse.jetty.client.api.Connection;
|
||||||
|
import org.eclipse.jetty.client.api.ContentResponse;
|
||||||
|
import org.eclipse.jetty.http.HttpStatus;
|
||||||
|
import org.eclipse.jetty.io.AbstractConnection;
|
||||||
|
import org.eclipse.jetty.io.ClientConnectionFactory;
|
||||||
|
import org.eclipse.jetty.io.EndPoint;
|
||||||
|
import org.eclipse.jetty.server.AbstractConnectionFactory;
|
||||||
|
import org.eclipse.jetty.server.Connector;
|
||||||
|
import org.eclipse.jetty.server.Handler;
|
||||||
|
import org.eclipse.jetty.server.HttpConfiguration;
|
||||||
|
import org.eclipse.jetty.server.Server;
|
||||||
|
import org.eclipse.jetty.server.ServerConnector;
|
||||||
|
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||||
|
import org.eclipse.jetty.spdy.api.SPDY;
|
||||||
|
import org.eclipse.jetty.spdy.client.SPDYClient;
|
||||||
|
import org.eclipse.jetty.spdy.server.http.HTTPSPDYServerConnectionFactory;
|
||||||
|
import org.eclipse.jetty.spdy.server.http.PushStrategy;
|
||||||
|
import org.eclipse.jetty.util.BufferUtil;
|
||||||
|
import org.eclipse.jetty.util.Callback;
|
||||||
|
import org.eclipse.jetty.util.Promise;
|
||||||
|
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class HttpClientCustomProxyTest
|
||||||
|
{
|
||||||
|
public static final byte[] CAFE_BABE = new byte[]{(byte)0xCA, (byte)0xFE, (byte)0xBA, (byte)0xBE};
|
||||||
|
|
||||||
|
private Server server;
|
||||||
|
private ServerConnector connector;
|
||||||
|
private SPDYClient.Factory factory;
|
||||||
|
private HttpClient httpClient;
|
||||||
|
|
||||||
|
public void prepare(Handler handler) throws Exception
|
||||||
|
{
|
||||||
|
server = new Server();
|
||||||
|
connector = new ServerConnector(server, new CAFEBABEServerConnectionFactory(new HTTPSPDYServerConnectionFactory(SPDY.V3, new HttpConfiguration(), new PushStrategy.None())));
|
||||||
|
server.addConnector(connector);
|
||||||
|
server.setHandler(handler);
|
||||||
|
server.start();
|
||||||
|
|
||||||
|
QueuedThreadPool executor = new QueuedThreadPool();
|
||||||
|
executor.setName(executor.getName() + "-client");
|
||||||
|
|
||||||
|
factory = new SPDYClient.Factory(executor);
|
||||||
|
factory.start();
|
||||||
|
|
||||||
|
httpClient = new HttpClient(new HttpClientTransportOverSPDY(factory.newSPDYClient(SPDY.V3)), null);
|
||||||
|
httpClient.setExecutor(executor);
|
||||||
|
httpClient.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void dispose() throws Exception
|
||||||
|
{
|
||||||
|
if (httpClient != null)
|
||||||
|
httpClient.stop();
|
||||||
|
if (factory != null)
|
||||||
|
factory.stop();
|
||||||
|
if (server != null)
|
||||||
|
server.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCustomProxy() throws Exception
|
||||||
|
{
|
||||||
|
final String serverHost = "server";
|
||||||
|
final int status = HttpStatus.NO_CONTENT_204;
|
||||||
|
prepare(new AbstractHandler()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||||
|
{
|
||||||
|
baseRequest.setHandled(true);
|
||||||
|
if (!URI.create(baseRequest.getUri().toString()).isAbsolute())
|
||||||
|
response.setStatus(HttpServletResponse.SC_USE_PROXY);
|
||||||
|
else if (serverHost.equals(request.getServerName()))
|
||||||
|
response.setStatus(status);
|
||||||
|
else
|
||||||
|
response.setStatus(HttpServletResponse.SC_NOT_ACCEPTABLE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Setup the custom proxy
|
||||||
|
int proxyPort = connector.getLocalPort();
|
||||||
|
int serverPort = proxyPort + 1; // Any port will do for these tests - just not the same as the proxy
|
||||||
|
httpClient.getProxyConfiguration().getProxies().add(new CAFEBABEProxy(new Origin.Address("localhost", proxyPort), false));
|
||||||
|
|
||||||
|
ContentResponse response = httpClient.newRequest(serverHost, serverPort)
|
||||||
|
.timeout(5, TimeUnit.SECONDS)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
Assert.assertEquals(status, response.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CAFEBABEProxy extends ProxyConfiguration.Proxy
|
||||||
|
{
|
||||||
|
private CAFEBABEProxy(Origin.Address address, boolean secure)
|
||||||
|
{
|
||||||
|
super(address, secure);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClientConnectionFactory newClientConnectionFactory(ClientConnectionFactory connectionFactory)
|
||||||
|
{
|
||||||
|
return new CAFEBABEClientConnectionFactory(connectionFactory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class CAFEBABEClientConnectionFactory implements ClientConnectionFactory
|
||||||
|
{
|
||||||
|
private final ClientConnectionFactory connectionFactory;
|
||||||
|
|
||||||
|
private CAFEBABEClientConnectionFactory(ClientConnectionFactory connectionFactory)
|
||||||
|
{
|
||||||
|
this.connectionFactory = connectionFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
|
||||||
|
{
|
||||||
|
HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
|
||||||
|
Executor executor = destination.getHttpClient().getExecutor();
|
||||||
|
return new CAFEBABEConnection(endPoint, executor, connectionFactory, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class CAFEBABEConnection extends AbstractConnection
|
||||||
|
{
|
||||||
|
private final ClientConnectionFactory connectionFactory;
|
||||||
|
private final Map<String, Object> context;
|
||||||
|
|
||||||
|
public CAFEBABEConnection(EndPoint endPoint, Executor executor, ClientConnectionFactory connectionFactory, Map<String, Object> context)
|
||||||
|
{
|
||||||
|
super(endPoint, executor);
|
||||||
|
this.connectionFactory = connectionFactory;
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onOpen()
|
||||||
|
{
|
||||||
|
super.onOpen();
|
||||||
|
fillInterested();
|
||||||
|
getEndPoint().write(new Callback.Adapter(), ByteBuffer.wrap(CAFE_BABE));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFillable()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ByteBuffer buffer = BufferUtil.allocate(4);
|
||||||
|
int filled = getEndPoint().fill(buffer);
|
||||||
|
Assert.assertEquals(4, filled);
|
||||||
|
Assert.assertArrayEquals(CAFE_BABE, buffer.array());
|
||||||
|
|
||||||
|
// We are good, upgrade the connection
|
||||||
|
ClientConnectionFactory.Helper.replaceConnection(this, connectionFactory.newConnection(getEndPoint(), context));
|
||||||
|
}
|
||||||
|
catch (Throwable x)
|
||||||
|
{
|
||||||
|
close();
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Promise<Connection> promise = (Promise<Connection>)context.get(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
|
||||||
|
promise.failed(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CAFEBABEServerConnectionFactory extends AbstractConnectionFactory
|
||||||
|
{
|
||||||
|
private final org.eclipse.jetty.server.ConnectionFactory connectionFactory;
|
||||||
|
|
||||||
|
private CAFEBABEServerConnectionFactory(org.eclipse.jetty.server.ConnectionFactory connectionFactory)
|
||||||
|
{
|
||||||
|
super("cafebabe");
|
||||||
|
this.connectionFactory = connectionFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public org.eclipse.jetty.io.Connection newConnection(Connector connector, EndPoint endPoint)
|
||||||
|
{
|
||||||
|
return new CAFEBABEServerConnection(connector, endPoint, connectionFactory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CAFEBABEServerConnection extends AbstractConnection
|
||||||
|
{
|
||||||
|
private final org.eclipse.jetty.server.ConnectionFactory connectionFactory;
|
||||||
|
|
||||||
|
public CAFEBABEServerConnection(Connector connector, EndPoint endPoint, org.eclipse.jetty.server.ConnectionFactory connectionFactory)
|
||||||
|
{
|
||||||
|
super(endPoint, connector.getExecutor());
|
||||||
|
this.connectionFactory = connectionFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onOpen()
|
||||||
|
{
|
||||||
|
super.onOpen();
|
||||||
|
fillInterested();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFillable()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ByteBuffer buffer = BufferUtil.allocate(4);
|
||||||
|
int filled = getEndPoint().fill(buffer);
|
||||||
|
Assert.assertEquals(4, filled);
|
||||||
|
Assert.assertArrayEquals(CAFE_BABE, buffer.array());
|
||||||
|
getEndPoint().write(new Callback.Adapter(), buffer);
|
||||||
|
|
||||||
|
// We are good, upgrade the connection
|
||||||
|
ClientConnectionFactory.Helper.replaceConnection(this, connectionFactory.newConnection(connector, getEndPoint()));
|
||||||
|
}
|
||||||
|
catch (Throwable x)
|
||||||
|
{
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue