Merge pull request #3313 from eclipse/jetty-10.0.x-1350-dynamic_client_transport

Issue #1350 - Dynamic selection of the transport to use based on ALPN on the client side.
This commit is contained in:
Simone Bordet 2019-03-19 15:31:11 +01:00 committed by GitHub
commit ec8a1bdb23
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
60 changed files with 1826 additions and 952 deletions

View File

@ -48,6 +48,6 @@ public class ALPNClientConnection extends NegotiatingClientConnection
if (protocol == null || !protocols.contains(protocol))
close();
else
completed();
completed(protocol);
}
}

View File

@ -111,4 +111,12 @@ public class ALPNClientConnectionFactory extends NegotiatingClientConnectionFact
}
throw new IllegalStateException("No ALPNProcessor for " + engine);
}
public static class ALPN extends Info
{
public ALPN(Executor executor, ClientConnectionFactory factory, List<String> protocols)
{
super(List.of("alpn"), new ALPNClientConnectionFactory(executor, factory, protocols));
}
}
}

View File

@ -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<Session> 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();
}
}

View File

@ -78,7 +78,6 @@ public class JDK9ALPNTest
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;

View File

@ -112,6 +112,12 @@
<artifactId>jetty-io</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-alpn-client</artifactId>
<version>${project.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-jmx</artifactId>

View File

@ -20,8 +20,10 @@ module org.eclipse.jetty.client
{
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 @@ module org.eclipse.jetty.client
// 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;
}

View File

@ -119,14 +119,14 @@ public abstract class AbstractConnectionPool implements ConnectionPool, Dumpable
if (LOG.isDebugEnabled())
LOG.debug("newConnection {}/{} connections {}/{} pending", total+1, maxConnections, pending+1, maxPending);
destination.newConnection(new Promise<Connection>()
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 abstract class AbstractConnectionPool implements ConnectionPool, Dumpable
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);
}
});

View File

@ -16,25 +16,22 @@
// ========================================================================
//
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
/**
* <p>A destination for those network transports that are duplex (e.g. HTTP/1.1 and FastCGI).</p>
*
* @see MultiplexHttpDestination
*/
public class DuplexHttpDestination extends HttpDestination
{
public HttpDestinationOverHTTP(HttpClient client, Origin origin)
public DuplexHttpDestination(HttpClient client, Origin origin)
{
super(client, origin);
this(client, new Key(origin, null));
}
@Override
protected SendFailure send(Connection connection, HttpExchange exchange)
public DuplexHttpDestination(HttpClient client, Key key)
{
return ((HttpConnectionOverHTTP)connection).send(exchange);
super(client, key);
}
}

View File

@ -28,6 +28,7 @@ import java.net.SocketAddress;
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.HttpParser;
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<Origin, HttpDestination> destinations = new ConcurrentHashMap<>();
private final ConcurrentMap<HttpDestination.Key, HttpDestination> destinations = new ConcurrentHashMap<>();
private final ProtocolHandlers handlers = new ProtocolHandlers();
private final List<Request.Listener> requestListeners = new ArrayList<>();
private final Set<ContentDecoder.Factory> 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 class HttpClient extends ContainerLifeCycle
*/
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 class HttpClient extends ContainerLifeCycle
*/
public void setCookieStore(CookieStore cookieStore)
{
if (isStarted())
throw new IllegalStateException();
this.cookieStore = Objects.requireNonNull(cookieStore);
this.cookieManager = newCookieManager();
}
@ -319,6 +337,8 @@ public class HttpClient extends ContainerLifeCycle
*/
public void setAuthenticationStore(AuthenticationStore authenticationStore)
{
if (isStarted())
throw new IllegalStateException();
this.authenticationStore = authenticationStore;
}
@ -523,10 +543,11 @@ public class HttpClient extends ContainerLifeCycle
*/
public Destination getDestination(String scheme, String host, int port)
{
return destinationFor(scheme, host, port);
Origin origin = createOrigin(scheme, host, port);
return resolveDestination(new HttpDestination.Key(origin, null));
}
protected HttpDestination destinationFor(String scheme, String host, int port)
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 +557,18 @@ public class HttpClient extends ContainerLifeCycle
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.Key key)
{
HttpDestination destination = destinations.get(key);
if (destination == null)
{
destination = transport.newHttpDestination(origin);
destination = getTransport().newHttpDestination(key);
// Start the destination before it's published to other threads.
addManaged(destination);
HttpDestination existing = destinations.putIfAbsent(origin, destination);
HttpDestination existing = destinations.putIfAbsent(key, destination);
if (existing != null)
{
removeBean(destination);
@ -560,7 +586,7 @@ public class HttpClient extends ContainerLifeCycle
protected boolean removeDestination(HttpDestination destination)
{
removeBean(destination);
return destinations.remove(destination.getOrigin(), destination);
return destinations.remove(destination.getKey(), destination);
}
/**
@ -573,7 +599,16 @@ public class HttpClient extends ContainerLifeCycle
protected void send(final HttpRequest request, List<Response.ResponseListener> listeners)
{
HttpDestination destination = destinationFor(request.getScheme(), request.getHost(), request.getPort());
Origin origin = createOrigin(request.getScheme(), request.getHost(), request.getPort());
HttpClientTransport transport = getTransport();
HttpDestination.Key destinationKey = null;
if (transport instanceof HttpClientTransport.Dynamic)
destinationKey = ((HttpClientTransport.Dynamic)transport).newDestinationKey(request, origin);
if (destinationKey == null)
destinationKey = new HttpDestination.Key(origin, null);
if (LOG.isDebugEnabled())
LOG.debug("Selected {} for {}", destinationKey, request);
HttpDestination destination = resolveDestination(destinationKey);
destination.send(request, listeners);
}
@ -636,7 +671,7 @@ public class HttpClient extends ContainerLifeCycle
*/
public ByteBufferPool getByteBufferPool()
{
return byteBufferPool;
return connector.getByteBufferPool();
}
/**
@ -644,10 +679,7 @@ public class HttpClient extends ContainerLifeCycle
*/
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 +709,7 @@ public class HttpClient extends ContainerLifeCycle
@ManagedAttribute("The timeout, in milliseconds, for connect() operations")
public long getConnectTimeout()
{
return connectTimeout;
return connector.getConnectTimeout().toMillis();
}
/**
@ -686,7 +718,7 @@ public class HttpClient extends ContainerLifeCycle
*/
public void setConnectTimeout(long connectTimeout)
{
this.connectTimeout = connectTimeout;
connector.setConnectTimeout(Duration.ofMillis(connectTimeout));
}
/**
@ -718,7 +750,7 @@ public class HttpClient extends ContainerLifeCycle
@ManagedAttribute("The timeout, in milliseconds, to close idle connections")
public long getIdleTimeout()
{
return idleTimeout;
return connector.getIdleTimeout().toMillis();
}
/**
@ -726,7 +758,7 @@ public class HttpClient extends ContainerLifeCycle
*/
public void setIdleTimeout(long idleTimeout)
{
this.idleTimeout = idleTimeout;
connector.setIdleTimeout(Duration.ofMillis(idleTimeout));
}
/**
@ -735,7 +767,7 @@ public class HttpClient extends ContainerLifeCycle
*/
public SocketAddress getBindAddress()
{
return bindAddress;
return connector.getBindAddress();
}
/**
@ -745,7 +777,7 @@ public class HttpClient extends ContainerLifeCycle
*/
public void setBindAddress(SocketAddress bindAddress)
{
this.bindAddress = bindAddress;
connector.setBindAddress(bindAddress);
}
/**
@ -790,7 +822,7 @@ public class HttpClient extends ContainerLifeCycle
*/
public Executor getExecutor()
{
return executor;
return connector.getExecutor();
}
/**
@ -798,10 +830,7 @@ public class HttpClient extends ContainerLifeCycle
*/
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 +838,7 @@ public class HttpClient extends ContainerLifeCycle
*/
public Scheduler getScheduler()
{
return scheduler;
return connector.getScheduler();
}
/**
@ -817,10 +846,7 @@ public class HttpClient extends ContainerLifeCycle
*/
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 +863,7 @@ public class HttpClient extends ContainerLifeCycle
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 +1085,7 @@ public class HttpClient extends ContainerLifeCycle
@ManagedAttribute("Whether the connect() operation is blocking")
public boolean isConnectBlocking()
{
return connectBlocking;
return connector.isConnectBlocking();
}
/**
@ -1074,7 +1100,7 @@ public class HttpClient extends ContainerLifeCycle
*/
public void setConnectBlocking(boolean connectBlocking)
{
this.connectBlocking = connectBlocking;
connector.setConnectBlocking(connectBlocking);
}
/**
@ -1109,7 +1135,7 @@ public class HttpClient extends ContainerLifeCycle
protected String normalizeHost(String host)
{
if (host != null && host.matches("\\[.*\\]"))
if (host != null && host.matches("\\[.*]"))
return host.substring(1, host.length() - 1);
return host;
}

View File

@ -43,7 +43,7 @@ public interface HttpClientTransport extends ClientConnectionFactory
* Sets the {@link HttpClient} instance on this transport.
* <p>
* This is needed because of a chicken-egg problem: in order to create the {@link HttpClient}
* a {@link HttpClientTransport} is needed, that therefore cannot have a reference yet to the
* a HttpClientTransport is needed, that therefore cannot have a reference yet to the
* {@link HttpClient}.
*
* @param client the {@link HttpClient} that uses this transport.
@ -56,15 +56,15 @@ public interface HttpClientTransport extends ClientConnectionFactory
* {@link HttpDestination} controls the destination-connection cardinality: protocols like
* HTTP have 1-N cardinality, while multiplexed protocols like HTTP/2 have a 1-1 cardinality.
*
* @param origin the destination origin
* @param key the destination key
* @return a new, transport-specific, {@link HttpDestination} object
*/
public HttpDestination newHttpDestination(Origin origin);
public HttpDestination newHttpDestination(HttpDestination.Key key);
/**
* Establishes a physical connection to the given {@code address}.
*
* @param address the address to connect to
* @param address the address to connect to
* @param context the context information to establish the connection
*/
public void connect(InetSocketAddress address, Map<String, Object> context);
@ -78,4 +78,20 @@ public interface HttpClientTransport extends ClientConnectionFactory
* @param factory the factory for ConnectionPool instances
*/
public void setConnectionPoolFactory(ConnectionPool.Factory factory);
/**
* Specifies whether a {@link HttpClientTransport} is dynamic.
*/
@FunctionalInterface
public interface Dynamic
{
/**
* Creates a new Key with the given request and origin.
*
* @param request the request that triggers the creation of the Key
* @param origin the origin of the server for the request
* @return a Key that identifies a destination
*/
public HttpDestination.Key newDestinationKey(HttpRequest request, Origin origin);
}
}

View File

@ -27,7 +27,6 @@ import java.util.concurrent.TimeUnit;
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.HttpCookieStore;
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 abstract class HttpConnection implements Connection
httpRequest.abort(result.failure);
}
protected abstract SendFailure send(HttpExchange exchange);
protected void normalizeRequest(Request request)
{
HttpVersion version = request.getVersion();

View File

@ -23,20 +23,25 @@ import java.io.IOException;
import java.nio.channels.AsynchronousCloseException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
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;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.dynamic.HttpClientTransportDynamic;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.io.ClientConnectionFactory;
import org.eclipse.jetty.io.CyclicTimeout;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.BlockingArrayQueue;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.HostPort;
@ -52,12 +57,12 @@ import org.eclipse.jetty.util.thread.Scheduler;
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 Key key;
private final Queue<HttpExchange> exchanges;
private final RequestNotifier requestNotifier;
private final ResponseNotifier responseNotifier;
@ -67,21 +72,44 @@ 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 Key(origin, null));
}
public HttpDestination(HttpClient client, Key key)
{
this(client, key, Function.identity());
}
public HttpDestination(HttpClient client, Key key, Function<ClientConnectionFactory, ClientConnectionFactory> factoryFn)
{
this.client = client;
this.origin = origin;
this.key = key;
this.exchanges = newExchangeQueue(client);
this.requestNotifier = new RequestNotifier(client);
this.responseNotifier = new ResponseNotifier();
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 +121,7 @@ public abstract class HttpDestination extends ContainerLifeCycle implements Dest
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 +170,14 @@ public abstract class HttpDestination extends ContainerLifeCycle implements Dest
return client;
}
public Key getKey()
{
return key;
}
public Origin getOrigin()
{
return origin;
return key.origin;
}
public Queue<HttpExchange> getHttpExchanges()
@ -181,7 +209,7 @@ public abstract class HttpDestination extends ContainerLifeCycle implements Dest
@ManagedAttribute(value = "The destination scheme", readonly = true)
public String getScheme()
{
return origin.getScheme();
return getOrigin().getScheme();
}
@Override
@ -190,14 +218,14 @@ public abstract class HttpDestination extends ContainerLifeCycle implements Dest
{
// 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 +236,7 @@ public abstract class HttpDestination extends ContainerLifeCycle implements Dest
public Origin.Address getConnectAddress()
{
return proxy == null ? origin.getAddress() : proxy.getAddress();
return proxy == null ? getOrigin().getAddress() : proxy.getAddress();
}
public HttpField getHostField()
@ -235,7 +263,7 @@ public abstract class HttpDestination extends ContainerLifeCycle implements Dest
}
protected void send(HttpRequest request, List<Response.ResponseListener> listeners)
{
{
if (!getScheme().equalsIgnoreCase(request.getScheme()))
throw new IllegalArgumentException("Invalid request scheme " + request.getScheme() + " for destination " + this);
if (!getHost().equalsIgnoreCase(request.getHost()))
@ -343,7 +371,7 @@ public abstract class HttpDestination extends ContainerLifeCycle implements Dest
}
else
{
SendFailure result = send(connection, exchange);
SendFailure result = ((IConnection)connection).send(exchange);
if (result != null)
{
if (LOG.isDebugEnabled())
@ -358,8 +386,6 @@ public abstract class HttpDestination extends ContainerLifeCycle implements Dest
}
}
protected abstract SendFailure send(Connection connection, HttpExchange exchange);
@Override
public void newConnection(Promise<Connection> promise)
{
@ -476,7 +502,7 @@ public abstract class HttpDestination extends ContainerLifeCycle implements Dest
public String asString()
{
return origin.asString();
return getKey().asString();
}
@Override
@ -490,7 +516,173 @@ public abstract class HttpDestination extends ContainerLifeCycle implements Dest
exchanges.size(),
connectionPool);
}
@FunctionalInterface
public interface Multiplexed
{
void setMaxRequestsPerConnection(int maxRequestsPerConnection);
}
/**
* <p>Class that groups the elements that uniquely identify a destination.</p>
* <p>The elements are an {@link Origin}, a {@link Protocol} and an opaque
* string that further distinguishes destinations that have the same origin
* and protocol.</p>
* <p>In general it is possible that, for the same origin, the server can
* speak different protocols (for example, clear-text HTTP/1.1 and clear-text
* HTTP/2), so the {@link Protocol} makes that distinction.</p>
* <p>Furthermore, it may be desirable to have different destinations for
* the same origin and protocol (for example, when using the PROXY protocol
* in a reverse proxy server, you want to be able to map the client ip:port
* to the destination {@code kind}, so that all the connections to the server
* associated to that destination can specify the PROXY protocol bytes for
* that particular client connection.</p>
*/
public static class Key
{
private final Origin origin;
private final Protocol protocol;
private final String kind;
/**
* Creates a Key with the given origin and protocol and a {@code null} kind.
*
* @param origin the origin
* @param protocol the protocol
*/
public Key(Origin origin, Protocol protocol)
{
this(origin, protocol, null);
}
/**
* Creates a Key with the given origin and protocol and kind.
*
* @param origin the origin
* @param protocol the protocol
* @param kind the opaque kind
*/
public Key(Origin origin, Protocol protocol, String kind)
{
this.origin = origin;
this.protocol = protocol;
this.kind = kind;
}
public Origin getOrigin()
{
return origin;
}
public Protocol getProtocol()
{
return protocol;
}
public String getKind()
{
return kind;
}
@Override
public boolean equals(Object obj)
{
if (this == obj)
return true;
if (obj == null || getClass() != obj.getClass())
return false;
Key that = (Key)obj;
return origin.equals(that.origin) &&
Objects.equals(protocol, that.protocol) &&
Objects.equals(kind, that.kind);
}
@Override
public int hashCode()
{
return Objects.hash(origin, protocol, kind);
}
public String asString()
{
return String.format("%s|%s,kind=%s",
origin.asString(),
protocol == null ? "null" : protocol.asString(),
kind);
}
@Override
public String toString()
{
return String.format("%s@%x[%s]", getClass().getSimpleName(), hashCode(), asString());
}
}
/**
* <p>The representation of a network protocol.</p>
* <p>A network protocol may have multiple protocol <em>names</em>
* associated to it, for example {@code ["h2", "h2-17", "h2-16"]}.</p>
* <p>A Protocol is then rendered into a {@link ClientConnectionFactory}
* chain, for example in
* {@link HttpClientTransportDynamic#newConnection(EndPoint, Map)}.</p>
*/
public static class Protocol
{
private final List<String> protocols;
private final boolean negotiate;
/**
* Creates a Protocol with the given list of protocol names
* and whether it should negotiate the protocol.
*
* @param protocols the protocol names
* @param negotiate whether the protocol should be negotiated
*/
public Protocol(List<String> protocols, boolean negotiate)
{
this.protocols = protocols;
this.negotiate = negotiate;
}
public List<String> getProtocols()
{
return protocols;
}
public boolean isNegotiate()
{
return negotiate;
}
@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;
}
@Override
public int hashCode()
{
return Objects.hash(protocols, negotiate);
}
public String asString()
{
return String.format("proto=%s,nego=%b", protocols, negotiate);
}
@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
{
@ -507,7 +699,7 @@ public abstract class HttpDestination extends ContainerLifeCycle implements Dest
nextTimeout.set(Long.MAX_VALUE);
long now = System.nanoTime();
long nextExpiresAt = Long.MAX_VALUE;
// Check all queued exchanges for those that have expired
// and to determine when the next check must be.
for (HttpExchange exchange : exchanges)
@ -521,7 +713,7 @@ public abstract class HttpDestination extends ContainerLifeCycle implements Dest
else if (expiresAt < nextExpiresAt)
nextExpiresAt = expiresAt;
}
if (nextExpiresAt < Long.MAX_VALUE && client.isRunning())
schedule(nextExpiresAt);
}

View File

@ -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);
}

View File

@ -22,11 +22,10 @@ import java.io.IOException;
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<Holder> idleConnections;
private final Map<Connection, Holder> muxedConnections;
private final Map<Connection, Holder> busyConnections;
private final Map<Connection, Holder> activeConnections;
private int maxMultiplex;
public MultiplexConnectionPool(HttpDestination destination, int maxConnections, Callback requester, int maxMultiplex)
@ -53,8 +50,7 @@ public class MultiplexConnectionPool extends AbstractConnectionPool implements C
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 class MultiplexConnectionPool extends AbstractConnectionPool implements C
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 class MultiplexConnectionPool extends AbstractConnectionPool implements C
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 class MultiplexConnectionPool extends AbstractConnectionPool implements C
}
}
}
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 @@ public class MultiplexConnectionPool extends AbstractConnectionPool implements C
{
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<Holder> iterator = idleConnections.iterator(); iterator.hasNext();)
for (Iterator<Holder> iterator = idleConnections.iterator(); iterator.hasNext(); )
{
holder = iterator.next();
if (holder.connection == connection)
@ -274,11 +194,6 @@ public class MultiplexConnectionPool extends AbstractConnectionPool implements C
}
}
}
finally
{
unlock();
}
if (activeRemoved || force)
released(connection);
boolean removed = activeRemoved || idleRemoved || force;
@ -291,65 +206,39 @@ public class MultiplexConnectionPool extends AbstractConnectionPool implements C
public void close()
{
super.close();
List<Connection> 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<Connection> 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 class MultiplexConnectionPool extends AbstractConnectionPool implements C
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);
}

View File

