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:
commit
ec8a1bdb23
|
@ -48,6 +48,6 @@ public class ALPNClientConnection extends NegotiatingClientConnection
|
|||
if (protocol == null || !protocols.contains(protocol))
|
||||
close();
|
||||
else
|
||||
completed();
|
||||
completed(protocol);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
})
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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<>());
|
||||
|
|
|
@ -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/"));
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue