Merged branch 'jetty-9.1' into 'http_client_connection_factories'.
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractHttpClientTransport.java b/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractHttpClientTransport.java
index 46cf76a..fac4bcf 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractHttpClientTransport.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractHttpClientTransport.java
@@ -19,24 +19,20 @@
 package org.eclipse.jetty.client;
 
 import java.io.IOException;
-import java.net.ConnectException;
 import java.net.SocketAddress;
-import java.net.SocketException;
 import java.nio.channels.SelectionKey;
 import java.nio.channels.SocketChannel;
-import javax.net.ssl.SSLEngine;
+import java.util.Map;
 
-import org.eclipse.jetty.http.HttpScheme;
-import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.client.api.Connection;
 import org.eclipse.jetty.io.EndPoint;
 import org.eclipse.jetty.io.SelectChannelEndPoint;
 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.component.ContainerLifeCycle;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.util.ssl.SslContextFactory;
 
 public abstract class AbstractHttpClientTransport extends ContainerLifeCycle implements HttpClientTransport
 {
@@ -72,12 +68,20 @@
     }
 
     @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;
         try
         {
             channel = SocketChannel.open();
+            HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
             HttpClient client = destination.getHttpClient();
             SocketAddress bindAddress = client.getBindAddress();
             if (bindAddress != null)
@@ -86,8 +90,9 @@
             channel.configureBlocking(false);
             channel.connect(address);
 
-            ConnectionCallback callback = new ConnectionCallback(destination, promise);
-            selectorManager.connect(channel, callback);
+            context.put(SslClientConnectionFactory.SSL_PEER_HOST_CONTEXT_KEY, destination.getHost());
+            context.put(SslClientConnectionFactory.SSL_PEER_PORT_CONTEXT_KEY, destination.getPort());
+            selectorManager.connect(channel, context);
         }
         // Must catch all exceptions, since some like
         // UnresolvedAddressException are not IOExceptions.
@@ -104,12 +109,14 @@
             }
             finally
             {
+                @SuppressWarnings("unchecked")
+                Promise<Connection> promise = (Promise<Connection>)context.get(HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
                 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());
     }
@@ -119,40 +126,6 @@
         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
     {
         private final HttpClient client;
@@ -170,63 +143,21 @@
         }
 
         @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;
-            HttpDestination destination = callback.destination;
-
-            SslContextFactory sslContextFactory = client.getSslContextFactory();
-            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;
-            }
+            @SuppressWarnings("unchecked")
+            Map<String, Object> context = (Map<String, Object>)attachment;
+            HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
+            return destination.getClientConnectionFactory().newConnection(endPoint, context);
         }
 
         @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);
         }
     }
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java b/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java
index 7adcb80..b19c52c 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java
@@ -109,16 +109,19 @@
                 return;
             }
 
-            URI uri = getAuthenticationURI(request);
             Authentication authentication = 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());
-                if (authentication != null)
+                for (Authentication.HeaderInfo element : headerInfos)
                 {
-                    headerInfo = element;
-                    break;
+                    authentication = client.getAuthenticationStore().findAuthentication(element.getType(), uri, element.getRealm());
+                    if (authentication != null)
+                    {
+                        headerInfo = element;
+                        break;
+                    }
                 }
             }
             if (authentication == null)
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpConnectionPool.java b/jetty-client/src/main/java/org/eclipse/jetty/client/ConnectionPool.java
similarity index 95%
rename from jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpConnectionPool.java
rename to jetty-client/src/main/java/org/eclipse/jetty/client/ConnectionPool.java
index a9d9841..9ed8d93 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpConnectionPool.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/ConnectionPool.java
@@ -16,7 +16,7 @@
 //  ========================================================================
 //
 
-package org.eclipse.jetty.client.http;
+package org.eclipse.jetty.client;
 
 import java.io.IOException;
 import java.util.concurrent.BlockingDeque;
@@ -33,9 +33,9 @@
 import org.eclipse.jetty.util.log.Log;
 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 Destination destination;
@@ -44,7 +44,7 @@
     private final BlockingDeque<Connection> idleConnections;
     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.maxConnections = maxConnections;
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java
index ef1fc0f..cfabe91 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java
@@ -27,10 +27,12 @@
 import java.nio.channels.SocketChannel;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
@@ -44,7 +46,6 @@
 import org.eclipse.jetty.client.api.Connection;
 import org.eclipse.jetty.client.api.ContentResponse;
 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.Response;
 import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
@@ -57,7 +58,6 @@
 import org.eclipse.jetty.util.Jetty;
 import org.eclipse.jetty.util.Promise;
 import org.eclipse.jetty.util.SocketAddressResolver;
-import org.eclipse.jetty.util.URIUtil;
 import org.eclipse.jetty.util.component.ContainerLifeCycle;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
@@ -104,12 +104,13 @@
 {
     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 List<ProtocolHandler> handlers = new ArrayList<>();
     private final List<Request.Listener> requestListeners = new ArrayList<>();
     private final AuthenticationStore authenticationStore = new HttpAuthenticationStore();
     private final Set<ContentDecoder.Factory> decoderFactories = new ContentDecoderFactorySet();
+    private final ProxyConfiguration proxyConfig = new ProxyConfiguration();
     private final HttpClientTransport transport;
     private final SslContextFactory sslContextFactory;
     private volatile CookieManager cookieManager;
@@ -132,7 +133,6 @@
     private volatile boolean tcpNoDelay = true;
     private volatile boolean dispatchIO = true;
     private volatile boolean strictEventOrdering = false;
-    private volatile ProxyConfiguration proxyConfig;
     private volatile HttpField encodingField;
 
     /**
@@ -359,7 +359,7 @@
      */
     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 @@
         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.
      * Applications may use {@link Destination}s to create {@link Connection}s
@@ -446,20 +439,20 @@
     {
         port = normalizePort(scheme, port);
 
-        String address = address(scheme, host, port);
-        HttpDestination destination = destinations.get(address);
+        Origin origin = new Origin(scheme, host, port);
+        HttpDestination destination = destinations.get(origin);
         if (destination == null)
         {
-            destination = transport.newHttpDestination(scheme, host, port);
+            destination = transport.newHttpDestination(origin);
             if (isRunning())
             {
-                HttpDestination existing = destinations.putIfAbsent(address, destination);
+                HttpDestination existing = destinations.putIfAbsent(origin, destination);
                 if (existing != null)
                     destination = existing;
                 else
                     LOG.debug("Created {}", destination);
                 if (!isRunning())
-                    destinations.remove(address);
+                    destinations.remove(origin);
             }
 
         }
@@ -486,13 +479,16 @@
 
     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>()
         {
             @Override
             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
@@ -881,14 +877,6 @@
         return proxyConfig;
     }
 
-    /**
-     * @param proxyConfig the forward proxy configuration
-     */
-    public void setProxyConfiguration(ProxyConfiguration proxyConfig)
-    {
-        this.proxyConfig = proxyConfig;
-    }
-
     protected HttpField getAcceptEncodingField()
     {
         return encodingField;
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClientTransport.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClientTransport.java
index de943a6..f6a21ef 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClientTransport.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClientTransport.java
@@ -19,9 +19,9 @@
 package org.eclipse.jetty.client;
 
 import java.net.SocketAddress;
+import java.util.Map;
 
-import org.eclipse.jetty.client.api.Connection;
-import org.eclipse.jetty.util.Promise;
+import org.eclipse.jetty.io.ClientConnectionFactory;
 
 /**
  * {@link HttpClientTransport} represents what transport implementations should provide
@@ -34,8 +34,11 @@
  * but the HTTP exchange may also be carried using the SPDY protocol or the FCGI protocol or, in future,
  * 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.
      * <p />
@@ -53,27 +56,16 @@
      * {@link HttpDestination} controls the destination-connection cardinality: protocols like
      * HTTP have 1-N cardinality, while multiplexed protocols like SPDY have a 1-1 cardinality.
      *
-     * @param scheme the destination scheme
-     * @param host the destination host
-     * @param port the destination port
+     * @param origin the destination origin
      * @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}.
      *
-     * @param destination the destination
      * @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);
-
-    /**
-     * Establishes an encrypted tunnel over the given {@code connection}
-     *
-     * @param connection the connection to tunnel
-     * @return the tunnelled connection
-     */
-    public Connection tunnel(Connection connection);
+    public void connect(SocketAddress address, Map<String, Object> context);
 }
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java
index e09079b..c9bbacf 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java
@@ -83,6 +83,7 @@
         HttpVersion version = request.getVersion();
         HttpFields headers = request.getHeaders();
         ContentProvider content = request.getContent();
+        ProxyConfiguration.Proxy proxy = destination.getProxy();
 
         // Make sure the path is there
         String path = request.getPath();
@@ -91,7 +92,7 @@
             path = "/";
             request.path(path);
         }
-        if (destination.isProxied() && !HttpMethod.CONNECT.is(method))
+        if (proxy != null && !HttpMethod.CONNECT.is(method))
         {
             path = request.getURI().toString();
             request.path(path);
@@ -136,9 +137,12 @@
             request.header(HttpHeader.COOKIE.asString(), cookieString.toString());
 
         // Authorization
-        URI authenticationURI = destination.isProxied() ? destination.getProxyURI() : request.getURI();
-        Authentication.Result authnResult = getHttpClient().getAuthenticationStore().findAuthenticationResult(authenticationURI);
-        if (authnResult != null)
-            authnResult.apply(request);
+        URI authenticationURI = proxy != null ? proxy.getURI() : request.getURI();
+        if (authenticationURI != null)
+        {
+            Authentication.Result authnResult = getHttpClient().getAuthenticationStore().findAuthenticationResult(authenticationURI);
+            if (authnResult != null)
+                authnResult.apply(request);
+        }
     }
 }
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java
index cf4c2df..534f248 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java
@@ -20,51 +20,45 @@
 
 import java.io.Closeable;
 import java.io.IOException;