@ -18,13 +18,33 @@
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;
/**
* <p>A destination for those transports that are multiplex (e.g. HTTP/2).</p>
* <p>Transports that negotiate the protocol, and therefore do not know in advance
* whether they are duplex or multiplex, should use this class and when the
* cardinality is known call {@link #setMaxRequestsPerConnection(int)} with
* the proper cardinality.</p>
* <p>If the cardinality is {@code 1}, the behavior of this class is similar
* to that of {@link DuplexHttpDestination}.</p>
*/
public class MultiplexHttpDestination extends HttpDestination implements HttpDestination.Multiplexed
{
protected MultiplexHttpDestination(HttpClient client, Origin origin)
public MultiplexHttpDestination(HttpClient client, Key key)
{
super(client, origin);
this(client, key, Function.identity());
}
public MultiplexHttpDestination(HttpClient client, Key key, Function<ClientConnectionFactory, ClientConnectionFactory> factoryFn)
{
super(client, key, factoryFn);
}
@ManagedAttribute(value = "The maximum number of concurrent requests per connection")
public int getMaxRequestsPerConnection()
{
ConnectionPool connectionPool = getConnectionPool();

View File

@ -75,7 +75,7 @@ public class Origin
@Override
public String toString()
{
return asString();
return String.format("%s@%x[%s]", getClass().getSimpleName(), hashCode(), asString());
}
public static class Address

View File

@ -73,7 +73,7 @@ public class ProxyAuthenticationProtocolHandler extends AuthenticationProtocolHa
@Override
protected URI getAuthenticationURI(Request request)
{
HttpDestination destination = getHttpClient().destinationFor(request.getScheme(), request.getHost(), request.getPort());
HttpDestination destination = (HttpDestination)getHttpClient().getDestination(request.getScheme(), request.getHost(), request.getPort());
ProxyConfiguration.Proxy proxy = destination.getProxy();
return proxy != null ? proxy.getURI() : request.getURI();
}

View File

@ -0,0 +1,201 @@
//
// ========================================================================
// 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 java.util.stream.Collectors;
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.Origin;
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;
/**
* <p>A {@link HttpClientTransport} that can dynamically switch among different application protocols.</p>
* <p>Applications create HttpClientTransportDynamic instances specifying all the <em>application protocols</em>
* it supports, in order of preference. The typical case is when the server supports both HTTP/1.1 and
* HTTP/2, but the client does not know that. In this case, the application will create a
* HttpClientTransportDynamic in this way:</p>
* <pre>
* ClientConnector clientConnector = new ClientConnector();
* // Configure the clientConnector.
*
* // Prepare the application protocols.
* HttpClientConnectionFactory.Key h1 = HttpClientConnectionFactory.HTTP;
* HTTP2Client http2Client = new HTTP2Client(clientConnector);
* ClientConnectionFactory.Key h2 = new ClientConnectionFactoryOverHTTP2.H2(http2Client);
*
* // Create the HttpClientTransportDynamic, preferring h2 over h1.
* HttpClientTransport transport = new HttpClientTransportDynamic(clientConnector, h2, h1);
*
* // Create the HttpClient.
* client = new HttpClient(transport);
* </pre>
* <p>Note how in the code above the HttpClientTransportDynamic has been created with the <em>application
* protocols</em> {@code h2} and {@code h1}, without the need to specify TLS (which is implied by the request
* scheme) or ALPN (which is implied by HTTP/2 over TLS).</p>
* <p>When a request is first sent, a destination needs to be created, and the {@link org.eclipse.jetty.client.Origin}
* {@code (scheme, host, port)} is not enough to identify the destination because the same origin may speak
* different protocols.
* For example, the Jetty server supports speaking clear-text {@code http/1.1} and {@code h2c} on the same port.
* Imagine a client sending a {@code h2c} request to that port; this will create a destination and connections
* that speak {@code h2c}; it won't be possible to use the connections from that destination to send
* {@code http/1.1} requests.
* Therefore a destination is identified by a {@link org.eclipse.jetty.client.HttpDestination.Key} and
* applications can customize the creation of the destination key (for example depending on request protocol
* version, or request headers, or request attributes, or even request path) by overriding
* {@link #newDestinationKey(HttpRequest, Origin)}.</p>
*/
public class HttpClientTransportDynamic extends AbstractConnectorHttpClientTransport implements HttpClientTransport.Dynamic
{
private final List<ClientConnectionFactory.Info> factoryInfos;
private final List<String> protocols;
/**
* Creates a transport that speaks only HTTP/1.1.
*/
public HttpClientTransportDynamic()
{
this(new ClientConnector(), HttpClientConnectionFactory.HTTP11);
}
/**
* Creates a transport with the given {@link ClientConnector} and the given <em>application protocols</em>.
*
* @param connector the ClientConnector used by this transport
* @param factoryInfos the <em>application protocols</em> that this transport can speak
*/
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);
this.protocols = Arrays.stream(factoryInfos)
.flatMap(info -> info.getProtocols().stream())
.distinct()
.collect(Collectors.toList());
for (ClientConnectionFactory.Info factoryInfo : factoryInfos)
addBean(factoryInfo);
setConnectionPoolFactory(destination ->
new MultiplexConnectionPool(destination, destination.getHttpClient().getMaxConnectionsPerDestination(), destination, 1));
}
@Override
public HttpDestination.Key newDestinationKey(HttpRequest request, Origin origin)
{
boolean ssl = HttpScheme.HTTPS.is(request.getScheme());
String http2 = ssl ? "h2" : "h2c";
List<String> protocols = List.of();
if (request.getVersion() == HttpVersion.HTTP_2)
{
// The application is explicitly asking for HTTP/2, so exclude HTTP/1.1.
if (this.protocols.contains(http2))
protocols = List.of(http2);
}
else
{
// Preserve the order of protocols chosen by the application.
protocols = this.protocols.stream()
.filter(p -> p.equals("http/1.1") || p.equals(http2))
.collect(Collectors.toList());
}
if (protocols.isEmpty())
return new HttpDestination.Key(origin, null);
return new HttpDestination.Key(origin, new HttpDestination.Protocol(protocols, ssl));
}
@Override
public HttpDestination newHttpDestination(HttpDestination.Key key)
{
return new MultiplexHttpDestination(getHttpClient(), key);
}
@Override
public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
{
HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
HttpDestination.Protocol protocol = destination.getKey().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<String, Object> context) throws IOException
{
try
{
ALPNClientConnection alpnConnection = (ALPNClientConnection)endPoint.getConnection();
String protocol = alpnConnection.getProtocol();
if (LOG.isDebugEnabled())
LOG.debug("ALPN negotiated {} among {}", protocol, alpnConnection.getProtocols());
if (protocol == null)
throw new IOException("Could not negotiate protocol among " + alpnConnection.getProtocols());
List<String> 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<Info> findClientConnectionFactoryInfo(List<String> protocols)
{
return factoryInfos.stream()
.filter(info -> info.matches(protocols))
.findFirst();
}
}

View File

@ -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 HTTP11 = new Info(List.of("http/1.1"), new HttpClientConnectionFactory());
@Override
public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context)
{
HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
@SuppressWarnings("unchecked")
Promise<Connection> promise = (Promise<Connection>)context.get(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
return customize(new HttpConnectionOverHTTP(endPoint, destination, promise), context);
}
}

View File

@ -23,8 +23,8 @@ import java.util.Map;
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 class HttpClientTransportOverHTTP extends AbstractConnectorHttpClientTran
}
@Override
public HttpDestination newHttpDestination(Origin origin)
public HttpDestination newHttpDestination(HttpDestination.Key key)
{
return new HttpDestinationOverHTTP(getHttpClient(), origin);
return new DuplexHttpDestination(getHttpClient(), key);
}
@Override

View File

@ -28,6 +28,7 @@ import java.util.concurrent.atomic.LongAdder;
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.Log;
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 class HttpConnectionOverHTTP extends AbstractConnection implements Connec
return channel;
}
public HttpDestinationOverHTTP getHttpDestination()
public HttpDestination getHttpDestination()
{
return (HttpDestinationOverHTTP)delegate.getHttpDestination();
return delegate.getHttpDestination();
}
@Override
@ -116,7 +117,8 @@ public class HttpConnectionOverHTTP extends AbstractConnection implements Connec
delegate.send(request, listener);
}
protected SendFailure send(HttpExchange exchange)
@Override
public SendFailure send(HttpExchange exchange)
{
return delegate.send(exchange);
}
@ -238,7 +240,7 @@ public class HttpConnectionOverHTTP extends AbstractConnection implements Connec
}
@Override
protected SendFailure send(HttpExchange exchange)
public SendFailure send(HttpExchange exchange)
{
Request request = exchange.getRequest();
normalizeRequest(request);

View File

@ -0,0 +1,140 @@
//
// ========================================================================
// 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.Origin;
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<Origin.Address> proxiedAddressSupplier;
public ProxyProtocolClientConnectionFactory(ClientConnectionFactory connectionFactory, Supplier<Origin.Address> proxiedAddressSupplier)
{
this.connectionFactory = connectionFactory;
this.proxiedAddressSupplier = proxiedAddressSupplier;
}
@Override
public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> 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<String, Object> context;
public ProxyProtocolConnection(EndPoint endPoint, Executor executor, Map<String, Object> context)
{
super(endPoint, executor);
this.context = context;
}
@Override
public void onOpen()
{
super.onOpen();
writePROXYLine();
}
protected void writePROXYLine()
{
Origin.Address proxiedAddress = proxiedAddressSupplier.get();
if (proxiedAddress == null)
{
failed(new IllegalArgumentException("Missing proxied socket address"));
return;
}
String proxiedIP = proxiedAddress.getHost();
int proxiedPort = proxiedAddress.getPort();
InetSocketAddress serverSocketAddress = getEndPoint().getRemoteAddress();
InetAddress serverAddress = serverSocketAddress.getAddress();
String serverIP = serverAddress.getHostAddress();
int serverPort = serverSocketAddress.getPort();
boolean ipv6 = 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<Connection> promise = (Promise<Connection>)context.get(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
promise.failed(x);
}
@Override
public InvocationType getInvocationType()
{
return InvocationType.NON_BLOCKING;
}
@Override
public void onFillable()
{
}
}
}

View File

@ -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 javax.servlet.http.HttpServletResponse;
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.eclipse.jetty.server.handler.AbstractHandler;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ArgumentsSource;
import static org.junit.jupiter.api.Assertions.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 class ClientConnectionCloseTest extends AbstractHttpClientServerTest
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 class ClientConnectionCloseTest extends AbstractHttpClientServerTest
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 class ClientConnectionCloseTest extends AbstractHttpClientServerTest
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 class ClientConnectionCloseTest extends AbstractHttpClientServerTest
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)

View File

@ -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.ContentResponse;
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 class HttpClientExplicitConnectionTest extends AbstractHttpClientServerTe
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 class HttpClientExplicitConnectionTest extends AbstractHttpClientServerTe
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());

View File

@ -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.Callback;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.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 class HttpClientFailureTest
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<ByteBuffer> iterator()
{
return new Iterator<ByteBuffer>()
{
@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));
}
*/
}

View File

@ -65,7 +65,6 @@ import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
import org.eclipse.jetty.client.http.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 class HttpClientTest extends AbstractHttpClientServerTest
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 class HttpClientTest extends AbstractHttpClientServerTest
.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();
})

View File

@ -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.api.Connection;
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.ServerConnector;
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 @@ public class HttpClientUploadDuringServerShutdown
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());

View File

@ -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.ContentResponse;
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.api.condition.DisabledIfSystemProperty;
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 class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
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<Connection> idleConnections = connectionPool.getIdleConnections();
@ -120,7 +119,7 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
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<Connection> idleConnections = connectionPool.getIdleConnections();
@ -172,7 +171,7 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
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<Connection> idleConnections = connectionPool.getIdleConnections();
@ -234,7 +233,7 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
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<Connection> idleConnections = connectionPool.getIdleConnections();
@ -308,7 +307,7 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
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<Connection> idleConnections = connectionPool.getIdleConnections();
@ -351,7 +350,7 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
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<Connection> idleConnections = connectionPool.getIdleConnections();
@ -400,7 +399,7 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
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<Connection> idleConnections = connectionPool.getIdleConnections();
@ -448,7 +447,7 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
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<Connection> idleConnections = connectionPool.getIdleConnections();
@ -481,7 +480,7 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
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<Connection> idleConnections = connectionPool.getIdleConnections();

View File

