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 4b6c25573b88..981e702285c5 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 @@ -48,6 +48,6 @@ public void selected(String protocol) if (protocol == null || !protocols.contains(protocol)) close(); else - completed(); + completed(protocol); } } 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 5922be7e4f96..83d7885e4cfa 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 @@ -111,4 +111,12 @@ public Connection newConnection(EndPoint endPoint, Map context) } throw new IllegalStateException("No ALPNProcessor for " + engine); } + + public static class ALPN extends Info + { + public ALPN(Executor executor, ClientConnectionFactory factory, List protocols) + { + super(List.of("alpn"), new ALPNClientConnectionFactory(executor, factory, protocols)); + } + } } diff --git a/jetty-alpn/jetty-alpn-conscrypt-client/src/test/java/org/eclipse/jetty/alpn/java/client/ConscryptHTTP2Client.java b/jetty-alpn/jetty-alpn-conscrypt-client/src/test/java/org/eclipse/jetty/alpn/java/client/ConscryptHTTP2Client.java deleted file mode 100644 index d1d714dfce69..000000000000 --- a/jetty-alpn/jetty-alpn-conscrypt-client/src/test/java/org/eclipse/jetty/alpn/java/client/ConscryptHTTP2Client.java +++ /dev/null @@ -1,89 +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.alpn.java.client; - -import java.net.InetSocketAddress; -import java.security.Security; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import org.conscrypt.OpenSSLProvider; -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.client.HTTP2Client; -import org.eclipse.jetty.http2.frames.DataFrame; -import org.eclipse.jetty.http2.frames.HeadersFrame; -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 ConscryptHTTP2Client -{ - public static void main(String[] args) throws Exception - { - Security.addProvider(new OpenSSLProvider()); - SslContextFactory sslContextFactory = new SslContextFactory(); - sslContextFactory.setProvider("Conscrypt"); - HTTP2Client client = new HTTP2Client(); - 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); - 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); - CountDownLatch latch = new CountDownLatch(1); - 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()) - latch.countDown(); - } - - @Override - public void onData(Stream stream, DataFrame frame, Callback callback) - { - System.err.println(frame); - callback.succeeded(); - if (frame.isEndStream()) - latch.countDown(); - } - }); - - latch.await(5, TimeUnit.SECONDS); - - client.stop(); - } -} 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 72a5aef503ac..7e818bbc520e 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 @@ -78,7 +78,6 @@ private SslContextFactory newSslContextFactory() sslContextFactory.setKeyStorePath("src/test/resources/keystore.jks"); sslContextFactory.setKeyStorePassword("OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4"); sslContextFactory.setKeyManagerPassword("OBF:1u2u1wml1z7s1z7a1wnl1u2g"); - sslContextFactory.setIncludeProtocols("TLSv1.2"); // The mandatory HTTP/2 cipher. sslContextFactory.setIncludeCipherSuites("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"); return sslContextFactory; diff --git a/jetty-client/pom.xml b/jetty-client/pom.xml index b078c636b3ed..2ce321e6e30a 100644 --- a/jetty-client/pom.xml +++ b/jetty-client/pom.xml @@ -112,6 +112,12 @@ jetty-io ${project.version} + + org.eclipse.jetty + jetty-alpn-client + ${project.version} + true + org.eclipse.jetty jetty-jmx diff --git a/jetty-client/src/main/java/module-info.java b/jetty-client/src/main/java/module-info.java index 92ca51083ca6..fcb2b0c2d7fd 100644 --- a/jetty-client/src/main/java/module-info.java +++ b/jetty-client/src/main/java/module-info.java @@ -20,8 +20,10 @@ { exports org.eclipse.jetty.client; exports org.eclipse.jetty.client.api; + exports org.eclipse.jetty.client.dynamic; exports org.eclipse.jetty.client.http; exports org.eclipse.jetty.client.jmx to org.eclipse.jetty.jmx; + exports org.eclipse.jetty.client.proxy; exports org.eclipse.jetty.client.util; requires org.eclipse.jetty.http; @@ -30,6 +32,8 @@ // Only required if using SPNEGO. requires static java.security.jgss; + // Only required if using the dynamic transport. + requires static org.eclipse.jetty.alpn.client; // Only required if using JMX. requires static org.eclipse.jetty.jmx; } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractConnectionPool.java b/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractConnectionPool.java index 78d2ee3aa5d3..55dff0d7c89f 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractConnectionPool.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractConnectionPool.java @@ -119,14 +119,14 @@ protected void tryCreate(int maxPending) if (LOG.isDebugEnabled()) LOG.debug("newConnection {}/{} connections {}/{} pending", total+1, maxConnections, pending+1, maxPending); - destination.newConnection(new Promise() + destination.newConnection(new Promise<>() { @Override public void succeeded(Connection connection) { if (LOG.isDebugEnabled()) - LOG.debug("Connection {}/{} creation succeeded {}", total+1, maxConnections, connection); - connections.add(-1,0); + LOG.debug("Connection {}/{} creation succeeded {}", total + 1, maxConnections, connection); + connections.add(-1, 0); onCreated(connection); proceed(); } @@ -135,8 +135,8 @@ public void succeeded(Connection connection) public void failed(Throwable x) { if (LOG.isDebugEnabled()) - LOG.debug("Connection " + (total+1) + "/" + maxConnections + " creation failed", x); - connections.add(-1,-1); + LOG.debug("Connection " + (total + 1) + "/" + maxConnections + " creation failed", x); + connections.add(-1, -1); requester.failed(x); } }); diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpDestinationOverHTTP.java b/jetty-client/src/main/java/org/eclipse/jetty/client/DuplexHttpDestination.java similarity index 56% rename from jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpDestinationOverHTTP.java rename to jetty-client/src/main/java/org/eclipse/jetty/client/DuplexHttpDestination.java index 85660039ea76..c5545c322ece 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpDestinationOverHTTP.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/DuplexHttpDestination.java @@ -16,25 +16,17 @@ // ======================================================================== // -package org.eclipse.jetty.client.http; +package org.eclipse.jetty.client; -import org.eclipse.jetty.client.HttpClient; -import org.eclipse.jetty.client.HttpExchange; -import org.eclipse.jetty.client.Origin; -import org.eclipse.jetty.client.PoolingHttpDestination; -import org.eclipse.jetty.client.SendFailure; -import org.eclipse.jetty.client.api.Connection; - -public class HttpDestinationOverHTTP extends PoolingHttpDestination +public class DuplexHttpDestination extends HttpDestination { - public HttpDestinationOverHTTP(HttpClient client, Origin origin) + public DuplexHttpDestination(HttpClient client, Origin origin) { - super(client, origin); + this(client, new Info(origin, null)); } - @Override - protected SendFailure send(Connection connection, HttpExchange exchange) + public DuplexHttpDestination(HttpClient client, Info info) { - return ((HttpConnectionOverHTTP)connection).send(exchange); + super(client, info); } } 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 24a301007185..d8415daf6b68 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 @@ -28,6 +28,7 @@ import java.net.URI; import java.nio.channels.SelectionKey; import java.nio.channels.SocketChannel; +import java.time.Duration; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -61,6 +62,7 @@ import org.eclipse.jetty.http.HttpScheme; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.ClientConnectionFactory; +import org.eclipse.jetty.io.ClientConnector; import org.eclipse.jetty.io.MappedByteBufferPool; import org.eclipse.jetty.io.ssl.SslClientConnectionFactory; import org.eclipse.jetty.util.Fields; @@ -122,19 +124,16 @@ public class HttpClient extends ContainerLifeCycle public static final String USER_AGENT = "Jetty/" + Jetty.VERSION; private static final Logger LOG = Log.getLogger(HttpClient.class); - private final ConcurrentMap destinations = new ConcurrentHashMap<>(); + private final ConcurrentMap destinations = new ConcurrentHashMap<>(); private final ProtocolHandlers handlers = new ProtocolHandlers(); private final List requestListeners = new ArrayList<>(); private final Set decoderFactories = new ContentDecoderFactorySet(); private final ProxyConfiguration proxyConfig = new ProxyConfiguration(); private final HttpClientTransport transport; - private final SslContextFactory sslContextFactory; + private final ClientConnector connector; private AuthenticationStore authenticationStore = new HttpAuthenticationStore(); private CookieManager cookieManager; private CookieStore cookieStore; - private Executor executor; - private ByteBufferPool byteBufferPool; - private Scheduler scheduler; private SocketAddressResolver resolver; private HttpField agentField = new HttpField(HttpHeader.USER_AGENT, USER_AGENT); private boolean followRedirects = true; @@ -143,48 +142,64 @@ public class HttpClient extends ContainerLifeCycle private int requestBufferSize = 4096; private int responseBufferSize = 16384; private int maxRedirects = 8; - private SocketAddress bindAddress; - private long connectTimeout = 15000; private long addressResolutionTimeout = 15000; - private long idleTimeout; private boolean tcpNoDelay = true; private boolean strictEventOrdering = false; private HttpField encodingField; private boolean removeIdleDestinations = false; - private boolean connectBlocking = false; private String name = getClass().getSimpleName() + "@" + Integer.toHexString(hashCode()); private HttpCompliance httpCompliance = HttpCompliance.RFC7230; private String defaultRequestContentType = "application/octet-stream"; /** - * 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. + * Creates a HttpClient instance that can perform HTTP/1.1 requests to non-TLS and TLS destinations. */ public HttpClient() { - this(null); + this(new HttpClientTransportOverHTTP()); } /** - * Creates a HttpClient instance that can perform requests to non-TLS and TLS destinations + * Creates a HttpClient instance that can perform HTTP 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 * @see #getSslContextFactory() + * @deprecated configure the SslContextFactory on the transport's {@link ClientConnector} */ + @Deprecated public HttpClient(SslContextFactory sslContextFactory) { this(new HttpClientTransportOverHTTP(), sslContextFactory); } + public HttpClient(HttpClientTransport transport) + { + this(null, transport); + } + + /** + * Creates a HttpClient instance that can perform HTTP requests with the given transport + * to non-TLS and TLS destinations (that is, both requests with the "http" scheme and with + * the "https" scheme). + * + * @param transport the transport that sends and receives HTTP requests and responses + * @param sslContextFactory the {@link SslContextFactory} that manages TLS encryption + * @deprecated configure the SslContextFactory on the transport's {@link ClientConnector} + */ + @Deprecated public HttpClient(HttpClientTransport transport, SslContextFactory sslContextFactory) { - this.transport = transport; + this(sslContextFactory, transport); + } + + private HttpClient(SslContextFactory sslContextFactory, HttpClientTransport transport) + { + this.transport = Objects.requireNonNull(transport); addBean(transport); - this.sslContextFactory = sslContextFactory; - addBean(sslContextFactory); + this.connector = ((AbstractHttpClientTransport)transport).getBean(ClientConnector.class); + if (sslContextFactory != null) + connector.setSslContextFactory(sslContextFactory); addBean(handlers); addBean(decoderFactories); } @@ -206,30 +221,31 @@ public HttpClientTransport getTransport() */ public SslContextFactory getSslContextFactory() { - return sslContextFactory; + return connector.getSslContextFactory(); } @Override protected void doStart() throws Exception { + Executor executor = getExecutor(); if (executor == null) { QueuedThreadPool threadPool = new QueuedThreadPool(); threadPool.setName(name); setExecutor(threadPool); } - + ByteBufferPool byteBufferPool = getByteBufferPool(); if (byteBufferPool == null) setByteBufferPool(new MappedByteBufferPool(2048, executor instanceof ThreadPool.SizedThreadPool ? ((ThreadPool.SizedThreadPool)executor).getMaxThreads() / 2 : ProcessorUtils.availableProcessors() * 2)); - + Scheduler scheduler = getScheduler(); if (scheduler == null) setScheduler(new ScheduledExecutorScheduler(name + "-scheduler", false)); if (resolver == null) - setSocketAddressResolver(new SocketAddressResolver.Async(executor, scheduler, getAddressResolutionTimeout())); + setSocketAddressResolver(new SocketAddressResolver.Async(getExecutor(), getScheduler(), getAddressResolutionTimeout())); handlers.put(new ContinueProtocolHandler()); handlers.put(new RedirectProtocolHandler(this)); @@ -291,6 +307,8 @@ public CookieStore getCookieStore() */ public void setCookieStore(CookieStore cookieStore) { + if (isStarted()) + throw new IllegalStateException(); this.cookieStore = Objects.requireNonNull(cookieStore); this.cookieManager = newCookieManager(); } @@ -319,6 +337,8 @@ public AuthenticationStore getAuthenticationStore() */ public void setAuthenticationStore(AuthenticationStore authenticationStore) { + if (isStarted()) + throw new IllegalStateException(); this.authenticationStore = authenticationStore; } @@ -526,7 +546,14 @@ public Destination getDestination(String scheme, String host, int port) return destinationFor(scheme, host, port); } + @Deprecated protected HttpDestination destinationFor(String scheme, String host, int port) + { + Origin origin = createOrigin(scheme, host, port); + return resolveDestination(new HttpDestination.Info(origin, null)); + } + + private Origin createOrigin(String scheme, String host, int port) { if (!HttpScheme.HTTP.is(scheme) && !HttpScheme.HTTPS.is(scheme) && !HttpScheme.WS.is(scheme) && !HttpScheme.WSS.is(scheme)) @@ -536,13 +563,18 @@ protected HttpDestination destinationFor(String scheme, String host, int port) host = host.toLowerCase(Locale.ENGLISH); port = normalizePort(scheme, port); - Origin origin = new Origin(scheme, host, port); - HttpDestination destination = destinations.get(origin); + return new Origin(scheme, host, port); + } + + private HttpDestination resolveDestination(HttpDestination.Info info) + { + HttpDestination destination = destinations.get(info); if (destination == null) { - destination = transport.newHttpDestination(origin); + destination = getTransport().newHttpDestination(info); + // Start the destination before it's published to other threads. addManaged(destination); - HttpDestination existing = destinations.putIfAbsent(origin, destination); + HttpDestination existing = destinations.putIfAbsent(info, destination); if (existing != null) { removeBean(destination); @@ -560,7 +592,7 @@ protected HttpDestination destinationFor(String scheme, String host, int port) protected boolean removeDestination(HttpDestination destination) { removeBean(destination); - return destinations.remove(destination.getOrigin(), destination); + return destinations.remove(destination.getInfo(), destination); } /** @@ -573,7 +605,15 @@ public List getDestinations() protected void send(final HttpRequest request, List listeners) { - HttpDestination destination = destinationFor(request.getScheme(), request.getHost(), request.getPort()); + Origin origin = createOrigin(request.getScheme(), request.getHost(), request.getPort()); + HttpDestination.Protocol protocol = null; + HttpClientTransport transport = getTransport(); + if (transport instanceof HttpClientTransport.Dynamic) + protocol = ((HttpClientTransport.Dynamic)transport).getProtocol(request); + if (LOG.isDebugEnabled()) + LOG.debug("Selected {} for {}", protocol, request); + HttpDestination.Info destinationInfo = new HttpDestination.Info(origin, protocol); + HttpDestination destination = resolveDestination(destinationInfo); destination.send(request, listeners); } @@ -636,7 +676,7 @@ protected ProtocolHandler findProtocolHandler(Request request, Response response */ public ByteBufferPool getByteBufferPool() { - return byteBufferPool; + return connector.getByteBufferPool(); } /** @@ -644,10 +684,7 @@ public ByteBufferPool getByteBufferPool() */ public void setByteBufferPool(ByteBufferPool byteBufferPool) { - if (isStarted()) - LOG.warn("Calling setByteBufferPool() while started is deprecated"); - updateBean(this.byteBufferPool, byteBufferPool); - this.byteBufferPool = byteBufferPool; + connector.setByteBufferPool(byteBufferPool); } /** @@ -677,7 +714,7 @@ public void setName(String name) @ManagedAttribute("The timeout, in milliseconds, for connect() operations") public long getConnectTimeout() { - return connectTimeout; + return connector.getConnectTimeout().toMillis(); } /** @@ -686,7 +723,7 @@ public long getConnectTimeout() */ public void setConnectTimeout(long connectTimeout) { - this.connectTimeout = connectTimeout; + connector.setConnectTimeout(Duration.ofMillis(connectTimeout)); } /** @@ -718,7 +755,7 @@ public void setAddressResolutionTimeout(long addressResolutionTimeout) @ManagedAttribute("The timeout, in milliseconds, to close idle connections") public long getIdleTimeout() { - return idleTimeout; + return connector.getIdleTimeout().toMillis(); } /** @@ -726,7 +763,7 @@ public long getIdleTimeout() */ public void setIdleTimeout(long idleTimeout) { - this.idleTimeout = idleTimeout; + connector.setIdleTimeout(Duration.ofMillis(idleTimeout)); } /** @@ -735,7 +772,7 @@ public void setIdleTimeout(long idleTimeout) */ public SocketAddress getBindAddress() { - return bindAddress; + return connector.getBindAddress(); } /** @@ -745,7 +782,7 @@ public SocketAddress getBindAddress() */ public void setBindAddress(SocketAddress bindAddress) { - this.bindAddress = bindAddress; + connector.setBindAddress(bindAddress); } /** @@ -790,7 +827,7 @@ public void setFollowRedirects(boolean follow) */ public Executor getExecutor() { - return executor; + return connector.getExecutor(); } /** @@ -798,10 +835,7 @@ public Executor getExecutor() */ public void setExecutor(Executor executor) { - if (isStarted()) - LOG.warn("Calling setExecutor() while started is deprecated"); - updateBean(this.executor, executor); - this.executor = executor; + connector.setExecutor(executor); } /** @@ -809,7 +843,7 @@ public void setExecutor(Executor executor) */ public Scheduler getScheduler() { - return scheduler; + return connector.getScheduler(); } /** @@ -817,10 +851,7 @@ public Scheduler getScheduler() */ public void setScheduler(Scheduler scheduler) { - if (isStarted()) - LOG.warn("Calling setScheduler() while started is deprecated"); - updateBean(this.scheduler, scheduler); - this.scheduler = scheduler; + connector.setScheduler(scheduler); } /** @@ -837,7 +868,7 @@ public SocketAddressResolver getSocketAddressResolver() public void setSocketAddressResolver(SocketAddressResolver resolver) { if (isStarted()) - LOG.warn("Calling setSocketAddressResolver() while started is deprecated"); + throw new IllegalStateException(); updateBean(this.resolver, resolver); this.resolver = resolver; } @@ -1059,7 +1090,7 @@ public void setRemoveIdleDestinations(boolean removeIdleDestinations) @ManagedAttribute("Whether the connect() operation is blocking") public boolean isConnectBlocking() { - return connectBlocking; + return connector.isConnectBlocking(); } /** @@ -1074,7 +1105,7 @@ public boolean isConnectBlocking() */ public void setConnectBlocking(boolean connectBlocking) { - this.connectBlocking = connectBlocking; + connector.setConnectBlocking(connectBlocking); } /** @@ -1109,7 +1140,7 @@ protected HttpField getAcceptEncodingField() protected String normalizeHost(String host) { - if (host != null && host.matches("\\[.*\\]")) + if (host != null && host.matches("\\[.*]")) return host.substring(1, host.length() - 1); return host; } 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 7d2366a19fd0..17e1590e8167 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 @@ -50,6 +50,8 @@ public interface HttpClientTransport extends ClientConnectionFactory */ public void setHttpClient(HttpClient client); + public HttpDestination newHttpDestination(HttpDestination.Info info); + /** * Creates a new, transport-specific, {@link HttpDestination} object. *