-import java.net.URI;
 import java.nio.channels.AsynchronousCloseException;
 import java.util.List;
 import java.util.Queue;
 import java.util.concurrent.RejectedExecutionException;
-import java.util.concurrent.TimeUnit;
 
 import org.eclipse.jetty.client.api.Connection;
 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.Response;
 import org.eclipse.jetty.client.api.Result;
 import org.eclipse.jetty.http.HttpField;
 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.ssl.SslClientConnectionFactory;
 import org.eclipse.jetty.util.BlockingArrayQueue;
 import org.eclipse.jetty.util.Promise;
 import org.eclipse.jetty.util.component.ContainerLifeCycle;
 import org.eclipse.jetty.util.component.Dumpable;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.util.ssl.SslContextFactory;
 
 public abstract class HttpDestination implements Destination, Closeable, Dumpable
 {
     protected static final Logger LOG = Log.getLogger(HttpDestination.class);
 
     private final HttpClient client;
-    private final String scheme;
-    private final String host;
-    private final Address address;
+    private final Origin origin;
     private final Queue<HttpExchange> exchanges;
     private final RequestNotifier requestNotifier;
     private final ResponseNotifier responseNotifier;
-    private final Address proxyAddress;
+    private final ProxyConfiguration.Proxy proxy;
+    private final ClientConnectionFactory connectionFactory;
     private final HttpField hostField;
 
-    public HttpDestination(HttpClient client, String scheme, String host, int port)
+    public HttpDestination(HttpClient client, Origin origin)
     {
         this.client = client;
-        this.scheme = scheme;
-        this.host = host;
-        this.address = new Address(host, port);
+        this.origin = origin;
 
         this.exchanges = new BlockingArrayQueue<>(client.getMaxRequestsQueuedPerDestination());
 
@@ -72,19 +66,40 @@
         this.responseNotifier = new ResponseNotifier(client);
 
         ProxyConfiguration proxyConfig = client.getProxyConfiguration();
-        proxyAddress = proxyConfig != null && proxyConfig.matches(host, port) ?
-                new Address(proxyConfig.getHost(), proxyConfig.getPort()) : null;
+        proxy = proxyConfig.match(origin);
+        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))
-            host += ":" + port;
+        String host = getHost();
+        if (!client.isDefaultPort(getScheme(), getPort()))
+            host += ":" + getPort();
         hostField = new HttpField(HttpHeader.HOST, host);
     }
 
+    protected ClientConnectionFactory newSslClientConnectionFactory(ClientConnectionFactory connectionFactory)
+    {
+        return new SslClientConnectionFactory(client.getSslContextFactory(), client.getByteBufferPool(), client.getExecutor(), connectionFactory);
+    }
+
     public HttpClient getHttpClient()
     {
         return client;
     }
 
+    public Origin getOrigin()
+    {
+        return origin;
+    }
+
     public Queue<HttpExchange> getHttpExchanges()
     {
         return exchanges;
@@ -100,10 +115,20 @@
         return responseNotifier;
     }
 
+    public ProxyConfiguration.Proxy getProxy()
+    {
+        return proxy;
+    }
+
+    public ClientConnectionFactory getClientConnectionFactory()
+    {
+        return connectionFactory;
+    }
+
     @Override
     public String getScheme()
     {
-        return scheme;
+        return origin.getScheme();
     }
 
     @Override
@@ -111,32 +136,18 @@
     {
         // InetSocketAddress.getHostString() transforms the host string
         // in case of IPv6 addresses, so we return the original host string
-        return host;
+        return origin.getAddress().getHost();
     }
 
     @Override
     public int getPort()
     {
-        return address.getPort();
+        return origin.getAddress().getPort();
     }
 
-    public Address getConnectAddress()
+    public Origin.Address getConnectAddress()
     {
-        return isProxied() ? proxyAddress : address;
-    }
-
-    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);
+        return proxy == null ? origin.getAddress() : proxy.getAddress();
     }
 
     public HttpField getHostField()
@@ -146,7 +157,7 @@
 
     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);
         if (!getHost().equals(request.getHost()))
             throw new IllegalArgumentException("Invalid request host " + request.getHost() + " for destination " + this);
@@ -188,7 +199,7 @@
 
     public void newConnection(Promise<Connection> promise)
     {
-        createConnection(new ProxyPromise(promise));
+        createConnection(promise);
     }
 
     protected void createConnection(Promise<Connection> promise)