@ -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 javax.servlet.http.HttpServletResponse;
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.eclipse.jetty.util.log.StacklessLogging;
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 class HttpRequestAbortTest extends AbstractHttpClientServerTest
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 class HttpRequestAbortTest extends AbstractHttpClientServerTest
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 class HttpRequestAbortTest extends AbstractHttpClientServerTest
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 class HttpRequestAbortTest extends AbstractHttpClientServerTest
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 class HttpRequestAbortTest extends AbstractHttpClientServerTest
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 class HttpRequestAbortTest extends AbstractHttpClientServerTest
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 class HttpRequestAbortTest extends AbstractHttpClientServerTest
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());

View File

@ -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 java.util.concurrent.TimeUnit;
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 @@ public class ServerConnectionCloseTest
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());

View File

@ -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 javax.net.ssl.SSLSocket;
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.api.AfterEach;
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 @@ public class TLSServerConnectionCloseTest
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());

View File

@ -26,8 +26,10 @@ import java.util.function.Supplier;
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 class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest
{
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 class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest
{
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 class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest
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 @@ public class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest
};
}
};
{
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 class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest
{
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 class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest
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();

View File

@ -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.TimeUnit;
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.ParameterizedTest;
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 class HttpReceiverOverHTTPTest
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<>());

View File

@ -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.Locale;
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.io.ByteArrayEndPoint;
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 class HttpSenderOverHTTPTest
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<Connection>());
Request request = client.newRequest(URI.create("http://localhost/"));
@ -99,7 +100,7 @@ public class HttpSenderOverHTTPTest
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<Connection>());
Request request = client.newRequest(URI.create("http://localhost/"));
@ -129,7 +130,7 @@ public class HttpSenderOverHTTPTest
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<Connection>());
Request request = client.newRequest(URI.create("http://localhost/"));
@ -159,7 +160,7 @@ public class HttpSenderOverHTTPTest
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<Connection>());
Request request = client.newRequest(URI.create("http://localhost/"));
@ -195,7 +196,7 @@ public class HttpSenderOverHTTPTest
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<Connection>());
Request request = client.newRequest(URI.create("http://localhost/"));
@ -230,7 +231,7 @@ public class HttpSenderOverHTTPTest
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<Connection>());
Request request = client.newRequest(URI.create("http://localhost/"));
@ -266,7 +267,7 @@ public class HttpSenderOverHTTPTest
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<Connection>());
Request request = client.newRequest(URI.create("http://localhost/"));

View File

@ -23,9 +23,9 @@ import java.util.Map;
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 class HttpClientTransportOverFCGI extends AbstractConnectorHttpClientTran
}
@Override
public HttpDestination newHttpDestination(Origin origin)
public HttpDestination newHttpDestination(HttpDestination.Key key)
{
return new HttpDestinationOverFCGI(getHttpClient(), origin);
return new DuplexHttpDestination(getHttpClient(), key);
}
@Override

View File

@ -34,6 +34,7 @@ import org.eclipse.jetty.client.HttpClient;
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.Promise;
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 class HttpConnectionOverFCGI extends AbstractConnection implements Connec
delegate.send(request, listener);
}
protected SendFailure send(HttpExchange exchange)
@Override
public SendFailure send(HttpExchange exchange)
{
return delegate.send(exchange);
}
@ -342,7 +344,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
}
@Override
protected SendFailure send(HttpExchange exchange)
public SendFailure send(HttpExchange exchange)
{
Request request = exchange.getRequest();
normalizeRequest(request);

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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<String, Object> 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));
}
}
}

View File

@ -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<Session>
{
private final AtomicMarkableReference<HttpConnectionOverHTTP2> connection = new AtomicMarkableReference<>(null, false);
private final Map<String, Object> context;
HTTPSessionListenerPromise(Map<String, Object> 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<Connection> connectionPromise()
{
return (Promise<Connection>)context.get(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
}
@Override
public void onSettings(Session session, SettingsFrame frame)
{
Map<Integer, Integer> 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;
}
}

View File

@ -20,26 +20,20 @@ package org.eclipse.jetty.http2.client.http;
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 @@ import org.eclipse.jetty.util.annotation.ManagedObject;
@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 @@ public class HttpClientTransportOverHTTP2 extends AbstractHttpClientTransport
}
addBean(client);
super.doStart();
connectionFactory = new HTTP2ClientConnectionFactory();
}
@Override
@ -111,9 +105,9 @@ public class HttpClientTransportOverHTTP2 extends AbstractHttpClientTransport
}
@Override
public HttpDestination newHttpDestination(Origin origin)
public HttpDestination newHttpDestination(HttpDestination.Key key)
{
return new HttpDestinationOverHTTP2(getHttpClient(), origin);
return new MultiplexHttpDestination(getHttpClient(), key);
}
@Override
@ -126,7 +120,7 @@ public class HttpClientTransportOverHTTP2 extends AbstractHttpClientTransport
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 class HttpClientTransportOverHTTP2 extends AbstractHttpClientTransport
endPoint.setIdleTimeout(getHttpClient().getIdleTimeout());
ClientConnectionFactory factory = connectionFactory;
HttpDestinationOverHTTP2 destination = (HttpDestinationOverHTTP2)context.get(HTTP_DESTINATION_CONTEXT_KEY);
HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
ProxyConfiguration.Proxy proxy = destination.getProxy();
boolean ssl = proxy == null ? HttpScheme.HTTPS.is(destination.getScheme()) : proxy.isSecure();
if (ssl && isUseALPN())
@ -159,96 +153,23 @@ public class HttpClientTransportOverHTTP2 extends AbstractHttpClientTransport
connection.close();
}
private class SessionListenerPromise extends Session.Listener.Adapter implements Promise<Session>
private class SessionListenerPromise extends HTTPSessionListenerPromise
{
private final AtomicMarkableReference<HttpConnectionOverHTTP2> connection = new AtomicMarkableReference<>(null, false);
private final Map<String, Object> context;
private SessionListenerPromise(Map<String, Object> 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)
void onClose(HttpConnectionOverHTTP2 connection, GoAwayFrame frame)
{
failConnectionPromise(failure);
}
private HttpDestinationOverHTTP2 destination()
{
return (HttpDestinationOverHTTP2)context.get(HTTP_DESTINATION_CONTEXT_KEY);
}
@SuppressWarnings("unchecked")
private Promise<Connection> connectionPromise()
{
return (Promise<Connection>)context.get(HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
}
@Override
public void onSettings(Session session, SettingsFrame frame)
{
Map<Integer, Integer> 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)
{
boolean result = connection.compareAndSet(null, null, false, true);
if (result)
connectionPromise().failed(failure);
return result;
HttpClientTransportOverHTTP2.this.onClose(connection, frame);
}
}
}

View File

@ -64,7 +64,7 @@ public class HttpConnectionOverHTTP2 extends HttpConnection implements Sweeper.S
}
@Override
protected SendFailure send(HttpExchange exchange)
public SendFailure send(HttpExchange exchange)
{
HttpRequest request = exchange.getRequest();
request.version(HttpVersion.HTTP_2);

View File

@ -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);
}
}

View File

@ -264,7 +264,7 @@ public class MaxConcurrentStreamsTest extends AbstractTest
});
// 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());

View File

@ -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

View File

@ -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,42 @@ public interface ClientConnectionFactory
client.getBeans(Connection.Listener.class).forEach(connection::addListener);
return connection;
}
/**
* <p>A holder for a list of protocol strings identifying a network protocol
* (for example {@code ["h2", "h2-17", "h2-16"]}) and a {@link ClientConnectionFactory}
* that creates connections that speak that network protocol.</p>
*/
public static class Info
{
private final List<String> protocols;
private final ClientConnectionFactory factory;
public Info(List<String> protocols, ClientConnectionFactory factory)
{
this.protocols = protocols;
this.factory = factory;
}
public List<String> getProtocols()
{
return protocols;
}
public ClientConnectionFactory getClientConnectionFactory()
{
return factory;
}
/**
* Tests whether one of the protocols of this class is also present in the given candidates list.
*
* @param candidates the candidates to match against
* @return whether one of the protocols of this class is present in the candidates
*/
public boolean matches(List<String> candidates)
{
return protocols.stream().anyMatch(candidates::contains);
}
}
}

View File

@ -42,7 +42,7 @@ import org.eclipse.jetty.util.thread.Scheduler;
public class ClientConnector extends ContainerLifeCycle
{
public static final String CLIENT_CONNECTOR_CONTEXT_KEY = "org.eclipse.jetty.client.connector";
public static final String SOCKET_ADDRESS_CONTEXT_KEY = CLIENT_CONNECTOR_CONTEXT_KEY + ".socketAddress";
public static final String 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 class ClientConnector extends ContainerLifeCycle
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 @@ public class ClientConnector extends ContainerLifeCycle
protected void connectFailed(Throwable failure, Map<String, Object> 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);
}

View File

@ -35,6 +35,7 @@ public abstract class NegotiatingClientConnection extends AbstractConnection
private final SSLEngine engine;
private final ClientConnectionFactory connectionFactory;
private final Map<String, Object> context;
private String protocol;
private volatile boolean completed;
protected NegotiatingClientConnection(EndPoint endPoint, Executor executor, SSLEngine sslEngine, ClientConnectionFactory connectionFactory, Map<String, Object> context)
@ -50,8 +51,14 @@ public abstract class NegotiatingClientConnection extends AbstractConnection
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 abstract class NegotiatingClientConnection extends AbstractConnection
catch (Throwable x)
{
close();
// TODO: should we not fail the promise in the context here?
throw new RuntimeIOException(x);
}
}

View File

@ -90,7 +90,7 @@ public class SslClientConnectionFactory implements ClientConnectionFactory
@Override
public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> 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 class SslClientConnectionFactory implements ClientConnectionFactory
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
{

View File

@ -19,6 +19,7 @@
package org.eclipse.jetty.osgi.test;
import java.util.ArrayList;
import javax.inject.Inject;
import org.eclipse.jetty.client.HttpClient;
@ -43,18 +44,16 @@ 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;
@ -66,27 +65,24 @@ public class TestJettyOSGiBootContextAsService
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
@ -94,12 +90,12 @@ public class TestJettyOSGiBootContextAsService
client.start();
String tmp = System.getProperty("boot.context.service.port");
assertNotNull(tmp);
int port = Integer.parseInt(tmp);
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("<h1>Test OSGi Context</h1>") != -1);
assertTrue(content.contains("<h1>Test OSGi Context</h1>"));
}
finally
{
@ -109,7 +105,7 @@ public class TestJettyOSGiBootContextAsService
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
@ -118,7 +114,7 @@ public class TestJettyOSGiBootContextAsService
// 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();
}
}

View File

