Issue #132 - ClientConnector abstraction.
Introduced ClientConnector and refactored HttpClient transports, removing duplicated code that was connect() to a remote host. Refactored also HTTP2Client to reference ClientConnector. Refactored tests accordingly to the changes introduced in the implementations. Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
This commit is contained in:
parent
2f6d0b3fb7
commit
6c4ee083be
|
@ -27,13 +27,9 @@ import javax.net.ssl.SSLEngine;
|
|||
import org.eclipse.jetty.io.ClientConnectionFactory;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
import org.eclipse.jetty.io.NegotiatingClientConnection;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
||||
public class ALPNClientConnection extends NegotiatingClientConnection
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(ALPNClientConnection.class);
|
||||
|
||||
private final List<String> protocols;
|
||||
|
||||
public ALPNClientConnection(EndPoint endPoint, Executor executor, ClientConnectionFactory connectionFactory, SSLEngine sslEngine, Map<String, Object> context, List<String> protocols)
|
||||
|
@ -49,9 +45,9 @@ public class ALPNClientConnection extends NegotiatingClientConnection
|
|||
|
||||
public void selected(String protocol)
|
||||
{
|
||||
if (protocol==null || !protocols.contains(protocol))
|
||||
if (protocol == null || !protocols.contains(protocol))
|
||||
close();
|
||||
else
|
||||
super.completed();
|
||||
completed();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
|
||||
package org.eclipse.jetty.alpn.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
@ -96,7 +95,7 @@ public class ALPNClientConnectionFactory extends NegotiatingClientConnectionFact
|
|||
}
|
||||
|
||||
@Override
|
||||
public Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
|
||||
public Connection newConnection(EndPoint endPoint, Map<String, Object> context)
|
||||
{
|
||||
SSLEngine engine = (SSLEngine)context.get(SslClientConnectionFactory.SSL_ENGINE_CONTEXT_KEY);
|
||||
for (Client processor : processors)
|
||||
|
|
|
@ -42,6 +42,7 @@
|
|||
<artifactId>jetty-alpn-client</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.http2</groupId>
|
||||
<artifactId>http2-client</artifactId>
|
||||
|
|
|
@ -35,22 +35,19 @@ import org.eclipse.jetty.util.Callback;
|
|||
import org.eclipse.jetty.util.FuturePromise;
|
||||
import org.eclipse.jetty.util.Jetty;
|
||||
import org.eclipse.jetty.util.Promise;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
|
||||
public class JDK9HTTP2Client
|
||||
{
|
||||
public static void main(String[] args) throws Exception
|
||||
{
|
||||
HTTP2Client client = new HTTP2Client();
|
||||
SslContextFactory sslContextFactory = new SslContextFactory();
|
||||
client.addBean(sslContextFactory);
|
||||
client.start();
|
||||
|
||||
String host = "webtide.com";
|
||||
int port = 443;
|
||||
|
||||
FuturePromise<Session> sessionPromise = new FuturePromise<>();
|
||||
client.connect(sslContextFactory, new InetSocketAddress(host, port), new Session.Listener.Adapter(), sessionPromise);
|
||||
client.connect(client.getClientConnector().getSslContextFactory(), new InetSocketAddress(host, port), new Session.Listener.Adapter(), sessionPromise);
|
||||
Session session = sessionPromise.get(5, TimeUnit.SECONDS);
|
||||
|
||||
HttpFields requestFields = new HttpFields();
|
||||
|
|
|
@ -18,11 +18,7 @@
|
|||
|
||||
package org.eclipse.jetty.alpn.java.server;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
|
@ -31,7 +27,6 @@ import java.nio.charset.StandardCharsets;
|
|||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLParameters;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
|
@ -45,8 +40,12 @@ import org.eclipse.jetty.server.Server;
|
|||
import org.eclipse.jetty.server.ServerConnector;
|
||||
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
|
||||
public class JDK9ALPNTest
|
||||
{
|
||||
private Server server;
|
||||
|
@ -66,6 +65,13 @@ public class JDK9ALPNTest
|
|||
server.start();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void stopServer() throws Exception
|
||||
{
|
||||
if (server != null)
|
||||
server.stop();
|
||||
}
|
||||
|
||||
private SslContextFactory newSslContextFactory()
|
||||
{
|
||||
SslContextFactory sslContextFactory = new SslContextFactory();
|
||||
|
@ -84,7 +90,7 @@ public class JDK9ALPNTest
|
|||
startServer(new AbstractHandler.ErrorDispatchHandler()
|
||||
{
|
||||
@Override
|
||||
protected void doNonErrorHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||
protected void doNonErrorHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
|
||||
{
|
||||
baseRequest.setHandled(true);
|
||||
}
|
||||
|
@ -126,7 +132,7 @@ public class JDK9ALPNTest
|
|||
startServer(new AbstractHandler.ErrorDispatchHandler()
|
||||
{
|
||||
@Override
|
||||
protected void doNonErrorHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||
protected void doNonErrorHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
|
||||
{
|
||||
baseRequest.setHandled(true);
|
||||
}
|
||||
|
@ -163,6 +169,5 @@ public class JDK9ALPNTest
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,21 +18,12 @@
|
|||
|
||||
package org.eclipse.jetty.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.SocketException;
|
||||
import java.nio.channels.SelectableChannel;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.time.Duration;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jetty.client.api.Connection;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
import org.eclipse.jetty.io.ManagedSelector;
|
||||
import org.eclipse.jetty.io.SelectorManager;
|
||||
import org.eclipse.jetty.io.SocketChannelEndPoint;
|
||||
import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
|
||||
import org.eclipse.jetty.io.ClientConnector;
|
||||
import org.eclipse.jetty.util.Promise;
|
||||
import org.eclipse.jetty.util.annotation.ManagedAttribute;
|
||||
import org.eclipse.jetty.util.annotation.ManagedObject;
|
||||
|
@ -40,147 +31,48 @@ import org.eclipse.jetty.util.annotation.ManagedObject;
|
|||
@ManagedObject
|
||||
public abstract class AbstractConnectorHttpClientTransport extends AbstractHttpClientTransport
|
||||
{
|
||||
private final int selectors;
|
||||
private SelectorManager selectorManager;
|
||||
private final ClientConnector connector;
|
||||
|
||||
protected AbstractConnectorHttpClientTransport(int selectors)
|
||||
protected AbstractConnectorHttpClientTransport(ClientConnector connector)
|
||||
{
|
||||
this.selectors = selectors;
|
||||
this.connector = connector;
|
||||
addBean(connector);
|
||||
}
|
||||
|
||||
public ClientConnector getClientConnector()
|
||||
{
|
||||
return connector;
|
||||
}
|
||||
|
||||
@ManagedAttribute(value = "The number of selectors", readonly = true)
|
||||
public int getSelectors()
|
||||
{
|
||||
return selectors;
|
||||
return connector.getSelectors();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doStart() throws Exception
|
||||
{
|
||||
HttpClient httpClient = getHttpClient();
|
||||
selectorManager = newSelectorManager(httpClient);
|
||||
selectorManager.setConnectTimeout(httpClient.getConnectTimeout());
|
||||
addBean(selectorManager);
|
||||
connector.setBindAddress(httpClient.getBindAddress());
|
||||
connector.setByteBufferPool(httpClient.getByteBufferPool());
|
||||
connector.setConnectBlocking(httpClient.isConnectBlocking());
|
||||
connector.setConnectTimeout(Duration.ofMillis(httpClient.getConnectTimeout()));
|
||||
connector.setExecutor(httpClient.getExecutor());
|
||||
connector.setIdleTimeout(Duration.ofMillis(httpClient.getIdleTimeout()));
|
||||
connector.setScheduler(httpClient.getScheduler());
|
||||
connector.setSslContextFactory(httpClient.getSslContextFactory());
|
||||
super.doStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doStop() throws Exception
|
||||
{
|
||||
super.doStop();
|
||||
removeBean(selectorManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect(InetSocketAddress address, Map<String, Object> context)
|
||||
{
|
||||
SocketChannel channel = null;
|
||||
try
|
||||
{
|
||||
channel = SocketChannel.open();
|
||||
HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
|
||||
HttpClient client = destination.getHttpClient();
|
||||
SocketAddress bindAddress = client.getBindAddress();
|
||||
if (bindAddress != null)
|
||||
channel.bind(bindAddress);
|
||||
configure(client, channel);
|
||||
|
||||
context.put(SslClientConnectionFactory.SSL_PEER_HOST_CONTEXT_KEY, destination.getHost());
|
||||
context.put(SslClientConnectionFactory.SSL_PEER_PORT_CONTEXT_KEY, destination.getPort());
|
||||
|
||||
boolean connected = true;
|
||||
if (client.isConnectBlocking())
|
||||
{
|
||||
channel.socket().connect(address, (int)client.getConnectTimeout());
|
||||
channel.configureBlocking(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
channel.configureBlocking(false);
|
||||
connected = channel.connect(address);
|
||||
}
|
||||
if (connected)
|
||||
selectorManager.accept(channel, context);
|
||||
else
|
||||
selectorManager.connect(channel, context);
|
||||
}
|
||||
// Must catch all exceptions, since some like
|
||||
// UnresolvedAddressException are not IOExceptions.
|
||||
catch (Throwable x)
|
||||
{
|
||||
// If IPv6 is not deployed, a generic SocketException "Network is unreachable"
|
||||
// exception is being thrown, so we attempt to provide a better error message.
|
||||
if (x.getClass() == SocketException.class)
|
||||
x = new SocketException("Could not connect to " + address).initCause(x);
|
||||
|
||||
try
|
||||
{
|
||||
if (channel != null)
|
||||
channel.close();
|
||||
}
|
||||
catch (IOException xx)
|
||||
{
|
||||
LOG.ignore(xx);
|
||||
}
|
||||
finally
|
||||
{
|
||||
connectFailed(context, x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void connectFailed(Map<String, Object> context, Throwable x)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Could not connect to {}", context.get(HTTP_DESTINATION_CONTEXT_KEY));
|
||||
HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
|
||||
context.put(ClientConnector.CLIENT_CONNECTION_FACTORY_CONTEXT_KEY, destination.getClientConnectionFactory());
|
||||
@SuppressWarnings("unchecked")
|
||||
Promise<Connection> promise = (Promise<Connection>)context.get(HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
|
||||
promise.failed(x);
|
||||
}
|
||||
|
||||
protected void configure(HttpClient client, SocketChannel channel) throws IOException
|
||||
{
|
||||
channel.socket().setTcpNoDelay(client.isTCPNoDelay());
|
||||
}
|
||||
|
||||
protected SelectorManager newSelectorManager(HttpClient client)
|
||||
{
|
||||
return new ClientSelectorManager(client, getSelectors());
|
||||
}
|
||||
|
||||
protected class ClientSelectorManager extends SelectorManager
|
||||
{
|
||||
private final HttpClient client;
|
||||
|
||||
protected ClientSelectorManager(HttpClient client, int selectors)
|
||||
{
|
||||
super(client.getExecutor(), client.getScheduler(), selectors);
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected EndPoint newEndPoint(SelectableChannel channel, ManagedSelector selector, SelectionKey key)
|
||||
{
|
||||
SocketChannelEndPoint endp = new SocketChannelEndPoint(channel, selector, key, getScheduler());
|
||||
endp.setIdleTimeout(client.getIdleTimeout());
|
||||
return endp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public org.eclipse.jetty.io.Connection newConnection(SelectableChannel channel, EndPoint endPoint, Object attachment) throws IOException
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> context = (Map<String, Object>)attachment;
|
||||
HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
|
||||
return destination.getClientConnectionFactory().newConnection(endPoint, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void connectionFailed(SelectableChannel channel, Throwable x, Object attachment)
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> context = (Map<String, Object>)attachment;
|
||||
connectFailed(context, x);
|
||||
}
|
||||
context.put(ClientConnector.CONNECTION_PROMISE_CONTEXT_KEY, new Promise.Wrapper<>(promise));
|
||||
connector.connect(address, context);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,10 @@
|
|||
|
||||
package org.eclipse.jetty.client;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jetty.client.api.Connection;
|
||||
import org.eclipse.jetty.util.Promise;
|
||||
import org.eclipse.jetty.util.annotation.ManagedObject;
|
||||
import org.eclipse.jetty.util.component.ContainerLifeCycle;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
|
@ -53,4 +57,13 @@ public abstract class AbstractHttpClientTransport extends ContainerLifeCycle imp
|
|||
{
|
||||
this.factory = factory;
|
||||
}
|
||||
|
||||
protected void connectFailed(Map<String, Object> context, Throwable failure)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Could not connect to {}", context.get(HTTP_DESTINATION_CONTEXT_KEY));
|
||||
@SuppressWarnings("unchecked")
|
||||
Promise<Connection> promise = (Promise<Connection>)context.get(HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
|
||||
promise.failed(failure);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -81,16 +81,16 @@ import org.eclipse.jetty.util.thread.Scheduler;
|
|||
import org.eclipse.jetty.util.thread.ThreadPool;
|
||||
|
||||
/**
|
||||
* <p>{@link HttpClient} provides an efficient, asynchronous, non-blocking implementation
|
||||
* <p>HttpClient provides an efficient, asynchronous, non-blocking implementation
|
||||
* to perform HTTP requests to a server through a simple API that offers also blocking semantic.</p>
|
||||
* <p>{@link HttpClient} provides easy-to-use methods such as {@link #GET(String)} that allow to perform HTTP
|
||||
* <p>HttpClient provides easy-to-use methods such as {@link #GET(String)} that allow to perform HTTP
|
||||
* requests in a one-liner, but also gives the ability to fine tune the configuration of requests via
|
||||
* {@link HttpClient#newRequest(URI)}.</p>
|
||||
* <p>{@link HttpClient} acts as a central configuration point for network parameters (such as idle timeouts)
|
||||
* <p>HttpClient acts as a central configuration point for network parameters (such as idle timeouts)
|
||||
* and HTTP parameters (such as whether to follow redirects).</p>
|
||||
* <p>{@link HttpClient} transparently pools connections to servers, but allows direct control of connections
|
||||
* <p>HttpClient transparently pools connections to servers, but allows direct control of connections
|
||||
* for cases where this is needed.</p>
|
||||
* <p>{@link HttpClient} also acts as a central configuration point for cookies, via {@link #getCookieStore()}.</p>
|
||||
* <p>HttpClient also acts as a central configuration point for cookies, via {@link #getCookieStore()}.</p>
|
||||
* <p>Typical usage:</p>
|
||||
* <pre>
|
||||
* HttpClient httpClient = new HttpClient();
|
||||
|
@ -157,7 +157,7 @@ public class HttpClient extends ContainerLifeCycle
|
|||
private String defaultRequestContentType = "application/octet-stream";
|
||||
|
||||
/**
|
||||
* Creates a {@link HttpClient} instance that can perform requests to non-TLS destinations only
|
||||
* Creates a HttpClient instance that can perform requests to non-TLS destinations only
|
||||
* (that is, requests with the "http" scheme only, and not "https").
|
||||
*
|
||||
* @see #HttpClient(SslContextFactory) to perform requests to TLS destinations.
|
||||
|
@ -168,7 +168,7 @@ public class HttpClient extends ContainerLifeCycle
|
|||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link HttpClient} instance that can perform requests to non-TLS and TLS destinations
|
||||
* Creates a HttpClient instance that can perform requests to non-TLS and TLS destinations
|
||||
* (that is, both requests with the "http" scheme and with the "https" scheme).
|
||||
*
|
||||
* @param sslContextFactory the {@link SslContextFactory} that manages TLS encryption
|
||||
|
@ -517,7 +517,7 @@ public class HttpClient extends ContainerLifeCycle
|
|||
/**
|
||||
* Returns a {@link Destination} for the given scheme, host and port.
|
||||
* Applications may use {@link Destination}s to create {@link Connection}s
|
||||
* that will be outside {@link HttpClient}'s pooling mechanism, to explicitly
|
||||
* that will be outside HttpClient's pooling mechanism, to explicitly
|
||||
* control the connection lifecycle (in particular their termination with
|
||||
* {@link Connection#close()}).
|
||||
*
|
||||
|
@ -570,7 +570,7 @@ public class HttpClient extends ContainerLifeCycle
|
|||
}
|
||||
|
||||
/**
|
||||
* @return the list of destinations known to this {@link HttpClient}.
|
||||
* @return the list of destinations known to this HttpClient.
|
||||
*/
|
||||
public List<Destination> getDestinations()
|
||||
{
|
||||
|
@ -586,13 +586,13 @@ public class HttpClient extends ContainerLifeCycle
|
|||
protected void newConnection(final HttpDestination destination, final Promise<Connection> promise)
|
||||
{
|
||||
Origin.Address address = destination.getConnectAddress();
|
||||
resolver.resolve(address.getHost(), address.getPort(), new Promise<List<InetSocketAddress>>()
|
||||
resolver.resolve(address.getHost(), address.getPort(), new Promise<>()
|
||||
{
|
||||
@Override
|
||||
public void succeeded(List<InetSocketAddress> socketAddresses)
|
||||
{
|
||||
Map<String, Object> context = new HashMap<>();
|
||||
context.put(ClientConnectionFactory.CONNECTOR_CONTEXT_KEY, HttpClient.this);
|
||||
context.put(ClientConnectionFactory.CLIENT_CONTEXT_KEY, HttpClient.this);
|
||||
context.put(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY, destination);
|
||||
connect(socketAddresses, 0, context);
|
||||
}
|
||||
|
@ -605,7 +605,7 @@ public class HttpClient extends ContainerLifeCycle
|
|||
|
||||
private void connect(List<InetSocketAddress> socketAddresses, int index, Map<String, Object> context)
|
||||
{
|
||||
context.put(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY, new Promise.Wrapper<Connection>(promise)
|
||||
context.put(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY, new Promise.Wrapper<>(promise)
|
||||
{
|
||||
@Override
|
||||
public void failed(Throwable x)
|
||||
|
@ -638,7 +638,7 @@ public class HttpClient extends ContainerLifeCycle
|
|||
}
|
||||
|
||||
/**
|
||||
* @return the {@link ByteBufferPool} of this {@link HttpClient}
|
||||
* @return the {@link ByteBufferPool} of this HttpClient
|
||||
*/
|
||||
public ByteBufferPool getByteBufferPool()
|
||||
{
|
||||
|
@ -646,7 +646,7 @@ public class HttpClient extends ContainerLifeCycle
|
|||
}
|
||||
|
||||
/**
|
||||
* @param byteBufferPool the {@link ByteBufferPool} of this {@link HttpClient}
|
||||
* @param byteBufferPool the {@link ByteBufferPool} of this HttpClient
|
||||
*/
|
||||
public void setByteBufferPool(ByteBufferPool byteBufferPool)
|
||||
{
|
||||
|
@ -706,7 +706,7 @@ public class HttpClient extends ContainerLifeCycle
|
|||
|
||||
/**
|
||||
* <p>Sets the socket address resolution timeout used by the default {@link SocketAddressResolver}
|
||||
* created by this {@link HttpClient} at startup.</p>
|
||||
* created by this HttpClient at startup.</p>
|
||||
* <p>For more fine tuned configuration of socket address resolution, see
|
||||
* {@link #setSocketAddressResolver(SocketAddressResolver)}.</p>
|
||||
*
|
||||
|
@ -755,7 +755,7 @@ public class HttpClient extends ContainerLifeCycle
|
|||
}
|
||||
|
||||
/**
|
||||
* @return the "User-Agent" HTTP field of this {@link HttpClient}
|
||||
* @return the "User-Agent" HTTP field of this HttpClient
|
||||
*/
|
||||
public HttpField getUserAgentField()
|
||||
{
|
||||
|
@ -763,7 +763,7 @@ public class HttpClient extends ContainerLifeCycle
|
|||
}
|
||||
|
||||
/**
|
||||
* @param agent the "User-Agent" HTTP header string of this {@link HttpClient}
|
||||
* @param agent the "User-Agent" HTTP header string of this HttpClient
|
||||
*/
|
||||
public void setUserAgentField(HttpField agent)
|
||||
{
|
||||
|
@ -773,7 +773,7 @@ public class HttpClient extends ContainerLifeCycle
|
|||
}
|
||||
|
||||
/**
|
||||
* @return whether this {@link HttpClient} follows HTTP redirects
|
||||
* @return whether this HttpClient follows HTTP redirects
|
||||
* @see Request#isFollowRedirects()
|
||||
*/
|
||||
@ManagedAttribute("Whether HTTP redirects are followed")
|
||||
|
@ -783,7 +783,7 @@ public class HttpClient extends ContainerLifeCycle
|
|||
}
|
||||
|
||||
/**
|
||||
* @param follow whether this {@link HttpClient} follows HTTP redirects
|
||||
* @param follow whether this HttpClient follows HTTP redirects
|
||||
* @see #setMaxRedirects(int)
|
||||
*/
|
||||
public void setFollowRedirects(boolean follow)
|
||||
|
@ -792,7 +792,7 @@ public class HttpClient extends ContainerLifeCycle
|
|||
}
|
||||
|
||||
/**
|
||||
* @return the {@link Executor} of this {@link HttpClient}
|
||||
* @return the {@link Executor} of this HttpClient
|
||||
*/
|
||||
public Executor getExecutor()
|
||||
{
|
||||
|
@ -800,7 +800,7 @@ public class HttpClient extends ContainerLifeCycle
|
|||
}
|
||||
|
||||
/**
|
||||
* @param executor the {@link Executor} of this {@link HttpClient}
|
||||
* @param executor the {@link Executor} of this HttpClient
|
||||
*/
|
||||
public void setExecutor(Executor executor)
|
||||
{
|
||||
|
@ -811,7 +811,7 @@ public class HttpClient extends ContainerLifeCycle
|
|||
}
|
||||
|
||||
/**
|
||||
* @return the {@link Scheduler} of this {@link HttpClient}
|
||||
* @return the {@link Scheduler} of this HttpClient
|
||||
*/
|
||||
public Scheduler getScheduler()
|
||||
{
|
||||
|
@ -819,7 +819,7 @@ public class HttpClient extends ContainerLifeCycle
|
|||
}
|
||||
|
||||
/**
|
||||
* @param scheduler the {@link Scheduler} of this {@link HttpClient}
|
||||
* @param scheduler the {@link Scheduler} of this HttpClient
|
||||
*/
|
||||
public void setScheduler(Scheduler scheduler)
|
||||
{
|
||||
|
@ -830,7 +830,7 @@ public class HttpClient extends ContainerLifeCycle
|
|||
}
|
||||
|
||||
/**
|
||||
* @return the {@link SocketAddressResolver} of this {@link HttpClient}
|
||||
* @return the {@link SocketAddressResolver} of this HttpClient
|
||||
*/
|
||||
public SocketAddressResolver getSocketAddressResolver()
|
||||
{
|
||||
|
@ -838,7 +838,7 @@ public class HttpClient extends ContainerLifeCycle
|
|||
}
|
||||
|
||||
/**
|
||||
* @param resolver the {@link SocketAddressResolver} of this {@link HttpClient}
|
||||
* @param resolver the {@link SocketAddressResolver} of this HttpClient
|
||||
*/
|
||||
public void setSocketAddressResolver(SocketAddressResolver resolver)
|
||||
{
|
||||
|
@ -849,7 +849,7 @@ public class HttpClient extends ContainerLifeCycle
|
|||
}
|
||||
|
||||
/**
|
||||
* @return the max number of connections that this {@link HttpClient} opens to {@link Destination}s
|
||||
* @return the max number of connections that this HttpClient opens to {@link Destination}s
|
||||
*/
|
||||
@ManagedAttribute("The max number of connections per each destination")
|
||||
public int getMaxConnectionsPerDestination()
|
||||
|
@ -862,11 +862,11 @@ public class HttpClient extends ContainerLifeCycle
|
|||
* <p>
|
||||
* RFC 2616 suggests that 2 connections should be opened per each destination,
|
||||
* but browsers commonly open 6.
|
||||
* If this {@link HttpClient} is used for load testing, it is common to have only one destination
|
||||
* If this HttpClient is used for load testing, it is common to have only one destination
|
||||
* (the server to load test), and it is recommended to set this value to a high value (at least as
|
||||
* much as the threads present in the {@link #getExecutor() executor}).
|
||||
*
|
||||
* @param maxConnectionsPerDestination the max number of connections that this {@link HttpClient} opens to {@link Destination}s
|
||||
* @param maxConnectionsPerDestination the max number of connections that this HttpClient opens to {@link Destination}s
|
||||
*/
|
||||
public void setMaxConnectionsPerDestination(int maxConnectionsPerDestination)
|
||||
{
|
||||
|
@ -885,11 +885,11 @@ public class HttpClient extends ContainerLifeCycle
|
|||
/**
|
||||
* Sets the max number of requests that may be queued to a destination.
|
||||
* <p>
|
||||
* If this {@link HttpClient} performs a high rate of requests to a destination,
|
||||
* If this HttpClient performs a high rate of requests to a destination,
|
||||
* and all the connections managed by that destination are busy with other requests,
|
||||
* then new requests will be queued up in the destination.
|
||||
* This parameter controls how many requests can be queued before starting to reject them.
|
||||
* If this {@link HttpClient} is used for load testing, it is common to have this parameter
|
||||
* If this HttpClient is used for load testing, it is common to have this parameter
|
||||
* set to a high value, although this may impact latency (requests sit in the queue for a long
|
||||
* time before being sent).
|
||||
*
|
||||
|
@ -970,35 +970,6 @@ public class HttpClient extends ContainerLifeCycle
|
|||
this.tcpNoDelay = tcpNoDelay;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true to dispatch I/O operations in a different thread, false to execute them in the selector thread
|
||||
* @see #setDispatchIO(boolean)
|
||||
*/
|
||||
@Deprecated
|
||||
public boolean isDispatchIO()
|
||||
{
|
||||
// TODO this did default to true, so usage needs to be evaluated.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to dispatch I/O operations from the selector thread to a different thread.
|
||||
* <p>
|
||||
* This implementation never blocks on I/O operation, but invokes application callbacks that may
|
||||
* take time to execute or block on other I/O.
|
||||
* If application callbacks are known to take time or block on I/O, then parameter {@code dispatchIO}
|
||||
* should be set to true.
|
||||
* If application callbacks are known to be quick and never block on I/O, then parameter {@code dispatchIO}
|
||||
* may be set to false.
|
||||
*
|
||||
* @param dispatchIO true to dispatch I/O operations in a different thread,
|
||||
* false to execute them in the selector thread
|
||||
*/
|
||||
@Deprecated
|
||||
public void setDispatchIO(boolean dispatchIO)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the http compliance mode for parsing http responses.
|
||||
* The default http compliance level is {@link HttpCompliance#RFC7230} which is the latest HTTP/1.1 specification
|
||||
|
@ -1256,7 +1227,7 @@ public class HttpClient extends ContainerLifeCycle
|
|||
public Iterator<ContentDecoder.Factory> iterator()
|
||||
{
|
||||
final Iterator<ContentDecoder.Factory> iterator = set.iterator();
|
||||
return new Iterator<ContentDecoder.Factory>()
|
||||
return new Iterator<>()
|
||||
{
|
||||
@Override
|
||||
public boolean hasNext()
|
||||
|
|
|
@ -36,8 +36,8 @@ import org.eclipse.jetty.io.ClientConnectionFactory;
|
|||
*/
|
||||
public interface HttpClientTransport extends ClientConnectionFactory
|
||||
{
|
||||
public static final String HTTP_DESTINATION_CONTEXT_KEY = "http.destination";
|
||||
public static final String HTTP_CONNECTION_PROMISE_CONTEXT_KEY = "http.connection.promise";
|
||||
public static final String HTTP_DESTINATION_CONTEXT_KEY = "org.eclipse.jetty.client.destination";
|
||||
public static final String HTTP_CONNECTION_PROMISE_CONTEXT_KEY = "org.eclipse.jetty.client.connection.promise";
|
||||
|
||||
/**
|
||||
* Sets the {@link HttpClient} instance on this transport.
|
||||
|
|
|
@ -48,6 +48,23 @@ public class Origin
|
|||
return address;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null || getClass() != obj.getClass())
|
||||
return false;
|
||||
Origin that = (Origin)obj;
|
||||
return scheme.equals(that.scheme) && address.equals(that.address);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return Objects.hash(scheme, address);
|
||||
}
|
||||
|
||||
public String asString()
|
||||
{
|
||||
StringBuilder result = new StringBuilder();
|
||||
|
@ -56,20 +73,9 @@ public class Origin
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
public String toString()
|
||||
{
|
||||
if (this == obj) return true;
|
||||
if (obj == null || getClass() != obj.getClass()) return false;
|
||||
Origin that = (Origin)obj;
|
||||
return scheme.equals(that.scheme) && address.equals(that.address);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
int result = scheme.hashCode();
|
||||
result = 31 * result + address.hashCode();
|
||||
return result;
|
||||
return asString();
|
||||
}
|
||||
|
||||
public static class Address
|
||||
|
@ -96,8 +102,10 @@ public class Origin
|
|||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (this == obj) return true;
|
||||
if (obj == null || getClass() != obj.getClass()) return false;
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null || getClass() != obj.getClass())
|
||||
return false;
|
||||
Address that = (Address)obj;
|
||||
return host.equals(that.host) && port == that.port;
|
||||
}
|
||||
|
@ -105,9 +113,7 @@ public class Origin
|
|||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
int result = host.hashCode();
|
||||
result = 31 * result + port;
|
||||
return result;
|
||||
return Objects.hash(host, port);
|
||||
}
|
||||
|
||||
public String asString()
|
||||
|
|
|
@ -203,16 +203,7 @@ public class ResponseNotifier
|
|||
|
||||
public void forwardSuccess(List<Response.ResponseListener> listeners, Response response)
|
||||
{
|
||||
notifyBegin(listeners, response);
|
||||
for (Iterator<HttpField> iterator = response.getHeaders().iterator(); iterator.hasNext();)
|
||||
{
|
||||
HttpField field = iterator.next();
|
||||
if (!notifyHeader(listeners, response, field))
|
||||
iterator.remove();
|
||||
}
|
||||
notifyHeaders(listeners, response);
|
||||
if (response instanceof ContentResponse)
|
||||
notifyContent(listeners, response, ByteBuffer.wrap(((ContentResponse)response).getContent()), Callback.NOOP);
|
||||
forwardEvents(listeners, response);
|
||||
notifySuccess(listeners, response);
|
||||
}
|
||||
|
||||
|
@ -223,9 +214,16 @@ public class ResponseNotifier
|
|||
}
|
||||
|
||||
public void forwardFailure(List<Response.ResponseListener> listeners, Response response, Throwable failure)
|
||||
{
|
||||
forwardEvents(listeners, response);
|
||||
notifyFailure(listeners, response, failure);
|
||||
}
|
||||
|
||||
private void forwardEvents(List<Response.ResponseListener> listeners, Response response)
|
||||
{
|
||||
notifyBegin(listeners, response);
|
||||
for (Iterator<HttpField> iterator = response.getHeaders().iterator(); iterator.hasNext();)
|
||||
Iterator<HttpField> iterator = response.getHeaders().iterator();
|
||||
while (iterator.hasNext())
|
||||
{
|
||||
HttpField field = iterator.next();
|
||||
if (!notifyHeader(listeners, response, field))
|
||||
|
@ -233,8 +231,11 @@ public class ResponseNotifier
|
|||
}
|
||||
notifyHeaders(listeners, response);
|
||||
if (response instanceof ContentResponse)
|
||||
notifyContent(listeners, response, ByteBuffer.wrap(((ContentResponse)response).getContent()), Callback.NOOP);
|
||||
notifyFailure(listeners, response, failure);
|
||||
{
|
||||
byte[] content = ((ContentResponse)response).getContent();
|
||||
if (content != null && content.length > 0)
|
||||
notifyContent(listeners, response, ByteBuffer.wrap(content), Callback.NOOP);
|
||||
}
|
||||
}
|
||||
|
||||
public void forwardFailureComplete(List<Response.ResponseListener> listeners, Request request, Throwable requestFailure, Response response, Throwable responseFailure)
|
||||
|
|
|
@ -26,6 +26,7 @@ import org.eclipse.jetty.client.DuplexConnectionPool;
|
|||
import org.eclipse.jetty.client.HttpDestination;
|
||||
import org.eclipse.jetty.client.Origin;
|
||||
import org.eclipse.jetty.client.api.Connection;
|
||||
import org.eclipse.jetty.io.ClientConnector;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
import org.eclipse.jetty.util.ProcessorUtils;
|
||||
import org.eclipse.jetty.util.Promise;
|
||||
|
@ -36,12 +37,18 @@ public class HttpClientTransportOverHTTP extends AbstractConnectorHttpClientTran
|
|||
{
|
||||
public HttpClientTransportOverHTTP()
|
||||
{
|
||||
this(Math.max( 1, ProcessorUtils.availableProcessors() / 2));
|
||||
this(Math.max(1, ProcessorUtils.availableProcessors() / 2));
|
||||
}
|
||||
|
||||
public HttpClientTransportOverHTTP(int selectors)
|
||||
{
|
||||
super(selectors);
|
||||
this(new ClientConnector());
|
||||
getClientConnector().setSelectors(selectors);
|
||||
}
|
||||
|
||||
public HttpClientTransportOverHTTP(ClientConnector connector)
|
||||
{
|
||||
super(connector);
|
||||
setConnectionPoolFactory(destination -> new DuplexConnectionPool(destination, getHttpClient().getMaxConnectionsPerDestination(), destination));
|
||||
}
|
||||
|
||||
|
|
|
@ -93,7 +93,6 @@ public class HttpConnectionOverHTTP extends AbstractConnection implements Connec
|
|||
return bytesOut.longValue();
|
||||
}
|
||||
|
||||
|
||||
protected void addBytesOut(long bytesOut)
|
||||
{
|
||||
this.bytesOut.add(bytesOut);
|
||||
|
|
|
@ -18,9 +18,6 @@
|
|||
|
||||
package org.eclipse.jetty.client;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Map;
|
||||
|
@ -49,9 +46,11 @@ import org.eclipse.jetty.util.Callback;
|
|||
import org.eclipse.jetty.util.Promise;
|
||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public class HttpClientCustomProxyTest
|
||||
{
|
||||
public static final byte[] CAFE_BABE = new byte[]{(byte)0xCA, (byte)0xFE, (byte)0xBA, (byte)0xBE};
|
||||
|
|
|
@ -18,15 +18,6 @@
|
|||
|
||||
package org.eclipse.jetty.client;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assumptions.assumeTrue;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
|
@ -53,17 +44,22 @@ import org.eclipse.jetty.io.ssl.SslHandshakeListener;
|
|||
import org.eclipse.jetty.server.Handler;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.ServerConnector;
|
||||
import org.eclipse.jetty.util.JavaVersion;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.eclipse.jetty.util.thread.ExecutorThreadPool;
|
||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Assumptions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.DisabledOnJre;
|
||||
import org.junit.jupiter.api.condition.JRE;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class HttpClientTLSTest
|
||||
{
|
||||
private Server server;
|
||||
|
|
|
@ -18,16 +18,6 @@
|
|||
|
||||
package org.eclipse.jetty.client;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
@ -106,6 +96,16 @@ import org.junit.jupiter.api.extension.ExtendWith;
|
|||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ArgumentsSource;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@ExtendWith(WorkDirExtension.class)
|
||||
public class HttpClientTest extends AbstractHttpClientServerTest
|
||||
{
|
||||
|
@ -1716,10 +1716,12 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
|||
|
||||
try (ServerSocket server = new ServerSocket(0))
|
||||
{
|
||||
startClient(scenario);
|
||||
client.setMaxConnectionsPerDestination(1);
|
||||
int idleTimeout = 2000;
|
||||
client.setIdleTimeout(idleTimeout);
|
||||
startClient(scenario, null, httpClient ->
|
||||
{
|
||||
httpClient.setMaxConnectionsPerDestination(1);
|
||||
httpClient.setIdleTimeout(idleTimeout);
|
||||
});
|
||||
|
||||
Request request = client.newRequest("localhost", server.getLocalPort())
|
||||
.scheme(scenario.getScheme())
|
||||
|
|
|
@ -18,13 +18,12 @@
|
|||
|
||||
package org.eclipse.jetty.client;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
|
||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
public class InsufficientThreadsDetectionTest
|
||||
{
|
||||
@Test
|
||||
|
@ -33,9 +32,7 @@ public class InsufficientThreadsDetectionTest
|
|||
QueuedThreadPool clientThreads = new QueuedThreadPool(1);
|
||||
HttpClient httpClient = new HttpClient(new HttpClientTransportOverHTTP(1), null);
|
||||
httpClient.setExecutor(clientThreads);
|
||||
assertThrows(IllegalStateException.class, ()->{
|
||||
httpClient.start();
|
||||
});
|
||||
assertThrows(IllegalStateException.class, httpClient::start);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -46,7 +43,8 @@ public class InsufficientThreadsDetectionTest
|
|||
httpClient1.setExecutor(clientThreads);
|
||||
httpClient1.start();
|
||||
|
||||
assertThrows(IllegalStateException.class, ()->{
|
||||
assertThrows(IllegalStateException.class, () ->
|
||||
{
|
||||
// Share the same thread pool with another instance.
|
||||
HttpClient httpClient2 = new HttpClient(new HttpClientTransportOverHTTP(1), null);
|
||||
httpClient2.setExecutor(clientThreads);
|
||||
|
|
|
@ -18,9 +18,6 @@
|
|||
|
||||
package org.eclipse.jetty.client.http;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
@ -39,10 +36,18 @@ import org.eclipse.jetty.client.api.Request;
|
|||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpHeaderValue;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ArgumentsSource;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
public class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest
|
||||
{
|
||||
@ParameterizedTest
|
||||
|
@ -178,10 +183,9 @@ public class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest
|
|||
@ArgumentsSource(ScenarioProvider.class)
|
||||
public void test_IdleConnection_IdleTimeout(Scenario scenario) throws Exception
|
||||
{
|
||||
start(scenario, new EmptyServerHandler());
|
||||
|
||||
startServer(scenario, new EmptyServerHandler());
|
||||
long idleTimeout = 1000;
|
||||
client.setIdleTimeout(idleTimeout);
|
||||
startClient(scenario, null, httpClient -> httpClient.setIdleTimeout(idleTimeout));
|
||||
|
||||
try (HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", connector.getLocalPort())))
|
||||
{
|
||||
|
|
|
@ -29,6 +29,7 @@ import org.eclipse.jetty.client.api.Connection;
|
|||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.fcgi.FCGI;
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.io.ClientConnector;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
import org.eclipse.jetty.util.ProcessorUtils;
|
||||
import org.eclipse.jetty.util.Promise;
|
||||
|
@ -47,7 +48,13 @@ public class HttpClientTransportOverFCGI extends AbstractConnectorHttpClientTran
|
|||
|
||||
public HttpClientTransportOverFCGI(int selectors, String scriptRoot)
|
||||
{
|
||||
super(selectors);
|
||||
this(new ClientConnector(), scriptRoot);
|
||||
getClientConnector().setSelectors(selectors);
|
||||
}
|
||||
|
||||
public HttpClientTransportOverFCGI(ClientConnector connector, String scriptRoot)
|
||||
{
|
||||
super(connector);
|
||||
this.scriptRoot = scriptRoot;
|
||||
setConnectionPoolFactory(destination ->
|
||||
{
|
||||
|
|
|
@ -18,13 +18,10 @@
|
|||
|
||||
package org.eclipse.jetty.http2.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.nio.channels.SelectableChannel;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.Arrays;
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -38,32 +35,24 @@ import org.eclipse.jetty.http2.frames.Frame;
|
|||
import org.eclipse.jetty.http2.frames.SettingsFrame;
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.io.ClientConnectionFactory;
|
||||
import org.eclipse.jetty.io.Connection;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
import org.eclipse.jetty.io.ManagedSelector;
|
||||
import org.eclipse.jetty.io.MappedByteBufferPool;
|
||||
import org.eclipse.jetty.io.SelectorManager;
|
||||
import org.eclipse.jetty.io.SocketChannelEndPoint;
|
||||
import org.eclipse.jetty.io.ClientConnector;
|
||||
import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
|
||||
import org.eclipse.jetty.util.Promise;
|
||||
import org.eclipse.jetty.util.annotation.ManagedAttribute;
|
||||
import org.eclipse.jetty.util.annotation.ManagedObject;
|
||||
import org.eclipse.jetty.util.component.ContainerLifeCycle;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||
import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
|
||||
import org.eclipse.jetty.util.thread.Scheduler;
|
||||
|
||||
/**
|
||||
* <p>{@link HTTP2Client} provides an asynchronous, non-blocking implementation
|
||||
* <p>HTTP2Client provides an asynchronous, non-blocking implementation
|
||||
* to send HTTP/2 frames to a server.</p>
|
||||
* <p>Typical usage:</p>
|
||||
* <pre>
|
||||
* // Create and start HTTP2Client.
|
||||
* HTTP2Client client = new HTTP2Client();
|
||||
* SslContextFactory sslContextFactory = new SslContextFactory();
|
||||
* client.addBean(sslContextFactory);
|
||||
* client.start();
|
||||
* SslContextFactory sslContextFactory = client.getClientConnector().getSslContextFactory();
|
||||
*
|
||||
* // Connect to host.
|
||||
* String host = "webtide.com";
|
||||
|
@ -108,7 +97,7 @@ import org.eclipse.jetty.util.thread.Scheduler;
|
|||
* // Use the Stream object to send request content, if any, using a DATA frame.
|
||||
* ByteBuffer content = ...;
|
||||
* DataFrame requestContent = new DataFrame(stream.getId(), content, true);
|
||||
* stream.data(requestContent, Callback.Adapter.INSTANCE);
|
||||
* stream.data(requestContent, Callback.NOOP);
|
||||
*
|
||||
* // When done, stop the client.
|
||||
* client.stop();
|
||||
|
@ -117,18 +106,9 @@ import org.eclipse.jetty.util.thread.Scheduler;
|
|||
@ManagedObject
|
||||
public class HTTP2Client extends ContainerLifeCycle
|
||||
{
|
||||
private Executor executor;
|
||||
private Scheduler scheduler;
|
||||
private ByteBufferPool bufferPool;
|
||||
private ClientConnectionFactory connectionFactory;
|
||||
private SelectorManager selector;
|
||||
private int selectors = 1;
|
||||
private long idleTimeout = 30000;
|
||||
private long connectTimeout = 10000;
|
||||
private boolean connectBlocking;
|
||||
private SocketAddress bindAddress;
|
||||
private final ClientConnector connector;
|
||||
private int inputBufferSize = 8192;
|
||||
private List<String> protocols = Arrays.asList("h2", "h2-17", "h2-16", "h2-15", "h2-14");
|
||||
private List<String> protocols = List.of("h2");
|
||||
private int initialSessionRecvWindow = 16 * 1024 * 1024;
|
||||
private int initialStreamRecvWindow = 8 * 1024 * 1024;
|
||||
private int maxFrameLength = Frame.DEFAULT_MAX_LENGTH;
|
||||
|
@ -136,96 +116,50 @@ public class HTTP2Client extends ContainerLifeCycle
|
|||
private int maxSettingsKeys = SettingsFrame.DEFAULT_MAX_KEYS;
|
||||
private FlowControlStrategy.Factory flowControlStrategyFactory = () -> new BufferingFlowControlStrategy(0.5F);
|
||||
|
||||
@Override
|
||||
protected void doStart() throws Exception
|
||||
public HTTP2Client()
|
||||
{
|
||||
if (executor == null)
|
||||
setExecutor(new QueuedThreadPool());
|
||||
|
||||
if (scheduler == null)
|
||||
setScheduler(new ScheduledExecutorScheduler());
|
||||
|
||||
if (bufferPool == null)
|
||||
setByteBufferPool(new MappedByteBufferPool());
|
||||
|
||||
if (connectionFactory == null)
|
||||
{
|
||||
HTTP2ClientConnectionFactory h2 = new HTTP2ClientConnectionFactory();
|
||||
setClientConnectionFactory((endPoint, context) ->
|
||||
{
|
||||
ClientConnectionFactory factory = h2;
|
||||
SslContextFactory sslContextFactory = (SslContextFactory)context.get(SslClientConnectionFactory.SSL_CONTEXT_FACTORY_CONTEXT_KEY);
|
||||
if (sslContextFactory != null)
|
||||
{
|
||||
ALPNClientConnectionFactory alpn = new ALPNClientConnectionFactory(getExecutor(), h2, getProtocols());
|
||||
factory = newSslClientConnectionFactory(sslContextFactory, alpn);
|
||||
}
|
||||
return factory.newConnection(endPoint, context);
|
||||
});
|
||||
}
|
||||
|
||||
if (selector == null)
|
||||
{
|
||||
selector = newSelectorManager();
|
||||
addBean(selector);
|
||||
}
|
||||
selector.setConnectTimeout(getConnectTimeout());
|
||||
|
||||
super.doStart();
|
||||
this(new ClientConnector());
|
||||
}
|
||||
|
||||
protected SelectorManager newSelectorManager()
|
||||
public HTTP2Client(ClientConnector connector)
|
||||
{
|
||||
return new ClientSelectorManager(getExecutor(), getScheduler(), getSelectors());
|
||||
this.connector = connector;
|
||||
addBean(connector);
|
||||
}
|
||||
|
||||
protected ClientConnectionFactory newSslClientConnectionFactory(SslContextFactory sslContextFactory, ClientConnectionFactory connectionFactory)
|
||||
public ClientConnector getClientConnector()
|
||||
{
|
||||
return new SslClientConnectionFactory(sslContextFactory, getByteBufferPool(), getExecutor(), connectionFactory);
|
||||
return connector;
|
||||
}
|
||||
|
||||
public Executor getExecutor()
|
||||
{
|
||||
return executor;
|
||||
return connector.getExecutor();
|
||||
}
|
||||
|
||||
public void setExecutor(Executor executor)
|
||||
{
|
||||
this.updateBean(this.executor, executor);
|
||||
this.executor = executor;
|
||||
connector.setExecutor(executor);
|
||||
}
|
||||
|
||||
public Scheduler getScheduler()
|
||||
{
|
||||
return scheduler;
|
||||
return connector.getScheduler();
|
||||
}
|
||||
|
||||
public void setScheduler(Scheduler scheduler)
|
||||
{
|
||||
this.updateBean(this.scheduler, scheduler);
|
||||
this.scheduler = scheduler;
|
||||
connector.setScheduler(scheduler);
|
||||
}
|
||||
|
||||
public ByteBufferPool getByteBufferPool()
|
||||
{
|
||||
return bufferPool;
|
||||
return connector.getByteBufferPool();
|
||||
}
|
||||
|
||||
public void setByteBufferPool(ByteBufferPool bufferPool)
|
||||
{
|
||||
this.updateBean(this.bufferPool, bufferPool);
|
||||
this.bufferPool = bufferPool;
|
||||
}
|
||||
|
||||
public ClientConnectionFactory getClientConnectionFactory()
|
||||
{
|
||||
return connectionFactory;
|
||||
}
|
||||
|
||||
public void setClientConnectionFactory(ClientConnectionFactory connectionFactory)
|
||||
{
|
||||
this.updateBean(this.connectionFactory, connectionFactory);
|
||||
this.connectionFactory = connectionFactory;
|
||||
connector.setByteBufferPool(bufferPool);
|
||||
}
|
||||
|
||||
public FlowControlStrategy.Factory getFlowControlStrategyFactory()
|
||||
|
@ -241,58 +175,55 @@ public class HTTP2Client extends ContainerLifeCycle
|
|||
@ManagedAttribute("The number of selectors")
|
||||
public int getSelectors()
|
||||
{
|
||||
return selectors;
|
||||
return connector.getSelectors();
|
||||
}
|
||||
|
||||
public void setSelectors(int selectors)
|
||||
{
|
||||
this.selectors = selectors;
|
||||
connector.setSelectors(selectors);
|
||||
}
|
||||
|
||||
@ManagedAttribute("The idle timeout in milliseconds")
|
||||
public long getIdleTimeout()
|
||||
{
|
||||
return idleTimeout;
|
||||
return connector.getIdleTimeout().toMillis();
|
||||
}
|
||||
|
||||
public void setIdleTimeout(long idleTimeout)
|
||||
{
|
||||
this.idleTimeout = idleTimeout;
|
||||
connector.setIdleTimeout(Duration.ofMillis(idleTimeout));
|
||||
}
|
||||
|
||||
@ManagedAttribute("The connect timeout in milliseconds")
|
||||
public long getConnectTimeout()
|
||||
{
|
||||
return connectTimeout;
|
||||
return connector.getConnectTimeout().toMillis();
|
||||
}
|
||||
|
||||
public void setConnectTimeout(long connectTimeout)
|
||||
{
|
||||
this.connectTimeout = connectTimeout;
|
||||
SelectorManager selector = this.selector;
|
||||
if (selector != null)
|
||||
selector.setConnectTimeout(connectTimeout);
|
||||
connector.setConnectTimeout(Duration.ofMillis(connectTimeout));
|
||||
}
|
||||
|
||||
@ManagedAttribute("Whether the connect() operation is blocking")
|
||||
public boolean isConnectBlocking()
|
||||
{
|
||||
return connectBlocking;
|
||||
return connector.isConnectBlocking();
|
||||
}
|
||||
|
||||
public void setConnectBlocking(boolean connectBlocking)
|
||||
{
|
||||
this.connectBlocking = connectBlocking;
|
||||
connector.setConnectBlocking(connectBlocking);
|
||||
}
|
||||
|
||||
public SocketAddress getBindAddress()
|
||||
{
|
||||
return bindAddress;
|
||||
return connector.getBindAddress();
|
||||
}
|
||||
|
||||
public void setBindAddress(SocketAddress bindAddress)
|
||||
{
|
||||
this.bindAddress = bindAddress;
|
||||
connector.setBindAddress(bindAddress);
|
||||
}
|
||||
|
||||
@ManagedAttribute("The size of the buffer used to read from the network")
|
||||
|
@ -374,6 +305,7 @@ public class HTTP2Client extends ContainerLifeCycle
|
|||
|
||||
public void connect(InetSocketAddress address, Session.Listener listener, Promise<Session> promise)
|
||||
{
|
||||
// Prior-knowledge clear-text HTTP/2 (h2c).
|
||||
connect(null, address, listener, promise);
|
||||
}
|
||||
|
||||
|
@ -384,112 +316,49 @@ public class HTTP2Client extends ContainerLifeCycle
|
|||
|
||||
public void connect(SslContextFactory sslContextFactory, InetSocketAddress address, Session.Listener listener, Promise<Session> promise, Map<String, Object> context)
|
||||
{
|
||||
try
|
||||
{
|
||||
SocketChannel channel = SocketChannel.open();
|
||||
SocketAddress bindAddress = getBindAddress();
|
||||
if (bindAddress != null)
|
||||
channel.bind(bindAddress);
|
||||
configure(channel);
|
||||
boolean connected = true;
|
||||
if (isConnectBlocking())
|
||||
{
|
||||
channel.socket().connect(address, (int)getConnectTimeout());
|
||||
channel.configureBlocking(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
channel.configureBlocking(false);
|
||||
connected = channel.connect(address);
|
||||
}
|
||||
context = contextFrom(sslContextFactory, address, listener, promise, context);
|
||||
if (connected)
|
||||
selector.accept(channel, context);
|
||||
else
|
||||
selector.connect(channel, context);
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
promise.failed(x);
|
||||
}
|
||||
ClientConnectionFactory factory = newClientConnectionFactory(sslContextFactory);
|
||||
connect(address, factory, listener, promise, context);
|
||||
}
|
||||
|
||||
public void connect(SocketAddress address, ClientConnectionFactory factory, Session.Listener listener, Promise<Session> promise, Map<String, Object> context)
|
||||
{
|
||||
context = contextFrom(factory, listener, promise, context);
|
||||
context.put(ClientConnector.CONNECTION_PROMISE_CONTEXT_KEY, new Promise.Wrapper<>(promise));
|
||||
connector.connect(address, context);
|
||||
}
|
||||
|
||||
public void accept(SslContextFactory sslContextFactory, SocketChannel channel, Session.Listener listener, Promise<Session> promise)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!channel.isConnected())
|
||||
throw new IllegalStateException("SocketChannel must be connected");
|
||||
channel.configureBlocking(false);
|
||||
Map<String, Object> context = contextFrom(sslContextFactory, (InetSocketAddress)channel.getRemoteAddress(), listener, promise, null);
|
||||
selector.accept(channel, context);
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
promise.failed(x);
|
||||
}
|
||||
ClientConnectionFactory factory = newClientConnectionFactory(sslContextFactory);
|
||||
accept(channel, factory, listener, promise);
|
||||
}
|
||||
|
||||
private Map<String, Object> contextFrom(SslContextFactory sslContextFactory, InetSocketAddress address, Session.Listener listener, Promise<Session> promise, Map<String, Object> context)
|
||||
public void accept(SocketChannel channel, ClientConnectionFactory factory, Session.Listener listener, Promise<Session> promise)
|
||||
{
|
||||
Map<String, Object> context = contextFrom(factory, listener, promise, null);
|
||||
context.put(ClientConnector.CONNECTION_PROMISE_CONTEXT_KEY, new Promise.Wrapper<>(promise));
|
||||
connector.accept(channel, context);
|
||||
}
|
||||
|
||||
private Map<String, Object> contextFrom(ClientConnectionFactory factory, Session.Listener listener, Promise<Session> promise, Map<String, Object> context)
|
||||
{
|
||||
if (context == null)
|
||||
context = new HashMap<>();
|
||||
context.put(HTTP2ClientConnectionFactory.CLIENT_CONTEXT_KEY, this);
|
||||
context.put(HTTP2ClientConnectionFactory.SESSION_LISTENER_CONTEXT_KEY, listener);
|
||||
context.put(HTTP2ClientConnectionFactory.SESSION_PROMISE_CONTEXT_KEY, promise);
|
||||
if (sslContextFactory != null)
|
||||
context.put(SslClientConnectionFactory.SSL_CONTEXT_FACTORY_CONTEXT_KEY, sslContextFactory);
|
||||
context.put(SslClientConnectionFactory.SSL_PEER_HOST_CONTEXT_KEY, address.getHostString());
|
||||
context.put(SslClientConnectionFactory.SSL_PEER_PORT_CONTEXT_KEY, address.getPort());
|
||||
context.putIfAbsent(ClientConnectionFactory.CONNECTOR_CONTEXT_KEY, this);
|
||||
context.put(ClientConnector.CLIENT_CONNECTION_FACTORY_CONTEXT_KEY, factory);
|
||||
return context;
|
||||
}
|
||||
|
||||
protected void configure(SocketChannel channel) throws IOException
|
||||
private ClientConnectionFactory newClientConnectionFactory(SslContextFactory sslContextFactory)
|
||||
{
|
||||
channel.socket().setTcpNoDelay(true);
|
||||
}
|
||||
|
||||
private class ClientSelectorManager extends SelectorManager
|
||||
{
|
||||
private ClientSelectorManager(Executor executor, Scheduler scheduler, int selectors)
|
||||
ClientConnectionFactory factory = new HTTP2ClientConnectionFactory();
|
||||
if (sslContextFactory != null)
|
||||
{
|
||||
super(executor, scheduler, selectors);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected EndPoint newEndPoint(SelectableChannel channel, ManagedSelector selector, SelectionKey selectionKey) throws IOException
|
||||
{
|
||||
SocketChannelEndPoint endp = new SocketChannelEndPoint(channel, selector, selectionKey, getScheduler());
|
||||
endp.setIdleTimeout(getIdleTimeout());
|
||||
return endp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Connection newConnection(SelectableChannel channel, EndPoint endpoint, Object attachment) throws IOException
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> context = (Map<String, Object>)attachment;
|
||||
context.put(HTTP2ClientConnectionFactory.BYTE_BUFFER_POOL_CONTEXT_KEY, getByteBufferPool());
|
||||
context.put(HTTP2ClientConnectionFactory.EXECUTOR_CONTEXT_KEY, getExecutor());
|
||||
context.put(HTTP2ClientConnectionFactory.SCHEDULER_CONTEXT_KEY, getScheduler());
|
||||
return getClientConnectionFactory().newConnection(endpoint, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void connectionFailed(SelectableChannel channel, Throwable failure, Object attachment)
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> context = (Map<String, Object>)attachment;
|
||||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
Object host = context.get(SslClientConnectionFactory.SSL_PEER_HOST_CONTEXT_KEY);
|
||||
Object port = context.get(SslClientConnectionFactory.SSL_PEER_PORT_CONTEXT_KEY);
|
||||
LOG.debug("Could not connect to {}:{}", host, port);
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
Promise<Session> promise = (Promise<Session>)context.get(HTTP2ClientConnectionFactory.SESSION_PROMISE_CONTEXT_KEY);
|
||||
promise.failed(failure);
|
||||
ALPNClientConnectionFactory alpn = new ALPNClientConnectionFactory(getExecutor(), factory, getProtocols());
|
||||
factory = new SslClientConnectionFactory(sslContextFactory, getByteBufferPool(), getExecutor(), alpn);
|
||||
}
|
||||
return factory;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,12 +42,9 @@ import org.eclipse.jetty.util.thread.Scheduler;
|
|||
|
||||
public class HTTP2ClientConnectionFactory implements ClientConnectionFactory
|
||||
{
|
||||
public static final String CLIENT_CONTEXT_KEY = "http2.client";
|
||||
public static final String BYTE_BUFFER_POOL_CONTEXT_KEY = "http2.client.byteBufferPool";
|
||||
public static final String EXECUTOR_CONTEXT_KEY = "http2.client.executor";
|
||||
public static final String SCHEDULER_CONTEXT_KEY = "http2.client.scheduler";
|
||||
public static final String SESSION_LISTENER_CONTEXT_KEY = "http2.client.sessionListener";
|
||||
public static final String SESSION_PROMISE_CONTEXT_KEY = "http2.client.sessionPromise";
|
||||
public static final String CLIENT_CONTEXT_KEY = "org.eclipse.jetty.client.http2";
|
||||
public static final String SESSION_LISTENER_CONTEXT_KEY = "org.eclipse.jetty.client.http2.sessionListener";
|
||||
public static final String SESSION_PROMISE_CONTEXT_KEY = "org.eclipse.jetty.client.http2.sessionPromise";
|
||||
|
||||
private final Connection.Listener connectionListener = new ConnectionListener();
|
||||
|
||||
|
@ -55,9 +52,9 @@ public class HTTP2ClientConnectionFactory implements ClientConnectionFactory
|
|||
public Connection newConnection(EndPoint endPoint, Map<String, Object> context)
|
||||
{
|
||||
HTTP2Client client = (HTTP2Client)context.get(CLIENT_CONTEXT_KEY);
|
||||
ByteBufferPool byteBufferPool = (ByteBufferPool)context.get(BYTE_BUFFER_POOL_CONTEXT_KEY);
|
||||
Executor executor = (Executor)context.get(EXECUTOR_CONTEXT_KEY);
|
||||
Scheduler scheduler = (Scheduler)context.get(SCHEDULER_CONTEXT_KEY);
|
||||
ByteBufferPool byteBufferPool = client.getByteBufferPool();
|
||||
Executor executor = client.getExecutor();
|
||||
Scheduler scheduler = client.getScheduler();
|
||||
Session.Listener listener = (Session.Listener)context.get(SESSION_LISTENER_CONTEXT_KEY);
|
||||
@SuppressWarnings("unchecked")
|
||||
Promise<Session> promise = (Promise<Session>)context.get(SESSION_PROMISE_CONTEXT_KEY);
|
||||
|
|
|
@ -1,94 +0,0 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.http2.client;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.concurrent.Phaser;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.http.HttpURI;
|
||||
import org.eclipse.jetty.http.HttpVersion;
|
||||
import org.eclipse.jetty.http.MetaData;
|
||||
import org.eclipse.jetty.http2.api.Session;
|
||||
import org.eclipse.jetty.http2.api.Stream;
|
||||
import org.eclipse.jetty.http2.api.server.ServerSessionListener;
|
||||
import org.eclipse.jetty.http2.frames.DataFrame;
|
||||
import org.eclipse.jetty.http2.frames.HeadersFrame;
|
||||
import org.eclipse.jetty.http2.frames.PushPromiseFrame;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.FuturePromise;
|
||||
import org.eclipse.jetty.util.Jetty;
|
||||
import org.eclipse.jetty.util.Promise;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
|
||||
public class Client
|
||||
{
|
||||
public static void main(String[] args) throws Exception
|
||||
{
|
||||
HTTP2Client client = new HTTP2Client();
|
||||
SslContextFactory sslContextFactory = new SslContextFactory();
|
||||
client.addBean(sslContextFactory);
|
||||
client.start();
|
||||
|
||||
String host = "webtide.com";
|
||||
int port = 443;
|
||||
|
||||
FuturePromise<Session> sessionPromise = new FuturePromise<>();
|
||||
client.connect(sslContextFactory, new InetSocketAddress(host, port), new ServerSessionListener.Adapter(), sessionPromise);
|
||||
Session session = sessionPromise.get(5, TimeUnit.SECONDS);
|
||||
|
||||
HttpFields requestFields = new HttpFields();
|
||||
requestFields.put("User-Agent", client.getClass().getName() + "/" + Jetty.VERSION);
|
||||
MetaData.Request metaData = new MetaData.Request("GET", new HttpURI("https://" + host + ":" + port + "/"), HttpVersion.HTTP_2, requestFields);
|
||||
HeadersFrame headersFrame = new HeadersFrame(metaData, null, true);
|
||||
final Phaser phaser = new Phaser(2);
|
||||
session.newStream(headersFrame, new Promise.Adapter<>(), new Stream.Listener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onHeaders(Stream stream, HeadersFrame frame)
|
||||
{
|
||||
System.err.println(frame);
|
||||
if (frame.isEndStream())
|
||||
phaser.arrive();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onData(Stream stream, DataFrame frame, Callback callback)
|
||||
{
|
||||
System.err.println(frame);
|
||||
callback.succeeded();
|
||||
if (frame.isEndStream())
|
||||
phaser.arrive();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream.Listener onPush(Stream stream, PushPromiseFrame frame)
|
||||
{
|
||||
System.err.println(frame);
|
||||
phaser.register();
|
||||
return this;
|
||||
}
|
||||
});
|
||||
|
||||
phaser.awaitAdvanceInterruptibly(phaser.arrive(), 5, TimeUnit.SECONDS);
|
||||
|
||||
client.stop();
|
||||
}
|
||||
}
|
|
@ -45,7 +45,6 @@ import org.eclipse.jetty.io.EndPoint;
|
|||
import org.eclipse.jetty.util.Promise;
|
||||
import org.eclipse.jetty.util.annotation.ManagedAttribute;
|
||||
import org.eclipse.jetty.util.annotation.ManagedObject;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
|
||||
@ManagedObject("The HTTP/2 client transport")
|
||||
public class HttpClientTransportOverHTTP2 extends AbstractHttpClientTransport
|
||||
|
@ -101,13 +100,7 @@ public class HttpClientTransportOverHTTP2 extends AbstractHttpClientTransport
|
|||
}
|
||||
addBean(client);
|
||||
super.doStart();
|
||||
|
||||
this.connectionFactory = new HTTP2ClientConnectionFactory();
|
||||
client.setClientConnectionFactory((endPoint, context) ->
|
||||
{
|
||||
HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
|
||||
return destination.getClientConnectionFactory().newConnection(endPoint, context);
|
||||
});
|
||||
connectionFactory = new HTTP2ClientConnectionFactory();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -134,16 +127,12 @@ public class HttpClientTransportOverHTTP2 extends AbstractHttpClientTransport
|
|||
SessionListenerPromise listenerPromise = new SessionListenerPromise(context);
|
||||
|
||||
HttpDestinationOverHTTP2 destination = (HttpDestinationOverHTTP2)context.get(HTTP_DESTINATION_CONTEXT_KEY);
|
||||
SslContextFactory sslContextFactory = null;
|
||||
if (HttpScheme.HTTPS.is(destination.getScheme()))
|
||||
sslContextFactory = httpClient.getSslContextFactory();
|
||||
|
||||
connect(sslContextFactory, address, listenerPromise, listenerPromise, context);
|
||||
connect(address, destination.getClientConnectionFactory(), listenerPromise, listenerPromise, context);
|
||||
}
|
||||
|
||||
protected void connect(SslContextFactory sslContextFactory, InetSocketAddress address, Session.Listener listener, Promise<Session> promise, Map<String, Object> context)
|
||||
protected void connect(InetSocketAddress address, ClientConnectionFactory factory, Session.Listener listener, Promise<Session> promise, Map<String, Object> context)
|
||||
{
|
||||
getHTTP2Client().connect(sslContextFactory, address, listener, promise, context);
|
||||
getHTTP2Client().connect(address, factory, listener, promise, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -51,11 +51,11 @@ import org.eclipse.jetty.http2.frames.PingFrame;
|
|||
import org.eclipse.jetty.http2.frames.ResetFrame;
|
||||
import org.eclipse.jetty.http2.frames.SettingsFrame;
|
||||
import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory;
|
||||
import org.eclipse.jetty.io.ClientConnectionFactory;
|
||||
import org.eclipse.jetty.server.Handler;
|
||||
import org.eclipse.jetty.server.HttpConfiguration;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.util.Promise;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
|
@ -171,9 +171,9 @@ public class MaxConcurrentStreamsTest extends AbstractTest
|
|||
client = new HttpClient(new HttpClientTransportOverHTTP2(new HTTP2Client())
|
||||
{
|
||||
@Override
|
||||
protected void connect(SslContextFactory sslContextFactory, InetSocketAddress address, Session.Listener listener, Promise<Session> promise, Map<String, Object> context)
|
||||
protected void connect(InetSocketAddress address, ClientConnectionFactory factory, Session.Listener listener, Promise<Session> promise, Map<String, Object> context)
|
||||
{
|
||||
super.connect(sslContextFactory, address, new Wrapper(listener)
|
||||
super.connect(address, factory, new Wrapper(listener)
|
||||
{
|
||||
@Override
|
||||
public void onSettings(Session session, SettingsFrame frame)
|
||||
|
|
|
@ -28,10 +28,9 @@ import org.eclipse.jetty.util.component.ContainerLifeCycle;
|
|||
*/
|
||||
public interface ClientConnectionFactory
|
||||
{
|
||||
public static final String CONNECTOR_CONTEXT_KEY = "client.connector";
|
||||
public static final String CLIENT_CONTEXT_KEY = "org.eclipse.jetty.client";
|
||||
|
||||
/**
|
||||
*
|
||||
* @param endPoint the {@link org.eclipse.jetty.io.EndPoint} to link the newly created connection to
|
||||
* @param context the context data to create the connection
|
||||
* @return a new {@link Connection}
|
||||
|
@ -41,8 +40,9 @@ public interface ClientConnectionFactory
|
|||
|
||||
public default Connection customize(Connection connection, Map<String, Object> context)
|
||||
{
|
||||
ContainerLifeCycle connector = (ContainerLifeCycle)context.get(CONNECTOR_CONTEXT_KEY);
|
||||
connector.getBeans(Connection.Listener.class).forEach(connection::addListener);
|
||||
ContainerLifeCycle client = (ContainerLifeCycle)context.get(CLIENT_CONTEXT_KEY);
|
||||
if (client != null)
|
||||
client.getBeans(Connection.Listener.class).forEach(connection::addListener);
|
||||
return connection;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,333 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.io;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.SocketException;
|
||||
import java.nio.channels.SelectableChannel;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import org.eclipse.jetty.util.Promise;
|
||||
import org.eclipse.jetty.util.component.ContainerLifeCycle;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||
import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
|
||||
import org.eclipse.jetty.util.thread.Scheduler;
|
||||
|
||||
public class ClientConnector extends ContainerLifeCycle
|
||||
{
|
||||
public static final String CLIENT_CONNECTOR_CONTEXT_KEY = "org.eclipse.jetty.client.connector";
|
||||
public static final String SOCKET_ADDRESS_CONTEXT_KEY = CLIENT_CONNECTOR_CONTEXT_KEY + ".socketAddress";
|
||||
public static final String CLIENT_CONNECTION_FACTORY_CONTEXT_KEY = CLIENT_CONNECTOR_CONTEXT_KEY + ".clientConnectionFactory";
|
||||
public static final String CONNECTION_PROMISE_CONTEXT_KEY = CLIENT_CONNECTOR_CONTEXT_KEY + ".connectionPromise";
|
||||
private static final Logger LOG = Log.getLogger(ClientConnector.class);
|
||||
|
||||
private Executor executor;
|
||||
private Scheduler scheduler;
|
||||
private ByteBufferPool byteBufferPool;
|
||||
private SslContextFactory sslContextFactory;
|
||||
private SelectorManager selectorManager;
|
||||
private int selectors = 1;
|
||||
private boolean connectBlocking;
|
||||
private Duration connectTimeout = Duration.ofSeconds(5);
|
||||
private Duration idleTimeout = Duration.ofSeconds(30);
|
||||
private SocketAddress bindAddress;
|
||||
|
||||
public Executor getExecutor()
|
||||
{
|
||||
return executor;
|
||||
}
|
||||
|
||||
public void setExecutor(Executor executor)
|
||||
{
|
||||
if (isStarted())
|
||||
throw new IllegalStateException();
|
||||
updateBean(this.executor, executor);
|
||||
this.executor = executor;
|
||||
}
|
||||
|
||||
public Scheduler getScheduler()
|
||||
{
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
public void setScheduler(Scheduler scheduler)
|
||||
{
|
||||
if (isStarted())
|
||||
throw new IllegalStateException();
|
||||
updateBean(this.scheduler, scheduler);
|
||||
this.scheduler = scheduler;
|
||||
}
|
||||
|
||||
public ByteBufferPool getByteBufferPool()
|
||||
{
|
||||
return byteBufferPool;
|
||||
}
|
||||
|
||||
public void setByteBufferPool(ByteBufferPool byteBufferPool)
|
||||
{
|
||||
if (isStarted())
|
||||
throw new IllegalStateException();
|
||||
updateBean(this.byteBufferPool, byteBufferPool);
|
||||
this.byteBufferPool = byteBufferPool;
|
||||
}
|
||||
|
||||
public SslContextFactory getSslContextFactory()
|
||||
{
|
||||
return sslContextFactory;
|
||||
}
|
||||
|
||||
public void setSslContextFactory(SslContextFactory sslContextFactory)
|
||||
{
|
||||
if (isStarted())
|
||||
throw new IllegalStateException();
|
||||
updateBean(this.sslContextFactory, sslContextFactory);
|
||||
this.sslContextFactory = sslContextFactory;
|
||||
}
|
||||
|
||||
public int getSelectors()
|
||||
{
|
||||
return selectors;
|
||||
}
|
||||
|
||||
public void setSelectors(int selectors)
|
||||
{
|
||||
if (isStarted())
|
||||
throw new IllegalStateException();
|
||||
this.selectors = selectors;
|
||||
}
|
||||
|
||||
public boolean isConnectBlocking()
|
||||
{
|
||||
return connectBlocking;
|
||||
}
|
||||
|
||||
public void setConnectBlocking(boolean connectBlocking)
|
||||
{
|
||||
this.connectBlocking = connectBlocking;
|
||||
}
|
||||
|
||||
public Duration getConnectTimeout()
|
||||
{
|
||||
return connectTimeout;
|
||||
}
|
||||
|
||||
public void setConnectTimeout(Duration connectTimeout)
|
||||
{
|
||||
this.connectTimeout = connectTimeout;
|
||||
if (selectorManager != null)
|
||||
selectorManager.setConnectTimeout(connectTimeout.toMillis());
|
||||
}
|
||||
|
||||
public Duration getIdleTimeout()
|
||||
{
|
||||
return idleTimeout;
|
||||
}
|
||||
|
||||
public void setIdleTimeout(Duration idleTimeout)
|
||||
{
|
||||
this.idleTimeout = idleTimeout;
|
||||
}
|
||||
|
||||
public SocketAddress getBindAddress()
|
||||
{
|
||||
return bindAddress;
|
||||
}
|
||||
|
||||
public void setBindAddress(SocketAddress bindAddress)
|
||||
{
|
||||
this.bindAddress = bindAddress;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doStart() throws Exception
|
||||
{
|
||||
if (executor == null)
|
||||
setExecutor(new QueuedThreadPool());
|
||||
if (scheduler == null)
|
||||
setScheduler(new ScheduledExecutorScheduler());
|
||||
if (byteBufferPool == null)
|
||||
setByteBufferPool(new MappedByteBufferPool());
|
||||
if (sslContextFactory == null)
|
||||
setSslContextFactory(newSslContextFactory());
|
||||
selectorManager = newSelectorManager();
|
||||
selectorManager.setConnectTimeout(getConnectTimeout().toMillis());
|
||||
addBean(selectorManager);
|
||||
super.doStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doStop() throws Exception
|
||||
{
|
||||
super.doStop();
|
||||
removeBean(selectorManager);
|
||||
}
|
||||
|
||||
protected SslContextFactory newSslContextFactory()
|
||||
{
|
||||
SslContextFactory sslContextFactory = new SslContextFactory(false);
|
||||
sslContextFactory.setEndpointIdentificationAlgorithm("HTTPS");
|
||||
return sslContextFactory;
|
||||
}
|
||||
|
||||
protected SelectorManager newSelectorManager()
|
||||
{
|
||||
return new ClientSelectorManager(getExecutor(), getScheduler(), getSelectors());
|
||||
}
|
||||
|
||||
public void connect(SocketAddress address, Map<String, Object> context)
|
||||
{
|
||||
SocketChannel channel = null;
|
||||
try
|
||||
{
|
||||
if (context == null)
|
||||
context = new HashMap<>();
|
||||
context.put(ClientConnector.CLIENT_CONNECTOR_CONTEXT_KEY, this);
|
||||
context.putIfAbsent(SOCKET_ADDRESS_CONTEXT_KEY, address);
|
||||
|
||||
channel = SocketChannel.open();
|
||||
SocketAddress bindAddress = getBindAddress();
|
||||
if (bindAddress != null)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Binding to {} to connect to {}", bindAddress, address);
|
||||
channel.bind(bindAddress);
|
||||
}
|
||||
configure(channel);
|
||||
|
||||
boolean connected = true;
|
||||
boolean blocking = isConnectBlocking();
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Connecting {} to {}", blocking ? "blocking" : "non-blocking", address);
|
||||
if (blocking)
|
||||
{
|
||||
channel.socket().connect(address, (int)getConnectTimeout().toMillis());
|
||||
channel.configureBlocking(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
channel.configureBlocking(false);
|
||||
connected = channel.connect(address);
|
||||
}
|
||||
|
||||
if (connected)
|
||||
selectorManager.accept(channel, context);
|
||||
else
|
||||
selectorManager.connect(channel, context);
|
||||
}
|
||||
// Must catch all exceptions, since some like
|
||||
// UnresolvedAddressException are not IOExceptions.
|
||||
catch (Throwable x)
|
||||
{
|
||||
// If IPv6 is not deployed, a generic SocketException "Network is unreachable"
|
||||
// exception is being thrown, so we attempt to provide a better error message.
|
||||
if (x.getClass() == SocketException.class)
|
||||
x = new SocketException("Could not connect to " + address).initCause(x);
|
||||
|
||||
try
|
||||
{
|
||||
if (channel != null)
|
||||
channel.close();
|
||||
}
|
||||
catch (IOException xx)
|
||||
{
|
||||
LOG.ignore(xx);
|
||||
}
|
||||
finally
|
||||
{
|
||||
connectFailed(x, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void accept(SocketChannel channel, Map<String, Object> context)
|
||||
{
|
||||
try
|
||||
{
|
||||
context.put(ClientConnector.CLIENT_CONNECTOR_CONTEXT_KEY, this);
|
||||
|
||||
if (!channel.isConnected())
|
||||
throw new IllegalStateException("SocketChannel must be connected");
|
||||
configure(channel);
|
||||
channel.configureBlocking(false);
|
||||
selectorManager.accept(channel, context);
|
||||
}
|
||||
catch (Throwable failure)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Could not accept {}", channel);
|
||||
Promise<?> promise = (Promise<?>)context.get(CONNECTION_PROMISE_CONTEXT_KEY);
|
||||
promise.failed(failure);
|
||||
}
|
||||
}
|
||||
|
||||
protected void configure(SocketChannel channel) throws IOException
|
||||
{
|
||||
channel.socket().setTcpNoDelay(true);
|
||||
}
|
||||
|
||||
private void connectFailed(Throwable failure, Map<String, Object> context)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Could not connect to {}", context.get(SOCKET_ADDRESS_CONTEXT_KEY));
|
||||
Promise<?> promise = (Promise<?>)context.get(CONNECTION_PROMISE_CONTEXT_KEY);
|
||||
promise.failed(failure);
|
||||
}
|
||||
|
||||
private class ClientSelectorManager extends SelectorManager
|
||||
{
|
||||
private ClientSelectorManager(Executor executor, Scheduler scheduler, int selectors)
|
||||
{
|
||||
super(executor, scheduler, selectors);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected EndPoint newEndPoint(SelectableChannel channel, ManagedSelector selector, SelectionKey selectionKey)
|
||||
{
|
||||
SocketChannelEndPoint endPoint = new SocketChannelEndPoint(channel, selector, selectionKey, getScheduler());
|
||||
endPoint.setIdleTimeout(getIdleTimeout().toMillis());
|
||||
return endPoint;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Connection newConnection(SelectableChannel channel, EndPoint endPoint, Object attachment) throws IOException
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> context = (Map<String, Object>)attachment;
|
||||
ClientConnectionFactory factory = (ClientConnectionFactory)context.get(CLIENT_CONNECTION_FACTORY_CONTEXT_KEY);
|
||||
return factory.newConnection(endPoint, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void connectionFailed(SelectableChannel channel, Throwable failure, Object attachment)
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> context = (Map<String, Object>)attachment;
|
||||
connectFailed(failure, context);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -37,9 +37,9 @@ public abstract class NegotiatingClientConnection extends AbstractConnection
|
|||
private final Map<String, Object> context;
|
||||
private volatile boolean completed;
|
||||
|
||||
protected NegotiatingClientConnection(EndPoint endp, Executor executor, SSLEngine sslEngine, ClientConnectionFactory connectionFactory, Map<String, Object> context)
|
||||
protected NegotiatingClientConnection(EndPoint endPoint, Executor executor, SSLEngine sslEngine, ClientConnectionFactory connectionFactory, Map<String, Object> context)
|
||||
{
|
||||
super(endp, executor);
|
||||
super(endPoint, executor);
|
||||
this.engine = sslEngine;
|
||||
this.connectionFactory = connectionFactory;
|
||||
this.context = context;
|
||||
|
@ -67,7 +67,7 @@ public abstract class NegotiatingClientConnection extends AbstractConnection
|
|||
else
|
||||
fillInterested();
|
||||
}
|
||||
catch (IOException x)
|
||||
catch (Throwable x)
|
||||
{
|
||||
close();
|
||||
throw new RuntimeIOException(x);
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
package org.eclipse.jetty.io.ssl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Executor;
|
||||
|
@ -27,6 +28,7 @@ import javax.net.ssl.SSLEngine;
|
|||
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.io.ClientConnectionFactory;
|
||||
import org.eclipse.jetty.io.ClientConnector;
|
||||
import org.eclipse.jetty.io.Connection;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
import org.eclipse.jetty.util.component.ContainerLifeCycle;
|
||||
|
@ -34,10 +36,7 @@ import org.eclipse.jetty.util.ssl.SslContextFactory;
|
|||
|
||||
public class SslClientConnectionFactory implements ClientConnectionFactory
|
||||
{
|
||||
public static final String SSL_CONTEXT_FACTORY_CONTEXT_KEY = "ssl.context.factory";
|
||||
public static final String SSL_PEER_HOST_CONTEXT_KEY = "ssl.peer.host";
|
||||
public static final String SSL_PEER_PORT_CONTEXT_KEY = "ssl.peer.port";
|
||||
public static final String SSL_ENGINE_CONTEXT_KEY = "ssl.engine";
|
||||
public static final String SSL_ENGINE_CONTEXT_KEY = "org.eclipse.jetty.client.ssl.engine";
|
||||
|
||||
private final SslContextFactory sslContextFactory;
|
||||
private final ByteBufferPool byteBufferPool;
|
||||
|
@ -88,9 +87,8 @@ public class SslClientConnectionFactory implements ClientConnectionFactory
|
|||
@Override
|
||||
public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
|
||||
{
|
||||
String host = (String)context.get(SSL_PEER_HOST_CONTEXT_KEY);
|
||||
int port = (Integer)context.get(SSL_PEER_PORT_CONTEXT_KEY);
|
||||
SSLEngine engine = sslContextFactory.newSSLEngine(host, port);
|
||||
InetSocketAddress address = (InetSocketAddress)context.get(ClientConnector.SOCKET_ADDRESS_CONTEXT_KEY);
|
||||
SSLEngine engine = sslContextFactory.newSSLEngine(address);
|
||||
engine.setUseClientMode(true);
|
||||
context.put(SSL_ENGINE_CONTEXT_KEY, engine);
|
||||
|
||||
|
@ -119,8 +117,9 @@ public class SslClientConnectionFactory implements ClientConnectionFactory
|
|||
sslConnection.setRenegotiationAllowed(sslContextFactory.isRenegotiationAllowed());
|
||||
sslConnection.setRenegotiationLimit(sslContextFactory.getRenegotiationLimit());
|
||||
sslConnection.setAllowMissingCloseMessage(isAllowMissingCloseMessage());
|
||||
ContainerLifeCycle connector = (ContainerLifeCycle)context.get(ClientConnectionFactory.CONNECTOR_CONTEXT_KEY);
|
||||
connector.getBeans(SslHandshakeListener.class).forEach(sslConnection::addHandshakeListener);
|
||||
ContainerLifeCycle client = (ContainerLifeCycle)context.get(ClientConnectionFactory.CLIENT_CONTEXT_KEY);
|
||||
if (client != null)
|
||||
client.getBeans(SslHandshakeListener.class).forEach(sslConnection::addHandshakeListener);
|
||||
}
|
||||
return ClientConnectionFactory.super.customize(connection, context);
|
||||
}
|
||||
|
|
|
@ -41,6 +41,7 @@ import java.util.Random;
|
|||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
|
@ -171,16 +172,23 @@ public class ProxyServletTest
|
|||
|
||||
private void startClient() throws Exception
|
||||
{
|
||||
client = prepareClient();
|
||||
startClient(null);
|
||||
}
|
||||
|
||||
private HttpClient prepareClient() throws Exception
|
||||
private void startClient(Consumer<HttpClient> consumer) throws Exception
|
||||
{
|
||||
client = prepareClient(consumer);
|
||||
}
|
||||
|
||||
private HttpClient prepareClient(Consumer<HttpClient> consumer) throws Exception
|
||||
{
|
||||
QueuedThreadPool clientPool = new QueuedThreadPool();
|
||||
clientPool.setName("client");
|
||||
HttpClient result = new HttpClient();
|
||||
result.setExecutor(clientPool);
|
||||
result.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyConnector.getLocalPort()));
|
||||
if (consumer != null)
|
||||
consumer.accept(result);
|
||||
result.start();
|
||||
return result;
|
||||
}
|
||||
|
@ -987,7 +995,7 @@ public class ProxyServletTest
|
|||
assertEquals(name, cookies.get(0).getName());
|
||||
assertEquals(value1, cookies.get(0).getValue());
|
||||
|
||||
HttpClient client2 = prepareClient();
|
||||
HttpClient client2 = prepareClient(null);
|
||||
try
|
||||
{
|
||||
String value2 = "2";
|
||||
|
@ -1373,10 +1381,8 @@ public class ProxyServletTest
|
|||
}
|
||||
});
|
||||
startProxy(proxyServletClass);
|
||||
startClient();
|
||||
|
||||
long idleTimeout = 1000;
|
||||
client.setIdleTimeout(idleTimeout);
|
||||
startClient(httpClient -> httpClient.setIdleTimeout(idleTimeout));
|
||||
|
||||
byte[] content = new byte[1024];
|
||||
new Random().nextBytes(content);
|
||||
|
|
|
@ -21,85 +21,110 @@ package org.eclipse.jetty.unixsocket.client;
|
|||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketException;
|
||||
import java.nio.channels.SelectableChannel;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.Selector;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.HttpDestination;
|
||||
import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
import org.eclipse.jetty.io.ManagedSelector;
|
||||
import org.eclipse.jetty.io.SelectorManager;
|
||||
import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
|
||||
import org.eclipse.jetty.unixsocket.UnixSocketEndPoint;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
||||
import jnr.enxio.channels.NativeSelectorProvider;
|
||||
import jnr.unixsocket.UnixSocketAddress;
|
||||
import jnr.unixsocket.UnixSocketChannel;
|
||||
import org.eclipse.jetty.client.AbstractHttpClientTransport;
|
||||
import org.eclipse.jetty.client.DuplexConnectionPool;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.HttpDestination;
|
||||
import org.eclipse.jetty.client.Origin;
|
||||
import org.eclipse.jetty.client.http.HttpConnectionOverHTTP;
|
||||
import org.eclipse.jetty.client.http.HttpDestinationOverHTTP;
|
||||
import org.eclipse.jetty.io.Connection;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
import org.eclipse.jetty.io.ManagedSelector;
|
||||
import org.eclipse.jetty.io.SelectorManager;
|
||||
import org.eclipse.jetty.unixsocket.UnixSocketEndPoint;
|
||||
import org.eclipse.jetty.util.Promise;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
||||
public class HttpClientTransportOverUnixSockets
|
||||
extends HttpClientTransportOverHTTP
|
||||
// TODO: this class needs a thorough review.
|
||||
public class HttpClientTransportOverUnixSockets extends AbstractHttpClientTransport
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger( HttpClientTransportOverUnixSockets.class );
|
||||
|
||||
private static final Logger LOG = Log.getLogger(HttpClientTransportOverUnixSockets.class);
|
||||
|
||||
private String _unixSocket;
|
||||
private SelectorManager selectorManager;
|
||||
|
||||
private UnixSocketChannel channel;
|
||||
|
||||
public HttpClientTransportOverUnixSockets( String unixSocket )
|
||||
public HttpClientTransportOverUnixSockets(String unixSocket)
|
||||
{
|
||||
if ( unixSocket == null )
|
||||
{
|
||||
throw new IllegalArgumentException( "Unix socket file cannot be null" );
|
||||
}
|
||||
if (unixSocket == null)
|
||||
throw new IllegalArgumentException("Unix socket file cannot be null");
|
||||
this._unixSocket = unixSocket;
|
||||
setConnectionPoolFactory(destination ->
|
||||
{
|
||||
HttpClient httpClient = getHttpClient();
|
||||
int maxConnections = httpClient.getMaxConnectionsPerDestination();
|
||||
return new DuplexConnectionPool(destination, maxConnections, destination);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SelectorManager newSelectorManager(HttpClient client)
|
||||
protected void doStart() throws Exception
|
||||
{
|
||||
return selectorManager = new UnixSocketSelectorManager(client,getSelectors());
|
||||
HttpClient httpClient = getHttpClient();
|
||||
selectorManager = new UnixSocketSelectorManager(httpClient, 1);
|
||||
selectorManager.setConnectTimeout(httpClient.getConnectTimeout());
|
||||
addBean(selectorManager);
|
||||
super.doStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect( InetSocketAddress address, Map<String, Object> context )
|
||||
protected void doStop() throws Exception
|
||||
{
|
||||
super.doStop();
|
||||
try
|
||||
{
|
||||
if (channel != null)
|
||||
channel.close();
|
||||
}
|
||||
catch (IOException xx)
|
||||
{
|
||||
LOG.ignore(xx);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpDestination newHttpDestination(Origin origin)
|
||||
{
|
||||
return new HttpDestinationOverHTTP(getHttpClient(), origin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect(InetSocketAddress address, Map<String, Object> context)
|
||||
{
|
||||
try
|
||||
{
|
||||
InetAddress inet = address.getAddress();
|
||||
if (!inet.isLoopbackAddress() && !inet.isLinkLocalAddress() && !inet.isSiteLocalAddress())
|
||||
throw new IOException("UnixSocket cannot connect to "+address.getHostString());
|
||||
|
||||
throw new IOException("UnixSocket cannot connect to " + address.getHostString());
|
||||
|
||||
// Open a unix socket
|
||||
UnixSocketAddress unixAddress = new UnixSocketAddress( this._unixSocket );
|
||||
channel = UnixSocketChannel.open( unixAddress );
|
||||
|
||||
UnixSocketAddress unixAddress = new UnixSocketAddress(this._unixSocket);
|
||||
channel = UnixSocketChannel.open(unixAddress);
|
||||
|
||||
HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
|
||||
HttpClient client = destination.getHttpClient();
|
||||
|
||||
configure(client, channel);
|
||||
|
||||
channel.configureBlocking(false);
|
||||
selectorManager.accept(channel, context);
|
||||
selectorManager.accept(channel, context);
|
||||
}
|
||||
// Must catch all exceptions, since some like
|
||||
// UnresolvedAddressException are not IOExceptions.
|
||||
catch (Throwable x)
|
||||
{
|
||||
// If IPv6 is not deployed, a generic SocketException "Network is unreachable"
|
||||
// exception is being thrown, so we attempt to provide a better error message.
|
||||
if (x.getClass() == SocketException.class)
|
||||
x = new SocketException("Could not connect to " + address).initCause(x);
|
||||
|
||||
try
|
||||
{
|
||||
if (channel != null)
|
||||
|
@ -116,11 +141,33 @@ public class HttpClientTransportOverUnixSockets
|
|||
}
|
||||
}
|
||||
|
||||
public class UnixSocketSelectorManager extends ClientSelectorManager
|
||||
@Override
|
||||
public Connection newConnection(EndPoint endPoint, Map<String, Object> context)
|
||||
{
|
||||
HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
|
||||
@SuppressWarnings("unchecked")
|
||||
Promise<org.eclipse.jetty.client.api.Connection> promise = (Promise<org.eclipse.jetty.client.api.Connection>)context.get(HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
|
||||
HttpConnectionOverHTTP connection = newHttpConnection(endPoint, destination, promise);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Created {}", connection);
|
||||
return customize(connection, context);
|
||||
}
|
||||
|
||||
protected HttpConnectionOverHTTP newHttpConnection(EndPoint endPoint, HttpDestination destination, Promise<org.eclipse.jetty.client.api.Connection> promise)
|
||||
{
|
||||
return new HttpConnectionOverHTTP(endPoint, destination, promise);
|
||||
}
|
||||
|
||||
protected void configure(HttpClient client, SocketChannel channel) throws IOException
|
||||
{
|
||||
channel.socket().setTcpNoDelay(client.isTCPNoDelay());
|
||||
}
|
||||
|
||||
public class UnixSocketSelectorManager extends SelectorManager
|
||||
{
|
||||
protected UnixSocketSelectorManager(HttpClient client, int selectors)
|
||||
{
|
||||
super(client,selectors);
|
||||
super(client.getExecutor(), client.getScheduler(), selectors);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -132,25 +179,26 @@ public class HttpClientTransportOverUnixSockets
|
|||
@Override
|
||||
protected EndPoint newEndPoint(SelectableChannel channel, ManagedSelector selector, SelectionKey key)
|
||||
{
|
||||
UnixSocketEndPoint endp = new UnixSocketEndPoint((UnixSocketChannel)channel, selector, key, getScheduler());
|
||||
endp.setIdleTimeout(getHttpClient().getIdleTimeout());
|
||||
return endp;
|
||||
UnixSocketEndPoint endPoint = new UnixSocketEndPoint((UnixSocketChannel)channel, selector, key, getScheduler());
|
||||
endPoint.setIdleTimeout(getHttpClient().getIdleTimeout());
|
||||
return endPoint;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doStop()
|
||||
throws Exception
|
||||
{
|
||||
super.doStop();
|
||||
try
|
||||
@Override
|
||||
public Connection newConnection(SelectableChannel channel, EndPoint endPoint, Object attachment) throws IOException
|
||||
{
|
||||
if (channel != null)
|
||||
channel.close();
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> context = (Map<String, Object>)attachment;
|
||||
HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
|
||||
return destination.getClientConnectionFactory().newConnection(endPoint, context);
|
||||
}
|
||||
catch (IOException xx)
|
||||
|
||||
@Override
|
||||
protected void connectionFailed(SelectableChannel channel, Throwable x, Object attachment)
|
||||
{
|
||||
LOG.ignore(xx);
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> context = (Map<String, Object>)attachment;
|
||||
connectFailed(context, x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,14 +18,6 @@
|
|||
|
||||
package org.eclipse.jetty.unixsocket;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.condition.OS.LINUX;
|
||||
import static org.junit.jupiter.api.condition.OS.MAC;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
|
@ -34,7 +26,6 @@ import java.nio.file.Paths;
|
|||
import java.util.Date;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
|
@ -54,6 +45,14 @@ import org.junit.jupiter.api.BeforeEach;
|
|||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledOnOs;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.condition.OS.LINUX;
|
||||
import static org.junit.jupiter.api.condition.OS.MAC;
|
||||
|
||||
@EnabledOnOs({LINUX, MAC})
|
||||
public class UnixSocketTest
|
||||
{
|
||||
|
@ -68,94 +67,86 @@ public class UnixSocketTest
|
|||
{
|
||||
server = null;
|
||||
httpClient = null;
|
||||
String unixSocketTmp = System.getProperty( "unix.socket.tmp" );
|
||||
if(StringUtil.isNotBlank( unixSocketTmp ) )
|
||||
{
|
||||
sockFile = Files.createTempFile( Paths.get(unixSocketTmp), "unix", ".sock" );
|
||||
} else {
|
||||
sockFile = Files.createTempFile("unix", ".sock" );
|
||||
}
|
||||
assertTrue(Files.deleteIfExists(sockFile),"temp sock file cannot be deleted");
|
||||
String unixSocketTmp = System.getProperty("unix.socket.tmp");
|
||||
if (StringUtil.isNotBlank(unixSocketTmp))
|
||||
sockFile = Files.createTempFile(Paths.get(unixSocketTmp), "unix", ".sock");
|
||||
else
|
||||
sockFile = Files.createTempFile("unix", ".sock");
|
||||
assertTrue(Files.deleteIfExists(sockFile), "temp sock file cannot be deleted");
|
||||
|
||||
}
|
||||
|
||||
|
||||
@AfterEach
|
||||
public void after() throws Exception
|
||||
{
|
||||
if (httpClient!=null)
|
||||
if (httpClient != null)
|
||||
httpClient.stop();
|
||||
if (server!=null)
|
||||
if (server != null)
|
||||
server.stop();
|
||||
// Force delete, this will fail if UnixSocket was not closed properly in the implementation
|
||||
FS.delete( sockFile);
|
||||
FS.delete(sockFile);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testUnixSocket() throws Exception
|
||||
{
|
||||
server = new Server();
|
||||
|
||||
HttpConnectionFactory http = new HttpConnectionFactory();
|
||||
UnixSocketConnector connector = new UnixSocketConnector(server, http);
|
||||
connector.setUnixSocket(sockFile.toString());
|
||||
server.addConnector(connector);
|
||||
|
||||
UnixSocketConnector connector = new UnixSocketConnector( server, http );
|
||||
connector.setUnixSocket( sockFile.toString() );
|
||||
server.addConnector( connector );
|
||||
|
||||
server.setHandler( new AbstractHandler.ErrorDispatchHandler()
|
||||
server.setHandler(new AbstractHandler.ErrorDispatchHandler()
|
||||
{
|
||||
@Override
|
||||
protected void doNonErrorHandle( String target, Request baseRequest, HttpServletRequest request,
|
||||
HttpServletResponse response )
|
||||
throws IOException, ServletException
|
||||
protected void doNonErrorHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||
{
|
||||
int l = 0;
|
||||
if ( request.getContentLength() != 0 )
|
||||
if (request.getContentLength() != 0)
|
||||
{
|
||||
InputStream in = request.getInputStream();
|
||||
byte[] buffer = new byte[4096];
|
||||
int r = 0;
|
||||
while ( r >= 0 )
|
||||
while (r >= 0)
|
||||
{
|
||||
l += r;
|
||||
r = in.read( buffer );
|
||||
r = in.read(buffer);
|
||||
}
|
||||
}
|
||||
log.info( "UnixSocketTest: request received" );
|
||||
baseRequest.setHandled( true );
|
||||
response.setStatus( 200 );
|
||||
response.getWriter().write( "Hello World " + new Date() + "\r\n" );
|
||||
log.info("UnixSocketTest: request received");
|
||||
baseRequest.setHandled(true);
|
||||
response.setStatus(200);
|
||||
response.getWriter().write("Hello World " + new Date() + "\r\n");
|
||||
response.getWriter().write(
|
||||
"remote=" + request.getRemoteAddr() + ":" + request.getRemotePort() + "\r\n" );
|
||||
"remote=" + request.getRemoteAddr() + ":" + request.getRemotePort() + "\r\n");
|
||||
response.getWriter().write(
|
||||
"local =" + request.getLocalAddr() + ":" + request.getLocalPort() + "\r\n" );
|
||||
response.getWriter().write( "read =" + l + "\r\n" );
|
||||
"local =" + request.getLocalAddr() + ":" + request.getLocalPort() + "\r\n");
|
||||
response.getWriter().write("read =" + l + "\r\n");
|
||||
}
|
||||
} );
|
||||
});
|
||||
|
||||
server.start();
|
||||
|
||||
httpClient = new HttpClient( new HttpClientTransportOverUnixSockets( sockFile.toString() ), null );
|
||||
httpClient = new HttpClient(new HttpClientTransportOverUnixSockets(sockFile.toString()), null);
|
||||
httpClient.start();
|
||||
|
||||
ContentResponse contentResponse = httpClient
|
||||
.newRequest( "http://localhost" )
|
||||
.newRequest("http://localhost")
|
||||
.send();
|
||||
|
||||
log.debug( "response from server: {}", contentResponse.getContentAsString() );
|
||||
log.debug("response from server: {}", contentResponse.getContentAsString());
|
||||
|
||||
assertThat(contentResponse.getContentAsString(), containsString( "Hello World" ));
|
||||
assertThat(contentResponse.getContentAsString(), containsString("Hello World"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotLocal() throws Exception
|
||||
{
|
||||
httpClient = new HttpClient( new HttpClientTransportOverUnixSockets( sockFile.toString() ), null );
|
||||
{
|
||||
httpClient = new HttpClient(new HttpClientTransportOverUnixSockets(sockFile.toString()), null);
|
||||
httpClient.start();
|
||||
|
||||
ExecutionException e = assertThrows(ExecutionException.class, ()->{
|
||||
httpClient.newRequest( "http://google.com" ).send();
|
||||
});
|
||||
|
||||
ExecutionException e = assertThrows(ExecutionException.class, () -> httpClient.newRequest("http://google.com").send());
|
||||
assertThat(e.getCause(), instanceOf(IOException.class));
|
||||
assertThat(e.getCause().getMessage(),containsString("UnixSocket cannot connect to google.com"));
|
||||
assertThat(e.getCause().getMessage(), containsString("UnixSocket cannot connect to google.com"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,10 +18,6 @@
|
|||
|
||||
package org.eclipse.jetty.http.client;
|
||||
|
||||
import static org.eclipse.jetty.http.client.Transport.H2C;
|
||||
import static org.eclipse.jetty.http.client.Transport.HTTP;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
@ -44,6 +40,11 @@ import org.junit.jupiter.api.Assumptions;
|
|||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ArgumentsSource;
|
||||
|
||||
import static org.eclipse.jetty.http.client.Transport.H2C;
|
||||
import static org.eclipse.jetty.http.client.Transport.HTTP;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class ConnectionStatisticsTest extends AbstractTest<TransportScenario>
|
||||
{
|
||||
@Override
|
||||
|
@ -73,7 +74,7 @@ public class ConnectionStatisticsTest extends AbstractTest<TransportScenario>
|
|||
{
|
||||
@Override
|
||||
public void onOpened(Connection connection)
|
||||
{
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -82,7 +83,7 @@ public class ConnectionStatisticsTest extends AbstractTest<TransportScenario>
|
|||
closed.countDown();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
ConnectionStatistics serverStats = new ConnectionStatistics();
|
||||
scenario.connector.addBean(serverStats);
|
||||
scenario.connector.addBean(closer);
|
||||
|
@ -93,20 +94,20 @@ public class ConnectionStatisticsTest extends AbstractTest<TransportScenario>
|
|||
scenario.client.addBean(closer);
|
||||
clientStats.start();
|
||||
|
||||
scenario.client.setIdleTimeout(1000);
|
||||
long idleTimeout = 1000;
|
||||
scenario.client.setIdleTimeout(idleTimeout);
|
||||
|
||||
byte[] content = new byte[3072];
|
||||
long contentLength = content.length;
|
||||
ContentResponse response = scenario.client.newRequest(scenario.newURI())
|
||||
.header(HttpHeader.CONNECTION,"close")
|
||||
.header(HttpHeader.CONNECTION, "close")
|
||||
.content(new BytesContentProvider(content))
|
||||
.timeout(5, TimeUnit.SECONDS)
|
||||
.send();
|
||||
|
||||
assertThat(response.getStatus(), Matchers.equalTo(HttpStatus.OK_200));
|
||||
assertTrue(closed.await(2 * idleTimeout, TimeUnit.MILLISECONDS));
|
||||
|
||||
closed.await();
|
||||
|
||||
assertThat(serverStats.getConnectionsMax(), Matchers.greaterThan(0L));
|
||||
assertThat(serverStats.getReceivedBytes(), Matchers.greaterThan(contentLength));
|
||||
assertThat(serverStats.getSentBytes(), Matchers.greaterThan(contentLength));
|
||||
|
|
|
@ -327,7 +327,7 @@ public class HttpClientContinueTest extends AbstractTest<TransportScenario>
|
|||
{
|
||||
init(transport);
|
||||
final long idleTimeout = 1000;
|
||||
scenario.start(new AbstractHandler()
|
||||
scenario.startServer(new AbstractHandler()
|
||||
{
|
||||
@Override
|
||||
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws ServletException
|
||||
|
@ -343,8 +343,7 @@ public class HttpClientContinueTest extends AbstractTest<TransportScenario>
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
scenario.client.setIdleTimeout(2 * idleTimeout);
|
||||
scenario.startClient(httpClient -> httpClient.setIdleTimeout(2 * idleTimeout));
|
||||
|
||||
byte[] content = new byte[1024];
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
|
@ -375,7 +374,7 @@ public class HttpClientContinueTest extends AbstractTest<TransportScenario>
|
|||
{
|
||||
init(transport);
|
||||
final long idleTimeout = 1000;
|
||||
scenario.start(new AbstractHandler()
|
||||
scenario.startServer(new AbstractHandler()
|
||||
{
|
||||
@Override
|
||||
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||
|
@ -393,8 +392,7 @@ public class HttpClientContinueTest extends AbstractTest<TransportScenario>
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
scenario.client.setIdleTimeout(idleTimeout);
|
||||
scenario.startClient(httpClient -> httpClient.setIdleTimeout(idleTimeout));
|
||||
|
||||
byte[] content = new byte[1024];
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
|
|
|
@ -18,9 +18,6 @@
|
|||
|
||||
package org.eclipse.jetty.http.client;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
@ -37,6 +34,9 @@ import org.eclipse.jetty.server.handler.AbstractHandler;
|
|||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ArgumentsSource;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class HttpClientIdleTimeoutTest extends AbstractTest<TransportScenario>
|
||||
{
|
||||
private long idleTimeout = 1000;
|
||||
|
@ -52,7 +52,7 @@ public class HttpClientIdleTimeoutTest extends AbstractTest<TransportScenario>
|
|||
public void testClientIdleTimeout(Transport transport) throws Exception
|
||||
{
|
||||
init(transport);
|
||||
scenario.start(new AbstractHandler()
|
||||
scenario.startServer(new AbstractHandler()
|
||||
{
|
||||
@Override
|
||||
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||
|
@ -65,9 +65,7 @@ public class HttpClientIdleTimeoutTest extends AbstractTest<TransportScenario>
|
|||
}
|
||||
}
|
||||
});
|
||||
scenario.client.stop();
|
||||
scenario.client.setIdleTimeout(idleTimeout);
|
||||
scenario.client.start();
|
||||
scenario.startClient(httpClient -> httpClient.setIdleTimeout(idleTimeout));
|
||||
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
scenario.client.newRequest(scenario.newURI())
|
||||
|
@ -126,10 +124,8 @@ public class HttpClientIdleTimeoutTest extends AbstractTest<TransportScenario>
|
|||
public void testIdleClientIdleTimeout(Transport transport) throws Exception
|
||||
{
|
||||
init(transport);
|
||||
scenario.start(new EmptyServerHandler());
|
||||
scenario.client.stop();
|
||||
scenario.client.setIdleTimeout(idleTimeout);
|
||||
scenario.client.start();
|
||||
scenario.startServer(new EmptyServerHandler());
|
||||
scenario.startClient(httpClient -> httpClient.setIdleTimeout(idleTimeout));
|
||||
|
||||
// Make a first request to open a connection.
|
||||
ContentResponse response = scenario.client.newRequest(scenario.newURI()).send();
|
||||
|
|
|
@ -18,15 +18,6 @@
|
|||
|
||||
package org.eclipse.jetty.http.client;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InterruptedIOException;
|
||||
|
@ -63,6 +54,15 @@ import org.junit.jupiter.api.Assumptions;
|
|||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ArgumentsSource;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
public class HttpClientTest extends AbstractTest<TransportScenario>
|
||||
{
|
||||
@Override
|
||||
|
@ -453,7 +453,9 @@ public class HttpClientTest extends AbstractTest<TransportScenario>
|
|||
public void testConnectionListener(Transport transport) throws Exception
|
||||
{
|
||||
init(transport);
|
||||
scenario.start(new EmptyServerHandler());
|
||||
scenario.startServer(new EmptyServerHandler());
|
||||
long idleTimeout = 1000;
|
||||
scenario.startClient(httpClient -> httpClient.setIdleTimeout(idleTimeout));
|
||||
|
||||
CountDownLatch openLatch = new CountDownLatch(1);
|
||||
CountDownLatch closeLatch = new CountDownLatch(1);
|
||||
|
@ -472,9 +474,6 @@ public class HttpClientTest extends AbstractTest<TransportScenario>
|
|||
}
|
||||
});
|
||||
|
||||
long idleTimeout = 1000;
|
||||
scenario.client.setIdleTimeout(idleTimeout);
|
||||
|
||||
ContentResponse response = scenario.client.newRequest(scenario.newURI())
|
||||
.scheme(scenario.getScheme())
|
||||
.timeout(5, TimeUnit.SECONDS)
|
||||
|
|
|
@ -2,6 +2,6 @@ org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
|
|||
#org.eclipse.jetty.LEVEL=DEBUG
|
||||
#org.eclipse.jetty.client.LEVEL=DEBUG
|
||||
#org.eclipse.jetty.http2.LEVEL=DEBUG
|
||||
#org.eclipse.jetty.http2.hpack.LEVEL=INFO
|
||||
org.eclipse.jetty.http2.hpack.LEVEL=INFO
|
||||
#org.eclipse.jetty.http2.client.LEVEL=DEBUG
|
||||
#org.eclipse.jetty.io.LEVEL=DEBUG
|
||||
|
|
Loading…
Reference in New Issue