@@ -236,18 +247,6 @@
         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
     public String dump()
     {
@@ -262,7 +261,7 @@
 
     public String asString()
     {
-        return client.address(getScheme(), getHost(), getPort());
+        return origin.asString();
     }
 
     @Override
@@ -271,84 +270,6 @@
         return String.format("%s(%s)%s",
                 HttpDestination.class.getSimpleName(),
                 asString(),
-                proxyAddress == null ? "" : " via " + proxyAddress.getHost() + ":" + proxyAddress.getPort());
-    }
-
-    /**
-     * 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));
-                        }
-                    }
-                }
-            });
-        }
+                proxy == null ? "" : " via " + proxy);
     }
 }
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpProxy.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpProxy.java
new file mode 100644
index 0000000..23cc2d2
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpProxy.java
@@ -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);
+            }
+        }
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java
index ee86fc2..d8d9f55 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java
@@ -589,7 +589,7 @@
             path += "?" + query;
         URI result = URI.create(path);
         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;
     }
 
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/MultiplexHttpDestination.java b/jetty-client/src/main/java/org/eclipse/jetty/client/MultiplexHttpDestination.java
index aa2106e..02687d9 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/MultiplexHttpDestination.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/MultiplexHttpDestination.java
@@ -29,9 +29,9 @@
     private final AtomicReference<ConnectState> connect = new AtomicReference<>(ConnectState.DISCONNECTED);
     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
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/Origin.java b/jetty-client/src/main/java/org/eclipse/jetty/client/Origin.java
new file mode 100644
index 0000000..93c4a33
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/Origin.java
@@ -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();
+        }
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/ProxyAuthenticationProtocolHandler.java b/jetty-client/src/main/java/org/eclipse/jetty/client/ProxyAuthenticationProtocolHandler.java
index 841c661..d7e96f3 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/ProxyAuthenticationProtocolHandler.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/ProxyAuthenticationProtocolHandler.java
@@ -59,6 +59,7 @@
     protected URI getAuthenticationURI(Request request)
     {
         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();
     }
 }
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/ProxyConfiguration.java b/jetty-client/src/main/java/org/eclipse/jetty/client/ProxyConfiguration.java
new file mode 100644
index 0000000..153d33a
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/ProxyConfiguration.java
@@ -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();
+        }
+    }
+
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/Socks4Proxy.java b/jetty-client/src/main/java/org/eclipse/jetty/client/Socks4Proxy.java
new file mode 100644
index 0000000..e06d3f2
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/Socks4Proxy.java
@@ -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);
+            }
+        }
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Destination.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Destination.java
index 03cece3..736a14b 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Destination.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Destination.java
@@ -63,43 +63,4 @@
      * @param promise the promise of a new, unpooled, {@link Connection}
      */
     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;
-        }
-    }
 }
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/ProxyConfiguration.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/ProxyConfiguration.java
deleted file mode 100644
index 4b84557..0000000
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/api/ProxyConfiguration.java
+++ /dev/null
@@ -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;
-    }
-}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpClientTransportOverHTTP.java b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpClientTransportOverHTTP.java
index 2f752ff..d2383b0 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpClientTransportOverHTTP.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpClientTransportOverHTTP.java
@@ -18,16 +18,21 @@
 
 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.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.util.Promise;
 
 public class HttpClientTransportOverHTTP extends AbstractHttpClientTransport
 {
     public HttpClientTransportOverHTTP()
     {
-        this(Runtime.getRuntime().availableProcessors());
+        this(Math.max(1, Runtime.getRuntime().availableProcessors() / 2));
     }
 
     public HttpClientTransportOverHTTP(int selectors)
@@ -36,21 +41,20 @@
     }
 
     @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
-    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
-    public org.eclipse.jetty.client.api.Connection tunnel(org.eclipse.jetty.client.api.Connection connection)
-    {
-        HttpConnectionOverHTTP httpConnection = (HttpConnectionOverHTTP)connection;
-        return tunnel(httpConnection.getEndPoint(), httpConnection.getHttpDestination(), connection);
+        HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
+        HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, destination);
+        @SuppressWarnings("unchecked")
+        Promise<Connection> promise = (Promise<Connection>)context.get(HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
+        promise.succeeded(connection);
+        return connection;
     }
 }
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpDestinationOverHTTP.java b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpDestinationOverHTTP.java
index e7f4f19..8419199 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpDestinationOverHTTP.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpDestinationOverHTTP.java
@@ -21,30 +21,32 @@
 import java.io.IOException;
 import java.util.Arrays;
 
+import org.eclipse.jetty.client.ConnectionPool;
 import org.eclipse.jetty.client.HttpClient;
 import org.eclipse.jetty.client.HttpDestination;
 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.Request;
 import org.eclipse.jetty.util.Promise;
 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);
-        this.connectionPool = newHttpConnectionPool(client);
+        super(client, origin);
+        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;
     }
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientCustomProxyTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientCustomProxyTest.java
new file mode 100644
index 0000000..b60de3a
--- /dev/null
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientCustomProxyTest.java
@@ -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();
+            }
+        }
+    }
+}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientExplicitConnectionTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientExplicitConnectionTest.java
index 6b7acc6..7260ae6 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientExplicitConnectionTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientExplicitConnectionTest.java
@@ -25,7 +25,6 @@
 import org.eclipse.jetty.client.api.Destination;
 import org.eclipse.jetty.client.api.Request;
 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.util.FutureResponseListener;
 import org.eclipse.jetty.toolchain.test.annotation.Slow;
@@ -60,7 +59,7 @@
             Assert.assertEquals(200, response.getStatus());
 
             HttpDestinationOverHTTP httpDestination = (HttpDestinationOverHTTP)destination;
-            HttpConnectionPool connectionPool = httpDestination.getHttpConnectionPool();
+            ConnectionPool connectionPool = httpDestination.getConnectionPool();
             Assert.assertTrue(connectionPool.getActiveConnections().isEmpty());
             Assert.assertTrue(connectionPool.getIdleConnections().isEmpty());
         }
@@ -92,7 +91,7 @@
         Assert.assertFalse(httpConnection.getEndPoint().isOpen());
 
         HttpDestinationOverHTTP httpDestination = (HttpDestinationOverHTTP)destination;
-        HttpConnectionPool connectionPool = httpDestination.getHttpConnectionPool();
+        ConnectionPool connectionPool = httpDestination.getConnectionPool();
         Assert.assertTrue(connectionPool.getActiveConnections().isEmpty());
         Assert.assertTrue(connectionPool.getIdleConnections().isEmpty());
     }
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientLoadTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientLoadTest.java
index 1ac4ae4..41213c2 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientLoadTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientLoadTest.java
@@ -37,7 +37,6 @@
 import org.eclipse.jetty.client.api.Response;
 import org.eclipse.jetty.client.api.Result;
 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.util.BytesContentProvider;
 import org.eclipse.jetty.http.HttpHeader;
@@ -85,7 +84,7 @@
         }
 
         // Re-run after warmup
-        iterations = 50_000;
+        iterations = 5_000;
         for (int i = 0; i < runs; ++i)
         {
             run(random, iterations);
@@ -111,7 +110,7 @@
                 for (String host : Arrays.asList("localhost", "127.0.0.1"))
                 {
                     HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, connector.getLocalPort());
-                    HttpConnectionPool connectionPool = destination.getHttpConnectionPool();
+                    ConnectionPool connectionPool = destination.getConnectionPool();
                     for (Connection connection : new ArrayList<>(connectionPool.getActiveConnections()))
                     {
                         HttpConnectionOverHTTP active = (HttpConnectionOverHTTP)connection;
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientProxyTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientProxyTest.java
index 9f7ff33..78d9784 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientProxyTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientProxyTest.java
@@ -27,7 +27,6 @@
 import javax.servlet.http.HttpServletResponse;
 
 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.util.BasicAuthentication;
 import org.eclipse.jetty.http.HttpHeader;
@@ -68,7 +67,7 @@
 
         int proxyPort = connector.getLocalPort();
         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)
                 .scheme(scheme)
@@ -115,7 +114,7 @@
         String proxyHost = "localhost";
         int proxyPort = connector.getLocalPort();
         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)
                 .scheme(scheme)
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java
index 974dad8..d261af5 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java
@@ -51,7 +51,6 @@
 import org.eclipse.jetty.client.api.Response;
 import org.eclipse.jetty.client.api.Result;
 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.util.BytesContentProvider;
 import org.eclipse.jetty.http.HttpField;