@ -65,9 +65,9 @@ public class TestJettyOSGiBootHTTP2JDK9
{
ArrayList<Option> options = new ArrayList<>();
options.add(CoreOptions.junitBundles());
options.addAll(TestOSGiUtil.configureJettyHomeAndPort(true,"jetty-http2-jdk9.xml"));
options.addAll(TestOSGiUtil.configureJettyHomeAndPort(true, "jetty-http2-jdk9.xml"));
options.add(CoreOptions.bootDelegationPackages("org.xml.sax", "org.xml.*", "org.w3c.*", "javax.xml.*", "javax.activation.*"));
options.add(CoreOptions.systemPackages("com.sun.org.apache.xalan.internal.res","com.sun.org.apache.xml.internal.utils",
options.add(CoreOptions.systemPackages("com.sun.org.apache.xalan.internal.res", "com.sun.org.apache.xml.internal.utils",
"com.sun.org.apache.xml.internal.utils", "com.sun.org.apache.xpath.internal",
"com.sun.org.apache.xpath.internal.jaxp", "com.sun.org.apache.xpath.internal.objects"));
options.addAll(http2JettyDependencies());
@ -101,7 +101,7 @@ public class TestJettyOSGiBootHTTP2JDK9
res.add(mavenBundle().groupId("org.eclipse.jetty.http2").artifactId("http2-server").versionAsInProject().start());
return res;
}
public void assertAllBundlesActiveOrResolved()
{
TestOSGiUtil.debugBundles(bundleContext);
@ -128,7 +128,7 @@ public class TestJettyOSGiBootHTTP2JDK9
String port = System.getProperty("boot.https.port");
assertNotNull(port);
Path path = Paths.get("src", "test", "config");
Path path = Paths.get("src", "test", "config");
File keys = path.resolve("etc").resolve("keystore").toFile();
//set up client to do http2
@ -144,14 +144,16 @@ public class TestJettyOSGiBootHTTP2JDK9
httpClient.setExecutor(executor);
httpClient.start();
ContentResponse response = httpClient.GET("https://localhost:"+port+"/jsp/jstl.jsp");
ContentResponse response = httpClient.GET("https://localhost:" + port + "/jsp/jstl.jsp");
assertEquals(200, response.getStatus());
assertTrue(response.getContentAsString().contains("JSTL Example"));
}
finally
{
if (httpClient != null) httpClient.stop();
if (http2Client != null) http2Client.stop();
if (httpClient != null)
httpClient.stop();
if (http2Client != null)
http2Client.stop();
}
}
}

View File

@ -20,6 +20,7 @@ package org.eclipse.jetty.osgi.test;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import org.eclipse.jetty.client.HttpClient;
@ -72,15 +73,16 @@ public class TestJettyOSGiBootWebAppAsService
"com.sun.org.apache.xpath.internal.jaxp", "com.sun.org.apache.xpath.internal.objects"));
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());
options.add(systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value(LOG_LEVEL));
options.add(systemProperty("org.eclipse.jetty.LEVEL").value(LOG_LEVEL));
options.addAll(TestOSGiUtil.jspDependencies());
options.addAll(testDependencies());
return options.toArray(new Option[options.size()]);
return options.toArray(new Option[0]);
}
public static List<Option> testDependencies()
{
List<Option> res = new ArrayList<>();
@ -95,21 +97,18 @@ public class TestJettyOSGiBootWebAppAsService
return res;
}
public void assertAllBundlesActiveOrResolved()
{
TestOSGiUtil.debugBundles(bundleContext);
TestOSGiUtil.assertAllBundlesActiveOrResolved(bundleContext);
}
@Test
public void testBundle() throws Exception
{
if (Boolean.getBoolean(TestOSGiUtil.BUNDLE_DEBUG))
assertAllBundlesActiveOrResolved();
ServiceReference<?>[] refs = bundleContext.getServiceReferences(WebAppContext.class.getName(), null);
assertNotNull(refs);
assertEquals(2, refs.length);
@ -151,6 +150,5 @@ public class TestJettyOSGiBootWebAppAsService
{
client.stop();
}
}
}

View File

@ -20,6 +20,7 @@ package org.eclipse.jetty.osgi.test;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import org.eclipse.jetty.client.HttpClient;
@ -60,21 +61,22 @@ public class TestJettyOSGiBootWithAnnotations
options.add(TestOSGiUtil.optionalRemoteDebug());
options.add(CoreOptions.junitBundles());
options.addAll(TestOSGiUtil.configureJettyHomeAndPort(false, "jetty-http-boot-with-annotations.xml"));
options.add(CoreOptions.bootDelegationPackages("org.xml.sax", "org.xml.*", "org.w3c.*", "javax.sql.*","javax.xml.*", "javax.activation.*"));
options.add(CoreOptions.systemPackages("com.sun.org.apache.xalan.internal.res","com.sun.org.apache.xml.internal.utils",
options.add(CoreOptions.bootDelegationPackages("org.xml.sax", "org.xml.*", "org.w3c.*", "javax.sql.*", "javax.xml.*", "javax.activation.*"));
options.add(CoreOptions.systemPackages("com.sun.org.apache.xalan.internal.res", "com.sun.org.apache.xml.internal.utils",
"com.sun.org.apache.xml.internal.utils", "com.sun.org.apache.xpath.internal",
"com.sun.org.apache.xpath.internal.jaxp", "com.sun.org.apache.xpath.internal.objects"));
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());
options.add(systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value(LOG_LEVEL));
options.add(systemProperty("org.eclipse.jetty.LEVEL").value(LOG_LEVEL));
options.addAll(jspDependencies());
options.addAll(annotationDependencies());
options.add(mavenBundle().groupId("org.eclipse.jetty.osgi").artifactId("test-jetty-osgi-fragment").versionAsInProject().noStart());
return options.toArray(new Option[options.size()]);
return options.toArray(new Option[0]);
}
public static List<Option> jspDependencies()
{
return TestOSGiUtil.jspDependencies();
@ -83,8 +85,8 @@ public class TestJettyOSGiBootWithAnnotations
public static List<Option> annotationDependencies()
{
List<Option> res = new ArrayList<>();
res.add(mavenBundle().groupId( "com.sun.activation" ).artifactId( "javax.activation" ).version( "1.2.0" ).noStart());
res.add(mavenBundle().groupId( "org.eclipse.jetty.orbit" ).artifactId( "javax.mail.glassfish" ).version( "1.4.1.v201005082020" ).noStart());
res.add(mavenBundle().groupId("com.sun.activation").artifactId("javax.activation").version("1.2.0").noStart());
res.add(mavenBundle().groupId("org.eclipse.jetty.orbit").artifactId("javax.mail.glassfish").version("1.4.1.v201005082020").noStart());
res.add(mavenBundle().groupId("org.eclipse.jetty.tests").artifactId("test-container-initializer").versionAsInProject());
res.add(mavenBundle().groupId("org.eclipse.jetty.tests").artifactId("test-mock-resources").versionAsInProject());
//test webapp bundle
@ -92,7 +94,6 @@ public class TestJettyOSGiBootWithAnnotations
return res;
}
public void assertAllBundlesActiveOrResolved()
{
TestOSGiUtil.debugBundles(bundleContext);
@ -102,7 +103,6 @@ public class TestJettyOSGiBootWithAnnotations
@Test
public void testIndex() throws Exception
{
if (Boolean.getBoolean(TestOSGiUtil.BUNDLE_DEBUG))
assertAllBundlesActiveOrResolved();
@ -136,5 +136,4 @@ public class TestJettyOSGiBootWithAnnotations
client.stop();
}
}
}

View File

@ -24,6 +24,7 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.inject.Inject;
import org.eclipse.jetty.client.HttpClient;
@ -44,25 +45,22 @@ import org.osgi.framework.Constants;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
import static org.ops4j.pax.exam.CoreOptions.options;
import static org.ops4j.pax.exam.CoreOptions.systemProperty;
/**
* TestJettyOSGiBootWithBundle
*
* Tests reading config from a bundle and loading clases from it
* Tests reading config from a bundle and loading classes from it
*
* Tests the ServiceContextProvider.
*
*/
@RunWith(PaxExam.class)
public class TestJettyOSGiBootWithBundle
{
private static final String TEST_JETTY_HOME_BUNDLE = "test-jetty-xml-bundle";
private static final String LOG_LEVEL = "WARN";
private static final String LOG_LEVEL = "WARN";
@Inject
BundleContext bundleContext = null;
@ -70,28 +68,30 @@ public class TestJettyOSGiBootWithBundle
@Configuration
public static Option[] configure() throws IOException
{
ArrayList<Option> options = new ArrayList<Option>();
ArrayList<Option> options = new ArrayList<>();
options.add(CoreOptions.junitBundles());
options.addAll(configureJettyHomeAndPort());
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());
options.addAll(Arrays.asList(options(systemProperty("pax.exam.logging").value("none"))));
options.addAll(Arrays.asList(options(systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value(LOG_LEVEL))));
options.addAll(Arrays.asList(options(systemProperty("org.eclipse.jetty.LEVEL").value(LOG_LEVEL))));
TinyBundle bundle = TinyBundles.bundle();
bundle.add(SomeCustomBean.class);
bundle.set( Constants.BUNDLE_SYMBOLICNAME, TEST_JETTY_HOME_BUNDLE );
bundle.set(Constants.BUNDLE_SYMBOLICNAME, TEST_JETTY_HOME_BUNDLE);
File etcFolder = new File("src/test/config/etc");
bundle.add("jettyhome/etc/jetty-http-boot-with-bundle.xml", new FileInputStream(new File(etcFolder, "jetty-http-boot-with-bundle.xml")));
bundle.add("jettyhome/etc/jetty-with-custom-class.xml", new FileInputStream(new File(etcFolder, "jetty-with-custom-class.xml")));
options.add(CoreOptions.streamBundle(bundle.build()).startLevel(1));
return options.toArray(new Option[options.size()]);
return options.toArray(new Option[0]);
}
public static List<Option> configureJettyHomeAndPort()
{
List<Option> options = new ArrayList<Option>();
List<Option> options = new ArrayList<>();
options.add(systemProperty(OSGiServerConstants.MANAGED_JETTY_XML_CONFIG_URLS).value("etc/jetty-with-custom-class.xml,etc/jetty-http-boot-with-bundle.xml"));
options.add(systemProperty("jetty.http.port").value("0"));
// TODO: FIXME: options.add(systemProperty("jetty.ssl.port").value(String.valueOf(TestOSGiUtil.DEFAULT_SSL_PORT)));
@ -105,9 +105,6 @@ public class TestJettyOSGiBootWithBundle
TestOSGiUtil.assertAllBundlesActiveOrResolved(bundleContext);
}
/**
*/
@Ignore
@Test
public void testContextHandlerAsOSGiService() throws Exception
@ -119,7 +116,7 @@ public class TestJettyOSGiBootWithBundle
client.start();
String tmp = System.getProperty("boot.bundle.port");
assertNotNull(tmp);
int port = Integer.parseInt(tmp.trim());
int port = Integer.valueOf(tmp.trim());
ContentResponse response = client.GET("http://127.0.0.1:" + port);
assertEquals(HttpStatus.NOT_FOUND_404, response.getStatus());
String content = new String(response.getContent());

View File

