From 6c4ee083be2d4ffb528a4a6259dfcf63bd74ac51 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Thu, 17 Jan 2019 10:43:03 +0100 Subject: [PATCH 01/22] Issue #132 - ClientConnector abstraction. Introduced ClientConnector and refactored HttpClient transports, removing duplicated code that was connect() to a remote host. Refactored also HTTP2Client to reference ClientConnector. Refactored tests accordingly to the changes introduced in the implementations. Signed-off-by: Simone Bordet --- .../alpn/client/ALPNClientConnection.java | 8 +- .../client/ALPNClientConnectionFactory.java | 3 +- jetty-alpn/jetty-alpn-java-client/pom.xml | 1 + .../alpn/java/client/JDK9HTTP2Client.java | 5 +- .../jetty/alpn/java/server/JDK9ALPNTest.java | 21 +- .../AbstractConnectorHttpClientTransport.java | 156 ++------ .../client/AbstractHttpClientTransport.java | 13 + .../org/eclipse/jetty/client/HttpClient.java | 91 ++--- .../jetty/client/HttpClientTransport.java | 4 +- .../java/org/eclipse/jetty/client/Origin.java | 42 ++- .../jetty/client/ResponseNotifier.java | 27 +- .../http/HttpClientTransportOverHTTP.java | 11 +- .../client/http/HttpConnectionOverHTTP.java | 1 - .../client/HttpClientCustomProxyTest.java | 7 +- .../jetty/client/HttpClientTLSTest.java | 20 +- .../eclipse/jetty/client/HttpClientTest.java | 28 +- .../InsufficientThreadsDetectionTest.java | 12 +- .../http/HttpDestinationOverHTTPTest.java | 18 +- .../http/HttpClientTransportOverFCGI.java | 9 +- .../jetty/http2/client/HTTP2Client.java | 245 +++---------- .../client/HTTP2ClientConnectionFactory.java | 15 +- .../eclipse/jetty/http2/client/Client.java | 94 ----- .../http/HttpClientTransportOverHTTP2.java | 19 +- .../client/http/MaxConcurrentStreamsTest.java | 6 +- .../jetty/io/ClientConnectionFactory.java | 8 +- .../org/eclipse/jetty/io/ClientConnector.java | 333 ++++++++++++++++++ .../jetty/io/NegotiatingClientConnection.java | 6 +- .../io/ssl/SslClientConnectionFactory.java | 17 +- .../eclipse/jetty/proxy/ProxyServletTest.java | 18 +- .../HttpClientTransportOverUnixSockets.java | 154 +++++--- .../jetty/unixsocket/UnixSocketTest.java | 97 +++-- .../http/client/ConnectionStatisticsTest.java | 21 +- .../http/client/HttpClientContinueTest.java | 10 +- .../client/HttpClientIdleTimeoutTest.java | 18 +- .../jetty/http/client/HttpClientTest.java | 25 +- .../test/resources/jetty-logging.properties | 2 +- 36 files changed, 795 insertions(+), 770 deletions(-) delete mode 100644 jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/Client.java create mode 100644 jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java diff --git a/jetty-alpn/jetty-alpn-client/src/main/java/org/eclipse/jetty/alpn/client/ALPNClientConnection.java b/jetty-alpn/jetty-alpn-client/src/main/java/org/eclipse/jetty/alpn/client/ALPNClientConnection.java index 9779f0ec49a..4b6c25573b8 100644 --- a/jetty-alpn/jetty-alpn-client/src/main/java/org/eclipse/jetty/alpn/client/ALPNClientConnection.java +++ b/jetty-alpn/jetty-alpn-client/src/main/java/org/eclipse/jetty/alpn/client/ALPNClientConnection.java @@ -27,13 +27,9 @@ import javax.net.ssl.SSLEngine; import org.eclipse.jetty.io.ClientConnectionFactory; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.NegotiatingClientConnection; -import org.eclipse.jetty.util.log.Log; -import org.eclipse.jetty.util.log.Logger; public class ALPNClientConnection extends NegotiatingClientConnection { - private static final Logger LOG = Log.getLogger(ALPNClientConnection.class); - private final List protocols; public ALPNClientConnection(EndPoint endPoint, Executor executor, ClientConnectionFactory connectionFactory, SSLEngine sslEngine, Map context, List protocols) @@ -49,9 +45,9 @@ public class ALPNClientConnection extends NegotiatingClientConnection public void selected(String protocol) { - if (protocol==null || !protocols.contains(protocol)) + if (protocol == null || !protocols.contains(protocol)) close(); else - super.completed(); + completed(); } } diff --git a/jetty-alpn/jetty-alpn-client/src/main/java/org/eclipse/jetty/alpn/client/ALPNClientConnectionFactory.java b/jetty-alpn/jetty-alpn-client/src/main/java/org/eclipse/jetty/alpn/client/ALPNClientConnectionFactory.java index c1a00326620..75d14ff337c 100644 --- a/jetty-alpn/jetty-alpn-client/src/main/java/org/eclipse/jetty/alpn/client/ALPNClientConnectionFactory.java +++ b/jetty-alpn/jetty-alpn-client/src/main/java/org/eclipse/jetty/alpn/client/ALPNClientConnectionFactory.java @@ -18,7 +18,6 @@ package org.eclipse.jetty.alpn.client; -import java.io.IOException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -96,7 +95,7 @@ public class ALPNClientConnectionFactory extends NegotiatingClientConnectionFact } @Override - public Connection newConnection(EndPoint endPoint, Map context) throws IOException + public Connection newConnection(EndPoint endPoint, Map context) { SSLEngine engine = (SSLEngine)context.get(SslClientConnectionFactory.SSL_ENGINE_CONTEXT_KEY); for (Client processor : processors) diff --git a/jetty-alpn/jetty-alpn-java-client/pom.xml b/jetty-alpn/jetty-alpn-java-client/pom.xml index 31660c91677..6054bc0d7a6 100644 --- a/jetty-alpn/jetty-alpn-java-client/pom.xml +++ b/jetty-alpn/jetty-alpn-java-client/pom.xml @@ -42,6 +42,7 @@ jetty-alpn-client ${project.version} + org.eclipse.jetty.http2 http2-client diff --git a/jetty-alpn/jetty-alpn-java-client/src/test/java/org/eclipse/jetty/alpn/java/client/JDK9HTTP2Client.java b/jetty-alpn/jetty-alpn-java-client/src/test/java/org/eclipse/jetty/alpn/java/client/JDK9HTTP2Client.java index 9dbcf11e479..aa772b826e3 100644 --- a/jetty-alpn/jetty-alpn-java-client/src/test/java/org/eclipse/jetty/alpn/java/client/JDK9HTTP2Client.java +++ b/jetty-alpn/jetty-alpn-java-client/src/test/java/org/eclipse/jetty/alpn/java/client/JDK9HTTP2Client.java @@ -35,22 +35,19 @@ import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.FuturePromise; import org.eclipse.jetty.util.Jetty; import org.eclipse.jetty.util.Promise; -import org.eclipse.jetty.util.ssl.SslContextFactory; public class JDK9HTTP2Client { public static void main(String[] args) throws Exception { HTTP2Client client = new HTTP2Client(); - SslContextFactory sslContextFactory = new SslContextFactory(); - client.addBean(sslContextFactory); client.start(); String host = "webtide.com"; int port = 443; FuturePromise sessionPromise = new FuturePromise<>(); - client.connect(sslContextFactory, new InetSocketAddress(host, port), new Session.Listener.Adapter(), sessionPromise); + client.connect(client.getClientConnector().getSslContextFactory(), new InetSocketAddress(host, port), new Session.Listener.Adapter(), sessionPromise); Session session = sessionPromise.get(5, TimeUnit.SECONDS); HttpFields requestFields = new HttpFields(); diff --git a/jetty-alpn/jetty-alpn-java-server/src/test/java/org/eclipse/jetty/alpn/java/server/JDK9ALPNTest.java b/jetty-alpn/jetty-alpn-java-server/src/test/java/org/eclipse/jetty/alpn/java/server/JDK9ALPNTest.java index 8f74502dfe3..72a5aef503a 100644 --- a/jetty-alpn/jetty-alpn-java-server/src/test/java/org/eclipse/jetty/alpn/java/server/JDK9ALPNTest.java +++ b/jetty-alpn/jetty-alpn-java-server/src/test/java/org/eclipse/jetty/alpn/java/server/JDK9ALPNTest.java @@ -18,11 +18,7 @@ package org.eclipse.jetty.alpn.java.server; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.containsString; - import java.io.BufferedReader; -import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; @@ -31,7 +27,6 @@ import java.nio.charset.StandardCharsets; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLSocket; -import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -45,8 +40,12 @@ import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; + public class JDK9ALPNTest { private Server server; @@ -66,6 +65,13 @@ public class JDK9ALPNTest server.start(); } + @AfterEach + public void stopServer() throws Exception + { + if (server != null) + server.stop(); + } + private SslContextFactory newSslContextFactory() { SslContextFactory sslContextFactory = new SslContextFactory(); @@ -84,7 +90,7 @@ public class JDK9ALPNTest startServer(new AbstractHandler.ErrorDispatchHandler() { @Override - protected void doNonErrorHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + protected void doNonErrorHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) { baseRequest.setHandled(true); } @@ -126,7 +132,7 @@ public class JDK9ALPNTest startServer(new AbstractHandler.ErrorDispatchHandler() { @Override - protected void doNonErrorHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + protected void doNonErrorHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) { baseRequest.setHandled(true); } @@ -163,6 +169,5 @@ public class JDK9ALPNTest break; } } - } } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractConnectorHttpClientTransport.java b/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractConnectorHttpClientTransport.java index 67f57a11486..0c234daa160 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractConnectorHttpClientTransport.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractConnectorHttpClientTransport.java @@ -18,21 +18,12 @@ package org.eclipse.jetty.client; -import java.io.IOException; import java.net.InetSocketAddress; -import java.net.SocketAddress; -import java.net.SocketException; -import java.nio.channels.SelectableChannel; -import java.nio.channels.SelectionKey; -import java.nio.channels.SocketChannel; +import java.time.Duration; import java.util.Map; import org.eclipse.jetty.client.api.Connection; -import org.eclipse.jetty.io.EndPoint; -import org.eclipse.jetty.io.ManagedSelector; -import org.eclipse.jetty.io.SelectorManager; -import org.eclipse.jetty.io.SocketChannelEndPoint; -import org.eclipse.jetty.io.ssl.SslClientConnectionFactory; +import org.eclipse.jetty.io.ClientConnector; import org.eclipse.jetty.util.Promise; import org.eclipse.jetty.util.annotation.ManagedAttribute; import org.eclipse.jetty.util.annotation.ManagedObject; @@ -40,147 +31,48 @@ import org.eclipse.jetty.util.annotation.ManagedObject; @ManagedObject public abstract class AbstractConnectorHttpClientTransport extends AbstractHttpClientTransport { - private final int selectors; - private SelectorManager selectorManager; + private final ClientConnector connector; - protected AbstractConnectorHttpClientTransport(int selectors) + protected AbstractConnectorHttpClientTransport(ClientConnector connector) { - this.selectors = selectors; + this.connector = connector; + addBean(connector); + } + + public ClientConnector getClientConnector() + { + return connector; } @ManagedAttribute(value = "The number of selectors", readonly = true) public int getSelectors() { - return selectors; + return connector.getSelectors(); } @Override protected void doStart() throws Exception { HttpClient httpClient = getHttpClient(); - selectorManager = newSelectorManager(httpClient); - selectorManager.setConnectTimeout(httpClient.getConnectTimeout()); - addBean(selectorManager); + connector.setBindAddress(httpClient.getBindAddress()); + connector.setByteBufferPool(httpClient.getByteBufferPool()); + connector.setConnectBlocking(httpClient.isConnectBlocking()); + connector.setConnectTimeout(Duration.ofMillis(httpClient.getConnectTimeout())); + connector.setExecutor(httpClient.getExecutor()); + connector.setIdleTimeout(Duration.ofMillis(httpClient.getIdleTimeout())); + connector.setScheduler(httpClient.getScheduler()); + connector.setSslContextFactory(httpClient.getSslContextFactory()); super.doStart(); } - @Override - protected void doStop() throws Exception - { - super.doStop(); - removeBean(selectorManager); - } - @Override public void connect(InetSocketAddress address, Map 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) - channel.bind(bindAddress); - configure(client, channel); - - context.put(SslClientConnectionFactory.SSL_PEER_HOST_CONTEXT_KEY, destination.getHost()); - context.put(SslClientConnectionFactory.SSL_PEER_PORT_CONTEXT_KEY, destination.getPort()); - - boolean connected = true; - if (client.isConnectBlocking()) - { - channel.socket().connect(address, (int)client.getConnectTimeout()); - channel.configureBlocking(false); - } - else - { - channel.configureBlocking(false); - connected = channel.connect(address); - } - if (connected) - selectorManager.accept(channel, context); - else - selectorManager.connect(channel, context); - } - // Must catch all exceptions, since some like - // UnresolvedAddressException are not IOExceptions. - catch (Throwable x) - { - // If IPv6 is not deployed, a generic SocketException "Network is unreachable" - // exception is being thrown, so we attempt to provide a better error message. - if (x.getClass() == SocketException.class) - x = new SocketException("Could not connect to " + address).initCause(x); - - try - { - if (channel != null) - channel.close(); - } - catch (IOException xx) - { - LOG.ignore(xx); - } - finally - { - connectFailed(context, x); - } - } - } - - protected void connectFailed(Map context, Throwable x) - { - if (LOG.isDebugEnabled()) - LOG.debug("Could not connect to {}", context.get(HTTP_DESTINATION_CONTEXT_KEY)); + HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY); + context.put(ClientConnector.CLIENT_CONNECTION_FACTORY_CONTEXT_KEY, destination.getClientConnectionFactory()); @SuppressWarnings("unchecked") Promise promise = (Promise)context.get(HTTP_CONNECTION_PROMISE_CONTEXT_KEY); - promise.failed(x); - } - - protected void configure(HttpClient client, SocketChannel channel) throws IOException - { - channel.socket().setTcpNoDelay(client.isTCPNoDelay()); - } - - protected SelectorManager newSelectorManager(HttpClient client) - { - return new ClientSelectorManager(client, getSelectors()); - } - - protected class ClientSelectorManager extends SelectorManager - { - private final HttpClient client; - - protected ClientSelectorManager(HttpClient client, int selectors) - { - super(client.getExecutor(), client.getScheduler(), selectors); - this.client = client; - } - - @Override - protected EndPoint newEndPoint(SelectableChannel channel, ManagedSelector selector, SelectionKey key) - { - SocketChannelEndPoint endp = new SocketChannelEndPoint(channel, selector, key, getScheduler()); - endp.setIdleTimeout(client.getIdleTimeout()); - return endp; - } - - @Override - public org.eclipse.jetty.io.Connection newConnection(SelectableChannel channel, EndPoint endPoint, Object attachment) throws IOException - { - @SuppressWarnings("unchecked") - Map context = (Map)attachment; - HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY); - return destination.getClientConnectionFactory().newConnection(endPoint, context); - } - - @Override - protected void connectionFailed(SelectableChannel channel, Throwable x, Object attachment) - { - @SuppressWarnings("unchecked") - Map context = (Map)attachment; - connectFailed(context, x); - } + context.put(ClientConnector.CONNECTION_PROMISE_CONTEXT_KEY, new Promise.Wrapper<>(promise)); + connector.connect(address, context); } } 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 3a601f7c237..e5282e0fce9 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 @@ -18,6 +18,10 @@ package org.eclipse.jetty.client; +import java.util.Map; + +import org.eclipse.jetty.client.api.Connection; +import org.eclipse.jetty.util.Promise; import org.eclipse.jetty.util.annotation.ManagedObject; import org.eclipse.jetty.util.component.ContainerLifeCycle; import org.eclipse.jetty.util.log.Log; @@ -53,4 +57,13 @@ public abstract class AbstractHttpClientTransport extends ContainerLifeCycle imp { this.factory = factory; } + + protected void connectFailed(Map context, Throwable failure) + { + if (LOG.isDebugEnabled()) + LOG.debug("Could not connect to {}", context.get(HTTP_DESTINATION_CONTEXT_KEY)); + @SuppressWarnings("unchecked") + Promise promise = (Promise)context.get(HTTP_CONNECTION_PROMISE_CONTEXT_KEY); + promise.failed(failure); + } } 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 e6761f41eca..555871270cc 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 @@ -81,16 +81,16 @@ import org.eclipse.jetty.util.thread.Scheduler; import org.eclipse.jetty.util.thread.ThreadPool; /** - *

{@link HttpClient} provides an efficient, asynchronous, non-blocking implementation + *

HttpClient provides an efficient, asynchronous, non-blocking implementation * to perform HTTP requests to a server through a simple API that offers also blocking semantic.

- *