@@ -85,7 +84,7 @@
         Assert.assertEquals(200, response.getStatus());
 
         HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
-        HttpConnectionPool connectionPool = destination.getHttpConnectionPool();
+        ConnectionPool connectionPool = destination.getConnectionPool();
 
         long start = System.nanoTime();
         HttpConnectionOverHTTP connection = null;
@@ -637,7 +636,7 @@
                     public void onBegin(Request request)
                     {
                         HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
-                        destination.getHttpConnectionPool().getActiveConnections().peek().close();
+                        destination.getConnectionPool().getActiveConnections().peek().close();
                     }
                 })
                 .send(new Response.Listener.Adapter()
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTimeoutTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTimeoutTest.java
index ccb4842..2b5a822 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTimeoutTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTimeoutTest.java
@@ -21,6 +21,7 @@
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -35,9 +36,13 @@
 import org.eclipse.jetty.client.api.Response;
 import org.eclipse.jetty.client.api.Result;
 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.InputStreamContentProvider;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.ClientConnectionFactory;
 import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
 import org.eclipse.jetty.io.ssl.SslConnection;
 import org.eclipse.jetty.server.handler.AbstractHandler;
 import org.eclipse.jetty.toolchain.test.annotation.Slow;
@@ -244,15 +249,30 @@
         client = new HttpClient(new HttpClientTransportOverHTTP()
         {
             @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
-                    protected boolean onReadTimeout()
+                    protected ClientConnectionFactory newSslClientConnectionFactory(ClientConnectionFactory connectionFactory)
                     {
-                        sslIdle.set(true);
-                        return super.onReadTimeout();
+                        HttpClient client = getHttpClient();
+                        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();
+                                    }
+                                };
+                            }
+                        };
                     }
                 };
             }
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java
index 9fcf5d0..f22bc5e 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java
@@ -33,7 +33,6 @@
 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.client.http.HttpConnectionPool;
 import org.eclipse.jetty.client.http.HttpDestinationOverHTTP;
 import org.eclipse.jetty.client.util.ByteBufferContentProvider;
 import org.eclipse.jetty.http.HttpHeader;
@@ -68,7 +67,7 @@
         String host = "localhost";
         int port = connector.getLocalPort();
         HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
-        HttpConnectionPool connectionPool = destination.getHttpConnectionPool();
+        ConnectionPool connectionPool = destination.getConnectionPool();
 
         final BlockingQueue<Connection> idleConnections = connectionPool.getIdleConnections();
         Assert.assertEquals(0, idleConnections.size());
@@ -129,7 +128,7 @@
         String host = "localhost";
         int port = connector.getLocalPort();
         HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
-        HttpConnectionPool connectionPool = destination.getHttpConnectionPool();
+        ConnectionPool connectionPool = destination.getConnectionPool();
 
         final BlockingQueue<Connection> idleConnections = connectionPool.getIdleConnections();
         Assert.assertEquals(0, idleConnections.size());
@@ -180,7 +179,7 @@
         String host = "localhost";
         int port = connector.getLocalPort();
         HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
-        HttpConnectionPool connectionPool = destination.getHttpConnectionPool();
+        ConnectionPool connectionPool = destination.getConnectionPool();
 
         final BlockingQueue<Connection> idleConnections = connectionPool.getIdleConnections();
         Assert.assertEquals(0, idleConnections.size());
@@ -240,7 +239,7 @@
         String host = "localhost";
         int port = connector.getLocalPort();
         HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
-        HttpConnectionPool connectionPool = destination.getHttpConnectionPool();
+        ConnectionPool connectionPool = destination.getConnectionPool();
 
         final BlockingQueue<Connection> idleConnections = connectionPool.getIdleConnections();
         Assert.assertEquals(0, idleConnections.size());
@@ -313,7 +312,7 @@
         String host = "localhost";
         int port = connector.getLocalPort();
         HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
-        HttpConnectionPool connectionPool = destination.getHttpConnectionPool();
+        ConnectionPool connectionPool = destination.getConnectionPool();
 
         final BlockingQueue<Connection> idleConnections = connectionPool.getIdleConnections();
         Assert.assertEquals(0, idleConnections.size());
@@ -366,7 +365,7 @@
         String host = "localhost";
         int port = connector.getLocalPort();
         HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
-        HttpConnectionPool connectionPool = destination.getHttpConnectionPool();
+        ConnectionPool connectionPool = destination.getConnectionPool();
 
         final BlockingQueue<Connection> idleConnections = connectionPool.getIdleConnections();
         Assert.assertEquals(0, idleConnections.size());
@@ -416,7 +415,7 @@
             String host = "localhost";
             int port = connector.getLocalPort();
             HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
-            HttpConnectionPool connectionPool = destination.getHttpConnectionPool();
+            ConnectionPool connectionPool = destination.getConnectionPool();
 
             final BlockingQueue<Connection> idleConnections = connectionPool.getIdleConnections();
             Assert.assertEquals(0, idleConnections.size());
@@ -466,7 +465,7 @@
         String host = "localhost";
         int port = connector.getLocalPort();
         HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
-        HttpConnectionPool connectionPool = destination.getHttpConnectionPool();
+        ConnectionPool connectionPool = destination.getConnectionPool();
 
         final BlockingQueue<Connection> idleConnections = connectionPool.getIdleConnections();
         Assert.assertEquals(0, idleConnections.size());
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpDestinationOverHTTPTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpDestinationOverHTTPTest.java
index 611335f..f7ed4c9 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpDestinationOverHTTPTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpDestinationOverHTTPTest.java
@@ -24,6 +24,7 @@
 
 import org.eclipse.jetty.client.AbstractHttpClientServerTest;
 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.ContentResponse;
 import org.eclipse.jetty.client.api.Request;
@@ -52,12 +53,12 @@
     @Test
     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();
         if (connection == null)
         {
             // 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);
     }
@@ -65,7 +66,7 @@
     @Test
     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();
         if (connection1 == null)
         {
@@ -74,7 +75,7 @@
             while (connection1 == null && TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - start) < 5)
             {
                 TimeUnit.MILLISECONDS.sleep(50);
-                connection1 = destination.getHttpConnectionPool().getIdleConnections().peek();
+                connection1 = destination.getConnectionPool().getIdleConnections().peek();
             }
             Assert.assertNotNull(connection1);
 
@@ -87,7 +88,7 @@
     public void test_SecondAcquire_ConcurrentWithFirstAcquire_WithEmptyQueue_CreatesTwoConnections() throws Exception
     {
         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
             protected void process(HttpConnectionOverHTTP connection, boolean dispatch)
@@ -115,23 +116,23 @@
         latch.countDown();
 
         // 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);
-        connection = destination.getHttpConnectionPool().getIdleConnections().poll(5, TimeUnit.SECONDS);
+        connection = destination.getConnectionPool().getIdleConnections().poll(5, TimeUnit.SECONDS);
         Assert.assertNotNull(connection);
     }
 
     @Test
     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();
 
         long start = System.nanoTime();
         while (connection1 == null && TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - start) < 5)
         {
             TimeUnit.MILLISECONDS.sleep(50);
-            connection1 = (HttpConnectionOverHTTP)destination.getHttpConnectionPool().getIdleConnections().peek();
+            connection1 = (HttpConnectionOverHTTP)destination.getConnectionPool().getIdleConnections().peek();
         }
         Assert.assertNotNull(connection1);
 