@ -18,11 +18,6 @@
package org.eclipse.jetty.osgi.test;
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.io.InputStream;
import java.net.URI;
import java.util.ArrayList;
@ -35,6 +30,7 @@ import javax.websocket.RemoteEndpoint;
import javax.websocket.Session;
import javax.websocket.WebSocketContainer;
import aQute.bnd.osgi.Constants;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.ops4j.pax.exam.Configuration;
@ -45,10 +41,13 @@ import org.ops4j.pax.tinybundles.core.TinyBundle;
import org.ops4j.pax.tinybundles.core.TinyBundles;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import aQute.bnd.osgi.Constants;
import org.osgi.framework.BundleException;
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;
/**
* Test using websocket in osgi
*/
@ -67,21 +66,22 @@ public class TestJettyOSGiBootWithJavaxWebSocket
// options.add(TestOSGiUtil.optionalRemoteDebug());
options.add(CoreOptions.junitBundles());
options.addAll(TestOSGiUtil.configureJettyHomeAndPort(false, "jetty-http-boot-with-javax-websocket.xml"));
options.add(CoreOptions.bootDelegationPackages("org.xml.sax", "org.xml.*", "org.w3c.*", "javax.sql.*","javax.xml.*", "javax.activation.*"));
options.add(CoreOptions.systemPackages("com.sun.org.apache.xalan.internal.res","com.sun.org.apache.xml.internal.utils",
"com.sun.org.apache.xml.internal.utils", "com.sun.org.apache.xpath.internal",
"com.sun.org.apache.xpath.internal.jaxp", "com.sun.org.apache.xpath.internal.objects"));
options.add(CoreOptions.bootDelegationPackages("org.xml.sax", "org.xml.*", "org.w3c.*", "javax.sql.*", "javax.xml.*", "javax.activation.*"));
options.add(CoreOptions.systemPackages("com.sun.org.apache.xalan.internal.res", "com.sun.org.apache.xml.internal.utils",
"com.sun.org.apache.xml.internal.utils", "com.sun.org.apache.xpath.internal",
"com.sun.org.apache.xpath.internal.jaxp", "com.sun.org.apache.xpath.internal.objects"));
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());
options.add(systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value(LOG_LEVEL));
options.add(systemProperty("org.eclipse.jetty.LEVEL").value(LOG_LEVEL));
options.addAll(jspDependencies());
options.addAll(testJettyWebApp());
options.addAll(extraDependencies());
return options.toArray(new Option[options.size()]);
return options.toArray(new Option[0]);
}
public static List<Option> jspDependencies()
{
return TestOSGiUtil.jspDependencies();
@ -94,15 +94,15 @@ public class TestJettyOSGiBootWithJavaxWebSocket
res.add(mavenBundle().groupId("org.eclipse.jetty").artifactId("test-jetty-webapp").classifier("webbundle").versionAsInProject().noStart());
return res;
}
public static List<Option> extraDependencies()
{
List<Option> res = new ArrayList<>();
res.add(mavenBundle().groupId("biz.aQute.bnd").artifactId("bndlib").versionAsInProject().start());
res.add(mavenBundle().groupId("org.ops4j.pax.tinybundles").artifactId("tinybundles").version("2.1.1").start());
res.add(mavenBundle().groupId("org.ops4j.pax.tinybundles").artifactId("tinybundles").version("2.1.1").start());
return res;
}
public void assertAllBundlesActiveOrResolved()
{
TestOSGiUtil.assertAllBundlesActiveOrResolved(bundleContext);
@ -112,7 +112,6 @@ public class TestJettyOSGiBootWithJavaxWebSocket
@Test
public void testWebsocket() throws Exception
{
fixJavaxWebSocketApi();
startBundle(bundleContext, "org.eclipse.jetty.websocket.javax.websocket.common");
@ -131,18 +130,16 @@ public class TestJettyOSGiBootWithJavaxWebSocket
SimpleJavaxWebSocket socket = new SimpleJavaxWebSocket();
URI uri = new URI("ws://127.0.0.1:" + port + "/javax.websocket/");
Session session = container.connectToServer(socket,uri);
try
try (Session session = container.connectToServer(socket, uri))
{
RemoteEndpoint.Basic remote = session.getBasicRemote();
String msg = "Foo";
remote.sendText(msg);
assertTrue(socket.messageLatch.await(1,TimeUnit.SECONDS)); // give remote 1 second to respond
assertTrue(socket.messageLatch.await(1, TimeUnit.SECONDS)); // give remote 1 second to respond
}
finally
{
session.close();
assertTrue(socket.closeLatch.await(1,TimeUnit.SECONDS)); // give remote 1 second to acknowledge response
assertTrue(socket.closeLatch.await(1, TimeUnit.SECONDS)); // give remote 1 second to acknowledge response
}
}
@ -153,7 +150,7 @@ public class TestJettyOSGiBootWithJavaxWebSocket
TinyBundle bundle = TinyBundles.bundle();
bundle.set(Constants.FRAGMENT_HOST, "javax.websocket-api");
bundle.set(Constants.REQUIRE_CAPABILITY,
"osgi.serviceloader;filter:=\"(osgi.serviceloader=javax.websocket.ContainerProvider)\";resolution:=optional;cardinality:=multiple, osgi.extender; filter:=\"(osgi.extender=osgi.serviceloader.processor)\"");
"osgi.serviceloader;filter:=\"(osgi.serviceloader=javax.websocket.ContainerProvider)\";resolution:=optional;cardinality:=multiple, osgi.extender; filter:=\"(osgi.extender=osgi.serviceloader.processor)\"");
bundle.set(Constants.BUNDLE_SYMBOLICNAME, "javax.websocket.api.fragment");
InputStream is = bundle.build(TinyBundles.withBnd());
bundleContext.installBundle("dummyLocation", is);
@ -167,7 +164,7 @@ public class TestJettyOSGiBootWithJavaxWebSocket
private void startBundle(BundleContext bundleContext, String symbolicName) throws BundleException
{
Bundle bundle = TestOSGiUtil.getBundle(bundleContext, symbolicName);
assertNotNull("Bundle[" + symbolicName + "] should exist",bundle);
assertNotNull("Bundle[" + symbolicName + "] should exist", bundle);
bundle.start();
}
}

View File

@ -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 java.util.List;
@ -40,6 +34,12 @@ import org.ops4j.pax.exam.Option;
import org.ops4j.pax.exam.junit.PaxExam;
import org.osgi.framework.BundleContext;
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;
/**
* Pax-Exam to make sure the jetty-osgi-boot can be started along with the
* httpservice web-bundle. Then make sure we can deploy an OSGi service on the
@ -58,58 +58,54 @@ public class TestJettyOSGiBootWithJsp
{
ArrayList<Option> options = new ArrayList<>();
options.add(CoreOptions.junitBundles());
options.addAll(TestOSGiUtil.configureJettyHomeAndPort(false,"jetty-http-boot-with-jsp.xml"));
options.addAll(TestOSGiUtil.configureJettyHomeAndPort(false, "jetty-http-boot-with-jsp.xml"));
options.add(CoreOptions.bootDelegationPackages("org.xml.sax", "org.xml.*", "org.w3c.*", "javax.xml.*", "javax.activation.*"));
options.add(CoreOptions.systemPackages("com.sun.org.apache.xalan.internal.res","com.sun.org.apache.xml.internal.utils",
"com.sun.org.apache.xml.internal.utils", "com.sun.org.apache.xpath.internal",
"com.sun.org.apache.xpath.internal.jaxp", "com.sun.org.apache.xpath.internal.objects"));
options.add(CoreOptions.systemPackages("com.sun.org.apache.xalan.internal.res", "com.sun.org.apache.xml.internal.utils",
"com.sun.org.apache.xml.internal.utils", "com.sun.org.apache.xpath.internal",
"com.sun.org.apache.xpath.internal.jaxp", "com.sun.org.apache.xpath.internal.objects"));
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());
options.add(systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value(LOG_LEVEL));
options.add(systemProperty("org.eclipse.jetty.LEVEL").value(LOG_LEVEL));
options.addAll(jspDependencies());
options.add(CoreOptions.cleanCaches(true));
return options.toArray(new Option[options.size()]);
return options.toArray(new Option[0]);
}
public static List<Option> jspDependencies()
{
List<Option> res = new ArrayList<>();
res.addAll(TestOSGiUtil.jspDependencies());
List<Option> res = new ArrayList<>(TestOSGiUtil.jspDependencies());
//test webapp bundle
res.add(mavenBundle().groupId("org.eclipse.jetty").artifactId("test-jetty-webapp").classifier("webbundle").versionAsInProject());
return res;
}
public void assertAllBundlesActiveOrResolved()
{
TestOSGiUtil.debugBundles(bundleContext);
TestOSGiUtil.assertAllBundlesActiveOrResolved(bundleContext);
}
@Test
public void testJspDump() throws Exception
{
if (Boolean.getBoolean(TestOSGiUtil.BUNDLE_DEBUG))
assertAllBundlesActiveOrResolved();
HttpClient client = new HttpClient();
try
{
client.start();
String port = System.getProperty("boot.jsp.port");
assertNotNull(port);
ContentResponse response = client.GET("http://127.0.0.1:" + port + "/jsp/jstl.jsp");
assertEquals(HttpStatus.OK_200, response.getStatus());
String content = response.getContentAsString();
assertTrue(content.contains("JSTL Example"));
assertTrue(content.contains("JSTL Example"));
}
finally
{

View File

@ -18,11 +18,6 @@
package org.eclipse.jetty.osgi.test;
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.net.URI;
import java.util.ArrayList;
import java.util.List;
@ -40,7 +35,13 @@ import org.ops4j.pax.exam.Option;
import org.ops4j.pax.exam.junit.PaxExam;
import org.osgi.framework.BundleContext;
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;
/**
*
*/
@RunWith(PaxExam.class)
@ -57,17 +58,19 @@ public class TestJettyOSGiBootWithWebSocket
ArrayList<Option> options = new ArrayList<>();
options.add(CoreOptions.junitBundles());
options.addAll(TestOSGiUtil.configureJettyHomeAndPort(false, "jetty-http-boot-with-websocket.xml"));
options.add(CoreOptions.bootDelegationPackages("org.xml.sax", "org.xml.*", "org.w3c.*", "javax.sql.*","javax.xml.*", "javax.activation.*"));
options.add(CoreOptions.systemPackages("com.sun.org.apache.xalan.internal.res","com.sun.org.apache.xml.internal.utils",
"com.sun.org.apache.xml.internal.utils", "com.sun.org.apache.xpath.internal",
"com.sun.org.apache.xpath.internal.jaxp", "com.sun.org.apache.xpath.internal.objects"));
options.add(CoreOptions.bootDelegationPackages("org.xml.sax", "org.xml.*", "org.w3c.*", "javax.sql.*", "javax.xml.*", "javax.activation.*"));
options.add(CoreOptions.systemPackages("com.sun.org.apache.xalan.internal.res", "com.sun.org.apache.xml.internal.utils",
"com.sun.org.apache.xml.internal.utils", "com.sun.org.apache.xpath.internal",
"com.sun.org.apache.xpath.internal.jaxp", "com.sun.org.apache.xpath.internal.objects"));
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());
options.add(systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value(LOG_LEVEL));
options.add(systemProperty("org.eclipse.jetty.LEVEL").value(LOG_LEVEL));
options.addAll(jspDependencies());
options.addAll(testJettyWebApp());
return options.toArray(new Option[options.size()]);
return options.toArray(new Option[0]);
}
public static List<Option> jspDependencies()
@ -83,14 +86,11 @@ public class TestJettyOSGiBootWithWebSocket
return res;
}
public void assertAllBundlesActiveOrResolved()
{
TestOSGiUtil.assertAllBundlesActiveOrResolved(bundleContext);
TestOSGiUtil.debugBundles(bundleContext);
}
@Test
public void testWebsocket() throws Exception
@ -101,22 +101,17 @@ public class TestJettyOSGiBootWithWebSocket
String port = System.getProperty("boot.websocket.port");
assertNotNull(port);
URI uri = new URI("ws://127.0.0.1:" + port+"/ws/foo");
URI uri = new URI("ws://127.0.0.1:" + port + "/ws/foo");
WebSocketClient client = new WebSocketClient();
try
{
SimpleEchoSocket socket = new SimpleEchoSocket();
client.start();
SimpleEchoSocket socket = new SimpleEchoSocket();
ClientUpgradeRequest request = new ClientUpgradeRequest();
request.setSubProtocols("chat");
client.connect(socket,uri,request);
client.connect(socket, uri, request);
// wait for closed socket connection.
assertTrue(socket.awaitClose(5,TimeUnit.SECONDS));
assertTrue(socket.awaitClose(5, TimeUnit.SECONDS));
}
finally
{

View File

@ -62,14 +62,14 @@ import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.DuplexConnectionPool;
import org.eclipse.jetty.client.ConnectionPool;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.HttpDestination;
import org.eclipse.jetty.client.HttpProxy;
import org.eclipse.jetty.client.api.ContentResponse;
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.BufferingResponseListener;
import org.eclipse.jetty.client.util.BytesContentProvider;
import org.eclipse.jetty.client.util.DeferredContentProvider;
@ -1088,9 +1088,9 @@ public class ProxyServletTest
// Make sure the proxy does not receive chunk2.
assertEquals(-1, input.read());
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination("http", "localhost", port);
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
assertEquals(0, connectionPool.getIdleConnections().size());
HttpDestination destination = (HttpDestination)client.getDestination("http", "localhost", port);
ConnectionPool connectionPool = destination.getConnectionPool();
assertTrue(connectionPool.isEmpty());
}
@ParameterizedTest
@ -1158,9 +1158,9 @@ public class ProxyServletTest
input.read();
});
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination("http", "localhost", port);
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
assertEquals(0, connectionPool.getIdleConnections().size());
HttpDestination destination = (HttpDestination)client.getDestination("http", "localhost", port);
ConnectionPool connectionPool = destination.getConnectionPool();
assertTrue(connectionPool.isEmpty());
}
@ParameterizedTest