{@link HttpClient} provides easy-to-use methods such as {@link #GET(String)} that allow to perform HTTP + *

HttpClient provides easy-to-use methods such as {@link #GET(String)} that allow to perform HTTP * requests in a one-liner, but also gives the ability to fine tune the configuration of requests via * {@link HttpClient#newRequest(URI)}.

- *

{@link HttpClient} acts as a central configuration point for network parameters (such as idle timeouts) + *

HttpClient acts as a central configuration point for network parameters (such as idle timeouts) * and HTTP parameters (such as whether to follow redirects).

- *

{@link HttpClient} transparently pools connections to servers, but allows direct control of connections + *

HttpClient transparently pools connections to servers, but allows direct control of connections * for cases where this is needed.

- *

{@link HttpClient} also acts as a central configuration point for cookies, via {@link #getCookieStore()}.

+ *

HttpClient also acts as a central configuration point for cookies, via {@link #getCookieStore()}.

*

Typical usage:

*
  * HttpClient httpClient = new HttpClient();
@@ -157,7 +157,7 @@ public class HttpClient extends ContainerLifeCycle
     private String defaultRequestContentType = "application/octet-stream";
 
     /**
-     * Creates a {@link HttpClient} instance that can perform requests to non-TLS destinations only
+     * Creates a HttpClient instance that can perform requests to non-TLS destinations only
      * (that is, requests with the "http" scheme only, and not "https").
      *
      * @see #HttpClient(SslContextFactory) to perform requests to TLS destinations.
@@ -168,7 +168,7 @@ public class HttpClient extends ContainerLifeCycle
     }
 
     /**
-     * Creates a {@link HttpClient} instance that can perform requests to non-TLS and TLS destinations
+     * Creates a HttpClient instance that can perform requests to non-TLS and TLS destinations
      * (that is, both requests with the "http" scheme and with the "https" scheme).
      *
      * @param sslContextFactory the {@link SslContextFactory} that manages TLS encryption
@@ -517,7 +517,7 @@ public class HttpClient extends ContainerLifeCycle
     /**
      * Returns a {@link Destination} for the given scheme, host and port.
      * Applications may use {@link Destination}s to create {@link Connection}s
-     * that will be outside {@link HttpClient}'s pooling mechanism, to explicitly
+     * that will be outside HttpClient's pooling mechanism, to explicitly
      * control the connection lifecycle (in particular their termination with
      * {@link Connection#close()}).
      *
@@ -570,7 +570,7 @@ public class HttpClient extends ContainerLifeCycle
     }
 
     /**
-     * @return the list of destinations known to this {@link HttpClient}.
+     * @return the list of destinations known to this HttpClient.
      */
     public List getDestinations()
     {
@@ -586,13 +586,13 @@ public class HttpClient extends ContainerLifeCycle
     protected void newConnection(final HttpDestination destination, final Promise promise)
     {
         Origin.Address address = destination.getConnectAddress();
-        resolver.resolve(address.getHost(), address.getPort(), new Promise>()
+        resolver.resolve(address.getHost(), address.getPort(), new Promise<>()
         {
             @Override
             public void succeeded(List socketAddresses)
             {
                 Map context = new HashMap<>();
-                context.put(ClientConnectionFactory.CONNECTOR_CONTEXT_KEY, HttpClient.this);
+                context.put(ClientConnectionFactory.CLIENT_CONTEXT_KEY, HttpClient.this);
                 context.put(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY, destination);
                 connect(socketAddresses, 0, context);
             }
@@ -605,7 +605,7 @@ public class HttpClient extends ContainerLifeCycle
 
             private void connect(List socketAddresses, int index, Map context)
             {
-                context.put(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY, new Promise.Wrapper(promise)
+                context.put(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY, new Promise.Wrapper<>(promise)
                 {
                     @Override
                     public void failed(Throwable x)
@@ -638,7 +638,7 @@ public class HttpClient extends ContainerLifeCycle
     }
 
     /**
-     * @return the {@link ByteBufferPool} of this {@link HttpClient}
+     * @return the {@link ByteBufferPool} of this HttpClient
      */
     public ByteBufferPool getByteBufferPool()
     {
@@ -646,7 +646,7 @@ public class HttpClient extends ContainerLifeCycle
     }
 
     /**
-     * @param byteBufferPool the {@link ByteBufferPool} of this {@link HttpClient}
+     * @param byteBufferPool the {@link ByteBufferPool} of this HttpClient
      */
     public void setByteBufferPool(ByteBufferPool byteBufferPool)
     {
@@ -706,7 +706,7 @@ public class HttpClient extends ContainerLifeCycle
 
     /**
      * 

Sets the socket address resolution timeout used by the default {@link SocketAddressResolver} - * created by this {@link HttpClient} at startup.

+ * created by this HttpClient at startup.

*

For more fine tuned configuration of socket address resolution, see * {@link #setSocketAddressResolver(SocketAddressResolver)}.

* @@ -755,7 +755,7 @@ public class HttpClient extends ContainerLifeCycle } /** - * @return the "User-Agent" HTTP field of this {@link HttpClient} + * @return the "User-Agent" HTTP field of this HttpClient */ public HttpField getUserAgentField() { @@ -763,7 +763,7 @@ public class HttpClient extends ContainerLifeCycle } /** - * @param agent the "User-Agent" HTTP header string of this {@link HttpClient} + * @param agent the "User-Agent" HTTP header string of this HttpClient */ public void setUserAgentField(HttpField agent) { @@ -773,7 +773,7 @@ public class HttpClient extends ContainerLifeCycle } /** - * @return whether this {@link HttpClient} follows HTTP redirects + * @return whether this HttpClient follows HTTP redirects * @see Request#isFollowRedirects() */ @ManagedAttribute("Whether HTTP redirects are followed") @@ -783,7 +783,7 @@ public class HttpClient extends ContainerLifeCycle } /** - * @param follow whether this {@link HttpClient} follows HTTP redirects + * @param follow whether this HttpClient follows HTTP redirects * @see #setMaxRedirects(int) */ public void setFollowRedirects(boolean follow) @@ -792,7 +792,7 @@ public class HttpClient extends ContainerLifeCycle } /** - * @return the {@link Executor} of this {@link HttpClient} + * @return the {@link Executor} of this HttpClient */ public Executor getExecutor() { @@ -800,7 +800,7 @@ public class HttpClient extends ContainerLifeCycle } /** - * @param executor the {@link Executor} of this {@link HttpClient} + * @param executor the {@link Executor} of this HttpClient */ public void setExecutor(Executor executor) { @@ -811,7 +811,7 @@ public class HttpClient extends ContainerLifeCycle } /** - * @return the {@link Scheduler} of this {@link HttpClient} + * @return the {@link Scheduler} of this HttpClient */ public Scheduler getScheduler() { @@ -819,7 +819,7 @@ public class HttpClient extends ContainerLifeCycle } /** - * @param scheduler the {@link Scheduler} of this {@link HttpClient} + * @param scheduler the {@link Scheduler} of this HttpClient */ public void setScheduler(Scheduler scheduler) { @@ -830,7 +830,7 @@ public class HttpClient extends ContainerLifeCycle } /** - * @return the {@link SocketAddressResolver} of this {@link HttpClient} + * @return the {@link SocketAddressResolver} of this HttpClient */ public SocketAddressResolver getSocketAddressResolver() { @@ -838,7 +838,7 @@ public class HttpClient extends ContainerLifeCycle } /** - * @param resolver the {@link SocketAddressResolver} of this {@link HttpClient} + * @param resolver the {@link SocketAddressResolver} of this HttpClient */ public void setSocketAddressResolver(SocketAddressResolver resolver) { @@ -849,7 +849,7 @@ public class HttpClient extends ContainerLifeCycle } /** - * @return the max number of connections that this {@link HttpClient} opens to {@link Destination}s + * @return the max number of connections that this HttpClient opens to {@link Destination}s */ @ManagedAttribute("The max number of connections per each destination") public int getMaxConnectionsPerDestination() @@ -862,11 +862,11 @@ public class HttpClient extends ContainerLifeCycle *

* RFC 2616 suggests that 2 connections should be opened per each destination, * but browsers commonly open 6. - * If this {@link HttpClient} is used for load testing, it is common to have only one destination + * If this HttpClient is used for load testing, it is common to have only one destination * (the server to load test), and it is recommended to set this value to a high value (at least as * much as the threads present in the {@link #getExecutor() executor}). * - * @param maxConnectionsPerDestination the max number of connections that this {@link HttpClient} opens to {@link Destination}s + * @param maxConnectionsPerDestination the max number of connections that this HttpClient opens to {@link Destination}s */ public void setMaxConnectionsPerDestination(int maxConnectionsPerDestination) { @@ -885,11 +885,11 @@ public class HttpClient extends ContainerLifeCycle /** * Sets the max number of requests that may be queued to a destination. *

- * If this {@link HttpClient} performs a high rate of requests to a destination, + * If this HttpClient performs a high rate of requests to a destination, * and all the connections managed by that destination are busy with other requests, * then new requests will be queued up in the destination. * This parameter controls how many requests can be queued before starting to reject them. - * If this {@link HttpClient} is used for load testing, it is common to have this parameter + * If this HttpClient is used for load testing, it is common to have this parameter * set to a high value, although this may impact latency (requests sit in the queue for a long * time before being sent). * @@ -970,35 +970,6 @@ public class HttpClient extends ContainerLifeCycle this.tcpNoDelay = tcpNoDelay; } - /** - * @return true to dispatch I/O operations in a different thread, false to execute them in the selector thread - * @see #setDispatchIO(boolean) - */ - @Deprecated - public boolean isDispatchIO() - { - // TODO this did default to true, so usage needs to be evaluated. - return false; - } - - /** - * Whether to dispatch I/O operations from the selector thread to a different thread. - *

- * This implementation never blocks on I/O operation, but invokes application callbacks that may - * take time to execute or block on other I/O. - * If application callbacks are known to take time or block on I/O, then parameter {@code dispatchIO} - * should be set to true. - * If application callbacks are known to be quick and never block on I/O, then parameter {@code dispatchIO} - * may be set to false. - * - * @param dispatchIO true to dispatch I/O operations in a different thread, - * false to execute them in the selector thread - */ - @Deprecated - public void setDispatchIO(boolean dispatchIO) - { - } - /** * Gets the http compliance mode for parsing http responses. * The default http compliance level is {@link HttpCompliance#RFC7230} which is the latest HTTP/1.1 specification @@ -1256,7 +1227,7 @@ public class HttpClient extends ContainerLifeCycle public Iterator iterator() { final Iterator iterator = set.iterator(); - return new Iterator() + return new Iterator<>() { @Override public boolean hasNext() 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 ff5f9269d6d..7d2366a19fd 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 @@ -36,8 +36,8 @@ import org.eclipse.jetty.io.ClientConnectionFactory; */ 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"; + public static final String HTTP_DESTINATION_CONTEXT_KEY = "org.eclipse.jetty.client.destination"; + public static final String HTTP_CONNECTION_PROMISE_CONTEXT_KEY = "org.eclipse.jetty.client.connection.promise"; /** * Sets the {@link HttpClient} instance on this transport. 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 index c94dfb8928c..344b1c16edb 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/Origin.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/Origin.java @@ -48,6 +48,23 @@ public class Origin return address; } + @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() + { + return Objects.hash(scheme, address); + } + public String asString() { StringBuilder result = new StringBuilder(); @@ -56,20 +73,9 @@ public class Origin } @Override - public boolean equals(Object obj) + public String toString() { - 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; + return asString(); } public static class Address @@ -96,8 +102,10 @@ public class Origin @Override public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null || getClass() != obj.getClass()) return false; + 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; } @@ -105,9 +113,7 @@ public class Origin @Override public int hashCode() { - int result = host.hashCode(); - result = 31 * result + port; - return result; + return Objects.hash(host, port); } public String asString() diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/ResponseNotifier.java b/jetty-client/src/main/java/org/eclipse/jetty/client/ResponseNotifier.java index 6d9faa94089..7db9af83851 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/ResponseNotifier.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/ResponseNotifier.java @@ -203,16 +203,7 @@ public class ResponseNotifier public void forwardSuccess(List listeners, Response response) { - notifyBegin(listeners, response); - for (Iterator iterator = response.getHeaders().iterator(); iterator.hasNext();) - { - HttpField field = iterator.next(); - if (!notifyHeader(listeners, response, field)) - iterator.remove(); - } - notifyHeaders(listeners, response); - if (response instanceof ContentResponse) - notifyContent(listeners, response, ByteBuffer.wrap(((ContentResponse)response).getContent()), Callback.NOOP); + forwardEvents(listeners, response); notifySuccess(listeners, response); } @@ -223,9 +214,16 @@ public class ResponseNotifier } public void forwardFailure(List listeners, Response response, Throwable failure) + { + forwardEvents(listeners, response); + notifyFailure(listeners, response, failure); + } + + private void forwardEvents(List listeners, Response response) { notifyBegin(listeners, response); - for (Iterator iterator = response.getHeaders().iterator(); iterator.hasNext();) + Iterator iterator = response.getHeaders().iterator(); + while (iterator.hasNext()) { HttpField field = iterator.next(); if (!notifyHeader(listeners, response, field)) @@ -233,8 +231,11 @@ public class ResponseNotifier } notifyHeaders(listeners, response); if (response instanceof ContentResponse) - notifyContent(listeners, response, ByteBuffer.wrap(((ContentResponse)response).getContent()), Callback.NOOP); - notifyFailure(listeners, response, failure); + { + byte[] content = ((ContentResponse)response).getContent(); + if (content != null && content.length > 0) + notifyContent(listeners, response, ByteBuffer.wrap(content), Callback.NOOP); + } } public void forwardFailureComplete(List listeners, Request request, Throwable requestFailure, Response response, Throwable responseFailure) 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 8c353e98f79..9ef6bd7c49e 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 @@ -26,6 +26,7 @@ import org.eclipse.jetty.client.DuplexConnectionPool; import org.eclipse.jetty.client.HttpDestination; import org.eclipse.jetty.client.Origin; import org.eclipse.jetty.client.api.Connection; +import org.eclipse.jetty.io.ClientConnector; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.util.ProcessorUtils; import org.eclipse.jetty.util.Promise; @@ -36,12 +37,18 @@ public class HttpClientTransportOverHTTP extends AbstractConnectorHttpClientTran { public HttpClientTransportOverHTTP() { - this(Math.max( 1, ProcessorUtils.availableProcessors() / 2)); + this(Math.max(1, ProcessorUtils.availableProcessors() / 2)); } public HttpClientTransportOverHTTP(int selectors) { - super(selectors); + this(new ClientConnector()); + getClientConnector().setSelectors(selectors); + } + + public HttpClientTransportOverHTTP(ClientConnector connector) + { + super(connector); setConnectionPoolFactory(destination -> new DuplexConnectionPool(destination, getHttpClient().getMaxConnectionsPerDestination(), destination)); } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpConnectionOverHTTP.java b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpConnectionOverHTTP.java index 001989b7913..746e54ec2b0 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpConnectionOverHTTP.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpConnectionOverHTTP.java @@ -93,7 +93,6 @@ public class HttpConnectionOverHTTP extends AbstractConnection implements Connec return bytesOut.longValue(); } - protected void addBytesOut(long bytesOut) { this.bytesOut.add(bytesOut); 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 index 529df7b4112..4dffe0b939b 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientCustomProxyTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientCustomProxyTest.java @@ -18,9 +18,6 @@ package org.eclipse.jetty.client; -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertEquals; - import java.io.IOException; import java.nio.ByteBuffer; import java.util.Map; @@ -49,9 +46,11 @@ import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.Promise; import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.junit.jupiter.api.AfterEach; - import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + public class HttpClientCustomProxyTest { public static final byte[] CAFE_BABE = new byte[]{(byte)0xCA, (byte)0xFE, (byte)0xBA, (byte)0xBE}; diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTLSTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTLSTest.java index ecb7789efb7..ea806cb8c31 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTLSTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTLSTest.java @@ -18,15 +18,6 @@ package org.eclipse.jetty.client; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.instanceOf; -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assumptions.assumeTrue; - import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.OutputStream; @@ -53,17 +44,22 @@ import org.eclipse.jetty.io.ssl.SslHandshakeListener; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; -import org.eclipse.jetty.util.JavaVersion; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.ExecutorThreadPool; import org.eclipse.jetty.util.thread.QueuedThreadPool; -import org.hamcrest.Matchers; import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledOnJre; import org.junit.jupiter.api.condition.JRE; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.instanceOf; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + public class HttpClientTLSTest { private Server server; 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 b32c271ff45..9fba73442ca 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 @@ -18,16 +18,6 @@ package org.eclipse.jetty.client; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.instanceOf; -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -106,6 +96,16 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ArgumentsSource; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.instanceOf; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + @ExtendWith(WorkDirExtension.class) public class HttpClientTest extends AbstractHttpClientServerTest { @@ -1716,10 +1716,12 @@ public class HttpClientTest extends AbstractHttpClientServerTest try (ServerSocket server = new ServerSocket(0)) { - startClient(scenario); - client.setMaxConnectionsPerDestination(1); int idleTimeout = 2000; - client.setIdleTimeout(idleTimeout); + startClient(scenario, null, httpClient -> + { + httpClient.setMaxConnectionsPerDestination(1); + httpClient.setIdleTimeout(idleTimeout); + }); Request request = client.newRequest("localhost", server.getLocalPort()) .scheme(scenario.getScheme()) diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/InsufficientThreadsDetectionTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/InsufficientThreadsDetectionTest.java index ea209422f79..86051d6f8d7 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/InsufficientThreadsDetectionTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/InsufficientThreadsDetectionTest.java @@ -18,13 +18,12 @@ package org.eclipse.jetty.client; -import static org.junit.jupiter.api.Assertions.assertThrows; - import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP; import org.eclipse.jetty.util.thread.QueuedThreadPool; - import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertThrows; + public class InsufficientThreadsDetectionTest { @Test @@ -33,9 +32,7 @@ public class InsufficientThreadsDetectionTest QueuedThreadPool clientThreads = new QueuedThreadPool(1); HttpClient httpClient = new HttpClient(new HttpClientTransportOverHTTP(1), null); httpClient.setExecutor(clientThreads); - assertThrows(IllegalStateException.class, ()->{ - httpClient.start(); - }); + assertThrows(IllegalStateException.class, httpClient::start); } @Test @@ -46,7 +43,8 @@ public class InsufficientThreadsDetectionTest httpClient1.setExecutor(clientThreads); httpClient1.start(); - assertThrows(IllegalStateException.class, ()->{ + assertThrows(IllegalStateException.class, () -> + { // Share the same thread pool with another instance. HttpClient httpClient2 = new HttpClient(new HttpClientTransportOverHTTP(1), null); httpClient2.setExecutor(clientThreads); 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 1d10407b6bb..7dff8d953a7 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 @@ -18,9 +18,6 @@ package org.eclipse.jetty.client.http; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.jupiter.api.Assertions.*; - import java.util.concurrent.CountDownLatch; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.TimeUnit; @@ -39,10 +36,18 @@ import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeaderValue; import org.hamcrest.Matchers; -import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ArgumentsSource; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + public class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest { @ParameterizedTest @@ -178,10 +183,9 @@ public class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest @ArgumentsSource(ScenarioProvider.class) public void test_IdleConnection_IdleTimeout(Scenario scenario) throws Exception { - start(scenario, new EmptyServerHandler()); - + startServer(scenario, new EmptyServerHandler()); long idleTimeout = 1000; - client.setIdleTimeout(idleTimeout); + startClient(scenario, null, httpClient -> httpClient.setIdleTimeout(idleTimeout)); try (HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", connector.getLocalPort()))) { diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java index 4ab77cb2d57..2c095ee2dab 100644 --- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java +++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java @@ -29,6 +29,7 @@ import org.eclipse.jetty.client.api.Connection; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.fcgi.FCGI; import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.io.ClientConnector; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.util.ProcessorUtils; import org.eclipse.jetty.util.Promise; @@ -47,7 +48,13 @@ public class HttpClientTransportOverFCGI extends AbstractConnectorHttpClientTran public HttpClientTransportOverFCGI(int selectors, String scriptRoot) { - super(selectors); + this(new ClientConnector(), scriptRoot); + getClientConnector().setSelectors(selectors); + } + + public HttpClientTransportOverFCGI(ClientConnector connector, String scriptRoot) + { + super(connector); this.scriptRoot = scriptRoot; setConnectionPoolFactory(destination -> { diff --git a/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2Client.java b/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2Client.java index 3b969874d63..a058215190a 100644 --- a/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2Client.java +++ b/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2Client.java @@ -18,13 +18,10 @@ package org.eclipse.jetty.http2.client; -import java.io.IOException; import java.net.InetSocketAddress; import java.net.SocketAddress; -import java.nio.channels.SelectableChannel; -import java.nio.channels.SelectionKey; import java.nio.channels.SocketChannel; -import java.util.Arrays; +import java.time.Duration; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -38,32 +35,24 @@ import org.eclipse.jetty.http2.frames.Frame; import org.eclipse.jetty.http2.frames.SettingsFrame; 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.ManagedSelector; -import org.eclipse.jetty.io.MappedByteBufferPool; -import org.eclipse.jetty.io.SelectorManager; -import org.eclipse.jetty.io.SocketChannelEndPoint; +import org.eclipse.jetty.io.ClientConnector; import org.eclipse.jetty.io.ssl.SslClientConnectionFactory; import org.eclipse.jetty.util.Promise; import org.eclipse.jetty.util.annotation.ManagedAttribute; import org.eclipse.jetty.util.annotation.ManagedObject; import org.eclipse.jetty.util.component.ContainerLifeCycle; import org.eclipse.jetty.util.ssl.SslContextFactory; -import org.eclipse.jetty.util.thread.QueuedThreadPool; -import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler; import org.eclipse.jetty.util.thread.Scheduler; /** - *

{@link HTTP2Client} provides an asynchronous, non-blocking implementation + *

HTTP2Client provides an asynchronous, non-blocking implementation * to send HTTP/2 frames to a server.

*

Typical usage:

*
  * // Create and start HTTP2Client.
  * HTTP2Client client = new HTTP2Client();
- * SslContextFactory sslContextFactory = new SslContextFactory();
- * client.addBean(sslContextFactory);
  * client.start();
+ * SslContextFactory sslContextFactory = client.getClientConnector().getSslContextFactory();
  *
  * // Connect to host.
  * String host = "webtide.com";
@@ -108,7 +97,7 @@ import org.eclipse.jetty.util.thread.Scheduler;
  * // Use the Stream object to send request content, if any, using a DATA frame.
  * ByteBuffer content = ...;
  * DataFrame requestContent = new DataFrame(stream.getId(), content, true);
- * stream.data(requestContent, Callback.Adapter.INSTANCE);
+ * stream.data(requestContent, Callback.NOOP);
  *
  * // When done, stop the client.
  * client.stop();
@@ -117,18 +106,9 @@ import org.eclipse.jetty.util.thread.Scheduler;
 @ManagedObject
 public class HTTP2Client extends ContainerLifeCycle
 {
-    private Executor executor;
-    private Scheduler scheduler;
-    private ByteBufferPool bufferPool;
-    private ClientConnectionFactory connectionFactory;
-    private SelectorManager selector;
-    private int selectors = 1;
-    private long idleTimeout = 30000;
-    private long connectTimeout = 10000;
-    private boolean connectBlocking;
-    private SocketAddress bindAddress;
+    private final ClientConnector connector;
     private int inputBufferSize = 8192;
-    private List protocols = Arrays.asList("h2", "h2-17", "h2-16", "h2-15", "h2-14");
+    private List protocols = List.of("h2");
     private int initialSessionRecvWindow = 16 * 1024 * 1024;
     private int initialStreamRecvWindow = 8 * 1024 * 1024;
     private int maxFrameLength = Frame.DEFAULT_MAX_LENGTH;
@@ -136,96 +116,50 @@ public class HTTP2Client extends ContainerLifeCycle
     private int maxSettingsKeys = SettingsFrame.DEFAULT_MAX_KEYS;
     private FlowControlStrategy.Factory flowControlStrategyFactory = () -> new BufferingFlowControlStrategy(0.5F);
 
-    @Override
-    protected void doStart() throws Exception
+    public HTTP2Client()
     {
-        if (executor == null)
-            setExecutor(new QueuedThreadPool());
-
-        if (scheduler == null)
-            setScheduler(new ScheduledExecutorScheduler());
-
-        if (bufferPool == null)
-            setByteBufferPool(new MappedByteBufferPool());
-
-        if (connectionFactory == null)
-        {
-            HTTP2ClientConnectionFactory h2 = new HTTP2ClientConnectionFactory();
-            setClientConnectionFactory((endPoint, context) ->
-            {
-                ClientConnectionFactory factory = h2;
-                SslContextFactory sslContextFactory = (SslContextFactory)context.get(SslClientConnectionFactory.SSL_CONTEXT_FACTORY_CONTEXT_KEY);
-                if (sslContextFactory != null)
-                {
-                    ALPNClientConnectionFactory alpn = new ALPNClientConnectionFactory(getExecutor(), h2, getProtocols());
-                    factory = newSslClientConnectionFactory(sslContextFactory, alpn);
-                }
-                return factory.newConnection(endPoint, context);
-            });
-        }
-
-        if (selector == null)
-        {
-            selector = newSelectorManager();
-            addBean(selector);
-        }
-        selector.setConnectTimeout(getConnectTimeout());
-
-        super.doStart();
+        this(new ClientConnector());
     }
 
-    protected SelectorManager newSelectorManager()
+    public HTTP2Client(ClientConnector connector)
     {
-        return new ClientSelectorManager(getExecutor(), getScheduler(), getSelectors());
+        this.connector = connector;
+        addBean(connector);
     }
 
-    protected ClientConnectionFactory newSslClientConnectionFactory(SslContextFactory sslContextFactory, ClientConnectionFactory connectionFactory)
+    public ClientConnector getClientConnector()
     {
-        return new SslClientConnectionFactory(sslContextFactory, getByteBufferPool(), getExecutor(), connectionFactory);
+        return connector;
     }
 
     public Executor getExecutor()
     {
-        return executor;
+        return connector.getExecutor();
     }
 
     public void setExecutor(Executor executor)
     {
-        this.updateBean(this.executor, executor);
-        this.executor = executor;
+        connector.setExecutor(executor);
     }
 
     public Scheduler getScheduler()
     {
-        return scheduler;
+        return connector.getScheduler();
     }
 
     public void setScheduler(Scheduler scheduler)
     {
-        this.updateBean(this.scheduler, scheduler);
-        this.scheduler = scheduler;
+        connector.setScheduler(scheduler);
     }
 
     public ByteBufferPool getByteBufferPool()
     {
-        return bufferPool;
+        return connector.getByteBufferPool();
     }
 
     public void setByteBufferPool(ByteBufferPool bufferPool)
     {
-        this.updateBean(this.bufferPool, bufferPool);
-        this.bufferPool = bufferPool;
-    }
-
-    public ClientConnectionFactory getClientConnectionFactory()
-    {
-        return connectionFactory;
-    }
-
-    public void setClientConnectionFactory(ClientConnectionFactory connectionFactory)
-    {
-        this.updateBean(this.connectionFactory, connectionFactory);
-        this.connectionFactory = connectionFactory;
+        connector.setByteBufferPool(bufferPool);
     }
 
     public FlowControlStrategy.Factory getFlowControlStrategyFactory()
@@ -241,58 +175,55 @@ public class HTTP2Client extends ContainerLifeCycle
     @ManagedAttribute("The number of selectors")
     public int getSelectors()
     {
-        return selectors;
+        return connector.getSelectors();
     }
 
     public void setSelectors(int selectors)
     {
-        this.selectors = selectors;
+        connector.setSelectors(selectors);
     }
 
     @ManagedAttribute("The idle timeout in milliseconds")
     public long getIdleTimeout()
     {
-        return idleTimeout;
+        return connector.getIdleTimeout().toMillis();
     }
 
     public void setIdleTimeout(long idleTimeout)
     {
-        this.idleTimeout = idleTimeout;
+        connector.setIdleTimeout(Duration.ofMillis(idleTimeout));
     }
 
     @ManagedAttribute("The connect timeout in milliseconds")
     public long getConnectTimeout()
     {
-        return connectTimeout;
+        return connector.getConnectTimeout().toMillis();
     }
 
     public void setConnectTimeout(long connectTimeout)
     {
-        this.connectTimeout = connectTimeout;
-        SelectorManager selector = this.selector;
-        if (selector != null)
-            selector.setConnectTimeout(connectTimeout);
+        connector.setConnectTimeout(Duration.ofMillis(connectTimeout));
     }
 
     @ManagedAttribute("Whether the connect() operation is blocking")
     public boolean isConnectBlocking()
     {
-        return connectBlocking;
+        return connector.isConnectBlocking();
     }
 
     public void setConnectBlocking(boolean connectBlocking)
     {
-        this.connectBlocking = connectBlocking;
+        connector.setConnectBlocking(connectBlocking);
     }
 
     public SocketAddress getBindAddress()
     {
-        return bindAddress;
+        return connector.getBindAddress();
     }
 
     public void setBindAddress(SocketAddress bindAddress)
     {
-        this.bindAddress = bindAddress;
+        connector.setBindAddress(bindAddress);
     }
 
     @ManagedAttribute("The size of the buffer used to read from the network")
@@ -374,6 +305,7 @@ public class HTTP2Client extends ContainerLifeCycle
 
     public void connect(InetSocketAddress address, Session.Listener listener, Promise promise)
     {
+        // Prior-knowledge clear-text HTTP/2 (h2c).
         connect(null, address, listener, promise);
     }
 
@@ -384,112 +316,49 @@ public class HTTP2Client extends ContainerLifeCycle
 
     public void connect(SslContextFactory sslContextFactory, InetSocketAddress address, Session.Listener listener, Promise promise, Map context)
     {
-        try
-        {
-            SocketChannel channel = SocketChannel.open();
-            SocketAddress bindAddress = getBindAddress();
-            if (bindAddress != null)
-                channel.bind(bindAddress);
-            configure(channel);
-            boolean connected = true;
-            if (isConnectBlocking())
-            {
-                channel.socket().connect(address, (int)getConnectTimeout());
-                channel.configureBlocking(false);
-            }
-            else
-            {
-                channel.configureBlocking(false);
-                connected = channel.connect(address);
-            }
-            context = contextFrom(sslContextFactory, address, listener, promise, context);
-            if (connected)
-                selector.accept(channel, context);
-            else
-                selector.connect(channel, context);
-        }
-        catch (Throwable x)
-        {
-            promise.failed(x);
-        }
+        ClientConnectionFactory factory = newClientConnectionFactory(sslContextFactory);
+        connect(address, factory, listener, promise, context);
+    }
+
+    public void connect(SocketAddress address, ClientConnectionFactory factory, Session.Listener listener, Promise promise, Map context)
+    {
+        context = contextFrom(factory, listener, promise, context);
+        context.put(ClientConnector.CONNECTION_PROMISE_CONTEXT_KEY, new Promise.Wrapper<>(promise));
+        connector.connect(address, context);
     }
 
     public void accept(SslContextFactory sslContextFactory, SocketChannel channel, Session.Listener listener, Promise promise)
     {
-        try
-        {
-            if (!channel.isConnected())
-                throw new IllegalStateException("SocketChannel must be connected");
-            channel.configureBlocking(false);
-            Map context = contextFrom(sslContextFactory, (InetSocketAddress)channel.getRemoteAddress(), listener, promise, null);
-            selector.accept(channel, context);
-        }
-        catch (Throwable x)
-        {
-            promise.failed(x);
-        }
+        ClientConnectionFactory factory = newClientConnectionFactory(sslContextFactory);
+        accept(channel, factory, listener, promise);
     }
 
-    private Map contextFrom(SslContextFactory sslContextFactory, InetSocketAddress address, Session.Listener listener, Promise promise, Map context)
+    public void accept(SocketChannel channel, ClientConnectionFactory factory, Session.Listener listener, Promise promise)
+    {
+        Map context = contextFrom(factory, listener, promise, null);
+        context.put(ClientConnector.CONNECTION_PROMISE_CONTEXT_KEY, new Promise.Wrapper<>(promise));
+        connector.accept(channel, context);
+    }
+
+    private Map contextFrom(ClientConnectionFactory factory, Session.Listener listener, Promise promise, Map context)
     {
         if (context == null)
             context = new HashMap<>();
         context.put(HTTP2ClientConnectionFactory.CLIENT_CONTEXT_KEY, this);
         context.put(HTTP2ClientConnectionFactory.SESSION_LISTENER_CONTEXT_KEY, listener);
         context.put(HTTP2ClientConnectionFactory.SESSION_PROMISE_CONTEXT_KEY, promise);
-        if (sslContextFactory != null)
-            context.put(SslClientConnectionFactory.SSL_CONTEXT_FACTORY_CONTEXT_KEY, sslContextFactory);
-        context.put(SslClientConnectionFactory.SSL_PEER_HOST_CONTEXT_KEY, address.getHostString());
-        context.put(SslClientConnectionFactory.SSL_PEER_PORT_CONTEXT_KEY, address.getPort());
-        context.putIfAbsent(ClientConnectionFactory.CONNECTOR_CONTEXT_KEY, this);
+        context.put(ClientConnector.CLIENT_CONNECTION_FACTORY_CONTEXT_KEY, factory);
         return context;
     }
 
-    protected void configure(SocketChannel channel) throws IOException
+    private ClientConnectionFactory newClientConnectionFactory(SslContextFactory sslContextFactory)
     {
-        channel.socket().setTcpNoDelay(true);
-    }
-
-    private class ClientSelectorManager extends SelectorManager
-    {
-        private ClientSelectorManager(Executor executor, Scheduler scheduler, int selectors)
+        ClientConnectionFactory factory = new HTTP2ClientConnectionFactory();
+        if (sslContextFactory != null)
         {
-            super(executor, scheduler, selectors);
-        }
-
-        @Override
-        protected EndPoint newEndPoint(SelectableChannel channel, ManagedSelector selector, SelectionKey selectionKey) throws IOException
-        {
-            SocketChannelEndPoint endp = new SocketChannelEndPoint(channel, selector, selectionKey, getScheduler());
-            endp.setIdleTimeout(getIdleTimeout());
-            return endp;
-        }
-
-        @Override
-        public Connection newConnection(SelectableChannel channel, EndPoint endpoint, Object attachment) throws IOException
-        {
-            @SuppressWarnings("unchecked")
-            Map context = (Map)attachment;
-            context.put(HTTP2ClientConnectionFactory.BYTE_BUFFER_POOL_CONTEXT_KEY, getByteBufferPool());
-            context.put(HTTP2ClientConnectionFactory.EXECUTOR_CONTEXT_KEY, getExecutor());
-            context.put(HTTP2ClientConnectionFactory.SCHEDULER_CONTEXT_KEY, getScheduler());
-            return getClientConnectionFactory().newConnection(endpoint, context);
-        }
-
-        @Override
-        protected void connectionFailed(SelectableChannel channel, Throwable failure, Object attachment)
-        {
-            @SuppressWarnings("unchecked")
-            Map context = (Map)attachment;
-            if (LOG.isDebugEnabled())
-            {
-                Object host = context.get(SslClientConnectionFactory.SSL_PEER_HOST_CONTEXT_KEY);
-                Object port = context.get(SslClientConnectionFactory.SSL_PEER_PORT_CONTEXT_KEY);
-                LOG.debug("Could not connect to {}:{}", host, port);
-            }
-            @SuppressWarnings("unchecked")
-            Promise promise = (Promise)context.get(HTTP2ClientConnectionFactory.SESSION_PROMISE_CONTEXT_KEY);
-            promise.failed(failure);
+            ALPNClientConnectionFactory alpn = new ALPNClientConnectionFactory(getExecutor(), factory, getProtocols());
+            factory = new SslClientConnectionFactory(sslContextFactory, getByteBufferPool(), getExecutor(), alpn);
         }
+        return factory;
     }
 }
diff --git a/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2ClientConnectionFactory.java b/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2ClientConnectionFactory.java
index cfe45c13d49..5236e9e143e 100644
--- a/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2ClientConnectionFactory.java
+++ b/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2ClientConnectionFactory.java
@@ -42,12 +42,9 @@ import org.eclipse.jetty.util.thread.Scheduler;
 
 public class HTTP2ClientConnectionFactory implements ClientConnectionFactory
 {
-    public static final String CLIENT_CONTEXT_KEY = "http2.client";
-    public static final String BYTE_BUFFER_POOL_CONTEXT_KEY = "http2.client.byteBufferPool";
-    public static final String EXECUTOR_CONTEXT_KEY = "http2.client.executor";
-    public static final String SCHEDULER_CONTEXT_KEY = "http2.client.scheduler";
-    public static final String SESSION_LISTENER_CONTEXT_KEY = "http2.client.sessionListener";
-    public static final String SESSION_PROMISE_CONTEXT_KEY = "http2.client.sessionPromise";
+    public static final String CLIENT_CONTEXT_KEY = "org.eclipse.jetty.client.http2";
+    public static final String SESSION_LISTENER_CONTEXT_KEY = "org.eclipse.jetty.client.http2.sessionListener";
+    public static final String SESSION_PROMISE_CONTEXT_KEY = "org.eclipse.jetty.client.http2.sessionPromise";
 
     private final Connection.Listener connectionListener = new ConnectionListener();
 
@@ -55,9 +52,9 @@ public class HTTP2ClientConnectionFactory implements ClientConnectionFactory
     public Connection newConnection(EndPoint endPoint, Map context)
     {
         HTTP2Client client = (HTTP2Client)context.get(CLIENT_CONTEXT_KEY);
-        ByteBufferPool byteBufferPool = (ByteBufferPool)context.get(BYTE_BUFFER_POOL_CONTEXT_KEY);
-        Executor executor = (Executor)context.get(EXECUTOR_CONTEXT_KEY);
-        Scheduler scheduler = (Scheduler)context.get(SCHEDULER_CONTEXT_KEY);
+        ByteBufferPool byteBufferPool = client.getByteBufferPool();
+        Executor executor = client.getExecutor();
+        Scheduler scheduler = client.getScheduler();
         Session.Listener listener = (Session.Listener)context.get(SESSION_LISTENER_CONTEXT_KEY);
         @SuppressWarnings("unchecked")
         Promise promise = (Promise)context.get(SESSION_PROMISE_CONTEXT_KEY);
diff --git a/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/Client.java b/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/Client.java
deleted file mode 100644
index 956a7b39c8f..00000000000
--- a/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/Client.java
+++ /dev/null
@@ -1,94 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2019 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.http2.client;
-
-import java.net.InetSocketAddress;
-import java.util.concurrent.Phaser;
-import java.util.concurrent.TimeUnit;
-
-import org.eclipse.jetty.http.HttpFields;
-import org.eclipse.jetty.http.HttpURI;
-import org.eclipse.jetty.http.HttpVersion;
-import org.eclipse.jetty.http.MetaData;
-import org.eclipse.jetty.http2.api.Session;
-import org.eclipse.jetty.http2.api.Stream;
-import org.eclipse.jetty.http2.api.server.ServerSessionListener;
-import org.eclipse.jetty.http2.frames.DataFrame;
-import org.eclipse.jetty.http2.frames.HeadersFrame;
-import org.eclipse.jetty.http2.frames.PushPromiseFrame;
-import org.eclipse.jetty.util.Callback;
-import org.eclipse.jetty.util.FuturePromise;
-import org.eclipse.jetty.util.Jetty;
-import org.eclipse.jetty.util.Promise;
-import org.eclipse.jetty.util.ssl.SslContextFactory;
-
-public class Client
-{
-    public static void main(String[] args) throws Exception
-    {
-        HTTP2Client client = new HTTP2Client();
-        SslContextFactory sslContextFactory = new SslContextFactory();
-        client.addBean(sslContextFactory);
-        client.start();
-
-        String host = "webtide.com";
-        int port = 443;
-
-        FuturePromise sessionPromise = new FuturePromise<>();
-        client.connect(sslContextFactory, new InetSocketAddress(host, port), new ServerSessionListener.Adapter(), sessionPromise);
-        Session session = sessionPromise.get(5, TimeUnit.SECONDS);
-
-        HttpFields requestFields = new HttpFields();
-        requestFields.put("User-Agent", client.getClass().getName() + "/" + Jetty.VERSION);
-        MetaData.Request metaData = new MetaData.Request("GET", new HttpURI("https://" + host + ":" + port + "/"), HttpVersion.HTTP_2, requestFields);
-        HeadersFrame headersFrame = new HeadersFrame(metaData, null, true);
-        final Phaser phaser = new Phaser(2);
-        session.newStream(headersFrame, new Promise.Adapter<>(), new Stream.Listener.Adapter()
-        {
-            @Override
-            public void onHeaders(Stream stream, HeadersFrame frame)
-            {
-                System.err.println(frame);
-                if (frame.isEndStream())
-                    phaser.arrive();
-            }
-
-            @Override
-            public void onData(Stream stream, DataFrame frame, Callback callback)
-            {
-                System.err.println(frame);
-                callback.succeeded();
-                if (frame.isEndStream())
-                    phaser.arrive();
-            }
-
-            @Override
-            public Stream.Listener onPush(Stream stream, PushPromiseFrame frame)
-            {
-                System.err.println(frame);
-                phaser.register();
-                return this;
-            }
-        });
-
-        phaser.awaitAdvanceInterruptibly(phaser.arrive(), 5, TimeUnit.SECONDS);
-
-        client.stop();
-    }
-}
diff --git a/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpClientTransportOverHTTP2.java b/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpClientTransportOverHTTP2.java
index 7584d1966e1..cacb023db7d 100644
--- a/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpClientTransportOverHTTP2.java
+++ b/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpClientTransportOverHTTP2.java
@@ -45,7 +45,6 @@ import org.eclipse.jetty.io.EndPoint;
 import org.eclipse.jetty.util.Promise;
 import org.eclipse.jetty.util.annotation.ManagedAttribute;
 import org.eclipse.jetty.util.annotation.ManagedObject;
-import org.eclipse.jetty.util.ssl.SslContextFactory;
 
 @ManagedObject("The HTTP/2 client transport")
 public class HttpClientTransportOverHTTP2 extends AbstractHttpClientTransport
@@ -101,13 +100,7 @@ public class HttpClientTransportOverHTTP2 extends AbstractHttpClientTransport
         }
         addBean(client);
         super.doStart();
-
-        this.connectionFactory = new HTTP2ClientConnectionFactory();
-        client.setClientConnectionFactory((endPoint, context) ->
-        {
-            HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
-            return destination.getClientConnectionFactory().newConnection(endPoint, context);
-        });
+        connectionFactory = new HTTP2ClientConnectionFactory();
     }
 
     @Override
@@ -134,16 +127,12 @@ public class HttpClientTransportOverHTTP2 extends AbstractHttpClientTransport
         SessionListenerPromise listenerPromise = new SessionListenerPromise(context);
 
         HttpDestinationOverHTTP2 destination = (HttpDestinationOverHTTP2)context.get(HTTP_DESTINATION_CONTEXT_KEY);
-        SslContextFactory sslContextFactory = null;
-        if (HttpScheme.HTTPS.is(destination.getScheme()))
-            sslContextFactory = httpClient.getSslContextFactory();
-
-        connect(sslContextFactory, address, listenerPromise, listenerPromise, context);
+        connect(address, destination.getClientConnectionFactory(), listenerPromise, listenerPromise, context);
     }
 
-    protected void connect(SslContextFactory sslContextFactory, InetSocketAddress address, Session.Listener listener, Promise promise, Map context)
+    protected void connect(InetSocketAddress address, ClientConnectionFactory factory, Session.Listener listener, Promise promise, Map context)
     {
-        getHTTP2Client().connect(sslContextFactory, address, listener, promise, context);
+        getHTTP2Client().connect(address, factory, listener, promise, context);
     }
 
     @Override
diff --git a/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/MaxConcurrentStreamsTest.java b/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/MaxConcurrentStreamsTest.java
index cee3802220d..21225c96a95 100644
--- a/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/MaxConcurrentStreamsTest.java
+++ b/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/MaxConcurrentStreamsTest.java
@@ -51,11 +51,11 @@ import org.eclipse.jetty.http2.frames.PingFrame;
 import org.eclipse.jetty.http2.frames.ResetFrame;
 import org.eclipse.jetty.http2.frames.SettingsFrame;
 import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory;
+import org.eclipse.jetty.io.ClientConnectionFactory;
 import org.eclipse.jetty.server.Handler;
 import org.eclipse.jetty.server.HttpConfiguration;
 import org.eclipse.jetty.server.Request;
 import org.eclipse.jetty.util.Promise;
-import org.eclipse.jetty.util.ssl.SslContextFactory;
 import org.eclipse.jetty.util.thread.QueuedThreadPool;
 import org.junit.jupiter.api.Test;
 
@@ -171,9 +171,9 @@ public class MaxConcurrentStreamsTest extends AbstractTest
         client = new HttpClient(new HttpClientTransportOverHTTP2(new HTTP2Client())
         {
             @Override
-            protected void connect(SslContextFactory sslContextFactory, InetSocketAddress address, Session.Listener listener, Promise promise, Map context)
+            protected void connect(InetSocketAddress address, ClientConnectionFactory factory, Session.Listener listener, Promise promise, Map context)
             {
-                super.connect(sslContextFactory, address, new Wrapper(listener)
+                super.connect(address, factory, new Wrapper(listener)
                 {
                     @Override
                     public void onSettings(Session session, SettingsFrame frame)
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
index 9902ce0d4e0..d935b3d1eb2 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnectionFactory.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnectionFactory.java
@@ -28,10 +28,9 @@ import org.eclipse.jetty.util.component.ContainerLifeCycle;
  */
 public interface ClientConnectionFactory
 {
-    public static final String CONNECTOR_CONTEXT_KEY = "client.connector";
+    public static final String CLIENT_CONTEXT_KEY = "org.eclipse.jetty.client";
 
     /**
-     *
      * @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}
@@ -41,8 +40,9 @@ public interface ClientConnectionFactory
 
     public default Connection customize(Connection connection, Map context)
     {
-        ContainerLifeCycle connector = (ContainerLifeCycle)context.get(CONNECTOR_CONTEXT_KEY);
-        connector.getBeans(Connection.Listener.class).forEach(connection::addListener);
+        ContainerLifeCycle client = (ContainerLifeCycle)context.get(CLIENT_CONTEXT_KEY);
+        if (client != null)
+            client.getBeans(Connection.Listener.class).forEach(connection::addListener);
         return connection;
     }
 }
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java
new file mode 100644
index 00000000000..5bb069124fb
--- /dev/null
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java
@@ -0,0 +1,333 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2019 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.net.SocketAddress;
+import java.net.SocketException;
+import java.nio.channels.SelectableChannel;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.SocketChannel;
+import java.time.Duration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+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;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+public class ClientConnector extends ContainerLifeCycle
+{
+    public static final String CLIENT_CONNECTOR_CONTEXT_KEY = "org.eclipse.jetty.client.connector";
+    public static final String SOCKET_ADDRESS_CONTEXT_KEY = CLIENT_CONNECTOR_CONTEXT_KEY + ".socketAddress";
+    public static final String CLIENT_CONNECTION_FACTORY_CONTEXT_KEY = CLIENT_CONNECTOR_CONTEXT_KEY + ".clientConnectionFactory";
+    public static final String CONNECTION_PROMISE_CONTEXT_KEY = CLIENT_CONNECTOR_CONTEXT_KEY + ".connectionPromise";
+    private static final Logger LOG = Log.getLogger(ClientConnector.class);
+
+    private Executor executor;
+    private Scheduler scheduler;
+    private ByteBufferPool byteBufferPool;
+    private SslContextFactory sslContextFactory;
+    private SelectorManager selectorManager;
+    private int selectors = 1;
+    private boolean connectBlocking;
+    private Duration connectTimeout = Duration.ofSeconds(5);
+    private Duration idleTimeout = Duration.ofSeconds(30);
+    private SocketAddress bindAddress;
+
+    public Executor getExecutor()
+    {
+        return executor;
+    }
+
+    public void setExecutor(Executor executor)
+    {
+        if (isStarted())
+            throw new IllegalStateException();
+        updateBean(this.executor, executor);
+        this.executor = executor;
+    }
+
+    public Scheduler getScheduler()
+    {
+        return scheduler;
+    }
+
+    public void setScheduler(Scheduler scheduler)
+    {
+        if (isStarted())
+            throw new IllegalStateException();
+        updateBean(this.scheduler, scheduler);
+        this.scheduler = scheduler;
+    }
+
+    public ByteBufferPool getByteBufferPool()
+    {
+        return byteBufferPool;
+    }
+
+    public void setByteBufferPool(ByteBufferPool byteBufferPool)
+    {
+        if (isStarted())
+            throw new IllegalStateException();
+        updateBean(this.byteBufferPool, byteBufferPool);
+        this.byteBufferPool = byteBufferPool;
+    }
+
+    public SslContextFactory getSslContextFactory()
+    {
+        return sslContextFactory;
+    }
+
+    public void setSslContextFactory(SslContextFactory sslContextFactory)
+    {
+        if (isStarted())
+            throw new IllegalStateException();
+        updateBean(this.sslContextFactory, sslContextFactory);
+        this.sslContextFactory = sslContextFactory;
+    }
+
+    public int getSelectors()
+    {
+        return selectors;
+    }
+
+    public void setSelectors(int selectors)
+    {
+        if (isStarted())
+            throw new IllegalStateException();
+        this.selectors = selectors;
+    }
+
+    public boolean isConnectBlocking()
+    {
+        return connectBlocking;
+    }
+
+    public void setConnectBlocking(boolean connectBlocking)
+    {
+        this.connectBlocking = connectBlocking;
+    }
+
+    public Duration getConnectTimeout()
+    {
+        return connectTimeout;
+    }
+
+    public void setConnectTimeout(Duration connectTimeout)
+    {
+        this.connectTimeout = connectTimeout;
+        if (selectorManager != null)
+            selectorManager.setConnectTimeout(connectTimeout.toMillis());
+    }
+
+    public Duration getIdleTimeout()
+    {
+        return idleTimeout;
+    }
+
+    public void setIdleTimeout(Duration idleTimeout)
+    {
+        this.idleTimeout = idleTimeout;
+    }
+
+    public SocketAddress getBindAddress()
+    {
+        return bindAddress;
+    }
+
+    public void setBindAddress(SocketAddress bindAddress)
+    {
+        this.bindAddress = bindAddress;
+    }
+
+    @Override
+    protected void doStart() throws Exception
+    {
+        if (executor == null)
+            setExecutor(new QueuedThreadPool());
+        if (scheduler == null)
+            setScheduler(new ScheduledExecutorScheduler());
+        if (byteBufferPool == null)
+            setByteBufferPool(new MappedByteBufferPool());
+        if (sslContextFactory == null)
+            setSslContextFactory(newSslContextFactory());
+        selectorManager = newSelectorManager();
+        selectorManager.setConnectTimeout(getConnectTimeout().toMillis());
+        addBean(selectorManager);
+        super.doStart();
+    }
+
+    @Override
+    protected void doStop() throws Exception
+    {
+        super.doStop();
+        removeBean(selectorManager);
+    }
+
+    protected SslContextFactory newSslContextFactory()
+    {
+        SslContextFactory sslContextFactory = new SslContextFactory(false);
+        sslContextFactory.setEndpointIdentificationAlgorithm("HTTPS");
+        return sslContextFactory;
+    }
+
+    protected SelectorManager newSelectorManager()
+    {
+        return new ClientSelectorManager(getExecutor(), getScheduler(), getSelectors());
+    }
+
+    public void connect(SocketAddress address, Map context)
+    {
+        SocketChannel channel = null;
+        try
+        {
+            if (context == null)
+                context = new HashMap<>();
+            context.put(ClientConnector.CLIENT_CONNECTOR_CONTEXT_KEY, this);
+            context.putIfAbsent(SOCKET_ADDRESS_CONTEXT_KEY, address);
+
+            channel = SocketChannel.open();
+            SocketAddress bindAddress = getBindAddress();
+            if (bindAddress != null)
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Binding to {} to connect to {}", bindAddress, address);
+                channel.bind(bindAddress);
+            }
+            configure(channel);
+
+            boolean connected = true;
+            boolean blocking = isConnectBlocking();
+            if (LOG.isDebugEnabled())
+                LOG.debug("Connecting {} to {}", blocking ? "blocking" : "non-blocking", address);
+            if (blocking)
+            {
+                channel.socket().connect(address, (int)getConnectTimeout().toMillis());
+                channel.configureBlocking(false);
+            }
+            else
+            {
+                channel.configureBlocking(false);
+                connected = channel.connect(address);
+            }
+
+            if (connected)
+                selectorManager.accept(channel, context);
+            else
+                selectorManager.connect(channel, context);
+        }
+        // Must catch all exceptions, since some like
+        // UnresolvedAddressException are not IOExceptions.
+        catch (Throwable x)
+        {
+            // If IPv6 is not deployed, a generic SocketException "Network is unreachable"
+            // exception is being thrown, so we attempt to provide a better error message.
+            if (x.getClass() == SocketException.class)
+                x = new SocketException("Could not connect to " + address).initCause(x);
+
+            try
+            {
+                if (channel != null)
+                    channel.close();
+            }
+            catch (IOException xx)
+            {
+                LOG.ignore(xx);
+            }
+            finally
+            {
+                connectFailed(x, context);
+            }
+        }
+    }
+
+    public void accept(SocketChannel channel, Map context)
+    {
+        try
+        {
+            context.put(ClientConnector.CLIENT_CONNECTOR_CONTEXT_KEY, this);
+
+            if (!channel.isConnected())
+                throw new IllegalStateException("SocketChannel must be connected");
+            configure(channel);
+            channel.configureBlocking(false);
+            selectorManager.accept(channel, context);
+        }
+        catch (Throwable failure)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("Could not accept {}", channel);
+            Promise promise = (Promise)context.get(CONNECTION_PROMISE_CONTEXT_KEY);
+            promise.failed(failure);
+        }
+    }
+
+    protected void configure(SocketChannel channel) throws IOException
+    {
+        channel.socket().setTcpNoDelay(true);
+    }
+
+    private void connectFailed(Throwable failure, Map context)
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("Could not connect to {}", context.get(SOCKET_ADDRESS_CONTEXT_KEY));
+        Promise promise = (Promise)context.get(CONNECTION_PROMISE_CONTEXT_KEY);
+        promise.failed(failure);
+    }
+
+    private class ClientSelectorManager extends SelectorManager
+    {
+        private ClientSelectorManager(Executor executor, Scheduler scheduler, int selectors)
+        {
+            super(executor, scheduler, selectors);
+        }
+
+        @Override
+        protected EndPoint newEndPoint(SelectableChannel channel, ManagedSelector selector, SelectionKey selectionKey)
+        {
+            SocketChannelEndPoint endPoint = new SocketChannelEndPoint(channel, selector, selectionKey, getScheduler());
+            endPoint.setIdleTimeout(getIdleTimeout().toMillis());
+            return endPoint;
+        }
+
+        @Override
+        public Connection newConnection(SelectableChannel channel, EndPoint endPoint, Object attachment) throws IOException
+        {
+            @SuppressWarnings("unchecked")
+            Map context = (Map)attachment;
+            ClientConnectionFactory factory = (ClientConnectionFactory)context.get(CLIENT_CONNECTION_FACTORY_CONTEXT_KEY);
+            return factory.newConnection(endPoint, context);
+        }
+
+        @Override
+        protected void connectionFailed(SelectableChannel channel, Throwable failure, Object attachment)
+        {
+            @SuppressWarnings("unchecked")
+            Map context = (Map)attachment;
+            connectFailed(failure, context);
+        }
+    }
+}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/NegotiatingClientConnection.java b/jetty-io/src/main/java/org/eclipse/jetty/io/NegotiatingClientConnection.java
index 932644d6e62..867f88957aa 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/NegotiatingClientConnection.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/NegotiatingClientConnection.java
@@ -37,9 +37,9 @@ public abstract class NegotiatingClientConnection extends AbstractConnection
     private final Map context;
     private volatile boolean completed;
 
-    protected NegotiatingClientConnection(EndPoint endp, Executor executor, SSLEngine sslEngine, ClientConnectionFactory connectionFactory, Map context)
+    protected NegotiatingClientConnection(EndPoint endPoint, Executor executor, SSLEngine sslEngine, ClientConnectionFactory connectionFactory, Map context)
     {
-        super(endp, executor);
+        super(endPoint, executor);
         this.engine = sslEngine;
         this.connectionFactory = connectionFactory;
         this.context = context;
@@ -67,7 +67,7 @@ public abstract class NegotiatingClientConnection extends AbstractConnection
             else
                 fillInterested();
         }
-        catch (IOException x)
+        catch (Throwable x)
         {
             close();
             throw new RuntimeIOException(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
index a86fcb9f3c7..874f2a815aa 100644
--- 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
@@ -19,6 +19,7 @@
 package org.eclipse.jetty.io.ssl;
 
 import java.io.IOException;
+import java.net.InetSocketAddress;
 import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.Executor;
@@ -27,6 +28,7 @@ import javax.net.ssl.SSLEngine;
 
 import org.eclipse.jetty.io.ByteBufferPool;
 import org.eclipse.jetty.io.ClientConnectionFactory;
+import org.eclipse.jetty.io.ClientConnector;
 import org.eclipse.jetty.io.Connection;
 import org.eclipse.jetty.io.EndPoint;
 import org.eclipse.jetty.util.component.ContainerLifeCycle;
@@ -34,10 +36,7 @@ import org.eclipse.jetty.util.ssl.SslContextFactory;
 
 public class SslClientConnectionFactory implements ClientConnectionFactory
 {
-    public static final String SSL_CONTEXT_FACTORY_CONTEXT_KEY = "ssl.context.factory";
-    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";
+    public static final String SSL_ENGINE_CONTEXT_KEY = "org.eclipse.jetty.client.ssl.engine";
 
     private final SslContextFactory sslContextFactory;
     private final ByteBufferPool byteBufferPool;
@@ -88,9 +87,8 @@ public class SslClientConnectionFactory implements ClientConnectionFactory
     @Override
     public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map 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);
+        InetSocketAddress address = (InetSocketAddress)context.get(ClientConnector.SOCKET_ADDRESS_CONTEXT_KEY);
+        SSLEngine engine = sslContextFactory.newSSLEngine(address);
         engine.setUseClientMode(true);
         context.put(SSL_ENGINE_CONTEXT_KEY, engine);
 
@@ -119,8 +117,9 @@ public class SslClientConnectionFactory implements ClientConnectionFactory
             sslConnection.setRenegotiationAllowed(sslContextFactory.isRenegotiationAllowed());
             sslConnection.setRenegotiationLimit(sslContextFactory.getRenegotiationLimit());
             sslConnection.setAllowMissingCloseMessage(isAllowMissingCloseMessage());
-            ContainerLifeCycle connector = (ContainerLifeCycle)context.get(ClientConnectionFactory.CONNECTOR_CONTEXT_KEY);
-            connector.getBeans(SslHandshakeListener.class).forEach(sslConnection::addHandshakeListener);
+            ContainerLifeCycle client = (ContainerLifeCycle)context.get(ClientConnectionFactory.CLIENT_CONTEXT_KEY);
+            if (client != null)
+                client.getBeans(SslHandshakeListener.class).forEach(sslConnection::addHandshakeListener);
         }
         return ClientConnectionFactory.super.customize(connection, context);
     }
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 a42b626d11a..fe62af7044e 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
@@ -41,6 +41,7 @@ import java.util.Random;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
 import java.util.stream.Stream;
 import java.util.zip.GZIPOutputStream;
 
@@ -171,16 +172,23 @@ public class ProxyServletTest
 
     private void startClient() throws Exception
     {
-        client = prepareClient();
+        startClient(null);
     }
 
-    private HttpClient prepareClient() throws Exception
+    private void startClient(Consumer consumer) throws Exception
+    {
+        client = prepareClient(consumer);
+    }
+
+    private HttpClient prepareClient(Consumer consumer) throws Exception
     {
         QueuedThreadPool clientPool = new QueuedThreadPool();
         clientPool.setName("client");
         HttpClient result = new HttpClient();
         result.setExecutor(clientPool);
         result.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyConnector.getLocalPort()));
+        if (consumer != null)
+            consumer.accept(result);
         result.start();
         return result;
     }
@@ -987,7 +995,7 @@ public class ProxyServletTest
         assertEquals(name, cookies.get(0).getName());
         assertEquals(value1, cookies.get(0).getValue());
 
-        HttpClient client2 = prepareClient();
+        HttpClient client2 = prepareClient(null);
         try
         {
             String value2 = "2";
@@ -1373,10 +1381,8 @@ public class ProxyServletTest
             }
         });
         startProxy(proxyServletClass);
-        startClient();
-
         long idleTimeout = 1000;
-        client.setIdleTimeout(idleTimeout);
+        startClient(httpClient -> httpClient.setIdleTimeout(idleTimeout));
 
         byte[] content = new byte[1024];
         new Random().nextBytes(content);
diff --git a/jetty-unixsocket/src/main/java/org/eclipse/jetty/unixsocket/client/HttpClientTransportOverUnixSockets.java b/jetty-unixsocket/src/main/java/org/eclipse/jetty/unixsocket/client/HttpClientTransportOverUnixSockets.java
index 9e30713099a..aa2c77463a8 100644
--- a/jetty-unixsocket/src/main/java/org/eclipse/jetty/unixsocket/client/HttpClientTransportOverUnixSockets.java
+++ b/jetty-unixsocket/src/main/java/org/eclipse/jetty/unixsocket/client/HttpClientTransportOverUnixSockets.java
@@ -21,85 +21,110 @@ package org.eclipse.jetty.unixsocket.client;
 import java.io.IOException;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
-import java.net.SocketException;
 import java.nio.channels.SelectableChannel;
 import java.nio.channels.SelectionKey;
 import java.nio.channels.Selector;
-import java.nio.file.Files;
-import java.nio.file.Paths;
+import java.nio.channels.SocketChannel;
 import java.util.Map;
 
-import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.client.HttpDestination;
-import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.io.ManagedSelector;
-import org.eclipse.jetty.io.SelectorManager;
-import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
-import org.eclipse.jetty.unixsocket.UnixSocketEndPoint;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
 import jnr.enxio.channels.NativeSelectorProvider;
 import jnr.unixsocket.UnixSocketAddress;
 import jnr.unixsocket.UnixSocketChannel;
+import org.eclipse.jetty.client.AbstractHttpClientTransport;
+import org.eclipse.jetty.client.DuplexConnectionPool;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.HttpDestination;
+import org.eclipse.jetty.client.Origin;
+import org.eclipse.jetty.client.http.HttpConnectionOverHTTP;
+import org.eclipse.jetty.client.http.HttpDestinationOverHTTP;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.ManagedSelector;
+import org.eclipse.jetty.io.SelectorManager;
+import org.eclipse.jetty.unixsocket.UnixSocketEndPoint;
+import org.eclipse.jetty.util.Promise;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
 
-public class HttpClientTransportOverUnixSockets
-    extends HttpClientTransportOverHTTP
+// TODO: this class needs a thorough review.
+public class HttpClientTransportOverUnixSockets extends AbstractHttpClientTransport
 {
-    private static final Logger LOG = Log.getLogger( HttpClientTransportOverUnixSockets.class );
-    
+    private static final Logger LOG = Log.getLogger(HttpClientTransportOverUnixSockets.class);
+
     private String _unixSocket;
     private SelectorManager selectorManager;
 
     private UnixSocketChannel channel;
 
-    public HttpClientTransportOverUnixSockets( String unixSocket )
+    public HttpClientTransportOverUnixSockets(String unixSocket)
     {
-        if ( unixSocket == null )
-        {
-            throw new IllegalArgumentException( "Unix socket file cannot be null" );
-        }
+        if (unixSocket == null)
+            throw new IllegalArgumentException("Unix socket file cannot be null");
         this._unixSocket = unixSocket;
+        setConnectionPoolFactory(destination ->
+        {
+            HttpClient httpClient = getHttpClient();
+            int maxConnections = httpClient.getMaxConnectionsPerDestination();
+            return new DuplexConnectionPool(destination, maxConnections, destination);
+        });
     }
 
     @Override
-    protected SelectorManager newSelectorManager(HttpClient client)
+    protected void doStart() throws Exception
     {
-        return selectorManager = new UnixSocketSelectorManager(client,getSelectors());
+        HttpClient httpClient = getHttpClient();
+        selectorManager = new UnixSocketSelectorManager(httpClient, 1);
+        selectorManager.setConnectTimeout(httpClient.getConnectTimeout());
+        addBean(selectorManager);
+        super.doStart();
     }
 
     @Override
-    public void connect( InetSocketAddress address, Map context )
+    protected void doStop() throws Exception
     {
+        super.doStop();
+        try
+        {
+            if (channel != null)
+                channel.close();
+        }
+        catch (IOException xx)
+        {
+            LOG.ignore(xx);
+        }
+    }
 
+    @Override
+    public HttpDestination newHttpDestination(Origin origin)
+    {
+        return new HttpDestinationOverHTTP(getHttpClient(), origin);
+    }
+
+    @Override
+    public void connect(InetSocketAddress address, Map context)
+    {
         try
         {
             InetAddress inet = address.getAddress();
             if (!inet.isLoopbackAddress() && !inet.isLinkLocalAddress() && !inet.isSiteLocalAddress())
-                throw new IOException("UnixSocket cannot connect to "+address.getHostString());
-            
+                throw new IOException("UnixSocket cannot connect to " + address.getHostString());
+
             // Open a unix socket
-            UnixSocketAddress unixAddress = new UnixSocketAddress( this._unixSocket );
-            channel = UnixSocketChannel.open( unixAddress );
-            
+            UnixSocketAddress unixAddress = new UnixSocketAddress(this._unixSocket);
+            channel = UnixSocketChannel.open(unixAddress);
+
             HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
             HttpClient client = destination.getHttpClient();
 
             configure(client, channel);
 
             channel.configureBlocking(false);
-            selectorManager.accept(channel, context);            
+            selectorManager.accept(channel, context);
         }
         // Must catch all exceptions, since some like
         // UnresolvedAddressException are not IOExceptions.
         catch (Throwable x)
         {
-            // If IPv6 is not deployed, a generic SocketException "Network is unreachable"
-            // exception is being thrown, so we attempt to provide a better error message.
-            if (x.getClass() == SocketException.class)
-                x = new SocketException("Could not connect to " + address).initCause(x);
-
             try
             {
                 if (channel != null)
@@ -116,11 +141,33 @@ public class HttpClientTransportOverUnixSockets
         }
     }
 
-    public class UnixSocketSelectorManager extends ClientSelectorManager
+    @Override
+    public Connection newConnection(EndPoint endPoint, Map context)
+    {
+        HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
+        @SuppressWarnings("unchecked")
+        Promise promise = (Promise)context.get(HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
+        HttpConnectionOverHTTP connection = newHttpConnection(endPoint, destination, promise);
+        if (LOG.isDebugEnabled())
+            LOG.debug("Created {}", connection);
+        return customize(connection, context);
+    }
+
+    protected HttpConnectionOverHTTP newHttpConnection(EndPoint endPoint, HttpDestination destination, Promise promise)
+    {
+        return new HttpConnectionOverHTTP(endPoint, destination, promise);
+    }
+
+    protected void configure(HttpClient client, SocketChannel channel) throws IOException
+    {
+        channel.socket().setTcpNoDelay(client.isTCPNoDelay());
+    }
+
+    public class UnixSocketSelectorManager extends SelectorManager
     {
         protected UnixSocketSelectorManager(HttpClient client, int selectors)
         {
-            super(client,selectors);
+            super(client.getExecutor(), client.getScheduler(), selectors);
         }
 
         @Override
@@ -132,25 +179,26 @@ public class HttpClientTransportOverUnixSockets
         @Override
         protected EndPoint newEndPoint(SelectableChannel channel, ManagedSelector selector, SelectionKey key)
         {
-            UnixSocketEndPoint endp = new UnixSocketEndPoint((UnixSocketChannel)channel, selector, key, getScheduler());
-            endp.setIdleTimeout(getHttpClient().getIdleTimeout());
-            return endp;
+            UnixSocketEndPoint endPoint = new UnixSocketEndPoint((UnixSocketChannel)channel, selector, key, getScheduler());
+            endPoint.setIdleTimeout(getHttpClient().getIdleTimeout());
+            return endPoint;
         }
-    }
 
-    @Override
-    protected void doStop()
-        throws Exception
-    {
-        super.doStop();
-        try
+        @Override
+        public Connection newConnection(SelectableChannel channel, EndPoint endPoint, Object attachment) throws IOException
         {
-            if (channel != null)
-                channel.close();
+            @SuppressWarnings("unchecked")
+            Map context = (Map)attachment;
+            HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
+            return destination.getClientConnectionFactory().newConnection(endPoint, context);
         }
-        catch (IOException xx)
+
+        @Override
+        protected void connectionFailed(SelectableChannel channel, Throwable x, Object attachment)
         {
-            LOG.ignore(xx);
+            @SuppressWarnings("unchecked")
+            Map context = (Map)attachment;
+            connectFailed(context, x);
         }
     }
 }
diff --git a/jetty-unixsocket/src/test/java/org/eclipse/jetty/unixsocket/UnixSocketTest.java b/jetty-unixsocket/src/test/java/org/eclipse/jetty/unixsocket/UnixSocketTest.java
index 682d55c78d1..3481fcb0daf 100644
--- a/jetty-unixsocket/src/test/java/org/eclipse/jetty/unixsocket/UnixSocketTest.java
+++ b/jetty-unixsocket/src/test/java/org/eclipse/jetty/unixsocket/UnixSocketTest.java
@@ -18,14 +18,6 @@
 
 package org.eclipse.jetty.unixsocket;
 
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.containsString;
-import static org.hamcrest.Matchers.instanceOf;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.junit.jupiter.api.condition.OS.LINUX;
-import static org.junit.jupiter.api.condition.OS.MAC;
-
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.file.Files;
@@ -34,7 +26,6 @@ import java.nio.file.Paths;
 import java.util.Date;
 import java.util.concurrent.ExecutionException;
 
-import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
@@ -54,6 +45,14 @@ import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.condition.EnabledOnOs;
 
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.condition.OS.LINUX;
+import static org.junit.jupiter.api.condition.OS.MAC;
+
 @EnabledOnOs({LINUX, MAC})
 public class UnixSocketTest
 {
@@ -68,94 +67,86 @@ public class UnixSocketTest
     {
         server = null;
         httpClient = null;
-        String unixSocketTmp = System.getProperty( "unix.socket.tmp" );
-        if(StringUtil.isNotBlank( unixSocketTmp ) )
-        {
-            sockFile = Files.createTempFile( Paths.get(unixSocketTmp), "unix", ".sock" );
-        } else {
-            sockFile = Files.createTempFile("unix", ".sock" );
-        }
-        assertTrue(Files.deleteIfExists(sockFile),"temp sock file cannot be deleted");
+        String unixSocketTmp = System.getProperty("unix.socket.tmp");
+        if (StringUtil.isNotBlank(unixSocketTmp))
+            sockFile = Files.createTempFile(Paths.get(unixSocketTmp), "unix", ".sock");
+        else
+            sockFile = Files.createTempFile("unix", ".sock");
+        assertTrue(Files.deleteIfExists(sockFile), "temp sock file cannot be deleted");
 
     }
-    
+
     @AfterEach
     public void after() throws Exception
     {
-        if (httpClient!=null)
+        if (httpClient != null)
             httpClient.stop();
-        if (server!=null)
+        if (server != null)
             server.stop();
         // Force delete, this will fail if UnixSocket was not closed properly in the implementation
-        FS.delete( sockFile);
+        FS.delete(sockFile);
     }
-    
+
     @Test
     public void testUnixSocket() throws Exception
     {
         server = new Server();
-
         HttpConnectionFactory http = new HttpConnectionFactory();
+        UnixSocketConnector connector = new UnixSocketConnector(server, http);
+        connector.setUnixSocket(sockFile.toString());
+        server.addConnector(connector);
 
-        UnixSocketConnector connector = new UnixSocketConnector( server, http );
-        connector.setUnixSocket( sockFile.toString() );
-        server.addConnector( connector );
-
-        server.setHandler( new AbstractHandler.ErrorDispatchHandler()
+        server.setHandler(new AbstractHandler.ErrorDispatchHandler()
         {
             @Override
-            protected void doNonErrorHandle( String target, Request baseRequest, HttpServletRequest request,
-                    HttpServletResponse response )
-                            throws IOException, ServletException
+            protected void doNonErrorHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
             {
                 int l = 0;
-                if ( request.getContentLength() != 0 )
+                if (request.getContentLength() != 0)
                 {
                     InputStream in = request.getInputStream();
                     byte[] buffer = new byte[4096];
                     int r = 0;
-                    while ( r >= 0 )
+                    while (r >= 0)
                     {
                         l += r;
-                        r = in.read( buffer );
+                        r = in.read(buffer);
                     }
                 }
-                log.info( "UnixSocketTest: request received" );
-                baseRequest.setHandled( true );
-                response.setStatus( 200 );
-                response.getWriter().write( "Hello World " + new Date() + "\r\n" );
+                log.info("UnixSocketTest: request received");
+                baseRequest.setHandled(true);
+                response.setStatus(200);
+                response.getWriter().write("Hello World " + new Date() + "\r\n");
                 response.getWriter().write(
-                        "remote=" + request.getRemoteAddr() + ":" + request.getRemotePort() + "\r\n" );
+                        "remote=" + request.getRemoteAddr() + ":" + request.getRemotePort() + "\r\n");
                 response.getWriter().write(
-                        "local =" + request.getLocalAddr() + ":" + request.getLocalPort() + "\r\n" );
-                response.getWriter().write( "read =" + l + "\r\n" );
+                        "local =" + request.getLocalAddr() + ":" + request.getLocalPort() + "\r\n");
+                response.getWriter().write("read =" + l + "\r\n");
             }
-        } );
+        });
 
         server.start();
 
-        httpClient = new HttpClient( new HttpClientTransportOverUnixSockets( sockFile.toString() ), null );
+        httpClient = new HttpClient(new HttpClientTransportOverUnixSockets(sockFile.toString()), null);
         httpClient.start();
 
         ContentResponse contentResponse = httpClient
-                .newRequest( "http://localhost" )
+                .newRequest("http://localhost")
                 .send();
 
-        log.debug( "response from server: {}", contentResponse.getContentAsString() );
+        log.debug("response from server: {}", contentResponse.getContentAsString());
 
-        assertThat(contentResponse.getContentAsString(), containsString( "Hello World" ));
+        assertThat(contentResponse.getContentAsString(), containsString("Hello World"));
     }
 
     @Test
     public void testNotLocal() throws Exception
-    {        
-        httpClient = new HttpClient( new HttpClientTransportOverUnixSockets( sockFile.toString() ), null );
+    {
+        httpClient = new HttpClient(new HttpClientTransportOverUnixSockets(sockFile.toString()), null);
         httpClient.start();
-        
-        ExecutionException e = assertThrows(ExecutionException.class, ()->{
-            httpClient.newRequest( "http://google.com" ).send();
-        });
+
+        ExecutionException e = assertThrows(ExecutionException.class, () -> httpClient.newRequest("http://google.com").send());
         assertThat(e.getCause(), instanceOf(IOException.class));
-        assertThat(e.getCause().getMessage(),containsString("UnixSocket cannot connect to google.com"));
+        assertThat(e.getCause().getMessage(), containsString("UnixSocket cannot connect to google.com"));
     }
 }
diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/ConnectionStatisticsTest.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/ConnectionStatisticsTest.java
index ef651ee12e5..16c5fd66bdc 100644
--- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/ConnectionStatisticsTest.java
+++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/ConnectionStatisticsTest.java
@@ -18,10 +18,6 @@
 
 package org.eclipse.jetty.http.client;
 
-import static org.eclipse.jetty.http.client.Transport.H2C;
-import static org.eclipse.jetty.http.client.Transport.HTTP;
-import static org.hamcrest.MatcherAssert.assertThat;
-
 import java.io.IOException;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -44,6 +40,11 @@ import org.junit.jupiter.api.Assumptions;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.ArgumentsSource;
 
+import static org.eclipse.jetty.http.client.Transport.H2C;
+import static org.eclipse.jetty.http.client.Transport.HTTP;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
 public class ConnectionStatisticsTest extends AbstractTest
 {
     @Override
@@ -73,7 +74,7 @@ public class ConnectionStatisticsTest extends AbstractTest
         {
             @Override
             public void onOpened(Connection connection)
-            {             
+            {
             }
 
             @Override
@@ -82,7 +83,7 @@ public class ConnectionStatisticsTest extends AbstractTest
                 closed.countDown();
             }
         };
-        
+
         ConnectionStatistics serverStats = new ConnectionStatistics();
         scenario.connector.addBean(serverStats);
         scenario.connector.addBean(closer);
@@ -93,20 +94,20 @@ public class ConnectionStatisticsTest extends AbstractTest
         scenario.client.addBean(closer);
         clientStats.start();
 
-        scenario.client.setIdleTimeout(1000);
+        long idleTimeout = 1000;
+        scenario.client.setIdleTimeout(idleTimeout);
 
         byte[] content = new byte[3072];
         long contentLength = content.length;
         ContentResponse response = scenario.client.newRequest(scenario.newURI())
-                .header(HttpHeader.CONNECTION,"close")
+                .header(HttpHeader.CONNECTION, "close")
                 .content(new BytesContentProvider(content))
                 .timeout(5, TimeUnit.SECONDS)
                 .send();
 
         assertThat(response.getStatus(), Matchers.equalTo(HttpStatus.OK_200));
+        assertTrue(closed.await(2 * idleTimeout, TimeUnit.MILLISECONDS));
 
-        closed.await();
-        
         assertThat(serverStats.getConnectionsMax(), Matchers.greaterThan(0L));
         assertThat(serverStats.getReceivedBytes(), Matchers.greaterThan(contentLength));
         assertThat(serverStats.getSentBytes(), Matchers.greaterThan(contentLength));
diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientContinueTest.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientContinueTest.java
index 082d7de6b9d..28d59edf9db 100644
--- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientContinueTest.java
+++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientContinueTest.java
@@ -327,7 +327,7 @@ public class HttpClientContinueTest extends AbstractTest
     {
         init(transport);
         final long idleTimeout = 1000;
-        scenario.start(new AbstractHandler()
+        scenario.startServer(new AbstractHandler()
         {
             @Override
             public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws ServletException
@@ -343,8 +343,7 @@ public class HttpClientContinueTest extends AbstractTest
                 }
             }
         });
-
-        scenario.client.setIdleTimeout(2 * idleTimeout);
+        scenario.startClient(httpClient -> httpClient.setIdleTimeout(2 * idleTimeout));
 
         byte[] content = new byte[1024];
         final CountDownLatch latch = new CountDownLatch(1);
@@ -375,7 +374,7 @@ public class HttpClientContinueTest extends AbstractTest
     {
         init(transport);
         final long idleTimeout = 1000;
-        scenario.start(new AbstractHandler()
+        scenario.startServer(new AbstractHandler()
         {
             @Override
             public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
@@ -393,8 +392,7 @@ public class HttpClientContinueTest extends AbstractTest
                 }
             }
         });
-
-        scenario.client.setIdleTimeout(idleTimeout);
+        scenario.startClient(httpClient -> httpClient.setIdleTimeout(idleTimeout));
 
         byte[] content = new byte[1024];
         final CountDownLatch latch = new CountDownLatch(1);
diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientIdleTimeoutTest.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientIdleTimeoutTest.java
index 3cde40558bf..6ba09196945 100644
--- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientIdleTimeoutTest.java
+++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientIdleTimeoutTest.java
@@ -18,9 +18,6 @@
 
 package org.eclipse.jetty.http.client;
 
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
 import java.io.IOException;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -37,6 +34,9 @@ import org.eclipse.jetty.server.handler.AbstractHandler;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.ArgumentsSource;
 
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
 public class HttpClientIdleTimeoutTest extends AbstractTest
 {
     private long idleTimeout = 1000;
@@ -52,7 +52,7 @@ public class HttpClientIdleTimeoutTest extends AbstractTest
     public void testClientIdleTimeout(Transport transport) throws Exception
     {
         init(transport);
-        scenario.start(new AbstractHandler()
+        scenario.startServer(new AbstractHandler()
         {
             @Override
             public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
@@ -65,9 +65,7 @@ public class HttpClientIdleTimeoutTest extends AbstractTest
                 }
             }
         });
-        scenario.client.stop();
-        scenario.client.setIdleTimeout(idleTimeout);
-        scenario.client.start();
+        scenario.startClient(httpClient -> httpClient.setIdleTimeout(idleTimeout));
 
         final CountDownLatch latch = new CountDownLatch(1);
         scenario.client.newRequest(scenario.newURI())
@@ -126,10 +124,8 @@ public class HttpClientIdleTimeoutTest extends AbstractTest
     public void testIdleClientIdleTimeout(Transport transport) throws Exception
     {
         init(transport);
-        scenario.start(new EmptyServerHandler());
-        scenario.client.stop();
-        scenario.client.setIdleTimeout(idleTimeout);
-        scenario.client.start();
+        scenario.startServer(new EmptyServerHandler());
+        scenario.startClient(httpClient -> httpClient.setIdleTimeout(idleTimeout));
 
         // Make a first request to open a connection.
         ContentResponse response = scenario.client.newRequest(scenario.newURI()).send();
diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientTest.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientTest.java
index 97eeb3727e8..fc5934c7aeb 100644
--- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientTest.java
+++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientTest.java
@@ -18,15 +18,6 @@
 
 package org.eclipse.jetty.http.client;
 
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.containsString;
-import static org.junit.jupiter.api.Assertions.assertArrayEquals;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.junit.jupiter.api.Assertions.fail;
-
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InterruptedIOException;
@@ -63,6 +54,15 @@ import org.junit.jupiter.api.Assumptions;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.ArgumentsSource;
 
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
 public class HttpClientTest extends AbstractTest
 {
     @Override
@@ -453,7 +453,9 @@ public class HttpClientTest extends AbstractTest
     public void testConnectionListener(Transport transport) throws Exception
     {
         init(transport);
-        scenario.start(new EmptyServerHandler());
+        scenario.startServer(new EmptyServerHandler());
+        long idleTimeout = 1000;
+        scenario.startClient(httpClient -> httpClient.setIdleTimeout(idleTimeout));
 
         CountDownLatch openLatch = new CountDownLatch(1);
         CountDownLatch closeLatch = new CountDownLatch(1);
@@ -472,9 +474,6 @@ public class HttpClientTest extends AbstractTest
             }
         });
 
-        long idleTimeout = 1000;
-        scenario.client.setIdleTimeout(idleTimeout);
-
         ContentResponse response = scenario.client.newRequest(scenario.newURI())
                 .scheme(scenario.getScheme())
                 .timeout(5, TimeUnit.SECONDS)
diff --git a/tests/test-http-client-transport/src/test/resources/jetty-logging.properties b/tests/test-http-client-transport/src/test/resources/jetty-logging.properties
index 1047ac8946d..914cac87711 100644
--- a/tests/test-http-client-transport/src/test/resources/jetty-logging.properties
+++ b/tests/test-http-client-transport/src/test/resources/jetty-logging.properties
@@ -2,6 +2,6 @@ org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
 #org.eclipse.jetty.LEVEL=DEBUG
 #org.eclipse.jetty.client.LEVEL=DEBUG
 #org.eclipse.jetty.http2.LEVEL=DEBUG
-#org.eclipse.jetty.http2.hpack.LEVEL=INFO
+org.eclipse.jetty.http2.hpack.LEVEL=INFO
 #org.eclipse.jetty.http2.client.LEVEL=DEBUG
 #org.eclipse.jetty.io.LEVEL=DEBUG

From 32a6d837395d8f13773a357a30983a97ce6dcc23 Mon Sep 17 00:00:00 2001
From: Simone Bordet 
Date: Fri, 18 Jan 2019 08:48:27 +0100
Subject: [PATCH 02/22] Issue #132 - ClientConnector abstraction.

Added name to default executor and scheduler after review.

Signed-off-by: Simone Bordet 
---
 .../main/java/org/eclipse/jetty/io/ClientConnector.java   | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java
index 5bb069124fb..540c6eeecda 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java
@@ -167,9 +167,13 @@ public class ClientConnector extends ContainerLifeCycle
     protected void doStart() throws Exception
     {
         if (executor == null)
-            setExecutor(new QueuedThreadPool());
+        {
+            QueuedThreadPool clientThreads = new QueuedThreadPool();
+            clientThreads.setName(String.format("client-pool@%x", hashCode()));
+            setExecutor(clientThreads);
+        }
         if (scheduler == null)
-            setScheduler(new ScheduledExecutorScheduler());
+            setScheduler(new ScheduledExecutorScheduler(String.format("client-scheduler@%x", hashCode()), false));
         if (byteBufferPool == null)
             setByteBufferPool(new MappedByteBufferPool());
         if (sslContextFactory == null)

From e135de98fab9776d4c68f7a51a99896fbc33e70d Mon Sep 17 00:00:00 2001
From: Simone Bordet 
Date: Tue, 22 Jan 2019 17:33:29 +0100
Subject: [PATCH 03/22] Issue #132 - ClientConnector abstraction.

Rewrote HttpClientTransportOverUnixSockets in light of ClientConnector.

Signed-off-by: Simone Bordet 
---
 .../AbstractConnectorHttpClientTransport.java |  16 ++
 .../http/HttpClientTransportOverHTTP.java     |  15 --
 .../http/HttpClientTransportOverFCGI.java     |  14 --
 .../org/eclipse/jetty/io/ClientConnector.java |  38 ++--
 .../jetty/unixsocket/UnixSocketEndPoint.java  |  15 +-
 .../HttpClientTransportOverUnixSockets.java   | 176 ++++++------------
 .../jetty/unixsocket/UnixSocketTest.java      |   4 +-
 7 files changed, 103 insertions(+), 175 deletions(-)

diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractConnectorHttpClientTransport.java b/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractConnectorHttpClientTransport.java
index 0c234daa160..efa92c222fa 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractConnectorHttpClientTransport.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractConnectorHttpClientTransport.java
@@ -18,12 +18,14 @@
 
 package org.eclipse.jetty.client;
 
+import java.io.IOException;
 import java.net.InetSocketAddress;
 import java.time.Duration;
 import java.util.Map;
 
 import org.eclipse.jetty.client.api.Connection;
 import org.eclipse.jetty.io.ClientConnector;
+import org.eclipse.jetty.io.EndPoint;
 import org.eclipse.jetty.util.Promise;
 import org.eclipse.jetty.util.annotation.ManagedAttribute;
 import org.eclipse.jetty.util.annotation.ManagedObject;
@@ -75,4 +77,18 @@ public abstract class AbstractConnectorHttpClientTransport extends AbstractHttpC
         context.put(ClientConnector.CONNECTION_PROMISE_CONTEXT_KEY, new Promise.Wrapper<>(promise));
         connector.connect(address, context);
     }
+
+    @Override
+    public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map context) throws IOException
+    {
+        HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
+        @SuppressWarnings("unchecked")
+        Promise promise = (Promise)context.get(HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
+        org.eclipse.jetty.io.Connection connection = newHttpConnection(endPoint, destination, promise);
+        if (LOG.isDebugEnabled())
+            LOG.debug("Created {}", connection);
+        return customize(connection, context);
+    }
+
+    protected abstract org.eclipse.jetty.io.Connection newHttpConnection(EndPoint endPoint, HttpDestination destination, Promise promise);
 }
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 9ef6bd7c49e..52e0c350596 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,9 +18,6 @@
 
 package org.eclipse.jetty.client.http;
 
-import java.io.IOException;
-import java.util.Map;
-
 import org.eclipse.jetty.client.AbstractConnectorHttpClientTransport;
 import org.eclipse.jetty.client.DuplexConnectionPool;
 import org.eclipse.jetty.client.HttpDestination;
@@ -58,18 +55,6 @@ public class HttpClientTransportOverHTTP extends AbstractConnectorHttpClientTran
         return new HttpDestinationOverHTTP(getHttpClient(), origin);
     }
 
-    @Override
-    public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map context) throws IOException
-    {
-        HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
-        @SuppressWarnings("unchecked")
-        Promise promise = (Promise)context.get(HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
-        HttpConnectionOverHTTP connection = newHttpConnection(endPoint, destination, promise);
-        if (LOG.isDebugEnabled())
-            LOG.debug("Created {}", connection);
-        return customize(connection, context);
-    }
-
     protected HttpConnectionOverHTTP newHttpConnection(EndPoint endPoint, HttpDestination destination, Promise promise)
     {
         return new HttpConnectionOverHTTP(endPoint, destination, promise);
diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java
index 2c095ee2dab..455b8db66cc 100644
--- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java
+++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java
@@ -18,8 +18,6 @@
 
 package org.eclipse.jetty.fcgi.client.http;
 
-import java.util.Map;
-
 import org.eclipse.jetty.client.AbstractConnectorHttpClientTransport;
 import org.eclipse.jetty.client.DuplexConnectionPool;
 import org.eclipse.jetty.client.HttpClient;
@@ -76,18 +74,6 @@ public class HttpClientTransportOverFCGI extends AbstractConnectorHttpClientTran
         return new HttpDestinationOverFCGI(getHttpClient(), origin);
     }
 
-    @Override
-    public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map context)
-    {
-        HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
-        @SuppressWarnings("unchecked")
-        Promise promise = (Promise)context.get(HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
-        HttpConnectionOverFCGI connection = newHttpConnection(endPoint, destination, promise);
-        if (LOG.isDebugEnabled())
-            LOG.debug("Created {}", connection);
-        return customize(connection, context);
-    }
-
     protected HttpConnectionOverFCGI newHttpConnection(EndPoint endPoint, HttpDestination destination, Promise promise)
     {
         return new HttpConnectionOverFCGI(endPoint, destination, promise);
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java
index 540c6eeecda..78cd26cfaba 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java
@@ -18,6 +18,7 @@
 
 package org.eclipse.jetty.io;
 
+import java.io.Closeable;
 import java.io.IOException;
 import java.net.SocketAddress;
 import java.net.SocketException;
@@ -251,20 +252,8 @@ public class ClientConnector extends ContainerLifeCycle
             // exception is being thrown, so we attempt to provide a better error message.
             if (x.getClass() == SocketException.class)
                 x = new SocketException("Could not connect to " + address).initCause(x);
-
-            try
-            {
-                if (channel != null)
-                    channel.close();
-            }
-            catch (IOException xx)
-            {
-                LOG.ignore(xx);
-            }
-            finally
-            {
-                connectFailed(x, context);
-            }
+            safeClose(channel);
+            connectFailed(x, context);
         }
     }
 
@@ -273,7 +262,6 @@ public class ClientConnector extends ContainerLifeCycle
         try
         {
             context.put(ClientConnector.CLIENT_CONNECTOR_CONTEXT_KEY, this);
-
             if (!channel.isConnected())
                 throw new IllegalStateException("SocketChannel must be connected");
             configure(channel);
@@ -284,17 +272,31 @@ public class ClientConnector extends ContainerLifeCycle
         {
             if (LOG.isDebugEnabled())
                 LOG.debug("Could not accept {}", channel);
+            safeClose(channel);
             Promise promise = (Promise)context.get(CONNECTION_PROMISE_CONTEXT_KEY);
             promise.failed(failure);
         }
     }
 
+    protected void safeClose(Closeable closeable)
+    {
+        try
+        {
+            if (closeable != null)
+                closeable.close();
+        }
+        catch (Throwable x)
+        {
+            LOG.ignore(x);
+        }
+    }
+
     protected void configure(SocketChannel channel) throws IOException
     {
         channel.socket().setTcpNoDelay(true);
     }
 
-    private void connectFailed(Throwable failure, Map context)
+    protected void connectFailed(Throwable failure, Map context)
     {
         if (LOG.isDebugEnabled())
             LOG.debug("Could not connect to {}", context.get(SOCKET_ADDRESS_CONTEXT_KEY));
@@ -302,9 +304,9 @@ public class ClientConnector extends ContainerLifeCycle
         promise.failed(failure);
     }
 
-    private class ClientSelectorManager extends SelectorManager
+    protected class ClientSelectorManager extends SelectorManager
     {
-        private ClientSelectorManager(Executor executor, Scheduler scheduler, int selectors)
+        protected ClientSelectorManager(Executor executor, Scheduler scheduler, int selectors)
         {
             super(executor, scheduler, selectors);
         }
diff --git a/jetty-unixsocket/src/main/java/org/eclipse/jetty/unixsocket/UnixSocketEndPoint.java b/jetty-unixsocket/src/main/java/org/eclipse/jetty/unixsocket/UnixSocketEndPoint.java
index 3556b73c390..57af91a5110 100644
--- a/jetty-unixsocket/src/main/java/org/eclipse/jetty/unixsocket/UnixSocketEndPoint.java
+++ b/jetty-unixsocket/src/main/java/org/eclipse/jetty/unixsocket/UnixSocketEndPoint.java
@@ -20,31 +20,25 @@ package org.eclipse.jetty.unixsocket;
 
 import java.io.IOException;
 import java.net.InetSocketAddress;
-import java.nio.ByteBuffer;
 import java.nio.channels.SelectionKey;
 
+import jnr.unixsocket.UnixSocketChannel;
 import org.eclipse.jetty.io.ChannelEndPoint;
-import org.eclipse.jetty.io.EofException;
 import org.eclipse.jetty.io.ManagedSelector;
-import org.eclipse.jetty.util.BufferUtil;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 import org.eclipse.jetty.util.thread.Scheduler;
 
-import jnr.unixsocket.UnixSocketChannel;
-
 public class UnixSocketEndPoint extends ChannelEndPoint
 {
     private static final Logger LOG = Log.getLogger(UnixSocketEndPoint.class);
-    private static final Logger CEPLOG = Log.getLogger(ChannelEndPoint.class);
-
 
     private final UnixSocketChannel _channel;
-    
+
     public UnixSocketEndPoint(UnixSocketChannel channel, ManagedSelector selector, SelectionKey key, Scheduler scheduler)
     {
-        super(channel,selector,key,scheduler);
-        _channel=channel;
+        super(channel, selector, key, scheduler);
+        _channel = channel;
     }
 
     @Override
@@ -59,7 +53,6 @@ public class UnixSocketEndPoint extends ChannelEndPoint
         return null;
     }
 
-    
     @Override
     protected void doShutdownOutput()
     {
diff --git a/jetty-unixsocket/src/main/java/org/eclipse/jetty/unixsocket/client/HttpClientTransportOverUnixSockets.java b/jetty-unixsocket/src/main/java/org/eclipse/jetty/unixsocket/client/HttpClientTransportOverUnixSockets.java
index aa2c77463a8..be94307618e 100644
--- a/jetty-unixsocket/src/main/java/org/eclipse/jetty/unixsocket/client/HttpClientTransportOverUnixSockets.java
+++ b/jetty-unixsocket/src/main/java/org/eclipse/jetty/unixsocket/client/HttpClientTransportOverUnixSockets.java
@@ -19,25 +19,28 @@
 package org.eclipse.jetty.unixsocket.client;
 
 import java.io.IOException;
+import java.net.ConnectException;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
+import java.net.SocketAddress;
 import java.nio.channels.SelectableChannel;
 import java.nio.channels.SelectionKey;
 import java.nio.channels.Selector;
 import java.nio.channels.SocketChannel;
 import java.util.Map;
+import java.util.concurrent.Executor;
 
 import jnr.enxio.channels.NativeSelectorProvider;
 import jnr.unixsocket.UnixSocketAddress;
 import jnr.unixsocket.UnixSocketChannel;
-import org.eclipse.jetty.client.AbstractHttpClientTransport;
+import org.eclipse.jetty.client.AbstractConnectorHttpClientTransport;
 import org.eclipse.jetty.client.DuplexConnectionPool;
 import org.eclipse.jetty.client.HttpClient;
 import org.eclipse.jetty.client.HttpDestination;
 import org.eclipse.jetty.client.Origin;
 import org.eclipse.jetty.client.http.HttpConnectionOverHTTP;
 import org.eclipse.jetty.client.http.HttpDestinationOverHTTP;
-import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.ClientConnector;
 import org.eclipse.jetty.io.EndPoint;
 import org.eclipse.jetty.io.ManagedSelector;
 import org.eclipse.jetty.io.SelectorManager;
@@ -45,22 +48,21 @@ import org.eclipse.jetty.unixsocket.UnixSocketEndPoint;
 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.thread.Scheduler;
 
 // TODO: this class needs a thorough review.
-public class HttpClientTransportOverUnixSockets extends AbstractHttpClientTransport
+public class HttpClientTransportOverUnixSockets extends AbstractConnectorHttpClientTransport
 {
     private static final Logger LOG = Log.getLogger(HttpClientTransportOverUnixSockets.class);
 
-    private String _unixSocket;
-    private SelectorManager selectorManager;
-
-    private UnixSocketChannel channel;
-
     public HttpClientTransportOverUnixSockets(String unixSocket)
     {
-        if (unixSocket == null)
-            throw new IllegalArgumentException("Unix socket file cannot be null");
-        this._unixSocket = unixSocket;
+        this(new UnixSocketClientConnector(unixSocket));
+    }
+
+    private HttpClientTransportOverUnixSockets(ClientConnector connector)
+    {
+        super(connector);
         setConnectionPoolFactory(destination ->
         {
             HttpClient httpClient = getHttpClient();
@@ -69,136 +71,80 @@ public class HttpClientTransportOverUnixSockets extends AbstractHttpClientTransp
         });
     }
 
-    @Override
-    protected void doStart() throws Exception
-    {
-        HttpClient httpClient = getHttpClient();
-        selectorManager = new UnixSocketSelectorManager(httpClient, 1);
-        selectorManager.setConnectTimeout(httpClient.getConnectTimeout());
-        addBean(selectorManager);
-        super.doStart();
-    }
-
-    @Override
-    protected void doStop() throws Exception
-    {
-        super.doStop();
-        try
-        {
-            if (channel != null)
-                channel.close();
-        }
-        catch (IOException xx)
-        {
-            LOG.ignore(xx);
-        }
-    }
-
     @Override
     public HttpDestination newHttpDestination(Origin origin)
     {
         return new HttpDestinationOverHTTP(getHttpClient(), origin);
     }
 
-    @Override
-    public void connect(InetSocketAddress address, Map context)
-    {
-        try
-        {
-            InetAddress inet = address.getAddress();
-            if (!inet.isLoopbackAddress() && !inet.isLinkLocalAddress() && !inet.isSiteLocalAddress())
-                throw new IOException("UnixSocket cannot connect to " + address.getHostString());
-
-            // Open a unix socket
-            UnixSocketAddress unixAddress = new UnixSocketAddress(this._unixSocket);
-            channel = UnixSocketChannel.open(unixAddress);
-
-            HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
-            HttpClient client = destination.getHttpClient();
-
-            configure(client, channel);
-
-            channel.configureBlocking(false);
-            selectorManager.accept(channel, context);
-        }
-        // Must catch all exceptions, since some like
-        // UnresolvedAddressException are not IOExceptions.
-        catch (Throwable x)
-        {
-            try
-            {
-                if (channel != null)
-                    channel.close();
-            }
-            catch (IOException xx)
-            {
-                LOG.ignore(xx);
-            }
-            finally
-            {
-                connectFailed(context, x);
-            }
-        }
-    }
-
-    @Override
-    public Connection newConnection(EndPoint endPoint, Map context)
-    {
-        HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
-        @SuppressWarnings("unchecked")
-        Promise promise = (Promise)context.get(HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
-        HttpConnectionOverHTTP connection = newHttpConnection(endPoint, destination, promise);
-        if (LOG.isDebugEnabled())
-            LOG.debug("Created {}", connection);
-        return customize(connection, context);
-    }
-
     protected HttpConnectionOverHTTP newHttpConnection(EndPoint endPoint, HttpDestination destination, Promise promise)
     {
         return new HttpConnectionOverHTTP(endPoint, destination, promise);
     }
 
-    protected void configure(HttpClient client, SocketChannel channel) throws IOException
+    private static class UnixSocketClientConnector extends ClientConnector
     {
-        channel.socket().setTcpNoDelay(client.isTCPNoDelay());
-    }
+        private final String unixSocket;
 
-    public class UnixSocketSelectorManager extends SelectorManager
-    {
-        protected UnixSocketSelectorManager(HttpClient client, int selectors)
+        private UnixSocketClientConnector(String unixSocket)
         {
-            super(client.getExecutor(), client.getScheduler(), selectors);
+            this.unixSocket = unixSocket;
         }
 
         @Override
-        protected Selector newSelector() throws IOException
+        protected SelectorManager newSelectorManager()
         {
-            return NativeSelectorProvider.getInstance().openSelector();
+            return new UnixSocketSelectorManager(getExecutor(), getScheduler(), getSelectors());
         }
 
         @Override
-        protected EndPoint newEndPoint(SelectableChannel channel, ManagedSelector selector, SelectionKey key)
+        public void connect(SocketAddress address, Map context)
         {
-            UnixSocketEndPoint endPoint = new UnixSocketEndPoint((UnixSocketChannel)channel, selector, key, getScheduler());
-            endPoint.setIdleTimeout(getHttpClient().getIdleTimeout());
-            return endPoint;
+            InetSocketAddress socketAddress = (InetSocketAddress)address;
+            InetAddress inetAddress = socketAddress.getAddress();
+            if (inetAddress.isLoopbackAddress() || inetAddress.isLinkLocalAddress() || inetAddress.isSiteLocalAddress())
+            {
+                SocketChannel channel = null;
+                try
+                {
+                    UnixSocketAddress unixAddress = new UnixSocketAddress(unixSocket);
+                    channel = UnixSocketChannel.open(unixAddress);
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("Created {} for {}", channel, unixAddress);
+                    accept(channel, context);
+                }
+                catch (Throwable x)
+                {
+                    safeClose(channel);
+                    connectFailed(x, context);
+                }
+            }
+            else
+            {
+                connectFailed(new ConnectException("UnixSocket cannot connect to " + socketAddress.getHostString()), context);
+            }
         }
 
-        @Override
-        public Connection newConnection(SelectableChannel channel, EndPoint endPoint, Object attachment) throws IOException
+        private class UnixSocketSelectorManager extends ClientSelectorManager
         {
-            @SuppressWarnings("unchecked")
-            Map context = (Map)attachment;
-            HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
-            return destination.getClientConnectionFactory().newConnection(endPoint, context);
-        }
+            private UnixSocketSelectorManager(Executor executor, Scheduler scheduler, int selectors)
+            {
+                super(executor, scheduler, selectors);
+            }
 
-        @Override
-        protected void connectionFailed(SelectableChannel channel, Throwable x, Object attachment)
-        {
-            @SuppressWarnings("unchecked")
-            Map context = (Map)attachment;
-            connectFailed(context, x);
+            @Override
+            protected Selector newSelector() throws IOException
+            {
+                return NativeSelectorProvider.getInstance().openSelector();
+            }
+
+            @Override
+            protected EndPoint newEndPoint(SelectableChannel channel, ManagedSelector selector, SelectionKey key)
+            {
+                UnixSocketEndPoint endPoint = new UnixSocketEndPoint((UnixSocketChannel)channel, selector, key, getScheduler());
+                endPoint.setIdleTimeout(getIdleTimeout().toMillis());
+                return endPoint;
+            }
         }
     }
 }
diff --git a/jetty-unixsocket/src/test/java/org/eclipse/jetty/unixsocket/UnixSocketTest.java b/jetty-unixsocket/src/test/java/org/eclipse/jetty/unixsocket/UnixSocketTest.java
index 3481fcb0daf..b7b8dddf8c9 100644
--- a/jetty-unixsocket/src/test/java/org/eclipse/jetty/unixsocket/UnixSocketTest.java
+++ b/jetty-unixsocket/src/test/java/org/eclipse/jetty/unixsocket/UnixSocketTest.java
@@ -20,6 +20,7 @@ package org.eclipse.jetty.unixsocket;
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.net.ConnectException;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -146,7 +147,6 @@ public class UnixSocketTest
         httpClient.start();
 
         ExecutionException e = assertThrows(ExecutionException.class, () -> httpClient.newRequest("http://google.com").send());
-        assertThat(e.getCause(), instanceOf(IOException.class));
-        assertThat(e.getCause().getMessage(), containsString("UnixSocket cannot connect to google.com"));
+        assertThat(e.getCause(), instanceOf(ConnectException.class));
     }
 }

From 20f438d43a2f1690a81560c27d0376d731600771 Mon Sep 17 00:00:00 2001
From: Simone Bordet 
Date: Tue, 22 Jan 2019 17:44:06 +0100
Subject: [PATCH 04/22] Issue #132 - ClientConnector abstraction.

Reverted refactoring of newConnection() to avoid
to bind the class to a too specific abstract method.

Signed-off-by: Simone Bordet 
---
 .../AbstractConnectorHttpClientTransport.java    | 16 ----------------
 .../client/http/HttpClientTransportOverHTTP.java | 15 +++++++++++++++
 .../client/http/HttpClientTransportOverFCGI.java | 15 +++++++++++++++
 .../HttpClientTransportOverUnixSockets.java      | 13 +++++++++++++
 4 files changed, 43 insertions(+), 16 deletions(-)

diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractConnectorHttpClientTransport.java b/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractConnectorHttpClientTransport.java
index efa92c222fa..0c234daa160 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractConnectorHttpClientTransport.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractConnectorHttpClientTransport.java
@@ -18,14 +18,12 @@
 
 package org.eclipse.jetty.client;
 
-import java.io.IOException;
 import java.net.InetSocketAddress;
 import java.time.Duration;
 import java.util.Map;
 
 import org.eclipse.jetty.client.api.Connection;
 import org.eclipse.jetty.io.ClientConnector;
-import org.eclipse.jetty.io.EndPoint;
 import org.eclipse.jetty.util.Promise;
 import org.eclipse.jetty.util.annotation.ManagedAttribute;
 import org.eclipse.jetty.util.annotation.ManagedObject;
@@ -77,18 +75,4 @@ public abstract class AbstractConnectorHttpClientTransport extends AbstractHttpC
         context.put(ClientConnector.CONNECTION_PROMISE_CONTEXT_KEY, new Promise.Wrapper<>(promise));
         connector.connect(address, context);
     }
-
-    @Override
-    public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map context) throws IOException
-    {
-        HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
-        @SuppressWarnings("unchecked")
-        Promise promise = (Promise)context.get(HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
-        org.eclipse.jetty.io.Connection connection = newHttpConnection(endPoint, destination, promise);
-        if (LOG.isDebugEnabled())
-            LOG.debug("Created {}", connection);
-        return customize(connection, context);
-    }
-
-    protected abstract org.eclipse.jetty.io.Connection newHttpConnection(EndPoint endPoint, HttpDestination destination, Promise promise);
 }
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 52e0c350596..ddfacc5f67a 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,6 +18,9 @@
 
 package org.eclipse.jetty.client.http;
 
+import java.io.IOException;
+import java.util.Map;
+
 import org.eclipse.jetty.client.AbstractConnectorHttpClientTransport;
 import org.eclipse.jetty.client.DuplexConnectionPool;
 import org.eclipse.jetty.client.HttpDestination;
@@ -55,6 +58,18 @@ public class HttpClientTransportOverHTTP extends AbstractConnectorHttpClientTran
         return new HttpDestinationOverHTTP(getHttpClient(), origin);
     }
 
+    @Override
+    public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map context) throws IOException
+    {
+        HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
+        @SuppressWarnings("unchecked")
+        Promise promise = (Promise)context.get(HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
+        org.eclipse.jetty.io.Connection connection = newHttpConnection(endPoint, destination, promise);
+        if (LOG.isDebugEnabled())
+            LOG.debug("Created {}", connection);
+        return customize(connection, context);
+    }
+
     protected HttpConnectionOverHTTP newHttpConnection(EndPoint endPoint, HttpDestination destination, Promise promise)
     {
         return new HttpConnectionOverHTTP(endPoint, destination, promise);
diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java
index 455b8db66cc..5a641c14999 100644
--- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java
+++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java
@@ -18,6 +18,9 @@
 
 package org.eclipse.jetty.fcgi.client.http;
 
+import java.io.IOException;
+import java.util.Map;
+
 import org.eclipse.jetty.client.AbstractConnectorHttpClientTransport;
 import org.eclipse.jetty.client.DuplexConnectionPool;
 import org.eclipse.jetty.client.HttpClient;
@@ -74,6 +77,18 @@ public class HttpClientTransportOverFCGI extends AbstractConnectorHttpClientTran
         return new HttpDestinationOverFCGI(getHttpClient(), origin);
     }
 
+    @Override
+    public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map context) throws IOException
+    {
+        HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
+        @SuppressWarnings("unchecked")
+        Promise promise = (Promise)context.get(HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
+        org.eclipse.jetty.io.Connection connection = newHttpConnection(endPoint, destination, promise);
+        if (LOG.isDebugEnabled())
+            LOG.debug("Created {}", connection);
+        return customize(connection, context);
+    }
+
     protected HttpConnectionOverFCGI newHttpConnection(EndPoint endPoint, HttpDestination destination, Promise promise)
     {
         return new HttpConnectionOverFCGI(endPoint, destination, promise);
diff --git a/jetty-unixsocket/src/main/java/org/eclipse/jetty/unixsocket/client/HttpClientTransportOverUnixSockets.java b/jetty-unixsocket/src/main/java/org/eclipse/jetty/unixsocket/client/HttpClientTransportOverUnixSockets.java
index be94307618e..36ea14e3bae 100644
--- a/jetty-unixsocket/src/main/java/org/eclipse/jetty/unixsocket/client/HttpClientTransportOverUnixSockets.java
+++ b/jetty-unixsocket/src/main/java/org/eclipse/jetty/unixsocket/client/HttpClientTransportOverUnixSockets.java
@@ -38,6 +38,7 @@ import org.eclipse.jetty.client.DuplexConnectionPool;
 import org.eclipse.jetty.client.HttpClient;
 import org.eclipse.jetty.client.HttpDestination;
 import org.eclipse.jetty.client.Origin;
+import org.eclipse.jetty.client.api.Connection;
 import org.eclipse.jetty.client.http.HttpConnectionOverHTTP;
 import org.eclipse.jetty.client.http.HttpDestinationOverHTTP;
 import org.eclipse.jetty.io.ClientConnector;
@@ -77,6 +78,18 @@ public class HttpClientTransportOverUnixSockets extends AbstractConnectorHttpCli
         return new HttpDestinationOverHTTP(getHttpClient(), origin);
     }
 
+    @Override
+    public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map context) throws IOException
+    {
+        HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
+        @SuppressWarnings("unchecked")
+        Promise promise = (Promise)context.get(HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
+        org.eclipse.jetty.io.Connection connection = newHttpConnection(endPoint, destination, promise);
+        if (LOG.isDebugEnabled())
+            LOG.debug("Created {}", connection);
+        return customize(connection, context);
+    }
+
     protected HttpConnectionOverHTTP newHttpConnection(EndPoint endPoint, HttpDestination destination, Promise promise)
     {
         return new HttpConnectionOverHTTP(endPoint, destination, promise);

From b4bd5980aaf685503f953faa07ba3fe68ab81326 Mon Sep 17 00:00:00 2001
From: Lachlan Roberts 
Date: Wed, 30 Jan 2019 10:00:56 +1100
Subject: [PATCH 05/22] Issue #3298 - Refactor of upgrade request class names

Signed-off-by: Lachlan Roberts 
---
 ...> DelegatedJavaxClientUpgradeRequest.java} | 11 +--
 ... DelegatedJavaxClientUpgradeResponse.java} | 12 +--
 ...pl.java => JavaxClientUpgradeRequest.java} | 18 ++--
 .../client/JavaxWebSocketClientContainer.java |  4 +-
 .../javax/common/JavaxWebSocketContainer.java | 19 +++--
 .../JavaxWebSocketFrameHandlerFactory.java    | 56 ++++++------
 ...bstractJavaxWebSocketFrameHandlerTest.java |  2 +-
 ...vaxWebSocketServerFrameHandlerFactory.java | 27 +++---
 ... DelegatedJavaxServletUpgradeRequest.java} | 10 +--
 .../websocket/javax/tests/NetworkFuzzer.java  |  4 +-
 .../client/SessionAddMessageHandlerTest.java  |  2 +-
 .../javax/tests/server/ConfiguratorTest.java  | 85 ++++++++++---------
 .../websocket/client/WebSocketClient.java     |  4 +-
 ...> DelegatedJettyClientUpgradeRequest.java} | 23 ++---
 ... DelegatedJettyClientUpgradeResponse.java} | 14 +--
 ...pl.java => JettyClientUpgradeRequest.java} | 25 +++---
 .../JettyServerFrameHandlerFactory.java       |  4 +-
 ... DelegatedJettyServletUpgradeRequest.java} | 12 +--
 .../jetty/websocket/core/FrameHandler.java    |  6 +-
 ...Request.java => ClientUpgradeRequest.java} | 10 +--
 .../core/client/WebSocketCoreClient.java      |  4 +-
 .../core/chat/ChatWebSocketClient.java        | 16 ++--
 .../client/WebSocketClientServerTest.java     |  2 +-
 23 files changed, 190 insertions(+), 180 deletions(-)
 rename jetty-websocket/javax-websocket-client/src/main/java/org/eclipse/jetty/websocket/javax/client/{DelegatedClientUpgradeRequest.java => DelegatedJavaxClientUpgradeRequest.java} (84%)
 rename jetty-websocket/javax-websocket-client/src/main/java/org/eclipse/jetty/websocket/javax/client/{DelegatedClientUpgradeResponse.java => DelegatedJavaxClientUpgradeResponse.java} (93%)
 rename jetty-websocket/javax-websocket-client/src/main/java/org/eclipse/jetty/websocket/javax/client/{ClientUpgradeRequestImpl.java => JavaxClientUpgradeRequest.java} (82%)
 rename jetty-websocket/javax-websocket-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/{UpgradeRequestAdapter.java => DelegatedJavaxServletUpgradeRequest.java} (89%)
 rename jetty-websocket/jetty-websocket-client/src/main/java/org/eclipse/jetty/websocket/client/impl/{DelegatedClientUpgradeRequest.java => DelegatedJettyClientUpgradeRequest.java} (95%)
 rename jetty-websocket/jetty-websocket-client/src/main/java/org/eclipse/jetty/websocket/client/impl/{DelegatedClientUpgradeResponse.java => DelegatedJettyClientUpgradeResponse.java} (95%)
 rename jetty-websocket/jetty-websocket-client/src/main/java/org/eclipse/jetty/websocket/client/impl/{ClientUpgradeRequestImpl.java => JettyClientUpgradeRequest.java} (86%)
 rename jetty-websocket/jetty-websocket-server/src/main/java/org/eclipse/jetty/websocket/server/internal/{UpgradeRequestAdapter.java => DelegatedJettyServletUpgradeRequest.java} (97%)
 rename jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/client/{UpgradeRequest.java => ClientUpgradeRequest.java} (97%)

diff --git a/jetty-websocket/javax-websocket-client/src/main/java/org/eclipse/jetty/websocket/javax/client/DelegatedClientUpgradeRequest.java b/jetty-websocket/javax-websocket-client/src/main/java/org/eclipse/jetty/websocket/javax/client/DelegatedJavaxClientUpgradeRequest.java
similarity index 84%
rename from jetty-websocket/javax-websocket-client/src/main/java/org/eclipse/jetty/websocket/javax/client/DelegatedClientUpgradeRequest.java
rename to jetty-websocket/javax-websocket-client/src/main/java/org/eclipse/jetty/websocket/javax/client/DelegatedJavaxClientUpgradeRequest.java
index 8b883c86ace..d09d7e0f793 100644
--- a/jetty-websocket/javax-websocket-client/src/main/java/org/eclipse/jetty/websocket/javax/client/DelegatedClientUpgradeRequest.java
+++ b/jetty-websocket/javax-websocket-client/src/main/java/org/eclipse/jetty/websocket/javax/client/DelegatedJavaxClientUpgradeRequest.java
@@ -18,20 +18,21 @@
 
 package org.eclipse.jetty.websocket.javax.client;
 
-import org.eclipse.jetty.websocket.javax.common.UpgradeRequest;
-
 import java.net.URI;
 import java.security.Principal;
 
+import org.eclipse.jetty.websocket.core.client.ClientUpgradeRequest;
+import org.eclipse.jetty.websocket.javax.common.UpgradeRequest;
+
 /**
  * Representing the Jetty {@link org.eclipse.jetty.client.HttpClient}'s {@link org.eclipse.jetty.client.HttpRequest}
  * in the {@link UpgradeRequest} interface.
  */
-public class DelegatedClientUpgradeRequest implements UpgradeRequest
+public class DelegatedJavaxClientUpgradeRequest implements UpgradeRequest
 {
-    private final org.eclipse.jetty.websocket.core.client.UpgradeRequest delegate;
+    private final ClientUpgradeRequest delegate;
 
-    public DelegatedClientUpgradeRequest(org.eclipse.jetty.websocket.core.client.UpgradeRequest delegate)
+    public DelegatedJavaxClientUpgradeRequest(ClientUpgradeRequest delegate)
     {
         this.delegate = delegate;
     }
diff --git a/jetty-websocket/javax-websocket-client/src/main/java/org/eclipse/jetty/websocket/javax/client/DelegatedClientUpgradeResponse.java b/jetty-websocket/javax-websocket-client/src/main/java/org/eclipse/jetty/websocket/javax/client/DelegatedJavaxClientUpgradeResponse.java
similarity index 93%
rename from jetty-websocket/javax-websocket-client/src/main/java/org/eclipse/jetty/websocket/javax/client/DelegatedClientUpgradeResponse.java
rename to jetty-websocket/javax-websocket-client/src/main/java/org/eclipse/jetty/websocket/javax/client/DelegatedJavaxClientUpgradeResponse.java
index f2646dea56c..2709aa88746 100644
--- a/jetty-websocket/javax-websocket-client/src/main/java/org/eclipse/jetty/websocket/javax/client/DelegatedClientUpgradeResponse.java
+++ b/jetty-websocket/javax-websocket-client/src/main/java/org/eclipse/jetty/websocket/javax/client/DelegatedJavaxClientUpgradeResponse.java
@@ -18,24 +18,24 @@
 
 package org.eclipse.jetty.websocket.javax.client;
 
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
 import org.eclipse.jetty.client.HttpResponse;
 import org.eclipse.jetty.http.HttpHeader;
 import org.eclipse.jetty.websocket.core.ExtensionConfig;
 import org.eclipse.jetty.websocket.javax.common.UpgradeResponse;
 
-import java.util.Collections;
-import java.util.List;
-import java.util.stream.Collectors;
-
 /**
  * Representing the Jetty {@link org.eclipse.jetty.client.HttpClient}'s {@link HttpResponse}
  * in the {@link UpgradeResponse} interface.
  */
-public class DelegatedClientUpgradeResponse implements UpgradeResponse
+public class DelegatedJavaxClientUpgradeResponse implements UpgradeResponse
 {
     private HttpResponse delegate;
 
-    public DelegatedClientUpgradeResponse(HttpResponse response)
+    public DelegatedJavaxClientUpgradeResponse(HttpResponse response)
     {
         this.delegate = response;
     }
diff --git a/jetty-websocket/javax-websocket-client/src/main/java/org/eclipse/jetty/websocket/javax/client/ClientUpgradeRequestImpl.java b/jetty-websocket/javax-websocket-client/src/main/java/org/eclipse/jetty/websocket/javax/client/JavaxClientUpgradeRequest.java
similarity index 82%
rename from jetty-websocket/javax-websocket-client/src/main/java/org/eclipse/jetty/websocket/javax/client/ClientUpgradeRequestImpl.java
rename to jetty-websocket/javax-websocket-client/src/main/java/org/eclipse/jetty/websocket/javax/client/JavaxClientUpgradeRequest.java
index e11ef886f69..ad338145666 100644
--- a/jetty-websocket/javax-websocket-client/src/main/java/org/eclipse/jetty/websocket/javax/client/ClientUpgradeRequestImpl.java
+++ b/jetty-websocket/javax-websocket-client/src/main/java/org/eclipse/jetty/websocket/javax/client/JavaxClientUpgradeRequest.java
@@ -18,24 +18,26 @@
 
 package org.eclipse.jetty.websocket.javax.client;
 
+import java.net.URI;
+import java.util.concurrent.CompletableFuture;
+
+import javax.websocket.Session;
+
 import org.eclipse.jetty.client.HttpResponse;
 import org.eclipse.jetty.websocket.core.FrameHandler;
+import org.eclipse.jetty.websocket.core.client.ClientUpgradeRequest;
 import org.eclipse.jetty.websocket.core.client.WebSocketCoreClient;
 import org.eclipse.jetty.websocket.javax.common.JavaxWebSocketFrameHandler;
 import org.eclipse.jetty.websocket.javax.common.UpgradeRequest;
 import org.eclipse.jetty.websocket.javax.common.UpgradeResponse;
 
-import javax.websocket.Session;
-import java.net.URI;
-import java.util.concurrent.CompletableFuture;
-
-public class ClientUpgradeRequestImpl extends org.eclipse.jetty.websocket.core.client.UpgradeRequest
+public class JavaxClientUpgradeRequest extends ClientUpgradeRequest
 {
     private final JavaxWebSocketClientContainer containerContext;
     private final Object websocketPojo;
     private final CompletableFuture futureJavaxSession;
 
-    public ClientUpgradeRequestImpl(JavaxWebSocketClientContainer clientContainer, WebSocketCoreClient coreClient, URI requestURI, Object websocketPojo)
+    public JavaxClientUpgradeRequest(JavaxWebSocketClientContainer clientContainer, WebSocketCoreClient coreClient, URI requestURI, Object websocketPojo)
     {
         super(coreClient, requestURI);
         this.containerContext = clientContainer;
@@ -53,8 +55,8 @@ public class ClientUpgradeRequestImpl extends org.eclipse.jetty.websocket.core.c
     @Override
     public FrameHandler getFrameHandler(WebSocketCoreClient coreClient, HttpResponse response)
     {
-        UpgradeRequest upgradeRequest = new DelegatedClientUpgradeRequest(this);
-        UpgradeResponse upgradeResponse = new DelegatedClientUpgradeResponse(response);
+        UpgradeRequest upgradeRequest = new DelegatedJavaxClientUpgradeRequest(this);
+        UpgradeResponse upgradeResponse = new DelegatedJavaxClientUpgradeResponse(response);
 
         JavaxWebSocketFrameHandler frameHandler = containerContext.newFrameHandler(websocketPojo, upgradeRequest, upgradeResponse, futureJavaxSession);
 
diff --git a/jetty-websocket/javax-websocket-client/src/main/java/org/eclipse/jetty/websocket/javax/client/JavaxWebSocketClientContainer.java b/jetty-websocket/javax-websocket-client/src/main/java/org/eclipse/jetty/websocket/javax/client/JavaxWebSocketClientContainer.java
index e43a2fec36a..9d0b128e01e 100644
--- a/jetty-websocket/javax-websocket-client/src/main/java/org/eclipse/jetty/websocket/javax/client/JavaxWebSocketClientContainer.java
+++ b/jetty-websocket/javax-websocket-client/src/main/java/org/eclipse/jetty/websocket/javax/client/JavaxWebSocketClientContainer.java
@@ -125,7 +125,7 @@ public class JavaxWebSocketClientContainer extends JavaxWebSocketContainer imple
      * @param upgradeRequest the upgrade request information
      * @return the future for the session, available on success of connect
      */
-    private CompletableFuture connect(ClientUpgradeRequestImpl upgradeRequest)
+    private CompletableFuture connect(JavaxClientUpgradeRequest upgradeRequest)
     {
         CompletableFuture fut = upgradeRequest.getFutureSession();
         try
@@ -145,7 +145,7 @@ public class JavaxWebSocketClientContainer extends JavaxWebSocketContainer imple
         Objects.requireNonNull(configuredEndpoint, "WebSocket configured endpoint cannot be null");
         Objects.requireNonNull(destURI, "Destination URI cannot be null");
 
-        ClientUpgradeRequestImpl upgradeRequest = new ClientUpgradeRequestImpl(this, getWebSocketCoreClient(), destURI, configuredEndpoint);
+        JavaxClientUpgradeRequest upgradeRequest = new JavaxClientUpgradeRequest(this, getWebSocketCoreClient(), destURI, configuredEndpoint);
 
         EndpointConfig config = configuredEndpoint.getConfig();
         if (config != null && config instanceof ClientEndpointConfig)
diff --git a/jetty-websocket/javax-websocket-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketContainer.java b/jetty-websocket/javax-websocket-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketContainer.java
index ffd283a8cf4..5f91aa9d13c 100644
--- a/jetty-websocket/javax-websocket-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketContainer.java
+++ b/jetty-websocket/javax-websocket-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketContainer.java
@@ -18,19 +18,20 @@
 
 package org.eclipse.jetty.websocket.javax.common;
 
-import org.eclipse.jetty.io.ByteBufferPool;
-import org.eclipse.jetty.util.DecoratedObjectFactory;
-import org.eclipse.jetty.util.component.ContainerLifeCycle;
-import org.eclipse.jetty.websocket.core.WebSocketExtensionRegistry;
-
-import javax.websocket.Extension;
-import javax.websocket.Session;
-import javax.websocket.WebSocketContainer;
 import java.util.HashSet;
 import java.util.Set;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.Executor;
 
+import javax.websocket.Extension;
+import javax.websocket.Session;
+import javax.websocket.WebSocketContainer;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.util.DecoratedObjectFactory;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.websocket.core.WebSocketExtensionRegistry;
+
 public abstract class JavaxWebSocketContainer extends ContainerLifeCycle implements javax.websocket.WebSocketContainer
 {
     private long defaultAsyncSendTimeout = -1;
@@ -105,7 +106,7 @@ public abstract class JavaxWebSocketContainer extends ContainerLifeCycle impleme
     public JavaxWebSocketFrameHandler newFrameHandler(Object websocketPojo, UpgradeRequest upgradeRequest, UpgradeResponse upgradeResponse,
         CompletableFuture futureSession)
     {
-        return getFrameHandlerFactory().newJavaxFrameHandler(websocketPojo, upgradeRequest, upgradeResponse, futureSession);
+        return getFrameHandlerFactory().newJavaxWebSocketFrameHandler(websocketPojo, upgradeRequest, upgradeResponse, futureSession);
     }
 
     @Override
diff --git a/jetty-websocket/javax-websocket-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketFrameHandlerFactory.java b/jetty-websocket/javax-websocket-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketFrameHandlerFactory.java
index 6692a5d6a06..778b3a3d695 100644
--- a/jetty-websocket/javax-websocket-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketFrameHandlerFactory.java
+++ b/jetty-websocket/javax-websocket-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketFrameHandlerFactory.java
@@ -18,6 +18,32 @@
 
 package org.eclipse.jetty.websocket.javax.common;
 
+import java.io.InputStream;
+import java.io.Reader;
+import java.lang.annotation.Annotation;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.websocket.CloseReason;
+import javax.websocket.Decoder;
+import javax.websocket.EndpointConfig;
+import javax.websocket.OnClose;
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.PongMessage;
+import javax.websocket.Session;
+
 import org.eclipse.jetty.http.pathmap.UriTemplatePathSpec;
 import org.eclipse.jetty.websocket.javax.common.decoders.AvailableDecoders;
 import org.eclipse.jetty.websocket.javax.common.messages.ByteArrayMessageSink;
@@ -37,31 +63,6 @@ import org.eclipse.jetty.websocket.javax.common.util.InvalidSignatureException;
 import org.eclipse.jetty.websocket.javax.common.util.InvokerUtils;
 import org.eclipse.jetty.websocket.javax.common.util.ReflectUtils;
 
-import javax.websocket.CloseReason;
-import javax.websocket.Decoder;
-import javax.websocket.EndpointConfig;
-import javax.websocket.OnClose;
-import javax.websocket.OnError;
-import javax.websocket.OnMessage;
-import javax.websocket.OnOpen;
-import javax.websocket.PongMessage;
-import javax.websocket.Session;
-import java.io.InputStream;
-import java.io.Reader;
-import java.lang.annotation.Annotation;
-import java.lang.invoke.MethodHandle;
-import java.lang.invoke.MethodHandles;
-import java.lang.invoke.MethodType;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ConcurrentHashMap;
-
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.eclipse.jetty.websocket.javax.common.JavaxWebSocketFrameHandlerMetadata.MessageMetadata;
 
@@ -107,8 +108,9 @@ public abstract class JavaxWebSocketFrameHandlerFactory
 
     public abstract JavaxWebSocketFrameHandlerMetadata createMetadata(Class endpointClass, EndpointConfig endpointConfig);
 
-    public JavaxWebSocketFrameHandler newJavaxFrameHandler(Object endpointInstance, UpgradeRequest upgradeRequest, UpgradeResponse upgradeResponse,
-        CompletableFuture futureSession)
+    public JavaxWebSocketFrameHandler newJavaxWebSocketFrameHandler(Object endpointInstance, UpgradeRequest upgradeRequest,
+                                                                    UpgradeResponse upgradeResponse,
+                                                                    CompletableFuture futureSession)
     {
         Object endpoint;
         EndpointConfig config;
diff --git a/jetty-websocket/javax-websocket-common/src/test/java/org/eclipse/jetty/websocket/javax/common/AbstractJavaxWebSocketFrameHandlerTest.java b/jetty-websocket/javax-websocket-common/src/test/java/org/eclipse/jetty/websocket/javax/common/AbstractJavaxWebSocketFrameHandlerTest.java
index f847260c592..02a7b22bbf1 100644
--- a/jetty-websocket/javax-websocket-common/src/test/java/org/eclipse/jetty/websocket/javax/common/AbstractJavaxWebSocketFrameHandlerTest.java
+++ b/jetty-websocket/javax-websocket-common/src/test/java/org/eclipse/jetty/websocket/javax/common/AbstractJavaxWebSocketFrameHandlerTest.java
@@ -69,7 +69,7 @@ public abstract class AbstractJavaxWebSocketFrameHandlerTest
         UpgradeRequest upgradeRequest = new UpgradeRequestAdapter();
         UpgradeResponse upgradeResponse = new UpgradeResponseAdapter();
 
-        JavaxWebSocketFrameHandler localEndpoint = factory.newJavaxFrameHandler(endpoint,
+        JavaxWebSocketFrameHandler localEndpoint = factory.newJavaxWebSocketFrameHandler(endpoint,
             upgradeRequest, upgradeResponse, new CompletableFuture<>());
 
         return localEndpoint;
diff --git a/jetty-websocket/javax-websocket-server/src/main/java/org/eclipse/jetty/websocket/javax/server/JavaxWebSocketServerFrameHandlerFactory.java b/jetty-websocket/javax-websocket-server/src/main/java/org/eclipse/jetty/websocket/javax/server/JavaxWebSocketServerFrameHandlerFactory.java
index 71e68d39efa..12d7746ae20 100644
--- a/jetty-websocket/javax-websocket-server/src/main/java/org/eclipse/jetty/websocket/javax/server/JavaxWebSocketServerFrameHandlerFactory.java
+++ b/jetty-websocket/javax-websocket-server/src/main/java/org/eclipse/jetty/websocket/javax/server/JavaxWebSocketServerFrameHandlerFactory.java
@@ -18,23 +18,24 @@
 
 package org.eclipse.jetty.websocket.javax.server;
 
-import org.eclipse.jetty.http.pathmap.UriTemplatePathSpec;
-import org.eclipse.jetty.websocket.core.FrameHandler;
-import org.eclipse.jetty.websocket.javax.server.internal.PathParamIdentifier;
-import org.eclipse.jetty.websocket.javax.server.internal.UpgradeRequestAdapter;
-import org.eclipse.jetty.websocket.javax.server.internal.UpgradeResponseAdapter;
-import org.eclipse.jetty.websocket.javax.common.JavaxWebSocketContainer;
-import org.eclipse.jetty.websocket.javax.common.JavaxWebSocketFrameHandlerFactory;
-import org.eclipse.jetty.websocket.javax.common.JavaxWebSocketFrameHandlerMetadata;
-import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
-import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
-import org.eclipse.jetty.websocket.servlet.FrameHandlerFactory;
+import java.util.concurrent.CompletableFuture;
 
 import javax.websocket.Endpoint;
 import javax.websocket.EndpointConfig;
 import javax.websocket.Session;
 import javax.websocket.server.ServerEndpoint;
-import java.util.concurrent.CompletableFuture;
+
+import org.eclipse.jetty.http.pathmap.UriTemplatePathSpec;
+import org.eclipse.jetty.websocket.core.FrameHandler;
+import org.eclipse.jetty.websocket.javax.common.JavaxWebSocketContainer;
+import org.eclipse.jetty.websocket.javax.common.JavaxWebSocketFrameHandlerFactory;
+import org.eclipse.jetty.websocket.javax.common.JavaxWebSocketFrameHandlerMetadata;
+import org.eclipse.jetty.websocket.javax.server.internal.DelegatedJavaxServletUpgradeRequest;
+import org.eclipse.jetty.websocket.javax.server.internal.PathParamIdentifier;
+import org.eclipse.jetty.websocket.javax.server.internal.UpgradeResponseAdapter;
+import org.eclipse.jetty.websocket.servlet.FrameHandlerFactory;
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
 
 public class JavaxWebSocketServerFrameHandlerFactory extends JavaxWebSocketFrameHandlerFactory implements FrameHandlerFactory
 {
@@ -68,6 +69,6 @@ public class JavaxWebSocketServerFrameHandlerFactory extends JavaxWebSocketFrame
     public FrameHandler newFrameHandler(Object websocketPojo, ServletUpgradeRequest upgradeRequest, ServletUpgradeResponse upgradeResponse)
     {
         CompletableFuture completableFuture = new CompletableFuture<>();
-        return newJavaxFrameHandler(websocketPojo, new UpgradeRequestAdapter(upgradeRequest), new UpgradeResponseAdapter(upgradeResponse), completableFuture);
+        return newJavaxWebSocketFrameHandler(websocketPojo, new DelegatedJavaxServletUpgradeRequest(upgradeRequest), new UpgradeResponseAdapter(upgradeResponse), completableFuture);
     }
 }
diff --git a/jetty-websocket/javax-websocket-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/UpgradeRequestAdapter.java b/jetty-websocket/javax-websocket-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/DelegatedJavaxServletUpgradeRequest.java
similarity index 89%
rename from jetty-websocket/javax-websocket-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/UpgradeRequestAdapter.java
rename to jetty-websocket/javax-websocket-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/DelegatedJavaxServletUpgradeRequest.java
index ffafa7b4973..c6a2eccde05 100644
--- a/jetty-websocket/javax-websocket-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/UpgradeRequestAdapter.java
+++ b/jetty-websocket/javax-websocket-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/DelegatedJavaxServletUpgradeRequest.java
@@ -18,17 +18,17 @@
 
 package org.eclipse.jetty.websocket.javax.server.internal;
 
-import org.eclipse.jetty.websocket.javax.common.UpgradeRequest;
-import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
-
 import java.net.URI;
 import java.security.Principal;
 
-public class UpgradeRequestAdapter implements UpgradeRequest
+import org.eclipse.jetty.websocket.javax.common.UpgradeRequest;
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
+
+public class DelegatedJavaxServletUpgradeRequest implements UpgradeRequest
 {
     private final ServletUpgradeRequest servletRequest;
 
-    public UpgradeRequestAdapter(ServletUpgradeRequest servletRequest)
+    public DelegatedJavaxServletUpgradeRequest(ServletUpgradeRequest servletRequest)
     {
         this.servletRequest = servletRequest;
     }
diff --git a/jetty-websocket/javax-websocket-tests/src/main/java/org/eclipse/jetty/websocket/javax/tests/NetworkFuzzer.java b/jetty-websocket/javax-websocket-tests/src/main/java/org/eclipse/jetty/websocket/javax/tests/NetworkFuzzer.java
index 5238b8882c2..6d9061280f2 100644
--- a/jetty-websocket/javax-websocket-tests/src/main/java/org/eclipse/jetty/websocket/javax/tests/NetworkFuzzer.java
+++ b/jetty-websocket/javax-websocket-tests/src/main/java/org/eclipse/jetty/websocket/javax/tests/NetworkFuzzer.java
@@ -38,7 +38,7 @@ import org.eclipse.jetty.websocket.core.Behavior;
 import org.eclipse.jetty.websocket.core.CloseStatus;
 import org.eclipse.jetty.websocket.core.Frame;
 import org.eclipse.jetty.websocket.core.FrameHandler;
-import org.eclipse.jetty.websocket.core.client.UpgradeRequest;
+import org.eclipse.jetty.websocket.core.client.ClientUpgradeRequest;
 import org.eclipse.jetty.websocket.core.client.WebSocketCoreClient;
 import org.eclipse.jetty.websocket.core.internal.Generator;
 
@@ -184,7 +184,7 @@ public class NetworkFuzzer extends Fuzzer.Adapter implements Fuzzer, AutoCloseab
         }
     }
 
-    public static class RawUpgradeRequest extends UpgradeRequest
+    public static class RawUpgradeRequest extends ClientUpgradeRequest
     {
         private final CompletableFuture futureCapture;
         private EndPoint endPoint;
diff --git a/jetty-websocket/javax-websocket-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/client/SessionAddMessageHandlerTest.java b/jetty-websocket/javax-websocket-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/client/SessionAddMessageHandlerTest.java
index c2344a5e270..ebee871ac30 100644
--- a/jetty-websocket/javax-websocket-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/client/SessionAddMessageHandlerTest.java
+++ b/jetty-websocket/javax-websocket-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/client/SessionAddMessageHandlerTest.java
@@ -84,7 +84,7 @@ public class SessionAddMessageHandlerTest
 
         JavaxWebSocketFrameHandlerFactory frameHandlerFactory = new JavaxWebSocketClientFrameHandlerFactory(container);
         CompletableFuture futureSession = new CompletableFuture<>();
-        frameHandler = frameHandlerFactory.newJavaxFrameHandler(ei, handshakeRequest, handshakeResponse, futureSession);
+        frameHandler = frameHandlerFactory.newJavaxWebSocketFrameHandler(ei, handshakeRequest, handshakeResponse, futureSession);
         frameHandler.onOpen(new FrameHandler.CoreSession.Empty(), Callback.NOOP);
 
         // Session
diff --git a/jetty-websocket/javax-websocket-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/ConfiguratorTest.java b/jetty-websocket/javax-websocket-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/ConfiguratorTest.java
index 3be59d1e298..9dae9ac822d 100644
--- a/jetty-websocket/javax-websocket-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/ConfiguratorTest.java
+++ b/jetty-websocket/javax-websocket-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/ConfiguratorTest.java
@@ -18,37 +18,6 @@
 
 package org.eclipse.jetty.websocket.javax.tests.server;
 
-import org.eclipse.jetty.http.HttpHeader;
-import org.eclipse.jetty.util.Callback;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.websocket.core.Frame;
-import org.eclipse.jetty.websocket.core.FrameHandler;
-import org.eclipse.jetty.websocket.core.OpCode;
-import org.eclipse.jetty.websocket.core.client.UpgradeRequest;
-import org.eclipse.jetty.websocket.core.client.WebSocketCoreClient;
-import org.eclipse.jetty.websocket.javax.server.internal.JavaxWebSocketCreator;
-import org.eclipse.jetty.websocket.javax.tests.LocalServer;
-import org.eclipse.jetty.websocket.javax.tests.Timeouts;
-import org.eclipse.jetty.websocket.javax.tests.framehandlers.FrameHandlerTracker;
-import org.hamcrest.Matcher;
-import org.junit.jupiter.api.AfterAll;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeAll;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
-import javax.websocket.DecodeException;
-import javax.websocket.Decoder;
-import javax.websocket.EndpointConfig;
-import javax.websocket.Extension;
-import javax.websocket.HandshakeResponse;
-import javax.websocket.OnMessage;
-import javax.websocket.Session;
-import javax.websocket.server.HandshakeRequest;
-import javax.websocket.server.ServerContainer;
-import javax.websocket.server.ServerEndpoint;
-import javax.websocket.server.ServerEndpointConfig;
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.net.InetSocketAddress;
@@ -69,6 +38,38 @@ import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.stream.Collectors;
 
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+import javax.websocket.EndpointConfig;
+import javax.websocket.Extension;
+import javax.websocket.HandshakeResponse;
+import javax.websocket.OnMessage;
+import javax.websocket.Session;
+import javax.websocket.server.HandshakeRequest;
+import javax.websocket.server.ServerContainer;
+import javax.websocket.server.ServerEndpoint;
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.core.Frame;
+import org.eclipse.jetty.websocket.core.FrameHandler;
+import org.eclipse.jetty.websocket.core.OpCode;
+import org.eclipse.jetty.websocket.core.client.ClientUpgradeRequest;
+import org.eclipse.jetty.websocket.core.client.WebSocketCoreClient;
+import org.eclipse.jetty.websocket.javax.server.internal.JavaxWebSocketCreator;
+import org.eclipse.jetty.websocket.javax.tests.LocalServer;
+import org.eclipse.jetty.websocket.javax.tests.Timeouts;
+import org.eclipse.jetty.websocket.javax.tests.framehandlers.FrameHandlerTracker;
+import org.hamcrest.Matcher;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
 
@@ -430,7 +431,7 @@ public class ConfiguratorTest
         URI wsUri = server.getWsUri().resolve("/capture-request-headers");
 
         FrameHandlerTracker clientSocket = new FrameHandlerTracker();
-        UpgradeRequest upgradeRequest = UpgradeRequest.from(client, wsUri, clientSocket);
+        ClientUpgradeRequest upgradeRequest = ClientUpgradeRequest.from(client, wsUri, clientSocket);
         upgradeRequest.addExtensions("identity");
         Future clientConnectFuture = client.connect(upgradeRequest);
 
@@ -454,7 +455,7 @@ public class ConfiguratorTest
         URI wsUri = server.getWsUri().resolve("/no-extensions");
 
         FrameHandlerTracker clientSocket = new FrameHandlerTracker();
-        UpgradeRequest upgradeRequest = UpgradeRequest.from(client, wsUri, clientSocket);
+        ClientUpgradeRequest upgradeRequest = ClientUpgradeRequest.from(client, wsUri, clientSocket);
         upgradeRequest.addExtensions("identity");
         Future clientConnectFuture = client.connect(upgradeRequest);
 
@@ -478,7 +479,7 @@ public class ConfiguratorTest
         URI wsUri = server.getWsUri().resolve("/capture-request-headers");
 
         FrameHandlerTracker clientSocket = new FrameHandlerTracker();
-        UpgradeRequest upgradeRequest = UpgradeRequest.from(client, wsUri, clientSocket);
+        ClientUpgradeRequest upgradeRequest = ClientUpgradeRequest.from(client, wsUri, clientSocket);
         upgradeRequest.header("X-Dummy", "Bogus");
         Future clientConnectFuture = client.connect(upgradeRequest);
 
@@ -503,7 +504,7 @@ public class ConfiguratorTest
 
         // First Request
         FrameHandlerTracker clientSocket = new FrameHandlerTracker();
-        UpgradeRequest upgradeRequest = UpgradeRequest.from(client, wsUri, clientSocket);
+        ClientUpgradeRequest upgradeRequest = ClientUpgradeRequest.from(client, wsUri, clientSocket);
         Future clientConnectFuture = client.connect(upgradeRequest);
 
         FrameHandler.CoreSession channel = clientConnectFuture.get(Timeouts.CONNECT_MS, TimeUnit.MILLISECONDS);
@@ -522,7 +523,7 @@ public class ConfiguratorTest
 
         // Second request
         clientSocket = new FrameHandlerTracker();
-        upgradeRequest = UpgradeRequest.from(client, wsUri, clientSocket);
+        upgradeRequest = ClientUpgradeRequest.from(client, wsUri, clientSocket);
         clientConnectFuture = client.connect(upgradeRequest);
 
         channel = clientConnectFuture.get(Timeouts.CONNECT_MS, TimeUnit.MILLISECONDS);
@@ -550,7 +551,7 @@ public class ConfiguratorTest
         URI wsUri = server.getWsUri().resolve("/addr");
 
         FrameHandlerTracker clientSocket = new FrameHandlerTracker();
-        UpgradeRequest upgradeRequest = UpgradeRequest.from(client, wsUri, clientSocket);
+        ClientUpgradeRequest upgradeRequest = ClientUpgradeRequest.from(client, wsUri, clientSocket);
         Future clientConnectFuture = client.connect(upgradeRequest);
 
         FrameHandler.CoreSession channel = clientConnectFuture.get(Timeouts.CONNECT_MS, TimeUnit.MILLISECONDS);
@@ -591,7 +592,7 @@ public class ConfiguratorTest
         ProtocolsConfigurator.seenProtocols.set(null);
 
         FrameHandlerTracker clientSocket = new FrameHandlerTracker();
-        UpgradeRequest upgradeRequest = UpgradeRequest.from(client, wsUri, clientSocket);
+        ClientUpgradeRequest upgradeRequest = ClientUpgradeRequest.from(client, wsUri, clientSocket);
         upgradeRequest.setSubProtocols("status");
         Future clientConnectFuture = client.connect(upgradeRequest);
 
@@ -610,7 +611,7 @@ public class ConfiguratorTest
         ProtocolsConfigurator.seenProtocols.set(null);
 
         FrameHandlerTracker clientSocket = new FrameHandlerTracker();
-        UpgradeRequest upgradeRequest = UpgradeRequest.from(client, wsUri, clientSocket);
+        ClientUpgradeRequest upgradeRequest = ClientUpgradeRequest.from(client, wsUri, clientSocket);
         upgradeRequest.setSubProtocols("echo", "chat", "status");
         Future clientConnectFuture = client.connect(upgradeRequest);
 
@@ -629,7 +630,7 @@ public class ConfiguratorTest
         ProtocolsConfigurator.seenProtocols.set(null);
 
         FrameHandlerTracker clientSocket = new FrameHandlerTracker();
-        UpgradeRequest upgradeRequest = UpgradeRequest.from(client, wsUri, clientSocket);
+        ClientUpgradeRequest upgradeRequest = ClientUpgradeRequest.from(client, wsUri, clientSocket);
         upgradeRequest.header("sec-websocket-protocol", "echo, chat, status");
         Future clientConnectFuture = client.connect(upgradeRequest);
 
@@ -648,7 +649,7 @@ public class ConfiguratorTest
         ProtocolsConfigurator.seenProtocols.set(null);
 
         FrameHandlerTracker clientSocket = new FrameHandlerTracker();
-        UpgradeRequest upgradeRequest = UpgradeRequest.from(client, wsUri, clientSocket);
+        ClientUpgradeRequest upgradeRequest = ClientUpgradeRequest.from(client, wsUri, clientSocket);
         // header name is not to spec (case wise)
         upgradeRequest.header("Sec-Websocket-Protocol", "echo, chat, status");
         Future clientConnectFuture = client.connect(upgradeRequest);
@@ -682,7 +683,7 @@ public class ConfiguratorTest
         URI wsUri = server.getWsUri().resolve("/timedecoder");
 
         FrameHandlerTracker clientSocket = new FrameHandlerTracker();
-        UpgradeRequest upgradeRequest = UpgradeRequest.from(client, wsUri, clientSocket);
+        ClientUpgradeRequest upgradeRequest = ClientUpgradeRequest.from(client, wsUri, clientSocket);
         upgradeRequest.setSubProtocols("gmt");
         Future clientConnectFuture = client.connect(upgradeRequest);
 
diff --git a/jetty-websocket/jetty-websocket-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketClient.java b/jetty-websocket/jetty-websocket-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketClient.java
index a9267c39c13..8d6b0f66841 100644
--- a/jetty-websocket/jetty-websocket-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketClient.java
+++ b/jetty-websocket/jetty-websocket-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketClient.java
@@ -40,7 +40,7 @@ import org.eclipse.jetty.websocket.api.UpgradeRequest;
 import org.eclipse.jetty.websocket.api.UpgradeResponse;
 import org.eclipse.jetty.websocket.api.WebSocketBehavior;
 import org.eclipse.jetty.websocket.api.WebSocketPolicy;
-import org.eclipse.jetty.websocket.client.impl.ClientUpgradeRequestImpl;
+import org.eclipse.jetty.websocket.client.impl.JettyClientUpgradeRequest;
 import org.eclipse.jetty.websocket.common.JettyWebSocketFrameHandler;
 import org.eclipse.jetty.websocket.common.JettyWebSocketFrameHandlerFactory;
 import org.eclipse.jetty.websocket.core.WebSocketExtensionRegistry;
@@ -107,7 +107,7 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketPoli
      */
     public CompletableFuture connect(Object websocket, URI toUri, UpgradeRequest request) throws IOException
     {
-        ClientUpgradeRequestImpl upgradeRequest = new ClientUpgradeRequestImpl(this, coreClient, request, toUri, websocket);
+        JettyClientUpgradeRequest upgradeRequest = new JettyClientUpgradeRequest(this, coreClient, request, toUri, websocket);
         coreClient.connect(upgradeRequest);
         return upgradeRequest.getFutureSession();
     }
diff --git a/jetty-websocket/jetty-websocket-client/src/main/java/org/eclipse/jetty/websocket/client/impl/DelegatedClientUpgradeRequest.java b/jetty-websocket/jetty-websocket-client/src/main/java/org/eclipse/jetty/websocket/client/impl/DelegatedJettyClientUpgradeRequest.java
similarity index 95%
rename from jetty-websocket/jetty-websocket-client/src/main/java/org/eclipse/jetty/websocket/client/impl/DelegatedClientUpgradeRequest.java
rename to jetty-websocket/jetty-websocket-client/src/main/java/org/eclipse/jetty/websocket/client/impl/DelegatedJettyClientUpgradeRequest.java
index 5c18b6a9f36..9df6187f8ae 100644
--- a/jetty-websocket/jetty-websocket-client/src/main/java/org/eclipse/jetty/websocket/client/impl/DelegatedClientUpgradeRequest.java
+++ b/jetty-websocket/jetty-websocket-client/src/main/java/org/eclipse/jetty/websocket/client/impl/DelegatedJettyClientUpgradeRequest.java
@@ -18,14 +18,6 @@
 
 package org.eclipse.jetty.websocket.client.impl;
 
-import org.eclipse.jetty.http.HttpField;
-import org.eclipse.jetty.http.HttpHeader;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.util.MultiMap;
-import org.eclipse.jetty.util.UrlEncoded;
-import org.eclipse.jetty.websocket.api.UpgradeRequest;
-import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
-
 import java.net.HttpCookie;
 import java.net.SocketAddress;
 import java.net.URI;
@@ -35,19 +27,28 @@ import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
 
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.MultiMap;
+import org.eclipse.jetty.util.UrlEncoded;
+import org.eclipse.jetty.websocket.api.UpgradeRequest;
+import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
+import org.eclipse.jetty.websocket.core.client.ClientUpgradeRequest;
+
 import static java.nio.charset.StandardCharsets.UTF_8;
 
 /**
  * Representing the Jetty {@link org.eclipse.jetty.client.HttpClient}'s {@link org.eclipse.jetty.client.HttpRequest}
  * in the {@link UpgradeRequest} interface.
  */
-public class DelegatedClientUpgradeRequest implements UpgradeRequest
+public class DelegatedJettyClientUpgradeRequest implements UpgradeRequest
 {
-    private final org.eclipse.jetty.websocket.core.client.UpgradeRequest delegate;
+    private final ClientUpgradeRequest delegate;
     private SocketAddress localSocketAddress;
     private SocketAddress remoteSocketAddress;
 
-    public DelegatedClientUpgradeRequest(org.eclipse.jetty.websocket.core.client.UpgradeRequest delegate)
+    public DelegatedJettyClientUpgradeRequest(ClientUpgradeRequest delegate)
     {
         this.delegate = delegate;
     }
diff --git a/jetty-websocket/jetty-websocket-client/src/main/java/org/eclipse/jetty/websocket/client/impl/DelegatedClientUpgradeResponse.java b/jetty-websocket/jetty-websocket-client/src/main/java/org/eclipse/jetty/websocket/client/impl/DelegatedJettyClientUpgradeResponse.java
similarity index 95%
rename from jetty-websocket/jetty-websocket-client/src/main/java/org/eclipse/jetty/websocket/client/impl/DelegatedClientUpgradeResponse.java
rename to jetty-websocket/jetty-websocket-client/src/main/java/org/eclipse/jetty/websocket/client/impl/DelegatedJettyClientUpgradeResponse.java
index 6fd75b00d5e..44bc2a3563d 100644
--- a/jetty-websocket/jetty-websocket-client/src/main/java/org/eclipse/jetty/websocket/client/impl/DelegatedClientUpgradeResponse.java
+++ b/jetty-websocket/jetty-websocket-client/src/main/java/org/eclipse/jetty/websocket/client/impl/DelegatedJettyClientUpgradeResponse.java
@@ -18,11 +18,6 @@
 
 package org.eclipse.jetty.websocket.client.impl;
 
-import org.eclipse.jetty.client.HttpResponse;
-import org.eclipse.jetty.http.HttpHeader;
-import org.eclipse.jetty.websocket.api.UpgradeResponse;
-import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
-
 import java.io.IOException;
 import java.util.Collections;
 import java.util.List;
@@ -30,15 +25,20 @@ import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
 
+import org.eclipse.jetty.client.HttpResponse;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.websocket.api.UpgradeResponse;
+import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
+
 /**
  * Representing the Jetty {@link org.eclipse.jetty.client.HttpClient}'s {@link org.eclipse.jetty.client.HttpResponse}
  * in the {@link UpgradeResponse} interface.
  */
-public class DelegatedClientUpgradeResponse implements UpgradeResponse
+public class DelegatedJettyClientUpgradeResponse implements UpgradeResponse
 {
     private HttpResponse delegate;
 
-    public DelegatedClientUpgradeResponse(HttpResponse response)
+    public DelegatedJettyClientUpgradeResponse(HttpResponse response)
     {
         this.delegate = response;
     }
diff --git a/jetty-websocket/jetty-websocket-client/src/main/java/org/eclipse/jetty/websocket/client/impl/ClientUpgradeRequestImpl.java b/jetty-websocket/jetty-websocket-client/src/main/java/org/eclipse/jetty/websocket/client/impl/JettyClientUpgradeRequest.java
similarity index 86%
rename from jetty-websocket/jetty-websocket-client/src/main/java/org/eclipse/jetty/websocket/client/impl/ClientUpgradeRequestImpl.java
rename to jetty-websocket/jetty-websocket-client/src/main/java/org/eclipse/jetty/websocket/client/impl/JettyClientUpgradeRequest.java
index 9e7e98d8834..fcb9ebebf4d 100644
--- a/jetty-websocket/jetty-websocket-client/src/main/java/org/eclipse/jetty/websocket/client/impl/ClientUpgradeRequestImpl.java
+++ b/jetty-websocket/jetty-websocket-client/src/main/java/org/eclipse/jetty/websocket/client/impl/JettyClientUpgradeRequest.java
@@ -18,34 +18,35 @@
 
 package org.eclipse.jetty.websocket.client.impl;
 
+import java.net.HttpCookie;
+import java.net.URI;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+
 import org.eclipse.jetty.client.HttpResponse;
 import org.eclipse.jetty.http.HttpFields;
 import org.eclipse.jetty.http.HttpHeader;
 import org.eclipse.jetty.http.HttpVersion;
 import org.eclipse.jetty.io.EndPoint;
 import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.UpgradeRequest;
 import org.eclipse.jetty.websocket.api.UpgradeResponse;
 import org.eclipse.jetty.websocket.client.WebSocketClient;
 import org.eclipse.jetty.websocket.common.JettyWebSocketFrameHandler;
 import org.eclipse.jetty.websocket.core.FrameHandler;
-import org.eclipse.jetty.websocket.core.client.UpgradeRequest;
+import org.eclipse.jetty.websocket.core.client.ClientUpgradeRequest;
 import org.eclipse.jetty.websocket.core.client.WebSocketCoreClient;
 
-import java.net.HttpCookie;
-import java.net.URI;
-import java.util.List;
-import java.util.concurrent.CompletableFuture;
-
-public class ClientUpgradeRequestImpl extends UpgradeRequest
+public class JettyClientUpgradeRequest extends ClientUpgradeRequest
 {
     private final WebSocketClient containerContext;
     private final Object websocketPojo;
     private final CompletableFuture onOpenFuture;
     private final CompletableFuture futureSession;
-    private final DelegatedClientUpgradeRequest handshakeRequest;
+    private final DelegatedJettyClientUpgradeRequest handshakeRequest;
 
-    public ClientUpgradeRequestImpl(WebSocketClient clientContainer, WebSocketCoreClient coreClient, org.eclipse.jetty.websocket.api.UpgradeRequest request,
-        URI requestURI, Object websocketPojo)
+    public JettyClientUpgradeRequest(WebSocketClient clientContainer, WebSocketCoreClient coreClient, UpgradeRequest request,
+                                     URI requestURI, Object websocketPojo)
     {
         super(coreClient, requestURI);
         this.containerContext = clientContainer;
@@ -89,7 +90,7 @@ public class ClientUpgradeRequestImpl extends UpgradeRequest
                 version(HttpVersion.fromString(request.getHttpVersion()));
         }
 
-        handshakeRequest = new DelegatedClientUpgradeRequest(this);
+        handshakeRequest = new DelegatedJettyClientUpgradeRequest(this);
     }
 
     @Override
@@ -108,7 +109,7 @@ public class ClientUpgradeRequestImpl extends UpgradeRequest
     @Override
     public FrameHandler getFrameHandler(WebSocketCoreClient coreClient, HttpResponse response)
     {
-        UpgradeResponse upgradeResponse = new DelegatedClientUpgradeResponse(response);
+        UpgradeResponse upgradeResponse = new DelegatedJettyClientUpgradeResponse(response);
 
         JettyWebSocketFrameHandler frameHandler = containerContext.newFrameHandler(websocketPojo,
             handshakeRequest, upgradeResponse, onOpenFuture);
diff --git a/jetty-websocket/jetty-websocket-server/src/main/java/org/eclipse/jetty/websocket/server/JettyServerFrameHandlerFactory.java b/jetty-websocket/jetty-websocket-server/src/main/java/org/eclipse/jetty/websocket/server/JettyServerFrameHandlerFactory.java
index 47bbd03d4e6..4c800db2389 100644
--- a/jetty-websocket/jetty-websocket-server/src/main/java/org/eclipse/jetty/websocket/server/JettyServerFrameHandlerFactory.java
+++ b/jetty-websocket/jetty-websocket-server/src/main/java/org/eclipse/jetty/websocket/server/JettyServerFrameHandlerFactory.java
@@ -29,7 +29,7 @@ import org.eclipse.jetty.servlet.ServletContextHandler;
 import org.eclipse.jetty.util.component.LifeCycle;
 import org.eclipse.jetty.websocket.common.JettyWebSocketFrameHandlerFactory;
 import org.eclipse.jetty.websocket.core.FrameHandler;
-import org.eclipse.jetty.websocket.server.internal.UpgradeRequestAdapter;
+import org.eclipse.jetty.websocket.server.internal.DelegatedJettyServletUpgradeRequest;
 import org.eclipse.jetty.websocket.server.internal.UpgradeResponseAdapter;
 import org.eclipse.jetty.websocket.servlet.FrameHandlerFactory;
 import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
@@ -67,7 +67,7 @@ public class JettyServerFrameHandlerFactory
     @Override
     public FrameHandler newFrameHandler(Object websocketPojo, ServletUpgradeRequest upgradeRequest, ServletUpgradeResponse upgradeResponse)
     {
-        return super.newJettyFrameHandler(websocketPojo, new UpgradeRequestAdapter(upgradeRequest), new UpgradeResponseAdapter(upgradeResponse),
+        return super.newJettyFrameHandler(websocketPojo, new DelegatedJettyServletUpgradeRequest(upgradeRequest), new UpgradeResponseAdapter(upgradeResponse),
             new CompletableFuture<>());
     }
 
diff --git a/jetty-websocket/jetty-websocket-server/src/main/java/org/eclipse/jetty/websocket/server/internal/UpgradeRequestAdapter.java b/jetty-websocket/jetty-websocket-server/src/main/java/org/eclipse/jetty/websocket/server/internal/DelegatedJettyServletUpgradeRequest.java
similarity index 97%
rename from jetty-websocket/jetty-websocket-server/src/main/java/org/eclipse/jetty/websocket/server/internal/UpgradeRequestAdapter.java
rename to jetty-websocket/jetty-websocket-server/src/main/java/org/eclipse/jetty/websocket/server/internal/DelegatedJettyServletUpgradeRequest.java
index b7e74c93cf7..b2d17f0873a 100644
--- a/jetty-websocket/jetty-websocket-server/src/main/java/org/eclipse/jetty/websocket/server/internal/UpgradeRequestAdapter.java
+++ b/jetty-websocket/jetty-websocket-server/src/main/java/org/eclipse/jetty/websocket/server/internal/DelegatedJettyServletUpgradeRequest.java
@@ -18,10 +18,6 @@
 
 package org.eclipse.jetty.websocket.server.internal;
 
-import org.eclipse.jetty.websocket.api.UpgradeRequest;
-import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
-import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
-
 import java.net.HttpCookie;
 import java.net.URI;
 import java.security.Principal;
@@ -29,11 +25,15 @@ import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
 
-public class UpgradeRequestAdapter implements UpgradeRequest
+import org.eclipse.jetty.websocket.api.UpgradeRequest;
+import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
+
+public class DelegatedJettyServletUpgradeRequest implements UpgradeRequest
 {
     private final ServletUpgradeRequest servletRequest;
 
-    public UpgradeRequestAdapter(ServletUpgradeRequest servletRequest)
+    public DelegatedJettyServletUpgradeRequest(ServletUpgradeRequest servletRequest)
     {
         this.servletRequest = servletRequest;
     }
diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/FrameHandler.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/FrameHandler.java
index cb54e59fadb..ccb1136ee52 100644
--- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/FrameHandler.java
+++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/FrameHandler.java
@@ -27,7 +27,7 @@ import java.util.Map;
 
 import org.eclipse.jetty.io.ByteBufferPool;
 import org.eclipse.jetty.util.Callback;
-import org.eclipse.jetty.websocket.core.client.UpgradeRequest;
+import org.eclipse.jetty.websocket.core.client.ClientUpgradeRequest;
 import org.eclipse.jetty.websocket.core.server.Negotiation;
 
 /**
@@ -42,8 +42,8 @@ import org.eclipse.jetty.websocket.core.server.Negotiation;
  * 
  • On the server, the application layer must provide a {@link org.eclipse.jetty.websocket.core.server.WebSocketNegotiator} instance * to negotiate and accept websocket connections, which will return the FrameHandler instance to use from * {@link org.eclipse.jetty.websocket.core.server.WebSocketNegotiator#negotiate(Negotiation)}.
  • - *
  • On the client, the application returns the FrameHandler instance to user from the {@link UpgradeRequest} - * instance that it passes to the {@link org.eclipse.jetty.websocket.core.client.WebSocketCoreClient#connect(UpgradeRequest)} method/
  • + *
  • On the client, the application returns the FrameHandler instance to user from the {@link ClientUpgradeRequest} + * instance that it passes to the {@link org.eclipse.jetty.websocket.core.client.WebSocketCoreClient#connect(ClientUpgradeRequest)} method/
  • * *

    * Once instantiated the FrameHandler follows is used as follows: diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/client/UpgradeRequest.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/client/ClientUpgradeRequest.java similarity index 97% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/client/UpgradeRequest.java rename to jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/client/ClientUpgradeRequest.java index 183a6df6b88..ed5141903cf 100644 --- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/client/UpgradeRequest.java +++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/client/ClientUpgradeRequest.java @@ -64,11 +64,11 @@ import org.eclipse.jetty.websocket.core.internal.WebSocketChannel; import org.eclipse.jetty.websocket.core.internal.WebSocketConnection; import org.eclipse.jetty.websocket.core.internal.WebSocketCore; -public abstract class UpgradeRequest extends HttpRequest implements Response.CompleteListener, HttpConnectionUpgrader +public abstract class ClientUpgradeRequest extends HttpRequest implements Response.CompleteListener, HttpConnectionUpgrader { - public static UpgradeRequest from(WebSocketCoreClient webSocketClient, URI requestURI, FrameHandler frameHandler) + public static ClientUpgradeRequest from(WebSocketCoreClient webSocketClient, URI requestURI, FrameHandler frameHandler) { - return new UpgradeRequest(webSocketClient, requestURI) + return new ClientUpgradeRequest(webSocketClient, requestURI) { @Override public FrameHandler getFrameHandler(WebSocketCoreClient coreClient, HttpResponse response) @@ -78,7 +78,7 @@ public abstract class UpgradeRequest extends HttpRequest implements Response.Com }; } - private static final Logger LOG = Log.getLogger(UpgradeRequest.class); + private static final Logger LOG = Log.getLogger(ClientUpgradeRequest.class); protected final CompletableFuture futureCoreSession; private final WebSocketCoreClient wsClient; private List upgradeListeners = new ArrayList<>(); @@ -91,7 +91,7 @@ public abstract class UpgradeRequest extends HttpRequest implements Response.Com */ private List subProtocols = new ArrayList<>(); - public UpgradeRequest(WebSocketCoreClient webSocketClient, URI requestURI) + public ClientUpgradeRequest(WebSocketCoreClient webSocketClient, URI requestURI) { super(webSocketClient.getHttpClient(), new HttpConversation(), requestURI); diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/client/WebSocketCoreClient.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/client/WebSocketCoreClient.java index 58fae3df9f2..3d247f3414f 100644 --- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/client/WebSocketCoreClient.java +++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/client/WebSocketCoreClient.java @@ -83,11 +83,11 @@ public class WebSocketCoreClient extends ContainerLifeCycle implements FrameHand public CompletableFuture connect(FrameHandler frameHandler, URI wsUri) throws IOException { - UpgradeRequest request = UpgradeRequest.from(this, wsUri, frameHandler); + ClientUpgradeRequest request = ClientUpgradeRequest.from(this, wsUri, frameHandler); return connect(request); } - public CompletableFuture connect(UpgradeRequest request) throws IOException + public CompletableFuture connect(ClientUpgradeRequest request) throws IOException { if (!isStarted()) { diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/chat/ChatWebSocketClient.java b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/chat/ChatWebSocketClient.java index ea0344a654f..565a481f4c6 100644 --- a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/chat/ChatWebSocketClient.java +++ b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/chat/ChatWebSocketClient.java @@ -18,13 +18,6 @@ package org.eclipse.jetty.websocket.core.chat; -import org.eclipse.jetty.util.Callback; -import org.eclipse.jetty.util.log.Log; -import org.eclipse.jetty.util.log.Logger; -import org.eclipse.jetty.websocket.core.MessageHandler; -import org.eclipse.jetty.websocket.core.client.UpgradeRequest; -import org.eclipse.jetty.websocket.core.client.WebSocketCoreClient; - import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.URI; @@ -34,6 +27,13 @@ import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.websocket.core.MessageHandler; +import org.eclipse.jetty.websocket.core.client.ClientUpgradeRequest; +import org.eclipse.jetty.websocket.core.client.WebSocketCoreClient; + public class ChatWebSocketClient { private static Logger LOG = Log.getLogger(ChatWebSocketClient.class); @@ -51,7 +51,7 @@ public class ChatWebSocketClient URI wsUri = baseWebsocketUri.resolve("/chat"); handler = MessageHandler.from(this::onText, null); - UpgradeRequest request = UpgradeRequest.from(client, wsUri, handler); + ClientUpgradeRequest request = ClientUpgradeRequest.from(client, wsUri, handler); request.setSubProtocols("chat"); client.connect(request).get(5, TimeUnit.SECONDS); handler.sendText("[" + name + ": has joined the room]", Callback.NOOP, false); diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/client/WebSocketClientServerTest.java b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/client/WebSocketClientServerTest.java index bf642358376..46fd2210de9 100644 --- a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/client/WebSocketClientServerTest.java +++ b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/client/WebSocketClientServerTest.java @@ -179,7 +179,7 @@ public class WebSocketClientServerTest public void start() throws Exception { - UpgradeRequest request = UpgradeRequest.from(client, baseWebSocketUri.resolve("/test"), handler); + ClientUpgradeRequest request = ClientUpgradeRequest.from(client, baseWebSocketUri.resolve("/test"), handler); request.setSubProtocols("test"); this.client.start(); Future response = client.connect(request); From edf74a655473e5dda82c8e67e5c9ffea6ec0d025 Mon Sep 17 00:00:00 2001 From: "alexey.barsov" Date: Wed, 30 Jan 2019 11:10:42 +0100 Subject: [PATCH 06/22] Issue #3302 Supporting host:ip in X-Forwarded-For Signed-off-by: alexey.barsov --- .../server/ForwardedRequestCustomizer.java | 22 ++++++++++++- .../ForwardedRequestCustomizerTest.java | 32 +++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ForwardedRequestCustomizer.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ForwardedRequestCustomizer.java index 9c464c763e3..c4761c49efa 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/ForwardedRequestCustomizer.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ForwardedRequestCustomizer.java @@ -29,6 +29,7 @@ import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpScheme; import org.eclipse.jetty.http.QuotedCSV; import org.eclipse.jetty.server.HttpConfiguration.Customizer; +import org.eclipse.jetty.util.HostPort; import org.eclipse.jetty.util.StringUtil; @@ -333,7 +334,7 @@ public class ForwardedRequestCustomizer implements Customizer forwardedServer = getLeftMost(field.getValue()); if (forwardedFor==null && _forwardedForHeader!=null && _forwardedForHeader.equalsIgnoreCase(name)) - forwardedFor = getLeftMost(field.getValue()); + forwardedFor = getRemoteAddr(field.getValue()); if (forwardedProto==null && _forwardedProtoHeader!=null && _forwardedProtoHeader.equalsIgnoreCase(name)) forwardedProto = getLeftMost(field.getValue()); @@ -430,6 +431,25 @@ public class ForwardedRequestCustomizer implements Customizer // The left-most value is the farthest downstream client return headerValue.substring(0,commaIndex).trim(); } + + protected String getRemoteAddr(String headerValue) + { + String leftMost = getLeftMost(headerValue); + + if (leftMost != null && leftMost.contains(":")) { + try { + HostPort hostPort = new HostPort(leftMost); + if (hostPort.getPort() > 0) { + // address in a format host:port, return host part only. + return hostPort.getHost(); + } + } catch (Exception e) { + // failed to parse in host[:port] format, fallback to the value resolved from header + } + } + + return leftMost; + } @Override public String toString() diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ForwardedRequestCustomizerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ForwardedRequestCustomizerTest.java index 3397e3f77d1..bd8857d89f3 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/ForwardedRequestCustomizerTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ForwardedRequestCustomizerTest.java @@ -218,6 +218,38 @@ public class ForwardedRequestCustomizerTest assertEquals("0",_results.poll()); } + @Test + public void testForIpv4WithPort() throws Exception + { + String response=_connector.getResponse( + "GET / HTTP/1.1\n"+ + "Host: myhost\n"+ + "X-Forwarded-For: 10.9.8.7:1111,6.5.4.3:1111\n"+ + "\n"); + assertThat(response, Matchers.containsString("200 OK")); + assertEquals("http",_results.poll()); + assertEquals("myhost",_results.poll()); + assertEquals("80",_results.poll()); + assertEquals("10.9.8.7",_results.poll()); + assertEquals("0",_results.poll()); + } + + @Test + public void testForIpv6WithPort() throws Exception + { + String response=_connector.getResponse( + "GET / HTTP/1.1\n"+ + "Host: myhost\n"+ + "X-Forwarded-For: [2001:db8:cafe::17]:1111,6.5.4.3:1111\n"+ + "\n"); + assertThat(response, Matchers.containsString("200 OK")); + assertEquals("http",_results.poll()); + assertEquals("myhost",_results.poll()); + assertEquals("80",_results.poll()); + assertEquals("[2001:db8:cafe::17]",_results.poll()); + assertEquals("0",_results.poll()); + } + @Test public void testLegacyProto() throws Exception { From d02762140d6e13e1a07351b2d5cb6dc101a95419 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Wed, 30 Jan 2019 19:41:35 +0100 Subject: [PATCH 07/22] Fixes #3305 - Avoid additional selectNow(). Using system property "org.eclipse.jetty.io.forceSelectNow" to force a selectNow() call; if absent, forcing selectNow() only in the Windows OS. Signed-off-by: Simone Bordet --- .../org/eclipse/jetty/io/ManagedSelector.java | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ManagedSelector.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ManagedSelector.java index 93ba2f3fbe6..1dca5816e75 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/ManagedSelector.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ManagedSelector.java @@ -35,6 +35,7 @@ import java.util.Collections; import java.util.Deque; import java.util.Iterator; import java.util.List; +import java.util.Locale; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; @@ -60,6 +61,20 @@ import org.eclipse.jetty.util.thread.strategy.EatWhatYouKill; public class ManagedSelector extends ContainerLifeCycle implements Dumpable { private static final Logger LOG = Log.getLogger(ManagedSelector.class); + private static final boolean FORCE_SELECT_NOW; + static + { + String property = System.getProperty("org.eclipse.jetty.io.forceSelectNow"); + if (property != null) + { + FORCE_SELECT_NOW = Boolean.parseBoolean(property); + } + else + { + property = System.getProperty("os.name"); + FORCE_SELECT_NOW = property != null && property.toLowerCase(Locale.ENGLISH).contains("windows"); + } + } private final AtomicBoolean _started = new AtomicBoolean(false); private boolean _selecting = false; @@ -457,7 +472,8 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable if (Thread.interrupted() && !isRunning()) throw new ClosedSelectorException(); - selected = selector.selectNow(); + if (FORCE_SELECT_NOW) + selected = selector.selectNow(); } if (LOG.isDebugEnabled()) LOG.debug("Selector {} woken up from select, {}/{}/{} selected", selector, selected, selector.selectedKeys().size(), selector.keys().size()); From 6fee10d5376e5c45e0984738de87f1edd06e980d Mon Sep 17 00:00:00 2001 From: olivier lamy Date: Thu, 31 Jan 2019 09:39:21 +1000 Subject: [PATCH 08/22] use snapshot of maven-invoker-plugin because of NPE https://issues.apache.org/jira/browse/MINVOKER-247 Signed-off-by: olivier lamy --- pom.xml | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 98577729b01..2f8469e9fec 100644 --- a/pom.xml +++ b/pom.xml @@ -483,7 +483,7 @@ org.apache.maven.plugins maven-invoker-plugin - 3.2.0 + 3.2.1-SNAPSHOT ${it.debug} ${java.home} @@ -2021,4 +2021,27 @@ + + + apache.snapshots + https://repository.apache.org/content/repositories/snapshots + + false + + + true + + + + plexus-snapshots + https://oss.sonatype.org/content/repositories/plexus-snapshots + + false + + + true + + + + From b43fe865bff97a5b5ca9c3016de1d6098ab88685 Mon Sep 17 00:00:00 2001 From: olivier lamy Date: Thu, 31 Jan 2019 09:50:07 +1000 Subject: [PATCH 09/22] fix merge Signed-off-by: olivier lamy --- pom.xml | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/pom.xml b/pom.xml index 26e22917052..0d4fbef6182 100644 --- a/pom.xml +++ b/pom.xml @@ -13,29 +13,6 @@ https://eclipse.org/jetty 1995 - - - apache.snapshots - https://repository.apache.org/content/repositories/snapshots - - false - - - true - - - - plexus-snapshots - https://oss.sonatype.org/content/repositories/plexus-snapshots - - false - - - true - - - - 11 11 From bd4f9b30fc565e0b0ec7f134fbe53517344267ea Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Thu, 31 Jan 2019 11:13:05 +1100 Subject: [PATCH 10/22] Issue #3298 - cleanup of CompletableFutures for WebSocket upgrades Jetty and Javax ClientUpgradeRequests no longer use the combination of the the onOpenFuture and the futureCoreSession and instead use only the CompletableFuture future given to the FrameHandler onOpen Signed-off-by: Lachlan Roberts --- .../javax/client/JavaxClientUpgradeRequest.java | 10 +++++----- .../javax/common/JavaxWebSocketFrameHandler.java | 7 +++---- .../client/impl/JettyClientUpgradeRequest.java | 9 +++------ .../websocket/common/JettyWebSocketFrameHandler.java | 4 ++-- .../websocket/core/client/ClientUpgradeRequest.java | 7 ++++--- 5 files changed, 17 insertions(+), 20 deletions(-) diff --git a/jetty-websocket/javax-websocket-client/src/main/java/org/eclipse/jetty/websocket/javax/client/JavaxClientUpgradeRequest.java b/jetty-websocket/javax-websocket-client/src/main/java/org/eclipse/jetty/websocket/javax/client/JavaxClientUpgradeRequest.java index ad338145666..b3ee91af5dc 100644 --- a/jetty-websocket/javax-websocket-client/src/main/java/org/eclipse/jetty/websocket/javax/client/JavaxClientUpgradeRequest.java +++ b/jetty-websocket/javax-websocket-client/src/main/java/org/eclipse/jetty/websocket/javax/client/JavaxClientUpgradeRequest.java @@ -35,21 +35,21 @@ public class JavaxClientUpgradeRequest extends ClientUpgradeRequest { private final JavaxWebSocketClientContainer containerContext; private final Object websocketPojo; - private final CompletableFuture futureJavaxSession; + private final CompletableFuture futureSession; public JavaxClientUpgradeRequest(JavaxWebSocketClientContainer clientContainer, WebSocketCoreClient coreClient, URI requestURI, Object websocketPojo) { super(coreClient, requestURI); this.containerContext = clientContainer; this.websocketPojo = websocketPojo; - this.futureJavaxSession = new CompletableFuture<>(); + this.futureSession = new CompletableFuture<>(); } @Override protected void handleException(Throwable failure) { super.handleException(failure); - futureJavaxSession.completeExceptionally(failure); + futureSession.completeExceptionally(failure); } @Override @@ -58,13 +58,13 @@ public class JavaxClientUpgradeRequest extends ClientUpgradeRequest UpgradeRequest upgradeRequest = new DelegatedJavaxClientUpgradeRequest(this); UpgradeResponse upgradeResponse = new DelegatedJavaxClientUpgradeResponse(response); - JavaxWebSocketFrameHandler frameHandler = containerContext.newFrameHandler(websocketPojo, upgradeRequest, upgradeResponse, futureJavaxSession); + JavaxWebSocketFrameHandler frameHandler = containerContext.newFrameHandler(websocketPojo, upgradeRequest, upgradeResponse, futureSession); return frameHandler; } public CompletableFuture getFutureSession() { - return futureJavaxSession; + return futureSession; } } diff --git a/jetty-websocket/javax-websocket-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketFrameHandler.java b/jetty-websocket/javax-websocket-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketFrameHandler.java index 7cf65580df8..1e11426fe81 100644 --- a/jetty-websocket/javax-websocket-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketFrameHandler.java +++ b/jetty-websocket/javax-websocket-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketFrameHandler.java @@ -29,6 +29,7 @@ import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; + import javax.websocket.CloseReason; import javax.websocket.Decoder; import javax.websocket.EndpointConfig; @@ -229,16 +230,14 @@ public class JavaxWebSocketFrameHandler implements FrameHandler openHandle.invoke(); container.addBean(session, true); - futureSession.complete(session); callback.succeeded(); + futureSession.complete(session); } catch (Throwable cause) { Exception wse = new WebSocketException(endpointInstance.getClass().getName() + " OPEN method error: " + cause.getMessage(), cause); - - // TODO This feels like double handling of the exception? Review need for futureSession - futureSession.completeExceptionally(wse); callback.failed(wse); + futureSession.completeExceptionally(wse); } } diff --git a/jetty-websocket/jetty-websocket-client/src/main/java/org/eclipse/jetty/websocket/client/impl/JettyClientUpgradeRequest.java b/jetty-websocket/jetty-websocket-client/src/main/java/org/eclipse/jetty/websocket/client/impl/JettyClientUpgradeRequest.java index fcb9ebebf4d..de112f4d05a 100644 --- a/jetty-websocket/jetty-websocket-client/src/main/java/org/eclipse/jetty/websocket/client/impl/JettyClientUpgradeRequest.java +++ b/jetty-websocket/jetty-websocket-client/src/main/java/org/eclipse/jetty/websocket/client/impl/JettyClientUpgradeRequest.java @@ -41,7 +41,6 @@ public class JettyClientUpgradeRequest extends ClientUpgradeRequest { private final WebSocketClient containerContext; private final Object websocketPojo; - private final CompletableFuture onOpenFuture; private final CompletableFuture futureSession; private final DelegatedJettyClientUpgradeRequest handshakeRequest; @@ -51,9 +50,7 @@ public class JettyClientUpgradeRequest extends ClientUpgradeRequest super(coreClient, requestURI); this.containerContext = clientContainer; this.websocketPojo = websocketPojo; - - this.onOpenFuture = new CompletableFuture<>(); - this.futureSession = super.futureCoreSession.thenCombine(onOpenFuture, (channel, session) -> session); + this.futureSession = new CompletableFuture<>(); if (request != null) { @@ -103,7 +100,7 @@ public class JettyClientUpgradeRequest extends ClientUpgradeRequest protected void handleException(Throwable failure) { super.handleException(failure); - onOpenFuture.completeExceptionally(failure); + futureSession.completeExceptionally(failure); } @Override @@ -112,7 +109,7 @@ public class JettyClientUpgradeRequest extends ClientUpgradeRequest UpgradeResponse upgradeResponse = new DelegatedJettyClientUpgradeResponse(response); JettyWebSocketFrameHandler frameHandler = containerContext.newFrameHandler(websocketPojo, - handshakeRequest, upgradeResponse, onOpenFuture); + handshakeRequest, upgradeResponse, futureSession); return frameHandler; } diff --git a/jetty-websocket/jetty-websocket-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandler.java b/jetty-websocket/jetty-websocket-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandler.java index 91d8a1d2485..4682668e9f0 100644 --- a/jetty-websocket/jetty-websocket-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandler.java +++ b/jetty-websocket/jetty-websocket-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandler.java @@ -141,13 +141,13 @@ public class JettyWebSocketFrameHandler implements FrameHandler if (openHandle != null) openHandle.invoke(); - futureSession.complete(session); callback.succeeded(); + futureSession.complete(session); } catch (Throwable cause) { - // TODO should futureSession be failed here? callback.failed(new WebSocketException(endpointInstance.getClass().getName() + " OPEN method error: " + cause.getMessage(), cause)); + futureSession.completeExceptionally(cause); } } diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/client/ClientUpgradeRequest.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/client/ClientUpgradeRequest.java index ed5141903cf..42b0a485b55 100644 --- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/client/ClientUpgradeRequest.java +++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/client/ClientUpgradeRequest.java @@ -347,11 +347,12 @@ public abstract class ClientUpgradeRequest extends HttpRequest implements Respon try { endp.upgrade(wsConnection); - } - finally - { futureCoreSession.complete(wsChannel); } + catch (Throwable t) + { + futureCoreSession.completeExceptionally(t); + } } /** From 6a98cc0bd28c0922f2abf3f9f73918991908dc2f Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Thu, 31 Jan 2019 22:24:09 +1100 Subject: [PATCH 11/22] Issue #3298 - bug fixes for jetty websockets succeed callback when you do not have a message sink OnWebSocketFrame annotation should take an API frame not a core frame Signed-off-by: Lachlan Roberts --- .../jetty/websocket/common/JettyWebSocketFrameHandler.java | 3 +++ .../websocket/common/JettyWebSocketFrameHandlerFactory.java | 3 +-- .../websocket/common/endpoints/annotated/FrameSocket.java | 2 +- .../org/eclipse/jetty/websocket/tests/JettyWebsocketTest.java | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/jetty-websocket/jetty-websocket-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandler.java b/jetty-websocket/jetty-websocket-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandler.java index 4682668e9f0..a1a3e224e0f 100644 --- a/jetty-websocket/jetty-websocket-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandler.java +++ b/jetty-websocket/jetty-websocket-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandler.java @@ -236,7 +236,10 @@ public class JettyWebSocketFrameHandler implements FrameHandler { // No message sink is active if (activeMessageSink == null) + { + callback.succeeded(); return; + } // Accept the payload into the message sink activeMessageSink.accept(frame, callback); diff --git a/jetty-websocket/jetty-websocket-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandlerFactory.java b/jetty-websocket/jetty-websocket-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandlerFactory.java index e1f26a58bd0..a06d5fce560 100644 --- a/jetty-websocket/jetty-websocket-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandlerFactory.java +++ b/jetty-websocket/jetty-websocket-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandlerFactory.java @@ -61,7 +61,6 @@ import org.eclipse.jetty.websocket.common.message.PartialTextMessageSink; import org.eclipse.jetty.websocket.common.message.ReaderMessageSink; import org.eclipse.jetty.websocket.common.message.StringMessageSink; import org.eclipse.jetty.websocket.common.util.ReflectUtils; -import org.eclipse.jetty.websocket.core.Frame; /** * Factory to create {@link JettyWebSocketFrameHandler} instances suitable for @@ -351,7 +350,7 @@ public class JettyWebSocketFrameHandlerFactory extends ContainerLifeCycle { assertSignatureValid(endpointClass, onmethod, OnWebSocketFrame.class); final InvokerUtils.Arg SESSION = new InvokerUtils.Arg(Session.class); - final InvokerUtils.Arg FRAME = new InvokerUtils.Arg(Frame.class).required(); + final InvokerUtils.Arg FRAME = new InvokerUtils.Arg(org.eclipse.jetty.websocket.api.extensions.Frame.class).required(); MethodHandle methodHandle = InvokerUtils.mutatedInvoker(endpointClass, onmethod, SESSION, FRAME); metadata.setFrameHandler(methodHandle, onmethod); } diff --git a/jetty-websocket/jetty-websocket-common/src/test/java/org/eclipse/jetty/websocket/common/endpoints/annotated/FrameSocket.java b/jetty-websocket/jetty-websocket-common/src/test/java/org/eclipse/jetty/websocket/common/endpoints/annotated/FrameSocket.java index 90687dd21c6..3d7e97ed992 100644 --- a/jetty-websocket/jetty-websocket-common/src/test/java/org/eclipse/jetty/websocket/common/endpoints/annotated/FrameSocket.java +++ b/jetty-websocket/jetty-websocket-common/src/test/java/org/eclipse/jetty/websocket/common/endpoints/annotated/FrameSocket.java @@ -20,7 +20,7 @@ package org.eclipse.jetty.websocket.common.endpoints.annotated; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketFrame; import org.eclipse.jetty.websocket.api.annotations.WebSocket; -import org.eclipse.jetty.websocket.core.Frame; +import org.eclipse.jetty.websocket.api.extensions.Frame; @WebSocket public class FrameSocket diff --git a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/JettyWebsocketTest.java b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/JettyWebsocketTest.java index 42bbf1b10c1..9f3f69adca7 100644 --- a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/JettyWebsocketTest.java +++ b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/JettyWebsocketTest.java @@ -40,7 +40,7 @@ import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertTrue; -public class JettyWebsocketTest +public class JettyWebSocketTest { @WebSocket From ccda1ee5f6a7861cc9e80739b2a611c6de26d657 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Thu, 31 Jan 2019 13:23:58 +0100 Subject: [PATCH 12/22] Fixes #3311 - Ability to serve HTTP and HTTPS from the same port. Introduced PlainOrSslConnectionFactory, to "sniff" the first bytes on a connection and upgrade to SSL (if the bytes are TLS bytes), or upgrade to a specific, configured, protocol. Added also the ability to fail the upgrade in case of a `http` request to a `https` port and write a minimal response to the client. Signed-off-by: Simone Bordet --- .../eclipse/jetty/io/ssl/SslConnection.java | 18 +- .../eclipse/jetty/server/HttpConnection.java | 13 +- .../server/PlainOrSslConnectionFactory.java | 192 ++++++++++++++++++ .../server/PlainOrSslConnectionTest.java | 189 +++++++++++++++++ .../test/resources/jetty-logging.properties | 3 +- 5 files changed, 409 insertions(+), 6 deletions(-) create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/PlainOrSslConnectionFactory.java create mode 100644 jetty-server/src/test/java/org/eclipse/jetty/server/PlainOrSslConnectionTest.java diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java index 2d76b105675..1ed5ed23a09 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java @@ -76,7 +76,7 @@ import org.eclipse.jetty.util.thread.Invocable; * be called again and make another best effort attempt to progress the connection. * */ -public class SslConnection extends AbstractConnection +public class SslConnection extends AbstractConnection implements Connection.UpgradeTo { private static final Logger LOG = Log.getLogger(SslConnection.class); private static final String TLS_1_3 = "TLSv1.3"; @@ -260,6 +260,19 @@ public class SslConnection extends AbstractConnection this._allowMissingCloseMessage = allowMissingCloseMessage; } + private void acquireEncryptedInput() + { + if (_encryptedInput == null) + _encryptedInput = _bufferPool.acquire(_sslEngine.getSession().getPacketBufferSize(), _encryptedDirectBuffers); + } + + @Override + public void onUpgradeTo(ByteBuffer buffer) + { + acquireEncryptedInput(); + BufferUtil.append(_encryptedInput, buffer); + } + @Override public void onOpen() { @@ -526,8 +539,7 @@ public class SslConnection extends AbstractConnection throw new IllegalStateException("Unexpected HandshakeStatus " + status); } - if (_encryptedInput == null) - _encryptedInput = _bufferPool.acquire(_sslEngine.getSession().getPacketBufferSize(), _encryptedDirectBuffers); + acquireEncryptedInput(); // can we use the passed buffer if it is big enough ByteBuffer app_in; diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java index 3d124c5d752..d620fd96ff5 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java @@ -50,7 +50,7 @@ import org.eclipse.jetty.util.log.Logger; /** *

    A {@link Connection} that handles the HTTP protocol.

    */ -public class HttpConnection extends AbstractConnection implements Runnable, HttpTransport, Connection.UpgradeFrom, WriteFlusher.Listener +public class HttpConnection extends AbstractConnection implements Runnable, HttpTransport, WriteFlusher.Listener, Connection.UpgradeFrom, Connection.UpgradeTo { private static final Logger LOG = Log.getLogger(HttpConnection.class); public static final HttpField CONNECTION_CLOSE = new PreEncodedHttpField(HttpHeader.CONNECTION,HttpHeaderValue.CLOSE.asString()); @@ -196,6 +196,12 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http return null; } + @Override + public void onUpgradeTo(ByteBuffer buffer) + { + BufferUtil.append(getRequestBuffer(), buffer); + } + @Override public void onFlushed(long bytes) throws IOException { @@ -500,7 +506,10 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http public void onOpen() { super.onOpen(); - fillInterested(); + if (isRequestBufferEmpty()) + fillInterested(); + else + getExecutor().execute(this); } @Override diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/PlainOrSslConnectionFactory.java b/jetty-server/src/main/java/org/eclipse/jetty/server/PlainOrSslConnectionFactory.java new file mode 100644 index 00000000000..e8114b32dd7 --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/PlainOrSslConnectionFactory.java @@ -0,0 +1,192 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.server; + +import java.io.IOException; +import java.nio.ByteBuffer; + +import org.eclipse.jetty.io.AbstractConnection; +import org.eclipse.jetty.io.Connection; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/** + *

    A ConnectionFactory whose connections detect whether the first bytes are + * TLS bytes and upgrades to either a TLS connection or to a plain connection.

    + */ +public class PlainOrSslConnectionFactory extends AbstractConnectionFactory +{ + private static final Logger LOG = Log.getLogger(PlainOrSslConnection.class); + private static final int TLS_ALERT_FRAME_TYPE = 0x15; + private static final int TLS_HANDSHAKE_FRAME_TYPE = 0x16; + private static final int TLS_MAJOR_VERSION = 3; + + private final SslConnectionFactory sslConnectionFactory; + private final String plainProtocol; + + /** + *

    Creates a new plain or TLS ConnectionFactory.

    + *

    If {@code plainProtocol} is {@code null}, and the first bytes are not TLS, then + * {@link #unknownProtocol(ByteBuffer, EndPoint)} is called; applications may override its + * behavior (by default it closes the EndPoint) for example by writing a minimal response.

    + * + * @param sslConnectionFactory The SslConnectionFactory to use if the first bytes are TLS + * @param plainProtocol the protocol of the ConnectionFactory to use if the first bytes are not TLS, or null. + */ + public PlainOrSslConnectionFactory(SslConnectionFactory sslConnectionFactory, String plainProtocol) + { + super("plain|ssl"); + this.sslConnectionFactory = sslConnectionFactory; + this.plainProtocol = plainProtocol; + } + + @Override + public Connection newConnection(Connector connector, EndPoint endPoint) + { + return configure(new PlainOrSslConnection(endPoint, connector), connector, endPoint); + } + + /** + * @param buffer The buffer with the first bytes of the connection + * @return whether the bytes seem TLS bytes + */ + protected boolean seemsTLS(ByteBuffer buffer) + { + int tlsFrameType = buffer.get(0) & 0xFF; + int tlsMajorVersion = buffer.get(1) & 0xFF; + return (tlsFrameType == TLS_HANDSHAKE_FRAME_TYPE || tlsFrameType == TLS_ALERT_FRAME_TYPE) && tlsMajorVersion == TLS_MAJOR_VERSION; + } + + /** + *

    Callback method invoked when {@code plainProtocol} is {@code null} + * and the first bytes are not TLS.

    + *

    This typically happens when a client is trying to connect to a TLS + * port using the {@code http} scheme (and not the {@code https} scheme).

    + *

    This method may be overridden to write back a minimal response such as:

    + *
    +     * HTTP/1.1 400 Bad Request
    +     * Content-Length: 35
    +     * Content-Type: text/plain; charset=UTF8
    +     * Connection: close
    +     *
    +     * Plain HTTP request sent to TLS port
    +     * 
    + * + * @param buffer The buffer with the first bytes of the connection + * @param endPoint The connection EndPoint object + * @see #seemsTLS(ByteBuffer) + */ + protected void unknownProtocol(ByteBuffer buffer, EndPoint endPoint) + { + endPoint.close(); + } + + private class PlainOrSslConnection extends AbstractConnection implements Connection.UpgradeFrom + { + private final Connector connector; + private final ByteBuffer buffer; + + public PlainOrSslConnection(EndPoint endPoint, Connector connector) + { + super(endPoint, connector.getExecutor()); + this.connector = connector; + this.buffer = BufferUtil.allocateDirect(1536); + } + + @Override + public void onOpen() + { + super.onOpen(); + fillInterested(); + } + + @Override + public void onFillable() + { + try + { + int filled = getEndPoint().fill(buffer); + if (filled > 0) + { + upgrade(buffer); + } + else if (filled == 0) + { + fillInterested(); + } + else + { + close(); + } + } + catch (IOException x) + { + LOG.warn(x); + close(); + } + } + + @Override + public ByteBuffer onUpgradeFrom() + { + return buffer; + } + + private void upgrade(ByteBuffer buffer) + { + if (LOG.isDebugEnabled()) + LOG.debug("Read {}", BufferUtil.toDetailString(buffer)); + + EndPoint endPoint = getEndPoint(); + if (seemsTLS(buffer)) + { + if (LOG.isDebugEnabled()) + LOG.debug("Detected TLS bytes, upgrading to {}", sslConnectionFactory); + endPoint.upgrade(sslConnectionFactory.newConnection(connector, endPoint)); + } + else + { + if (plainProtocol != null) + { + ConnectionFactory connectionFactory = connector.getConnectionFactory(plainProtocol); + if (connectionFactory != null) + { + if (LOG.isDebugEnabled()) + LOG.debug("Detected plain bytes, upgrading to {}", connectionFactory); + Connection next = connectionFactory.newConnection(connector, endPoint); + endPoint.upgrade(next); + } + else + { + LOG.warn("Missing {} {} in {}", plainProtocol, ConnectionFactory.class.getSimpleName(), connector); + close(); + } + } + else + { + if (LOG.isDebugEnabled()) + LOG.debug("Detected plain bytes, but no configured protocol to upgrade to"); + unknownProtocol(buffer, endPoint); + } + } + } + } +} diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/PlainOrSslConnectionTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/PlainOrSslConnectionTest.java new file mode 100644 index 00000000000..6267190d014 --- /dev/null +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/PlainOrSslConnectionTest.java @@ -0,0 +1,189 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.server; + +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.function.Function; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.http.HttpTester; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.toolchain.test.MavenTestingUtils; +import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.eclipse.jetty.util.thread.QueuedThreadPool; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +public class PlainOrSslConnectionTest +{ + private Server server; + private ServerConnector connector; + + private void startServer(Function configFn, Handler handler) throws Exception + { + QueuedThreadPool serverThreads = new QueuedThreadPool(); + serverThreads.setName("server"); + server = new Server(serverThreads); + + String keystore = MavenTestingUtils.getTestResourceFile("keystore").getAbsolutePath(); + SslContextFactory sslContextFactory = new SslContextFactory(); + sslContextFactory.setKeyStorePath(keystore); + sslContextFactory.setKeyStorePassword("storepwd"); + sslContextFactory.setKeyManagerPassword("keypwd"); + + HttpConfiguration httpConfig = new HttpConfiguration(); + HttpConnectionFactory http = new HttpConnectionFactory(httpConfig); + SslConnectionFactory ssl = new SslConnectionFactory(sslContextFactory, http.getProtocol()); + PlainOrSslConnectionFactory plainOrSsl = configFn.apply(ssl); + connector = new ServerConnector(server, 1, 1, plainOrSsl, ssl, http); + server.addConnector(connector); + + server.setHandler(handler); + + server.start(); + } + + @AfterEach + public void stopServer() throws Exception + { + if (server != null) + server.stop(); + } + + private PlainOrSslConnectionFactory plainOrSsl(SslConnectionFactory ssl) + { + return new PlainOrSslConnectionFactory(ssl, ssl.getNextProtocol()); + } + + private PlainOrSslConnectionFactory plainToSslWithReport(SslConnectionFactory ssl) + { + return new PlainOrSslConnectionFactory(ssl, null) + { + @Override + protected void unknownProtocol(ByteBuffer buffer, EndPoint endPoint) + { + String response = "" + + "HTTP/1.1 400 Bad Request\r\n" + + "Content-Length: 0\r\n" + + "Connection: close\r\n" + + "\r\n"; + Callback.Completable callback = new Callback.Completable(); + endPoint.write(callback, ByteBuffer.wrap(response.getBytes(StandardCharsets.US_ASCII))); + callback.whenComplete((r, x) -> endPoint.close()); + } + }; + } + + @Test + public void testPlainOrSslConnection() throws Exception + { + startServer(this::plainOrSsl, new EmptyServerHandler()); + + String request = "" + + "GET / HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "\r\n"; + byte[] requestBytes = request.getBytes(StandardCharsets.US_ASCII); + + // Try first a plain text connection. + try (Socket plain = new Socket()) + { + plain.connect(new InetSocketAddress("localhost", connector.getLocalPort()), 1000); + OutputStream plainOutput = plain.getOutputStream(); + plainOutput.write(requestBytes); + plainOutput.flush(); + + plain.setSoTimeout(5000); + InputStream plainInput = plain.getInputStream(); + HttpTester.Response response = HttpTester.parseResponse(plainInput); + assertNotNull(response); + assertEquals(HttpStatus.OK_200, response.getStatus()); + } + + // Then try a SSL connection. + SslContextFactory sslContextFactory = new SslContextFactory(true); + sslContextFactory.start(); + try (Socket ssl = sslContextFactory.newSslSocket()) + { + ssl.connect(new InetSocketAddress("localhost", connector.getLocalPort()), 1000); + OutputStream sslOutput = ssl.getOutputStream(); + sslOutput.write(requestBytes); + sslOutput.flush(); + + ssl.setSoTimeout(5000); + InputStream sslInput = ssl.getInputStream(); + HttpTester.Response response = HttpTester.parseResponse(sslInput); + assertNotNull(response); + assertEquals(HttpStatus.OK_200, response.getStatus()); + } + finally + { + sslContextFactory.stop(); + } + } + + @Test + public void testPlainToSslWithReport() throws Exception + { + startServer(this::plainToSslWithReport, new EmptyServerHandler()); + + String request = "" + + "GET / HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "\r\n"; + byte[] requestBytes = request.getBytes(StandardCharsets.US_ASCII); + + // Send a plain text HTTP request to SSL port: we should get back a minimal HTTP response. + try (Socket plain = new Socket()) + { + plain.connect(new InetSocketAddress("localhost", connector.getLocalPort()), 1000); + OutputStream plainOutput = plain.getOutputStream(); + plainOutput.write(requestBytes); + plainOutput.flush(); + + plain.setSoTimeout(5000); + InputStream plainInput = plain.getInputStream(); + HttpTester.Response response = HttpTester.parseResponse(plainInput); + assertNotNull(response); + assertEquals(HttpStatus.BAD_REQUEST_400, response.getStatus()); + } + } + + private static class EmptyServerHandler extends AbstractHandler.ErrorDispatchHandler + { + @Override + protected void doNonErrorHandle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) + { + jettyRequest.setHandled(true); + } + } +} diff --git a/jetty-server/src/test/resources/jetty-logging.properties b/jetty-server/src/test/resources/jetty-logging.properties index 3f8368fa054..21db0759fe3 100644 --- a/jetty-server/src/test/resources/jetty-logging.properties +++ b/jetty-server/src/test/resources/jetty-logging.properties @@ -1,4 +1,5 @@ org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog #org.eclipse.jetty.LEVEL=DEBUG +#org.eclipse.jetty.server.LEVEL=DEBUG #org.eclipse.jetty.server.ConnectionLimit.LEVEL=DEBUG -#org.eclipse.jetty.server.AcceptRateLimit.LEVEL=DEBUG \ No newline at end of file +#org.eclipse.jetty.server.AcceptRateLimit.LEVEL=DEBUG From 5061a5ca307a59a3ff70d72002aa5a27d8cb1b8d Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Thu, 31 Jan 2019 17:15:07 +0100 Subject: [PATCH 13/22] Fixes #3311 - Ability to serve HTTP and HTTPS from the same port. Fixed handling of upgradeTo() in case of null buffers. Signed-off-by: Simone Bordet --- .../java/org/eclipse/jetty/io/AbstractEndPoint.java | 11 ++++++----- .../java/org/eclipse/jetty/io/ssl/SslConnection.java | 7 +++++-- .../java/org/eclipse/jetty/server/HttpConnection.java | 3 ++- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java index e5403752a7e..c7c4b32c5da 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java @@ -427,15 +427,16 @@ public abstract class AbstractEndPoint extends IdleTimeout implements EndPoint if (LOG.isDebugEnabled()) LOG.debug("{} upgrading from {} to {}", this, old_connection, newConnection); - ByteBuffer prefilled = (old_connection instanceof Connection.UpgradeFrom) - ?((Connection.UpgradeFrom)old_connection).onUpgradeFrom():null; + ByteBuffer buffer = (old_connection instanceof Connection.UpgradeFrom) ? + ((Connection.UpgradeFrom)old_connection).onUpgradeFrom() : + null; old_connection.onClose(); old_connection.getEndPoint().setConnection(newConnection); if (newConnection instanceof Connection.UpgradeTo) - ((Connection.UpgradeTo)newConnection).onUpgradeTo(prefilled); - else if (BufferUtil.hasContent(prefilled)) - throw new IllegalStateException(); + ((Connection.UpgradeTo)newConnection).onUpgradeTo(buffer); + else if (BufferUtil.hasContent(buffer)) + throw new IllegalStateException("Cannot upgrade: " + newConnection + " does not implement " + Connection.UpgradeTo.class.getName()); newConnection.onOpen(); } diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java index 1ed5ed23a09..53467589e95 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java @@ -269,8 +269,11 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr @Override public void onUpgradeTo(ByteBuffer buffer) { - acquireEncryptedInput(); - BufferUtil.append(_encryptedInput, buffer); + if (BufferUtil.hasContent(buffer)) + { + acquireEncryptedInput(); + BufferUtil.append(_encryptedInput, buffer); + } } @Override diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java index d620fd96ff5..f370e9b847d 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java @@ -199,7 +199,8 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http @Override public void onUpgradeTo(ByteBuffer buffer) { - BufferUtil.append(getRequestBuffer(), buffer); + if (BufferUtil.hasContent(buffer)) + BufferUtil.append(getRequestBuffer(), buffer); } @Override From d9855fb1bcd434432eb02fc9d8d4bcbf49a20c7d Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Fri, 1 Feb 2019 09:14:32 +0100 Subject: [PATCH 14/22] Fixes #3311 - Ability to serve HTTP and HTTPS from the same port. Do not assume that the read from the network produced more than 1 byte. Signed-off-by: Simone Bordet --- .../org/eclipse/jetty/server/PlainOrSslConnectionFactory.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/PlainOrSslConnectionFactory.java b/jetty-server/src/main/java/org/eclipse/jetty/server/PlainOrSslConnectionFactory.java index e8114b32dd7..d3d525f5924 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/PlainOrSslConnectionFactory.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/PlainOrSslConnectionFactory.java @@ -37,7 +37,6 @@ public class PlainOrSslConnectionFactory extends AbstractConnectionFactory private static final Logger LOG = Log.getLogger(PlainOrSslConnection.class); private static final int TLS_ALERT_FRAME_TYPE = 0x15; private static final int TLS_HANDSHAKE_FRAME_TYPE = 0x16; - private static final int TLS_MAJOR_VERSION = 3; private final SslConnectionFactory sslConnectionFactory; private final String plainProtocol; @@ -71,8 +70,7 @@ public class PlainOrSslConnectionFactory extends AbstractConnectionFactory protected boolean seemsTLS(ByteBuffer buffer) { int tlsFrameType = buffer.get(0) & 0xFF; - int tlsMajorVersion = buffer.get(1) & 0xFF; - return (tlsFrameType == TLS_HANDSHAKE_FRAME_TYPE || tlsFrameType == TLS_ALERT_FRAME_TYPE) && tlsMajorVersion == TLS_MAJOR_VERSION; + return tlsFrameType == TLS_HANDSHAKE_FRAME_TYPE || tlsFrameType == TLS_ALERT_FRAME_TYPE; } /** From 66873e863737260aa030d3422d015c44162a2967 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Fri, 1 Feb 2019 12:20:22 +0100 Subject: [PATCH 15/22] Code cleanups. Signed-off-by: Simone Bordet --- .../client/HttpClientAuthenticationTest.java | 157 +++++++++--------- 1 file changed, 74 insertions(+), 83 deletions(-) diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java index da66f466d51..9a2b22c2a55 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java @@ -18,13 +18,6 @@ package org.eclipse.jetty.client; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalToIgnoringCase; -import static org.hamcrest.Matchers.equalTo; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - import java.io.File; import java.io.IOException; import java.net.URI; @@ -38,7 +31,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.function.IntFunction; -import javax.servlet.ServletException; + import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -74,6 +67,13 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ArgumentsSource; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalToIgnoringCase; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest { private String realm = "TestRealm"; @@ -232,7 +232,7 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest private final AtomicInteger requests = new AtomicInteger(); @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException { baseRequest.setHandled(true); if (requests.incrementAndGet() == 1) @@ -272,7 +272,7 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest startBasic(scenario, new AbstractHandler() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException { baseRequest.setHandled(true); if (request.getRequestURI().endsWith("/redirect")) @@ -401,15 +401,11 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest .scheme(scenario.getScheme()) .path("/secure") .timeout(5, TimeUnit.SECONDS) - .send(new Response.CompleteListener() + .send(result -> { - @Override - public void onComplete(Result result) - { - assertTrue(result.isFailed()); - assertEquals(cause, result.getFailure().getMessage()); - latch.countDown(); - } + assertTrue(result.isFailed()); + assertEquals(cause, result.getFailure().getMessage()); + latch.countDown(); }); assertTrue(latch.await(5, TimeUnit.SECONDS)); @@ -480,7 +476,6 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest assertTrue(resultLatch.await(5, TimeUnit.SECONDS)); } - @ParameterizedTest @ArgumentsSource(ScenarioProvider.class) public void test_RequestFailsAfterResponse(Scenario scenario) throws Exception @@ -488,10 +483,9 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest startBasic(scenario, new EmptyServerHandler() { @Override - protected void service(String target, org.eclipse.jetty.server.Request jettyRequest, HttpServletRequest request, - HttpServletResponse response) throws IOException, ServletException + protected void service(String target, org.eclipse.jetty.server.Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException { - IO.readBytes(jettyRequest.getInputStream()); + IO.readBytes(jettyRequest.getInputStream()); } }); @@ -543,8 +537,9 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest { authLatch.await(); } - catch(InterruptedException e) - {} + catch(InterruptedException ignored) + { + } // Trigger request failure. throw new RuntimeException(); @@ -636,28 +631,27 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest HeaderInfo headerInfo = aph.getHeaderInfo("Digest realm=\"thermostat\", qop=\"auth\", nonce=\"1523430383\"").get(0); assertTrue(headerInfo.getType().equalsIgnoreCase("Digest")); - assertTrue(headerInfo.getParameter("qop").equals("auth")); - assertTrue(headerInfo.getParameter("realm").equals("thermostat")); - assertTrue(headerInfo.getParameter("nonce").equals("1523430383")); + assertEquals("auth", headerInfo.getParameter("qop")); + assertEquals("thermostat", headerInfo.getParameter("realm")); + assertEquals("1523430383", headerInfo.getParameter("nonce")); headerInfo = aph.getHeaderInfo("Digest qop=\"auth\", realm=\"thermostat\", nonce=\"1523430383\"").get(0); assertTrue(headerInfo.getType().equalsIgnoreCase("Digest")); - assertTrue(headerInfo.getParameter("qop").equals("auth")); - assertTrue(headerInfo.getParameter("realm").equals("thermostat")); - assertTrue(headerInfo.getParameter("nonce").equals("1523430383")); + assertEquals("auth", headerInfo.getParameter("qop")); + assertEquals("thermostat", headerInfo.getParameter("realm")); + assertEquals("1523430383", headerInfo.getParameter("nonce")); headerInfo = aph.getHeaderInfo("Digest qop=\"auth\", nonce=\"1523430383\", realm=\"thermostat\"").get(0); assertTrue(headerInfo.getType().equalsIgnoreCase("Digest")); - assertTrue(headerInfo.getParameter("qop").equals("auth")); - assertTrue(headerInfo.getParameter("realm").equals("thermostat")); - assertTrue(headerInfo.getParameter("nonce").equals("1523430383")); + assertEquals("auth", headerInfo.getParameter("qop")); + assertEquals("thermostat", headerInfo.getParameter("realm")); + assertEquals("1523430383", headerInfo.getParameter("nonce")); headerInfo = aph.getHeaderInfo("Digest qop=\"auth\", nonce=\"1523430383\"").get(0); assertTrue(headerInfo.getType().equalsIgnoreCase("Digest")); - assertTrue(headerInfo.getParameter("qop").equals("auth")); - assertTrue(headerInfo.getParameter("realm") == null); - assertTrue(headerInfo.getParameter("nonce").equals("1523430383")); - + assertEquals("auth", headerInfo.getParameter("qop")); + assertNull(headerInfo.getParameter("realm")); + assertEquals("1523430383", headerInfo.getParameter("nonce")); // test multiple authentications List headerInfoList = aph.getHeaderInfo("Digest qop=\"auth\", realm=\"thermostat\", nonce=\"1523430383\", " @@ -666,34 +660,34 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest + "Digest qop=\"auth4\", nonce=\"3526435321\""); assertTrue(headerInfoList.get(0).getType().equalsIgnoreCase("Digest")); - assertTrue(headerInfoList.get(0).getParameter("qop").equals("auth")); - assertTrue(headerInfoList.get(0).getParameter("realm").equals("thermostat")); - assertTrue(headerInfoList.get(0).getParameter("nonce").equals("1523430383")); + assertEquals("auth", headerInfoList.get(0).getParameter("qop")); + assertEquals("thermostat", headerInfoList.get(0).getParameter("realm")); + assertEquals("1523430383", headerInfoList.get(0).getParameter("nonce")); assertTrue(headerInfoList.get(1).getType().equalsIgnoreCase("Digest")); - assertTrue(headerInfoList.get(1).getParameter("qop").equals("auth2")); - assertTrue(headerInfoList.get(1).getParameter("realm").equals("thermostat2")); - assertTrue(headerInfoList.get(1).getParameter("nonce").equals("4522530354")); + assertEquals("auth2", headerInfoList.get(1).getParameter("qop")); + assertEquals("thermostat2", headerInfoList.get(1).getParameter("realm")); + assertEquals("4522530354", headerInfoList.get(1).getParameter("nonce")); assertTrue(headerInfoList.get(2).getType().equalsIgnoreCase("Digest")); - assertTrue(headerInfoList.get(2).getParameter("qop").equals("auth3")); - assertTrue(headerInfoList.get(2).getParameter("realm").equals("thermostat3")); - assertTrue(headerInfoList.get(2).getParameter("nonce").equals("9523570528")); + assertEquals("auth3", headerInfoList.get(2).getParameter("qop")); + assertEquals("thermostat3", headerInfoList.get(2).getParameter("realm")); + assertEquals("9523570528", headerInfoList.get(2).getParameter("nonce")); assertTrue(headerInfoList.get(3).getType().equalsIgnoreCase("Digest")); - assertTrue(headerInfoList.get(3).getParameter("qop").equals("auth4")); - assertTrue(headerInfoList.get(3).getParameter("realm") == null); - assertTrue(headerInfoList.get(3).getParameter("nonce").equals("3526435321")); + assertEquals("auth4", headerInfoList.get(3).getParameter("qop")); + assertNull(headerInfoList.get(3).getParameter("realm")); + assertEquals("3526435321", headerInfoList.get(3).getParameter("nonce")); List headerInfos = aph.getHeaderInfo("Newauth realm=\"apps\", type=1, title=\"Login to \\\"apps\\\"\", Basic realm=\"simple\""); assertTrue(headerInfos.get(0).getType().equalsIgnoreCase("Newauth")); - assertTrue(headerInfos.get(0).getParameter("realm").equals("apps")); - assertTrue(headerInfos.get(0).getParameter("type").equals("1")); + assertEquals("apps", headerInfos.get(0).getParameter("realm")); + assertEquals("1", headerInfos.get(0).getParameter("type")); assertEquals(headerInfos.get(0).getParameter("title"),"Login to \"apps\""); assertTrue(headerInfos.get(1).getType().equalsIgnoreCase("Basic")); - assertTrue(headerInfos.get(1).getParameter("realm").equals("simple")); + assertEquals("simple", headerInfos.get(1).getParameter("realm")); } @Test @@ -702,7 +696,7 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest HeaderInfo headerInfo = aph.getHeaderInfo("Scheme").get(0); assertTrue(headerInfo.getType().equalsIgnoreCase("Scheme")); - assertTrue(headerInfo.getParameter("realm") == null); + assertNull(headerInfo.getParameter("realm")); List headerInfos = aph.getHeaderInfo("Scheme1 , Scheme2 , Scheme3"); assertEquals(3, headerInfos.size()); @@ -712,51 +706,49 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest headerInfo = aph.getHeaderInfo("Scheme name=\"value\", other=\"value2\"").get(0); assertTrue(headerInfo.getType().equalsIgnoreCase("Scheme")); - assertTrue(headerInfo.getParameter("name").equals("value")); - assertTrue(headerInfo.getParameter("other").equals("value2")); + assertEquals("value", headerInfo.getParameter("name")); + assertEquals("value2", headerInfo.getParameter("other")); headerInfo = aph.getHeaderInfo("Scheme name = value , other = \"value2\" ").get(0); assertTrue(headerInfo.getType().equalsIgnoreCase("Scheme")); - assertTrue(headerInfo.getParameter("name").equals("value")); - assertTrue(headerInfo.getParameter("other").equals("value2")); + assertEquals("value", headerInfo.getParameter("name")); + assertEquals("value2", headerInfo.getParameter("other")); headerInfos = aph.getHeaderInfo(", , , , ,,,Scheme name=value, ,,Scheme2 name=value2,, ,,"); assertEquals(headerInfos.size(), 2); assertTrue(headerInfos.get(0).getType().equalsIgnoreCase("Scheme")); - assertTrue(headerInfos.get(0).getParameter("nAmE").equals("value")); + assertEquals("value", headerInfos.get(0).getParameter("nAmE")); assertTrue(headerInfos.get(1).getType().equalsIgnoreCase("Scheme2")); headerInfos = aph.getHeaderInfo("Scheme name=value, Scheme2 name=value2"); assertEquals(headerInfos.size(), 2); assertTrue(headerInfos.get(0).getType().equalsIgnoreCase("Scheme")); - assertTrue(headerInfos.get(0).getParameter("nAmE").equals("value")); + assertEquals("value", headerInfos.get(0).getParameter("nAmE")); assertThat(headerInfos.get(1).getType(), equalToIgnoringCase("Scheme2")); - assertTrue(headerInfos.get(1).getParameter("nAmE").equals("value2")); + assertEquals("value2", headerInfos.get(1).getParameter("nAmE")); headerInfos = aph.getHeaderInfo("Scheme , ,, ,, name=value, Scheme2 name=value2"); assertEquals(headerInfos.size(), 2); assertTrue(headerInfos.get(0).getType().equalsIgnoreCase("Scheme")); - assertTrue(headerInfos.get(0).getParameter("name").equals("value")); + assertEquals("value", headerInfos.get(0).getParameter("name")); assertTrue(headerInfos.get(1).getType().equalsIgnoreCase("Scheme2")); - assertTrue(headerInfos.get(1).getParameter("name").equals("value2")); + assertEquals("value2", headerInfos.get(1).getParameter("name")); //Negotiate with base64 Content headerInfo = aph.getHeaderInfo("Negotiate TlRMTVNTUAABAAAAB4IIogAAAAAAAAAAAAAAAAAAAAAFAs4OAAAADw==").get(0); assertTrue(headerInfo.getType().equalsIgnoreCase("Negotiate")); - assertTrue(headerInfo.getBase64().equals("TlRMTVNTUAABAAAAB4IIogAAAAAAAAAAAAAAAAAAAAAFAs4OAAAADw==")); + assertEquals("TlRMTVNTUAABAAAAB4IIogAAAAAAAAAAAAAAAAAAAAAFAs4OAAAADw==", headerInfo.getBase64()); headerInfos = aph.getHeaderInfo("Negotiate TlRMTVNTUAABAAAAAAAAAFAs4OAAAADw==, " + "Negotiate YIIJvwYGKwYBBQUCoIIJszCCCa+gJDAi="); assertTrue(headerInfos.get(0).getType().equalsIgnoreCase("Negotiate")); - assertTrue(headerInfos.get(0).getBase64().equals("TlRMTVNTUAABAAAAAAAAAFAs4OAAAADw==")); + assertEquals("TlRMTVNTUAABAAAAAAAAAFAs4OAAAADw==", headerInfos.get(0).getBase64()); assertTrue(headerInfos.get(1).getType().equalsIgnoreCase("Negotiate")); - assertTrue(headerInfos.get(1).getBase64().equals("YIIJvwYGKwYBBQUCoIIJszCCCa+gJDAi=")); + assertEquals("YIIJvwYGKwYBBQUCoIIJszCCCa+gJDAi=", headerInfos.get(1).getBase64()); } - - @Test public void testEqualsInParam() { @@ -765,10 +757,9 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest headerInfo = aph.getHeaderInfo("Digest realm=\"=the=rmo=stat=\", qop=\"=a=u=t=h=\", nonce=\"=1523430383=\"").get(0); assertTrue(headerInfo.getType().equalsIgnoreCase("Digest")); - assertTrue(headerInfo.getParameter("qop").equals("=a=u=t=h=")); - assertTrue(headerInfo.getParameter("realm").equals("=the=rmo=stat=")); - assertTrue(headerInfo.getParameter("nonce").equals("=1523430383=")); - + assertEquals("=a=u=t=h=", headerInfo.getParameter("qop")); + assertEquals("=the=rmo=stat=", headerInfo.getParameter("realm")); + assertEquals("=1523430383=", headerInfo.getParameter("nonce")); // test multiple authentications List headerInfoList = aph.getHeaderInfo("Digest qop=\"=au=th=\", realm=\"=ther=mostat=\", nonce=\"=152343=0383=\", " @@ -776,23 +767,23 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest + "Digest qop=\"auth3=\", nonce=\"9523570528=\", realm=\"thermostat3=\", "); assertTrue(headerInfoList.get(0).getType().equalsIgnoreCase("Digest")); - assertTrue(headerInfoList.get(0).getParameter("qop").equals("=au=th=")); - assertTrue(headerInfoList.get(0).getParameter("realm").equals("=ther=mostat=")); - assertTrue(headerInfoList.get(0).getParameter("nonce").equals("=152343=0383=")); + assertEquals("=au=th=", headerInfoList.get(0).getParameter("qop")); + assertEquals("=ther=mostat=", headerInfoList.get(0).getParameter("realm")); + assertEquals("=152343=0383=", headerInfoList.get(0).getParameter("nonce")); assertTrue(headerInfoList.get(1).getType().equalsIgnoreCase("Digest")); - assertTrue(headerInfoList.get(1).getParameter("qop").equals("=auth2")); - assertTrue(headerInfoList.get(1).getParameter("realm").equals("=thermostat2")); - assertTrue(headerInfoList.get(1).getParameter("nonce").equals("=4522530354")); + assertEquals("=auth2", headerInfoList.get(1).getParameter("qop")); + assertEquals("=thermostat2", headerInfoList.get(1).getParameter("realm")); + assertEquals("=4522530354", headerInfoList.get(1).getParameter("nonce")); assertTrue(headerInfoList.get(2).getType().equalsIgnoreCase("Digest")); - assertTrue(headerInfoList.get(2).getParameter("qop").equals("auth3=")); - assertTrue(headerInfoList.get(2).getParameter("realm").equals("thermostat3=")); - assertTrue(headerInfoList.get(2).getParameter("nonce").equals("9523570528=")); + assertEquals("auth3=", headerInfoList.get(2).getParameter("qop")); + assertEquals("thermostat3=", headerInfoList.get(2).getParameter("realm")); + assertEquals("9523570528=", headerInfoList.get(2).getParameter("nonce")); } @Test - public void testSingleChallangeLooksLikeMultipleChallenge() + public void testSingleChallengeLooksLikeMultipleChallenges() { AuthenticationProtocolHandler aph = new WWWAuthenticationProtocolHandler(client); List headerInfoList = aph.getHeaderInfo("Digest param=\",f \""); @@ -803,8 +794,8 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest HeaderInfo headerInfo = headerInfoList.get(0); assertTrue(headerInfo.getType().equalsIgnoreCase("Digest")); - assertTrue(headerInfo.getParameter("qop").equals(",Digest realm=hello")); - assertTrue(headerInfo.getParameter("realm").equals("thermostat")); + assertEquals(",Digest realm=hello", headerInfo.getParameter("qop")); + assertEquals("thermostat", headerInfo.getParameter("realm")); assertEquals(headerInfo.getParameter("nonce"), "1523430383="); } } From 33bceb3cc8f007ab8ddddba45b172a4f431ce0cb Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Fri, 1 Feb 2019 12:21:21 +0100 Subject: [PATCH 16/22] Fixes #3234 - AuthenticationProtocolHandler should not cache the failed results. Now only caching authentication results for 2xx and 3xx codes. Signed-off-by: Simone Bordet --- .../eclipse/jetty/client/AuthenticationProtocolHandler.java | 5 ++++- .../eclipse/jetty/client/HttpClientAuthenticationTest.java | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) 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 1fe2cd264cf..1388931e233 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 @@ -37,6 +37,7 @@ import org.eclipse.jetty.client.api.Result; import org.eclipse.jetty.client.util.BufferingResponseListener; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.QuotedCSV; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -309,7 +310,9 @@ public abstract class AuthenticationProtocolHandler implements ProtocolHandler @Override public void onSuccess(Response response) { - client.getAuthenticationStore().addAuthenticationResult(authenticationResult); + int status = response.getStatus(); + if (HttpStatus.isSuccess(status) || HttpStatus.isRedirection(status)) + client.getAuthenticationStore().addAuthenticationResult(authenticationResult); } } } diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java index 9a2b22c2a55..5e3307746a2 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java @@ -369,6 +369,9 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest ContentResponse response = request.timeout(5, TimeUnit.SECONDS).send(); assertNotNull(response); assertEquals(401, response.getStatus()); + + Authentication.Result authenticationResult = authenticationStore.findAuthenticationResult(uri); + assertNull(authenticationResult); } @ParameterizedTest From 803a45b43beda48aa9f399cb2e437d55365c31da Mon Sep 17 00:00:00 2001 From: "alexey.barsov" Date: Fri, 1 Feb 2019 15:38:33 +0100 Subject: [PATCH 17/22] Issue #3302 Supporting host:ip in X-Forwarded-For (fixing review remarks) Signed-off-by: alexey.barsov --- .../server/ForwardedRequestCustomizer.java | 35 +++++++++++-------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ForwardedRequestCustomizer.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ForwardedRequestCustomizer.java index c4761c49efa..bb37dd2a2fc 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/ForwardedRequestCustomizer.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ForwardedRequestCustomizer.java @@ -31,6 +31,8 @@ import org.eclipse.jetty.http.QuotedCSV; import org.eclipse.jetty.server.HttpConfiguration.Customizer; import org.eclipse.jetty.util.HostPort; import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; /* ------------------------------------------------------------ */ @@ -55,6 +57,8 @@ import org.eclipse.jetty.util.StringUtil; */ public class ForwardedRequestCustomizer implements Customizer { + private static final Logger LOG = Log.getLogger(ForwardedRequestCustomizer.class); + private HostPortHttpField _forcedHost; private String _forwardedHeader = HttpHeader.FORWARDED.toString(); private String _forwardedHostHeader = HttpHeader.X_FORWARDED_HOST.toString(); @@ -298,7 +302,7 @@ public class ForwardedRequestCustomizer implements Customizer RFC7239 rfc7239 = null; String forwardedHost = null; String forwardedServer = null; - String forwardedFor = null; + HostPort forwardedFor = null; String forwardedProto = null; String forwardedHttps = null; @@ -390,7 +394,7 @@ public class ForwardedRequestCustomizer implements Customizer } else if (forwardedFor != null) { - request.setRemoteAddr(InetSocketAddress.createUnresolved(forwardedFor,request.getRemotePort())); + request.setRemoteAddr(InetSocketAddress.createUnresolved(forwardedFor.getHost(), (forwardedFor.getPort() > 0) ? forwardedFor.getPort() : request.getRemotePort())); } // handle protocol identifier @@ -432,23 +436,24 @@ public class ForwardedRequestCustomizer implements Customizer return headerValue.substring(0,commaIndex).trim(); } - protected String getRemoteAddr(String headerValue) + protected HostPort getRemoteAddr(String headerValue) { String leftMost = getLeftMost(headerValue); - - if (leftMost != null && leftMost.contains(":")) { - try { - HostPort hostPort = new HostPort(leftMost); - if (hostPort.getPort() > 0) { - // address in a format host:port, return host part only. - return hostPort.getHost(); - } - } catch (Exception e) { - // failed to parse in host[:port] format, fallback to the value resolved from header - } + if (leftMost == null) + { + return null; } - return leftMost; + try + { + return new HostPort(leftMost); + } + catch (Exception e) + { + // failed to parse in host[:port] format + LOG.ignore(e); + return null; + } } @Override From 25afa868da838d0df0b172d28ba2adc18022c023 Mon Sep 17 00:00:00 2001 From: "alexey.barsov" Date: Fri, 1 Feb 2019 15:48:58 +0100 Subject: [PATCH 18/22] Issue #3302 Supporting host:ip in X-Forwarded-For (fixing unit tests) Signed-off-by: alexey.barsov --- .../jetty/server/ForwardedRequestCustomizerTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ForwardedRequestCustomizerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ForwardedRequestCustomizerTest.java index bd8857d89f3..2235203eb99 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/ForwardedRequestCustomizerTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ForwardedRequestCustomizerTest.java @@ -224,14 +224,14 @@ public class ForwardedRequestCustomizerTest String response=_connector.getResponse( "GET / HTTP/1.1\n"+ "Host: myhost\n"+ - "X-Forwarded-For: 10.9.8.7:1111,6.5.4.3:1111\n"+ + "X-Forwarded-For: 10.9.8.7:1111,6.5.4.3:2222\n"+ "\n"); assertThat(response, Matchers.containsString("200 OK")); assertEquals("http",_results.poll()); assertEquals("myhost",_results.poll()); assertEquals("80",_results.poll()); assertEquals("10.9.8.7",_results.poll()); - assertEquals("0",_results.poll()); + assertEquals("1111",_results.poll()); } @Test @@ -240,14 +240,14 @@ public class ForwardedRequestCustomizerTest String response=_connector.getResponse( "GET / HTTP/1.1\n"+ "Host: myhost\n"+ - "X-Forwarded-For: [2001:db8:cafe::17]:1111,6.5.4.3:1111\n"+ + "X-Forwarded-For: [2001:db8:cafe::17]:1111,6.5.4.3:2222\n"+ "\n"); assertThat(response, Matchers.containsString("200 OK")); assertEquals("http",_results.poll()); assertEquals("myhost",_results.poll()); assertEquals("80",_results.poll()); assertEquals("[2001:db8:cafe::17]",_results.poll()); - assertEquals("0",_results.poll()); + assertEquals("1111",_results.poll()); } @Test From 9af4707556a6d90e8dcc1ef6fb4ff55e63173e35 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Sat, 2 Feb 2019 18:56:58 +0100 Subject: [PATCH 19/22] Issue #3234 - AuthenticationProtocolHandler should not cache the failed results. Fixed failing test. Signed-off-by: Simone Bordet --- .../eclipse/jetty/client/util/BasicAuthentication.java | 3 ++- .../org/eclipse/jetty/client/HttpClientProxyTest.java | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/BasicAuthentication.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/BasicAuthentication.java index 4d8c2680f6a..50a2bdc344b 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/BasicAuthentication.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/BasicAuthentication.java @@ -103,7 +103,8 @@ public class BasicAuthentication extends AbstractAuthentication @Override public void apply(Request request) { - request.header(header, value); + if (!request.getHeaders().contains(header, value)) + request.header(header, value); } @Override 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 f46d58d330e..f28893ca4e8 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 @@ -18,8 +18,6 @@ package org.eclipse.jetty.client; -import static org.junit.jupiter.api.Assertions.assertEquals; - import java.io.IOException; import java.net.URI; import java.nio.charset.StandardCharsets; @@ -41,6 +39,8 @@ import org.eclipse.jetty.util.B64Code; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ArgumentsSource; +import static org.junit.jupiter.api.Assertions.assertEquals; + public class HttpClientProxyTest extends AbstractHttpClientServerTest { @ParameterizedTest @@ -315,7 +315,7 @@ public class HttpClientProxyTest extends AbstractHttpClientServerTest assertEquals(status, response1.getStatus()); assertEquals(3, requests.get()); - // Make again the request, authentication is cached, expect 204. + // Make again the request, only the server authentication is cached, expect 407 + 204. requests.set(0); ContentResponse response2 = client.newRequest(serverHost, serverPort) .scheme(scenario.getScheme()) @@ -323,7 +323,7 @@ public class HttpClientProxyTest extends AbstractHttpClientServerTest .send(); assertEquals(status, response2.getStatus()); - assertEquals(1, requests.get()); + assertEquals(2, requests.get()); } @ParameterizedTest From da490673af40832484939bf797e8822f341ed2ed Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Sun, 3 Feb 2019 14:24:07 +0100 Subject: [PATCH 20/22] Fixes #3311 - Ability to serve HTTP and HTTPS from the same port. Updated implementation and tests after reviews. Signed-off-by: Simone Bordet --- ...java => OptionalSslConnectionFactory.java} | 126 +++++++++++------- ...st.java => OptionalSslConnectionTest.java} | 103 ++++++++------ 2 files changed, 146 insertions(+), 83 deletions(-) rename jetty-server/src/main/java/org/eclipse/jetty/server/{PlainOrSslConnectionFactory.java => OptionalSslConnectionFactory.java} (52%) rename jetty-server/src/test/java/org/eclipse/jetty/server/{PlainOrSslConnectionTest.java => OptionalSslConnectionTest.java} (64%) diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/PlainOrSslConnectionFactory.java b/jetty-server/src/main/java/org/eclipse/jetty/server/OptionalSslConnectionFactory.java similarity index 52% rename from jetty-server/src/main/java/org/eclipse/jetty/server/PlainOrSslConnectionFactory.java rename to jetty-server/src/main/java/org/eclipse/jetty/server/OptionalSslConnectionFactory.java index d3d525f5924..7b75a01917a 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/PlainOrSslConnectionFactory.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/OptionalSslConnectionFactory.java @@ -20,47 +20,51 @@ package org.eclipse.jetty.server; import java.io.IOException; import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; import org.eclipse.jetty.io.AbstractConnection; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; /** *

    A ConnectionFactory whose connections detect whether the first bytes are - * TLS bytes and upgrades to either a TLS connection or to a plain connection.

    + * TLS bytes and upgrades to either a TLS connection or to another configurable + * connection.

    */ -public class PlainOrSslConnectionFactory extends AbstractConnectionFactory +public class OptionalSslConnectionFactory extends AbstractConnectionFactory { - private static final Logger LOG = Log.getLogger(PlainOrSslConnection.class); + private static final Logger LOG = Log.getLogger(OptionalSslConnection.class); private static final int TLS_ALERT_FRAME_TYPE = 0x15; private static final int TLS_HANDSHAKE_FRAME_TYPE = 0x16; + private static final int TLS_MAJOR_VERSION = 3; private final SslConnectionFactory sslConnectionFactory; - private final String plainProtocol; + private final String otherProtocol; /** - *

    Creates a new plain or TLS ConnectionFactory.

    - *

    If {@code plainProtocol} is {@code null}, and the first bytes are not TLS, then - * {@link #unknownProtocol(ByteBuffer, EndPoint)} is called; applications may override its - * behavior (by default it closes the EndPoint) for example by writing a minimal response.

    + *

    Creates a new ConnectionFactory whose connections can upgrade to TLS or another protocol.

    + *

    If {@code otherProtocol} is {@code null}, and the first bytes are not TLS, then + * {@link #otherProtocol(ByteBuffer, EndPoint)} is called.

    * * @param sslConnectionFactory The SslConnectionFactory to use if the first bytes are TLS - * @param plainProtocol the protocol of the ConnectionFactory to use if the first bytes are not TLS, or null. + * @param otherProtocol the protocol of the ConnectionFactory to use if the first bytes are not TLS, + * or null to explicitly handle the non-TLS case */ - public PlainOrSslConnectionFactory(SslConnectionFactory sslConnectionFactory, String plainProtocol) + public OptionalSslConnectionFactory(SslConnectionFactory sslConnectionFactory, String otherProtocol) { - super("plain|ssl"); + super("ssl|other"); this.sslConnectionFactory = sslConnectionFactory; - this.plainProtocol = plainProtocol; + this.otherProtocol = otherProtocol; } @Override public Connection newConnection(Connector connector, EndPoint endPoint) { - return configure(new PlainOrSslConnection(endPoint, connector), connector, endPoint); + return configure(new OptionalSslConnection(endPoint, connector), connector, endPoint); } /** @@ -70,39 +74,61 @@ public class PlainOrSslConnectionFactory extends AbstractConnectionFactory protected boolean seemsTLS(ByteBuffer buffer) { int tlsFrameType = buffer.get(0) & 0xFF; - return tlsFrameType == TLS_HANDSHAKE_FRAME_TYPE || tlsFrameType == TLS_ALERT_FRAME_TYPE; + int tlsMajorVersion = buffer.get(1) & 0xFF; + return (tlsFrameType == TLS_HANDSHAKE_FRAME_TYPE || tlsFrameType == TLS_ALERT_FRAME_TYPE) && tlsMajorVersion == TLS_MAJOR_VERSION; } /** - *

    Callback method invoked when {@code plainProtocol} is {@code null} + *

    Callback method invoked when {@code otherProtocol} is {@code null} * and the first bytes are not TLS.

    *

    This typically happens when a client is trying to connect to a TLS * port using the {@code http} scheme (and not the {@code https} scheme).

    - *

    This method may be overridden to write back a minimal response such as:

    - *
    -     * HTTP/1.1 400 Bad Request
    -     * Content-Length: 35
    -     * Content-Type: text/plain; charset=UTF8
    -     * Connection: close
    -     *
    -     * Plain HTTP request sent to TLS port
    -     * 
    * * @param buffer The buffer with the first bytes of the connection * @param endPoint The connection EndPoint object * @see #seemsTLS(ByteBuffer) */ - protected void unknownProtocol(ByteBuffer buffer, EndPoint endPoint) + protected void otherProtocol(ByteBuffer buffer, EndPoint endPoint) { - endPoint.close(); + // There are always at least 2 bytes. + int byte1 = buffer.get(0) & 0xFF; + int byte2 = buffer.get(1) & 0xFF; + if (byte1 == 'G' && byte2 == 'E') + { + // Plain text HTTP to a HTTPS port, + // write a minimal response. + String body = "" + + "\r\n" + + "\r\n" + + "Bad Request\r\n" + + "" + + "

    Bad Request

    " + + "

    HTTP request to HTTPS port

    " + + "\r\n" + + ""; + String response = "" + + "HTTP/1.1 400 Bad Request\r\n" + + "Content-Type: text/html\r\n" + + "Content-Length: " + body.length() + "\r\n" + + "Connection: close\r\n" + + "\r\n" + + body; + Callback.Completable completable = new Callback.Completable(); + endPoint.write(completable, ByteBuffer.wrap(response.getBytes(StandardCharsets.US_ASCII))); + completable.whenComplete((r, x) -> endPoint.close()); + } + else + { + endPoint.close(); + } } - private class PlainOrSslConnection extends AbstractConnection implements Connection.UpgradeFrom + private class OptionalSslConnection extends AbstractConnection implements Connection.UpgradeFrom { private final Connector connector; private final ByteBuffer buffer; - public PlainOrSslConnection(EndPoint endPoint, Connector connector) + public OptionalSslConnection(EndPoint endPoint, Connector connector) { super(endPoint, connector.getExecutor()); this.connector = connector; @@ -121,18 +147,28 @@ public class PlainOrSslConnectionFactory extends AbstractConnectionFactory { try { - int filled = getEndPoint().fill(buffer); - if (filled > 0) + while (true) { - upgrade(buffer); - } - else if (filled == 0) - { - fillInterested(); - } - else - { - close(); + int filled = getEndPoint().fill(buffer); + if (filled > 0) + { + // Always have at least 2 bytes. + if (BufferUtil.length(buffer) >= 2) + { + upgrade(buffer); + break; + } + } + else if (filled == 0) + { + fillInterested(); + break; + } + else + { + close(); + break; + } } } catch (IOException x) @@ -162,27 +198,27 @@ public class PlainOrSslConnectionFactory extends AbstractConnectionFactory } else { - if (plainProtocol != null) + if (otherProtocol != null) { - ConnectionFactory connectionFactory = connector.getConnectionFactory(plainProtocol); + ConnectionFactory connectionFactory = connector.getConnectionFactory(otherProtocol); if (connectionFactory != null) { if (LOG.isDebugEnabled()) - LOG.debug("Detected plain bytes, upgrading to {}", connectionFactory); + LOG.debug("Detected non-TLS bytes, upgrading to {}", connectionFactory); Connection next = connectionFactory.newConnection(connector, endPoint); endPoint.upgrade(next); } else { - LOG.warn("Missing {} {} in {}", plainProtocol, ConnectionFactory.class.getSimpleName(), connector); + LOG.warn("Missing {} {} in {}", otherProtocol, ConnectionFactory.class.getSimpleName(), connector); close(); } } else { if (LOG.isDebugEnabled()) - LOG.debug("Detected plain bytes, but no configured protocol to upgrade to"); - unknownProtocol(buffer, endPoint); + LOG.debug("Detected non-TLS bytes, but no other protocol to upgrade to"); + otherProtocol(buffer, endPoint); } } } diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/PlainOrSslConnectionTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/OptionalSslConnectionTest.java similarity index 64% rename from jetty-server/src/test/java/org/eclipse/jetty/server/PlainOrSslConnectionTest.java rename to jetty-server/src/test/java/org/eclipse/jetty/server/OptionalSslConnectionTest.java index 6267190d014..7a8e7a00e7e 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/PlainOrSslConnectionTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/OptionalSslConnectionTest.java @@ -22,7 +22,6 @@ import java.io.InputStream; import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.Socket; -import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.function.Function; @@ -31,10 +30,8 @@ import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpTester; -import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; -import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.junit.jupiter.api.AfterEach; @@ -43,12 +40,12 @@ import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -public class PlainOrSslConnectionTest +public class OptionalSslConnectionTest { private Server server; private ServerConnector connector; - private void startServer(Function configFn, Handler handler) throws Exception + private void startServer(Function configFn, Handler handler) throws Exception { QueuedThreadPool serverThreads = new QueuedThreadPool(); serverThreads.setName("server"); @@ -63,8 +60,8 @@ public class PlainOrSslConnectionTest HttpConfiguration httpConfig = new HttpConfiguration(); HttpConnectionFactory http = new HttpConnectionFactory(httpConfig); SslConnectionFactory ssl = new SslConnectionFactory(sslContextFactory, http.getProtocol()); - PlainOrSslConnectionFactory plainOrSsl = configFn.apply(ssl); - connector = new ServerConnector(server, 1, 1, plainOrSsl, ssl, http); + OptionalSslConnectionFactory sslOrOther = configFn.apply(ssl); + connector = new ServerConnector(server, 1, 1, sslOrOther, ssl, http); server.addConnector(connector); server.setHandler(handler); @@ -79,34 +76,20 @@ public class PlainOrSslConnectionTest server.stop(); } - private PlainOrSslConnectionFactory plainOrSsl(SslConnectionFactory ssl) + private OptionalSslConnectionFactory optionalSsl(SslConnectionFactory ssl) { - return new PlainOrSslConnectionFactory(ssl, ssl.getNextProtocol()); + return new OptionalSslConnectionFactory(ssl, ssl.getNextProtocol()); } - private PlainOrSslConnectionFactory plainToSslWithReport(SslConnectionFactory ssl) + private OptionalSslConnectionFactory optionalSslNoOtherProtocol(SslConnectionFactory ssl) { - return new PlainOrSslConnectionFactory(ssl, null) - { - @Override - protected void unknownProtocol(ByteBuffer buffer, EndPoint endPoint) - { - String response = "" + - "HTTP/1.1 400 Bad Request\r\n" + - "Content-Length: 0\r\n" + - "Connection: close\r\n" + - "\r\n"; - Callback.Completable callback = new Callback.Completable(); - endPoint.write(callback, ByteBuffer.wrap(response.getBytes(StandardCharsets.US_ASCII))); - callback.whenComplete((r, x) -> endPoint.close()); - } - }; + return new OptionalSslConnectionFactory(ssl, null); } @Test - public void testPlainOrSslConnection() throws Exception + public void testOptionalSslConnection() throws Exception { - startServer(this::plainOrSsl, new EmptyServerHandler()); + startServer(this::optionalSsl, new EmptyServerHandler()); String request = "" + "GET / HTTP/1.1\r\n" + @@ -152,9 +135,52 @@ public class PlainOrSslConnectionTest } @Test - public void testPlainToSslWithReport() throws Exception + public void testOptionalSslConnectionWithOnlyOneByteShouldIdleTimeout() throws Exception { - startServer(this::plainToSslWithReport, new EmptyServerHandler()); + startServer(this::optionalSsl, new EmptyServerHandler()); + long idleTimeout = 1000; + connector.setIdleTimeout(idleTimeout); + + try (Socket socket = new Socket()) + { + socket.connect(new InetSocketAddress("localhost", connector.getLocalPort()), 1000); + OutputStream output = socket.getOutputStream(); + output.write(0x16); + output.flush(); + + socket.setSoTimeout((int)(2 * idleTimeout)); + InputStream input = socket.getInputStream(); + int read = input.read(); + assertEquals(-1, read); + } + } + + @Test + public void testOptionalSslConnectionWithUnknownBytes() throws Exception + { + startServer(this::optionalSslNoOtherProtocol, new EmptyServerHandler()); + + try (Socket socket = new Socket()) + { + socket.connect(new InetSocketAddress("localhost", connector.getLocalPort()), 1000); + OutputStream output = socket.getOutputStream(); + output.write(0x00); + output.flush(); + Thread.sleep(500); + output.write(0x00); + output.flush(); + + socket.setSoTimeout(5000); + InputStream input = socket.getInputStream(); + int read = input.read(); + assertEquals(-1, read); + } + } + + @Test + public void testOptionalSslConnectionWithHTTPBytes() throws Exception + { + startServer(this::optionalSslNoOtherProtocol, new EmptyServerHandler()); String request = "" + "GET / HTTP/1.1\r\n" + @@ -162,17 +188,18 @@ public class PlainOrSslConnectionTest "\r\n"; byte[] requestBytes = request.getBytes(StandardCharsets.US_ASCII); - // Send a plain text HTTP request to SSL port: we should get back a minimal HTTP response. - try (Socket plain = new Socket()) + // Send a plain text HTTP request to SSL port, + // we should get back a minimal HTTP response. + try (Socket socket = new Socket()) { - plain.connect(new InetSocketAddress("localhost", connector.getLocalPort()), 1000); - OutputStream plainOutput = plain.getOutputStream(); - plainOutput.write(requestBytes); - plainOutput.flush(); + socket.connect(new InetSocketAddress("localhost", connector.getLocalPort()), 1000); + OutputStream output = socket.getOutputStream(); + output.write(requestBytes); + output.flush(); - plain.setSoTimeout(5000); - InputStream plainInput = plain.getInputStream(); - HttpTester.Response response = HttpTester.parseResponse(plainInput); + socket.setSoTimeout(5000); + InputStream input = socket.getInputStream(); + HttpTester.Response response = HttpTester.parseResponse(input); assertNotNull(response); assertEquals(HttpStatus.BAD_REQUEST_400, response.getStatus()); } From 77b7e275ccfe80f99ce332062fe42ff0a1530848 Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Mon, 4 Feb 2019 14:54:27 +1100 Subject: [PATCH 21/22] Issue #3298 - fix to pom file Signed-off-by: Lachlan Roberts --- jetty-websocket/jetty-websocket-tests/pom.xml | 47 ++++++++----------- 1 file changed, 20 insertions(+), 27 deletions(-) diff --git a/jetty-websocket/jetty-websocket-tests/pom.xml b/jetty-websocket/jetty-websocket-tests/pom.xml index 04ffd5cb160..bc7556acb69 100644 --- a/jetty-websocket/jetty-websocket-tests/pom.xml +++ b/jetty-websocket/jetty-websocket-tests/pom.xml @@ -8,15 +8,15 @@ 10.0.0-SNAPSHOT - 4.0.0 + 4.0.0 jetty-websocket-tests Jetty :: Websocket :: org.eclipse.jetty.websocket :: Tests - - ${project.groupId}.jetty.tests + + ${project.groupId}.jetty.websocket.tests - + org.eclipse.jetty.websocket jetty-websocket-api @@ -32,6 +32,11 @@ jetty-websocket-server ${project.version} + + org.eclipse.jetty.tests + jetty-http-tools + ${project.version} + org.eclipse.jetty.toolchain jetty-test-helper @@ -39,40 +44,28 @@ - + - org.apache.maven.plugins - maven-enforcer-plugin + org.apache.felix + maven-bundle-plugin + true - ban-java-servlet-api - enforce + manifest - - - - javax.servlet - servletapi - org.eclipse.jetty.orbit:javax.servlet - org.mortbay.jetty:servlet-api - jetty:servlet-api - - - + + jetty.websocket Integration Tests + + org.eclipse.jetty.websocket.jetty.tests.*;version="${parsedVersion.majorVersion}.${parsedVersion.minorVersion}.${parsedVersion.incrementalVersion}" + + - - org.apache.maven.plugins - maven-deploy-plugin - - true - - From bfcb890ab230556b148523843279d5f970bd447e Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Mon, 4 Feb 2019 15:05:15 +1100 Subject: [PATCH 22/22] Issue #3298 - rename typo in JettyWebSocketTest name Signed-off-by: Lachlan Roberts --- .../tests/{JettyWebsocketTest.java => JettyWebSocketTest.java} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/{JettyWebsocketTest.java => JettyWebSocketTest.java} (100%) diff --git a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/JettyWebsocketTest.java b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/JettyWebSocketTest.java similarity index 100% rename from jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/JettyWebsocketTest.java rename to jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/JettyWebSocketTest.java