@@ -152,7 +153,7 @@
         long idleTimeout = 1000;
         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();
         if (connection1 == null)
         {
@@ -161,13 +162,13 @@
             while (connection1 == null && TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - start) < 5)
             {
                 TimeUnit.MILLISECONDS.sleep(50);
-                connection1 = destination.getHttpConnectionPool().getIdleConnections().peek();
+                connection1 = destination.getConnectionPool().getIdleConnections().peek();
             }
             Assert.assertNotNull(connection1);
 
             TimeUnit.MILLISECONDS.sleep(2 * idleTimeout);
 
-            connection1 = destination.getHttpConnectionPool().getIdleConnections().poll();
+            connection1 = destination.getConnectionPool().getIdleConnections().poll();
             Assert.assertNull(connection1);
         }
     }
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnectionFactory.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnectionFactory.java
new file mode 100644
index 0000000..075e261
--- /dev/null
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnectionFactory.java
@@ -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);
+            }
+        }
+    }
+}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslClientConnectionFactory.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslClientConnectionFactory.java
new file mode 100644
index 0000000..1289c59
--- /dev/null
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslClientConnectionFactory.java
@@ -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);
+    }
+}
diff --git a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java
index 2d356ed..45c871e 100644
--- a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java
+++ b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java
@@ -48,8 +48,9 @@
 
 import org.eclipse.jetty.client.HttpClient;
 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.ProxyConfiguration;
 import org.eclipse.jetty.client.api.Request;
 import org.eclipse.jetty.client.api.Response;
 import org.eclipse.jetty.client.api.Result;
@@ -110,7 +111,7 @@
     private HttpClient prepareClient() throws Exception
     {
         HttpClient result = new HttpClient();
-        result.setProxyConfiguration(new ProxyConfiguration("localhost", proxyConnector.getLocalPort()));
+        result.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyConnector.getLocalPort()));
         result.start();
         return result;
     }
@@ -236,7 +237,7 @@
         prepareProxy(new ProxyServlet());
 
         HttpClient result = new HttpClient();
-        result.setProxyConfiguration(new ProxyConfiguration("localhost", proxyConnector.getLocalPort()));
+        result.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyConnector.getLocalPort()));
         QueuedThreadPool threadPool = new QueuedThreadPool();
         threadPool.setName("foo");
         threadPool.setMaxThreads(20);
@@ -631,7 +632,7 @@
             }
         });
         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
         ContentResponse response = client.newRequest("localhost", port)
@@ -865,7 +866,7 @@
             }
         });
 
-        ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
+        client.newRequest("localhost", serverConnector.getLocalPort())
                 .timeout(5, TimeUnit.SECONDS)
                 .send();
 
diff --git a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyTunnellingTest.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyTunnellingTest.java
index 01fa377..d7b55b4 100644
--- a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyTunnellingTest.java
+++ b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyTunnellingTest.java
@@ -26,17 +26,16 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
-
 import javax.servlet.ServletException;
 import javax.servlet.ServletOutputStream;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 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.ContentResponse;
 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.StringContentProvider;
 import org.eclipse.jetty.http.HttpHeader;
@@ -143,7 +142,7 @@
         startProxy();
 
         HttpClient httpClient = new HttpClient(sslContextFactory);
-        httpClient.setProxyConfiguration(new ProxyConfiguration("localhost", proxyPort()));
+        httpClient.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyPort()));
         httpClient.start();
 
         try
@@ -172,7 +171,7 @@
         startProxy();
 
         HttpClient httpClient = new HttpClient(sslContextFactory);
-        httpClient.setProxyConfiguration(new ProxyConfiguration("localhost", proxyPort()));
+        httpClient.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyPort()));
         httpClient.start();
 
         try
@@ -215,7 +214,7 @@
         startProxy();
 
         final HttpClient httpClient = new HttpClient(sslContextFactory);
-        httpClient.setProxyConfiguration(new ProxyConfiguration("localhost", proxyPort()));
+        httpClient.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyPort()));
         httpClient.start();
 
         try
@@ -285,7 +284,7 @@
         stopProxy();
 
         HttpClient httpClient = new HttpClient(sslContextFactory);
-        httpClient.setProxyConfiguration(new ProxyConfiguration("localhost", proxyPort));
+        httpClient.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyPort));
         httpClient.start();
 
         try
@@ -317,7 +316,7 @@
         startProxy();
 
         HttpClient httpClient = new HttpClient(sslContextFactory);
-        httpClient.setProxyConfiguration(new ProxyConfiguration("localhost", proxyPort()));
+        httpClient.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyPort()));
         httpClient.start();
 
         try
@@ -354,7 +353,7 @@
         });
 
         HttpClient httpClient = new HttpClient(sslContextFactory);
-        httpClient.setProxyConfiguration(new ProxyConfiguration("localhost", proxyPort()));
+        httpClient.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyPort()));
         httpClient.start();
 
         try
@@ -394,7 +393,7 @@
         sslContextFactory.start();
 
         HttpClient httpClient = new HttpClient(sslContextFactory);
-        httpClient.setProxyConfiguration(new ProxyConfiguration(proxyHost, proxyPort));
+        httpClient.getProxyConfiguration().getProxies().add(new HttpProxy(proxyHost, proxyPort));
         httpClient.start();
 
         try
diff --git a/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/NextProtoNegoClientConnection.java b/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/NPNClientConnection.java
similarity index 73%
rename from jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/NextProtoNegoClientConnection.java
rename to jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/NPNClientConnection.java
index cd745f3..66c1aee 100644
--- a/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/NextProtoNegoClientConnection.java
+++ b/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/NPNClientConnection.java
@@ -19,38 +19,36 @@
 package org.eclipse.jetty.spdy.client;
 
 import java.io.IOException;
-import java.nio.channels.SocketChannel;
 import java.util.List;
-import java.util.concurrent.Executor;
-
+import java.util.Map;
 import javax.net.ssl.SSLEngine;
 
 import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.ClientConnectionFactory;
 import org.eclipse.jetty.io.Connection;
 import org.eclipse.jetty.io.EndPoint;
 import org.eclipse.jetty.io.RuntimeIOException;
-import org.eclipse.jetty.io.ssl.SslConnection.DecryptedEndPoint;
 import org.eclipse.jetty.npn.NextProtoNego;
 import org.eclipse.jetty.util.BufferUtil;
 import org.eclipse.jetty.util.log.Log;
 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 SocketChannel channel;
-    private final Object attachment;
     private final SPDYClient client;
+    private final ClientConnectionFactory connectionFactory;
     private final SSLEngine engine;
+    private final Map<String, Object> context;
     private volatile boolean completed;
 
-    public NextProtoNegoClientConnection(SocketChannel channel, DecryptedEndPoint endPoint, Object attachment, Executor executor, SPDYClient client)
+    public NPNClientConnection(EndPoint endPoint, SPDYClient client, ClientConnectionFactory connectionFactory, SSLEngine sslEngine, Map<String, Object> context)
     {
-        super(endPoint, executor);
-        this.channel = channel;
-        this.attachment = attachment;
+        super(endPoint, client.getFactory().getExecutor());
         this.client = client;
-        this.engine = endPoint.getSslConnection().getSSLEngine();
+        this.connectionFactory = connectionFactory;
+        this.engine = sslEngine;
+        this.context = context;
         NextProtoNego.put(engine, this);
     }
 