View File

@ -35,12 +35,11 @@ import jnr.unixsocket.UnixSocketAddress;
import jnr.unixsocket.UnixSocketChannel;
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.http.HttpConnectionOverHTTP;
import org.eclipse.jetty.client.http.HttpDestinationOverHTTP;
import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.ManagedSelector;
@ -73,13 +72,13 @@ public class HttpClientTransportOverUnixSockets extends AbstractConnectorHttpCli
}
@Override
public HttpDestination newHttpDestination(Origin origin)
public HttpDestination newHttpDestination(HttpDestination.Key key)
{
return new HttpDestinationOverHTTP(getHttpClient(), origin);
return new DuplexHttpDestination(getHttpClient(), key);
}
@Override
public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context)
{
HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
@SuppressWarnings("unchecked")

View File

@ -0,0 +1,498 @@
//
// ========================================================================
// 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.http.client;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Function;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.HttpDestination;
import org.eclipse.jetty.client.HttpRequest;
import org.eclipse.jetty.client.MultiplexHttpDestination;
import org.eclipse.jetty.client.Origin;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Destination;
import org.eclipse.jetty.client.dynamic.HttpClientTransportDynamic;
import org.eclipse.jetty.client.http.HttpClientConnectionFactory;
import org.eclipse.jetty.client.proxy.ProxyProtocolClientConnectionFactory;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.http2.client.HTTP2Client;
import org.eclipse.jetty.http2.client.http.ClientConnectionFactoryOverHTTP2;
import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory;
import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory;
import org.eclipse.jetty.io.ClientConnectionFactory;
import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.server.AbstractConnectionFactory;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.ProxyConnectionFactory;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
public class HttpClientTransportDynamicTest
{
private Server server;
private ServerConnector connector;
private HttpClient client;
private void startServer(Function<Server, ServerConnector> connectorFn, Handler handler) throws Exception
{
prepareServer(connectorFn, handler);
server.start();
}
private void prepareServer(Function<Server, ServerConnector> connectorFn, Handler handler)
{
QueuedThreadPool serverThreads = new QueuedThreadPool();
serverThreads.setName("server");
serverThreads.setDetailedDump(true);
server = new Server(serverThreads);
connector = connectorFn.apply(server);
server.setHandler(handler);
}
private ServerConnector h1(Server server)
{
HttpConfiguration httpConfiguration = new HttpConfiguration();
HttpConnectionFactory h1 = new HttpConnectionFactory(httpConfiguration);
ServerConnector connector = new ServerConnector(server, 1, 1, h1);
server.addConnector(connector);
return connector;
}
private ServerConnector h1_h2c(Server server)
{
HttpConfiguration httpConfiguration = new HttpConfiguration();
HttpConnectionFactory h1 = new HttpConnectionFactory(httpConfiguration);
HTTP2CServerConnectionFactory h2c = new HTTP2CServerConnectionFactory(httpConfiguration);
ServerConnector connector = new ServerConnector(server, 1, 1, h1, h2c);
server.addConnector(connector);
return connector;
}
private ServerConnector ssl_alpn_h1(Server server)
{
HttpConfiguration httpConfiguration = new HttpConfiguration();
HttpConnectionFactory h1 = new HttpConnectionFactory(httpConfiguration);
ALPNServerConnectionFactory alpn = new ALPNServerConnectionFactory();
alpn.setDefaultProtocol(h1.getProtocol());
SslContextFactory sslContextFactory = newSslContextFactory(false);
ServerConnector connector = new ServerConnector(server, 1, 1, AbstractConnectionFactory.getFactories(sslContextFactory, alpn, h1));
server.addConnector(connector);
return connector;
}
private ServerConnector ssl_h1_h2c(Server server)
{
HttpConfiguration httpConfiguration = new HttpConfiguration();
HttpConnectionFactory h1 = new HttpConnectionFactory(httpConfiguration);
HTTP2CServerConnectionFactory h2c = new HTTP2CServerConnectionFactory(httpConfiguration);
SslContextFactory sslContextFactory = newSslContextFactory(false);
// No ALPN.
ServerConnector connector = new ServerConnector(server, 1, 1, AbstractConnectionFactory.getFactories(sslContextFactory, h1, h2c));
server.addConnector(connector);
return connector;
}
private ServerConnector ssl_alpn_h1_h2(Server server)
{
HttpConfiguration httpConfiguration = new HttpConfiguration();
HttpConnectionFactory h1 = new HttpConnectionFactory(httpConfiguration);
ALPNServerConnectionFactory alpn = new ALPNServerConnectionFactory();
alpn.setDefaultProtocol(h1.getProtocol());
HTTP2ServerConnectionFactory h2 = new HTTP2ServerConnectionFactory(httpConfiguration);
SslContextFactory sslContextFactory = newSslContextFactory(false);
// Make explicitly h1 the default protocol (normally it would be h2).
ServerConnector connector = new ServerConnector(server, 1, 1, AbstractConnectionFactory.getFactories(sslContextFactory, alpn, h1, h2));
server.addConnector(connector);
return connector;
}
private ServerConnector ssl_alpn_h2_h1(Server server)
{
HttpConfiguration httpConfiguration = new HttpConfiguration();
HttpConnectionFactory h1 = new HttpConnectionFactory(httpConfiguration);
ALPNServerConnectionFactory alpn = new ALPNServerConnectionFactory();
alpn.setDefaultProtocol(h1.getProtocol());
HTTP2ServerConnectionFactory h2 = new HTTP2ServerConnectionFactory(httpConfiguration);
SslContextFactory sslContextFactory = newSslContextFactory(false);
ServerConnector connector = new ServerConnector(server, 1, 1, AbstractConnectionFactory.getFactories(sslContextFactory, alpn, h2, h1));
server.addConnector(connector);
return connector;
}
private ServerConnector proxy_h1_h2c(Server server)
{
HttpConfiguration httpConfiguration = new HttpConfiguration();
HttpConnectionFactory h1 = new HttpConnectionFactory(httpConfiguration);
ProxyConnectionFactory proxy = new ProxyConnectionFactory(h1.getProtocol());
ServerConnector connector = new ServerConnector(server, 1, 1, proxy, h1);
server.addConnector(connector);
return connector;
}
@AfterEach
public void stop() throws Exception
{
if (server != null)
server.stop();
if (client != null)
client.stop();
}
private SslContextFactory newSslContextFactory(boolean client)
{
SslContextFactory sslContextFactory = new SslContextFactory();
sslContextFactory.setKeyStorePath("src/test/resources/keystore.jks");
sslContextFactory.setKeyStorePassword("storepwd");
// The mandatory HTTP/2 cipher.
sslContextFactory.setIncludeCipherSuites("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256");
if (client)
sslContextFactory.setEndpointIdentificationAlgorithm(null);
return sslContextFactory;
}
@Test
public void testClearTextHTTP1() throws Exception
{
startServer(this::h1_h2c, new EmptyServerHandler());
HttpClientConnectionFactory.Info h1c = HttpClientConnectionFactory.HTTP11;
client = new HttpClient(new HttpClientTransportDynamic(new ClientConnector(), h1c));
client.start();
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
.send();
assertEquals(HttpStatus.OK_200, response.getStatus());
}
@Test
public void testClearTextHTTP2() throws Exception
{
startServer(this::h1_h2c, new EmptyServerHandler());
// TODO: why do we need HTTP2Client? we only use it for configuration,
// so the configuration can instead be moved to the CCF?
ClientConnector clientConnector = new ClientConnector();
HTTP2Client http2Client = new HTTP2Client(clientConnector);
ClientConnectionFactory.Info h2c = new ClientConnectionFactoryOverHTTP2.H2C(http2Client);
client = new HttpClient(new HttpClientTransportDynamic(clientConnector, h2c));
client.addBean(http2Client);
client.start();
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
// .version(HttpVersion.HTTP_2)
.send();
assertEquals(HttpStatus.OK_200, response.getStatus());
}
@Test
public void testClearTextProtocolSelection() throws Exception
{
startServer(this::h1_h2c, new EmptyServerHandler());
testProtocolSelection(HttpScheme.HTTP);
}
@Test
public void testEncryptedProtocolSelectionWithoutNegotiation() throws Exception
{
startServer(this::ssl_h1_h2c, new EmptyServerHandler());
testProtocolSelection(HttpScheme.HTTPS);
}
private void testProtocolSelection(HttpScheme scheme) throws Exception
{
ClientConnector clientConnector = new ClientConnector();
clientConnector.setSslContextFactory(newSslContextFactory(true));
HttpClientConnectionFactory.Info h1 = HttpClientConnectionFactory.HTTP11;
HTTP2Client http2Client = new HTTP2Client(clientConnector);
ClientConnectionFactory.Info h2c = new ClientConnectionFactoryOverHTTP2.H2C(http2Client);
HttpClientTransportDynamic transport = new HttpClientTransportDynamic(clientConnector, h1, h2c)
{
@Override
public HttpDestination.Key newDestinationKey(HttpRequest request, Origin origin)
{
// Use prior-knowledge, i.e. negotiate==false.
List<String> protocols = HttpVersion.HTTP_2 == request.getVersion() ? h2c.getProtocols() : h1.getProtocols();
return new HttpDestination.Key(origin, new HttpDestination.Protocol(protocols, false));
}
};
client = new HttpClient(transport);
client.addBean(http2Client);
client.start();
// Make a HTTP/1.1 request.
ContentResponse h1cResponse = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme.asString())
.version(HttpVersion.HTTP_1_1)
.send();
assertEquals(HttpStatus.OK_200, h1cResponse.getStatus());
// Make a HTTP/2 request.
ContentResponse h2cResponse = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme.asString())
.version(HttpVersion.HTTP_2)
.send();
assertEquals(HttpStatus.OK_200, h2cResponse.getStatus());
// We must have 2 different destinations with the same origin.
List<Destination> destinations = client.getDestinations();
assertEquals(2, destinations.size());
assertEquals(1, destinations.stream()
.map(HttpDestination.class::cast)
.map(HttpDestination::getKey)
.map(HttpDestination.Key::getOrigin)
.distinct()
.count());
}
@Test
public void testEncryptedProtocolSelectionWithNegotiation() throws Exception
{
startServer(this::ssl_alpn_h1_h2, new EmptyServerHandler());
ClientConnector clientConnector = new ClientConnector();
clientConnector.setSslContextFactory(newSslContextFactory(true));
HttpClientConnectionFactory.Info h1 = HttpClientConnectionFactory.HTTP11;
HTTP2Client http2Client = new HTTP2Client(clientConnector);
ClientConnectionFactory.Info h2 = new ClientConnectionFactoryOverHTTP2.H2(http2Client);
client = new HttpClient(new HttpClientTransportDynamic(clientConnector, h1, h2));
client.addBean(http2Client);
client.start();
// Make a request, should be HTTP/1.1 because of the order of protocols on server.
ContentResponse h1cResponse = client.newRequest("localhost", connector.getLocalPort())
.scheme("https")
.send();
assertEquals(HttpStatus.OK_200, h1cResponse.getStatus());
// Now clearly specify HTTP/2 in the client.
ContentResponse h2cResponse = client.newRequest("localhost", connector.getLocalPort())
.scheme("https")
.version(HttpVersion.HTTP_2)
.send();
assertEquals(HttpStatus.OK_200, h2cResponse.getStatus());
// We must have 2 different destinations with the same origin.
List<Destination> destinations = client.getDestinations();
assertEquals(2, destinations.size());
assertEquals(1, destinations.stream()
.map(HttpDestination.class::cast)
.map(HttpDestination::getKey)
.map(HttpDestination.Key::getOrigin)
.distinct()
.count());
}
@Test
public void testServerOnlySpeaksEncryptedHTTP11ClientFallsBackToHTTP11() throws Exception
{
startServer(this::ssl_alpn_h1, new EmptyServerHandler());
ClientConnector clientConnector = new ClientConnector();
clientConnector.setSslContextFactory(newSslContextFactory(true));
HttpClientConnectionFactory.Info h1 = HttpClientConnectionFactory.HTTP11;
HTTP2Client http2Client = new HTTP2Client(clientConnector);
ClientConnectionFactory.Info h2 = new ClientConnectionFactoryOverHTTP2.H2(http2Client);
client = new HttpClient(new HttpClientTransportDynamic(clientConnector, h2, h1));
client.addBean(http2Client);
client.start();
// The client prefers h2 over h1, and use of TLS and ALPN will allow the fallback to h1.
ContentResponse h1cResponse = client.newRequest("localhost", connector.getLocalPort())
.scheme("https")
.send();
assertEquals(HttpStatus.OK_200, h1cResponse.getStatus());
}
@Test
public void testServerOnlySpeaksClearTextHTTP11ClientFailsHTTP2() throws Exception
{
startServer(this::h1, new EmptyServerHandler());
ClientConnector clientConnector = new ClientConnector();
HttpClientConnectionFactory.Info h1 = HttpClientConnectionFactory.HTTP11;
HTTP2Client http2Client = new HTTP2Client(clientConnector);
ClientConnectionFactory.Info h2c = new ClientConnectionFactoryOverHTTP2.H2C(http2Client);
client = new HttpClient(new HttpClientTransportDynamic(clientConnector, h1, h2c));
client.addBean(http2Client);
client.start();
// The client forces HTTP/2, but the server cannot speak it, so the request fails.
// There is no fallback to HTTP/1 because the protocol version is set explicitly.
assertThrows(ExecutionException.class, () -> client.newRequest("localhost", connector.getLocalPort())
.version(HttpVersion.HTTP_2)
.send());
}
@Test
public void testDestinationClientConnectionFactoryWrapped() throws Exception
{
// A more complicated test simulating a reverse proxy.
//
// If a reverse proxy is totally stateless, it can proxy multiple clients
// using the same connection pool (and hence just one destination).
//
// However, if we want to use the PROXY protocol, the proxy should have one
// destination per client IP:port, because we need to send the PROXY bytes
// with the client IP:port when opening a connection to the server.
// Note that if the client speaks HTTP/2 to the proxy, but the proxy speaks
// HTTP/1.1 to the server, there may be the need for the proxy to have
// multiple HTTP/1.1 connections for the same client IP:port.
// client :1234 <-> :8888 proxy :5678 <-> server :8080
// client :2345 <-> :8888 proxy :6789 <-> server :8080
startServer(this::proxy_h1_h2c, new EmptyServerHandler()
{
@Override
protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
{
response.setContentType(MimeTypes.Type.TEXT_PLAIN.asString());
response.getOutputStream().print(request.getRemotePort());
}
});
ClientConnector clientConnector = new ClientConnector();
clientConnector.setSslContextFactory(newSslContextFactory(true));
ClientConnectionFactory.Info h1 = HttpClientConnectionFactory.HTTP11;
Map<HttpRequest, String> mapping = new ConcurrentHashMap<>();
client = new HttpClient(new HttpClientTransportDynamic(clientConnector, h1)
{
@Override
public HttpDestination.Key newDestinationKey(HttpRequest request, Origin origin)
{
String kind = mapping.remove(request);
return new HttpDestination.Key(origin, new HttpDestination.Protocol(List.of("http/1.1"), false), kind);
}
@Override
public HttpDestination newHttpDestination(HttpDestination.Key key)
{
// Here we want to wrap the destination with the PROXY
// protocol, for a specific remote client socket address.
return new MultiplexHttpDestination(client, key, factory -> new ProxyProtocolClientConnectionFactory(factory, () ->
{
String[] address = key.getKind().split(":");
return new Origin.Address(address[0], Integer.parseInt(address[1]));
}));
}
});
client.start();
// Simulate a proxy request to the server.
HttpRequest proxyRequest1 = (HttpRequest)client.newRequest("localhost", connector.getLocalPort());
// Map the proxy request to client IP:port.
int clientPort1 = ThreadLocalRandom.current().nextInt(1024, 65536);
mapping.put(proxyRequest1, "localhost:" + clientPort1);
ContentResponse proxyResponse1 = proxyRequest1.send();
assertEquals(String.valueOf(clientPort1), proxyResponse1.getContentAsString());
// Simulate another request to the server, from a different client port.
HttpRequest proxyRequest2 = (HttpRequest)client.newRequest("localhost", connector.getLocalPort());
int clientPort2 = ThreadLocalRandom.current().nextInt(1024, 65536);
mapping.put(proxyRequest2, "localhost:" + clientPort2);
ContentResponse proxyResponse2 = proxyRequest2.send();
assertEquals(String.valueOf(clientPort2), proxyResponse2.getContentAsString());
// We must have 2 different destinations with the same origin.
List<Destination> destinations = client.getDestinations();
assertEquals(2, destinations.size());
assertEquals(1, destinations.stream()
.map(HttpDestination.class::cast)
.map(HttpDestination::getKey)
.map(HttpDestination.Key::getOrigin)
.distinct()
.count());
}
@Test
public void testClearTextAndEncryptedHTTP2() throws Exception
{
prepareServer(this::ssl_alpn_h2_h1, new EmptyServerHandler());
ServerConnector clearConnector = h1_h2c(server);
server.start();
ClientConnector clientConnector = new ClientConnector();
clientConnector.setSslContextFactory(newSslContextFactory(true));
HttpClientConnectionFactory.Info h1 = HttpClientConnectionFactory.HTTP11;
HTTP2Client http2Client = new HTTP2Client(clientConnector);
ClientConnectionFactory.Info h2c = new ClientConnectionFactoryOverHTTP2.H2C(http2Client);
ClientConnectionFactory.Info h2 = new ClientConnectionFactoryOverHTTP2.H2(http2Client);
client = new HttpClient(new HttpClientTransportDynamic(clientConnector, h2, h1, h2c));
client.addBean(http2Client);
client.start();
// Make a clear-text request using HTTP/1.1.
ContentResponse h1cResponse = client.newRequest("localhost", clearConnector.getLocalPort())
.send();
assertEquals(HttpStatus.OK_200, h1cResponse.getStatus());
// Make a clear-text request using HTTP/2.
ContentResponse h2cResponse = client.newRequest("localhost", clearConnector.getLocalPort())
.version(HttpVersion.HTTP_2)
.send();
assertEquals(HttpStatus.OK_200, h2cResponse.getStatus());
// Make an encrypted request without specifying the protocol.
// Because the server prefers h2, this request will be HTTP/2, but will
// generate a different destination than an explicit HTTP/2 request (like below).
ContentResponse h1Response = client.newRequest("localhost", connector.getLocalPort())
.scheme("https")
.send();
assertEquals(HttpStatus.OK_200, h1Response.getStatus());
// Make an encrypted request using explicitly HTTP/2.
ContentResponse h2Response = client.newRequest("localhost", connector.getLocalPort())
.scheme("https")
.version(HttpVersion.HTTP_2)
.send();
assertEquals(HttpStatus.OK_200, h2Response.getStatus());
// There should be 4 destinations with 2 origins.
List<Destination> destinations = client.getDestinations();
assertEquals(4, destinations.size());
assertEquals(2, destinations.stream()
.map(HttpDestination.class::cast)
.map(HttpDestination::getKey)
.map(HttpDestination.Key::getOrigin)
.distinct()
.count());
}
}