@@ -58,8 +60,13 @@ public interface HttpClientTransport extends ClientConnectionFactory * * @param origin the destination origin * @return a new, transport-specific, {@link HttpDestination} object + * @deprecated use {@link #newHttpDestination(HttpDestination.Info)} instead */ - public HttpDestination newHttpDestination(Origin origin); + @Deprecated + public default HttpDestination newHttpDestination(Origin origin) + { + return newHttpDestination(new HttpDestination.Info(origin, null)); + } /** * Establishes a physical connection to the given {@code address}. @@ -78,4 +85,10 @@ public interface HttpClientTransport extends ClientConnectionFactory * @param factory the factory for ConnectionPool instances */ public void setConnectionPoolFactory(ConnectionPool.Factory factory); + + @FunctionalInterface + public interface Dynamic + { + public HttpDestination.Protocol getProtocol(HttpRequest request); + } } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java index de3c4576da45..a4a264e83425 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java @@ -27,7 +27,6 @@ import java.util.concurrent.TimeoutException; import org.eclipse.jetty.client.api.Authentication; -import org.eclipse.jetty.client.api.Connection; import org.eclipse.jetty.client.api.ContentProvider; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; @@ -38,7 +37,7 @@ import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; -public abstract class HttpConnection implements Connection +public abstract class HttpConnection implements IConnection { private static final Logger LOG = Log.getLogger(HttpConnection.class); @@ -80,8 +79,6 @@ public void send(Request request, Response.CompleteListener listener) httpRequest.abort(result.failure); } - protected abstract SendFailure send(HttpExchange exchange); - protected void normalizeRequest(Request request) { HttpVersion version = request.getVersion(); diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java index 58fde88a4892..a6c5c6104db3 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java @@ -23,11 +23,13 @@ import java.nio.channels.AsynchronousCloseException; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.Queue; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Function; import org.eclipse.jetty.client.api.Connection; import org.eclipse.jetty.client.api.Destination; @@ -52,12 +54,12 @@ import org.eclipse.jetty.util.thread.Sweeper; @ManagedObject -public abstract class HttpDestination extends ContainerLifeCycle implements Destination, Closeable, Callback, Dumpable +public class HttpDestination extends ContainerLifeCycle implements Destination, Closeable, Callback, Dumpable { protected static final Logger LOG = Log.getLogger(HttpDestination.class); private final HttpClient client; - private final Origin origin; + private final Info info; private final Queue exchanges; private final RequestNotifier requestNotifier; private final ResponseNotifier responseNotifier; @@ -67,10 +69,21 @@ public abstract class HttpDestination extends ContainerLifeCycle implements Dest private final TimeoutTask timeout; private ConnectionPool connectionPool; + @Deprecated public HttpDestination(HttpClient client, Origin origin) + { + this(client, new Info(origin, null)); + } + + public HttpDestination(HttpClient client, Info info) + { + this(client, info, Function.identity()); + } + + public HttpDestination(HttpClient client, Info info, Function factoryFn) { this.client = client; - this.origin = origin; + this.info = info; this.exchanges = newExchangeQueue(client); @@ -79,9 +92,21 @@ public HttpDestination(HttpClient client, Origin origin) this.timeout = new TimeoutTask(client.getScheduler()); + String host = HostPort.normalizeHost(getHost()); + if (!client.isDefaultPort(getScheme(), getPort())) + host += ":" + getPort(); + hostField = new HttpField(HttpHeader.HOST, host); + ProxyConfiguration proxyConfig = client.getProxyConfiguration(); - proxy = proxyConfig.match(origin); - ClientConnectionFactory connectionFactory = client.getTransport(); + this.proxy = proxyConfig.match(getOrigin()); + + this.connectionFactory = factoryFn.apply(createClientConnectionFactory()); + } + + private ClientConnectionFactory createClientConnectionFactory() + { + ProxyConfiguration.Proxy proxy = getProxy(); + ClientConnectionFactory connectionFactory = getHttpClient().getTransport(); if (proxy != null) { connectionFactory = proxy.newClientConnectionFactory(connectionFactory); @@ -93,12 +118,7 @@ public HttpDestination(HttpClient client, Origin origin) if (isSecure()) connectionFactory = newSslClientConnectionFactory(connectionFactory); } - this.connectionFactory = connectionFactory; - - String host = HostPort.normalizeHost(getHost()); - if (!client.isDefaultPort(getScheme(), getPort())) - host += ":" + getPort(); - hostField = new HttpField(HttpHeader.HOST, host); + return connectionFactory; } @Override @@ -147,9 +167,14 @@ public HttpClient getHttpClient() return client; } + public Info getInfo() + { + return info; + } + public Origin getOrigin() { - return origin; + return info.origin; } public Queue getHttpExchanges() @@ -181,7 +206,7 @@ public ClientConnectionFactory getClientConnectionFactory() @ManagedAttribute(value = "The destination scheme", readonly = true) public String getScheme() { - return origin.getScheme(); + return getOrigin().getScheme(); } @Override @@ -190,14 +215,14 @@ public String getHost() { // InetSocketAddress.getHostString() transforms the host string // in case of IPv6 addresses, so we return the original host string - return origin.getAddress().getHost(); + return getOrigin().getAddress().getHost(); } @Override @ManagedAttribute(value = "The destination port", readonly = true) public int getPort() { - return origin.getAddress().getPort(); + return getOrigin().getAddress().getPort(); } @ManagedAttribute(value = "The number of queued requests", readonly = true) @@ -208,7 +233,7 @@ public int getQueuedRequestCount() public Origin.Address getConnectAddress() { - return proxy == null ? origin.getAddress() : proxy.getAddress(); + return proxy == null ? getOrigin().getAddress() : proxy.getAddress(); } public HttpField getHostField() @@ -343,7 +368,7 @@ public boolean process(final Connection connection) } else { - SendFailure result = send(connection, exchange); + SendFailure result = ((IConnection)connection).send(exchange); if (result != null) { if (LOG.isDebugEnabled()) @@ -358,8 +383,6 @@ public boolean process(final Connection connection) } } - protected abstract SendFailure send(Connection connection, HttpExchange exchange); - @Override public void newConnection(Promise promise) { @@ -476,7 +499,7 @@ public void dump(Appendable out, String indent) throws IOException public String asString() { - return origin.asString(); + return getInfo().asString(); } @Override @@ -490,7 +513,127 @@ public String toString() exchanges.size(), connectionPool); } - + + @FunctionalInterface + public interface Multiplexed + { + void setMaxRequestsPerConnection(int maxRequestsPerConnection); + } + + public static class Info + { + private final Origin origin; + private final Protocol protocol; + + public Info(Origin origin, Protocol protocol) + { + this.origin = origin; + this.protocol = protocol; + } + + public Origin getOrigin() + { + return origin; + } + + public Protocol getProtocol() + { + return protocol; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + return true; + if (obj == null || getClass() != obj.getClass()) + return false; + Info that = (Info)obj; + return origin.equals(that.origin) && Objects.equals(protocol, that.protocol); + } + + @Override + public int hashCode() + { + return Objects.hash(origin, protocol); + } + + public String asString() + { + return String.format("%s|%s", origin.asString(), protocol == null ? "null" : protocol.asString()); + } + + @Override + public String toString() + { + return String.format("%s@%x[%s]", getClass().getSimpleName(), hashCode(), asString()); + } + } + + public static class Protocol + { + private final List protocols; + private final boolean negotiate; + private final String kind; + + public Protocol(List protocols, boolean negotiate) + { + this(protocols, negotiate, null); + } + + public Protocol(List protocols, boolean negotiate, String kind) + { + this.protocols = protocols; + this.negotiate = negotiate; + this.kind = kind; + } + + public List getProtocols() + { + return protocols; + } + + public boolean isNegotiate() + { + return negotiate; + } + + public String getKind() + { + return kind; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + return true; + if (obj == null || getClass() != obj.getClass()) + return false; + Protocol that = (Protocol)obj; + return protocols.equals(that.protocols) && + negotiate == that.negotiate && + Objects.equals(kind, that.kind); + } + + @Override + public int hashCode() + { + return Objects.hash(protocols, negotiate, kind); + } + + public String asString() + { + return String.format("proto=%s,nego=%b,kind=%s", protocols, negotiate, kind); + } + + @Override + public String toString() + { + return String.format("%s@%x[%s]", getClass().getSimpleName(), hashCode(), asString()); + } + } + // The TimeoutTask that expires when the next check of expiry is needed private class TimeoutTask extends CyclicTimeout { diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/PoolingHttpDestination.java b/jetty-client/src/main/java/org/eclipse/jetty/client/IConnection.java similarity index 82% rename from jetty-client/src/main/java/org/eclipse/jetty/client/PoolingHttpDestination.java rename to jetty-client/src/main/java/org/eclipse/jetty/client/IConnection.java index 1da6f1e48595..0e935aba1e16 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/PoolingHttpDestination.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/IConnection.java @@ -18,10 +18,9 @@ package org.eclipse.jetty.client; -public abstract class PoolingHttpDestination extends HttpDestination +import org.eclipse.jetty.client.api.Connection; + +public interface IConnection extends Connection { - public PoolingHttpDestination(HttpClient client, Origin origin) - { - super(client, origin); - } + public SendFailure send(HttpExchange exchange); } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/MultiplexConnectionPool.java b/jetty-client/src/main/java/org/eclipse/jetty/client/MultiplexConnectionPool.java index 05179ffd4217..cfdfbd530a9f 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/MultiplexConnectionPool.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/MultiplexConnectionPool.java @@ -22,11 +22,10 @@ import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; -import java.util.HashMap; import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; import org.eclipse.jetty.client.api.Connection; @@ -41,11 +40,9 @@ public class MultiplexConnectionPool extends AbstractConnectionPool implements C { private static final Logger LOG = Log.getLogger(MultiplexConnectionPool.class); - private final ReentrantLock lock = new ReentrantLock(); private final HttpDestination destination; private final Deque idleConnections; - private final Map muxedConnections; - private final Map busyConnections; + private final Map activeConnections; private int maxMultiplex; public MultiplexConnectionPool(HttpDestination destination, int maxConnections, Callback requester, int maxMultiplex) @@ -53,8 +50,7 @@ public MultiplexConnectionPool(HttpDestination destination, int maxConnections, super(destination, maxConnections, requester); this.destination = destination; this.idleConnections = new ArrayDeque<>(maxConnections); - this.muxedConnections = new HashMap<>(maxConnections); - this.busyConnections = new HashMap<>(maxConnections); + this.activeConnections = new LinkedHashMap<>(maxConnections); this.maxMultiplex = maxMultiplex; } @@ -69,120 +65,73 @@ public Connection acquire() connection = activate(); } return connection; - } - - protected void lock() - { - lock.lock(); - } - - protected void unlock() - { - lock.unlock(); } @Override public int getMaxMultiplex() { - lock(); - try + synchronized (this) { return maxMultiplex; } - finally - { - unlock(); - } } @Override public void setMaxMultiplex(int maxMultiplex) { - lock(); - try + synchronized (this) { this.maxMultiplex = maxMultiplex; } - finally - { - unlock(); - } } @Override public boolean isActive(Connection connection) { - lock(); - try + synchronized (this) { - if (muxedConnections.containsKey(connection)) - return true; - if (busyConnections.containsKey(connection)) - return true; - return false; - } - finally - { - unlock(); + return activeConnections.containsKey(connection); } } @Override protected void onCreated(Connection connection) { - lock(); - try + synchronized (this) { // Use "cold" connections as last. idleConnections.offer(new Holder(connection)); } - finally - { - unlock(); - } - idle(connection, false); } @Override protected Connection activate() { - Holder holder; - lock(); - try + Holder result = null; + synchronized (this) { - while (true) + for (Holder holder : activeConnections.values()) { - if (muxedConnections.isEmpty()) - { - holder = idleConnections.poll(); - if (holder == null) - return null; - muxedConnections.put(holder.connection, holder); - } - else - { - holder = muxedConnections.values().iterator().next(); - } - if (holder.count < maxMultiplex) { - ++holder.count; + result = holder; break; } - else - { - muxedConnections.remove(holder.connection); - busyConnections.put(holder.connection, holder); - } } - } - finally - { - unlock(); - } - return active(holder.connection); + if (result == null) + { + Holder holder = idleConnections.poll(); + if (holder == null) + return null; + activeConnections.put(holder.connection, holder); + result = holder; + } + + ++result.count; + } + return active(result.connection); } @Override @@ -191,16 +140,15 @@ public boolean release(Connection connection) boolean closed = isClosed(); boolean idle = false; Holder holder; - lock(); - try + synchronized (this) { - holder = muxedConnections.get(connection); + holder = activeConnections.get(connection); if (holder != null) { int count = --holder.count; if (count == 0) { - muxedConnections.remove(connection); + activeConnections.remove(connection); if (!closed) { idleConnections.offerFirst(holder); @@ -208,32 +156,7 @@ public boolean release(Connection connection) } } } - else - { - holder = busyConnections.remove(connection); - if (holder != null) - { - int count = --holder.count; - if (!closed) - { - if (count == 0) - { - idleConnections.offerFirst(holder); - idle = true; - } - else - { - muxedConnections.put(connection, holder); - } - } - } - } - } - finally - { - unlock(); } - if (holder == null) return false; @@ -253,16 +176,13 @@ protected boolean remove(Connection connection, boolean force) { boolean activeRemoved = true; boolean idleRemoved = false; - lock(); - try + synchronized (this) { - Holder holder = muxedConnections.remove(connection); - if (holder == null) - holder = busyConnections.remove(connection); + Holder holder = activeConnections.remove(connection); if (holder == null) { activeRemoved = false; - for (Iterator iterator = idleConnections.iterator(); iterator.hasNext();) + for (Iterator iterator = idleConnections.iterator(); iterator.hasNext(); ) { holder = iterator.next(); if (holder.connection == connection) @@ -274,11 +194,6 @@ protected boolean remove(Connection connection, boolean force) } } } - finally - { - unlock(); - } - if (activeRemoved || force) released(connection); boolean removed = activeRemoved || idleRemoved || force; @@ -291,65 +206,39 @@ protected boolean remove(Connection connection, boolean force) public void close() { super.close(); - List connections; - lock(); - try + synchronized (this) { connections = idleConnections.stream().map(holder -> holder.connection).collect(Collectors.toList()); - connections.addAll(muxedConnections.keySet()); - connections.addAll(busyConnections.keySet()); + connections.addAll(activeConnections.keySet()); } - finally - { - unlock(); - } - close(connections); } @Override public void dump(Appendable out, String indent) throws IOException { - DumpableCollection busy; - DumpableCollection muxed; + DumpableCollection active; DumpableCollection idle; - lock(); - try + synchronized (this) { - busy = new DumpableCollection("busy", new ArrayList<>(busyConnections.values())); - muxed = new DumpableCollection("muxed", new ArrayList<>(muxedConnections.values())); + active = new DumpableCollection("active", new ArrayList<>(activeConnections.values())); idle = new DumpableCollection("idle", new ArrayList<>(idleConnections)); } - finally - { - unlock(); - } - - Dumpable.dumpObjects(out, indent, this, busy, muxed, idle); + Dumpable.dumpObjects(out, indent, this, active, idle); } @Override public boolean sweep() { List toSweep = new ArrayList<>(); - lock(); - try + synchronized (this) { - busyConnections.values().stream() - .map(holder -> holder.connection) - .filter(connection -> connection instanceof Sweeper.Sweepable) - .collect(Collectors.toCollection(() -> toSweep)); - muxedConnections.values().stream() + activeConnections.values().stream() .map(holder -> holder.connection) .filter(connection -> connection instanceof Sweeper.Sweepable) .collect(Collectors.toCollection(() -> toSweep)); } - finally - { - unlock(); - } - for (Connection connection : toSweep) { if (((Sweeper.Sweepable)connection).sweep()) @@ -363,34 +252,26 @@ public boolean sweep() dump()); } } - return false; } @Override public String toString() { - int busySize; - int muxedSize; + int activeSize; int idleSize; - lock(); - try + synchronized (this) { - busySize = busyConnections.size(); - muxedSize = muxedConnections.size(); + activeSize = activeConnections.size(); idleSize = idleConnections.size(); } - finally - { - unlock(); - } - return String.format("%s@%x[c=%d/%d,b=%d,m=%d,i=%d]", + return String.format("%s@%x[connections=%d/%d,multiplex=%d,active=%d,idle=%d]", getClass().getSimpleName(), hashCode(), getConnectionCount(), getMaxConnectionCount(), - busySize, - muxedSize, + getMaxMultiplex(), + activeSize, idleSize); } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/MultiplexHttpDestination.java b/jetty-client/src/main/java/org/eclipse/jetty/client/MultiplexHttpDestination.java index 544422c3dc98..1ee516710cb8 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/MultiplexHttpDestination.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/MultiplexHttpDestination.java @@ -18,13 +18,24 @@ package org.eclipse.jetty.client; -public abstract class MultiplexHttpDestination extends HttpDestination +import java.util.function.Function; + +import org.eclipse.jetty.io.ClientConnectionFactory; +import org.eclipse.jetty.util.annotation.ManagedAttribute; + +public class MultiplexHttpDestination extends HttpDestination implements HttpDestination.Multiplexed { - protected MultiplexHttpDestination(HttpClient client, Origin origin) + public MultiplexHttpDestination(HttpClient client, Info info) + { + this(client, info, Function.identity()); + } + + public MultiplexHttpDestination(HttpClient client, Info info, Function factoryFn) { - super(client, origin); + super(client, info, factoryFn); } + @ManagedAttribute(value = "The maximum number of concurrent requests per connection") public int getMaxRequestsPerConnection() { ConnectionPool connectionPool = getConnectionPool(); 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 344b1c16edb5..8c364dba568d 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 @@ -75,7 +75,7 @@ public String asString() @Override public String toString() { - return asString(); + return String.format("%s@%x[%s]", getClass().getSimpleName(), hashCode(), asString()); } public static class Address diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/dynamic/HttpClientTransportDynamic.java b/jetty-client/src/main/java/org/eclipse/jetty/client/dynamic/HttpClientTransportDynamic.java new file mode 100644 index 000000000000..82c5207e7a12 --- /dev/null +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/dynamic/HttpClientTransportDynamic.java @@ -0,0 +1,142 @@ +// +// ======================================================================== +// 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.client.dynamic; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import org.eclipse.jetty.alpn.client.ALPNClientConnection; +import org.eclipse.jetty.alpn.client.ALPNClientConnectionFactory; +import org.eclipse.jetty.client.AbstractConnectorHttpClientTransport; +import org.eclipse.jetty.client.HttpClientTransport; +import org.eclipse.jetty.client.HttpDestination; +import org.eclipse.jetty.client.HttpRequest; +import org.eclipse.jetty.client.MultiplexConnectionPool; +import org.eclipse.jetty.client.MultiplexHttpDestination; +import org.eclipse.jetty.client.http.HttpClientConnectionFactory; +import org.eclipse.jetty.http.HttpScheme; +import org.eclipse.jetty.http.HttpVersion; +import org.eclipse.jetty.io.ClientConnectionFactory; +import org.eclipse.jetty.io.ClientConnector; +import org.eclipse.jetty.io.Connection; +import org.eclipse.jetty.io.EndPoint; + +public class HttpClientTransportDynamic extends AbstractConnectorHttpClientTransport implements HttpClientTransport.Dynamic +{ + private final List factoryInfos; + + public HttpClientTransportDynamic() + { + this(new ClientConnector(), HttpClientConnectionFactory.HTTP); + } + + public HttpClientTransportDynamic(ClientConnector connector, ClientConnectionFactory.Info... factoryInfos) + { + super(connector); + addBean(connector); + if (factoryInfos.length == 0) + throw new IllegalArgumentException("Missing ClientConnectionFactory"); + this.factoryInfos = Arrays.asList(factoryInfos); + for (ClientConnectionFactory.Info factoryInfo : factoryInfos) + addBean(factoryInfo); + setConnectionPoolFactory(destination -> + new MultiplexConnectionPool(destination, destination.getHttpClient().getMaxConnectionsPerDestination(), destination, 1)); + } + + @Override + public HttpDestination.Protocol getProtocol(HttpRequest request) + { + HttpVersion version = request.getVersion(); + if (HttpScheme.HTTPS.is(request.getScheme())) + { + List protocols = version == HttpVersion.HTTP_2 ? List.of("h2") : List.of("h2", "http/1.1"); + if (findClientConnectionFactoryInfo(protocols).isPresent()) + return new HttpDestination.Protocol(protocols, true); + } + else + { + List protocols = version == HttpVersion.HTTP_2 ? List.of("h2c") : List.of("http/1.1", "h2c"); + if (findClientConnectionFactoryInfo(protocols).isPresent()) + return new HttpDestination.Protocol(protocols, false); + } + return null; + } + + @Override + public HttpDestination newHttpDestination(HttpDestination.Info info) + { + return new MultiplexHttpDestination(getHttpClient(), info); + } + + @Override + public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map context) throws IOException + { + HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY); + HttpDestination.Protocol protocol = destination.getInfo().getProtocol(); + ClientConnectionFactory.Info factoryInfo; + if (protocol == null) + { + // Use the default ClientConnectionFactory. + factoryInfo = factoryInfos.get(0); + } + else + { + if (destination.isSecure() && protocol.isNegotiate()) + { + factoryInfo = new ALPNClientConnectionFactory.ALPN(getClientConnector().getExecutor(), this::newNegotiatedConnection, protocol.getProtocols()); + } + else + { + factoryInfo = findClientConnectionFactoryInfo(protocol.getProtocols()) + .orElseThrow(() -> new IOException("Cannot find " + ClientConnectionFactory.class.getSimpleName() + " for " + protocol)); + } + } + return factoryInfo.getClientConnectionFactory().newConnection(endPoint, context); + } + + protected Connection newNegotiatedConnection(EndPoint endPoint, Map context) throws IOException + { + try + { + ALPNClientConnection alpnConnection = (ALPNClientConnection)endPoint.getConnection(); + String protocol = alpnConnection.getProtocol(); + if (LOG.isDebugEnabled()) + LOG.debug("ALPN negotiated {} among {}", protocol, alpnConnection.getProtocols()); + List protocols = List.of(protocol); + Info factoryInfo = findClientConnectionFactoryInfo(protocols) + .orElseThrow(() -> new IOException("Cannot find " + ClientConnectionFactory.class.getSimpleName() + " for negotiated protocol " + protocol)); + return factoryInfo.getClientConnectionFactory().newConnection(endPoint, context); + } + catch (Throwable failure) + { + this.connectFailed(context, failure); + throw failure; + } + } + + private Optional findClientConnectionFactoryInfo(List protocols) + { + return factoryInfos.stream() + .filter(info -> info.matches(protocols)) + .findFirst(); + } +} diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpClientConnectionFactory.java b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpClientConnectionFactory.java new file mode 100644 index 000000000000..1b8df9ed243d --- /dev/null +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpClientConnectionFactory.java @@ -0,0 +1,43 @@ +// +// ======================================================================== +// 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.client.http; + +import java.util.List; +import java.util.Map; + +import org.eclipse.jetty.client.HttpClientTransport; +import org.eclipse.jetty.client.HttpDestination; +import org.eclipse.jetty.client.api.Connection; +import org.eclipse.jetty.io.ClientConnectionFactory; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.util.Promise; + +public class HttpClientConnectionFactory implements ClientConnectionFactory +{ + public static final Info HTTP = new Info(List.of("http/1.1"), new HttpClientConnectionFactory()); + + @Override + public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map context) + { + HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY); + @SuppressWarnings("unchecked") + Promise promise = (Promise)context.get(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY); + return customize(new HttpConnectionOverHTTP(endPoint, destination, promise), context); + } +} 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 ddfacc5f67a4..b61b8515c665 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 @@ -23,8 +23,8 @@ import org.eclipse.jetty.client.AbstractConnectorHttpClientTransport; import org.eclipse.jetty.client.DuplexConnectionPool; +import org.eclipse.jetty.client.DuplexHttpDestination; 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; @@ -53,9 +53,9 @@ public HttpClientTransportOverHTTP(ClientConnector connector) } @Override - public HttpDestination newHttpDestination(Origin origin) + public HttpDestination newHttpDestination(HttpDestination.Info info) { - return new HttpDestinationOverHTTP(getHttpClient(), origin); + return new DuplexHttpDestination(getHttpClient(), info); } @Override 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 746e54ec2b02..fcc2bb2b67fc 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 @@ -28,6 +28,7 @@ import org.eclipse.jetty.client.HttpConnection; import org.eclipse.jetty.client.HttpDestination; import org.eclipse.jetty.client.HttpExchange; +import org.eclipse.jetty.client.IConnection; import org.eclipse.jetty.client.SendFailure; import org.eclipse.jetty.client.api.Connection; import org.eclipse.jetty.client.api.Request; @@ -39,7 +40,7 @@ import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.thread.Sweeper; -public class HttpConnectionOverHTTP extends AbstractConnection implements Connection, org.eclipse.jetty.io.Connection.UpgradeFrom, Sweeper.Sweepable +public class HttpConnectionOverHTTP extends AbstractConnection implements IConnection, org.eclipse.jetty.io.Connection.UpgradeFrom, Sweeper.Sweepable { private static final Logger LOG = Log.getLogger(HttpConnectionOverHTTP.class); @@ -71,9 +72,9 @@ public HttpChannelOverHTTP getHttpChannel() return channel; } - public HttpDestinationOverHTTP getHttpDestination() + public HttpDestination getHttpDestination() { - return (HttpDestinationOverHTTP)delegate.getHttpDestination(); + return delegate.getHttpDestination(); } @Override @@ -116,7 +117,8 @@ public void send(Request request, Response.CompleteListener listener) delegate.send(request, listener); } - protected SendFailure send(HttpExchange exchange) + @Override + public SendFailure send(HttpExchange exchange) { return delegate.send(exchange); } @@ -238,7 +240,7 @@ private Delegate(HttpDestination destination) } @Override - protected SendFailure send(HttpExchange exchange) + public SendFailure send(HttpExchange exchange) { Request request = exchange.getRequest(); normalizeRequest(request); diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/proxy/ProxyProtocolClientConnectionFactory.java b/jetty-client/src/main/java/org/eclipse/jetty/client/proxy/ProxyProtocolClientConnectionFactory.java new file mode 100644 index 000000000000..b26fbabccd49 --- /dev/null +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/proxy/ProxyProtocolClientConnectionFactory.java @@ -0,0 +1,146 @@ +// +// ======================================================================== +// 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.client.proxy; + +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.concurrent.Executor; +import java.util.function.Supplier; + +import org.eclipse.jetty.client.HttpClientTransport; +import org.eclipse.jetty.client.HttpDestination; +import org.eclipse.jetty.client.api.Connection; +import org.eclipse.jetty.io.AbstractConnection; +import org.eclipse.jetty.io.ClientConnectionFactory; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.Promise; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +public class ProxyProtocolClientConnectionFactory implements ClientConnectionFactory +{ + private final ClientConnectionFactory connectionFactory; + private final Supplier proxiedAddressSupplier; + + public ProxyProtocolClientConnectionFactory(ClientConnectionFactory connectionFactory, Supplier proxiedAddressSupplier) + { + this.connectionFactory = connectionFactory; + this.proxiedAddressSupplier = proxiedAddressSupplier; + } + + @Override + public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map context) + { + HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY); + Executor executor = destination.getHttpClient().getExecutor(); + ProxyProtocolConnection connection = new ProxyProtocolConnection(endPoint, executor, context); + return customize(connection, context); + } + + private class ProxyProtocolConnection extends AbstractConnection implements Callback + { + private final Logger LOG = Log.getLogger(ProxyProtocolConnection.class); + private final Map context; + + public ProxyProtocolConnection(EndPoint endPoint, Executor executor, Map context) + { + super(endPoint, executor); + this.context = context; + } + + @Override + public void onOpen() + { + super.onOpen(); + writePROXYLine(); + } + + protected void writePROXYLine() + { + InetSocketAddress proxiedSocketAddress = proxiedAddressSupplier.get(); + if (proxiedSocketAddress == null) + { + failed(new IllegalArgumentException("Missing proxied socket address")); + return; + } + InetAddress proxiedAddress = proxiedSocketAddress.getAddress(); + if (proxiedAddress == null) + { + failed(new IllegalArgumentException("Unresolved proxied socket address " + proxiedSocketAddress)); + return; + } + + String proxiedIP = proxiedAddress.getHostAddress(); + int proxiedPort = proxiedSocketAddress.getPort(); + InetSocketAddress serverSocketAddress = getEndPoint().getRemoteAddress(); + InetAddress serverAddress = serverSocketAddress.getAddress(); + String serverIP = serverAddress.getHostAddress(); + int serverPort = serverSocketAddress.getPort(); + + boolean ipv6 = proxiedAddress instanceof Inet6Address && serverAddress instanceof Inet6Address; + String line = String.format("PROXY %s %s %s %d %d\r\n", ipv6 ? "TCP6" : "TCP4" , proxiedIP, serverIP, proxiedPort, serverPort); + if (LOG.isDebugEnabled()) + LOG.debug("Writing PROXY line: {}", line.trim()); + ByteBuffer buffer = ByteBuffer.wrap(line.getBytes(StandardCharsets.US_ASCII)); + getEndPoint().write(this, buffer); + } + + @Override + public void succeeded() + { + try + { + EndPoint endPoint = getEndPoint(); + org.eclipse.jetty.io.Connection connection = connectionFactory.newConnection(endPoint, context); + if (LOG.isDebugEnabled()) + LOG.debug("Written PROXY line, upgrading to {}", connection); + endPoint.upgrade(connection); + } + catch (Throwable x) + { + failed(x); + } + } + + @Override + public void failed(Throwable x) + { + close(); + @SuppressWarnings("unchecked") + Promise promise = (Promise)context.get(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY); + promise.failed(x); + } + + @Override + public InvocationType getInvocationType() + { + return InvocationType.NON_BLOCKING; + } + + @Override + public void onFillable() + { + } + } +} diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/ClientConnectionCloseTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ClientConnectionCloseTest.java index 6e81bcf45f68..0ed5dcf2d7d8 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/ClientConnectionCloseTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/ClientConnectionCloseTest.java @@ -18,11 +18,6 @@ package org.eclipse.jetty.client; -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.assertTrue; - import java.io.IOException; import java.io.InterruptedIOException; import java.nio.ByteBuffer; @@ -36,7 +31,6 @@ import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.http.HttpConnectionOverHTTP; -import org.eclipse.jetty.client.http.HttpDestinationOverHTTP; import org.eclipse.jetty.client.util.DeferredContentProvider; import org.eclipse.jetty.client.util.StringContentProvider; import org.eclipse.jetty.http.HttpHeader; @@ -47,6 +41,11 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ArgumentsSource; +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.assertTrue; + public class ClientConnectionCloseTest extends AbstractHttpClientServerTest { @ParameterizedTest @@ -87,7 +86,7 @@ public void handle(String target, Request baseRequest, HttpServletRequest reques String host = "localhost"; int port = connector.getLocalPort(); - HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scenario.getScheme(), host, port); + HttpDestination destination = (HttpDestination)client.getDestination(scenario.getScheme(), host, port); DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool(); ContentResponse response = client.newRequest(host, port) @@ -124,7 +123,7 @@ public void handle(String target, Request baseRequest, HttpServletRequest reques String host = "localhost"; int port = connector.getLocalPort(); - HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scenario.getScheme(), host, port); + HttpDestination destination = (HttpDestination)client.getDestination(scenario.getScheme(), host, port); DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool(); CountDownLatch resultLatch = new CountDownLatch(1); @@ -185,7 +184,7 @@ public void handle(String target, Request baseRequest, HttpServletRequest reques String host = "localhost"; int port = connector.getLocalPort(); - HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scenario.getScheme(), host, port); + HttpDestination destination = (HttpDestination)client.getDestination(scenario.getScheme(), host, port); DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool(); DeferredContentProvider content = new DeferredContentProvider(ByteBuffer.allocate(8)); @@ -240,7 +239,7 @@ public void handle(String target, Request baseRequest, HttpServletRequest reques String host = "localhost"; int port = connector.getLocalPort(); - HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scenario.getScheme(), host, port); + HttpDestination destination = (HttpDestination)client.getDestination(scenario.getScheme(), host, port); DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool(); ContentResponse response = client.newRequest(host, port) diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientExplicitConnectionTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientExplicitConnectionTest.java index a4c27d2f6eda..ee3559261f29 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientExplicitConnectionTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientExplicitConnectionTest.java @@ -18,11 +18,6 @@ package org.eclipse.jetty.client; -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.assertTrue; - import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -31,13 +26,17 @@ import org.eclipse.jetty.client.api.Destination; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.http.HttpConnectionOverHTTP; -import org.eclipse.jetty.client.http.HttpDestinationOverHTTP; import org.eclipse.jetty.client.util.FutureResponseListener; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.util.FuturePromise; 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.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + public class HttpClientExplicitConnectionTest extends AbstractHttpClientServerTest { @ParameterizedTest @@ -59,7 +58,7 @@ public void testExplicitConnection(Scenario scenario) throws Exception assertNotNull(response); assertEquals(200, response.getStatus()); - HttpDestinationOverHTTP httpDestination = (HttpDestinationOverHTTP)destination; + HttpDestination httpDestination = (HttpDestination)destination; DuplexConnectionPool connectionPool = (DuplexConnectionPool)httpDestination.getConnectionPool(); assertTrue(connectionPool.getActiveConnections().isEmpty()); assertTrue(connectionPool.getIdleConnections().isEmpty()); @@ -94,7 +93,7 @@ public void testExplicitConnectionIsClosedOnRemoteClose(Scenario scenario) throw HttpConnectionOverHTTP httpConnection = (HttpConnectionOverHTTP)connection; assertFalse(httpConnection.getEndPoint().isOpen()); - HttpDestinationOverHTTP httpDestination = (HttpDestinationOverHTTP)destination; + HttpDestination httpDestination = (HttpDestination)destination; DuplexConnectionPool connectionPool = (DuplexConnectionPool)httpDestination.getConnectionPool(); assertTrue(connectionPool.getActiveConnections().isEmpty()); assertTrue(connectionPool.getIdleConnections().isEmpty()); diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientFailureTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientFailureTest.java index 5192550d7435..675067c3044a 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientFailureTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientFailureTest.java @@ -18,10 +18,6 @@ package org.eclipse.jetty.client; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - import java.nio.ByteBuffer; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; @@ -40,9 +36,12 @@ 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.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + public class HttpClientFailureTest { private Server server; @@ -155,98 +154,4 @@ public void failed(Throwable x) assertEquals(0, connectionPool.getActiveConnections().size()); assertEquals(0, connectionPool.getIdleConnections().size()); } -/* - @Test - public void test_ExchangeIsComplete_WhenRequestFailsMidway_WithResponse() throws Exception - { - start(new AbstractHandler() - { - @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException - { - // Echo back - IO.copy(request.getInputStream(), response.getOutputStream()); - } - }); - - final CountDownLatch latch = new CountDownLatch(1); - client.newRequest("localhost", connector.getLocalPort()) - .scheme(scheme) - // The second ByteBuffer set to null will throw an exception - .content(new ContentProvider() - { - @Override - public long getLength() - { - return -1; - } - - @Override - public Iterator iterator() - { - return new Iterator() - { - @Override - public boolean hasNext() - { - return true; - } - - @Override - public ByteBuffer next() - { - throw new NoSuchElementException("explicitly_thrown_by_test"); - } - - @Override - public void remove() - { - throw new UnsupportedOperationException(); - } - }; - } - }) - .send(new Response.Listener.Adapter() - { - @Override - public void onComplete(Result result) - { - latch.countDown(); - } - }); - - assertTrue(latch.await(5, TimeUnit.SECONDS)); - } - - @Test - public void test_ExchangeIsComplete_WhenRequestFails_WithNoResponse() throws Exception - { - start(new EmptyServerHandler()); - - final CountDownLatch latch = new CountDownLatch(1); - final String host = "localhost"; - final int port = connector.getLocalPort(); - client.newRequest(host, port) - .scheme(scheme) - .onRequestBegin(new Request.BeginListener() - { - @Override - public void onBegin(Request request) - { - HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port); - destination.getConnectionPool().getActiveConnections().peek().close(); - } - }) - .send(new Response.Listener.Adapter() - { - @Override - public void onComplete(Result result) - { - latch.countDown(); - } - }); - - assertTrue(latch.await(5, TimeUnit.SECONDS)); - } -*/ } 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 9fba73442ca6..161819348b06 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 @@ -65,7 +65,6 @@ import org.eclipse.jetty.client.api.Result; import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP; import org.eclipse.jetty.client.http.HttpConnectionOverHTTP; -import org.eclipse.jetty.client.http.HttpDestinationOverHTTP; import org.eclipse.jetty.client.util.BufferingResponseListener; import org.eclipse.jetty.client.util.BytesContentProvider; import org.eclipse.jetty.client.util.DeferredContentProvider; @@ -124,7 +123,7 @@ public void testStoppingClosesConnections(Scenario scenario) throws Exception Response response = client.GET(scenario.getScheme() + "://" + host + ":" + port + path); assertEquals(200, response.getStatus()); - HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scenario.getScheme(), host, port); + HttpDestination destination = (HttpDestination)client.getDestination(scenario.getScheme(), host, port); DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool(); long start = System.nanoTime(); @@ -681,7 +680,7 @@ public void test_ExchangeIsComplete_WhenRequestFails_WithNoResponse(Scenario sce .scheme(scenario.getScheme()) .onRequestBegin(request -> { - HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scenario.getScheme(), host, port); + HttpDestination destination = (HttpDestination)client.getDestination(scenario.getScheme(), host, port); DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool(); connectionPool.getActiveConnections().iterator().next().close(); }) diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientUploadDuringServerShutdown.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientUploadDuringServerShutdown.java index 09c436593152..d8a0e0d48cb6 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientUploadDuringServerShutdown.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientUploadDuringServerShutdown.java @@ -18,9 +18,6 @@ package org.eclipse.jetty.client; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - import java.io.IOException; import java.io.InputStream; import java.util.Random; @@ -37,7 +34,6 @@ import org.eclipse.jetty.client.http.HttpChannelOverHTTP; import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP; import org.eclipse.jetty.client.http.HttpConnectionOverHTTP; -import org.eclipse.jetty.client.http.HttpDestinationOverHTTP; import org.eclipse.jetty.client.util.BytesContentProvider; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.server.Request; @@ -46,9 +42,11 @@ import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.util.Promise; import org.eclipse.jetty.util.thread.QueuedThreadPool; - import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + public class HttpClientUploadDuringServerShutdown { /** @@ -252,7 +250,7 @@ protected boolean abort(Throwable failure) assertTrue(completeLatch.await(5, TimeUnit.SECONDS)); - HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination("http", "localhost", connector.getLocalPort()); + HttpDestination destination = (HttpDestination)client.getDestination("http", "localhost", connector.getLocalPort()); DuplexConnectionPool pool = (DuplexConnectionPool)destination.getConnectionPool(); assertEquals(0, pool.getConnectionCount()); assertEquals(0, pool.getIdleConnections().size()); diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java index 9dfc0cde09d5..97d2006dd935 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java @@ -18,10 +18,6 @@ package org.eclipse.jetty.client; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - import java.io.IOException; import java.nio.ByteBuffer; import java.util.Arrays; @@ -39,7 +35,6 @@ import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.api.Result; -import org.eclipse.jetty.client.http.HttpDestinationOverHTTP; import org.eclipse.jetty.client.util.ByteBufferContentProvider; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpVersion; @@ -51,6 +46,10 @@ 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.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest { @Override @@ -69,7 +68,7 @@ public void test_SuccessfulRequest_ReturnsConnection(Scenario scenario) throws E String host = "localhost"; int port = connector.getLocalPort(); - HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scenario.getScheme(), host, port); + HttpDestination destination = (HttpDestination)client.getDestination(scenario.getScheme(), host, port); DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool(); final Collection idleConnections = connectionPool.getIdleConnections(); @@ -120,7 +119,7 @@ public void test_FailedRequest_RemovesConnection(Scenario scenario) throws Excep String host = "localhost"; int port = connector.getLocalPort(); - HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scenario.getScheme(), host, port); + HttpDestination destination = (HttpDestination)client.getDestination(scenario.getScheme(), host, port); DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool(); final Collection idleConnections = connectionPool.getIdleConnections(); @@ -172,7 +171,7 @@ public void test_BadRequest_RemovesConnection(Scenario scenario) throws Exceptio String host = "localhost"; int port = connector.getLocalPort(); - HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scenario.getScheme(), host, port); + HttpDestination destination = (HttpDestination)client.getDestination(scenario.getScheme(), host, port); DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool(); final Queue idleConnections = connectionPool.getIdleConnections(); @@ -234,7 +233,7 @@ public void test_BadRequest_WithSlowRequest_RemovesConnection(Scenario scenario) String host = "localhost"; int port = connector.getLocalPort(); - HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scenario.getScheme(), host, port); + HttpDestination destination = (HttpDestination)client.getDestination(scenario.getScheme(), host, port); DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool(); final Collection idleConnections = connectionPool.getIdleConnections(); @@ -308,7 +307,7 @@ public void test_ConnectionFailure_RemovesConnection(Scenario scenario) throws E String host = "localhost"; int port = connector.getLocalPort(); - HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scenario.getScheme(), host, port); + HttpDestination destination = (HttpDestination)client.getDestination(scenario.getScheme(), host, port); DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool(); final Collection idleConnections = connectionPool.getIdleConnections(); @@ -351,7 +350,7 @@ public void handle(String target, org.eclipse.jetty.server.Request baseRequest, String host = "localhost"; int port = connector.getLocalPort(); - HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scenario.getScheme(), host, port); + HttpDestination destination = (HttpDestination)client.getDestination(scenario.getScheme(), host, port); DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool(); final Collection idleConnections = connectionPool.getIdleConnections(); @@ -400,7 +399,7 @@ public void handle(String target, org.eclipse.jetty.server.Request baseRequest, String host = "localhost"; int port = connector.getLocalPort(); - HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scenario.getScheme(), host, port); + HttpDestination destination = (HttpDestination)client.getDestination(scenario.getScheme(), host, port); DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool(); final Collection idleConnections = connectionPool.getIdleConnections(); @@ -448,7 +447,7 @@ public void test_IdleConnection_IsClosed_OnRemoteClose(Scenario scenario) throws String host = "localhost"; int port = connector.getLocalPort(); - HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scenario.getScheme(), host, port); + HttpDestination destination = (HttpDestination)client.getDestination(scenario.getScheme(), host, port); DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool(); final Collection idleConnections = connectionPool.getIdleConnections(); @@ -481,7 +480,7 @@ public void testConnectionForHTTP10ResponseIsRemoved(Scenario scenario) throws E String host = "localhost"; int port = connector.getLocalPort(); - HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scenario.getScheme(), host, port); + HttpDestination destination = (HttpDestination)client.getDestination(scenario.getScheme(), host, port); DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool(); final Collection idleConnections = connectionPool.getIdleConnections(); diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpRequestAbortTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpRequestAbortTest.java index c739d8eae07f..b9c4c82f5f3d 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpRequestAbortTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpRequestAbortTest.java @@ -18,12 +18,6 @@ package org.eclipse.jetty.client; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - import java.io.IOException; import java.nio.ByteBuffer; import java.util.concurrent.CountDownLatch; @@ -39,7 +33,6 @@ import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Result; -import org.eclipse.jetty.client.http.HttpDestinationOverHTTP; import org.eclipse.jetty.client.util.ByteBufferContentProvider; import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.util.IO; @@ -47,6 +40,12 @@ 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.assertFalse; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + public class HttpRequestAbortTest extends AbstractHttpClientServerTest { @ParameterizedTest @@ -112,7 +111,7 @@ public void onBegin(Request request) assertSame(cause, x.getCause()); assertFalse(begin.get()); - HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scenario.getScheme(), "localhost", connector.getLocalPort()); + HttpDestination destination = (HttpDestination)client.getDestination(scenario.getScheme(), "localhost", connector.getLocalPort()); DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool(); assertEquals(0, connectionPool.getConnectionCount()); assertEquals(0, connectionPool.getActiveConnections().size()); @@ -156,7 +155,7 @@ public void onCommit(Request request) assertSame(cause, x.getCause()); assertFalse(committed.await(1, TimeUnit.SECONDS)); - HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scenario.getScheme(), "localhost", connector.getLocalPort()); + HttpDestination destination = (HttpDestination)client.getDestination(scenario.getScheme(), "localhost", connector.getLocalPort()); DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool(); assertEquals(0, connectionPool.getConnectionCount()); assertEquals(0, connectionPool.getActiveConnections().size()); @@ -200,7 +199,7 @@ public void onCommit(Request request) assertSame(cause, x.getCause()); assertFalse(committed.await(1, TimeUnit.SECONDS)); - HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scenario.getScheme(), "localhost", connector.getLocalPort()); + HttpDestination destination = (HttpDestination)client.getDestination(scenario.getScheme(), "localhost", connector.getLocalPort()); DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool(); assertEquals(0, connectionPool.getConnectionCount()); assertEquals(0, connectionPool.getActiveConnections().size()); @@ -235,7 +234,7 @@ public void testAbortOnCommit(Scenario scenario) throws Exception if (aborted.get()) assertSame(cause, x.getCause()); - HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scenario.getScheme(), "localhost", connector.getLocalPort()); + HttpDestination destination = (HttpDestination)client.getDestination(scenario.getScheme(), "localhost", connector.getLocalPort()); DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool(); assertEquals(0, connectionPool.getConnectionCount()); assertEquals(0, connectionPool.getActiveConnections().size()); @@ -293,7 +292,7 @@ public long getLength() if (aborted.get()) assertSame(cause, x.getCause()); - HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scenario.getScheme(), "localhost", connector.getLocalPort()); + HttpDestination destination = (HttpDestination)client.getDestination(scenario.getScheme(), "localhost", connector.getLocalPort()); DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool(); assertEquals(0, connectionPool.getConnectionCount()); assertEquals(0, connectionPool.getActiveConnections().size()); @@ -353,7 +352,7 @@ public long getLength() assertTrue(serverLatch.await(5, TimeUnit.SECONDS)); - HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scenario.getScheme(), "localhost", connector.getLocalPort()); + HttpDestination destination = (HttpDestination)client.getDestination(scenario.getScheme(), "localhost", connector.getLocalPort()); DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool(); assertEquals(0, connectionPool.getConnectionCount()); assertEquals(0, connectionPool.getActiveConnections().size()); @@ -460,7 +459,7 @@ public void handle(String target, org.eclipse.jetty.server.Request baseRequest, assertSame(cause, x.getCause()); } - HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scenario.getScheme(), "localhost", connector.getLocalPort()); + HttpDestination destination = (HttpDestination)client.getDestination(scenario.getScheme(), "localhost", connector.getLocalPort()); DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool(); assertEquals(0, connectionPool.getConnectionCount()); assertEquals(0, connectionPool.getActiveConnections().size()); diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/ServerConnectionCloseTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ServerConnectionCloseTest.java index c47a69db0971..54f5f7ba1f9f 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/ServerConnectionCloseTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/ServerConnectionCloseTest.java @@ -18,8 +18,6 @@ package org.eclipse.jetty.client; -import static org.junit.jupiter.api.Assertions.assertEquals; - import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -30,14 +28,14 @@ import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP; -import org.eclipse.jetty.client.http.HttpDestinationOverHTTP; import org.eclipse.jetty.client.util.FutureResponseListener; import org.eclipse.jetty.http.HttpStatus; 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; + public class ServerConnectionCloseTest { private HttpClient client; @@ -149,7 +147,7 @@ private void testServerSendsConnectionClose(boolean shutdownOutput, boolean chun Thread.sleep(1000); // Connection should have been removed from pool. - HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination("http", "localhost", port); + HttpDestination destination = (HttpDestination)client.getDestination("http", "localhost", port); DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool(); assertEquals(0, connectionPool.getConnectionCount()); assertEquals(0, connectionPool.getIdleConnectionCount()); diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/TLSServerConnectionCloseTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/TLSServerConnectionCloseTest.java index cdd6603dca13..20bb341c3ac3 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/TLSServerConnectionCloseTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/TLSServerConnectionCloseTest.java @@ -18,8 +18,6 @@ package org.eclipse.jetty.client; -import static org.junit.jupiter.api.Assertions.assertEquals; - import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -33,7 +31,6 @@ import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP; -import org.eclipse.jetty.client.http.HttpDestinationOverHTTP; import org.eclipse.jetty.client.util.FutureResponseListener; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.util.ssl.SslContextFactory; @@ -42,6 +39,8 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; +import static org.junit.jupiter.api.Assertions.assertEquals; + public class TLSServerConnectionCloseTest { private HttpClient client; @@ -168,7 +167,7 @@ private void testServerSendsConnectionClose(final CloseMode closeMode, boolean c Thread.sleep(1000); // Connection should have been removed from pool. - HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination("http", "localhost", port); + HttpDestination destination = (HttpDestination)client.getDestination("http", "localhost", port); DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool(); assertEquals(0, connectionPool.getConnectionCount()); assertEquals(0, connectionPool.getIdleConnectionCount()); 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 7dff8d953a71..e595e7d4456c 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 @@ -26,8 +26,10 @@ import org.eclipse.jetty.client.AbstractHttpClientServerTest; import org.eclipse.jetty.client.ConnectionPool; import org.eclipse.jetty.client.DuplexConnectionPool; +import org.eclipse.jetty.client.DuplexHttpDestination; import org.eclipse.jetty.client.EmptyServerHandler; 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.api.ContentResponse; @@ -56,7 +58,7 @@ public void test_FirstAcquire_WithEmptyQueue(Scenario scenario) throws Exception { start(scenario, new EmptyServerHandler()); - try(HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", connector.getLocalPort()))) + try(HttpDestination destination = new DuplexHttpDestination(client, new Origin("http", "localhost", connector.getLocalPort()))) { destination.start(); DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool(); @@ -76,7 +78,7 @@ public void test_SecondAcquire_AfterFirstAcquire_WithEmptyQueue_ReturnsSameConne { start(scenario, new EmptyServerHandler()); - try(HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", connector.getLocalPort()))) + try(HttpDestination destination = new DuplexHttpDestination(client, new Origin("http", "localhost", connector.getLocalPort()))) { destination.start(); @@ -102,7 +104,7 @@ public void test_SecondAcquire_ConcurrentWithFirstAcquire_WithEmptyQueue_Creates final CountDownLatch idleLatch = new CountDownLatch(1); final CountDownLatch latch = new CountDownLatch(1); - HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", connector.getLocalPort())) + HttpDestination destination = new DuplexHttpDestination(client, new Origin("http", "localhost", connector.getLocalPort())) { @Override protected ConnectionPool newConnectionPool(HttpClient client) @@ -126,30 +128,29 @@ protected void onCreated(Connection connection) }; } }; - { - destination.start(); - DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool(); - Connection connection1 = connectionPool.acquire(); - // Make sure we entered idleCreated(). - assertTrue(idleLatch.await(5, TimeUnit.SECONDS)); + destination.start(); + DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool(); + Connection connection1 = connectionPool.acquire(); - // There are no available existing connections, so acquire() - // returns null because we delayed idleCreated() above - assertNull(connection1); + // Make sure we entered idleCreated(). + assertTrue(idleLatch.await(5, TimeUnit.SECONDS)); - // Second attempt also returns null because we delayed idleCreated() above. - Connection connection2 = connectionPool.acquire(); - assertNull(connection2); + // There are no available existing connections, so acquire() + // returns null because we delayed idleCreated() above + assertNull(connection1); - latch.countDown(); + // Second attempt also returns null because we delayed idleCreated() above. + Connection connection2 = connectionPool.acquire(); + assertNull(connection2); - // There must be 2 idle connections. - Connection connection = pollIdleConnection(connectionPool, 5, TimeUnit.SECONDS); - assertNotNull(connection); - connection = pollIdleConnection(connectionPool, 5, TimeUnit.SECONDS); - assertNotNull(connection); - } + latch.countDown(); + + // There must be 2 idle connections. + Connection connection = pollIdleConnection(connectionPool, 5, TimeUnit.SECONDS); + assertNotNull(connection); + connection = pollIdleConnection(connectionPool, 5, TimeUnit.SECONDS); + assertNotNull(connection); } @ParameterizedTest @@ -158,7 +159,7 @@ public void test_Acquire_Process_Release_Acquire_ReturnsSameConnection(Scenario { start(scenario, new EmptyServerHandler()); - try(HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", connector.getLocalPort()))) + try(HttpDestination destination = new DuplexHttpDestination(client, new Origin("http", "localhost", connector.getLocalPort()))) { destination.start(); DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool(); @@ -187,7 +188,7 @@ public void test_IdleConnection_IdleTimeout(Scenario scenario) throws Exception long idleTimeout = 1000; startClient(scenario, null, httpClient -> httpClient.setIdleTimeout(idleTimeout)); - try (HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", connector.getLocalPort()))) + try(HttpDestination destination = new DuplexHttpDestination(client, new Origin("http", "localhost", connector.getLocalPort()))) { destination.start(); DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool(); diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTPTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTPTest.java index c70cc68181d7..739e1bde451f 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTPTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTPTest.java @@ -18,15 +18,6 @@ package org.eclipse.jetty.client.http; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.instanceOf; -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.assertSame; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - import java.io.EOFException; import java.nio.charset.StandardCharsets; import java.util.Collections; @@ -35,15 +26,17 @@ import java.util.concurrent.TimeoutException; import java.util.stream.Stream; +import org.eclipse.jetty.client.DuplexHttpDestination; import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.HttpDestination; import org.eclipse.jetty.client.HttpExchange; import org.eclipse.jetty.client.HttpRequest; import org.eclipse.jetty.client.HttpResponseException; import org.eclipse.jetty.client.Origin; import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.util.FutureResponseListener; -import org.eclipse.jetty.http.HttpCompliance; import org.eclipse.jetty.http.BadMessageException; +import org.eclipse.jetty.http.HttpCompliance; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpVersion; @@ -54,10 +47,19 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.instanceOf; +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.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + public class HttpReceiverOverHTTPTest { private HttpClient client; - private HttpDestinationOverHTTP destination; + private HttpDestination destination; private ByteArrayEndPoint endPoint; private HttpConnectionOverHTTP connection; @@ -77,7 +79,7 @@ public void init(HttpCompliance compliance) throws Exception client = new HttpClient(); client.setHttpCompliance(compliance); client.start(); - destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", 8080)); + destination = new DuplexHttpDestination(client, new Origin("http", "localhost", 8080)); destination.start(); endPoint = new ByteArrayEndPoint(); connection = new HttpConnectionOverHTTP(endPoint, destination, new Promise.Adapter<>()); diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpSenderOverHTTPTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpSenderOverHTTPTest.java index 5130cfd23eb4..0e79a5820af4 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpSenderOverHTTPTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpSenderOverHTTPTest.java @@ -18,9 +18,6 @@ package org.eclipse.jetty.client.http; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.jupiter.api.Assertions.assertTrue; - import java.net.URI; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; @@ -28,7 +25,9 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import org.eclipse.jetty.client.DuplexHttpDestination; 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.api.Request; @@ -39,11 +38,13 @@ import org.eclipse.jetty.util.Promise; import org.hamcrest.Matchers; import org.junit.jupiter.api.AfterEach; - import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledIfSystemProperty; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; + public class HttpSenderOverHTTPTest { private HttpClient client; @@ -65,7 +66,7 @@ public void destroy() throws Exception public void test_Send_NoRequestContent() throws Exception { ByteArrayEndPoint endPoint = new ByteArrayEndPoint(); - HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", 8080)); + HttpDestination destination = new DuplexHttpDestination(client, new Origin("http", "localhost", 8080)); destination.start(); HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, destination, new Promise.Adapter()); Request request = client.newRequest(URI.create("http://localhost/")); @@ -99,7 +100,7 @@ public void onSuccess(Request request) public void test_Send_NoRequestContent_IncompleteFlush() throws Exception { ByteArrayEndPoint endPoint = new ByteArrayEndPoint("", 16); - HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", 8080)); + HttpDestination destination = new DuplexHttpDestination(client, new Origin("http", "localhost", 8080)); destination.start(); HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, destination, new Promise.Adapter()); Request request = client.newRequest(URI.create("http://localhost/")); @@ -129,7 +130,7 @@ public void test_Send_NoRequestContent_Exception() throws Exception ByteArrayEndPoint endPoint = new ByteArrayEndPoint(); // Shutdown output to trigger the exception on write endPoint.shutdownOutput(); - HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", 8080)); + HttpDestination destination = new DuplexHttpDestination(client, new Origin("http", "localhost", 8080)); destination.start(); HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, destination, new Promise.Adapter()); Request request = client.newRequest(URI.create("http://localhost/")); @@ -159,7 +160,7 @@ public void onComplete(Result result) public void test_Send_NoRequestContent_IncompleteFlush_Exception() throws Exception { ByteArrayEndPoint endPoint = new ByteArrayEndPoint("", 16); - HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", 8080)); + HttpDestination destination = new DuplexHttpDestination(client, new Origin("http", "localhost", 8080)); destination.start(); HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, destination, new Promise.Adapter()); Request request = client.newRequest(URI.create("http://localhost/")); @@ -195,7 +196,7 @@ public void onComplete(Result result) public void test_Send_SmallRequestContent_InOneBuffer() throws Exception { ByteArrayEndPoint endPoint = new ByteArrayEndPoint(); - HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", 8080)); + HttpDestination destination = new DuplexHttpDestination(client, new Origin("http", "localhost", 8080)); destination.start(); HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, destination, new Promise.Adapter()); Request request = client.newRequest(URI.create("http://localhost/")); @@ -230,7 +231,7 @@ public void onSuccess(Request request) public void test_Send_SmallRequestContent_InTwoBuffers() throws Exception { ByteArrayEndPoint endPoint = new ByteArrayEndPoint(); - HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", 8080)); + HttpDestination destination = new DuplexHttpDestination(client, new Origin("http", "localhost", 8080)); destination.start(); HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, destination, new Promise.Adapter()); Request request = client.newRequest(URI.create("http://localhost/")); @@ -266,7 +267,7 @@ public void onSuccess(Request request) public void test_Send_SmallRequestContent_Chunked_InTwoChunks() throws Exception { ByteArrayEndPoint endPoint = new ByteArrayEndPoint(); - HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", 8080)); + HttpDestination destination = new DuplexHttpDestination(client, new Origin("http", "localhost", 8080)); destination.start(); HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, destination, new Promise.Adapter()); Request request = client.newRequest(URI.create("http://localhost/")); 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 5a641c149998..250eaab161f5 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 @@ -23,9 +23,9 @@ import org.eclipse.jetty.client.AbstractConnectorHttpClientTransport; import org.eclipse.jetty.client.DuplexConnectionPool; +import org.eclipse.jetty.client.DuplexHttpDestination; 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.api.Request; import org.eclipse.jetty.fcgi.FCGI; @@ -72,9 +72,9 @@ public String getScriptRoot() } @Override - public HttpDestination newHttpDestination(Origin origin) + public HttpDestination newHttpDestination(HttpDestination.Info info) { - return new HttpDestinationOverFCGI(getHttpClient(), origin); + return new DuplexHttpDestination(getHttpClient(), info); } @Override diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java index 4ceaaf04026a..43aa56d6d6ac 100644 --- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java +++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java @@ -34,6 +34,7 @@ import org.eclipse.jetty.client.HttpConnection; import org.eclipse.jetty.client.HttpDestination; import org.eclipse.jetty.client.HttpExchange; +import org.eclipse.jetty.client.IConnection; import org.eclipse.jetty.client.SendFailure; import org.eclipse.jetty.client.api.Connection; import org.eclipse.jetty.client.api.Request; @@ -54,7 +55,7 @@ import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; -public class HttpConnectionOverFCGI extends AbstractConnection implements Connection +public class HttpConnectionOverFCGI extends AbstractConnection implements IConnection { private static final Logger LOG = Log.getLogger(HttpConnectionOverFCGI.class); @@ -96,7 +97,8 @@ public void send(Request request, Response.CompleteListener listener) delegate.send(request, listener); } - protected SendFailure send(HttpExchange exchange) + @Override + public SendFailure send(HttpExchange exchange) { return delegate.send(exchange); } @@ -342,7 +344,7 @@ private Delegate(HttpDestination destination) } @Override - protected SendFailure send(HttpExchange exchange) + public SendFailure send(HttpExchange exchange) { Request request = exchange.getRequest(); normalizeRequest(request); diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpDestinationOverFCGI.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpDestinationOverFCGI.java deleted file mode 100644 index 9a296c1bf137..000000000000 --- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpDestinationOverFCGI.java +++ /dev/null @@ -1,40 +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.fcgi.client.http; - -import org.eclipse.jetty.client.HttpClient; -import org.eclipse.jetty.client.HttpExchange; -import org.eclipse.jetty.client.Origin; -import org.eclipse.jetty.client.PoolingHttpDestination; -import org.eclipse.jetty.client.SendFailure; -import org.eclipse.jetty.client.api.Connection; - -public class HttpDestinationOverFCGI extends PoolingHttpDestination -{ - public HttpDestinationOverFCGI(HttpClient client, Origin origin) - { - super(client, origin); - } - - @Override - protected SendFailure send(Connection connection, HttpExchange exchange) - { - return ((HttpConnectionOverFCGI)connection).send(exchange); - } -} diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/MultiplexHttpDestinationOverFCGI.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/MultiplexHttpDestinationOverFCGI.java deleted file mode 100644 index dcf7d2fc605e..000000000000 --- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/MultiplexHttpDestinationOverFCGI.java +++ /dev/null @@ -1,40 +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.fcgi.client.http; - -import org.eclipse.jetty.client.HttpClient; -import org.eclipse.jetty.client.HttpExchange; -import org.eclipse.jetty.client.MultiplexHttpDestination; -import org.eclipse.jetty.client.Origin; -import org.eclipse.jetty.client.SendFailure; -import org.eclipse.jetty.client.api.Connection; - -public class MultiplexHttpDestinationOverFCGI extends MultiplexHttpDestination -{ - public MultiplexHttpDestinationOverFCGI(HttpClient client, Origin origin) - { - super(client, origin); - } - - @Override - protected SendFailure send(Connection connection, HttpExchange exchange) - { - return ((HttpConnectionOverFCGI)connection).send(exchange); - } -} diff --git a/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/ClientConnectionFactoryOverHTTP2.java b/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/ClientConnectionFactoryOverHTTP2.java new file mode 100644 index 000000000000..8ca10232392c --- /dev/null +++ b/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/ClientConnectionFactoryOverHTTP2.java @@ -0,0 +1,66 @@ +// +// ======================================================================== +// 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.http; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import org.eclipse.jetty.http2.client.HTTP2Client; +import org.eclipse.jetty.http2.client.HTTP2ClientConnectionFactory; +import org.eclipse.jetty.io.ClientConnectionFactory; +import org.eclipse.jetty.io.Connection; +import org.eclipse.jetty.io.EndPoint; + +public class ClientConnectionFactoryOverHTTP2 implements ClientConnectionFactory +{ + private final ClientConnectionFactory factory = new HTTP2ClientConnectionFactory(); + private final HTTP2Client client; + + public ClientConnectionFactoryOverHTTP2(HTTP2Client client) + { + this.client = client; + } + + @Override + public Connection newConnection(EndPoint endPoint, Map context) throws IOException + { + HTTPSessionListenerPromise listenerPromise = new HTTPSessionListenerPromise(context); + context.put(HTTP2ClientConnectionFactory.CLIENT_CONTEXT_KEY, client); + context.put(HTTP2ClientConnectionFactory.SESSION_LISTENER_CONTEXT_KEY, listenerPromise); + context.put(HTTP2ClientConnectionFactory.SESSION_PROMISE_CONTEXT_KEY, listenerPromise); + return factory.newConnection(endPoint, context); + } + + public static class H2 extends Info + { + public H2(HTTP2Client client) + { + super(List.of("h2"), new ClientConnectionFactoryOverHTTP2(client)); + } + } + + public static class H2C extends Info + { + public H2C(HTTP2Client client) + { + super(List.of("h2c"), new ClientConnectionFactoryOverHTTP2(client)); + } + } +} diff --git a/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HTTPSessionListenerPromise.java b/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HTTPSessionListenerPromise.java new file mode 100644 index 000000000000..6db3aad9b70f --- /dev/null +++ b/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HTTPSessionListenerPromise.java @@ -0,0 +1,139 @@ +// +// ======================================================================== +// 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.http; + +import java.nio.channels.ClosedChannelException; +import java.util.Map; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicMarkableReference; + +import org.eclipse.jetty.client.HttpClientTransport; +import org.eclipse.jetty.client.HttpDestination; +import org.eclipse.jetty.client.api.Connection; +import org.eclipse.jetty.http2.HTTP2Session; +import org.eclipse.jetty.http2.api.Session; +import org.eclipse.jetty.http2.frames.GoAwayFrame; +import org.eclipse.jetty.http2.frames.SettingsFrame; +import org.eclipse.jetty.util.Promise; + +class HTTPSessionListenerPromise extends Session.Listener.Adapter implements Promise +{ + private final AtomicMarkableReference connection = new AtomicMarkableReference<>(null, false); + private final Map context; + + HTTPSessionListenerPromise(Map context) + { + this.context = context; + } + + @Override + public void succeeded(Session session) + { + // This method is invoked when the client preface + // is sent, but we want to succeed the nested + // promise when the server preface is received. + } + + @Override + public void failed(Throwable failure) + { + failConnectionPromise(failure); + } + + private HttpDestination destination() + { + return (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY); + } + + @SuppressWarnings("unchecked") + private Promise connectionPromise() + { + return (Promise)context.get(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY); + } + + @Override + public void onSettings(Session session, SettingsFrame frame) + { + Map settings = frame.getSettings(); + if (settings.containsKey(SettingsFrame.MAX_CONCURRENT_STREAMS)) + { + HttpDestination destination = destination(); + if (destination instanceof HttpDestination.Multiplexed) + ((HttpDestination.Multiplexed)destination).setMaxRequestsPerConnection(settings.get(SettingsFrame.MAX_CONCURRENT_STREAMS)); + } + if (!connection.isMarked()) + onServerPreface(session); + } + + private void onServerPreface(Session session) + { + HttpConnectionOverHTTP2 connection = newHttpConnection(destination(), session); + if (this.connection.compareAndSet(null, connection, false, true)) + connectionPromise().succeeded(connection); + } + + protected HttpConnectionOverHTTP2 newHttpConnection(HttpDestination destination, Session session) + { + return new HttpConnectionOverHTTP2(destination, session); + } + + @Override + public void onClose(Session session, GoAwayFrame frame) + { + if (failConnectionPromise(new ClosedChannelException())) + return; + HttpConnectionOverHTTP2 connection = this.connection.getReference(); + if (connection != null) + onClose(connection, frame); + } + + void onClose(HttpConnectionOverHTTP2 connection, GoAwayFrame frame) + { + } + + @Override + public boolean onIdleTimeout(Session session) + { + long idleTimeout = ((HTTP2Session)session).getEndPoint().getIdleTimeout(); + if (failConnectionPromise(new TimeoutException("Idle timeout expired: " + idleTimeout + " ms"))) + return true; + HttpConnectionOverHTTP2 connection = this.connection.getReference(); + if (connection != null) + return connection.onIdleTimeout(idleTimeout); + return true; + } + + @Override + public void onFailure(Session session, Throwable failure) + { + if (failConnectionPromise(failure)) + return; + HttpConnectionOverHTTP2 connection = this.connection.getReference(); + if (connection != null) + connection.close(failure); + } + + private boolean failConnectionPromise(Throwable failure) + { + boolean result = connection.compareAndSet(null, null, false, true); + if (result) + connectionPromise().failed(failure); + return result; + } +} 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 cacb023db7dd..16484689818a 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 @@ -20,26 +20,20 @@ import java.io.IOException; import java.net.InetSocketAddress; -import java.nio.channels.ClosedChannelException; import java.util.Map; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicMarkableReference; import org.eclipse.jetty.alpn.client.ALPNClientConnectionFactory; import org.eclipse.jetty.client.AbstractHttpClientTransport; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.HttpDestination; import org.eclipse.jetty.client.MultiplexConnectionPool; -import org.eclipse.jetty.client.Origin; +import org.eclipse.jetty.client.MultiplexHttpDestination; import org.eclipse.jetty.client.ProxyConfiguration; -import org.eclipse.jetty.client.api.Connection; import org.eclipse.jetty.http.HttpScheme; -import org.eclipse.jetty.http2.HTTP2Session; import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.client.HTTP2Client; import org.eclipse.jetty.http2.client.HTTP2ClientConnectionFactory; import org.eclipse.jetty.http2.frames.GoAwayFrame; -import org.eclipse.jetty.http2.frames.SettingsFrame; import org.eclipse.jetty.io.ClientConnectionFactory; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.util.Promise; @@ -49,13 +43,14 @@ @ManagedObject("The HTTP/2 client transport") public class HttpClientTransportOverHTTP2 extends AbstractHttpClientTransport { + private final ClientConnectionFactory connectionFactory = new HTTP2ClientConnectionFactory(); private final HTTP2Client client; - private ClientConnectionFactory connectionFactory; private boolean useALPN = true; public HttpClientTransportOverHTTP2(HTTP2Client client) { this.client = client; + addBean(client.getClientConnector(), false); setConnectionPoolFactory(destination -> { HttpClient httpClient = getHttpClient(); @@ -100,7 +95,6 @@ protected void doStart() throws Exception } addBean(client); super.doStart(); - connectionFactory = new HTTP2ClientConnectionFactory(); } @Override @@ -111,9 +105,9 @@ protected void doStop() throws Exception } @Override - public HttpDestination newHttpDestination(Origin origin) + public HttpDestination newHttpDestination(HttpDestination.Info info) { - return new HttpDestinationOverHTTP2(getHttpClient(), origin); + return new MultiplexHttpDestination(getHttpClient(), info); } @Override @@ -126,7 +120,7 @@ public void connect(InetSocketAddress address, Map context) SessionListenerPromise listenerPromise = new SessionListenerPromise(context); - HttpDestinationOverHTTP2 destination = (HttpDestinationOverHTTP2)context.get(HTTP_DESTINATION_CONTEXT_KEY); + HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY); connect(address, destination.getClientConnectionFactory(), listenerPromise, listenerPromise, context); } @@ -141,7 +135,7 @@ public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map + private class SessionListenerPromise extends HTTPSessionListenerPromise { - private final AtomicMarkableReference connection = new AtomicMarkableReference<>(null, false); - private final Map context; - private SessionListenerPromise(Map context) { - this.context = context; + super(context); } @Override - public void succeeded(Session session) + protected HttpConnectionOverHTTP2 newHttpConnection(HttpDestination destination, Session session) { - // This method is invoked when the client preface - // is sent, but we want to succeed the nested - // promise when the server preface is received. + return HttpClientTransportOverHTTP2.this.newHttpConnection(destination, session); } @Override - public void failed(Throwable failure) - { - failConnectionPromise(failure); - } - - private HttpDestinationOverHTTP2 destination() - { - return (HttpDestinationOverHTTP2)context.get(HTTP_DESTINATION_CONTEXT_KEY); - } - - @SuppressWarnings("unchecked") - private Promise connectionPromise() - { - return (Promise)context.get(HTTP_CONNECTION_PROMISE_CONTEXT_KEY); - } - - @Override - public void onSettings(Session session, SettingsFrame frame) - { - Map settings = frame.getSettings(); - if (settings.containsKey(SettingsFrame.MAX_CONCURRENT_STREAMS)) - destination().setMaxRequestsPerConnection(settings.get(SettingsFrame.MAX_CONCURRENT_STREAMS)); - if (!connection.isMarked()) - onServerPreface(session); - } - - private void onServerPreface(Session session) - { - HttpConnectionOverHTTP2 connection = newHttpConnection(destination(), session); - if (this.connection.compareAndSet(null, connection, false, true)) - connectionPromise().succeeded(connection); - } - - @Override - public void onClose(Session session, GoAwayFrame frame) - { - if (failConnectionPromise(new ClosedChannelException())) - return; - HttpConnectionOverHTTP2 connection = this.connection.getReference(); - if (connection != null) - HttpClientTransportOverHTTP2.this.onClose(connection, frame); - } - - @Override - public boolean onIdleTimeout(Session session) - { - long idleTimeout = ((HTTP2Session)session).getEndPoint().getIdleTimeout(); - if (failConnectionPromise(new TimeoutException("Idle timeout expired: " + idleTimeout + " ms"))) - return true; - HttpConnectionOverHTTP2 connection = this.connection.getReference(); - if (connection != null) - return connection.onIdleTimeout(idleTimeout); - return true; - } - - @Override - public void onFailure(Session session, Throwable failure) - { - if (failConnectionPromise(failure)) - return; - HttpConnectionOverHTTP2 connection = this.connection.getReference(); - if (connection != null) - connection.close(failure); - } - - private boolean failConnectionPromise(Throwable failure) + void onClose(HttpConnectionOverHTTP2 connection, GoAwayFrame frame) { - boolean result = connection.compareAndSet(null, null, false, true); - if (result) - connectionPromise().failed(failure); - return result; + HttpClientTransportOverHTTP2.this.onClose(connection, frame); } } } diff --git a/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpConnectionOverHTTP2.java b/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpConnectionOverHTTP2.java index 31e415ebce7d..dfbebd96a0ee 100644 --- a/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpConnectionOverHTTP2.java +++ b/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpConnectionOverHTTP2.java @@ -64,7 +64,7 @@ public Session getSession() } @Override - protected SendFailure send(HttpExchange exchange) + public SendFailure send(HttpExchange exchange) { HttpRequest request = exchange.getRequest(); request.version(HttpVersion.HTTP_2); diff --git a/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpDestinationOverHTTP2.java b/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpDestinationOverHTTP2.java deleted file mode 100644 index 5a030431de94..000000000000 --- a/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpDestinationOverHTTP2.java +++ /dev/null @@ -1,40 +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.http; - -import org.eclipse.jetty.client.HttpClient; -import org.eclipse.jetty.client.HttpExchange; -import org.eclipse.jetty.client.MultiplexHttpDestination; -import org.eclipse.jetty.client.Origin; -import org.eclipse.jetty.client.SendFailure; -import org.eclipse.jetty.client.api.Connection; - -public class HttpDestinationOverHTTP2 extends MultiplexHttpDestination -{ - public HttpDestinationOverHTTP2(HttpClient client, Origin origin) - { - super(client, origin); - } - - @Override - protected SendFailure send(Connection connection, HttpExchange exchange) - { - return ((HttpConnectionOverHTTP2)connection).send(exchange); - } -} 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 21225c96a95b..6ae87a5064f8 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 @@ -264,7 +264,7 @@ protected void service(String target, Request jettyRequest, HttpServletRequest r }); // The last exchange should remain in the queue. - HttpDestinationOverHTTP2 destination = (HttpDestinationOverHTTP2)client.getDestination("http", "localhost", connector.getLocalPort()); + HttpDestination destination = (HttpDestination)client.getDestination("http", "localhost", connector.getLocalPort()); assertEquals(1, destination.getHttpExchanges().size()); assertEquals(path, destination.getHttpExchanges().peek().getRequest().getPath()); diff --git a/jetty-http2/http2-http-client-transport/src/test/resources/jetty-logging.properties b/jetty-http2/http2-http-client-transport/src/test/resources/jetty-logging.properties index 287d28319e0f..34929219c9dc 100644 --- a/jetty-http2/http2-http-client-transport/src/test/resources/jetty-logging.properties +++ b/jetty-http2/http2-http-client-transport/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.client.LEVEL=DEBUG org.eclipse.jetty.http2.hpack.LEVEL=INFO #org.eclipse.jetty.http2.LEVEL=DEBUG 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 d935b3d1eb23..a54fc58504f5 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 @@ -19,6 +19,7 @@ package org.eclipse.jetty.io; import java.io.IOException; +import java.util.List; import java.util.Map; import org.eclipse.jetty.util.component.ContainerLifeCycle; @@ -45,4 +46,31 @@ public default Connection customize(Connection connection, Map c client.getBeans(Connection.Listener.class).forEach(connection::addListener); return connection; } + + public static class Info + { + private final List protocols; + private final ClientConnectionFactory factory; + + public Info(List protocols, ClientConnectionFactory factory) + { + this.protocols = protocols; + this.factory = factory; + } + + public List getProtocols() + { + return protocols; + } + + public ClientConnectionFactory getClientConnectionFactory() + { + return factory; + } + + public boolean matches(List candidates) + { + return protocols.stream().anyMatch(candidates::contains); + } + } } 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 78cd26cfaba4..29ce05f4564d 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 @@ -42,7 +42,7 @@ 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 REMOTE_SOCKET_ADDRESS_CONTEXT_KEY = CLIENT_CONNECTOR_CONTEXT_KEY + ".remoteSocketAddress"; 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); @@ -212,7 +212,7 @@ public void connect(SocketAddress address, Map context) if (context == null) context = new HashMap<>(); context.put(ClientConnector.CLIENT_CONNECTOR_CONTEXT_KEY, this); - context.putIfAbsent(SOCKET_ADDRESS_CONTEXT_KEY, address); + context.putIfAbsent(REMOTE_SOCKET_ADDRESS_CONTEXT_KEY, address); channel = SocketChannel.open(); SocketAddress bindAddress = getBindAddress(); @@ -299,7 +299,7 @@ protected void configure(SocketChannel channel) throws IOException protected void connectFailed(Throwable failure, Map context) { if (LOG.isDebugEnabled()) - LOG.debug("Could not connect to {}", context.get(SOCKET_ADDRESS_CONTEXT_KEY)); + LOG.debug("Could not connect to {}", context.get(REMOTE_SOCKET_ADDRESS_CONTEXT_KEY)); Promise promise = (Promise)context.get(CONNECTION_PROMISE_CONTEXT_KEY); promise.failed(failure); } 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 867f88957aac..b7574f1363b5 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 @@ -35,6 +35,7 @@ public abstract class NegotiatingClientConnection extends AbstractConnection private final SSLEngine engine; private final ClientConnectionFactory connectionFactory; private final Map context; + private String protocol; private volatile boolean completed; protected NegotiatingClientConnection(EndPoint endPoint, Executor executor, SSLEngine sslEngine, ClientConnectionFactory connectionFactory, Map context) @@ -50,8 +51,14 @@ public SSLEngine getSSLEngine() return engine; } - protected void completed() + public String getProtocol() { + return protocol; + } + + protected void completed(String protocol) + { + this.protocol = protocol; completed = true; } @@ -70,6 +77,7 @@ public void onOpen() catch (Throwable x) { close(); + // TODO: should we not fail the promise in the context here? 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 1a6feaa2f6c5..d75e29fbd4ef 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 @@ -90,7 +90,7 @@ public void setAllowMissingCloseMessage(boolean allowMissingCloseMessage) @Override public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map context) throws IOException { - InetSocketAddress address = (InetSocketAddress)context.get(ClientConnector.SOCKET_ADDRESS_CONTEXT_KEY); + InetSocketAddress address = (InetSocketAddress)context.get(ClientConnector.REMOTE_SOCKET_ADDRESS_CONTEXT_KEY); SSLEngine engine = sslContextFactory.newSSLEngine(address); engine.setUseClientMode(true); context.put(SSL_ENGINE_CONTEXT_KEY, engine); @@ -143,7 +143,7 @@ public void handshakeSucceeded(Event event) throws SSLException HostnameVerifier verifier = sslContextFactory.getHostnameVerifier(); if (verifier != null) { - InetSocketAddress address = (InetSocketAddress)context.get(ClientConnector.SOCKET_ADDRESS_CONTEXT_KEY); + InetSocketAddress address = (InetSocketAddress)context.get(ClientConnector.REMOTE_SOCKET_ADDRESS_CONTEXT_KEY); String host = address.getHostString(); try { diff --git a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootContextAsService.java b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootContextAsService.java index 16e6f139eefa..7533105461d4 100644 --- a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootContextAsService.java +++ b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootContextAsService.java @@ -18,12 +18,6 @@ package org.eclipse.jetty.osgi.test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.ops4j.pax.exam.CoreOptions.mavenBundle; -import static org.ops4j.pax.exam.CoreOptions.systemProperty; - import java.util.ArrayList; import javax.inject.Inject; @@ -42,20 +36,24 @@ import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceReference; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.ops4j.pax.exam.CoreOptions.mavenBundle; +import static org.ops4j.pax.exam.CoreOptions.systemProperty; + /** * TestJettyOSGiBootContextAsService - * + * * Tests deployment of a ContextHandler as an osgi Service. - * + * * Tests the ServiceContextProvider. - * */ @RunWith(PaxExam.class) public class TestJettyOSGiBootContextAsService { private static final String LOG_LEVEL = "WARN"; - @Inject BundleContext bundleContext = null; @@ -67,27 +65,24 @@ public static Option[] configure() options.addAll(TestOSGiUtil.configureJettyHomeAndPort(false, "jetty-http-boot-context-as-service.xml")); options.add(CoreOptions.bootDelegationPackages("org.xml.sax", "org.xml.*", "org.w3c.*", "javax.xml.*")); options.addAll(TestOSGiUtil.coreJettyDependencies()); + options.add(mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-alpn-java-client").versionAsInProject().start()); + options.add(mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-alpn-client").versionAsInProject().start()); - // a bundle that registers a webapp as a service for the jetty osgi core - // to pick up and deploy + // a bundle that registers a webapp as a service for the jetty osgi core to pick up and deploy options.add(mavenBundle().groupId("org.eclipse.jetty.osgi").artifactId("test-jetty-osgi-context").versionAsInProject().start()); options.add(systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value(LOG_LEVEL)); options.add(systemProperty("org.eclipse.jetty.LEVEL").value(LOG_LEVEL)); - options.add( systemProperty( "org.ops4j.pax.url.mvn.localRepository" ).value( System.getProperty( "mavenRepoPath" ) ) ); + options.add(systemProperty("org.ops4j.pax.url.mvn.localRepository").value(System.getProperty("mavenRepoPath"))); - return options.toArray(new Option[options.size()]); + return options.toArray(new Option[0]); } - - - /** - */ @Test public void testContextHandlerAsOSGiService() throws Exception { if (Boolean.getBoolean(TestOSGiUtil.BUNDLE_DEBUG)) TestOSGiUtil.assertAllBundlesActiveOrResolved(bundleContext); - + // now test the context HttpClient client = new HttpClient(); try @@ -95,12 +90,12 @@ public void testContextHandlerAsOSGiService() throws Exception client.start(); String tmp = System.getProperty("boot.context.service.port"); assertNotNull(tmp); - int port = Integer.valueOf(tmp).intValue(); + int port = Integer.valueOf(tmp); ContentResponse response = client.GET("http://127.0.0.1:" + port + "/acme/index.html"); assertEquals(HttpStatus.OK_200, response.getStatus()); String content = new String(response.getContent()); - assertTrue(content.indexOf("

Test OSGi Context

") != -1); + assertTrue(content.contains("

Test OSGi Context

")); } finally { @@ -110,7 +105,7 @@ public void testContextHandlerAsOSGiService() throws Exception ServiceReference[] refs = bundleContext.getServiceReferences(ContextHandler.class.getName(), null); assertNotNull(refs); assertEquals(1, refs.length); - ContextHandler ch = (ContextHandler) bundleContext.getService(refs[0]); + ContextHandler ch = (ContextHandler)bundleContext.getService(refs[0]); assertEquals("/acme", ch.getContextPath()); // Stop the bundle with the ContextHandler in it and check the jetty @@ -119,7 +114,7 @@ public void testContextHandlerAsOSGiService() throws Exception // than checking stderr output Bundle testWebBundle = TestOSGiUtil.getBundle(bundleContext, "org.eclipse.jetty.osgi.testcontext"); assertNotNull("Could not find the org.eclipse.jetty.test-jetty-osgi-context.jar bundle", testWebBundle); - assertTrue("The bundle org.eclipse.jetty.testcontext is not correctly resolved", testWebBundle.getState() == Bundle.ACTIVE); + assertEquals("The bundle org.eclipse.jetty.testcontext is not correctly resolved", Bundle.ACTIVE, testWebBundle.getState()); testWebBundle.stop(); } } diff --git a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootHTTP2JDK9.java b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootHTTP2JDK9.java index da10a161a0a3..728bb12578c0 100644 --- a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootHTTP2JDK9.java +++ b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootHTTP2JDK9.java @@ -65,9 +65,9 @@ public Option[] config() { ArrayList