@@ -97,7 +95,7 @@
         {
             LOG.debug(x);
             NextProtoNego.remove(engine);
-            getEndPoint().close();
+            close();
             return -1;
         }
     }
@@ -120,16 +118,22 @@
     {
         NextProtoNego.remove(engine);
         completed = true;
-        String protocol = client.selectProtocol(protocols);
-        return protocol == null ? null : protocol;
+        return client.selectProtocol(protocols);
     }
 
     private void replaceConnection()
     {
         EndPoint endPoint = getEndPoint();
-        Connection connection = client.getConnectionFactory().newConnection(channel, endPoint, attachment);
-        endPoint.getConnection().onClose();
-        endPoint.setConnection(connection);
-        connection.onOpen();
+        try
+        {
+            Connection oldConnection = endPoint.getConnection();
+            Connection newConnection = connectionFactory.newConnection(endPoint, context);
+            ClientConnectionFactory.Helper.replaceConnection(oldConnection, newConnection);
+        }
+        catch (Throwable x)
+        {
+            LOG.debug(x);
+            close();
+        }
     }
 }
diff --git a/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/NPNClientConnectionFactory.java b/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/NPNClientConnectionFactory.java
new file mode 100644
index 0000000..7e2d472
--- /dev/null
+++ b/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/NPNClientConnectionFactory.java
@@ -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);
+    }
+}
diff --git a/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/SPDYClient.java b/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/SPDYClient.java
index 80d2286..974b32a 100644
--- a/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/SPDYClient.java
+++ b/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/SPDYClient.java
@@ -19,26 +19,28 @@
 package org.eclipse.jetty.spdy.client;
 
 import java.io.IOException;
+import java.net.InetSocketAddress;
 import java.net.SocketAddress;
 import java.nio.channels.SelectionKey;
 import java.nio.channels.SocketChannel;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Queue;
 import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.ExecutionException;
 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.Connection;
 import org.eclipse.jetty.io.EndPoint;
 import org.eclipse.jetty.io.MappedByteBufferPool;
 import org.eclipse.jetty.io.SelectChannelEndPoint;
 import org.eclipse.jetty.io.SelectorManager;
-import org.eclipse.jetty.io.ssl.SslConnection;
-import org.eclipse.jetty.io.ssl.SslConnection.DecryptedEndPoint;
+import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
 import org.eclipse.jetty.spdy.FlowControlStrategy;
 import org.eclipse.jetty.spdy.api.GoAwayInfo;
 import org.eclipse.jetty.spdy.api.Session;
@@ -55,7 +57,7 @@
 /**
  * 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.
- * <p />
+ * <p/>
  * {@link SPDYClient} instances are created through a {@link Factory}:
  * <pre>
  * SPDYClient.Factory factory = new SPDYClient.Factory();
@@ -70,19 +72,113 @@
  */
 public class SPDYClient
 {
-    private final SPDYClientConnectionFactory connectionFactory = new SPDYClientConnectionFactory();
-    final short version;
-    final Factory factory;
+    private final short version;
+    private final Factory factory;
     private volatile SocketAddress bindAddress;
     private volatile long idleTimeout = -1;
     private volatile int initialWindowSize;
-    private volatile boolean executeOnFillable;
+    private volatile boolean dispatchIO;
+    private volatile ClientConnectionFactory connectionFactory;
 
     protected SPDYClient(short version, Factory factory)
     {
         this.version = version;
         this.factory = factory;
         setInitialWindowSize(65536);
+        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()
+    {
+        return version;
+    }
+
+    public Factory getFactory()
+    {
+        return factory;
+    }
+
+    /**
+     * Equivalent to:
+     * <pre>
+     * Future&lt;Session&gt; promise = new FuturePromise&lt;&gt;();
+     * 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);
     }
 
     /**
@@ -103,59 +199,6 @@
         this.bindAddress = bindAddress;
     }
 
-    /**
-     * Equivalent to:
-     * <pre>
-     * Future&lt;Session&gt; promise = new FuturePromise&lt;&gt;();
-     * 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);
-
-            SessionPromise result = new SessionPromise(promise, channel, this, listener);
-
-            channel.connect(address);
-            factory.selector.connect(channel, result);
-        }
-        catch (IOException x)
-        {
-            promise.failed(x);
-        }
-    }
-
     public long getIdleTimeout()
     {
         return idleTimeout;
@@ -176,14 +219,24 @@
         this.initialWindowSize = initialWindowSize;
     }
 
-    public boolean isExecuteOnFillable()
+    public boolean isDispatchIO()
     {
-        return executeOnFillable;
+        return dispatchIO;
     }
 
-    public void setExecuteOnFillable(boolean executeOnFillable)
+    public void setDispatchIO(boolean dispatchIO)
     {
-        this.executeOnFillable = executeOnFillable;
+        this.dispatchIO = dispatchIO;
+    }
+
+    public ClientConnectionFactory getClientConnectionFactory()
+    {
+        return connectionFactory;
+    }
+
+    public void setClientConnectionFactory(ClientConnectionFactory connectionFactory)
+    {
+        this.connectionFactory = connectionFactory;
     }
 
     protected String selectProtocol(List<String> serverProtocols)
@@ -197,20 +250,6 @@
         return null;
     }
 
-    public SPDYClientConnectionFactory getConnectionFactory()
-    {
-        return 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()
     {
         return FlowControlStrategyFactory.newFlowControlStrategy(version);
@@ -225,7 +264,7 @@
         private final SslContextFactory sslContextFactory;
         private final SelectorManager selector;
         private final long idleTimeout;
-        private long connectTimeout = 15000;
+        private long connectTimeout;
 
         public Factory()
         {
@@ -255,6 +294,7 @@
         public Factory(Executor executor, Scheduler scheduler, SslContextFactory sslContextFactory, long idleTimeout)
         {
             this.idleTimeout = idleTimeout;
+            setConnectTimeout(15000);
 
             if (executor == null)
                 executor = new QueuedThreadPool();
@@ -290,6 +330,11 @@
             return executor;
         }
 
+        public SslContextFactory getSslContextFactory()
+        {
+            return sslContextFactory;
+        }
+
         public long getConnectTimeout()
         {
             return connectTimeout;
@@ -347,89 +392,33 @@
             @Override
             protected EndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key) throws IOException
             {
-                SessionPromise attachment = (SessionPromise)key.attachment();
-
-                long clientIdleTimeout = attachment.client.getIdleTimeout();
+                @SuppressWarnings("unchecked")
+                Map<String, Object> context = (Map<String, Object>)key.attachment();
+                SPDYClient client = (SPDYClient)context.get(SPDYClientConnectionFactory.SPDY_CLIENT_CONTEXT_KEY);
+                long clientIdleTimeout = client.getIdleTimeout();
                 if (clientIdleTimeout < 0)
                     clientIdleTimeout = idleTimeout;
-
                 return new SelectChannelEndPoint(channel, selectSet, key, getScheduler(), clientIdleTimeout);
             }
 
             @Override
-            public Connection newConnection(final SocketChannel channel, EndPoint endPoint, final Object attachment)
+            public Connection newConnection(SocketChannel channel, EndPoint endPoint, Object attachment) throws IOException
             {
-                SessionPromise sessionPromise = (SessionPromise)attachment;
-                final SPDYClient client = sessionPromise.client;
-
+                @SuppressWarnings("unchecked")
+                Map<String, Object> context = (Map<String, Object>)attachment;
                 try
                 {
-                    if (sslContextFactory != null)
-                    {
-                        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(channel, sslEndPoint, attachment, getExecutor(), client);
-                        sslEndPoint.setConnection(connection);
-                        return sslConnection;
-                    }
-
-                    SPDYClientConnectionFactory connectionFactory = new SPDYClientConnectionFactory();
-                    return connectionFactory.newConnection(channel, endPoint, attachment);
+                    SPDYClient client = (SPDYClient)context.get(SPDYClientConnectionFactory.SPDY_CLIENT_CONTEXT_KEY);
+                    return client.getClientConnectionFactory().newConnection(endPoint, context);
                 }
-                catch (RuntimeException x)
+                catch (Throwable x)
                 {
-                    sessionPromise.failed(x);
+                    @SuppressWarnings("unchecked")
+                    Promise<Session> promise = (Promise<Session>)context.get(SPDYClientConnectionFactory.SPDY_SESSION_PROMISE_CONTEXT_KEY);
+                    promise.failed(x);
                     throw x;
                 }
             }
         }
     }
-
-    static class SessionPromise extends FuturePromise<Session>
-    {
-        private final SocketChannel channel;
-        private final Promise<Session> wrappedPromise;
-        final SPDYClient client;
-        final SessionFrameListener listener;
-
-        private SessionPromise(Promise<Session> promise, SocketChannel channel, SPDYClient client,
-                               SessionFrameListener listener)
-        {
-            this.channel = channel;
-            this.client = client;
-            this.listener = listener;
-            this.wrappedPromise = promise;
-        }
-
-        @Override
-        public void succeeded(Session result)
-        {
-            wrappedPromise.succeeded(result);
-            super.succeeded(result);
-        }
-
-        @Override
-        public void failed(Throwable cause)
-        {
-            wrappedPromise.failed(cause);
-            super.failed(cause);
-        }
-
-        @Override
-        public boolean cancel(boolean mayInterruptIfRunning)
-        {
-            try
-            {
-                super.cancel(mayInterruptIfRunning);
-                channel.close();
-                return true;
-            }
-            catch (IOException x)
-            {
-                return true;
-            }
-        }
-    }
 }
diff --git a/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/SPDYClientConnectionFactory.java b/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/SPDYClientConnectionFactory.java
index 18244e8..dae5268 100644
--- a/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/SPDYClientConnectionFactory.java
+++ b/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/SPDYClientConnectionFactory.java
@@ -18,46 +18,54 @@
 
 package org.eclipse.jetty.spdy.client;
 
-import java.nio.channels.SocketChannel;
+import java.io.IOException;
+import java.util.Map;
 
 import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.ClientConnectionFactory;
 import org.eclipse.jetty.io.Connection;
 import org.eclipse.jetty.io.EndPoint;
 import org.eclipse.jetty.spdy.CompressionFactory;
 import org.eclipse.jetty.spdy.FlowControlStrategy;
 import org.eclipse.jetty.spdy.StandardCompressionFactory;
 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.SessionPromise;
 import org.eclipse.jetty.spdy.generator.Generator;
 import org.eclipse.jetty.spdy.parser.Parser;
+import org.eclipse.jetty.util.Promise;
 
-public class SPDYClientConnectionFactory
+public class SPDYClientConnectionFactory implements ClientConnectionFactory
 {
-    public Connection newConnection(SocketChannel channel, EndPoint endPoint, Object attachment)
-    {
-        SessionPromise sessionPromise = (SessionPromise)attachment;
-        SPDYClient client = sessionPromise.client;
-        Factory factory = client.factory;
-        ByteBufferPool bufferPool = factory.getByteBufferPool();
+    public static final String SPDY_CLIENT_CONTEXT_KEY = "spdy.client";
+    public static final String SPDY_SESSION_LISTENER_CONTEXT_KEY = "spdy.session.listener";
+    public static final String SPDY_SESSION_PROMISE_CONTEXT_KEY = "spdy.session.promise";
 
+    @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();
         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.isExecuteOnFillable());
+        SPDYConnection connection = new ClientSPDYConnection(endPoint, byteBufferPool, parser, factory, client.isDispatchIO());
 
         FlowControlStrategy flowControlStrategy = client.newFlowControlStrategy();
 
-        StandardSession session = new StandardSession(client.version, bufferPool, factory.getExecutor(),
-                factory.getScheduler(), connection, endPoint, connection, 1, sessionPromise.listener, generator,
-                flowControlStrategy);
+        SessionFrameListener listener = (SessionFrameListener)context.get(SPDY_SESSION_LISTENER_CONTEXT_KEY);
+        StandardSession session = new StandardSession(client.getVersion(), byteBufferPool, factory.getExecutor(),
+                factory.getScheduler(), connection, endPoint, connection, 1, listener, generator, flowControlStrategy);
         session.setWindowSize(client.getInitialWindowSize());
         parser.addListener(session);
-        sessionPromise.succeeded(session);
         connection.setSession(session);
 
-        factory.sessionOpened(session);
+        @SuppressWarnings("unchecked")
+        Promise<Session> promise = (Promise<Session>)context.get(SPDY_SESSION_PROMISE_CONTEXT_KEY);
+        promise.succeeded(session);
 
         return connection;
     }
@@ -66,14 +74,20 @@
     {
         private final Factory factory;
 
-        public ClientSPDYConnection(EndPoint endPoint, ByteBufferPool bufferPool, Parser parser, Factory factory,
-                                    boolean executeOnFillable)
+        public ClientSPDYConnection(EndPoint endPoint, ByteBufferPool bufferPool, Parser parser, Factory factory, boolean dispatchIO)
         {
-            super(endPoint, bufferPool, parser, factory.getExecutor(), executeOnFillable);
+            super(endPoint, bufferPool, parser, factory.getExecutor(), dispatchIO);
             this.factory = factory;
         }
 
         @Override
+        public void onOpen()
+        {
+            super.onOpen();
+            factory.sessionOpened(getSession());
+        }
+
+        @Override
         public void onClose()
         {
             super.onClose();
diff --git a/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/SPDYConnection.java b/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/SPDYConnection.java
index e1217a5..b66edba 100644
--- a/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/SPDYConnection.java
+++ b/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/SPDYConnection.java
@@ -44,26 +44,20 @@
     private volatile ISession session;
     private volatile boolean idle = false;
 
-    public SPDYConnection(EndPoint endPoint, ByteBufferPool bufferPool, Parser parser, Executor executor,
-                          boolean executeOnFillable)
+    public SPDYConnection(EndPoint endPoint, ByteBufferPool bufferPool, Parser parser, Executor executor, boolean dispatchIO)
     {
-        this(endPoint, bufferPool, parser, executor, executeOnFillable, 8192);
+        this(endPoint, bufferPool, parser, executor, dispatchIO, 8192);
     }
 
-    public SPDYConnection(EndPoint endPoint, ByteBufferPool bufferPool, Parser parser, Executor executor,
-                          boolean executeOnFillable, int bufferSize)
+    public SPDYConnection(EndPoint endPoint, ByteBufferPool bufferPool, Parser parser, Executor executor, boolean dispatchIO, int bufferSize)
     {
         // Since SPDY is multiplexed, onFillable() must never block while calling application code. In fact,
         // the SPDY code always dispatches to a new thread when calling application code,
-        // so here we can safely pass false as last parameter, and avoid to dispatch to onFillable(). The IO
-        // operation (read, parse, etc.) will not block and will be fast in almost all cases. Big uploads to a server
-        // however might block the Selector thread for a long time and therefore block other connections to be read.
-        // This might be a good reason to set executeOnFillable to true.
-        //
-        // Due to a jvm bug we've had a Selector thread being stuck at
-        // sun.nio.ch.FileDispatcherImpl.preClose0(Native Method). That's why we now default executeOnFillable to
-        // true even if for most use cases it is faster to not dispatch the IO events.
-        super(endPoint, executor, executeOnFillable);
+        // so here we can safely pass false as last parameter, and avoid to dispatch to onFillable().
+        // The IO operation (read, parse, etc.) will not block and will be fast in almost all cases.
+        // Big uploads to a server, however, might occupy the Selector thread for a long time and
+        // therefore starve other connections, so by default dispatchIO is true.
+        super(endPoint, executor, dispatchIO);
         this.bufferPool = bufferPool;
         this.parser = parser;
         onIdle(true);
diff --git a/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpClientTransportOverSPDY.java b/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpClientTransportOverSPDY.java
index b2fb3a6..813f550 100644
--- a/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpClientTransportOverSPDY.java
+++ b/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpClientTransportOverSPDY.java
@@ -18,12 +18,17 @@
 
 package org.eclipse.jetty.spdy.client.http;
 
+import java.io.IOException;
 import java.net.SocketAddress;
+import java.util.Map;
 
 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.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.SessionFrameListener;
 import org.eclipse.jetty.spdy.client.SPDYClient;
@@ -32,11 +37,22 @@
 public class HttpClientTransportOverSPDY implements HttpClientTransport
 {
     private final SPDYClient client;
-    private volatile HttpClient httpClient;
+    private final ClientConnectionFactory connectionFactory;
+    private HttpClient httpClient;
 
     public HttpClientTransportOverSPDY(SPDYClient 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
@@ -46,51 +62,46 @@
     }
 
     @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
-    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()
         {
             @Override
             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);
             }
         };
 
         client.connect(address, listener, new Promise<Session>()
-                {
-                    @Override
-                    public void succeeded(Session session)
-                    {
-                        Connection result = new HttpConnectionOverSPDY(destination, session);
-                        promise.succeeded(result);
-                    }
+        {
+            @Override
+            public void succeeded(Session session)
+            {
+                promise.succeeded(new HttpConnectionOverSPDY(destination, session));
+            }
 
-                    @Override
-                    public void failed(Throwable x)
-                    {
-                        promise.failed(x);
-                    }
-                }
-        );
+            @Override
+            public void failed(Throwable x)
+            {
+                promise.failed(x);
+            }
+        }, context);
     }
 
     @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);
     }
 }
diff --git a/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpDestinationOverSPDY.java b/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpDestinationOverSPDY.java
index 321fb99..10ff922 100644
--- a/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpDestinationOverSPDY.java
+++ b/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpDestinationOverSPDY.java
@@ -21,12 +21,13 @@
 import org.eclipse.jetty.client.HttpClient;
 import org.eclipse.jetty.client.HttpExchange;
 import org.eclipse.jetty.client.MultiplexHttpDestination;
+import org.eclipse.jetty.client.Origin;
 
 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
@@ -34,4 +35,12 @@
     {
         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);
+    }
 }
diff --git a/jetty-spdy/spdy-http-client-transport/src/test/java/org/eclipse/jetty/spdy/client/http/HttpClientCustomProxyTest.java b/jetty-spdy/spdy-http-client-transport/src/test/java/org/eclipse/jetty/spdy/client/http/HttpClientCustomProxyTest.java
new file mode 100644
index 0000000..7b06d39
--- /dev/null
+++ b/jetty-spdy/spdy-http-client-transport/src/test/java/org/eclipse/jetty/spdy/client/http/HttpClientCustomProxyTest.java
@@ -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();
+            }
+        }
+    }
+}
diff --git a/jetty-spdy/spdy-server/src/main/java/org/eclipse/jetty/spdy/server/SPDYServerConnectionFactory.java b/jetty-spdy/spdy-server/src/main/java/org/eclipse/jetty/spdy/server/SPDYServerConnectionFactory.java
index 5e6815b..147c03c 100644
--- a/jetty-spdy/spdy-server/src/main/java/org/eclipse/jetty/spdy/server/SPDYServerConnectionFactory.java
+++ b/jetty-spdy/spdy-server/src/main/java/org/eclipse/jetty/spdy/server/SPDYServerConnectionFactory.java
@@ -42,14 +42,10 @@
 import org.eclipse.jetty.util.Callback;
 import org.eclipse.jetty.util.annotation.ManagedAttribute;
 import org.eclipse.jetty.util.annotation.ManagedObject;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
 
 @ManagedObject("SPDY Server Connection Factory")
 public class SPDYServerConnectionFactory extends AbstractConnectionFactory
 {
-    private static final Logger LOG = Log.getLogger(SPDYServerConnectionFactory.class);
-
     // This method is placed here so as to provide a check for NPN before attempting to load any
     // NPN classes.
     public static void checkNPNAvailable()
@@ -66,11 +62,11 @@
         }
     }
 
+    private final Queue<Session> sessions = new ConcurrentLinkedQueue<>();
     private final short version;
     private final ServerSessionFrameListener listener;
     private int initialWindowSize;
-    private boolean executeOnFillable = true;
-    private final Queue<Session> sessions = new ConcurrentLinkedQueue<>();
+    private boolean dispatchIO;
 
     public SPDYServerConnectionFactory(int version)
     {
@@ -83,6 +79,7 @@
         this.version = (short)version;
         this.listener = listener;
         setInitialWindowSize(65536);
+        setDispatchIO(true);
     }
 
     @ManagedAttribute("SPDY version")
@@ -105,14 +102,14 @@
 
         ServerSessionFrameListener listener = provideServerSessionFrameListener(connector, endPoint);
         SPDYConnection connection = new ServerSPDYConnection(connector, endPoint, parser, listener,
-                executeOnFillable, getInputBufferSize());
+                isDispatchIO(), getInputBufferSize());
 
         FlowControlStrategy flowControlStrategy = newFlowControlStrategy(version);
 
         StandardSession session = new StandardSession(getVersion(), connector.getByteBufferPool(),
                 connector.getExecutor(), connector.getScheduler(), connection, endPoint, connection, 2, listener,
                 generator, flowControlStrategy);
-        session.setWindowSize(initialWindowSize);
+        session.setWindowSize(getInitialWindowSize());
         parser.addListener(session);
         connection.setSession(session);
 
@@ -142,15 +139,15 @@
         this.initialWindowSize = initialWindowSize;
     }
 
-    @ManagedAttribute("Execute onFillable")
-    public boolean isExecuteOnFillable()
+    @ManagedAttribute("Dispatch I/O to a pooled thread")
+    public boolean isDispatchIO()
     {
-        return executeOnFillable;
+        return dispatchIO;
     }
 
-    public void setExecuteOnFillable(boolean executeOnFillable)
+    public void setDispatchIO(boolean dispatchIO)
     {
-        this.executeOnFillable = executeOnFillable;
+        this.dispatchIO = dispatchIO;
     }
 
     protected boolean sessionOpened(Session session)
@@ -191,10 +188,10 @@
         private final AtomicBoolean connected = new AtomicBoolean();
 
         private ServerSPDYConnection(Connector connector, EndPoint endPoint, Parser parser,
-                                     ServerSessionFrameListener listener, boolean executeOnFillable, int bufferSize)
+                                     ServerSessionFrameListener listener, boolean dispatchIO, int bufferSize)
         {
             super(endPoint, connector.getByteBufferPool(), parser, connector.getExecutor(),
-                    executeOnFillable, bufferSize);
+                    dispatchIO, bufferSize);
             this.listener = listener;
         }