diff --git a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/client/http/client-http-transport.adoc b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/client/http/client-http-transport.adoc
index 778c0438267f..f66c0a15849e 100644
--- a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/client/http/client-http-transport.adoc
+++ b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/client/http/client-http-transport.adoc
@@ -187,9 +187,11 @@ All the transports can be configured with a `ClientConnector`, the component tha
By default, `ClientConnector` uses TCP networking to send bytes to the server and receive bytes from the server.
-When you are using Java 16 or later, `ClientConnector` also support xref:pg-client-io-arch-unix-domain[Unix-Domain sockets], and every transport can be configured to use Unix-Domain sockets instead of TCP networking.
+When you are using Java 16 or later, `ClientConnector` also support xref:pg-client-io-arch-unix-domain[Unix-Domain sockets].
-To configure Unix-Domain sockets, you can create a `ClientConnector` instance in the following way:
+Unix-Domain sockets can be used for HTTP/1.1 and HTTP/2 but not for HTTP/3, because HTTP/3 is based on UDP and there is currently no support in Java for UDP over Unix-Domain sockets.
+
+You can send requests to a Unix-Domain sockets server in the following way:
[source,java,indent=0]
----
diff --git a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/server/http/server-http-connector.adoc b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/server/http/server-http-connector.adoc
index 1b1c0d59092b..e74a42b3fb59 100644
--- a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/server/http/server-http-connector.adoc
+++ b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/server/http/server-http-connector.adoc
@@ -20,12 +20,14 @@ The available implementations are:
* `org.eclipse.jetty.server.ServerConnector`, for TCP/IP sockets.
* `org.eclipse.jetty.unixdomain.server.UnixDomainServerConnector` for Unix-Domain sockets (requires Java 16 or later).
-* `org.eclipse.jetty.quic.server.QuicServerConnector`, for the low-level QUIC protocol.
-* `org.eclipse.jetty.http3.server.HTTP3ServerConnector` for the HTTP/3 protocol.
+* `org.eclipse.jetty.quic.server.QuicServerConnector`, for the low-level QUIC protocol and HTTP/3.
+* `org.eclipse.jetty.server.MemoryConnector`, for memory communication between client and server.
-The first two use a `java.nio.channels.ServerSocketChannel` to listen to a socket address and to accept socket connections, while last two use a `java.nio.channels.DatagramChannel`.
+`ServerConnector` and `UnixDomainServerConnector` use a `java.nio.channels.ServerSocketChannel` to listen to a socket address and to accept socket connections.
+`QuicServerConnector` uses a `java.nio.channels.DatagramChannel` to listen to incoming UDP packets.
+`MemoryConnector` uses memory for the communication between client and server, avoiding the use of sockets
-Since `ServerConnector` wraps a `ServerSocketChannel`, it can be configured in a similar way, for example the IP port to listen to, the IP address to bind to, etc.:
+Since `ServerConnector` wraps a `ServerSocketChannel`, it can be configured in a similar way, for example the TCP port to listen to, the IP address to bind to, etc.:
[source,java,indent=0]
----
@@ -44,13 +46,15 @@ include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/http/HTTPSer
You can use Unix-Domain sockets support only when you run your server with Java 16 or later.
====
-`QuicServerConnector` and its extension `HTTP3ServerConnector` wrap a `DatagramChannel` and can be configured in a similar way:
+`QuicServerConnector` wraps a `DatagramChannel` and can be configured in a similar way:
[source,java,indent=0]
----
include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/http/HTTPServerDocs.java[tags=configureConnectorQuic]
----
+// TODO: add a section on MemoryConnector.
+
[[pg-server-http-connector-acceptors]]
===== Acceptors
diff --git a/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/client/ClientConnectorDocs.java b/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/client/ClientConnectorDocs.java
index fccb713eea71..60dff979f791 100644
--- a/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/client/ClientConnectorDocs.java
+++ b/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/client/ClientConnectorDocs.java
@@ -22,6 +22,7 @@
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
@@ -153,7 +154,7 @@ public void onFillable()
CompletableFuture connectionPromise = new Promise.Completable<>();
// Populate the context with the mandatory keys to create and obtain connections.
- Map context = new HashMap<>();
+ Map context = new ConcurrentHashMap<>();
context.put(ClientConnector.CLIENT_CONNECTION_FACTORY_CONTEXT_KEY, connectionFactory);
context.put(ClientConnector.CONNECTION_PROMISE_CONTEXT_KEY, connectionPromise);
clientConnector.connect(address, context);
diff --git a/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/client/http/HTTPClientDocs.java b/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/client/http/HTTPClientDocs.java
index 1c73a3a25405..1d9a6e195b1f 100644
--- a/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/client/http/HTTPClientDocs.java
+++ b/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/client/http/HTTPClientDocs.java
@@ -69,6 +69,8 @@
import org.eclipse.jetty.io.ClientConnectionFactory;
import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.io.Content;
+import org.eclipse.jetty.io.TransportProtocol;
+import org.eclipse.jetty.quic.client.ClientQuicConfiguration;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.ssl.SslContextFactory;
@@ -871,8 +873,11 @@ public void http2Transport() throws Exception
public void http3Transport() throws Exception
{
// tag::http3Transport[]
+ // HTTP/3 requires secure communication.
+ SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
// The HTTP3Client powers the HTTP/3 transport.
- HTTP3Client h3Client = new HTTP3Client();
+ ClientQuicConfiguration clientQuicConfig = new ClientQuicConfiguration(sslContextFactory, null);
+ HTTP3Client h3Client = new HTTP3Client(clientQuicConfig);
h3Client.getQuicConfiguration().setSessionRecvWindow(64 * 1024 * 1024);
// Create and configure the HTTP/3 transport.
@@ -1018,25 +1023,29 @@ public void unixDomain() throws Exception
// This is the path where the server "listens" on.
Path unixDomainPath = Path.of("/path/to/server.sock");
- // Creates a ClientConnector that uses Unix-Domain
- // sockets, not the network, to connect to the server.
- ClientConnector unixDomainClientConnector = ClientConnector.forUnixDomain(unixDomainPath);
+ // Creates a ClientConnector.
+ ClientConnector clientConnector = new ClientConnector();
- // Use Unix-Domain for HTTP/1.1.
- HttpClientTransportOverHTTP http1Transport = new HttpClientTransportOverHTTP(unixDomainClientConnector);
+ // You can use Unix-Domain for HTTP/1.1.
+ HttpClientTransportOverHTTP http1Transport = new HttpClientTransportOverHTTP(clientConnector);
// You can use Unix-Domain also for HTTP/2.
- HTTP2Client http2Client = new HTTP2Client(unixDomainClientConnector);
+ HTTP2Client http2Client = new HTTP2Client(clientConnector);
HttpClientTransportOverHTTP2 http2Transport = new HttpClientTransportOverHTTP2(http2Client);
- // You can also use UnixDomain for the dynamic transport.
+ // You can use Unix-Domain also for the dynamic transport.
ClientConnectionFactory.Info http1 = HttpClientConnectionFactory.HTTP11;
ClientConnectionFactoryOverHTTP2.HTTP2 http2 = new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client);
- HttpClientTransportDynamic dynamicTransport = new HttpClientTransportDynamic(unixDomainClientConnector, http1, http2);
+ HttpClientTransportDynamic dynamicTransport = new HttpClientTransportDynamic(clientConnector, http1, http2);
// Choose the transport you prefer for HttpClient, for example the dynamic transport.
HttpClient httpClient = new HttpClient(dynamicTransport);
httpClient.start();
+
+ // Make a request and specify that you want to send it over Unix-Domain.
+ ContentResponse response = httpClient.newRequest("jetty.org", 80)
+ .transportProtocol(new TransportProtocol.TCPUnix(unixDomainPath))
+ .send();
// end::unixDomain[]
}
}
diff --git a/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/client/http3/HTTP3ClientDocs.java b/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/client/http3/HTTP3ClientDocs.java
index 309f645fe8d1..a18a2b14903c 100644
--- a/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/client/http3/HTTP3ClientDocs.java
+++ b/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/client/http3/HTTP3ClientDocs.java
@@ -33,6 +33,8 @@
import org.eclipse.jetty.http3.client.HTTP3Client;
import org.eclipse.jetty.http3.frames.DataFrame;
import org.eclipse.jetty.http3.frames.HeadersFrame;
+import org.eclipse.jetty.quic.client.ClientQuicConfiguration;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
import static java.lang.System.Logger.Level.INFO;
@@ -43,7 +45,8 @@ public void start() throws Exception
{
// tag::start[]
// Instantiate HTTP3Client.
- HTTP3Client http3Client = new HTTP3Client();
+ SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
+ HTTP3Client http3Client = new HTTP3Client(new ClientQuicConfiguration(sslContextFactory, null));
// Configure HTTP3Client, for example:
http3Client.getHTTP3Configuration().setStreamIdleTimeout(15000);
@@ -55,7 +58,8 @@ public void start() throws Exception
public void stop() throws Exception
{
- HTTP3Client http3Client = new HTTP3Client();
+ SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
+ HTTP3Client http3Client = new HTTP3Client(new ClientQuicConfiguration(sslContextFactory, null));
http3Client.start();
// tag::stop[]
// Stop HTTP3Client.
@@ -65,7 +69,8 @@ public void stop() throws Exception
public void connect() throws Exception
{
- HTTP3Client http3Client = new HTTP3Client();
+ SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
+ HTTP3Client http3Client = new HTTP3Client(new ClientQuicConfiguration(sslContextFactory, null));
http3Client.start();
// tag::connect[]
// Address of the server's port.
@@ -83,7 +88,8 @@ public void connect() throws Exception
public void configure() throws Exception
{
- HTTP3Client http3Client = new HTTP3Client();
+ SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
+ HTTP3Client http3Client = new HTTP3Client(new ClientQuicConfiguration(sslContextFactory, null));
http3Client.start();
// tag::configure[]
@@ -105,7 +111,8 @@ public Map onPreface(Session session)
public void newStream() throws Exception
{
- HTTP3Client http3Client = new HTTP3Client();
+ SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
+ HTTP3Client http3Client = new HTTP3Client(new ClientQuicConfiguration(sslContextFactory, null));
http3Client.start();
// tag::newStream[]
SocketAddress serverAddress = new InetSocketAddress("localhost", 8444);
@@ -130,7 +137,8 @@ public void newStream() throws Exception
public void newStreamWithData() throws Exception
{
- HTTP3Client http3Client = new HTTP3Client();
+ SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
+ HTTP3Client http3Client = new HTTP3Client(new ClientQuicConfiguration(sslContextFactory, null));
http3Client.start();
// tag::newStreamWithData[]
SocketAddress serverAddress = new InetSocketAddress("localhost", 8444);
@@ -173,7 +181,8 @@ public void newStreamWithData() throws Exception
public void responseListener() throws Exception
{
- HTTP3Client http3Client = new HTTP3Client();
+ SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
+ HTTP3Client http3Client = new HTTP3Client(new ClientQuicConfiguration(sslContextFactory, null));
http3Client.start();
SocketAddress serverAddress = new InetSocketAddress("localhost", 8444);
CompletableFuture sessionCF = http3Client.connect(serverAddress, new Session.Client.Listener() {});
@@ -231,7 +240,8 @@ private void process(ByteBuffer byteBuffer)
public void reset() throws Exception
{
- HTTP3Client http3Client = new HTTP3Client();
+ SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
+ HTTP3Client http3Client = new HTTP3Client(new ClientQuicConfiguration(sslContextFactory, null));
http3Client.start();
SocketAddress serverAddress = new InetSocketAddress("localhost", 8444);
CompletableFuture sessionCF = http3Client.connect(serverAddress, new Session.Client.Listener() {});
diff --git a/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/client/websocket/WebSocketClientDocs.java b/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/client/websocket/WebSocketClientDocs.java
index f32612e6827d..f1750d753732 100644
--- a/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/client/websocket/WebSocketClientDocs.java
+++ b/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/client/websocket/WebSocketClientDocs.java
@@ -25,6 +25,7 @@
import org.eclipse.jetty.http2.client.HTTP2Client;
import org.eclipse.jetty.http2.client.transport.ClientConnectionFactoryOverHTTP2;
import org.eclipse.jetty.http2.client.transport.HttpClientTransportOverHTTP2;
+import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
@@ -124,8 +125,9 @@ public void connectHTTP2Dynamic() throws Exception
{
// tag::connectHTTP2Dynamic[]
// Use the dynamic HTTP/2 transport for HttpClient.
- HTTP2Client http2Client = new HTTP2Client();
- HttpClient httpClient = new HttpClient(new HttpClientTransportDynamic(new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client)));
+ ClientConnector clientConnector = new ClientConnector();
+ HTTP2Client http2Client = new HTTP2Client(clientConnector);
+ HttpClient httpClient = new HttpClient(new HttpClientTransportDynamic(clientConnector, new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client)));
// Create and start WebSocketClient.
WebSocketClient webSocketClient = new WebSocketClient(httpClient);
diff --git a/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/server/http/HTTPServerDocs.java b/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/server/http/HTTPServerDocs.java
index a2eed0eb2850..e1a8e0b621a8 100644
--- a/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/server/http/HTTPServerDocs.java
+++ b/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/server/http/HTTPServerDocs.java
@@ -46,8 +46,9 @@
import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory;
import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory;
import org.eclipse.jetty.http3.server.HTTP3ServerConnectionFactory;
-import org.eclipse.jetty.http3.server.HTTP3ServerConnector;
import org.eclipse.jetty.io.Content;
+import org.eclipse.jetty.quic.server.QuicServerConnector;
+import org.eclipse.jetty.quic.server.ServerQuicConfiguration;
import org.eclipse.jetty.rewrite.handler.CompactPathRule;
import org.eclipse.jetty.rewrite.handler.RedirectRegexRule;
import org.eclipse.jetty.rewrite.handler.RewriteHandler;
@@ -223,8 +224,10 @@ public void configureConnectorQuic() throws Exception
sslContextFactory.setKeyStorePath("/path/to/keystore");
sslContextFactory.setKeyStorePassword("secret");
- // Create an HTTP3ServerConnector instance.
- HTTP3ServerConnector connector = new HTTP3ServerConnector(server, sslContextFactory, new HTTP3ServerConnectionFactory());
+ // Create a QuicServerConnector instance.
+ Path pemWorkDir = Path.of("/path/to/pem/dir");
+ ServerQuicConfiguration serverQuicConfig = new ServerQuicConfiguration(sslContextFactory, pemWorkDir);
+ QuicServerConnector connector = new QuicServerConnector(server, serverQuicConfig, new HTTP3ServerConnectionFactory(serverQuicConfig));
// The port to listen to.
connector.setPort(8080);
@@ -289,8 +292,9 @@ public void sameRandomPort() throws Exception
server.addConnector(plainConnector);
// Third, create the connector for HTTP/3.
- HTTP3ServerConnectionFactory http3 = new HTTP3ServerConnectionFactory(secureConfig);
- HTTP3ServerConnector http3Connector = new HTTP3ServerConnector(server, sslContextFactory, http3);
+ Path pemWorkDir = Path.of("/path/to/pem/dir");
+ ServerQuicConfiguration serverQuicConfig = new ServerQuicConfiguration(sslContextFactory, pemWorkDir);
+ QuicServerConnector http3Connector = new QuicServerConnector(server, serverQuicConfig, new HTTP3ServerConnectionFactory(serverQuicConfig));
server.addConnector(http3Connector);
// Set up a listener so that when the secure connector starts,
@@ -493,12 +497,12 @@ public void h3() throws Exception
httpConfig.addCustomizer(new SecureRequestCustomizer());
// Create and configure the HTTP/3 connector.
- HTTP3ServerConnector connector = new HTTP3ServerConnector(server, sslContextFactory, new HTTP3ServerConnectionFactory(httpConfig));
+ // It is mandatory to configure the PEM directory.
+ Path pemWorkDir = Path.of("/path/to/pem/dir");
+ ServerQuicConfiguration serverQuicConfig = new ServerQuicConfiguration(sslContextFactory, pemWorkDir);
+ QuicServerConnector connector = new QuicServerConnector(server, serverQuicConfig, new HTTP3ServerConnectionFactory(serverQuicConfig));
connector.setPort(843);
- // It is mandatory to set the PEM directory.
- connector.getQuicConfiguration().setPemWorkDirectory(Path.of("/path/to/pem/dir"));
-
server.addConnector(connector);
server.start();
diff --git a/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/server/http3/HTTP3ServerDocs.java b/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/server/http3/HTTP3ServerDocs.java
index 0453e2783307..f93598dd2acd 100644
--- a/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/server/http3/HTTP3ServerDocs.java
+++ b/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/server/http3/HTTP3ServerDocs.java
@@ -15,6 +15,7 @@
import java.net.SocketAddress;
import java.nio.ByteBuffer;
+import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.RejectedExecutionException;
@@ -29,8 +30,9 @@
import org.eclipse.jetty.http3.api.Stream;
import org.eclipse.jetty.http3.frames.DataFrame;
import org.eclipse.jetty.http3.frames.HeadersFrame;
-import org.eclipse.jetty.http3.server.HTTP3ServerConnector;
import org.eclipse.jetty.http3.server.RawHTTP3ServerConnectionFactory;
+import org.eclipse.jetty.quic.server.QuicServerConnector;
+import org.eclipse.jetty.quic.server.ServerQuicConfiguration;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.ssl.SslContextFactory;
@@ -53,14 +55,16 @@ public void setup() throws Exception
// The listener for session events.
Session.Server.Listener sessionListener = new Session.Server.Listener() {};
+ ServerQuicConfiguration quicConfiguration = new ServerQuicConfiguration(sslContextFactory, Path.of("/path/to/pem/dir"));
+ // Configure the max number of requests per QUIC connection.
+ quicConfiguration.setMaxBidirectionalRemoteStreams(1024);
+
// Create and configure the RawHTTP3ServerConnectionFactory.
- RawHTTP3ServerConnectionFactory http3 = new RawHTTP3ServerConnectionFactory(sessionListener);
+ RawHTTP3ServerConnectionFactory http3 = new RawHTTP3ServerConnectionFactory(quicConfiguration, sessionListener);
http3.getHTTP3Configuration().setStreamIdleTimeout(15000);
- // Create and configure the HTTP3ServerConnector.
- HTTP3ServerConnector connector = new HTTP3ServerConnector(server, sslContextFactory, http3);
- // Configure the max number of requests per QUIC connection.
- connector.getQuicConfiguration().setMaxBidirectionalRemoteStreams(1024);
+ // Create and configure the QuicServerConnector.
+ QuicServerConnector connector = new QuicServerConnector(server, quicConfiguration, http3);
// Add the Connector to the Server.
server.addConnector(connector);
diff --git a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractConnectorHttpClientTransport.java b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractConnectorHttpClientTransport.java
index c390bbd8a3e3..3794710e5520 100644
--- a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractConnectorHttpClientTransport.java
+++ b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractConnectorHttpClientTransport.java
@@ -70,7 +70,8 @@ public void connect(SocketAddress address, Map context)
@SuppressWarnings("unchecked")
Promise promise = (Promise)context.get(HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
context.put(ClientConnector.CONNECTION_PROMISE_CONTEXT_KEY, Promise.from(ioConnection -> {}, promise::failed));
- connector.connect(address, context);
+ context.put(ClientConnector.CLIENT_CONNECTOR_CONTEXT_KEY, connector);
+ destination.getOrigin().getTransportProtocol().connect(address, context);
}
@Override
diff --git a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java
index c327e73588e1..c7ede005975b 100644
--- a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java
+++ b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java
@@ -50,6 +50,7 @@
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.ClientConnectionFactory;
import org.eclipse.jetty.io.ClientConnector;
+import org.eclipse.jetty.io.TransportProtocol;
import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
import org.eclipse.jetty.util.Fields;
import org.eclipse.jetty.util.Jetty;
@@ -470,7 +471,16 @@ public Origin createOrigin(Request request, Origin.Protocol protocol)
host = host.toLowerCase(Locale.ENGLISH);
int port = request.getPort();
port = normalizePort(scheme, port);
- return new Origin(scheme, host, port, request.getTag(), protocol);
+ TransportProtocol transportProtocol = request.getTransportProtocol();
+ if (transportProtocol == null)
+ {
+ // Ask the ClientConnector for backwards compatibility
+ // until ClientConnector.Configurator is removed.
+ transportProtocol = connector.newTransportProtocol();
+ if (transportProtocol == null)
+ transportProtocol = TransportProtocol.TCP_IP;
+ }
+ return new Origin(scheme, new Origin.Address(host, port), request.getTag(), protocol, transportProtocol);
}
/**
@@ -528,39 +538,54 @@ public void newConnection(Destination destination, Promise promise)
List protocols = protocol != null ? protocol.getProtocols() : List.of("http/1.1");
context.put(ClientConnector.APPLICATION_PROTOCOLS_CONTEXT_KEY, protocols);
+ Origin origin = destination.getOrigin();
ProxyConfiguration.Proxy proxy = destination.getProxy();
- Origin.Address address = proxy == null ? destination.getOrigin().getAddress() : proxy.getAddress();
- getSocketAddressResolver().resolve(address.getHost(), address.getPort(), new Promise<>()
+ if (proxy != null)
+ origin = proxy.getOrigin();
+
+ TransportProtocol transportProtocol = origin.getTransportProtocol();
+ context.put(TransportProtocol.class.getName(), transportProtocol);
+
+ if (transportProtocol.requiresDomainNamesResolution())
{
- @Override
- public void succeeded(List socketAddresses)
+ Origin.Address address = origin.getAddress();
+ getSocketAddressResolver().resolve(address.getHost(), address.getPort(), new Promise<>()
{
- connect(socketAddresses, 0, context);
- }
+ @Override
+ public void succeeded(List socketAddresses)
+ {
+ connect(socketAddresses, 0, context);
+ }
- @Override
- public void failed(Throwable x)
- {
- promise.failed(x);
- }
+ @Override
+ public void failed(Throwable x)
+ {
+ promise.failed(x);
+ }
- private void connect(List socketAddresses, int index, Map context)
- {
- context.put(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY, new Promise.Wrapper<>(promise)
+ private void connect(List socketAddresses, int index, Map context)
{
- @Override
- public void failed(Throwable x)
+ context.put(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY, new Promise.Wrapper<>(promise)
{
- int nextIndex = index + 1;
- if (nextIndex == socketAddresses.size())
- super.failed(x);
- else
- connect(socketAddresses, nextIndex, context);
- }
- });
- transport.connect((SocketAddress)socketAddresses.get(index), context);
- }
- });
+ @Override
+ public void failed(Throwable x)
+ {
+ int nextIndex = index + 1;
+ if (nextIndex == socketAddresses.size())
+ super.failed(x);
+ else
+ connect(socketAddresses, nextIndex, context);
+ }
+ });
+ transport.connect((SocketAddress)socketAddresses.get(index), context);
+ }
+ });
+ }
+ else
+ {
+ context.put(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY, promise);
+ transport.connect(transportProtocol.getSocketAddress(), context);
+ }
}
private HttpConversation newConversation()
diff --git a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/HttpProxy.java b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/HttpProxy.java
index 97f98b052985..4de7c7dc60c8 100644
--- a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/HttpProxy.java
+++ b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/HttpProxy.java
@@ -19,7 +19,6 @@
import java.net.URI;
import java.util.List;
import java.util.Map;
-import java.util.Objects;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.client.internal.TunnelRequest;
@@ -32,6 +31,7 @@
import org.eclipse.jetty.io.ClientConnectionFactory;
import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.TransportProtocol;
import org.eclipse.jetty.util.Attachable;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.ssl.SslContextFactory;
@@ -49,27 +49,27 @@ public HttpProxy(String host, int port)
public HttpProxy(Origin.Address address, boolean secure)
{
- this(address, secure, null, new Origin.Protocol(List.of("http/1.1"), false));
+ this(address, secure, new Origin.Protocol(List.of("http/1.1"), false));
}
public HttpProxy(Origin.Address address, boolean secure, Origin.Protocol protocol)
{
- this(address, secure, null, Objects.requireNonNull(protocol));
+ this(new Origin(secure ? "https" : "http", address, null, protocol, TransportProtocol.TCP_IP), null);
}
public HttpProxy(Origin.Address address, SslContextFactory.Client sslContextFactory)
{
- this(address, true, sslContextFactory, new Origin.Protocol(List.of("http/1.1"), false));
+ this(address, sslContextFactory, new Origin.Protocol(List.of("http/1.1"), false));
}
public HttpProxy(Origin.Address address, SslContextFactory.Client sslContextFactory, Origin.Protocol protocol)
{
- this(address, true, sslContextFactory, Objects.requireNonNull(protocol));
+ this(new Origin(sslContextFactory == null ? "http" : "https", address, null, protocol, TransportProtocol.TCP_IP), sslContextFactory);
}
- private HttpProxy(Origin.Address address, boolean secure, SslContextFactory.Client sslContextFactory, Origin.Protocol protocol)
+ public HttpProxy(Origin origin, SslContextFactory.Client sslContextFactory)
{
- super(address, secure, sslContextFactory, Objects.requireNonNull(protocol));
+ super(origin, sslContextFactory);
}
@Override
diff --git a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/Origin.java b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/Origin.java
index 2f838969d6c1..7d48dd5c6997 100644
--- a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/Origin.java
+++ b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/Origin.java
@@ -22,6 +22,7 @@
import org.eclipse.jetty.client.transport.HttpClientTransportDynamic;
import org.eclipse.jetty.io.ClientConnectionFactory;
import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.TransportProtocol;
import org.eclipse.jetty.util.HostPort;
import org.eclipse.jetty.util.URIUtil;
@@ -46,6 +47,7 @@ public class Origin
private final Address address;
private final Object tag;
private final Protocol protocol;
+ private final TransportProtocol transport;
public Origin(String scheme, String host, int port)
{
@@ -73,11 +75,17 @@ public Origin(String scheme, Address address, Object tag)
}
public Origin(String scheme, Address address, Object tag, Protocol protocol)
+ {
+ this(scheme, address, tag, protocol, TransportProtocol.TCP_IP);
+ }
+
+ public Origin(String scheme, Address address, Object tag, Protocol protocol, TransportProtocol transport)
{
this.scheme = Objects.requireNonNull(scheme);
this.address = address;
this.tag = tag;
this.protocol = protocol;
+ this.transport = transport;
}
public String getScheme()
@@ -100,6 +108,17 @@ public Protocol getProtocol()
return protocol;
}
+ public TransportProtocol getTransportProtocol()
+ {
+ return transport;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return Objects.hash(scheme, address, tag, protocol, transport);
+ }
+
@Override
public boolean equals(Object obj)
{
@@ -109,15 +128,10 @@ public boolean equals(Object obj)
return false;
Origin that = (Origin)obj;
return scheme.equals(that.scheme) &&
- address.equals(that.address) &&
- Objects.equals(tag, that.tag) &&
- Objects.equals(protocol, that.protocol);
- }
-
- @Override
- public int hashCode()
- {
- return Objects.hash(scheme, address, tag, protocol);
+ address.equals(that.address) &&
+ Objects.equals(tag, that.tag) &&
+ Objects.equals(protocol, that.protocol) &&
+ Objects.equals(transport, that.transport);
}
public String asString()
@@ -130,12 +144,14 @@ public String asString()
@Override
public String toString()
{
- return String.format("%s@%x[%s,tag=%s,protocol=%s]",
+ return String.format("%s@%x[%s,tag=%s,protocol=%s,transport=%s]",
getClass().getSimpleName(),
hashCode(),
asString(),
getTag(),
- getProtocol());
+ getProtocol(),
+ getTransportProtocol()
+ );
}
public static class Address
@@ -217,7 +233,7 @@ public static class Protocol
*/
public Protocol(List protocols, boolean negotiate)
{
- this.protocols = protocols;
+ this.protocols = List.copyOf(protocols);
this.negotiate = negotiate;
}
diff --git a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/Request.java b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/Request.java
index 10a8e75bd035..1bd11eaea2e9 100644
--- a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/Request.java
+++ b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/Request.java
@@ -33,6 +33,7 @@
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.io.TransportProtocol;
import org.eclipse.jetty.util.Fields;
/**
@@ -103,6 +104,23 @@ default Request port(int port)
return this;
}
+ /**
+ * @param transport the {@link TransportProtocol} of this request
+ * @return this request object
+ */
+ default Request transportProtocol(TransportProtocol transport)
+ {
+ return this;
+ }
+
+ /**
+ * @return the {@link TransportProtocol} of this request
+ */
+ default TransportProtocol getTransportProtocol()
+ {
+ return null;
+ }
+
/**
* @return the method of this request, such as GET or POST, as a String
*/
diff --git a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/transport/HttpClientConnectionFactory.java b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/transport/HttpClientConnectionFactory.java
index 90c78946f128..e2a1ee43c850 100644
--- a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/transport/HttpClientConnectionFactory.java
+++ b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/transport/HttpClientConnectionFactory.java
@@ -19,6 +19,7 @@
import org.eclipse.jetty.client.transport.internal.HttpConnectionOverHTTP;
import org.eclipse.jetty.io.ClientConnectionFactory;
import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.TransportProtocol;
public class HttpClientConnectionFactory implements ClientConnectionFactory
{
@@ -49,6 +50,12 @@ public List getProtocols(boolean secure)
return protocols;
}
+ @Override
+ public TransportProtocol newTransportProtocol()
+ {
+ return TransportProtocol.TCP_IP;
+ }
+
@Override
public String toString()
{
diff --git a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/transport/HttpClientTransportDynamic.java b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/transport/HttpClientTransportDynamic.java
index 77c6f22900d9..14f457d34c95 100644
--- a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/transport/HttpClientTransportDynamic.java
+++ b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/transport/HttpClientTransportDynamic.java
@@ -14,15 +14,12 @@
package org.eclipse.jetty.client.transport;
import java.io.IOException;
-import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
-import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
-import java.util.stream.Stream;
import org.eclipse.jetty.alpn.client.ALPNClientConnection;
import org.eclipse.jetty.alpn.client.ALPNClientConnectionFactory;
@@ -39,6 +36,7 @@
import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.TransportProtocol;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -81,42 +79,42 @@ public class HttpClientTransportDynamic extends AbstractConnectorHttpClientTrans
{
private static final Logger LOG = LoggerFactory.getLogger(HttpClientTransportDynamic.class);
- private final List factoryInfos;
- private final List protocols;
+ private final List infos;
/**
- * Creates a transport that speaks only HTTP/1.1.
+ * Creates a dynamic transport that speaks only HTTP/1.1.
*/
public HttpClientTransportDynamic()
{
- this(HttpClientConnectionFactory.HTTP11);
+ this(new ClientConnector(), HttpClientConnectionFactory.HTTP11);
}
- public HttpClientTransportDynamic(ClientConnectionFactory.Info... factoryInfos)
+ /**
+ * Creates a dynamic transport that speaks the given protocols.
+ *
+ * @param infos the protocols this dynamic transport speaks
+ * @deprecated use {@link #HttpClientTransportDynamic(ClientConnector, ClientConnectionFactory.Info...)}
+ */
+ @Deprecated(since = "12.0.7", forRemoval = true)
+ public HttpClientTransportDynamic(ClientConnectionFactory.Info... infos)
{
- this(findClientConnector(factoryInfos), factoryInfos);
+ this(findClientConnector(infos), infos);
}
/**
- * Creates a transport with the given {@link ClientConnector} and the given application protocols.
+ * Creates a dynamic transport with the given {@link ClientConnector} and the given application protocols.
*
* @param connector the ClientConnector used by this transport
- * @param factoryInfos the application protocols that this transport can speak
+ * @param infos the application protocols that this transport can speak
*/
- public HttpClientTransportDynamic(ClientConnector connector, ClientConnectionFactory.Info... factoryInfos)
+ public HttpClientTransportDynamic(ClientConnector connector, ClientConnectionFactory.Info... infos)
{
super(connector);
- if (factoryInfos.length == 0)
- factoryInfos = new Info[]{HttpClientConnectionFactory.HTTP11};
- this.factoryInfos = Arrays.asList(factoryInfos);
- this.protocols = Arrays.stream(factoryInfos)
- .flatMap(info -> Stream.concat(info.getProtocols(false).stream(), info.getProtocols(true).stream()))
- .distinct()
- .map(p -> p.toLowerCase(Locale.ENGLISH))
- .collect(Collectors.toList());
- Arrays.stream(factoryInfos).forEach(this::addBean);
+ this.infos = infos.length == 0 ? List.of(HttpClientConnectionFactory.HTTP11) : List.of(infos);
+ this.infos.forEach(this::addBean);
setConnectionPoolFactory(destination ->
- new MultiplexConnectionPool(destination, destination.getHttpClient().getMaxConnectionsPerDestination(), 1));
+ new MultiplexConnectionPool(destination, destination.getHttpClient().getMaxConnectionsPerDestination(), 1)
+ );
}
private static ClientConnector findClientConnector(ClientConnectionFactory.Info[] infos)
@@ -130,72 +128,128 @@ private static ClientConnector findClientConnector(ClientConnectionFactory.Info[
@Override
public Origin newOrigin(Request request)
{
- String scheme = request.getScheme();
- boolean secure = HttpScheme.isSecure(scheme);
- String http1 = "http/1.1";
- String http2 = secure ? "h2" : "h2c";
- List protocols = List.of();
+ boolean secure = HttpScheme.isSecure(request.getScheme());
+
+ List matchingInfos = new ArrayList<>();
+ boolean negotiate = false;
+
if (((HttpRequest)request).isVersionExplicit())
{
HttpVersion version = request.getVersion();
- String desired = version == HttpVersion.HTTP_2 ? http2 : http1;
- if (this.protocols.contains(desired))
- protocols = List.of(desired);
+ List wanted = toProtocols(version);
+ for (Info info : infos)
+ {
+ // Find the first protocol that matches the version.
+ List protocols = info.getProtocols(secure);
+ for (String p : protocols)
+ {
+ if (wanted.contains(p))
+ {
+ matchingInfos.add(info);
+ break;
+ }
+ }
+ if (matchingInfos.isEmpty())
+ continue;
+
+ if (secure && protocols.size() > 1)
+ negotiate = true;
+
+ break;
+ }
}
else
{
+ Info preferredInfo = infos.get(0);
if (secure)
{
- // There may be protocol negotiation, so preserve the order
- // of protocols chosen by the application.
- // We need to keep multiple protocols in case the protocol
- // is negotiated: e.g. [http/1.1, h2] negotiates [h2], but
- // here we don't know yet what will be negotiated.
- List http = List.of("http/1.1", "h2c", "h2");
- protocols = this.protocols.stream()
- .filter(http::contains)
- .collect(Collectors.toCollection(ArrayList::new));
+ if (preferredInfo.getProtocols(true).contains("h3"))
+ {
+ // HTTP/3 is not compatible with HTTP/2 and HTTP/1
+ // due to UDP vs TCP, so we can only try HTTP/3.
+ matchingInfos.add(preferredInfo);
+ }
+ else
+ {
+ // If the preferred protocol is not HTTP/3, then
+ // must be excluded since it won't be compatible
+ // with the other HTTP versions due to UDP vs TCP.
+ for (Info info : infos)
+ {
+ if (info.getProtocols(true).contains("h3"))
+ continue;
+ matchingInfos.add(info);
+ }
- // The http/1.1 upgrade to http/2 over TLS implicitly
- // "negotiates" [h2c], so we need to remove [h2]
- // because we don't want to negotiate using ALPN.
- if (request.getHeaders().contains(HttpHeader.UPGRADE, "h2c"))
- protocols.remove("h2");
+ // We can only have HTTP/1 and HTTP/2 here,
+ // decide whether negotiation is necessary.
+ if (!request.getHeaders().contains(HttpHeader.UPGRADE, "h2c"))
+ {
+ int matches = matchingInfos.size();
+ if (matches > 1)
+ negotiate = true;
+ else if (matches == 1)
+ negotiate = matchingInfos.get(0).getProtocols(true).size() > 1;
+ }
+ }
}
else
{
- // Pick the first.
- protocols = List.of(this.protocols.get(0));
+ // Pick the first that allows non-secure.
+ for (Info info : infos)
+ {
+ if (info.getProtocols(false).contains("h3"))
+ continue;
+ matchingInfos.add(info);
+ break;
+ }
}
}
- Origin.Protocol protocol = null;
- if (!protocols.isEmpty())
- protocol = new Origin.Protocol(protocols, secure && protocols.contains(http2));
+
+ if (matchingInfos.isEmpty())
+ return getHttpClient().createOrigin(request, null);
+
+ TransportProtocol transportProtocol = request.getTransportProtocol();
+ if (transportProtocol == null)
+ {
+ // Ask the ClientConnector for backwards compatibility
+ // until ClientConnector.Configurator is removed.
+ transportProtocol = getClientConnector().newTransportProtocol();
+ if (transportProtocol == null)
+ transportProtocol = matchingInfos.get(0).newTransportProtocol();
+ request.transportProtocol(transportProtocol);
+ }
+
+ List protocols = matchingInfos.stream()
+ .flatMap(info -> info.getProtocols(secure).stream())
+ .collect(Collectors.toCollection(ArrayList::new));
+ if (negotiate)
+ protocols.remove("h2c");
+ Origin.Protocol protocol = new Origin.Protocol(protocols, negotiate);
return getHttpClient().createOrigin(request, protocol);
}
@Override
public Destination newDestination(Origin origin)
{
- SocketAddress address = origin.getAddress().getSocketAddress();
- return new HttpDestination(getHttpClient(), origin, getClientConnector().isIntrinsicallySecure(address));
+ return new HttpDestination(getHttpClient(), origin);
}
@Override
public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map context) throws IOException
{
HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
- Origin.Protocol protocol = destination.getOrigin().getProtocol();
+ Origin origin = destination.getOrigin();
+ Origin.Protocol protocol = origin.getProtocol();
ClientConnectionFactory factory;
if (protocol == null)
{
// Use the default ClientConnectionFactory.
- factory = factoryInfos.get(0).getClientConnectionFactory();
+ factory = infos.get(0).getClientConnectionFactory();
}
else
{
- SocketAddress address = destination.getOrigin().getAddress().getSocketAddress();
- boolean intrinsicallySecure = getClientConnector().isIntrinsicallySecure(address);
+ boolean intrinsicallySecure = origin.getTransportProtocol().isIntrinsicallySecure();
if (!intrinsicallySecure && destination.isSecure() && protocol.isNegotiate())
{
factory = new ALPNClientConnectionFactory(getClientConnector().getExecutor(), this::newNegotiatedConnection, protocol.getProtocols());
@@ -237,7 +291,7 @@ protected Connection newNegotiatedConnection(EndPoint endPoint, Map findClientConnectionFactoryInfo(List protocols, boolean secure)
{
- return factoryInfos.stream()
- .filter(info -> info.matches(protocols, secure))
- .findFirst();
+ return infos.stream()
+ .filter(info -> info.matches(protocols, secure))
+ .findFirst();
+ }
+
+ private List toProtocols(HttpVersion version)
+ {
+ return switch (version)
+ {
+ case HTTP_0_9, HTTP_1_0, HTTP_1_1 -> List.of("http/1.1");
+ case HTTP_2 -> List.of("h2c", "h2");
+ case HTTP_3 -> List.of("h3");
+ };
}
}
diff --git a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/transport/HttpClientTransportOverHTTP.java b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/transport/HttpClientTransportOverHTTP.java
index 50153c5db8fe..bab51d75dcaf 100644
--- a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/transport/HttpClientTransportOverHTTP.java
+++ b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/transport/HttpClientTransportOverHTTP.java
@@ -14,7 +14,6 @@
package org.eclipse.jetty.client.transport;
import java.io.IOException;
-import java.net.SocketAddress;
import java.util.List;
import java.util.Map;
@@ -68,8 +67,7 @@ public Origin newOrigin(Request request)
@Override
public Destination newDestination(Origin origin)
{
- SocketAddress address = origin.getAddress().getSocketAddress();
- return new HttpDestination(getHttpClient(), origin, getClientConnector().isIntrinsicallySecure(address));
+ return new HttpDestination(getHttpClient(), origin);
}
@Override
diff --git a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/transport/HttpDestination.java b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/transport/HttpDestination.java
index 4c61e00350b2..6fc8d6fbf2c3 100644
--- a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/transport/HttpDestination.java
+++ b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/transport/HttpDestination.java
@@ -69,7 +69,25 @@ public class HttpDestination extends ContainerLifeCycle implements Destination,
private boolean stale;
private long activeNanoTime;
+ /**
+ * @param client the {@link HttpClient}
+ * @param origin the {@link Origin}
+ * @param intrinsicallySecure whether the destination is intrinsically secure
+ * @deprecated use {@link #HttpDestination(HttpClient, Origin)} instead
+ */
+ @Deprecated
public HttpDestination(HttpClient client, Origin origin, boolean intrinsicallySecure)
+ {
+ this(client, origin);
+ }
+
+ /**
+ * Creates a new HTTP destination.
+ *
+ * @param client the {@link HttpClient}
+ * @param origin the {@link Origin}
+ */
+ public HttpDestination(HttpClient client, Origin origin)
{
this.client = client;
this.origin = origin;
@@ -84,9 +102,11 @@ public HttpDestination(HttpClient client, Origin origin, boolean intrinsicallySe
host += ":" + port;
hostField = new HttpField(HttpHeader.HOST, host);
+ ClientConnectionFactory connectionFactory = client.getTransport();
+ boolean intrinsicallySecure = origin.getTransportProtocol().isIntrinsicallySecure();
+
ProxyConfiguration proxyConfig = client.getProxyConfiguration();
proxy = proxyConfig.match(origin);
- ClientConnectionFactory connectionFactory = client.getTransport();
if (proxy != null)
{
connectionFactory = proxy.newClientConnectionFactory(connectionFactory);
@@ -383,7 +403,7 @@ private boolean process(Connection connection)
if (cause != null)
{
if (LOG.isDebugEnabled())
- LOG.debug("Aborted before processing {}: {}", exchange, cause);
+ LOG.debug("Aborted before processing {}", exchange, cause);
// Won't use this connection, release it back.
boolean released = connectionPool.release(connection);
if (!released)
diff --git a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/transport/HttpRequest.java b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/transport/HttpRequest.java
index 86cb57949f81..27bff58e7833 100644
--- a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/transport/HttpRequest.java
+++ b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/transport/HttpRequest.java
@@ -56,6 +56,7 @@
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.io.TransportProtocol;
import org.eclipse.jetty.util.Fields;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.Promise;
@@ -78,6 +79,7 @@ public class HttpRequest implements Request
private String path;
private String query;
private URI uri;
+ private TransportProtocol transport;
private String method = HttpMethod.GET.asString();
private HttpVersion version = HttpVersion.HTTP_1_1;
private boolean versionExplicit;
@@ -217,6 +219,19 @@ public Request port(int port)
return this;
}
+ @Override
+ public Request transportProtocol(TransportProtocol transport)
+ {
+ this.transport = transport;
+ return this;
+ }
+
+ @Override
+ public TransportProtocol getTransportProtocol()
+ {
+ return transport;
+ }
+
@Override
public String getMethod()
{
diff --git a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/ConnectionPoolTest.java b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/ConnectionPoolTest.java
index 239a5620d7e5..f4ae856e9d7e 100644
--- a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/ConnectionPoolTest.java
+++ b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/ConnectionPoolTest.java
@@ -675,7 +675,7 @@ public void testCountersSweepToStringThroughLifecycle(ConnectionPoolFactory fact
return connectionPool;
});
- AbstractConnectionPool connectionPool = (AbstractConnectionPool)factory.factory.newConnectionPool(new HttpDestination(client, new Origin("", "", 0), false));
+ AbstractConnectionPool connectionPool = (AbstractConnectionPool)factory.factory.newConnectionPool(new HttpDestination(client, new Origin("", "", 0)));
assertThat(connectionPool.getConnectionCount(), is(0));
assertThat(connectionPool.getActiveConnectionCount(), is(0));
assertThat(connectionPool.getIdleConnectionCount(), is(0));
diff --git a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/DuplexHttpDestinationTest.java b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/DuplexHttpDestinationTest.java
index f3f8daf60034..02d0866a690d 100644
--- a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/DuplexHttpDestinationTest.java
+++ b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/DuplexHttpDestinationTest.java
@@ -46,7 +46,7 @@ public void testAcquireWithEmptyQueue(Scenario scenario) throws Exception
{
start(scenario, new EmptyServerHandler());
- HttpDestination destination = new HttpDestination(client, new Origin("http", "localhost", connector.getLocalPort()), false);
+ HttpDestination destination = new HttpDestination(client, new Origin("http", "localhost", connector.getLocalPort()));
try
{
destination.start();
@@ -71,7 +71,7 @@ public void testAcquireWithOneExchangeQueued(Scenario scenario) throws Exception
{
start(scenario, new EmptyServerHandler());
- HttpDestination destination = new HttpDestination(client, new Origin("http", "localhost", connector.getLocalPort()), false);
+ HttpDestination destination = new HttpDestination(client, new Origin("http", "localhost", connector.getLocalPort()));
try
{
destination.start();
@@ -100,7 +100,7 @@ public void testSecondAcquireAfterFirstAcquireWithEmptyQueueReturnsSameConnectio
{
start(scenario, new EmptyServerHandler());
- HttpDestination destination = new HttpDestination(client, new Origin("http", "localhost", connector.getLocalPort()), false);
+ HttpDestination destination = new HttpDestination(client, new Origin("http", "localhost", connector.getLocalPort()));
try
{
destination.start();
@@ -134,7 +134,7 @@ public void testSecondAcquireConcurrentWithFirstAcquireWithEmptyQueueCreatesTwoC
CountDownLatch idleLatch = new CountDownLatch(1);
CountDownLatch latch = new CountDownLatch(1);
- HttpDestination destination = new HttpDestination(client, new Origin("http", "localhost", connector.getLocalPort()), false)
+ HttpDestination destination = new HttpDestination(client, new Origin("http", "localhost", connector.getLocalPort()))
{
@Override
protected ConnectionPool newConnectionPool(HttpClient client)
@@ -201,7 +201,7 @@ public void testAcquireProcessReleaseAcquireReturnsSameConnection(Scenario scena
{
start(scenario, new EmptyServerHandler());
- HttpDestination destination = new HttpDestination(client, new Origin("http", "localhost", connector.getLocalPort()), false);
+ HttpDestination destination = new HttpDestination(client, new Origin("http", "localhost", connector.getLocalPort()));
try
{
destination.start();
@@ -243,7 +243,7 @@ public void testIdleConnectionIdleTimeout(Scenario scenario) throws Exception
long idleTimeout = 1000;
startClient(scenario, httpClient -> httpClient.setIdleTimeout(idleTimeout));
- HttpDestination destination = new HttpDestination(client, new Origin("http", "localhost", connector.getLocalPort()), false);
+ HttpDestination destination = new HttpDestination(client, new Origin("http", "localhost", connector.getLocalPort()));
try
{
destination.start();
diff --git a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAsyncContentTest.java b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAsyncContentTest.java
index 73b217a17ce9..447e47091fdc 100644
--- a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAsyncContentTest.java
+++ b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAsyncContentTest.java
@@ -392,7 +392,7 @@ protected void service(Request request, org.eclipse.jetty.server.Response respon
Thread.sleep(500);
demandRef.get().accept(1);
- assertTrue(resultLatch.await(555, TimeUnit.SECONDS));
+ assertTrue(resultLatch.await(5, TimeUnit.SECONDS));
}
*/
}
diff --git a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientIdleTimeoutTest.java b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientIdleTimeoutTest.java
index 67f6fd3be96c..7336e0a514c4 100644
--- a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientIdleTimeoutTest.java
+++ b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientIdleTimeoutTest.java
@@ -98,7 +98,7 @@ protected void service(Request request, Response response)
@Override
public Destination newDestination(Origin origin)
{
- return new HttpDestination(getHttpClient(), origin, false)
+ return new HttpDestination(getHttpClient(), origin)
{
@Override
protected SendFailure send(IConnection connection, HttpExchange exchange)
diff --git a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTPTest.java b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTPTest.java
index 4b74ab3f784d..ffa73f332c75 100644
--- a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTPTest.java
+++ b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTPTest.java
@@ -79,7 +79,7 @@ public void init(HttpCompliance compliance) throws Exception
client = new HttpClient();
client.setHttpCompliance(compliance);
client.start();
- destination = new HttpDestination(client, new Origin("http", "localhost", 8080), false);
+ destination = new HttpDestination(client, new Origin("http", "localhost", 8080));
destination.start();
endPoint = new ByteArrayEndPoint();
connection = new HttpConnectionOverHTTP(endPoint, destination, new Promise.Adapter<>());
diff --git a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpSenderOverHTTPTest.java b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpSenderOverHTTPTest.java
index 6a723811c4ff..313fd841de41 100644
--- a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpSenderOverHTTPTest.java
+++ b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpSenderOverHTTPTest.java
@@ -61,7 +61,7 @@ public void destroy() throws Exception
public void testSendNoRequestContent() throws Exception
{
ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
- HttpDestination destination = new HttpDestination(client, new Origin("http", "localhost", 8080), false);
+ HttpDestination destination = new HttpDestination(client, new Origin("http", "localhost", 8080));
destination.start();
HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, destination, new Promise.Adapter());
Request request = client.newRequest(URI.create("http://localhost/"));
@@ -94,7 +94,7 @@ public void onSuccess(Request request)
public void testSendNoRequestContentIncompleteFlush() throws Exception
{
ByteArrayEndPoint endPoint = new ByteArrayEndPoint("", 16);
- HttpDestination destination = new HttpDestination(client, new Origin("http", "localhost", 8080), false);
+ HttpDestination destination = new HttpDestination(client, new Origin("http", "localhost", 8080));
destination.start();
HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, destination, new Promise.Adapter());
Request request = client.newRequest(URI.create("http://localhost/"));
@@ -124,7 +124,7 @@ public void testSendNoRequestContentException() throws Exception
ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
// Shutdown output to trigger the exception on write
endPoint.shutdownOutput();
- HttpDestination destination = new HttpDestination(client, new Origin("http", "localhost", 8080), false);
+ HttpDestination destination = new HttpDestination(client, new Origin("http", "localhost", 8080));
destination.start();
HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, destination, new Promise.Adapter());
Request request = client.newRequest(URI.create("http://localhost/"));
@@ -154,7 +154,7 @@ public void onComplete(Result result)
public void testSendNoRequestContentIncompleteFlushException() throws Exception
{
ByteArrayEndPoint endPoint = new ByteArrayEndPoint("", 16);
- HttpDestination destination = new HttpDestination(client, new Origin("http", "localhost", 8080), false);
+ HttpDestination destination = new HttpDestination(client, new Origin("http", "localhost", 8080));
destination.start();
HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, destination, new Promise.Adapter());
Request request = client.newRequest(URI.create("http://localhost/"));
@@ -190,7 +190,7 @@ public void onComplete(Result result)
public void testSendSmallRequestContentInOneBuffer() throws Exception
{
ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
- HttpDestination destination = new HttpDestination(client, new Origin("http", "localhost", 8080), false);
+ HttpDestination destination = new HttpDestination(client, new Origin("http", "localhost", 8080));
destination.start();
HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, destination, new Promise.Adapter());
Request request = client.newRequest(URI.create("http://localhost/"));
@@ -225,7 +225,7 @@ public void onSuccess(Request request)
public void testSendSmallRequestContentInTwoBuffers() throws Exception
{
ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
- HttpDestination destination = new HttpDestination(client, new Origin("http", "localhost", 8080), false);
+ HttpDestination destination = new HttpDestination(client, new Origin("http", "localhost", 8080));
destination.start();
HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, destination, new Promise.Adapter());
Request request = client.newRequest(URI.create("http://localhost/"));
@@ -261,7 +261,7 @@ public void onSuccess(Request request)
public void testSendSmallRequestContentChunkedInTwoChunks() throws Exception
{
ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
- HttpDestination destination = new HttpDestination(client, new Origin("http", "localhost", 8080), false);
+ HttpDestination destination = new HttpDestination(client, new Origin("http", "localhost", 8080));
destination.start();
HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, destination, new Promise.Adapter());
Request request = client.newRequest(URI.create("http://localhost/"));
diff --git a/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/transport/HttpClientTransportOverFCGI.java b/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/transport/HttpClientTransportOverFCGI.java
index ccac893f5d60..422f736260db 100644
--- a/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/transport/HttpClientTransportOverFCGI.java
+++ b/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/transport/HttpClientTransportOverFCGI.java
@@ -13,7 +13,6 @@
package org.eclipse.jetty.fcgi.client.transport;
-import java.net.SocketAddress;
import java.util.List;
import java.util.Map;
@@ -82,8 +81,7 @@ public Origin newOrigin(Request request)
@Override
public Destination newDestination(Origin origin)
{
- SocketAddress address = origin.getAddress().getSocketAddress();
- return new HttpDestination(getHttpClient(), origin, getClientConnector().isIntrinsicallySecure(address));
+ return new HttpDestination(getHttpClient(), origin);
}
@Override
diff --git a/jetty-core/jetty-fcgi/jetty-fcgi-proxy/src/main/java/org/eclipse/jetty/fcgi/proxy/FastCGIProxyHandler.java b/jetty-core/jetty-fcgi/jetty-fcgi-proxy/src/main/java/org/eclipse/jetty/fcgi/proxy/FastCGIProxyHandler.java
index e893b54ab144..2fb046668a6c 100644
--- a/jetty-core/jetty-fcgi/jetty-fcgi-proxy/src/main/java/org/eclipse/jetty/fcgi/proxy/FastCGIProxyHandler.java
+++ b/jetty-core/jetty-fcgi/jetty-fcgi-proxy/src/main/java/org/eclipse/jetty/fcgi/proxy/FastCGIProxyHandler.java
@@ -33,6 +33,7 @@
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.io.ClientConnector;
+import org.eclipse.jetty.io.TransportProtocol;
import org.eclipse.jetty.proxy.ProxyHandler;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
@@ -267,12 +268,7 @@ protected void doStart() throws Exception
@Override
protected HttpClient newHttpClient()
{
- ClientConnector clientConnector;
- Path unixDomainPath = getUnixDomainPath();
- if (unixDomainPath != null)
- clientConnector = ClientConnector.forUnixDomain(unixDomainPath);
- else
- clientConnector = new ClientConnector();
+ ClientConnector clientConnector = new ClientConnector();
QueuedThreadPool proxyClientThreads = new QueuedThreadPool();
proxyClientThreads.setName("proxy-client");
clientConnector.setExecutor(proxyClientThreads);
@@ -333,6 +329,10 @@ protected void sendProxyToServerRequest(Request clientToProxyRequest, org.eclips
proxyToServerRequest.headers(headers -> headers.put(HttpHeader.COOKIE, allCookies));
}
+ Path unixDomain = getUnixDomainPath();
+ if (unixDomain != null)
+ proxyToServerRequest.transportProtocol(new TransportProtocol.TCPUnix(unixDomain));
+
super.sendProxyToServerRequest(clientToProxyRequest, proxyToServerRequest, proxyToClientResponse, proxyToClientCallback);
}
diff --git a/jetty-core/jetty-http2/jetty-http2-client-transport/src/main/java/org/eclipse/jetty/http2/client/transport/ClientConnectionFactoryOverHTTP2.java b/jetty-core/jetty-http2/jetty-http2-client-transport/src/main/java/org/eclipse/jetty/http2/client/transport/ClientConnectionFactoryOverHTTP2.java
index ca6cb64b10b9..3bfe11785abb 100644
--- a/jetty-core/jetty-http2/jetty-http2-client-transport/src/main/java/org/eclipse/jetty/http2/client/transport/ClientConnectionFactoryOverHTTP2.java
+++ b/jetty-core/jetty-http2/jetty-http2-client-transport/src/main/java/org/eclipse/jetty/http2/client/transport/ClientConnectionFactoryOverHTTP2.java
@@ -29,6 +29,7 @@
import org.eclipse.jetty.http2.client.transport.internal.HttpConnectionOverHTTP2;
import org.eclipse.jetty.io.ClientConnectionFactory;
import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.TransportProtocol;
import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
import org.eclipse.jetty.io.ssl.SslConnection;
import org.eclipse.jetty.util.Promise;
@@ -37,19 +38,19 @@
public class ClientConnectionFactoryOverHTTP2 extends ContainerLifeCycle implements ClientConnectionFactory
{
private final ClientConnectionFactory factory = new HTTP2ClientConnectionFactory();
- private final HTTP2Client client;
+ private final HTTP2Client http2Client;
- public ClientConnectionFactoryOverHTTP2(HTTP2Client client)
+ public ClientConnectionFactoryOverHTTP2(HTTP2Client http2Client)
{
- this.client = client;
- addBean(client);
+ this.http2Client = http2Client;
+ addBean(http2Client);
}
@Override
public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map context) throws IOException
{
HTTPSessionListenerPromise listenerPromise = new HTTPSessionListenerPromise(context);
- context.put(HTTP2ClientConnectionFactory.CLIENT_CONTEXT_KEY, client);
+ context.put(HTTP2ClientConnectionFactory.CLIENT_CONTEXT_KEY, http2Client);
context.put(HTTP2ClientConnectionFactory.SESSION_LISTENER_CONTEXT_KEY, listenerPromise);
context.put(HTTP2ClientConnectionFactory.SESSION_PROMISE_CONTEXT_KEY, listenerPromise);
return factory.newConnection(endPoint, context);
@@ -65,9 +66,9 @@ public static class HTTP2 extends Info
private static final List protocols = List.of("h2", "h2c");
private static final List h2c = List.of("h2c");
- public HTTP2(HTTP2Client client)
+ public HTTP2(HTTP2Client http2Client)
{
- super(new ClientConnectionFactoryOverHTTP2(client));
+ super(new ClientConnectionFactoryOverHTTP2(http2Client));
}
@Override
@@ -76,6 +77,12 @@ public List getProtocols(boolean secure)
return secure ? protocols : h2c;
}
+ @Override
+ public TransportProtocol newTransportProtocol()
+ {
+ return TransportProtocol.TCP_IP;
+ }
+
@Override
public void upgrade(EndPoint endPoint, Map context)
{
diff --git a/jetty-core/jetty-http2/jetty-http2-client-transport/src/main/java/org/eclipse/jetty/http2/client/transport/HttpClientTransportOverHTTP2.java b/jetty-core/jetty-http2/jetty-http2-client-transport/src/main/java/org/eclipse/jetty/http2/client/transport/HttpClientTransportOverHTTP2.java
index f2394adb02f4..0b0e3fc27c67 100644
--- a/jetty-core/jetty-http2/jetty-http2-client-transport/src/main/java/org/eclipse/jetty/http2/client/transport/HttpClientTransportOverHTTP2.java
+++ b/jetty-core/jetty-http2/jetty-http2-client-transport/src/main/java/org/eclipse/jetty/http2/client/transport/HttpClientTransportOverHTTP2.java
@@ -46,13 +46,13 @@
public class HttpClientTransportOverHTTP2 extends AbstractHttpClientTransport
{
private final ClientConnectionFactory connectionFactory = new HTTP2ClientConnectionFactory();
- private final HTTP2Client client;
+ private final HTTP2Client http2Client;
private boolean useALPN = true;
- public HttpClientTransportOverHTTP2(HTTP2Client client)
+ public HttpClientTransportOverHTTP2(HTTP2Client http2Client)
{
- this.client = client;
- addBean(client.getClientConnector(), false);
+ this.http2Client = http2Client;
+ addBean(http2Client);
setConnectionPoolFactory(destination ->
{
HttpClient httpClient = getHttpClient();
@@ -62,13 +62,13 @@ public HttpClientTransportOverHTTP2(HTTP2Client client)
public HTTP2Client getHTTP2Client()
{
- return client;
+ return http2Client;
}
@ManagedAttribute(value = "The number of selectors", readonly = true)
public int getSelectors()
{
- return client.getSelectors();
+ return http2Client.getSelectors();
}
@ManagedAttribute(value = "Whether ALPN should be used when establishing connections")
@@ -85,31 +85,23 @@ public void setUseALPN(boolean useALPN)
@Override
protected void doStart() throws Exception
{
- if (!client.isStarted())
+ if (!http2Client.isStarted())
{
HttpClient httpClient = getHttpClient();
- client.setExecutor(httpClient.getExecutor());
- client.setScheduler(httpClient.getScheduler());
- client.setByteBufferPool(httpClient.getByteBufferPool());
- client.setConnectTimeout(httpClient.getConnectTimeout());
- client.setIdleTimeout(httpClient.getIdleTimeout());
- client.setInputBufferSize(httpClient.getResponseBufferSize());
- client.setUseInputDirectByteBuffers(httpClient.isUseInputDirectByteBuffers());
- client.setUseOutputDirectByteBuffers(httpClient.isUseOutputDirectByteBuffers());
- client.setConnectBlocking(httpClient.isConnectBlocking());
- client.setBindAddress(httpClient.getBindAddress());
+ http2Client.setExecutor(httpClient.getExecutor());
+ http2Client.setScheduler(httpClient.getScheduler());
+ http2Client.setByteBufferPool(httpClient.getByteBufferPool());
+ http2Client.setConnectTimeout(httpClient.getConnectTimeout());
+ http2Client.setIdleTimeout(httpClient.getIdleTimeout());
+ http2Client.setInputBufferSize(httpClient.getResponseBufferSize());
+ http2Client.setUseInputDirectByteBuffers(httpClient.isUseInputDirectByteBuffers());
+ http2Client.setUseOutputDirectByteBuffers(httpClient.isUseOutputDirectByteBuffers());
+ http2Client.setConnectBlocking(httpClient.isConnectBlocking());
+ http2Client.setBindAddress(httpClient.getBindAddress());
}
- addBean(client);
super.doStart();
}
- @Override
- protected void doStop() throws Exception
- {
- super.doStop();
- removeBean(client);
- }
-
@Override
public Origin newOrigin(Request request)
{
@@ -120,20 +112,13 @@ public Origin newOrigin(Request request)
@Override
public Destination newDestination(Origin origin)
{
- SocketAddress address = origin.getAddress().getSocketAddress();
- return new HttpDestination(getHttpClient(), origin, getHTTP2Client().getClientConnector().isIntrinsicallySecure(address));
+ return new HttpDestination(getHttpClient(), origin);
}
@Override
public void connect(SocketAddress address, Map context)
{
- HttpClient httpClient = getHttpClient();
- client.setConnectTimeout(httpClient.getConnectTimeout());
- client.setConnectBlocking(httpClient.isConnectBlocking());
- client.setBindAddress(httpClient.getBindAddress());
-
SessionListenerPromise listenerPromise = new SessionListenerPromise(context);
-
HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
connect(address, destination.getClientConnectionFactory(), listenerPromise, listenerPromise, context);
}
@@ -146,7 +131,8 @@ public void connect(InetSocketAddress address, Map context)
protected void connect(SocketAddress address, ClientConnectionFactory factory, Session.Listener listener, Promise promise, Map context)
{
- getHTTP2Client().connect(address, factory, listener, promise, context);
+ HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
+ getHTTP2Client().connect(destination.getOrigin().getTransportProtocol(), address, factory, listener, promise, context);
}
protected void connect(InetSocketAddress address, ClientConnectionFactory factory, Session.Listener listener, Promise promise, Map context)
@@ -164,7 +150,7 @@ public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map connect(SocketAddress address, Session.Listener listener)
{
- return connect(null, address, listener);
+ return Promise.Completable.with(p -> connect(address, listener, p));
}
public void connect(SocketAddress address, Session.Listener listener, Promise promise)
@@ -423,16 +424,32 @@ public void connect(SslContextFactory.Client sslContextFactory, SocketAddress ad
}
public void connect(SslContextFactory.Client sslContextFactory, SocketAddress address, Session.Listener listener, Promise promise, Map context)
+ {
+ connect(TransportProtocol.TCP_IP, sslContextFactory, address, listener, promise, context);
+ }
+
+ public CompletableFuture connect(TransportProtocol transportProtocol, SslContextFactory.Client sslContextFactory, SocketAddress address, Session.Listener listener)
+ {
+ return Promise.Completable.with(p -> connect(transportProtocol, sslContextFactory, address, listener, p, null));
+ }
+
+ public void connect(TransportProtocol transportProtocol, SslContextFactory.Client sslContextFactory, SocketAddress address, Session.Listener listener, Promise promise, Map context)
{
ClientConnectionFactory factory = newClientConnectionFactory(sslContextFactory);
- connect(address, factory, listener, promise, context);
+ connect(transportProtocol, address, factory, listener, promise, context);
}
public void connect(SocketAddress address, ClientConnectionFactory factory, Session.Listener listener, Promise promise, Map context)
+ {
+ connect(TransportProtocol.TCP_IP, address, factory, listener, promise, context);
+ }
+
+ public void connect(TransportProtocol transportProtocol, SocketAddress address, ClientConnectionFactory factory, Session.Listener listener, Promise promise, Map context)
{
context = contextFrom(factory, listener, promise, context);
+ context.put(TransportProtocol.class.getName(), transportProtocol);
context.put(ClientConnector.CONNECTION_PROMISE_CONTEXT_KEY, Promise.from(ioConnection -> {}, promise::failed));
- connector.connect(address, context);
+ transportProtocol.connect(address, context);
}
public void accept(SslContextFactory.Client sslContextFactory, SocketChannel channel, Session.Listener listener, Promise promise)
@@ -442,8 +459,14 @@ public void accept(SslContextFactory.Client sslContextFactory, SocketChannel cha
}
public void accept(SocketChannel channel, ClientConnectionFactory factory, Session.Listener listener, Promise promise)
+ {
+ accept(TransportProtocol.TCP_IP, channel, factory, listener, promise);
+ }
+
+ public void accept(TransportProtocol transportProtocol, SocketChannel channel, ClientConnectionFactory factory, Session.Listener listener, Promise promise)
{
Map context = contextFrom(factory, listener, promise, null);
+ context.put(TransportProtocol.class.getName(), transportProtocol);
context.put(ClientConnector.CONNECTION_PROMISE_CONTEXT_KEY, Promise.from(ioConnection -> {}, promise::failed));
connector.accept(channel, context);
}
@@ -452,6 +475,7 @@ private Map contextFrom(ClientConnectionFactory factory, Session
{
if (context == null)
context = new ConcurrentHashMap<>();
+ context.put(ClientConnector.CLIENT_CONNECTOR_CONTEXT_KEY, connector);
context.put(HTTP2ClientConnectionFactory.CLIENT_CONTEXT_KEY, this);
context.put(HTTP2ClientConnectionFactory.SESSION_LISTENER_CONTEXT_KEY, listener);
context.put(HTTP2ClientConnectionFactory.SESSION_PROMISE_CONTEXT_KEY, promise);
diff --git a/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2CServerConnectionFactory.java b/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2CServerConnectionFactory.java
index e0ee0f48b89b..1a7bab78f2df 100644
--- a/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2CServerConnectionFactory.java
+++ b/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2CServerConnectionFactory.java
@@ -44,6 +44,11 @@ public class HTTP2CServerConnectionFactory extends HTTP2ServerConnectionFactory
{
private static final Logger LOG = LoggerFactory.getLogger(HTTP2CServerConnectionFactory.class);
+ public HTTP2CServerConnectionFactory()
+ {
+ this(new HttpConfiguration());
+ }
+
public HTTP2CServerConnectionFactory(@Name("config") HttpConfiguration httpConfiguration)
{
this(httpConfiguration, "h2c");
diff --git a/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnectionFactory.java b/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnectionFactory.java
index 0bb7517c9ec7..ba2b27b520b4 100644
--- a/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnectionFactory.java
+++ b/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnectionFactory.java
@@ -44,6 +44,11 @@ public class HTTP2ServerConnectionFactory extends AbstractHTTP2ServerConnectionF
{
private static final Logger LOG = LoggerFactory.getLogger(HTTP2ServerConnectionFactory.class);
+ public HTTP2ServerConnectionFactory()
+ {
+ this(new HttpConfiguration());
+ }
+
public HTTP2ServerConnectionFactory(@Name("config") HttpConfiguration httpConfiguration)
{
super(httpConfiguration);
diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/HTTP2ServerTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/HTTP2ServerTest.java
index e191ac642a41..ec30896c9adf 100644
--- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/HTTP2ServerTest.java
+++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/HTTP2ServerTest.java
@@ -108,7 +108,7 @@ public void onGoAway(GoAwayFrame frame)
parseResponse(client, parser);
- assertTrue(latch.await(555, TimeUnit.SECONDS));
+ assertTrue(latch.await(5, TimeUnit.SECONDS));
}
}
diff --git a/jetty-core/jetty-http3/jetty-http3-client-transport/src/main/java/org/eclipse/jetty/http3/client/transport/ClientConnectionFactoryOverHTTP3.java b/jetty-core/jetty-http3/jetty-http3-client-transport/src/main/java/org/eclipse/jetty/http3/client/transport/ClientConnectionFactoryOverHTTP3.java
index bfcdfc3efc11..a71c5960079e 100644
--- a/jetty-core/jetty-http3/jetty-http3-client-transport/src/main/java/org/eclipse/jetty/http3/client/transport/ClientConnectionFactoryOverHTTP3.java
+++ b/jetty-core/jetty-http3/jetty-http3-client-transport/src/main/java/org/eclipse/jetty/http3/client/transport/ClientConnectionFactoryOverHTTP3.java
@@ -23,6 +23,8 @@
import org.eclipse.jetty.http3.client.transport.internal.SessionClientListener;
import org.eclipse.jetty.io.ClientConnectionFactory;
import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.TransportProtocol;
+import org.eclipse.jetty.quic.client.QuicTransportProtocol;
import org.eclipse.jetty.quic.common.ProtocolSession;
import org.eclipse.jetty.quic.common.QuicSession;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
@@ -30,12 +32,10 @@
public class ClientConnectionFactoryOverHTTP3 extends ContainerLifeCycle implements ClientConnectionFactory
{
private final HTTP3ClientConnectionFactory factory = new HTTP3ClientConnectionFactory();
- private final HTTP3Client client;
- public ClientConnectionFactoryOverHTTP3(HTTP3Client client)
+ public ClientConnectionFactoryOverHTTP3(HTTP3Client http3Client)
{
- this.client = client;
- addBean(client);
+ addBean(http3Client);
}
@Override
@@ -51,22 +51,38 @@ public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map protocols = List.of("h3");
+
+ private final HTTP3Client http3Client;
+
public HTTP3(HTTP3Client client)
{
super(new ClientConnectionFactoryOverHTTP3(client));
+ http3Client = client;
+ }
+
+ public HTTP3Client getHTTP3Client()
+ {
+ return http3Client;
}
@Override
public List getProtocols(boolean secure)
{
- return List.of("h3");
+ return protocols;
+ }
+
+ @Override
+ public TransportProtocol newTransportProtocol()
+ {
+ return new QuicTransportProtocol(getHTTP3Client().getQuicConfiguration());
}
@Override
public ProtocolSession newProtocolSession(QuicSession quicSession, Map context)
{
ClientConnectionFactoryOverHTTP3 http3 = (ClientConnectionFactoryOverHTTP3)getClientConnectionFactory();
- context.put(HTTP3Client.CLIENT_CONTEXT_KEY, http3.client);
+ context.put(HTTP3Client.CLIENT_CONTEXT_KEY, http3Client);
SessionClientListener listener = new SessionClientListener(context);
context.put(HTTP3Client.SESSION_LISTENER_CONTEXT_KEY, listener);
return http3.factory.newProtocolSession(quicSession, context);
diff --git a/jetty-core/jetty-http3/jetty-http3-client-transport/src/main/java/org/eclipse/jetty/http3/client/transport/HttpClientTransportOverHTTP3.java b/jetty-core/jetty-http3/jetty-http3-client-transport/src/main/java/org/eclipse/jetty/http3/client/transport/HttpClientTransportOverHTTP3.java
index 0423204ff7ec..1b3390ee144a 100644
--- a/jetty-core/jetty-http3/jetty-http3-client-transport/src/main/java/org/eclipse/jetty/http3/client/transport/HttpClientTransportOverHTTP3.java
+++ b/jetty-core/jetty-http3/jetty-http3-client-transport/src/main/java/org/eclipse/jetty/http3/client/transport/HttpClientTransportOverHTTP3.java
@@ -36,18 +36,20 @@
import org.eclipse.jetty.http3.client.transport.internal.SessionClientListener;
import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.TransportProtocol;
+import org.eclipse.jetty.quic.client.QuicTransportProtocol;
import org.eclipse.jetty.quic.common.ProtocolSession;
import org.eclipse.jetty.quic.common.QuicSession;
public class HttpClientTransportOverHTTP3 extends AbstractHttpClientTransport implements ProtocolSession.Factory
{
private final HTTP3ClientConnectionFactory factory = new HTTP3ClientConnectionFactory();
- private final HTTP3Client client;
+ private final HTTP3Client http3Client;
- public HttpClientTransportOverHTTP3(HTTP3Client client)
+ public HttpClientTransportOverHTTP3(HTTP3Client http3Client)
{
- this.client = Objects.requireNonNull(client);
- addBean(client);
+ this.http3Client = Objects.requireNonNull(http3Client);
+ addBean(http3Client);
setConnectionPoolFactory(destination ->
{
HttpClient httpClient = getHttpClient();
@@ -57,16 +59,16 @@ public HttpClientTransportOverHTTP3(HTTP3Client client)
public HTTP3Client getHTTP3Client()
{
- return client;
+ return http3Client;
}
@Override
protected void doStart() throws Exception
{
- if (!client.isStarted())
+ if (!http3Client.isStarted())
{
HttpClient httpClient = getHttpClient();
- ClientConnector clientConnector = this.client.getClientConnector();
+ ClientConnector clientConnector = this.http3Client.getClientConnector();
clientConnector.setExecutor(httpClient.getExecutor());
clientConnector.setScheduler(httpClient.getScheduler());
clientConnector.setByteBufferPool(httpClient.getByteBufferPool());
@@ -74,7 +76,7 @@ protected void doStart() throws Exception
clientConnector.setConnectBlocking(httpClient.isConnectBlocking());
clientConnector.setBindAddress(httpClient.getBindAddress());
clientConnector.setIdleTimeout(Duration.ofMillis(httpClient.getIdleTimeout()));
- HTTP3Configuration configuration = client.getHTTP3Configuration();
+ HTTP3Configuration configuration = http3Client.getHTTP3Configuration();
configuration.setInputBufferSize(httpClient.getResponseBufferSize());
configuration.setUseInputDirectByteBuffers(httpClient.isUseInputDirectByteBuffers());
configuration.setUseOutputDirectByteBuffers(httpClient.isUseOutputDirectByteBuffers());
@@ -85,14 +87,16 @@ protected void doStart() throws Exception
@Override
public Origin newOrigin(Request request)
{
+ TransportProtocol transportProtocol = request.getTransportProtocol();
+ if (transportProtocol == null)
+ request.transportProtocol(new QuicTransportProtocol(http3Client.getQuicConfiguration()));
return getHttpClient().createOrigin(request, new Origin.Protocol(List.of("h3"), false));
}
@Override
public Destination newDestination(Origin origin)
{
- SocketAddress address = origin.getAddress().getSocketAddress();
- return new HttpDestination(getHttpClient(), origin, getHTTP3Client().getClientConnector().isIntrinsicallySecure(address));
+ return new HttpDestination(getHttpClient(), origin);
}
@Override
@@ -104,17 +108,11 @@ public void connect(InetSocketAddress address, Map context)
@Override
public void connect(SocketAddress address, Map context)
{
- HttpClient httpClient = getHttpClient();
- ClientConnector clientConnector = client.getClientConnector();
- clientConnector.setConnectTimeout(Duration.ofMillis(httpClient.getConnectTimeout()));
- clientConnector.setConnectBlocking(httpClient.isConnectBlocking());
- clientConnector.setBindAddress(httpClient.getBindAddress());
-
HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
context.put(ClientConnector.CLIENT_CONNECTION_FACTORY_CONTEXT_KEY, destination.getClientConnectionFactory());
SessionClientListener listener = new TransportSessionClientListener(context);
- getHTTP3Client().connect(address, listener, context)
+ getHTTP3Client().connect(destination.getOrigin().getTransportProtocol(), address, listener, context)
.whenComplete(listener::onConnect);
}
diff --git a/jetty-core/jetty-http3/jetty-http3-client/src/main/java/org/eclipse/jetty/http3/client/HTTP3Client.java b/jetty-core/jetty-http3/jetty-http3-client/src/main/java/org/eclipse/jetty/http3/client/HTTP3Client.java
index e0debf261133..76ed05eb81d1 100644
--- a/jetty-core/jetty-http3/jetty-http3-client/src/main/java/org/eclipse/jetty/http3/client/HTTP3Client.java
+++ b/jetty-core/jetty-http3/jetty-http3-client/src/main/java/org/eclipse/jetty/http3/client/HTTP3Client.java
@@ -22,13 +22,12 @@
import org.eclipse.jetty.http3.HTTP3Configuration;
import org.eclipse.jetty.http3.api.Session;
import org.eclipse.jetty.io.ClientConnector;
-import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.DatagramChannelEndPoint;
+import org.eclipse.jetty.io.TransportProtocol;
+import org.eclipse.jetty.quic.client.ClientQuicConfiguration;
import org.eclipse.jetty.quic.client.ClientQuicConnection;
import org.eclipse.jetty.quic.client.ClientQuicSession;
-import org.eclipse.jetty.quic.client.QuicClientConnectorConfigurator;
-import org.eclipse.jetty.quic.common.QuicConfiguration;
-import org.eclipse.jetty.quic.common.QuicConnection;
+import org.eclipse.jetty.quic.client.QuicTransportProtocol;
import org.eclipse.jetty.quic.common.QuicSessionContainer;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
@@ -134,20 +133,22 @@ public class HTTP3Client extends ContainerLifeCycle
public static final String SESSION_PROMISE_CONTEXT_KEY = CLIENT_CONTEXT_KEY + ".promise";
private static final Logger LOG = LoggerFactory.getLogger(HTTP3Client.class);
- private final HTTP3Configuration http3Configuration = new HTTP3Configuration();
private final QuicSessionContainer container = new QuicSessionContainer();
+ private final HTTP3Configuration http3Configuration = new HTTP3Configuration();
+ private final ClientQuicConfiguration quicConfiguration;
private final ClientConnector connector;
- private final QuicConfiguration quicConfiguration;
- public HTTP3Client()
+ public HTTP3Client(ClientQuicConfiguration quicConfiguration)
+ {
+ this(quicConfiguration, new ClientConnector());
+ }
+
+ public HTTP3Client(ClientQuicConfiguration quicConfiguration, ClientConnector connector)
{
- QuicClientConnectorConfigurator configurator = new QuicClientConnectorConfigurator(this::configureConnection);
- this.connector = new ClientConnector(configurator);
- this.quicConfiguration = configurator.getQuicConfiguration();
+ this.quicConfiguration = quicConfiguration;
+ this.connector = connector;
addBean(connector);
- addBean(quicConfiguration);
- addBean(http3Configuration);
- addBean(container);
+ connector.setSslContextFactory(quicConfiguration.getSslContextFactory());
// Allow the mandatory unidirectional streams, plus pushed streams.
quicConfiguration.setMaxUnidirectionalRemoteStreams(48);
quicConfiguration.setUnidirectionalStreamRecvWindow(4 * 1024 * 1024);
@@ -159,7 +160,7 @@ public ClientConnector getClientConnector()
return connector;
}
- public QuicConfiguration getQuicConfiguration()
+ public ClientQuicConfiguration getQuicConfiguration()
{
return quicConfiguration;
}
@@ -173,45 +174,46 @@ public HTTP3Configuration getHTTP3Configuration()
protected void doStart() throws Exception
{
LOG.info("HTTP/3+QUIC support is experimental and not suited for production use.");
+ addBean(quicConfiguration);
+ addBean(container);
+ addBean(http3Configuration);
+ quicConfiguration.addEventListener(container);
super.doStart();
}
- public CompletableFuture connect(SocketAddress address, Session.Client.Listener listener)
+ public CompletableFuture connect(SocketAddress socketAddress, Session.Client.Listener listener)
{
Map context = new ConcurrentHashMap<>();
- return connect(address, listener, context);
+ return connect(socketAddress, listener, context);
+ }
+
+ public CompletableFuture connect(SocketAddress socketAddress, Session.Client.Listener listener, Map context)
+ {
+ if (context == null)
+ context = new ConcurrentHashMap<>();
+ return connect(new QuicTransportProtocol(getQuicConfiguration()), socketAddress, listener, context);
}
- public CompletableFuture connect(SocketAddress address, Session.Client.Listener listener, Map context)
+ public CompletableFuture connect(TransportProtocol transportProtocol, SocketAddress socketAddress, Session.Client.Listener listener, Map context)
{
+ if (context == null)
+ context = new ConcurrentHashMap<>();
Promise.Completable completable = new Promise.Completable<>();
context.put(CLIENT_CONTEXT_KEY, this);
context.put(SESSION_LISTENER_CONTEXT_KEY, listener);
context.put(SESSION_PROMISE_CONTEXT_KEY, completable);
+ context.putIfAbsent(ClientConnector.CLIENT_CONNECTOR_CONTEXT_KEY, connector);
context.computeIfAbsent(ClientConnector.CLIENT_CONNECTION_FACTORY_CONTEXT_KEY, key -> new HTTP3ClientConnectionFactory());
context.put(ClientConnector.CONNECTION_PROMISE_CONTEXT_KEY, Promise.from(ioConnection -> {}, completable::failed));
+ context.put(TransportProtocol.class.getName(), transportProtocol);
if (LOG.isDebugEnabled())
- LOG.debug("connecting to {}", address);
+ LOG.debug("connecting to {}", socketAddress);
- connector.connect(address, context);
+ transportProtocol.connect(socketAddress, context);
return completable;
}
- private Connection configureConnection(Connection connection)
- {
- if (connection instanceof QuicConnection)
- {
- QuicConnection quicConnection = (QuicConnection)connection;
- quicConnection.addEventListener(container);
- quicConnection.setInputBufferSize(getHTTP3Configuration().getInputBufferSize());
- quicConnection.setOutputBufferSize(getHTTP3Configuration().getOutputBufferSize());
- quicConnection.setUseInputDirectByteBuffers(getHTTP3Configuration().isUseInputDirectByteBuffers());
- quicConnection.setUseOutputDirectByteBuffers(getHTTP3Configuration().isUseOutputDirectByteBuffers());
- }
- return connection;
- }
-
public CompletableFuture shutdown()
{
return container.shutdown();
diff --git a/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/AbstractHTTP3ServerConnectionFactory.java b/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/AbstractHTTP3ServerConnectionFactory.java
index 7e1882a03bce..ff27435a561b 100644
--- a/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/AbstractHTTP3ServerConnectionFactory.java
+++ b/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/AbstractHTTP3ServerConnectionFactory.java
@@ -26,6 +26,7 @@
import org.eclipse.jetty.quic.common.ProtocolSession;
import org.eclipse.jetty.quic.common.QuicSession;
import org.eclipse.jetty.quic.common.QuicStreamEndPoint;
+import org.eclipse.jetty.quic.server.ServerQuicConfiguration;
import org.eclipse.jetty.quic.server.ServerQuicSession;
import org.eclipse.jetty.server.AbstractConnectionFactory;
import org.eclipse.jetty.server.Connector;
@@ -33,21 +34,31 @@
public abstract class AbstractHTTP3ServerConnectionFactory extends AbstractConnectionFactory implements ProtocolSession.Factory
{
- private final HTTP3Configuration configuration = new HTTP3Configuration();
+ private final HTTP3Configuration http3Configuration = new HTTP3Configuration();
+ private final ServerQuicConfiguration quicConfiguration;
private final HttpConfiguration httpConfiguration;
private final Session.Server.Listener listener;
- public AbstractHTTP3ServerConnectionFactory(HttpConfiguration httpConfiguration, Session.Server.Listener listener)
+ public AbstractHTTP3ServerConnectionFactory(ServerQuicConfiguration quicConfiguration, HttpConfiguration httpConfiguration, Session.Server.Listener listener)
{
super("h3");
- addBean(configuration);
+ this.quicConfiguration = Objects.requireNonNull(quicConfiguration);
this.httpConfiguration = Objects.requireNonNull(httpConfiguration);
- addBean(httpConfiguration);
this.listener = listener;
- configuration.setUseInputDirectByteBuffers(httpConfiguration.isUseInputDirectByteBuffers());
- configuration.setUseOutputDirectByteBuffers(httpConfiguration.isUseOutputDirectByteBuffers());
- configuration.setMaxRequestHeadersSize(httpConfiguration.getRequestHeaderSize());
- configuration.setMaxResponseHeadersSize(httpConfiguration.getResponseHeaderSize());
+ // Max concurrent streams that a client can open.
+ quicConfiguration.setMaxBidirectionalRemoteStreams(128);
+ // HTTP/3 requires a few mandatory unidirectional streams.
+ quicConfiguration.setMaxUnidirectionalRemoteStreams(8);
+ quicConfiguration.setUnidirectionalStreamRecvWindow(1024 * 1024);
+ http3Configuration.setUseInputDirectByteBuffers(httpConfiguration.isUseInputDirectByteBuffers());
+ http3Configuration.setUseOutputDirectByteBuffers(httpConfiguration.isUseOutputDirectByteBuffers());
+ http3Configuration.setMaxRequestHeadersSize(httpConfiguration.getRequestHeaderSize());
+ http3Configuration.setMaxResponseHeadersSize(httpConfiguration.getResponseHeaderSize());
+ }
+
+ public ServerQuicConfiguration getQuicConfiguration()
+ {
+ return quicConfiguration;
}
public HttpConfiguration getHttpConfiguration()
@@ -57,7 +68,16 @@ public HttpConfiguration getHttpConfiguration()
public HTTP3Configuration getHTTP3Configuration()
{
- return configuration;
+ return http3Configuration;
+ }
+
+ @Override
+ protected void doStart() throws Exception
+ {
+ addBean(quicConfiguration);
+ addBean(http3Configuration);
+ addBean(httpConfiguration);
+ super.doStart();
}
@Override
diff --git a/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/HTTP3ServerConnectionFactory.java b/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/HTTP3ServerConnectionFactory.java
index 42256031e64f..fde8bb5dbfbe 100644
--- a/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/HTTP3ServerConnectionFactory.java
+++ b/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/HTTP3ServerConnectionFactory.java
@@ -16,7 +16,8 @@
import java.util.Objects;
import java.util.concurrent.TimeoutException;
-import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http3.HTTP3Stream;
import org.eclipse.jetty.http3.api.Session;
@@ -27,34 +28,40 @@
import org.eclipse.jetty.http3.server.internal.ServerHTTP3Session;
import org.eclipse.jetty.http3.server.internal.ServerHTTP3StreamConnection;
import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.quic.server.ServerQuicConfiguration;
import org.eclipse.jetty.server.ConnectionMetaData;
+import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.NetworkConnector;
+import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.util.Promise;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class HTTP3ServerConnectionFactory extends AbstractHTTP3ServerConnectionFactory
{
- public HTTP3ServerConnectionFactory()
+ public HTTP3ServerConnectionFactory(ServerQuicConfiguration quicConfiguration)
{
- this(new HttpConfiguration());
+ this(quicConfiguration, new HttpConfiguration());
}
- public HTTP3ServerConnectionFactory(HttpConfiguration configuration)
+ public HTTP3ServerConnectionFactory(ServerQuicConfiguration quicConfiguration, HttpConfiguration configuration)
{
- super(configuration, new HTTP3SessionListener());
- configuration.addCustomizer((request, responseHeaders) ->
+ super(quicConfiguration, configuration, new HTTP3SessionListener());
+ configuration.addCustomizer(new AltSvcCustomizer());
+ }
+
+ private static class AltSvcCustomizer implements HttpConfiguration.Customizer
+ {
+ @Override
+ public Request customize(Request request, HttpFields.Mutable responseHeaders)
{
ConnectionMetaData connectionMetaData = request.getConnectionMetaData();
- HTTP3ServerConnector http3Connector = connectionMetaData.getConnector().getServer().getBean(HTTP3ServerConnector.class);
- if (http3Connector != null && HttpVersion.HTTP_2 == connectionMetaData.getHttpVersion())
- {
- HttpField altSvc = http3Connector.getAltSvcHttpField();
- if (altSvc != null)
- responseHeaders.add(altSvc);
- }
+ Connector connector = connectionMetaData.getConnector();
+ if (connector instanceof NetworkConnector networkConnector && HttpVersion.HTTP_2 == connectionMetaData.getHttpVersion())
+ responseHeaders.add(HttpHeader.ALT_SVC, String.format("h3=\":%d\"", networkConnector.getLocalPort()));
return request;
- });
+ }
}
private static class HTTP3SessionListener implements Session.Server.Listener
diff --git a/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/HTTP3ServerConnector.java b/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/HTTP3ServerConnector.java
index 0b50ef95a841..96076ceae5db 100644
--- a/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/HTTP3ServerConnector.java
+++ b/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/HTTP3ServerConnector.java
@@ -13,6 +13,7 @@
package org.eclipse.jetty.http3.server;
+import java.util.Arrays;
import java.util.concurrent.Executor;
import org.eclipse.jetty.http.HttpField;
@@ -20,6 +21,7 @@
import org.eclipse.jetty.http.PreEncodedHttpField;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.quic.server.QuicServerConnector;
+import org.eclipse.jetty.quic.server.ServerQuicConfiguration;
import org.eclipse.jetty.server.ConnectionFactory;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.ssl.SslContextFactory;
@@ -32,7 +34,10 @@
*
* HTTP/3+QUIC support is experimental and not suited for production use.
* APIs may change incompatibly between releases.
+ *
+ * @deprecated use {@link QuicServerConnector} instead
*/
+@Deprecated(since = "12.0.7", forRemoval = true)
public class HTTP3ServerConnector extends QuicServerConnector
{
private static final Logger LOG = LoggerFactory.getLogger(HTTP3ServerConnector.class);
@@ -46,12 +51,17 @@ public HTTP3ServerConnector(Server server, SslContextFactory.Server sslContextFa
public HTTP3ServerConnector(Server server, Executor executor, Scheduler scheduler, ByteBufferPool bufferPool, SslContextFactory.Server sslContextFactory, ConnectionFactory... factories)
{
- super(server, executor, scheduler, bufferPool, sslContextFactory, factories);
- // Max concurrent streams that a client can open.
- getQuicConfiguration().setMaxBidirectionalRemoteStreams(128);
- // HTTP/3 requires a few mandatory unidirectional streams.
- getQuicConfiguration().setMaxUnidirectionalRemoteStreams(8);
- getQuicConfiguration().setUnidirectionalStreamRecvWindow(1024 * 1024);
+ super(server, executor, scheduler, bufferPool, extractServerQuicConfiguration(factories), factories);
+ }
+
+ private static ServerQuicConfiguration extractServerQuicConfiguration(ConnectionFactory[] factories)
+ {
+ return Arrays.stream(factories)
+ .filter(factory -> factory instanceof AbstractHTTP3ServerConnectionFactory)
+ .map(AbstractHTTP3ServerConnectionFactory.class::cast)
+ .map(AbstractHTTP3ServerConnectionFactory::getQuicConfiguration)
+ .findAny()
+ .orElseThrow(() -> new IllegalArgumentException("Missing HTTP/3 ConnectionFactory"));
}
@Override
diff --git a/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/RawHTTP3ServerConnectionFactory.java b/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/RawHTTP3ServerConnectionFactory.java
index 5774814ac3ad..d6dc4cb40b47 100644
--- a/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/RawHTTP3ServerConnectionFactory.java
+++ b/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/RawHTTP3ServerConnectionFactory.java
@@ -14,17 +14,18 @@
package org.eclipse.jetty.http3.server;
import org.eclipse.jetty.http3.api.Session;
+import org.eclipse.jetty.quic.server.ServerQuicConfiguration;
import org.eclipse.jetty.server.HttpConfiguration;
public class RawHTTP3ServerConnectionFactory extends AbstractHTTP3ServerConnectionFactory
{
- public RawHTTP3ServerConnectionFactory(Session.Server.Listener listener)
+ public RawHTTP3ServerConnectionFactory(ServerQuicConfiguration quicConfiguration, Session.Server.Listener listener)
{
- this(new HttpConfiguration(), listener);
+ this(quicConfiguration, new HttpConfiguration(), listener);
}
- public RawHTTP3ServerConnectionFactory(HttpConfiguration httpConfiguration, Session.Server.Listener listener)
+ public RawHTTP3ServerConnectionFactory(ServerQuicConfiguration quicConfiguration, HttpConfiguration httpConfiguration, Session.Server.Listener listener)
{
- super(httpConfiguration, listener);
+ super(quicConfiguration, httpConfiguration, listener);
}
}
diff --git a/jetty-core/jetty-http3/jetty-http3-tests/src/test/java/org/eclipse/jetty/http3/tests/AbstractClientServerTest.java b/jetty-core/jetty-http3/jetty-http3-tests/src/test/java/org/eclipse/jetty/http3/tests/AbstractClientServerTest.java
index 188169ba5730..05c45c4f3c82 100644
--- a/jetty-core/jetty-http3/jetty-http3-tests/src/test/java/org/eclipse/jetty/http3/tests/AbstractClientServerTest.java
+++ b/jetty-core/jetty-http3/jetty-http3-tests/src/test/java/org/eclipse/jetty/http3/tests/AbstractClientServerTest.java
@@ -13,10 +13,8 @@
package org.eclipse.jetty.http3.tests;
-import java.io.InputStream;
import java.lang.management.ManagementFactory;
import java.net.InetSocketAddress;
-import java.security.KeyStore;
import java.util.concurrent.TimeUnit;
import javax.management.MBeanServer;
@@ -31,9 +29,12 @@
import org.eclipse.jetty.http3.client.HTTP3Client;
import org.eclipse.jetty.http3.client.transport.ClientConnectionFactoryOverHTTP3;
import org.eclipse.jetty.http3.server.HTTP3ServerConnectionFactory;
-import org.eclipse.jetty.http3.server.HTTP3ServerConnector;
import org.eclipse.jetty.http3.server.RawHTTP3ServerConnectionFactory;
+import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.jmx.MBeanContainer;
+import org.eclipse.jetty.quic.client.ClientQuicConfiguration;
+import org.eclipse.jetty.quic.server.QuicServerConnector;
+import org.eclipse.jetty.quic.server.ServerQuicConfiguration;
import org.eclipse.jetty.server.ConnectionFactory;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
@@ -53,21 +54,30 @@ public class AbstractClientServerTest
public WorkDir workDir;
@RegisterExtension
- final BeforeTestExecutionCallback printMethodName = context ->
+ public final BeforeTestExecutionCallback printMethodName = context ->
System.err.printf("Running %s.%s() %s%n", context.getRequiredTestClass().getSimpleName(), context.getRequiredTestMethod().getName(), context.getDisplayName());
protected Server server;
- protected HTTP3ServerConnector connector;
+ protected QuicServerConnector connector;
protected HTTP3Client http3Client;
protected HttpClient httpClient;
protected void start(Handler handler) throws Exception
{
- prepareServer(new HTTP3ServerConnectionFactory());
+ ServerQuicConfiguration quicConfiguration = newServerQuicConfiguration();
+ prepareServer(quicConfiguration, new HTTP3ServerConnectionFactory(quicConfiguration));
server.setHandler(handler);
server.start();
startClient();
}
+ private ServerQuicConfiguration newServerQuicConfiguration()
+ {
+ SslContextFactory.Server sslServer = new SslContextFactory.Server();
+ sslServer.setKeyStorePath("src/test/resources/keystore.p12");
+ sslServer.setKeyStorePassword("storepwd");
+ return new ServerQuicConfiguration(sslServer, workDir.getEmptyPathDir());
+ }
+
protected void start(Session.Server.Listener listener) throws Exception
{
startServer(listener);
@@ -76,20 +86,17 @@ protected void start(Session.Server.Listener listener) throws Exception
protected void startServer(Session.Server.Listener listener) throws Exception
{
- prepareServer(new RawHTTP3ServerConnectionFactory(listener));
+ ServerQuicConfiguration quicConfiguration = newServerQuicConfiguration();
+ prepareServer(quicConfiguration, new RawHTTP3ServerConnectionFactory(quicConfiguration, listener));
server.start();
}
- private void prepareServer(ConnectionFactory serverConnectionFactory)
+ private void prepareServer(ServerQuicConfiguration quicConfiguration, ConnectionFactory serverConnectionFactory)
{
- SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
- sslContextFactory.setKeyStorePath("src/test/resources/keystore.p12");
- sslContextFactory.setKeyStorePassword("storepwd");
QueuedThreadPool serverThreads = new QueuedThreadPool();
serverThreads.setName("server");
server = new Server(serverThreads);
- connector = new HTTP3ServerConnector(server, sslContextFactory, serverConnectionFactory);
- connector.getQuicConfiguration().setPemWorkDirectory(workDir.getEmptyPathDir());
+ connector = new QuicServerConnector(server, quicConfiguration, serverConnectionFactory);
server.addConnector(connector);
MBeanContainer mbeanContainer = new MBeanContainer(ManagementFactory.getPlatformMBeanServer());
server.addBean(mbeanContainer);
@@ -97,20 +104,15 @@ private void prepareServer(ConnectionFactory serverConnectionFactory)
protected void startClient() throws Exception
{
- KeyStore trustStore = KeyStore.getInstance("PKCS12");
- try (InputStream is = getClass().getResourceAsStream("/keystore.p12"))
- {
- trustStore.load(is, "storepwd".toCharArray());
- }
-
- http3Client = new HTTP3Client();
- SslContextFactory.Client clientSslContextFactory = new SslContextFactory.Client();
- clientSslContextFactory.setTrustStore(trustStore);
- http3Client.getClientConnector().setSslContextFactory(clientSslContextFactory);
- httpClient = new HttpClient(new HttpClientTransportDynamic(new ClientConnectionFactoryOverHTTP3.HTTP3(http3Client)));
+ ClientConnector clientConnector = new ClientConnector();
QueuedThreadPool clientThreads = new QueuedThreadPool();
clientThreads.setName("client");
- httpClient.setExecutor(clientThreads);
+ clientConnector.setExecutor(clientThreads);
+ SslContextFactory.Client sslClient = new SslContextFactory.Client(true);
+ clientConnector.setSslContextFactory(sslClient);
+ ClientQuicConfiguration quicConfiguration = new ClientQuicConfiguration(sslClient, null);
+ http3Client = new HTTP3Client(quicConfiguration, clientConnector);
+ httpClient = new HttpClient(new HttpClientTransportDynamic(clientConnector, new ClientConnectionFactoryOverHTTP3.HTTP3(http3Client)));
MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer();
MBeanContainer mbeanContainer = new MBeanContainer(mbeanServer);
httpClient.addBean(mbeanContainer);
diff --git a/jetty-core/jetty-http3/jetty-http3-tests/src/test/java/org/eclipse/jetty/http3/tests/ExternalServerTest.java b/jetty-core/jetty-http3/jetty-http3-tests/src/test/java/org/eclipse/jetty/http3/tests/ExternalServerTest.java
index 894f9a1ddfe9..1c7cd9048bef 100644
--- a/jetty-core/jetty-http3/jetty-http3-tests/src/test/java/org/eclipse/jetty/http3/tests/ExternalServerTest.java
+++ b/jetty-core/jetty-http3/jetty-http3-tests/src/test/java/org/eclipse/jetty/http3/tests/ExternalServerTest.java
@@ -30,7 +30,9 @@
import org.eclipse.jetty.http3.client.HTTP3Client;
import org.eclipse.jetty.http3.client.transport.HttpClientTransportOverHTTP3;
import org.eclipse.jetty.http3.frames.HeadersFrame;
+import org.eclipse.jetty.quic.client.ClientQuicConfiguration;
import org.eclipse.jetty.util.HostPort;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
@@ -49,7 +51,9 @@ public class ExternalServerTest
@Tag("external")
public void testExternalServerWithHttpClient() throws Exception
{
- HTTP3Client client = new HTTP3Client();
+ SslContextFactory.Client sslClient = new SslContextFactory.Client();
+ ClientQuicConfiguration quicConfig = new ClientQuicConfiguration(sslClient, null);
+ HTTP3Client client = new HTTP3Client(quicConfig);
HttpClientTransportOverHTTP3 transport = new HttpClientTransportOverHTTP3(client);
HttpClient httpClient = new HttpClient(transport);
httpClient.start();
@@ -69,7 +73,9 @@ public void testExternalServerWithHttpClient() throws Exception
@Tag("external")
public void testExternalServerWithHTTP3Client() throws Exception
{
- HTTP3Client client = new HTTP3Client();
+ SslContextFactory.Client sslClient = new SslContextFactory.Client();
+ ClientQuicConfiguration quicConfig = new ClientQuicConfiguration(sslClient, null);
+ HTTP3Client client = new HTTP3Client(quicConfig);
client.start();
try
{
diff --git a/jetty-core/jetty-http3/jetty-http3-tests/src/test/java/org/eclipse/jetty/http3/tests/GoAwayTest.java b/jetty-core/jetty-http3/jetty-http3-tests/src/test/java/org/eclipse/jetty/http3/tests/GoAwayTest.java
index f62c616bd318..75b42dab098c 100644
--- a/jetty-core/jetty-http3/jetty-http3-tests/src/test/java/org/eclipse/jetty/http3/tests/GoAwayTest.java
+++ b/jetty-core/jetty-http3/jetty-http3-tests/src/test/java/org/eclipse/jetty/http3/tests/GoAwayTest.java
@@ -1049,7 +1049,7 @@ public void onFailure(Stream.Client stream, long error, Throwable failure)
// Client sends a graceful GOAWAY.
clientSession.goAway(true);
- assertTrue(serverGracefulGoAwayLatch.await(555, TimeUnit.SECONDS));
+ assertTrue(serverGracefulGoAwayLatch.await(5, TimeUnit.SECONDS));
assertTrue(streamFailureLatch.await(5, TimeUnit.SECONDS));
assertTrue(clientGoAwayLatch.await(2 * idleTimeout, TimeUnit.MILLISECONDS));
assertTrue(serverDisconnectLatch.await(5, TimeUnit.SECONDS));
diff --git a/jetty-core/jetty-http3/jetty-http3-tests/src/test/java/org/eclipse/jetty/http3/tests/HTTP3ServerConnectorTest.java b/jetty-core/jetty-http3/jetty-http3-tests/src/test/java/org/eclipse/jetty/http3/tests/HTTP3ServerConnectorTest.java
index 6c3e4d9a6863..ef4e695f045c 100644
--- a/jetty-core/jetty-http3/jetty-http3-tests/src/test/java/org/eclipse/jetty/http3/tests/HTTP3ServerConnectorTest.java
+++ b/jetty-core/jetty-http3/jetty-http3-tests/src/test/java/org/eclipse/jetty/http3/tests/HTTP3ServerConnectorTest.java
@@ -19,7 +19,7 @@
import org.eclipse.jetty.http3.server.HTTP3ServerConnectionFactory;
import org.eclipse.jetty.http3.server.HTTP3ServerConnector;
-import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.quic.server.ServerQuicConfiguration;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
@@ -40,8 +40,8 @@ public void testStartHTTP3ServerConnectorWithoutKeyStore()
{
Server server = new Server();
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
- HTTP3ServerConnector connector = new HTTP3ServerConnector(server, sslContextFactory, new HTTP3ServerConnectionFactory(new HttpConfiguration()));
- connector.getQuicConfiguration().setPemWorkDirectory(workDir.getEmptyPathDir());
+ ServerQuicConfiguration quicConfiguration = new ServerQuicConfiguration(sslContextFactory, workDir.getEmptyPathDir());
+ HTTP3ServerConnector connector = new HTTP3ServerConnector(server, sslContextFactory, new HTTP3ServerConnectionFactory(quicConfiguration));
server.addConnector(connector);
assertThrows(IllegalStateException.class, server::start);
}
@@ -52,8 +52,8 @@ public void testStartHTTP3ServerConnectorWithoutKeyStoreWithSSLContext() throws
Server server = new Server();
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
sslContextFactory.setSslContext(SSLContext.getDefault());
- HTTP3ServerConnector connector = new HTTP3ServerConnector(server, sslContextFactory, new HTTP3ServerConnectionFactory(new HttpConfiguration()));
- connector.getQuicConfiguration().setPemWorkDirectory(workDir.getEmptyPathDir());
+ ServerQuicConfiguration quicConfiguration = new ServerQuicConfiguration(sslContextFactory, workDir.getEmptyPathDir());
+ HTTP3ServerConnector connector = new HTTP3ServerConnector(server, sslContextFactory, new HTTP3ServerConnectionFactory(quicConfiguration));
server.addConnector(connector);
assertThrows(IllegalStateException.class, server::start);
}
@@ -66,8 +66,8 @@ public void testStartHTTP3ServerConnectorWithEmptyKeyStoreInstance() throws Exce
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(null, null);
sslContextFactory.setKeyStore(keyStore);
- HTTP3ServerConnector connector = new HTTP3ServerConnector(server, sslContextFactory, new HTTP3ServerConnectionFactory(new HttpConfiguration()));
- connector.getQuicConfiguration().setPemWorkDirectory(workDir.getEmptyPathDir());
+ ServerQuicConfiguration quicConfiguration = new ServerQuicConfiguration(sslContextFactory, workDir.getEmptyPathDir());
+ HTTP3ServerConnector connector = new HTTP3ServerConnector(server, sslContextFactory, new HTTP3ServerConnectionFactory(quicConfiguration));
server.addConnector(connector);
assertThrows(IllegalStateException.class, server::start);
}
@@ -84,7 +84,8 @@ public void testStartHTTP3ServerConnectorWithValidKeyStoreInstanceWithoutPemWork
}
sslContextFactory.setKeyStore(keyStore);
sslContextFactory.setKeyManagerPassword("storepwd");
- HTTP3ServerConnector connector = new HTTP3ServerConnector(server, sslContextFactory, new HTTP3ServerConnectionFactory(new HttpConfiguration()));
+ ServerQuicConfiguration quicConfiguration = new ServerQuicConfiguration(sslContextFactory, null);
+ HTTP3ServerConnector connector = new HTTP3ServerConnector(server, sslContextFactory, new HTTP3ServerConnectionFactory(quicConfiguration));
server.addConnector(connector);
assertThrows(IllegalStateException.class, server::start);
}
@@ -101,8 +102,8 @@ public void testStartHTTP3ServerConnectorWithValidKeyStoreInstance() throws Exce
}
sslContextFactory.setKeyStore(keyStore);
sslContextFactory.setKeyManagerPassword("storepwd");
- HTTP3ServerConnector connector = new HTTP3ServerConnector(server, sslContextFactory, new HTTP3ServerConnectionFactory(new HttpConfiguration()));
- connector.getQuicConfiguration().setPemWorkDirectory(workDir.getEmptyPathDir());
+ ServerQuicConfiguration quicConfiguration = new ServerQuicConfiguration(sslContextFactory, workDir.getEmptyPathDir());
+ HTTP3ServerConnector connector = new HTTP3ServerConnector(server, sslContextFactory, new HTTP3ServerConnectionFactory(quicConfiguration));
server.addConnector(connector);
try
{
diff --git a/jetty-core/jetty-http3/jetty-http3-tests/src/test/java/org/eclipse/jetty/http3/tests/HandlerClientServerTest.java b/jetty-core/jetty-http3/jetty-http3-tests/src/test/java/org/eclipse/jetty/http3/tests/HandlerClientServerTest.java
index d4f00d4a3779..aa4c1b64f943 100644
--- a/jetty-core/jetty-http3/jetty-http3-tests/src/test/java/org/eclipse/jetty/http3/tests/HandlerClientServerTest.java
+++ b/jetty-core/jetty-http3/jetty-http3-tests/src/test/java/org/eclipse/jetty/http3/tests/HandlerClientServerTest.java
@@ -142,10 +142,10 @@ public void onDataAvailable(Stream.Client stream)
new Random().nextBytes(bytes);
stream.data(new DataFrame(ByteBuffer.wrap(bytes, 0, bytes.length / 2), false))
.thenCompose(s -> s.data(new DataFrame(ByteBuffer.wrap(bytes, bytes.length / 2, bytes.length / 2), true)))
- .get(555, TimeUnit.SECONDS);
+ .get(5, TimeUnit.SECONDS);
- assertTrue(serverLatch.await(555, TimeUnit.SECONDS));
- assertTrue(clientResponseLatch.await(555, TimeUnit.SECONDS));
+ assertTrue(serverLatch.await(5, TimeUnit.SECONDS));
+ assertTrue(clientResponseLatch.await(5, TimeUnit.SECONDS));
int sum = clientReceivedBuffers.stream().mapToInt(Buffer::remaining).sum();
assertThat(sum, is(bytes.length));
diff --git a/jetty-core/jetty-http3/jetty-http3-tests/src/test/java/org/eclipse/jetty/http3/tests/HttpClientTransportOverHTTP3Test.java b/jetty-core/jetty-http3/jetty-http3-tests/src/test/java/org/eclipse/jetty/http3/tests/HttpClientTransportOverHTTP3Test.java
index 475a13563b12..b6802bb159a3 100644
--- a/jetty-core/jetty-http3/jetty-http3-tests/src/test/java/org/eclipse/jetty/http3/tests/HttpClientTransportOverHTTP3Test.java
+++ b/jetty-core/jetty-http3/jetty-http3-tests/src/test/java/org/eclipse/jetty/http3/tests/HttpClientTransportOverHTTP3Test.java
@@ -21,6 +21,7 @@
import org.eclipse.jetty.client.ContentResponse;
import org.eclipse.jetty.client.Response;
+import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.io.Content;
@@ -51,6 +52,7 @@ public boolean handle(Request request, org.eclipse.jetty.server.Response respons
});
ContentResponse response = httpClient.newRequest("localhost", connector.getLocalPort())
+ .scheme(HttpScheme.HTTPS.asString())
.onRequestBegin(request ->
{
if (request.getVersion() != HttpVersion.HTTP_3)
@@ -164,6 +166,7 @@ public boolean handle(Request request, org.eclipse.jetty.server.Response respons
AtomicInteger contentCount = new AtomicInteger();
CountDownLatch latch = new CountDownLatch(1);
httpClient.newRequest("localhost", connector.getLocalPort())
+ .scheme(HttpScheme.HTTPS.asString())
.onResponseContentSource((response, contentSource) ->
{
// Do not demand.
@@ -208,6 +211,7 @@ public boolean handle(Request request, org.eclipse.jetty.server.Response respons
CountDownLatch contentLatch = new CountDownLatch(1);
CountDownLatch latch = new CountDownLatch(1);
httpClient.newRequest("localhost", connector.getLocalPort())
+ .scheme(HttpScheme.HTTPS.asString())
.onResponseContentSource((response, contentSource) ->
{
// Do not demand.
diff --git a/jetty-core/jetty-http3/jetty-http3-tests/src/test/java/org/eclipse/jetty/http3/tests/IdleTimeoutTest.java b/jetty-core/jetty-http3/jetty-http3-tests/src/test/java/org/eclipse/jetty/http3/tests/IdleTimeoutTest.java
index 3d450df6da93..99727ffc307d 100644
--- a/jetty-core/jetty-http3/jetty-http3-tests/src/test/java/org/eclipse/jetty/http3/tests/IdleTimeoutTest.java
+++ b/jetty-core/jetty-http3/jetty-http3-tests/src/test/java/org/eclipse/jetty/http3/tests/IdleTimeoutTest.java
@@ -29,13 +29,14 @@
import org.eclipse.jetty.http3.api.Stream;
import org.eclipse.jetty.http3.client.HTTP3Client;
import org.eclipse.jetty.http3.frames.HeadersFrame;
-import org.eclipse.jetty.http3.server.HTTP3ServerConnector;
import org.eclipse.jetty.http3.server.RawHTTP3ServerConnectionFactory;
import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.quic.client.ClientQuicConfiguration;
import org.eclipse.jetty.quic.quiche.QuicheConnection;
+import org.eclipse.jetty.quic.server.QuicServerConnector;
+import org.eclipse.jetty.quic.server.ServerQuicConfiguration;
import org.eclipse.jetty.quic.server.ServerQuicConnection;
import org.eclipse.jetty.quic.server.ServerQuicSession;
-import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
@@ -74,9 +75,15 @@ public void dispose()
public void testIdleTimeoutWhenCongested(WorkDir workDir) throws Exception
{
long idleTimeout = 1000;
+
+ SslContextFactory.Server sslServer = new SslContextFactory.Server();
+ sslServer.setKeyStorePath("src/test/resources/keystore.p12");
+ sslServer.setKeyStorePassword("storepwd");
+ ServerQuicConfiguration serverQuicConfig = new ServerQuicConfiguration(sslServer, workDir.getEmptyPathDir());
+
AtomicBoolean established = new AtomicBoolean();
CountDownLatch disconnectLatch = new CountDownLatch(1);
- RawHTTP3ServerConnectionFactory h3 = new RawHTTP3ServerConnectionFactory(new HttpConfiguration(), new Session.Server.Listener()
+ RawHTTP3ServerConnectionFactory h3 = new RawHTTP3ServerConnectionFactory(serverQuicConfig, new Session.Server.Listener()
{
@Override
public void onAccept(Session session)
@@ -92,15 +99,12 @@ public void onDisconnect(Session session, long error, String reason)
});
CountDownLatch closeLatch = new CountDownLatch(1);
- SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
- sslContextFactory.setKeyStorePath("src/test/resources/keystore.p12");
- sslContextFactory.setKeyStorePassword("storepwd");
- HTTP3ServerConnector connector = new HTTP3ServerConnector(server, sslContextFactory, h3)
+ QuicServerConnector connector = new QuicServerConnector(server, serverQuicConfig, h3)
{
@Override
protected ServerQuicConnection newConnection(EndPoint endpoint)
{
- return new ServerQuicConnection(this, endpoint)
+ return new ServerQuicConnection(this, getQuicConfiguration(), endpoint)
{
@Override
protected ServerQuicSession newQuicSession(SocketAddress remoteAddress, QuicheConnection quicheConnection)
@@ -126,13 +130,13 @@ public void outwardClose(long error, String reason)
};
}
};
- connector.getQuicConfiguration().setPemWorkDirectory(workDir.getEmptyPathDir());
connector.setIdleTimeout(idleTimeout);
server.addConnector(connector);
server.start();
- http3Client = new HTTP3Client();
- http3Client.getClientConnector().setSslContextFactory(new SslContextFactory.Client(true));
+ SslContextFactory.Client sslClient = new SslContextFactory.Client(true);
+ http3Client = new HTTP3Client(new ClientQuicConfiguration(sslClient, null));
+ http3Client.getClientConnector().setSslContextFactory(sslClient);
http3Client.start();
Session.Client session = http3Client.connect(new InetSocketAddress("localhost", connector.getLocalPort()), new Session.Client.Listener() {})
diff --git a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnectionFactory.java b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnectionFactory.java
index a4bbc7e5ebef..9fbeb7d14ef4 100644
--- a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnectionFactory.java
+++ b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnectionFactory.java
@@ -61,9 +61,10 @@ interface Decorator
}
/**
- * A holder for a list of protocol strings identifying an application protocol
- * (for example {@code ["h2", "h2-17", "h2-16"]}) and a {@link ClientConnectionFactory}
- * that creates connections that speak that network protocol.
+ * A holder for a list of protocol strings identifiers
+ * (for example {@code ["h2", "h2-17", "h2-16"]}) and a
+ * {@link ClientConnectionFactory} that creates connections
+ * that speak an application protocol such as HTTP.
*/
public abstract static class Info extends ContainerLifeCycle
{
@@ -75,15 +76,29 @@ public Info(ClientConnectionFactory factory)
addBean(factory);
}
+ /**
+ * @param secure {@code true} for the secure protocol identifiers,
+ * {@code false} for the clear-text protocol identifiers
+ * @return a list of protocol string identifiers
+ */
public abstract List getProtocols(boolean secure);
+ /**
+ * @return the {@link ClientConnectionFactory} that speaks the protocol
+ */
public ClientConnectionFactory getClientConnectionFactory()
{
return factory;
}
/**
- * Tests whether one of the protocols of this class is also present in the given candidates list.
+ * @return the default {@link TransportProtocol} used by the protocol
+ */
+ public abstract TransportProtocol newTransportProtocol();
+
+ /**
+ * Tests whether one of the protocol identifiers of this
+ * class is also present in the given candidates list.
*
* @param candidates the candidates to match against
* @param secure whether the protocol should be a secure one
@@ -94,6 +109,12 @@ public boolean matches(List candidates, boolean secure)
return getProtocols(secure).stream().anyMatch(p -> candidates.stream().anyMatch(c -> c.equalsIgnoreCase(p)));
}
+ /**
+ * Upgrades the given {@link EndPoint} to the protocol represented by this class.
+ *
+ * @param endPoint the {@link EndPoint} to upgrade
+ * @param context the context information to perform the upgrade
+ */
public void upgrade(EndPoint endPoint, Map context)
{
throw new UnsupportedOperationException(this + " does not support upgrade to another protocol");
diff --git a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java
index f94fab824678..0049e6e56ca8 100644
--- a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java
+++ b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java
@@ -21,6 +21,7 @@
import java.net.SocketOption;
import java.net.StandardProtocolFamily;
import java.net.StandardSocketOptions;
+import java.nio.channels.DatagramChannel;
import java.nio.channels.NetworkChannel;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
@@ -29,7 +30,6 @@
import java.time.Duration;
import java.util.Map;
import java.util.Objects;
-import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import org.eclipse.jetty.util.IO;
@@ -85,7 +85,9 @@ public class ClientConnector extends ContainerLifeCycle
*
* @param path the Unix-Domain path to connect to
* @return a ClientConnector that connects to the given Unix-Domain path
+ * @deprecated replaced by {@link TransportProtocol.TCPUnix}
*/
+ @Deprecated(since = "12.0.7", forRemoval = true)
public static ClientConnector forUnixDomain(Path path)
{
return new ClientConnector(Configurator.forUnixDomain(path));
@@ -113,6 +115,11 @@ public ClientConnector()
this(new Configurator());
}
+ /**
+ * @param configurator the {@link Configurator}
+ * @deprecated replaced by {@link TransportProtocol}
+ */
+ @Deprecated(since = "12.0.7", forRemoval = true)
public ClientConnector(Configurator configurator)
{
this.configurator = Objects.requireNonNull(configurator);
@@ -124,17 +131,40 @@ public ClientConnector(Configurator configurator)
* @param address the SocketAddress to connect to
* @return whether the connection to the given SocketAddress is intrinsically secure
* @see Configurator#isIntrinsicallySecure(ClientConnector, SocketAddress)
+ *
+ * @deprecated replaced by {@link TransportProtocol#isIntrinsicallySecure()}
*/
+ @Deprecated(since = "12.0.7", forRemoval = true)
public boolean isIntrinsicallySecure(SocketAddress address)
{
return configurator.isIntrinsicallySecure(this, address);
}
+ public SelectorManager getSelectorManager()
+ {
+ return selectorManager;
+ }
+
public Executor getExecutor()
{
return executor;
}
+ /**
+ * Returns the default {@link TransportProtocol} for this connector.
+ * This method only exists for backwards compatibility, when
+ * {@link Configurator} was used, and should be removed when
+ * {@link Configurator} is removed.
+ *
+ * @return the default {@link TransportProtocol} for this connector
+ * @deprecated use {@link TransportProtocol} instead
+ */
+ @Deprecated(since = "12.0.7", forRemoval = true)
+ public TransportProtocol newTransportProtocol()
+ {
+ return configurator.newTransportProtocol();
+ }
+
public void setExecutor(Executor executor)
{
if (isStarted())
@@ -401,25 +431,29 @@ public void connect(SocketAddress address, Map context)
SelectableChannel channel = null;
try
{
- if (context == null)
- context = new ConcurrentHashMap<>();
context.put(ClientConnector.CLIENT_CONNECTOR_CONTEXT_KEY, this);
- context.putIfAbsent(REMOTE_SOCKET_ADDRESS_CONTEXT_KEY, address);
- Configurator.ChannelWithAddress channelWithAddress = configurator.newChannelWithAddress(this, address, context);
- channel = channelWithAddress.getSelectableChannel();
- address = channelWithAddress.getSocketAddress();
+ TransportProtocol transport = (TransportProtocol)context.get(TransportProtocol.class.getName());
+
+ if (address == null)
+ address = transport.getSocketAddress();
+ context.putIfAbsent(REMOTE_SOCKET_ADDRESS_CONTEXT_KEY, address);
+ channel = transport.newSelectableChannel();
configure(channel);
- SocketAddress bindAddress = getBindAddress();
- if (bindAddress != null && channel instanceof NetworkChannel)
- bind((NetworkChannel)channel, bindAddress);
+ if (channel instanceof NetworkChannel networkChannel)
+ {
+ SocketAddress bindAddress = getBindAddress();
+ if (bindAddress != null)
+ bind(networkChannel, bindAddress);
+ else if (networkChannel instanceof DatagramChannel)
+ bind(networkChannel, null);
+ }
boolean connected = true;
- if (channel instanceof SocketChannel)
+ if (channel instanceof SocketChannel socketChannel)
{
- SocketChannel socketChannel = (SocketChannel)channel;
boolean blocking = isConnectBlocking() && address instanceof InetSocketAddress;
if (LOG.isDebugEnabled())
LOG.debug("Connecting {} to {}", blocking ? "blocking" : "non-blocking", address);
@@ -462,9 +496,11 @@ public void accept(SelectableChannel selectable, Map context)
try
{
SocketChannel channel = (SocketChannel)selectable;
- context.put(ClientConnector.CLIENT_CONNECTOR_CONTEXT_KEY, this);
if (!channel.isConnected())
throw new IllegalStateException("SocketChannel must be connected");
+
+ context.put(ClientConnector.CLIENT_CONNECTOR_CONTEXT_KEY, this);
+
configure(channel);
channel.configureBlocking(false);
selectorManager.accept(channel, context);
@@ -474,9 +510,7 @@ public void accept(SelectableChannel selectable, Map context)
if (LOG.isDebugEnabled())
LOG.debug("Could not accept {}", selectable);
IO.close(selectable);
- Promise> promise = (Promise>)context.get(CONNECTION_PROMISE_CONTEXT_KEY);
- if (promise != null)
- promise.failed(failure);
+ acceptFailed(failure, selectable, context);
}
}
@@ -489,9 +523,8 @@ private void bind(NetworkChannel channel, SocketAddress bindAddress) throws IOEx
protected void configure(SelectableChannel selectable) throws IOException
{
- if (selectable instanceof NetworkChannel)
+ if (selectable instanceof NetworkChannel channel)
{
- NetworkChannel channel = (NetworkChannel)selectable;
setSocketOption(channel, StandardSocketOptions.TCP_NODELAY, isTCPNoDelay());
setSocketOption(channel, StandardSocketOptions.SO_REUSEADDR, getReuseAddress());
setSocketOption(channel, StandardSocketOptions.SO_REUSEPORT, isReusePort());
@@ -521,14 +554,23 @@ protected EndPoint newEndPoint(SelectableChannel selectable, ManagedSelector sel
{
@SuppressWarnings("unchecked")
Map context = (Map)selectionKey.attachment();
- SocketAddress address = (SocketAddress)context.get(REMOTE_SOCKET_ADDRESS_CONTEXT_KEY);
- return configurator.newEndPoint(this, address, selectable, selector, selectionKey);
+ TransportProtocol transportProtocol = (TransportProtocol)context.get(TransportProtocol.class.getName());
+ return transportProtocol.newEndPoint(getScheduler(), selector, selectable, selectionKey);
}
protected Connection newConnection(EndPoint endPoint, Map context) throws IOException
{
- SocketAddress address = (SocketAddress)context.get(REMOTE_SOCKET_ADDRESS_CONTEXT_KEY);
- return configurator.newConnection(this, address, endPoint, context);
+ TransportProtocol transportProtocol = (TransportProtocol)context.get(TransportProtocol.class.getName());
+ return transportProtocol.newConnection(endPoint, context);
+ }
+
+ protected void acceptFailed(Throwable failure, SelectableChannel channel, Map context)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Could not accept {}", channel);
+ Promise> promise = (Promise>)context.get(CONNECTION_PROMISE_CONTEXT_KEY);
+ if (promise != null)
+ promise.failed(failure);
}
protected void connectFailed(Throwable failure, Map context)
@@ -566,15 +608,21 @@ public Connection newConnection(SelectableChannel channel, EndPoint endPoint, Ob
@Override
public void connectionOpened(Connection connection, Object context)
{
- super.connectionOpened(connection, context);
- // TODO: the block below should be moved to Connection.onOpen() in each implementation,
- // so that each implementation can decide when to notify the promise, possibly not in onOpen().
@SuppressWarnings("unchecked")
Map contextMap = (Map)context;
@SuppressWarnings("unchecked")
Promise promise = (Promise)contextMap.get(CONNECTION_PROMISE_CONTEXT_KEY);
- if (promise != null)
+ try
+ {
+ super.connectionOpened(connection, context);
+ // TODO: the block below should be moved to Connection.onOpen() in each implementation,
+ // so that each implementation can decide when to notify the promise, possibly not in onOpen().
promise.succeeded(connection);
+ }
+ catch (Throwable x)
+ {
+ promise.failed(x);
+ }
}
@Override
@@ -588,9 +636,20 @@ protected void connectionFailed(SelectableChannel channel, Throwable failure, Ob
/**
* Configures a {@link ClientConnector}.
+ *
+ * @deprecated replaced by {@link TransportProtocol}
*/
+ @Deprecated(since = "12.0.7", forRemoval = true)
public static class Configurator extends ContainerLifeCycle
{
+ /**
+ * @return the default {@link TransportProtocol} for this configurator
+ */
+ public TransportProtocol newTransportProtocol()
+ {
+ return null;
+ }
+
/**
* Returns whether the connection to a given {@link SocketAddress} is intrinsically secure.
* A protocol such as HTTP/1.1 can be transported by TCP; however, TCP is not secure because
@@ -649,7 +708,10 @@ public Connection newConnection(ClientConnector clientConnector, SocketAddress a
/**
*
A pair/record holding a {@link SelectableChannel} and a {@link SocketAddress} to connect to.
+ *
+ * @deprecated replaced by {@link TransportProtocol}
*/
+ @Deprecated(since = "12.0.7", forRemoval = true)
public static class ChannelWithAddress
{
private final SelectableChannel channel;
@@ -676,6 +738,12 @@ private static Configurator forUnixDomain(Path path)
{
return new Configurator()
{
+ @Override
+ public TransportProtocol newTransportProtocol()
+ {
+ return new TransportProtocol.TCPUnix(path);
+ }
+
@Override
public ChannelWithAddress newChannelWithAddress(ClientConnector clientConnector, SocketAddress address, Map context)
{
diff --git a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/DatagramChannelEndPoint.java b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/DatagramChannelEndPoint.java
index 4bdced9d9b32..52a28cb58bb8 100644
--- a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/DatagramChannelEndPoint.java
+++ b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/DatagramChannelEndPoint.java
@@ -14,7 +14,6 @@
package org.eclipse.jetty.io;
import java.io.IOException;
-import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
@@ -32,7 +31,6 @@
*/
public class DatagramChannelEndPoint extends SelectableChannelEndPoint
{
- public static final SocketAddress EOF = InetSocketAddress.createUnresolved("", 0);
private static final Logger LOG = LoggerFactory.getLogger(DatagramChannelEndPoint.class);
public DatagramChannelEndPoint(DatagramChannel channel, ManagedSelector selector, SelectionKey key, Scheduler scheduler)
@@ -61,14 +59,7 @@ public SocketAddress getRemoteSocketAddress()
return null;
}
- /**
- * Receives data into the given buffer from the returned address.
- * This method should be used to receive UDP data.
- *
- * @param buffer the buffer to fill with data
- * @return the peer address that sent the data
- * @throws IOException if the receive fails
- */
+ @Override
public SocketAddress receive(ByteBuffer buffer) throws IOException
{
if (isInputShutdown())
@@ -88,16 +79,7 @@ public SocketAddress receive(ByteBuffer buffer) throws IOException
return peer;
}
- /**
- * Sends to the given address the data in the given buffers.
- * This methods should be used to send UDP data.
- *
- * @param address the peer address to send data to
- * @param buffers the buffers containing the data to send
- * @return true if all the buffers have been consumed
- * @throws IOException if the send fails
- * @see #write(Callback, SocketAddress, ByteBuffer...)
- */
+ @Override
public boolean send(SocketAddress address, ByteBuffer... buffers) throws IOException
{
boolean flushedAll = true;
@@ -130,16 +112,7 @@ public boolean send(SocketAddress address, ByteBuffer... buffers) throws IOExcep
return flushedAll;
}
- /**
- * Writes to the given address the data contained in the given buffers, and invokes
- * the given callback when either all the data has been sent, or a failure occurs.
- *
- * @param callback the callback to notify of the success or failure of the write operation
- * @param address the peer address to send data to
- * @param buffers the buffers containing the data to send
- * @throws WritePendingException if a previous write was initiated but was not yet completed
- * @see #send(SocketAddress, ByteBuffer...)
- */
+ @Override
public void write(Callback callback, SocketAddress address, ByteBuffer... buffers) throws WritePendingException
{
getWriteFlusher().write(callback, address, buffers);
diff --git a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/EndPoint.java b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/EndPoint.java
index 5779cd6509ca..e06fdce80afa 100644
--- a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/EndPoint.java
+++ b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/EndPoint.java
@@ -92,6 +92,11 @@
*/
public interface EndPoint extends Closeable
{
+ /**
+ * Constant returned by {@link #receive(ByteBuffer)} to indicate the end-of-file.
+ */
+ SocketAddress EOF = InetSocketAddress.createUnresolved("", 0);
+
/**
* Marks an {@code EndPoint} that wraps another {@code EndPoint}.
*/
@@ -211,6 +216,24 @@ default int fill(ByteBuffer buffer) throws IOException
throw new UnsupportedOperationException();
}
+ /**
+ * Receives data into the given buffer from the returned address.
+ * This method should be used to receive UDP data.
+ *
+ * @param buffer the buffer to fill with data
+ * @return the peer address that sent the data, or {@link #EOF}
+ * @throws IOException if the receive fails
+ */
+ default SocketAddress receive(ByteBuffer buffer) throws IOException
+ {
+ int filled = fill(buffer);
+ if (filled < 0)
+ return EndPoint.EOF;
+ if (filled == 0)
+ return null;
+ return getRemoteSocketAddress();
+ }
+
/**
* Flush data from the passed header/buffer to this endpoint. As many bytes as can be consumed
* are taken from the header/buffer position up until the buffer limit. The header/buffers position
@@ -226,6 +249,21 @@ default boolean flush(ByteBuffer... buffer) throws IOException
throw new UnsupportedOperationException();
}
+ /**
+ * Sends to the given address the data in the given buffers.
+ * This methods should be used to send UDP data.
+ *
+ * @param address the peer address to send data to
+ * @param buffers the buffers containing the data to send
+ * @return true if all the buffers have been consumed
+ * @throws IOException if the send fails
+ * @see #write(Callback, SocketAddress, ByteBuffer...)
+ */
+ default boolean send(SocketAddress address, ByteBuffer... buffers) throws IOException
+ {
+ return flush(buffers);
+ }
+
/**
* @return The underlying transport object (socket, channel, etc.)
*/
@@ -285,6 +323,21 @@ default void write(Callback callback, ByteBuffer... buffers) throws WritePending
throw new UnsupportedOperationException();
}
+ /**
+ * Writes to the given address the data contained in the given buffers, and invokes
+ * the given callback when either all the data has been sent, or a failure occurs.
+ *
+ * @param callback the callback to notify of the success or failure of the write operation
+ * @param address the peer address to send data to
+ * @param buffers the buffers containing the data to send
+ * @throws WritePendingException if a previous write was initiated but was not yet completed
+ * @see #send(SocketAddress, ByteBuffer...)
+ */
+ default void write(Callback callback, SocketAddress address, ByteBuffer... buffers) throws WritePendingException
+ {
+ write(callback, buffers);
+ }
+
/**
* @return the {@link Connection} associated with this EndPoint
* @see #setConnection(Connection)
@@ -439,4 +492,20 @@ static SslSessionData withSslSessionId(SslSessionData baseData, String sslSessio
baseData.peerCertificates());
}
}
+
+ /**
+ * A communication conduit between two peers.
+ */
+ interface Pipe
+ {
+ /**
+ * @return the {@link EndPoint} of the local peer
+ */
+ EndPoint getLocalEndPoint();
+
+ /**
+ * @return the {@link EndPoint} of the remote peer
+ */
+ EndPoint getRemoteEndPoint();
+ }
}
diff --git a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/MemoryEndPointPipe.java b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/MemoryEndPointPipe.java
new file mode 100644
index 000000000000..b7b1786e8ca7
--- /dev/null
+++ b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/MemoryEndPointPipe.java
@@ -0,0 +1,344 @@
+//
+// ========================================================================
+// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+// which is available at https://www.apache.org/licenses/LICENSE-2.0.
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.IOException;
+import java.net.SocketAddress;
+import java.nio.ByteBuffer;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.HexFormat;
+import java.util.Objects;
+import java.util.Queue;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Consumer;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.thread.AutoLock;
+import org.eclipse.jetty.util.thread.Invocable;
+import org.eclipse.jetty.util.thread.Scheduler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Memory-based implementation of {@link EndPoint.Pipe}.
+ */
+public class MemoryEndPointPipe implements EndPoint.Pipe
+{
+ private static final Logger LOG = LoggerFactory.getLogger(MemoryEndPointPipe.class);
+
+ private final LocalEndPoint localEndPoint;
+ private final RemoteEndPoint remoteEndPoint;
+ private final Consumer taskConsumer;
+
+ public MemoryEndPointPipe(Scheduler scheduler, Consumer consumer, SocketAddress socketAddress)
+ {
+ localEndPoint = new LocalEndPoint(scheduler, socketAddress);
+ remoteEndPoint = new RemoteEndPoint(scheduler, new MemorySocketAddress());
+ localEndPoint.setPeerEndPoint(remoteEndPoint);
+ remoteEndPoint.setPeerEndPoint(localEndPoint);
+ taskConsumer = consumer;
+ }
+
+ @Override
+ public EndPoint getLocalEndPoint()
+ {
+ return localEndPoint;
+ }
+
+ @Override
+ public EndPoint getRemoteEndPoint()
+ {
+ return remoteEndPoint;
+ }
+
+ private class MemoryEndPoint extends AbstractEndPoint
+ {
+ private static final ByteBuffer EOF = ByteBuffer.allocate(0);
+
+ private final AutoLock lock = new AutoLock();
+ private final Deque byteBuffers = new ArrayDeque<>();
+ private final SocketAddress localAddress;
+ private MemoryEndPoint peerEndPoint;
+ private Invocable.Task fillableTask;
+ private Invocable.Task completeWriteTask;
+ private long maxCapacity;
+ private long capacity;
+
+ private MemoryEndPoint(Scheduler scheduler, SocketAddress localAddress)
+ {
+ super(scheduler);
+ this.localAddress = localAddress;
+ }
+
+ void setPeerEndPoint(MemoryEndPoint peerEndPoint)
+ {
+ this.peerEndPoint = peerEndPoint;
+ this.fillableTask = new FillableTask(peerEndPoint.getFillInterest());
+ this.completeWriteTask = new CompleteWriteTask(peerEndPoint.getWriteFlusher());
+ }
+
+ public long getMaxCapacity()
+ {
+ return maxCapacity;
+ }
+
+ public void setMaxCapacity(long maxCapacity)
+ {
+ this.maxCapacity = maxCapacity;
+ }
+
+ @Override
+ public Object getTransport()
+ {
+ return null;
+ }
+
+ @Override
+ public SocketAddress getLocalSocketAddress()
+ {
+ return localAddress;
+ }
+
+ @Override
+ public SocketAddress getRemoteSocketAddress()
+ {
+ return peerEndPoint.getLocalSocketAddress();
+ }
+
+ @Override
+ protected void onIncompleteFlush()
+ {
+ }
+
+ @Override
+ protected void needsFillInterest()
+ {
+ }
+
+ @Override
+ public int fill(ByteBuffer buffer) throws IOException
+ {
+ if (!isOpen())
+ throw new IOException("closed");
+ if (isInputShutdown())
+ return -1;
+
+ int filled;
+ ByteBuffer data;
+ try (AutoLock ignored = peerEndPoint.lock.lock())
+ {
+ Queue byteBuffers = peerEndPoint.byteBuffers;
+ data = byteBuffers.peek();
+
+ if (data == null)
+ {
+ filled = 0;
+ }
+ else if (data == EOF)
+ {
+ filled = -1;
+ }
+ else
+ {
+ int length = data.remaining();
+ int space = BufferUtil.space(buffer);
+ if (length <= space)
+ byteBuffers.poll();
+
+ filled = Math.min(length, space);
+ peerEndPoint.capacity -= filled;
+ }
+ }
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("filled {} from {}", filled, this);
+
+ if (data == null)
+ return 0;
+
+ if (data == EOF)
+ {
+ shutdownInput();
+ return -1;
+ }
+
+ int copied = BufferUtil.append(buffer, data);
+ assert copied == filled;
+
+ if (filled > 0)
+ {
+ notIdle();
+ onFilled();
+ }
+
+ return filled;
+ }
+
+ private void onFilled()
+ {
+ taskConsumer.accept(completeWriteTask);
+ }
+
+ @Override
+ public boolean flush(ByteBuffer... buffers) throws IOException
+ {
+ if (!isOpen())
+ throw new IOException("closed");
+ if (isOutputShutdown())
+ throw new IOException("shutdown");
+
+ long flushed = 0;
+ boolean result = true;
+ try (AutoLock ignored = lock.lock())
+ {
+ for (ByteBuffer buffer : buffers)
+ {
+ int remaining = buffer.remaining();
+ if (remaining == 0)
+ continue;
+
+ long newCapacity = capacity + remaining;
+ long maxCapacity = getMaxCapacity();
+ if (maxCapacity > 0 && newCapacity > maxCapacity)
+ {
+ result = false;
+ break;
+ }
+
+ byteBuffers.offer(BufferUtil.copy(buffer));
+ buffer.position(buffer.limit());
+ capacity = newCapacity;
+ flushed += remaining;
+ }
+ }
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("flushed {} to {}", flushed, this);
+
+ if (flushed > 0)
+ {
+ notIdle();
+ onFlushed();
+ }
+
+ return result;
+ }
+
+ @Override
+ protected void doShutdownOutput()
+ {
+ super.doShutdownOutput();
+ try (AutoLock ignored = lock.lock())
+ {
+ byteBuffers.offer(EOF);
+ }
+ onFlushed();
+ }
+
+ @Override
+ protected void doClose()
+ {
+ super.doClose();
+ try (AutoLock ignored = lock.lock())
+ {
+ ByteBuffer last = byteBuffers.peekLast();
+ if (last != EOF)
+ byteBuffers.offer(EOF);
+ }
+ onFlushed();
+ }
+
+ private void onFlushed()
+ {
+ taskConsumer.accept(fillableTask);
+ }
+ }
+
+ private class LocalEndPoint extends MemoryEndPoint
+ {
+ private LocalEndPoint(Scheduler scheduler, SocketAddress socketAddress)
+ {
+ super(scheduler, socketAddress);
+ }
+ }
+
+ private class RemoteEndPoint extends MemoryEndPoint
+ {
+ private RemoteEndPoint(Scheduler scheduler, SocketAddress socketAddress)
+ {
+ super(scheduler, socketAddress);
+ }
+ }
+
+ private record FillableTask(FillInterest fillInterest) implements Invocable.Task
+ {
+ @Override
+ public void run()
+ {
+ fillInterest.fillable();
+ }
+
+ @Override
+ public InvocationType getInvocationType()
+ {
+ return fillInterest.getCallbackInvocationType();
+ }
+ }
+
+ private record CompleteWriteTask(WriteFlusher writeFlusher) implements Invocable.Task
+ {
+ @Override
+ public void run()
+ {
+ writeFlusher.completeWrite();
+ }
+
+ @Override
+ public InvocationType getInvocationType()
+ {
+ return writeFlusher.getCallbackInvocationType();
+ }
+ }
+
+ private static class MemorySocketAddress extends SocketAddress
+ {
+ private static final AtomicLong ID = new AtomicLong();
+
+ private final long id = ID.incrementAndGet();
+ private final String address = "[memory:/%s]".formatted(HexFormat.of().formatHex(ByteBuffer.allocate(8).putLong(id).array()));
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ return true;
+ if (obj instanceof MemorySocketAddress that)
+ return id == that.id;
+ return false;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return Objects.hash(id);
+ }
+
+ @Override
+ public String toString()
+ {
+ return address;
+ }
+ }
+}
diff --git a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/TransportProtocol.java b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/TransportProtocol.java
new file mode 100644
index 000000000000..487264320676
--- /dev/null
+++ b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/TransportProtocol.java
@@ -0,0 +1,402 @@
+//
+// ========================================================================
+// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+// which is available at https://www.apache.org/licenses/LICENSE-2.0.
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.IOException;
+import java.net.SocketAddress;
+import java.net.StandardProtocolFamily;
+import java.net.UnixDomainSocketAddress;
+import java.nio.channels.DatagramChannel;
+import java.nio.channels.SelectableChannel;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.SocketChannel;
+import java.nio.file.Path;
+import java.util.Map;
+import java.util.Objects;
+
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * The low-level transport protocol used by clients.
+ * A high-level protocol such as HTTP/1.1 can be transported over a low-level
+ * protocol such as TCP/IP, Unix-Domain sockets, QUIC, shared memory, etc.
+ * This class defines the programming interface to implement low-level
+ * protocols, and useful implementations for commonly used low-level
+ * protocols such as TCP/IP or Unix-Domain sockets.
+ * Low-level transports may be layered; some of them maybe considered
+ * lower-level than others, but from the point of view of the high-level
+ * protocols they are all considered low-level.
+ * For example, QUIC is typically layered on top of the UDP/IP low-level
+ * transport protocol, but it may be layered on top Unix-Domain sockets,
+ * or on top of shared memory.
+ * As QUIC provides a reliable, ordered, stream-based transport, it may
+ * be seen as a replacement for TCP, and high-level protocols that need
+ * a reliable, ordered, stream-based transport may use either the non-layered
+ * TCP/IP or the layered QUIC over UDP/IP without noticing the difference.
+ * This makes possible to transport HTTP/1.1 over QUIC over Unix-Domain
+ * sockets, or HTTP/2 over QUIC over shared memory, etc.
+ */
+public interface TransportProtocol
+{
+ /**
+ * The transport protocol TCP/IP.
+ */
+ TransportProtocol TCP_IP = new TCPIP();
+
+ /**
+ * The transport protocol UDP/IP.
+ */
+ TransportProtocol UDP_IP = new UDPIP();
+
+ /**
+ * @return whether this transport protocol is intrinsically secure.
+ */
+ default boolean isIntrinsicallySecure()
+ {
+ return false;
+ }
+
+ /**
+ * Returns whether this transport protocol requires resolution of domain
+ * names.
+ * When domain name resolution is required, it must be performed by
+ * an external service, and the value returned by {@link #getSocketAddress()}
+ * is ignored, while the resolved socket address is eventually passed to
+ * {@link #connect(SocketAddress, Map)}.
+ * Otherwise, domain name resolution is not required, and the value returned
+ * by {@link #getSocketAddress()} is eventually passed to
+ * {@link #connect(SocketAddress, Map)}.
+ *
+ * @return whether this transport protocol requires domain names resolution
+ */
+ default boolean requiresDomainNamesResolution()
+ {
+ return false;
+ }
+
+ /**
+ * Establishes a connection to the given socket address.
+ *
+ * @param socketAddress the socket address to connect to
+ * @param context the context information to establish the connection
+ */
+ default void connect(SocketAddress socketAddress, Map context)
+ {
+ }
+
+ /**
+ * @return the socket address to use in case domain name resolution is not required
+ */
+ default SocketAddress getSocketAddress()
+ {
+ return null;
+ }
+
+ /**
+ * For transport protocols that are based on sockets, or for transport protocols
+ * that are layered on top of another transport protocol that is based on sockets,
+ * this method is invoked to create a new {@link SelectableChannel} used for the
+ * socket communication.
+ *
+ * @return a new {@link SelectableChannel} used for the socket communication,
+ * or {@code null} if the communication does not use sockets.
+ * @throws IOException if the {@link SelectableChannel} cannot be created
+ */
+ default SelectableChannel newSelectableChannel() throws IOException
+ {
+ return null;
+ }
+
+ /**
+ * For transport protocols that are based on sockets, or for transport protocols
+ * that are layered on top of another transport protocol that is based on sockets,
+ * this method is invoked to create a new {@link EndPoint} that wraps the
+ * {@link SelectableChannel} created by {@link #newSelectableChannel()}.
+ *
+ * @param scheduler the {@link Scheduler}
+ * @param selector the {@link ManagedSelector}
+ * @param selectable the {@link SelectableChannel}
+ * @param selectionKey the {@link SelectionKey}
+ * @return a new {@link EndPoint}
+ */
+ default EndPoint newEndPoint(Scheduler scheduler, ManagedSelector selector, SelectableChannel selectable, SelectionKey selectionKey)
+ {
+ return null;
+ }
+
+ /**
+ * Creates a new {@link Connection} to be associated with the given low-level {@link EndPoint}.
+ * For non-layered transport protocols such as TCP/IP, the {@link Connection} is typically
+ * that of the high-level protocol.
+ * For layered transport protocols such as QUIC, the {@link Connection} is typically that of the
+ * layered transport protocol.
+ *
+ * @param endPoint the {@link EndPoint} to associate the {@link Connection} to
+ * @param context the context information to create the connection
+ * @return a new {@link Connection}
+ * @throws IOException if the {@link Connection} cannot be created
+ */
+ default Connection newConnection(EndPoint endPoint, Map context) throws IOException
+ {
+ ClientConnectionFactory factory = (ClientConnectionFactory)context.get(ClientConnector.CLIENT_CONNECTION_FACTORY_CONTEXT_KEY);
+ return factory.newConnection(endPoint, context);
+ }
+
+ int hashCode();
+
+ boolean equals(Object obj);
+
+ /**
+ * Abstract implementation of transport protocols based on sockets.
+ */
+ abstract class Socket implements TransportProtocol
+ {
+ @Override
+ public void connect(SocketAddress socketAddress, Map context)
+ {
+ ClientConnector connector = (ClientConnector)context.get(ClientConnector.CLIENT_CONNECTOR_CONTEXT_KEY);
+ connector.connect(socketAddress, context);
+ }
+
+ @Override
+ public String toString()
+ {
+ return "%s@%x".formatted(getClass().getSimpleName(), hashCode());
+ }
+ }
+
+ /**
+ * Abstract implementation of transport protocols based on IP.
+ */
+ abstract class IP extends Socket
+ {
+ @Override
+ public boolean requiresDomainNamesResolution()
+ {
+ return true;
+ }
+ }
+
+ /**
+ * The TCP/IP transport protocol.
+ */
+ class TCPIP extends IP
+ {
+ protected TCPIP()
+ {
+ // Do not instantiate, use the singleton.
+ }
+
+ @Override
+ public SelectableChannel newSelectableChannel() throws IOException
+ {
+ return SocketChannel.open();
+ }
+
+ @Override
+ public EndPoint newEndPoint(Scheduler scheduler, ManagedSelector selector, SelectableChannel selectable, SelectionKey selectionKey)
+ {
+ return new SocketChannelEndPoint((SocketChannel)selectable, selector, selectionKey, scheduler);
+ }
+ }
+
+ /**
+ * The UDP/IP transport protocol.
+ */
+ class UDPIP extends TransportProtocol.IP
+ {
+ protected UDPIP()
+ {
+ // Do not instantiate, use the singleton.
+ }
+
+ @Override
+ public SelectableChannel newSelectableChannel() throws IOException
+ {
+ return DatagramChannel.open();
+ }
+
+ @Override
+ public EndPoint newEndPoint(Scheduler scheduler, ManagedSelector selector, SelectableChannel selectable, SelectionKey selectionKey)
+ {
+ return new DatagramChannelEndPoint((DatagramChannel)selectable, selector, selectionKey, scheduler);
+ }
+ }
+
+ /**
+ * Abstract implementation of transport protocols based on Unix-Domain sockets.
+ */
+ abstract class Unix extends Socket
+ {
+ private final UnixDomainSocketAddress socketAddress;
+
+ protected Unix(Path path)
+ {
+ this.socketAddress = UnixDomainSocketAddress.of(path);
+ }
+
+ @Override
+ public SocketAddress getSocketAddress()
+ {
+ return socketAddress;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return Objects.hash(socketAddress);
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ return true;
+ if (obj instanceof Unix unix)
+ return Objects.equals(socketAddress, unix.socketAddress);
+ return false;
+ }
+
+ @Override
+ public String toString()
+ {
+ return "%s[%s]".formatted(super.toString(), socketAddress.getPath());
+ }
+ }
+
+ /**
+ * The stream Unix-Domain socket transport protocol.
+ */
+ class TCPUnix extends Unix
+ {
+ public TCPUnix(Path path)
+ {
+ super(path);
+ }
+
+ @Override
+ public SelectableChannel newSelectableChannel() throws IOException
+ {
+ return SocketChannel.open(StandardProtocolFamily.UNIX);
+ }
+
+ @Override
+ public EndPoint newEndPoint(Scheduler scheduler, ManagedSelector selector, SelectableChannel selectable, SelectionKey selectionKey)
+ {
+ return new SocketChannelEndPoint((SocketChannel)selectable, selector, selectionKey, scheduler);
+ }
+ }
+
+ /**
+ * The datagram Unix-Domain socket transport protocol.
+ */
+ class UDPUnix extends Unix
+ {
+ public UDPUnix(Path path)
+ {
+ super(path);
+ }
+
+ @Override
+ public SelectableChannel newSelectableChannel() throws IOException
+ {
+ return DatagramChannel.open(StandardProtocolFamily.UNIX);
+ }
+
+ @Override
+ public EndPoint newEndPoint(Scheduler scheduler, ManagedSelector selector, SelectableChannel selectable, SelectionKey selectionKey)
+ {
+ return new DatagramChannelEndPoint((DatagramChannel)selectable, selector, selectionKey, scheduler);
+ }
+ }
+
+ /**
+ * A wrapper for {@link TransportProtocol} instances to allow layering of transport protocols.
+ */
+ class Wrapper implements TransportProtocol
+ {
+ private final TransportProtocol wrapped;
+
+ public Wrapper(TransportProtocol wrapped)
+ {
+ this.wrapped = Objects.requireNonNull(wrapped);
+ }
+
+ public TransportProtocol getWrapped()
+ {
+ return wrapped;
+ }
+
+ public TransportProtocol unwrap()
+ {
+ TransportProtocol result = getWrapped();
+ while (true)
+ {
+ if (result instanceof Wrapper wrapper)
+ result = wrapper.getWrapped();
+ else
+ break;
+ }
+ return result;
+ }
+
+ @Override
+ public boolean isIntrinsicallySecure()
+ {
+ return wrapped.isIntrinsicallySecure();
+ }
+
+ @Override
+ public boolean requiresDomainNamesResolution()
+ {
+ return wrapped.requiresDomainNamesResolution();
+ }
+
+ @Override
+ public void connect(SocketAddress socketAddress, Map context)
+ {
+ wrapped.connect(socketAddress, context);
+ }
+
+ @Override
+ public SocketAddress getSocketAddress()
+ {
+ return wrapped.getSocketAddress();
+ }
+
+ @Override
+ public SelectableChannel newSelectableChannel() throws IOException
+ {
+ return wrapped.newSelectableChannel();
+ }
+
+ @Override
+ public EndPoint newEndPoint(Scheduler scheduler, ManagedSelector selector, SelectableChannel selectable, SelectionKey selectionKey)
+ {
+ return wrapped.newEndPoint(scheduler, selector, selectable, selectionKey);
+ }
+
+ @Override
+ public Connection newConnection(EndPoint endPoint, Map context) throws IOException
+ {
+ return wrapped.newConnection(endPoint, context);
+ }
+
+ @Override
+ public String toString()
+ {
+ return "%s@%x[%s]".formatted(getClass().getSimpleName(), hashCode(), getWrapped());
+ }
+ }
+}
diff --git a/jetty-core/jetty-quic/jetty-quic-client/src/main/java/org/eclipse/jetty/quic/client/ClientQuicConfiguration.java b/jetty-core/jetty-quic/jetty-quic-client/src/main/java/org/eclipse/jetty/quic/client/ClientQuicConfiguration.java
new file mode 100644
index 000000000000..669c5cc0531e
--- /dev/null
+++ b/jetty-core/jetty-quic/jetty-quic-client/src/main/java/org/eclipse/jetty/quic/client/ClientQuicConfiguration.java
@@ -0,0 +1,106 @@
+//
+// ========================================================================
+// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+// which is available at https://www.apache.org/licenses/LICENSE-2.0.
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+package org.eclipse.jetty.quic.client;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.KeyStore;
+
+import org.eclipse.jetty.quic.common.QuicConfiguration;
+import org.eclipse.jetty.quic.quiche.PemExporter;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Client-side {@link QuicConfiguration} with client-specific settings.
+ * The PEM working directory constructor argument is only necessary
+ * when the client-side needs to send certificates to the server, or
+ * when it needs a TrustStore, otherwise it may be null.
+ */
+public class ClientQuicConfiguration extends QuicConfiguration
+{
+ private static final Logger LOG = LoggerFactory.getLogger(ClientQuicConfiguration.class);
+
+ private final SslContextFactory.Client sslContextFactory;
+
+ public ClientQuicConfiguration(SslContextFactory.Client sslContextFactory, Path pemWorkDirectory)
+ {
+ this.sslContextFactory = sslContextFactory;
+ setPemWorkDirectory(pemWorkDirectory);
+ setSessionRecvWindow(16 * 1024 * 1024);
+ setBidirectionalStreamRecvWindow(8 * 1024 * 1024);
+ }
+
+ public SslContextFactory.Client getSslContextFactory()
+ {
+ return sslContextFactory;
+ }
+
+ @Override
+ protected void doStart() throws Exception
+ {
+ addBean(sslContextFactory);
+
+ super.doStart();
+
+ Path pemWorkDirectory = getPemWorkDirectory();
+ KeyStore trustStore = sslContextFactory.getTrustStore();
+ if (trustStore != null)
+ {
+ Path trustedCertificatesPemPath = PemExporter.exportTrustStore(trustStore, pemWorkDirectory);
+ getImplementationConfiguration().put(TRUSTED_CERTIFICATES_PEM_PATH_KEY, trustedCertificatesPemPath);
+ }
+
+ String certAlias = sslContextFactory.getCertAlias();
+ if (certAlias != null)
+ {
+ KeyStore keyStore = sslContextFactory.getKeyStore();
+ String keyManagerPassword = sslContextFactory.getKeyManagerPassword();
+ char[] password = keyManagerPassword == null ? sslContextFactory.getKeyStorePassword().toCharArray() : keyManagerPassword.toCharArray();
+ Path[] keyPair = PemExporter.exportKeyPair(keyStore, certAlias, password, pemWorkDirectory);
+ Path privateKeyPemPath = keyPair[0];
+ getImplementationConfiguration().put(PRIVATE_KEY_PEM_PATH_KEY, privateKeyPemPath);
+ Path certificateChainPemPath = keyPair[1];
+ getImplementationConfiguration().put(CERTIFICATE_CHAIN_PEM_PATH_KEY, certificateChainPemPath);
+ }
+ }
+
+ @Override
+ protected void doStop() throws Exception
+ {
+ super.doStop();
+
+ Path certificateChainPemPath = (Path)getImplementationConfiguration().remove(CERTIFICATE_CHAIN_PEM_PATH_KEY);
+ deleteFile(certificateChainPemPath);
+ Path privateKeyPemPath = (Path)getImplementationConfiguration().remove(PRIVATE_KEY_PEM_PATH_KEY);
+ deleteFile(privateKeyPemPath);
+ Path trustedCertificatesPemPath = (Path)getImplementationConfiguration().remove(TRUSTED_CERTIFICATES_PEM_PATH_KEY);
+ deleteFile(trustedCertificatesPemPath);
+ }
+
+ private void deleteFile(Path path)
+ {
+ try
+ {
+ if (path != null)
+ Files.delete(path);
+ }
+ catch (Throwable x)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("could not delete {}", path, x);
+ }
+ }
+}
diff --git a/jetty-core/jetty-quic/jetty-quic-client/src/main/java/org/eclipse/jetty/quic/client/ClientQuicConnection.java b/jetty-core/jetty-quic/jetty-quic-client/src/main/java/org/eclipse/jetty/quic/client/ClientQuicConnection.java
index bd0e307cb788..76452046bda2 100644
--- a/jetty-core/jetty-quic/jetty-quic-client/src/main/java/org/eclipse/jetty/quic/client/ClientQuicConnection.java
+++ b/jetty-core/jetty-quic/jetty-quic-client/src/main/java/org/eclipse/jetty/quic/client/ClientQuicConnection.java
@@ -14,14 +14,15 @@
package org.eclipse.jetty.quic.client;
import java.io.IOException;
+import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.SocketTimeoutException;
import java.nio.ByteBuffer;
+import java.nio.file.Path;
import java.util.Collection;
import java.util.List;
import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@@ -49,16 +50,19 @@ public class ClientQuicConnection extends QuicConnection
{
private static final Logger LOG = LoggerFactory.getLogger(ClientQuicConnection.class);
- private final Map pendingSessions = new ConcurrentHashMap<>();
private final ClientConnector connector;
private final Map context;
+ private final InetSocketAddress inetLocalAddress;
private Scheduler.Task connectTask;
+ private ClientQuicSession pendingSession;
+ private InetSocketAddress inetRemoteAddress;
public ClientQuicConnection(ClientConnector connector, EndPoint endPoint, Map context)
{
super(connector.getExecutor(), connector.getScheduler(), connector.getByteBufferPool(), endPoint);
this.connector = connector;
this.context = context;
+ this.inetLocalAddress = getEndPoint().getLocalSocketAddress() instanceof InetSocketAddress inet ? inet : new InetSocketAddress(InetAddress.getLoopbackAddress(), 0xFA93);
}
@Override
@@ -83,9 +87,15 @@ public void onOpen()
quicheConfig.setDisableActiveMigration(quicConfiguration.isDisableActiveMigration());
quicheConfig.setVerifyPeer(!connector.getSslContextFactory().isTrustAll());
Map implCtx = quicConfiguration.getImplementationConfiguration();
- quicheConfig.setTrustedCertsPemPath((String)implCtx.get(QuicClientConnectorConfigurator.TRUSTED_CERTIFICATES_PEM_PATH_KEY));
- quicheConfig.setPrivKeyPemPath((String)implCtx.get(QuicClientConnectorConfigurator.PRIVATE_KEY_PEM_PATH_KEY));
- quicheConfig.setCertChainPemPath((String)implCtx.get(QuicClientConnectorConfigurator.CERTIFICATE_CHAIN_PEM_PATH_KEY));
+ Path trustedCertificatesPath = (Path)implCtx.get(QuicConfiguration.TRUSTED_CERTIFICATES_PEM_PATH_KEY);
+ if (trustedCertificatesPath != null)
+ quicheConfig.setTrustedCertsPemPath(trustedCertificatesPath.toString());
+ Path privateKeyPath = (Path)implCtx.get(QuicConfiguration.PRIVATE_KEY_PEM_PATH_KEY);
+ if (privateKeyPath != null)
+ quicheConfig.setPrivKeyPemPath(privateKeyPath.toString());
+ Path certificatesPath = (Path)implCtx.get(QuicConfiguration.CERTIFICATE_CHAIN_PEM_PATH_KEY);
+ if (certificatesPath != null)
+ quicheConfig.setCertChainPemPath(certificatesPath.toString());
// Idle timeouts must not be managed by Quiche.
quicheConfig.setMaxIdleTimeout(0L);
quicheConfig.setInitialMaxData((long)quicConfiguration.getSessionRecvWindow());
@@ -96,14 +106,15 @@ public void onOpen()
quicheConfig.setInitialMaxStreamsBidi((long)quicConfiguration.getMaxBidirectionalRemoteStreams());
quicheConfig.setCongestionControl(QuicheConfig.CongestionControl.CUBIC);
- InetSocketAddress remoteAddress = (InetSocketAddress)context.get(ClientConnector.REMOTE_SOCKET_ADDRESS_CONTEXT_KEY);
+ SocketAddress remoteAddress = (SocketAddress)context.get(ClientConnector.REMOTE_SOCKET_ADDRESS_CONTEXT_KEY);
+ inetRemoteAddress = remoteAddress instanceof InetSocketAddress inet ? inet : new InetSocketAddress(InetAddress.getLoopbackAddress(), 443);
if (LOG.isDebugEnabled())
LOG.debug("connecting to {} with protocols {}", remoteAddress, protocols);
- QuicheConnection quicheConnection = QuicheConnection.connect(quicheConfig, getEndPoint().getLocalAddress(), remoteAddress);
- ClientQuicSession session = new ClientQuicSession(getExecutor(), getScheduler(), getByteBufferPool(), quicheConnection, this, remoteAddress, context);
- pendingSessions.put(remoteAddress, session);
+ QuicheConnection quicheConnection = QuicheConnection.connect(quicheConfig, inetLocalAddress, inetRemoteAddress);
+ ClientQuicSession session = new ClientQuicSession(getExecutor(), getScheduler(), getByteBufferPool(), quicheConnection, this, inetRemoteAddress, context);
+ pendingSession = session;
if (LOG.isDebugEnabled())
LOG.debug("created {}", session);
@@ -132,30 +143,41 @@ private void connectTimeout(SocketAddress remoteAddress)
if (LOG.isDebugEnabled())
LOG.debug("connect timeout {} ms to {} on {}", connector.getConnectTimeout(), remoteAddress, this);
close();
- outwardClose(remoteAddress, new SocketTimeoutException("connect timeout"));
+ outwardClose(new SocketTimeoutException("connect timeout"));
}
@Override
protected QuicSession createSession(SocketAddress remoteAddress, ByteBuffer cipherBuffer) throws IOException
{
- ClientQuicSession session = pendingSessions.get(remoteAddress);
- if (session != null)
+ InetSocketAddress inetRemote = remoteAddress instanceof InetSocketAddress inet ? inet : inetRemoteAddress;
+ Runnable task = pendingSession.process(inetRemote, cipherBuffer);
+ pendingSession.offerTask(task);
+ if (pendingSession.isConnectionEstablished())
{
- Runnable task = session.process(remoteAddress, cipherBuffer);
- session.offerTask(task);
- if (session.isConnectionEstablished())
- {
- pendingSessions.remove(remoteAddress);
- return session;
- }
+ ClientQuicSession session = pendingSession;
+ pendingSession = null;
+ return session;
}
return null;
}
+ @Override
+ public InetSocketAddress getLocalInetSocketAddress()
+ {
+ return inetLocalAddress;
+ }
+
+ @Override
+ protected Runnable process(QuicSession session, SocketAddress remoteAddress, ByteBuffer cipherBuffer)
+ {
+ InetSocketAddress inetRemote = remoteAddress instanceof InetSocketAddress inet ? inet : inetRemoteAddress;
+ return super.process(session, inetRemote, cipherBuffer);
+ }
+
@Override
protected void onFailure(Throwable failure)
{
- pendingSessions.values().forEach(session -> outwardClose(session, failure));
+ outwardClose(pendingSession, failure);
super.onFailure(failure);
}
@@ -178,21 +200,15 @@ public boolean onIdleExpired(TimeoutException timeoutException)
public void outwardClose(QuicSession session, Throwable failure)
{
super.outwardClose(session, failure);
- SocketAddress remoteAddress = session.getRemoteAddress();
- outwardClose(remoteAddress, failure);
+ outwardClose(failure);
}
- private void outwardClose(SocketAddress remoteAddress, Throwable failure)
+ private void outwardClose(Throwable failure)
{
- if (remoteAddress != null)
- {
- if (pendingSessions.remove(remoteAddress) != null)
- {
- Promise> promise = (Promise>)context.get(ClientConnector.CONNECTION_PROMISE_CONTEXT_KEY);
- if (promise != null)
- promise.failed(failure);
- }
- }
+ pendingSession = null;
+ Promise> promise = (Promise>)context.get(ClientConnector.CONNECTION_PROMISE_CONTEXT_KEY);
+ if (promise != null)
+ promise.failed(failure);
getEndPoint().close(failure);
}
}
diff --git a/jetty-core/jetty-quic/jetty-quic-client/src/main/java/org/eclipse/jetty/quic/client/QuicClientConnectorConfigurator.java b/jetty-core/jetty-quic/jetty-quic-client/src/main/java/org/eclipse/jetty/quic/client/QuicClientConnectorConfigurator.java
index 2d471a85be0f..6a4bcd01a2d8 100644
--- a/jetty-core/jetty-quic/jetty-quic-client/src/main/java/org/eclipse/jetty/quic/client/QuicClientConnectorConfigurator.java
+++ b/jetty-core/jetty-quic/jetty-quic-client/src/main/java/org/eclipse/jetty/quic/client/QuicClientConnectorConfigurator.java
@@ -19,9 +19,6 @@
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.security.KeyStore;
import java.util.Map;
import java.util.Objects;
import java.util.function.UnaryOperator;
@@ -32,11 +29,9 @@
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.ManagedSelector;
import org.eclipse.jetty.io.SocketChannelEndPoint;
+import org.eclipse.jetty.io.TransportProtocol;
import org.eclipse.jetty.quic.common.QuicConfiguration;
-import org.eclipse.jetty.quic.quiche.PemExporter;
import org.eclipse.jetty.util.ssl.SslContextFactory;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
/**
* A QUIC specific {@link ClientConnector.Configurator}.
@@ -45,20 +40,14 @@
* {@link SocketChannelEndPoint}s.
*
* @see QuicConfiguration
+ * @deprecated replaced by {@link TransportProtocol}
*/
+@Deprecated(since = "12.0.7", forRemoval = true)
public class QuicClientConnectorConfigurator extends ClientConnector.Configurator
{
- private static final Logger LOG = LoggerFactory.getLogger(QuicClientConnectorConfigurator.class);
-
- static final String PRIVATE_KEY_PEM_PATH_KEY = QuicClientConnectorConfigurator.class.getName() + ".privateKeyPemPath";
- static final String CERTIFICATE_CHAIN_PEM_PATH_KEY = QuicClientConnectorConfigurator.class.getName() + ".certificateChainPemPath";
- static final String TRUSTED_CERTIFICATES_PEM_PATH_KEY = QuicClientConnectorConfigurator.class.getName() + ".trustedCertificatesPemPath";
-
- private final QuicConfiguration configuration = new QuicConfiguration();
+ private final QuicConfiguration initQuicConfig = new QuicConfiguration();
private final UnaryOperator configurator;
- private Path privateKeyPemPath;
- private Path certificateChainPemPath;
- private Path trustedCertificatesPemPath;
+ private ClientQuicConfiguration quicConfig;
public QuicClientConnectorConfigurator()
{
@@ -69,72 +58,48 @@ public QuicClientConnectorConfigurator(UnaryOperator configurator)
{
this.configurator = Objects.requireNonNull(configurator);
// Initialize to sane defaults for a client.
- configuration.setSessionRecvWindow(16 * 1024 * 1024);
- configuration.setBidirectionalStreamRecvWindow(8 * 1024 * 1024);
- configuration.setDisableActiveMigration(true);
+ initQuicConfig.setSessionRecvWindow(16 * 1024 * 1024);
+ initQuicConfig.setBidirectionalStreamRecvWindow(8 * 1024 * 1024);
+ initQuicConfig.setDisableActiveMigration(true);
}
public QuicConfiguration getQuicConfiguration()
{
- return configuration;
+ if (!isStarted())
+ return initQuicConfig;
+ else
+ return quicConfig;
}
@Override
protected void doStart() throws Exception
{
- Path pemWorkDirectory = configuration.getPemWorkDirectory();
ClientConnector clientConnector = getBean(ClientConnector.class);
SslContextFactory.Client sslContextFactory = clientConnector.getSslContextFactory();
- KeyStore trustStore = sslContextFactory.getTrustStore();
- if (trustStore != null)
- {
- trustedCertificatesPemPath = PemExporter.exportTrustStore(trustStore, pemWorkDirectory != null ? pemWorkDirectory : Path.of(System.getProperty("java.io.tmpdir")));
- configuration.getImplementationConfiguration().put(TRUSTED_CERTIFICATES_PEM_PATH_KEY, trustedCertificatesPemPath.toString());
- }
- String certAlias = sslContextFactory.getCertAlias();
- if (certAlias != null)
- {
- if (pemWorkDirectory == null)
- throw new IllegalStateException("No PEM work directory configured");
- KeyStore keyStore = sslContextFactory.getKeyStore();
- String keyManagerPassword = sslContextFactory.getKeyManagerPassword();
- char[] password = keyManagerPassword == null ? sslContextFactory.getKeyStorePassword().toCharArray() : keyManagerPassword.toCharArray();
- Path[] keyPair = PemExporter.exportKeyPair(keyStore, certAlias, password, pemWorkDirectory);
- privateKeyPemPath = keyPair[0];
- certificateChainPemPath = keyPair[1];
- configuration.getImplementationConfiguration().put(PRIVATE_KEY_PEM_PATH_KEY, privateKeyPemPath.toString());
- configuration.getImplementationConfiguration().put(CERTIFICATE_CHAIN_PEM_PATH_KEY, certificateChainPemPath.toString());
- }
+
+ quicConfig = new ClientQuicConfiguration(sslContextFactory, initQuicConfig.getPemWorkDirectory());
+ addBean(quicConfig);
+
+ quicConfig.setInputBufferSize(initQuicConfig.getInputBufferSize());
+ quicConfig.setOutputBufferSize(initQuicConfig.getOutputBufferSize());
+ quicConfig.setUseInputDirectByteBuffers(initQuicConfig.isUseInputDirectByteBuffers());
+ quicConfig.setUseOutputDirectByteBuffers(initQuicConfig.isUseOutputDirectByteBuffers());
+ quicConfig.setProtocols(initQuicConfig.getProtocols());
+ quicConfig.setDisableActiveMigration(initQuicConfig.isDisableActiveMigration());
+ quicConfig.setMaxBidirectionalRemoteStreams(initQuicConfig.getMaxBidirectionalRemoteStreams());
+ quicConfig.setMaxUnidirectionalRemoteStreams(initQuicConfig.getMaxUnidirectionalRemoteStreams());
+ quicConfig.setSessionRecvWindow(initQuicConfig.getSessionRecvWindow());
+ quicConfig.setBidirectionalStreamRecvWindow(initQuicConfig.getBidirectionalStreamRecvWindow());
+ quicConfig.setUnidirectionalStreamRecvWindow(initQuicConfig.getUnidirectionalStreamRecvWindow());
+ quicConfig.getImplementationConfiguration().putAll(initQuicConfig.getImplementationConfiguration());
+
super.doStart();
}
@Override
- protected void doStop() throws Exception
+ public TransportProtocol newTransportProtocol()
{
- super.doStop();
- deleteFile(privateKeyPemPath);
- privateKeyPemPath = null;
- configuration.getImplementationConfiguration().remove(PRIVATE_KEY_PEM_PATH_KEY);
- deleteFile(certificateChainPemPath);
- certificateChainPemPath = null;
- configuration.getImplementationConfiguration().remove(CERTIFICATE_CHAIN_PEM_PATH_KEY);
- deleteFile(trustedCertificatesPemPath);
- trustedCertificatesPemPath = null;
- configuration.getImplementationConfiguration().remove(TRUSTED_CERTIFICATES_PEM_PATH_KEY);
- }
-
- private void deleteFile(Path file)
- {
- try
- {
- if (file != null)
- Files.delete(file);
- }
- catch (IOException x)
- {
- if (LOG.isDebugEnabled())
- LOG.debug("could not delete {}", file, x);
- }
+ return new QuicTransportProtocol(quicConfig);
}
@Override
@@ -146,7 +111,7 @@ public boolean isIntrinsicallySecure(ClientConnector clientConnector, SocketAddr
@Override
public ChannelWithAddress newChannelWithAddress(ClientConnector clientConnector, SocketAddress address, Map context) throws IOException
{
- context.put(QuicConfiguration.CONTEXT_KEY, configuration);
+ context.put(QuicConfiguration.CONTEXT_KEY, initQuicConfig);
DatagramChannel channel = DatagramChannel.open();
if (clientConnector.getBindAddress() == null)
diff --git a/jetty-core/jetty-quic/jetty-quic-client/src/main/java/org/eclipse/jetty/quic/client/QuicTransportProtocol.java b/jetty-core/jetty-quic/jetty-quic-client/src/main/java/org/eclipse/jetty/quic/client/QuicTransportProtocol.java
new file mode 100644
index 000000000000..6ae1d5da8ea5
--- /dev/null
+++ b/jetty-core/jetty-quic/jetty-quic-client/src/main/java/org/eclipse/jetty/quic/client/QuicTransportProtocol.java
@@ -0,0 +1,81 @@
+//
+// ========================================================================
+// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+// which is available at https://www.apache.org/licenses/LICENSE-2.0.
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+package org.eclipse.jetty.quic.client;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.Objects;
+
+import org.eclipse.jetty.io.ClientConnector;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.TransportProtocol;
+import org.eclipse.jetty.quic.common.QuicConfiguration;
+
+/**
+ * A {@link TransportProtocol} for QUIC that delegates to another {@code TransportProtocol}.
+ * By default, the delegate is {@link TransportProtocol#UDP_IP}, but it may be a different
+ * implementation.
+ */
+public class QuicTransportProtocol extends TransportProtocol.Wrapper
+{
+ private final ClientQuicConfiguration quicConfiguration;
+
+ public QuicTransportProtocol(ClientQuicConfiguration quicConfiguration)
+ {
+ this(UDP_IP, quicConfiguration);
+ }
+
+ public QuicTransportProtocol(TransportProtocol wrapped, ClientQuicConfiguration quicConfiguration)
+ {
+ super(wrapped);
+ this.quicConfiguration = quicConfiguration;
+ }
+
+ @Override
+ public boolean isIntrinsicallySecure()
+ {
+ return true;
+ }
+
+ @Override
+ public Connection newConnection(EndPoint endPoint, Map context) throws IOException
+ {
+ context.put(QuicConfiguration.CONTEXT_KEY, quicConfiguration);
+ ClientConnector clientConnector = (ClientConnector)context.get(ClientConnector.CLIENT_CONNECTOR_CONTEXT_KEY);
+ ClientQuicConnection connection = new ClientQuicConnection(clientConnector, endPoint, context);
+ connection.setInputBufferSize(quicConfiguration.getInputBufferSize());
+ connection.setOutputBufferSize(quicConfiguration.getOutputBufferSize());
+ connection.setUseInputDirectByteBuffers(quicConfiguration.isUseInputDirectByteBuffers());
+ connection.setUseOutputDirectByteBuffers(quicConfiguration.isUseOutputDirectByteBuffers());
+ quicConfiguration.getEventListeners().forEach(connection::addEventListener);
+ return connection;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return getWrapped().hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ return true;
+ if (obj instanceof QuicTransportProtocol that)
+ return Objects.equals(getWrapped(), that.getWrapped());
+ return false;
+ }
+}
diff --git a/jetty-core/jetty-quic/jetty-quic-client/src/test/java/org/eclipse/jetty/quic/client/End2EndClientTest.java b/jetty-core/jetty-quic/jetty-quic-client/src/test/java/org/eclipse/jetty/quic/client/End2EndClientTest.java
index a9a35e429990..526b7d2d3358 100644
--- a/jetty-core/jetty-quic/jetty-quic-client/src/test/java/org/eclipse/jetty/quic/client/End2EndClientTest.java
+++ b/jetty-core/jetty-quic/jetty-quic-client/src/test/java/org/eclipse/jetty/quic/client/End2EndClientTest.java
@@ -74,7 +74,6 @@ public void setUp() throws Exception
{
keyStore.load(is, "storepwd".toCharArray());
}
-
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
sslContextFactory.setKeyStore(keyStore);
sslContextFactory.setKeyStorePassword("storepwd");
@@ -84,6 +83,7 @@ public void setUp() throws Exception
HttpConfiguration httpConfiguration = new HttpConfiguration();
HttpConnectionFactory http1 = new HttpConnectionFactory(httpConfiguration);
HTTP2ServerConnectionFactory http2 = new HTTP2ServerConnectionFactory(httpConfiguration);
+ // Use the deprecated APIs for backwards compatibility testing.
connector = new QuicServerConnector(server, sslContextFactory, http1, http2);
connector.getQuicConfiguration().setPemWorkDirectory(workDir.getEmptyPathDir());
server.addConnector(connector);
@@ -100,9 +100,9 @@ public boolean handle(Request request, Response response, Callback callback)
server.start();
+ // Use the deprecated APIs for backwards compatibility testing.
ClientConnector clientConnector = new ClientConnector(new QuicClientConnectorConfigurator());
- SslContextFactory.Client clientSslContextFactory = new SslContextFactory.Client();
- clientSslContextFactory.setTrustStore(keyStore);
+ SslContextFactory.Client clientSslContextFactory = new SslContextFactory.Client(true);
clientConnector.setSslContextFactory(clientSslContextFactory);
ClientConnectionFactory.Info http1Info = HttpClientConnectionFactory.HTTP11;
ClientConnectionFactoryOverHTTP2.HTTP2 http2Info = new ClientConnectionFactoryOverHTTP2.HTTP2(new HTTP2Client(clientConnector));
diff --git a/jetty-core/jetty-quic/jetty-quic-client/src/test/java/org/eclipse/jetty/quic/client/End2EndClientWithClientCertAuthTest.java b/jetty-core/jetty-quic/jetty-quic-client/src/test/java/org/eclipse/jetty/quic/client/End2EndClientWithClientCertAuthTest.java
index 50a757cf516b..7ff11ef82d98 100644
--- a/jetty-core/jetty-quic/jetty-quic-client/src/test/java/org/eclipse/jetty/quic/client/End2EndClientWithClientCertAuthTest.java
+++ b/jetty-core/jetty-quic/jetty-quic-client/src/test/java/org/eclipse/jetty/quic/client/End2EndClientWithClientCertAuthTest.java
@@ -95,6 +95,7 @@ public void setUp() throws Exception
httpConfiguration.addCustomizer(new SecureRequestCustomizer());
HttpConnectionFactory http1 = new HttpConnectionFactory(httpConfiguration);
HTTP2ServerConnectionFactory http2 = new HTTP2ServerConnectionFactory(httpConfiguration);
+ // Use the deprecated APIs for backwards compatibility testing.
connector = new QuicServerConnector(server, serverSslContextFactory, http1, http2);
connector.getQuicConfiguration().setPemWorkDirectory(serverWorkPath);
server.addConnector(connector);
@@ -111,6 +112,7 @@ public boolean handle(Request request, Response response, Callback callback)
server.start();
+ // Use the deprecated APIs for backwards compatibility testing.
QuicClientConnectorConfigurator configurator = new QuicClientConnectorConfigurator();
configurator.getQuicConfiguration().setPemWorkDirectory(clientWorkPath);
ClientConnector clientConnector = new ClientConnector(configurator);
@@ -154,7 +156,7 @@ public void testServerRejectsClientInvalidCert() throws Exception
server.start();
assertThrows(TimeoutException.class, () -> client.newRequest("https://localhost:" + connector.getLocalPort())
- .timeout(5, TimeUnit.SECONDS)
+ .timeout(1, TimeUnit.SECONDS)
.send());
}
}
diff --git a/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicConfiguration.java b/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicConfiguration.java
index ba74b7648d4b..aeeb9e0a92fb 100644
--- a/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicConfiguration.java
+++ b/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicConfiguration.java
@@ -18,13 +18,22 @@
import java.util.List;
import java.util.Map;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+
/**
* A record that captures QUIC configuration parameters.
*/
-public class QuicConfiguration
+public class QuicConfiguration extends ContainerLifeCycle
{
public static final String CONTEXT_KEY = QuicConfiguration.class.getName();
-
+ public static final String PRIVATE_KEY_PEM_PATH_KEY = CONTEXT_KEY + ".privateKeyPemPath";
+ public static final String CERTIFICATE_CHAIN_PEM_PATH_KEY = CONTEXT_KEY + ".certificateChainPemPath";
+ public static final String TRUSTED_CERTIFICATES_PEM_PATH_KEY = CONTEXT_KEY + ".trustedCertificatesPemPath";
+
+ private int inputBufferSize = 2048;
+ private int outputBufferSize = 2048;
+ private boolean useInputDirectByteBuffers = true;
+ private boolean useOutputDirectByteBuffers = true;
private List protocols = List.of();
private boolean disableActiveMigration;
private int maxBidirectionalRemoteStreams;
@@ -35,6 +44,46 @@ public class QuicConfiguration
private Path pemWorkDirectory;
private final Map implementationConfiguration = new HashMap<>();
+ public int getInputBufferSize()
+ {
+ return inputBufferSize;
+ }
+
+ public void setInputBufferSize(int inputBufferSize)
+ {
+ this.inputBufferSize = inputBufferSize;
+ }
+
+ public int getOutputBufferSize()
+ {
+ return outputBufferSize;
+ }
+
+ public void setOutputBufferSize(int outputBufferSize)
+ {
+ this.outputBufferSize = outputBufferSize;
+ }
+
+ public boolean isUseInputDirectByteBuffers()
+ {
+ return useInputDirectByteBuffers;
+ }
+
+ public void setUseInputDirectByteBuffers(boolean useInputDirectByteBuffers)
+ {
+ this.useInputDirectByteBuffers = useInputDirectByteBuffers;
+ }
+
+ public boolean isUseOutputDirectByteBuffers()
+ {
+ return useOutputDirectByteBuffers;
+ }
+
+ public void setUseOutputDirectByteBuffers(boolean useOutputDirectByteBuffers)
+ {
+ this.useOutputDirectByteBuffers = useOutputDirectByteBuffers;
+ }
+
public List getProtocols()
{
return protocols;
@@ -112,6 +161,8 @@ public Path getPemWorkDirectory()
public void setPemWorkDirectory(Path pemWorkDirectory)
{
+ if (isStarted())
+ throw new IllegalStateException("cannot change PEM working directory after start");
this.pemWorkDirectory = pemWorkDirectory;
}
diff --git a/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicConnection.java b/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicConnection.java
index 0b324ec8a870..c5ff003db1b0 100644
--- a/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicConnection.java
+++ b/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicConnection.java
@@ -14,6 +14,7 @@
package org.eclipse.jetty.quic.common;
import java.io.IOException;
+import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.util.ArrayDeque;
@@ -71,19 +72,11 @@ public abstract class QuicConnection extends AbstractConnection
protected QuicConnection(Executor executor, Scheduler scheduler, ByteBufferPool bufferPool, EndPoint endPoint)
{
super(endPoint, executor);
- if (!(endPoint instanceof DatagramChannelEndPoint))
- throw new IllegalArgumentException("EndPoint must be a " + DatagramChannelEndPoint.class.getSimpleName());
this.scheduler = scheduler;
this.bufferPool = bufferPool;
this.strategy = new AdaptiveExecutionStrategy(new QuicProducer(), getExecutor());
}
- @Override
- public DatagramChannelEndPoint getEndPoint()
- {
- return (DatagramChannelEndPoint)super.getEndPoint();
- }
-
public Scheduler getScheduler()
{
return scheduler;
@@ -211,6 +204,8 @@ public void outwardClose(QuicSession session, Throwable failure)
protected abstract QuicSession createSession(SocketAddress remoteAddress, ByteBuffer cipherBuffer) throws IOException;
+ public abstract InetSocketAddress getLocalInetSocketAddress();
+
public void write(Callback callback, SocketAddress remoteAddress, ByteBuffer... buffers)
{
flusher.offer(callback, remoteAddress, buffers);
@@ -232,10 +227,9 @@ private Runnable receiveAndProcess()
{
BufferUtil.clear(cipherBuffer);
SocketAddress remoteAddress = getEndPoint().receive(cipherBuffer);
- int fill = remoteAddress == DatagramChannelEndPoint.EOF ? -1 : cipherBuffer.remaining();
+ int fill = remoteAddress == EndPoint.EOF ? -1 : cipherBuffer.remaining();
if (LOG.isDebugEnabled())
LOG.debug("filled cipher buffer with {} byte(s)", fill);
- // DatagramChannelEndPoint will only return -1 if input is shut down.
if (fill < 0)
{
buffer.release();
@@ -314,7 +308,7 @@ private Runnable receiveAndProcess()
}
}
- private Runnable process(QuicSession session, SocketAddress remoteAddress, ByteBuffer cipherBuffer)
+ protected Runnable process(QuicSession session, SocketAddress remoteAddress, ByteBuffer cipherBuffer)
{
try
{
diff --git a/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicSession.java b/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicSession.java
index 606f444d9dcc..664522383b34 100644
--- a/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicSession.java
+++ b/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicSession.java
@@ -306,7 +306,7 @@ public Runnable process(SocketAddress remoteAddress, ByteBuffer cipherBufferIn)
int remaining = cipherBufferIn.remaining();
if (LOG.isDebugEnabled())
LOG.debug("feeding {} cipher bytes to {}", remaining, this);
- int accepted = quicheConnection.feedCipherBytes(cipherBufferIn, getLocalAddress(), remoteAddress);
+ int accepted = quicheConnection.feedCipherBytes(cipherBufferIn, connection.getLocalInetSocketAddress(), remoteAddress);
if (accepted != remaining)
throw new IllegalStateException();
diff --git a/jetty-core/jetty-quic/jetty-quic-server/src/main/java/org/eclipse/jetty/quic/server/QuicServerConnectionFactory.java b/jetty-core/jetty-quic/jetty-quic-server/src/main/java/org/eclipse/jetty/quic/server/QuicServerConnectionFactory.java
new file mode 100644
index 000000000000..a93bde24664c
--- /dev/null
+++ b/jetty-core/jetty-quic/jetty-quic-server/src/main/java/org/eclipse/jetty/quic/server/QuicServerConnectionFactory.java
@@ -0,0 +1,74 @@
+//
+// ========================================================================
+// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+// which is available at https://www.apache.org/licenses/LICENSE-2.0.
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+package org.eclipse.jetty.quic.server;
+
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.server.AbstractConnectionFactory;
+import org.eclipse.jetty.server.ConnectionFactory;
+import org.eclipse.jetty.server.Connector;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A {@link ConnectionFactory} for QUIC that can be used by
+ * {@link Connector}s that are not {@link QuicServerConnector}.
+ */
+public class QuicServerConnectionFactory extends AbstractConnectionFactory
+{
+ private static final Logger LOG = LoggerFactory.getLogger(QuicServerConnectionFactory.class);
+
+ private final ServerQuicConfiguration quicConfiguration;
+
+ public QuicServerConnectionFactory(ServerQuicConfiguration quicConfiguration)
+ {
+ super("quic");
+ this.quicConfiguration = quicConfiguration;
+ }
+
+ public ServerQuicConfiguration getQuicConfiguration()
+ {
+ return quicConfiguration;
+ }
+
+ @Override
+ public int getInputBufferSize()
+ {
+ return quicConfiguration.getInputBufferSize();
+ }
+
+ @Override
+ public void setInputBufferSize(int size)
+ {
+ quicConfiguration.setInputBufferSize(size);
+ }
+
+ @Override
+ protected void doStart() throws Exception
+ {
+ LOG.info("HTTP/3+QUIC support is experimental and not suited for production use.");
+ addBean(quicConfiguration);
+ super.doStart();
+ }
+
+ @Override
+ public ServerQuicConnection newConnection(Connector connector, EndPoint endPoint)
+ {
+ ServerQuicConnection connection = new ServerQuicConnection(connector, quicConfiguration, endPoint);
+ connection = configure(connection, connector, endPoint);
+ connection.setOutputBufferSize(quicConfiguration.getOutputBufferSize());
+ connection.setUseInputDirectByteBuffers(quicConfiguration.isUseInputDirectByteBuffers());
+ connection.setUseOutputDirectByteBuffers(quicConfiguration.isUseOutputDirectByteBuffers());
+ return connection;
+ }
+}
diff --git a/jetty-core/jetty-quic/jetty-quic-server/src/main/java/org/eclipse/jetty/quic/server/QuicServerConnector.java b/jetty-core/jetty-quic/jetty-quic-server/src/main/java/org/eclipse/jetty/quic/server/QuicServerConnector.java
index a5726e261ef3..789bda3ee286 100644
--- a/jetty-core/jetty-quic/jetty-quic-server/src/main/java/org/eclipse/jetty/quic/server/QuicServerConnector.java
+++ b/jetty-core/jetty-quic/jetty-quic-server/src/main/java/org/eclipse/jetty/quic/server/QuicServerConnector.java
@@ -20,10 +20,7 @@
import java.nio.channels.SelectionKey;
import java.nio.file.Files;
import java.nio.file.Path;
-import java.security.KeyStore;
import java.util.EventListener;
-import java.util.List;
-import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
@@ -33,12 +30,9 @@
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.ManagedSelector;
import org.eclipse.jetty.io.SelectorManager;
-import org.eclipse.jetty.quic.common.QuicConfiguration;
import org.eclipse.jetty.quic.common.QuicSession;
import org.eclipse.jetty.quic.common.QuicSessionContainer;
import org.eclipse.jetty.quic.common.QuicStreamEndPoint;
-import org.eclipse.jetty.quic.quiche.PemExporter;
-import org.eclipse.jetty.quic.quiche.QuicheConfig;
import org.eclipse.jetty.server.AbstractNetworkConnector;
import org.eclipse.jetty.server.ConnectionFactory;
import org.eclipse.jetty.server.Server;
@@ -48,54 +42,63 @@
/**
* A server side network connector that uses a {@link DatagramChannel} to listen on a network port for QUIC traffic.
- * This connector uses {@link ConnectionFactory}s to configure the protocols to support.
+ *
This connector uses {@link ConnectionFactory}s to configure the protocols to be transported by QUIC.
* The protocol is negotiated during the connection establishment by {@link QuicSession}, and for each QUIC stream
* managed by a {@link QuicSession} a {@link ConnectionFactory} is used to create a {@link Connection} for the
* correspondent {@link QuicStreamEndPoint}.
*
- * @see QuicConfiguration
+ * @see ServerQuicConfiguration
*/
public class QuicServerConnector extends AbstractNetworkConnector
{
- private final QuicConfiguration quicConfiguration = new QuicConfiguration();
private final QuicSessionContainer container = new QuicSessionContainer();
private final ServerDatagramSelectorManager selectorManager;
- private final SslContextFactory.Server sslContextFactory;
- private Path privateKeyPemPath;
- private Path certificateChainPemPath;
- private Path trustedCertificatesPemPath;
+ private final QuicServerConnectionFactory connectionFactory;
private volatile DatagramChannel datagramChannel;
private volatile int localPort = -1;
- private int inputBufferSize = 2048;
- private int outputBufferSize = 2048;
- private boolean useInputDirectByteBuffers = true;
- private boolean useOutputDirectByteBuffers = true;
+ /**
+ * @param server the {@link Server}
+ * @param sslContextFactory the {@link SslContextFactory.Server}
+ * @param factories the {@link ConnectionFactory}s of the protocols transported by QUIC
+ * @deprecated use {@link #QuicServerConnector(Server, ServerQuicConfiguration, ConnectionFactory...)} instead
+ */
+ @Deprecated(since = "12.0.7", forRemoval = true)
public QuicServerConnector(Server server, SslContextFactory.Server sslContextFactory, ConnectionFactory... factories)
{
- this(server, null, null, null, sslContextFactory, factories);
+ this(server, new ServerQuicConfiguration(sslContextFactory, null), factories);
}
+ public QuicServerConnector(Server server, ServerQuicConfiguration quicConfiguration, ConnectionFactory... factories)
+ {
+ this(server, null, null, null, quicConfiguration, factories);
+ }
+
+ /**
+ * @param server the {@link Server}
+ * @param executor the {@link Executor}
+ * @param scheduler the {@link Scheduler}
+ * @param bufferPool the {@link ByteBufferPool}
+ * @param sslContextFactory the {@link SslContextFactory.Server}
+ * @param factories the {@link ConnectionFactory}s of the protocols transported by QUIC
+ * @deprecated use {@link #QuicServerConnector(Server, Executor, Scheduler, ByteBufferPool, ServerQuicConfiguration, ConnectionFactory...)} instead
+ */
+ @Deprecated(since = "12.0.7", forRemoval = true)
public QuicServerConnector(Server server, Executor executor, Scheduler scheduler, ByteBufferPool bufferPool, SslContextFactory.Server sslContextFactory, ConnectionFactory... factories)
+ {
+ this(server, executor, scheduler, bufferPool, new ServerQuicConfiguration(sslContextFactory, null), factories);
+ }
+
+ public QuicServerConnector(Server server, Executor executor, Scheduler scheduler, ByteBufferPool bufferPool, ServerQuicConfiguration quicConfiguration, ConnectionFactory... factories)
{
super(server, executor, scheduler, bufferPool, 0, factories);
this.selectorManager = new ServerDatagramSelectorManager(getExecutor(), getScheduler(), 1);
- addBean(this.selectorManager);
- this.sslContextFactory = sslContextFactory;
- addBean(this.sslContextFactory);
- addBean(quicConfiguration);
- addBean(container);
- // Initialize to sane defaults for a server.
- quicConfiguration.setSessionRecvWindow(4 * 1024 * 1024);
- quicConfiguration.setBidirectionalStreamRecvWindow(2 * 1024 * 1024);
- // One bidirectional stream to simulate the TCP stream, and no unidirectional streams.
- quicConfiguration.setMaxBidirectionalRemoteStreams(1);
- quicConfiguration.setMaxUnidirectionalRemoteStreams(0);
+ this.connectionFactory = new QuicServerConnectionFactory(quicConfiguration);
}
- public QuicConfiguration getQuicConfiguration()
+ public ServerQuicConfiguration getQuicConfiguration()
{
- return quicConfiguration;
+ return connectionFactory.getQuicConfiguration();
}
@Override
@@ -106,42 +109,42 @@ public int getLocalPort()
public int getInputBufferSize()
{
- return inputBufferSize;
+ return getQuicConfiguration().getInputBufferSize();
}
public void setInputBufferSize(int inputBufferSize)
{
- this.inputBufferSize = inputBufferSize;
+ getQuicConfiguration().setInputBufferSize(inputBufferSize);
}
public int getOutputBufferSize()
{
- return outputBufferSize;
+ return getQuicConfiguration().getOutputBufferSize();
}
public void setOutputBufferSize(int outputBufferSize)
{
- this.outputBufferSize = outputBufferSize;
+ getQuicConfiguration().setOutputBufferSize(outputBufferSize);
}
public boolean isUseInputDirectByteBuffers()
{
- return useInputDirectByteBuffers;
+ return getQuicConfiguration().isUseInputDirectByteBuffers();
}
public void setUseInputDirectByteBuffers(boolean useInputDirectByteBuffers)
{
- this.useInputDirectByteBuffers = useInputDirectByteBuffers;
+ getQuicConfiguration().setUseInputDirectByteBuffers(useInputDirectByteBuffers);
}
public boolean isUseOutputDirectByteBuffers()
{
- return useOutputDirectByteBuffers;
+ return getQuicConfiguration().isUseOutputDirectByteBuffers();
}
public void setUseOutputDirectByteBuffers(boolean useOutputDirectByteBuffers)
{
- this.useOutputDirectByteBuffers = useOutputDirectByteBuffers;
+ getQuicConfiguration().setUseOutputDirectByteBuffers(useOutputDirectByteBuffers);
}
@Override
@@ -154,27 +157,18 @@ public boolean isOpen()
@Override
protected void doStart() throws Exception
{
+ addBean(container);
+ addBean(selectorManager);
+ addBean(connectionFactory);
+
for (EventListener l : getBeans(SelectorManager.SelectorManagerListener.class))
selectorManager.addEventListener(l);
+
+ connectionFactory.getQuicConfiguration().setPemWorkDirectory(findPemWorkDirectory());
+
super.doStart();
- selectorManager.accept(datagramChannel);
- Set aliases = sslContextFactory.getAliases();
- if (aliases.isEmpty())
- throw new IllegalStateException("Missing or invalid KeyStore: a SslContextFactory configured with a valid, non-empty KeyStore is required");
- String alias = sslContextFactory.getCertAlias();
- if (alias == null)
- alias = aliases.stream().findFirst().orElseThrow();
- String keyManagerPassword = sslContextFactory.getKeyManagerPassword();
- char[] password = keyManagerPassword == null ? sslContextFactory.getKeyStorePassword().toCharArray() : keyManagerPassword.toCharArray();
- KeyStore keyStore = sslContextFactory.getKeyStore();
- Path certificateWorkPath = findPemWorkDirectory();
- Path[] keyPair = PemExporter.exportKeyPair(keyStore, alias, password, certificateWorkPath);
- privateKeyPemPath = keyPair[0];
- certificateChainPemPath = keyPair[1];
- KeyStore trustStore = sslContextFactory.getTrustStore();
- if (trustStore != null)
- trustedCertificatesPemPath = PemExporter.exportTrustStore(trustStore, certificateWorkPath);
+ selectorManager.accept(datagramChannel);
}
private Path findPemWorkDirectory()
@@ -222,29 +216,6 @@ protected DatagramChannel openDatagramChannel() throws IOException
}
}
- QuicheConfig newQuicheConfig()
- {
- QuicheConfig quicheConfig = new QuicheConfig();
- quicheConfig.setPrivKeyPemPath(privateKeyPemPath.toString());
- quicheConfig.setCertChainPemPath(certificateChainPemPath.toString());
- quicheConfig.setTrustedCertsPemPath(trustedCertificatesPemPath == null ? null : trustedCertificatesPemPath.toString());
- quicheConfig.setVerifyPeer(sslContextFactory.getNeedClientAuth() || sslContextFactory.getWantClientAuth());
- // Idle timeouts must not be managed by Quiche.
- quicheConfig.setMaxIdleTimeout(0L);
- quicheConfig.setInitialMaxData((long)quicConfiguration.getSessionRecvWindow());
- quicheConfig.setInitialMaxStreamDataBidiLocal((long)quicConfiguration.getBidirectionalStreamRecvWindow());
- quicheConfig.setInitialMaxStreamDataBidiRemote((long)quicConfiguration.getBidirectionalStreamRecvWindow());
- quicheConfig.setInitialMaxStreamDataUni((long)quicConfiguration.getUnidirectionalStreamRecvWindow());
- quicheConfig.setInitialMaxStreamsUni((long)quicConfiguration.getMaxUnidirectionalRemoteStreams());
- quicheConfig.setInitialMaxStreamsBidi((long)quicConfiguration.getMaxBidirectionalRemoteStreams());
- quicheConfig.setCongestionControl(QuicheConfig.CongestionControl.CUBIC);
- List protocols = getProtocols();
- // This is only needed for Quiche example clients.
- protocols.add(0, "http/0.9");
- quicheConfig.setApplicationProtos(protocols.toArray(String[]::new));
- return quicheConfig;
- }
-
@Override
public void setIdleTimeout(long idleTimeout)
{
@@ -255,13 +226,6 @@ public void setIdleTimeout(long idleTimeout)
@Override
protected void doStop() throws Exception
{
- deleteFile(privateKeyPemPath);
- privateKeyPemPath = null;
- deleteFile(certificateChainPemPath);
- certificateChainPemPath = null;
- deleteFile(trustedCertificatesPemPath);
- trustedCertificatesPemPath = null;
-
// We want the DatagramChannel to be stopped by the SelectorManager.
super.doStop();
@@ -269,24 +233,12 @@ protected void doStop() throws Exception
datagramChannel = null;
localPort = -2;
+ removeBean(connectionFactory);
+
for (EventListener l : getBeans(EventListener.class))
selectorManager.removeEventListener(l);
}
- private void deleteFile(Path file)
- {
- try
- {
- if (file != null)
- Files.delete(file);
- }
- catch (IOException x)
- {
- if (LOG.isDebugEnabled())
- LOG.debug("could not delete {}", file, x);
- }
- }
-
@Override
public CompletableFuture shutdown()
{
@@ -312,7 +264,7 @@ protected EndPoint newEndPoint(DatagramChannel channel, ManagedSelector selector
protected ServerQuicConnection newConnection(EndPoint endpoint)
{
- return new ServerQuicConnection(QuicServerConnector.this, endpoint);
+ return connectionFactory.newConnection(QuicServerConnector.this, endpoint);
}
private class ServerDatagramSelectorManager extends SelectorManager
@@ -333,13 +285,7 @@ protected EndPoint newEndPoint(SelectableChannel channel, ManagedSelector select
@Override
public Connection newConnection(SelectableChannel channel, EndPoint endpoint, Object attachment)
{
- ServerQuicConnection connection = QuicServerConnector.this.newConnection(endpoint);
- connection.addEventListener(container);
- connection.setInputBufferSize(getInputBufferSize());
- connection.setOutputBufferSize(getOutputBufferSize());
- connection.setUseInputDirectByteBuffers(isUseInputDirectByteBuffers());
- connection.setUseOutputDirectByteBuffers(isUseOutputDirectByteBuffers());
- return connection;
+ return QuicServerConnector.this.newConnection(endpoint);
}
@Override
diff --git a/jetty-core/jetty-quic/jetty-quic-server/src/main/java/org/eclipse/jetty/quic/server/ServerQuicConfiguration.java b/jetty-core/jetty-quic/jetty-quic-server/src/main/java/org/eclipse/jetty/quic/server/ServerQuicConfiguration.java
new file mode 100644
index 000000000000..00d959cda03a
--- /dev/null
+++ b/jetty-core/jetty-quic/jetty-quic-server/src/main/java/org/eclipse/jetty/quic/server/ServerQuicConfiguration.java
@@ -0,0 +1,111 @@
+//
+// ========================================================================
+// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+// which is available at https://www.apache.org/licenses/LICENSE-2.0.
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+package org.eclipse.jetty.quic.server;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.KeyStore;
+import java.util.Set;
+
+import org.eclipse.jetty.quic.common.QuicConfiguration;
+import org.eclipse.jetty.quic.quiche.PemExporter;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Server-side {@link QuicConfiguration} with server-specific settings.
+ * The PEM working directory constructor argument is mandatory, although
+ * it may be set after construction via {@link #setPemWorkDirectory(Path)}
+ * before starting this instance.
+ */
+public class ServerQuicConfiguration extends QuicConfiguration
+{
+ private static final Logger LOG = LoggerFactory.getLogger(ServerQuicConfiguration.class);
+
+ private final SslContextFactory.Server sslContextFactory;
+
+ public ServerQuicConfiguration(SslContextFactory.Server sslContextFactory, Path pemWorkDirectory)
+ {
+ this.sslContextFactory = sslContextFactory;
+ setPemWorkDirectory(pemWorkDirectory);
+ setSessionRecvWindow(4 * 1024 * 1024);
+ setBidirectionalStreamRecvWindow(2 * 1024 * 1024);
+ // One bidirectional stream to simulate the TCP stream, and no unidirectional streams.
+ setMaxBidirectionalRemoteStreams(1);
+ setMaxUnidirectionalRemoteStreams(0);
+ }
+
+ public SslContextFactory.Server getSslContextFactory()
+ {
+ return sslContextFactory;
+ }
+
+ @Override
+ protected void doStart() throws Exception
+ {
+ addBean(sslContextFactory);
+
+ super.doStart();
+
+ Path pemWorkDirectory = getPemWorkDirectory();
+ Set aliases = sslContextFactory.getAliases();
+ if (aliases.isEmpty())
+ throw new IllegalStateException("Missing or invalid KeyStore: a SslContextFactory configured with a valid, non-empty KeyStore is required");
+ String alias = sslContextFactory.getCertAlias();
+ if (alias == null)
+ alias = aliases.stream().findFirst().orElseThrow();
+ String keyManagerPassword = sslContextFactory.getKeyManagerPassword();
+ char[] password = keyManagerPassword == null ? sslContextFactory.getKeyStorePassword().toCharArray() : keyManagerPassword.toCharArray();
+ KeyStore keyStore = sslContextFactory.getKeyStore();
+ Path[] keyPair = PemExporter.exportKeyPair(keyStore, alias, password, pemWorkDirectory);
+ Path privateKeyPemPath = keyPair[0];
+ getImplementationConfiguration().put(PRIVATE_KEY_PEM_PATH_KEY, privateKeyPemPath);
+ Path certificateChainPemPath = keyPair[1];
+ getImplementationConfiguration().put(CERTIFICATE_CHAIN_PEM_PATH_KEY, certificateChainPemPath);
+ KeyStore trustStore = sslContextFactory.getTrustStore();
+ if (trustStore != null)
+ {
+ Path trustedCertificatesPemPath = PemExporter.exportTrustStore(trustStore, pemWorkDirectory);
+ getImplementationConfiguration().put(TRUSTED_CERTIFICATES_PEM_PATH_KEY, trustedCertificatesPemPath);
+ }
+ }
+
+ @Override
+ protected void doStop() throws Exception
+ {
+ super.doStop();
+
+ Path trustedCertificatesPemPath = (Path)getImplementationConfiguration().remove(TRUSTED_CERTIFICATES_PEM_PATH_KEY);
+ deleteFile(trustedCertificatesPemPath);
+ Path certificateChainPemPath = (Path)getImplementationConfiguration().remove(CERTIFICATE_CHAIN_PEM_PATH_KEY);
+ deleteFile(certificateChainPemPath);
+ Path privateKeyPemPath = (Path)getImplementationConfiguration().remove(PRIVATE_KEY_PEM_PATH_KEY);
+ deleteFile(privateKeyPemPath);
+ }
+
+ private void deleteFile(Path path)
+ {
+ try
+ {
+ if (path != null)
+ Files.delete(path);
+ }
+ catch (Throwable x)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("could not delete {}", path, x);
+ }
+ }
+}
diff --git a/jetty-core/jetty-quic/jetty-quic-server/src/main/java/org/eclipse/jetty/quic/server/ServerQuicConnection.java b/jetty-core/jetty-quic/jetty-quic-server/src/main/java/org/eclipse/jetty/quic/server/ServerQuicConnection.java
index 08a212857fef..ce9d77f44844 100644
--- a/jetty-core/jetty-quic/jetty-quic-server/src/main/java/org/eclipse/jetty/quic/server/ServerQuicConnection.java
+++ b/jetty-core/jetty-quic/jetty-quic-server/src/main/java/org/eclipse/jetty/quic/server/ServerQuicConnection.java
@@ -14,23 +14,32 @@
package org.eclipse.jetty.quic.server;
import java.io.IOException;
+import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
+import java.nio.file.Path;
import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeoutException;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.CyclicTimeouts;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.RetainableByteBuffer;
+import org.eclipse.jetty.quic.common.QuicConfiguration;
import org.eclipse.jetty.quic.common.QuicConnection;
import org.eclipse.jetty.quic.common.QuicSession;
+import org.eclipse.jetty.quic.quiche.QuicheConfig;
import org.eclipse.jetty.quic.quiche.QuicheConnection;
import org.eclipse.jetty.quic.server.internal.SimpleTokenMinter;
import org.eclipse.jetty.quic.server.internal.SimpleTokenValidator;
+import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.Scheduler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -42,17 +51,22 @@ public class ServerQuicConnection extends QuicConnection
{
private static final Logger LOG = LoggerFactory.getLogger(ServerQuicConnection.class);
- private final QuicServerConnector connector;
+ private final Map remoteSocketAddresses = new ConcurrentHashMap<>();
+ private final Connector connector;
+ private final ServerQuicConfiguration quicConfiguration;
private final SessionTimeouts sessionTimeouts;
+ private final InetSocketAddress inetLocalAddress;
- public ServerQuicConnection(QuicServerConnector connector, EndPoint endPoint)
+ public ServerQuicConnection(Connector connector, ServerQuicConfiguration quicConfiguration, EndPoint endPoint)
{
super(connector.getExecutor(), connector.getScheduler(), connector.getByteBufferPool(), endPoint);
this.connector = connector;
+ this.quicConfiguration = quicConfiguration;
this.sessionTimeouts = new SessionTimeouts(connector.getScheduler());
+ this.inetLocalAddress = endPoint.getLocalSocketAddress() instanceof InetSocketAddress inet ? inet : new InetSocketAddress(InetAddress.getLoopbackAddress(), 443);
}
- public QuicServerConnector getQuicServerConnector()
+ public Connector getQuicServerConnector()
{
return connector;
}
@@ -64,19 +78,27 @@ public void onOpen()
fillInterested();
}
+ private InetSocketAddress toInetSocketAddress(SocketAddress socketAddress)
+ {
+ if (socketAddress instanceof InetSocketAddress inet)
+ return inet;
+ return remoteSocketAddresses.computeIfAbsent(socketAddress, key -> new InetSocketAddress(InetAddress.getLoopbackAddress(), 0xFA93));
+ }
+
@Override
protected QuicSession createSession(SocketAddress remoteAddress, ByteBuffer cipherBuffer) throws IOException
{
+ InetSocketAddress inetRemote = toInetSocketAddress(remoteAddress);
ByteBufferPool bufferPool = getByteBufferPool();
// TODO make the token validator configurable
- QuicheConnection quicheConnection = QuicheConnection.tryAccept(connector.newQuicheConfig(), new SimpleTokenValidator((InetSocketAddress)remoteAddress), cipherBuffer, getEndPoint().getLocalAddress(), remoteAddress);
+ QuicheConnection quicheConnection = QuicheConnection.tryAccept(newQuicheConfig(), new SimpleTokenValidator(inetRemote), cipherBuffer, inetLocalAddress, inetRemote);
if (quicheConnection == null)
{
RetainableByteBuffer negotiationBuffer = bufferPool.acquire(getOutputBufferSize(), true);
ByteBuffer byteBuffer = negotiationBuffer.getByteBuffer();
int pos = BufferUtil.flipToFill(byteBuffer);
// TODO make the token minter configurable
- if (!QuicheConnection.negotiate(new SimpleTokenMinter((InetSocketAddress)remoteAddress), cipherBuffer, byteBuffer))
+ if (!QuicheConnection.negotiate(new SimpleTokenMinter(inetRemote), cipherBuffer, byteBuffer))
{
if (LOG.isDebugEnabled())
LOG.debug("QUIC connection negotiation failed, dropping packet");
@@ -104,6 +126,48 @@ protected ServerQuicSession newQuicSession(SocketAddress remoteAddress, QuicheCo
return new ServerQuicSession(getExecutor(), getScheduler(), getByteBufferPool(), quicheConnection, this, remoteAddress, getQuicServerConnector());
}
+ @Override
+ public InetSocketAddress getLocalInetSocketAddress()
+ {
+ return inetLocalAddress;
+ }
+
+ @Override
+ protected Runnable process(QuicSession session, SocketAddress remoteAddress, ByteBuffer cipherBuffer)
+ {
+ InetSocketAddress inetRemote = toInetSocketAddress(remoteAddress);
+ return super.process(session, inetRemote, cipherBuffer);
+ }
+
+ private QuicheConfig newQuicheConfig()
+ {
+ QuicheConfig quicheConfig = new QuicheConfig();
+ Map implConfig = quicConfiguration.getImplementationConfiguration();
+ Path privateKeyPath = (Path)implConfig.get(QuicConfiguration.PRIVATE_KEY_PEM_PATH_KEY);
+ quicheConfig.setPrivKeyPemPath(privateKeyPath.toString());
+ Path certificatesPath = (Path)implConfig.get(QuicConfiguration.CERTIFICATE_CHAIN_PEM_PATH_KEY);
+ quicheConfig.setCertChainPemPath(certificatesPath.toString());
+ Path trustedCertificatesPath = (Path)implConfig.get(QuicConfiguration.TRUSTED_CERTIFICATES_PEM_PATH_KEY);
+ if (trustedCertificatesPath != null)
+ quicheConfig.setTrustedCertsPemPath(trustedCertificatesPath.toString());
+ SslContextFactory.Server sslContextFactory = quicConfiguration.getSslContextFactory();
+ quicheConfig.setVerifyPeer(sslContextFactory.getNeedClientAuth() || sslContextFactory.getWantClientAuth());
+ // Idle timeouts must not be managed by Quiche.
+ quicheConfig.setMaxIdleTimeout(0L);
+ quicheConfig.setInitialMaxData((long)quicConfiguration.getSessionRecvWindow());
+ quicheConfig.setInitialMaxStreamDataBidiLocal((long)quicConfiguration.getBidirectionalStreamRecvWindow());
+ quicheConfig.setInitialMaxStreamDataBidiRemote((long)quicConfiguration.getBidirectionalStreamRecvWindow());
+ quicheConfig.setInitialMaxStreamDataUni((long)quicConfiguration.getUnidirectionalStreamRecvWindow());
+ quicheConfig.setInitialMaxStreamsUni((long)quicConfiguration.getMaxUnidirectionalRemoteStreams());
+ quicheConfig.setInitialMaxStreamsBidi((long)quicConfiguration.getMaxBidirectionalRemoteStreams());
+ quicheConfig.setCongestionControl(QuicheConfig.CongestionControl.CUBIC);
+ List protocols = connector.getProtocols();
+ // This is only needed for Quiche example clients.
+ protocols.add(0, "http/0.9");
+ quicheConfig.setApplicationProtos(protocols.toArray(String[]::new));
+ return quicheConfig;
+ }
+
public void schedule(ServerQuicSession session)
{
sessionTimeouts.schedule(session);
diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnectionFactory.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnectionFactory.java
index 019da630d8ef..130324f1ef89 100644
--- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnectionFactory.java
+++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnectionFactory.java
@@ -90,16 +90,16 @@ protected static String findNextProtocol(Connector connector, String currentProt
return nextProtocol;
}
- protected AbstractConnection configure(AbstractConnection connection, Connector connector, EndPoint endPoint)
+ protected T configure(T connection, Connector connector, EndPoint endPoint)
{
- connection.setInputBufferSize(getInputBufferSize());
-
- // Add Connection.Listeners from Connector
+ // Add Connection.Listeners from Connector.
connector.getEventListeners().forEach(connection::addEventListener);
- // Add Connection.Listeners from this factory
+ // Add Connection.Listeners from this factory.
getEventListeners().forEach(connection::addEventListener);
+ connection.setInputBufferSize(getInputBufferSize());
+
return connection;
}
diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/MemoryConnector.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/MemoryConnector.java
new file mode 100644
index 000000000000..2df1a2c6dabc
--- /dev/null
+++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/MemoryConnector.java
@@ -0,0 +1,203 @@
+//
+// ========================================================================
+// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+// which is available at https://www.apache.org/licenses/LICENSE-2.0.
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.net.SocketAddress;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.Executor;
+
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.MemoryEndPointPipe;
+import org.eclipse.jetty.util.thread.ExecutionStrategy;
+import org.eclipse.jetty.util.thread.Invocable;
+import org.eclipse.jetty.util.thread.Scheduler;
+import org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A server {@link Connector} that allows clients to communicate via memory.
+ * Typical usage on the server-side:
+ * {@code
+ * Server server = new Server();
+ * MemoryConnector memoryConnector = new MemoryConnector(server, new HttpConnectionFactory());
+ * server.addConnector(memoryConnector);
+ * server.start();
+ * }
+ * Typical usage on the client-side:
+ * {@code
+ * // Connect to the server and get the local, client-side, EndPoint.
+ * EndPoint clientEndPoint = memoryConnector.connect().getLocalEndPoint();
+ *
+ * // Be ready to read responses.
+ * Callback readCallback = ...;
+ * clientEndPoint.fillInterested(readCallback);
+ *
+ * // Write a request to the server.
+ * ByteBuffer request = StandardCharsets.UTF_8.encode("""
+ * GET / HTTP/1.1
+ * Host: localhost
+ *
+ * """);
+ * Callback.Completable writeCallback = new Callback.Completable();
+ * clientEndPoint.write(writeCallback, request);
+ * }
+ */
+public class MemoryConnector extends AbstractConnector
+{
+ private static final Logger LOG = LoggerFactory.getLogger(MemoryConnector.class);
+
+ private final SocketAddress socketAddress = new MemorySocketAddress();
+ private final TaskProducer producer = new TaskProducer();
+ private ExecutionStrategy strategy;
+
+ public MemoryConnector(Server server, ConnectionFactory... factories)
+ {
+ this(server, null, null, null, factories);
+ }
+
+ public MemoryConnector(Server server, Executor executor, Scheduler scheduler, ByteBufferPool bufferPool, ConnectionFactory... factories)
+ {
+ super(server, executor, scheduler, bufferPool, 0, factories);
+ }
+
+ @Override
+ protected void doStart() throws Exception
+ {
+ strategy = new AdaptiveExecutionStrategy(producer, getExecutor());
+ addBean(strategy);
+ super.doStart();
+ }
+
+ @Override
+ protected void doStop() throws Exception
+ {
+ super.doStop();
+ removeBean(strategy);
+ }
+
+ @Override
+ public Object getTransport()
+ {
+ return null;
+ }
+
+ @Override
+ protected void accept(int acceptorID) throws IOException, InterruptedException
+ {
+ // Nothing to do here.
+ }
+
+ /**
+ * Client-side applications use this method to connect to the server and obtain a {@link EndPoint.Pipe}.
+ * Client-side applications should then use {@link EndPoint.Pipe#getLocalEndPoint()} to access the
+ * client-side {@link EndPoint} to write requests bytes to the server and read response bytes.
+ *
+ * @return a {@link EndPoint.Pipe} representing the connection between client and server
+ */
+ public EndPoint.Pipe connect()
+ {
+ MemoryEndPointPipe pipe = new MemoryEndPointPipe(getScheduler(), producer::offer, socketAddress);
+ accept(pipe.getRemoteEndPoint());
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("connected {} to {}", pipe, this);
+
+ return pipe;
+ }
+
+ private void accept(EndPoint endPoint)
+ {
+ endPoint.setIdleTimeout(getIdleTimeout());
+
+ AbstractConnection connection = (AbstractConnection)getDefaultConnectionFactory().newConnection(this, endPoint);
+ endPoint.setConnection(connection);
+
+ endPoint.onOpen();
+ onEndPointOpened(endPoint);
+
+ connection.addEventListener(new Connection.Listener()
+ {
+ @Override
+ public void onClosed(Connection connection)
+ {
+ onEndPointClosed(endPoint);
+ }
+ });
+
+ connection.onOpen();
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("accepted {} in {}", endPoint, this);
+ }
+
+ /**
+ * @return the local {@link SocketAddress} of this connector
+ */
+ public SocketAddress getLocalSocketAddress()
+ {
+ return socketAddress;
+ }
+
+ private class TaskProducer implements ExecutionStrategy.Producer
+ {
+ private final Queue tasks = new ConcurrentLinkedQueue<>();
+
+ @Override
+ public Runnable produce()
+ {
+ return tasks.poll();
+ }
+
+ private void offer(Invocable.Task task)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("offer {} to {}", task, MemoryConnector.this);
+ tasks.offer(task);
+ strategy.produce();
+ }
+ }
+
+ private class MemorySocketAddress extends SocketAddress
+ {
+ private final String address = "[memory:@%x]".formatted(System.identityHashCode(MemoryConnector.this));
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ return true;
+ if (obj instanceof MemorySocketAddress that)
+ return address.equals(that.address);
+ return false;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return address.hashCode();
+ }
+
+ @Override
+ public String toString()
+ {
+ return address;
+ }
+ }
+}
diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/MemoryTransportProtocol.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/MemoryTransportProtocol.java
new file mode 100644
index 000000000000..9ad145357a45
--- /dev/null
+++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/MemoryTransportProtocol.java
@@ -0,0 +1,89 @@
+//
+// ========================================================================
+// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+// which is available at https://www.apache.org/licenses/LICENSE-2.0.
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.net.SocketAddress;
+import java.util.Map;
+import java.util.Objects;
+
+import org.eclipse.jetty.io.ClientConnector;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.TransportProtocol;
+import org.eclipse.jetty.util.Promise;
+
+/**
+ * A {@link TransportProtocol} suitable to be used when using a {@link MemoryConnector}.
+ */
+public class MemoryTransportProtocol implements TransportProtocol
+{
+ private final MemoryConnector connector;
+
+ public MemoryTransportProtocol(MemoryConnector connector)
+ {
+ this.connector = connector;
+ }
+
+ @Override
+ public void connect(SocketAddress socketAddress, Map context)
+ {
+ @SuppressWarnings("unchecked")
+ Promise promise = (Promise)context.get(ClientConnector.CONNECTION_PROMISE_CONTEXT_KEY);
+ try
+ {
+ EndPoint endPoint = connector.connect().getLocalEndPoint();
+ ClientConnector clientConnector = (ClientConnector)context.get(ClientConnector.CLIENT_CONNECTOR_CONTEXT_KEY);
+ endPoint.setIdleTimeout(clientConnector.getIdleTimeout().toMillis());
+
+ // This instance may be nested inside other TransportProtocol instances.
+ // Retrieve the outermost instance to call newConnection().
+ TransportProtocol transportProtocol = (TransportProtocol)context.get(TransportProtocol.class.getName());
+ Connection connection = transportProtocol.newConnection(endPoint, context);
+ endPoint.setConnection(connection);
+
+ endPoint.onOpen();
+ connection.onOpen();
+
+ // TODO: move this to Connection.onOpen(), see
+ // ClientSelectorManager.connectionOpened()
+ promise.succeeded(connection);
+ }
+ catch (Throwable x)
+ {
+ promise.failed(x);
+ }
+ }
+
+ @Override
+ public SocketAddress getSocketAddress()
+ {
+ return connector.getLocalSocketAddress();
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return Objects.hash(connector);
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ return true;
+ if (obj instanceof MemoryTransportProtocol that)
+ return Objects.equals(connector, that.connector);
+ return false;
+ }
+}
diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/SslConnectionFactory.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/SslConnectionFactory.java
index 9246f05b3677..aa894af0c3a5 100644
--- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/SslConnectionFactory.java
+++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/SslConnectionFactory.java
@@ -169,7 +169,7 @@ protected SslConnection newSslConnection(Connector connector, EndPoint endPoint,
}
@Override
- protected AbstractConnection configure(AbstractConnection connection, Connector connector, EndPoint endPoint)
+ protected T configure(T connection, Connector connector, EndPoint endPoint)
{
if (connection instanceof SslConnection sslConnection)
{
diff --git a/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/AbstractTest.java b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/AbstractTest.java
index eee7f0a138b4..b51f938ea72f 100644
--- a/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/AbstractTest.java
+++ b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/AbstractTest.java
@@ -13,13 +13,11 @@
package org.eclipse.jetty.test.client.transport;
-import java.io.InputStream;
import java.lang.management.ManagementFactory;
import java.lang.reflect.AnnotatedElement;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
-import java.security.KeyStore;
import java.util.Collection;
import java.util.Comparator;
import java.util.EnumSet;
@@ -45,10 +43,11 @@
import org.eclipse.jetty.http3.client.transport.HttpClientTransportOverHTTP3;
import org.eclipse.jetty.http3.server.AbstractHTTP3ServerConnectionFactory;
import org.eclipse.jetty.http3.server.HTTP3ServerConnectionFactory;
-import org.eclipse.jetty.http3.server.HTTP3ServerConnector;
import org.eclipse.jetty.io.ArrayByteBufferPool;
import org.eclipse.jetty.io.ClientConnector;
+import org.eclipse.jetty.quic.client.ClientQuicConfiguration;
import org.eclipse.jetty.quic.server.QuicServerConnector;
+import org.eclipse.jetty.quic.server.ServerQuicConfiguration;
import org.eclipse.jetty.server.AbstractConnector;
import org.eclipse.jetty.server.ConnectionFactory;
import org.eclipse.jetty.server.Handler;
@@ -60,9 +59,9 @@
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SslConnectionFactory;
+import org.eclipse.jetty.toolchain.test.MavenPaths;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
-import org.eclipse.jetty.unixdomain.server.UnixDomainServerConnector;
import org.eclipse.jetty.util.SocketAddressResolver;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.ssl.SslContextFactory;
@@ -76,7 +75,6 @@
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.RegisterExtension;
-import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
@ExtendWith(WorkDirExtension.class)
@@ -89,10 +87,10 @@ public class AbstractTest
System.err.printf("Running %s.%s() %s%n", context.getRequiredTestClass().getSimpleName(), context.getRequiredTestMethod().getName(), context.getDisplayName());
protected final HttpConfiguration httpConfig = new HttpConfiguration();
protected SslContextFactory.Server sslContextFactoryServer;
+ protected ServerQuicConfiguration serverQuicConfig;
protected Server server;
protected AbstractConnector connector;
protected HttpClient client;
- protected Path unixDomainPath;
protected ArrayByteBufferPool.Tracking serverBufferPool;
protected ArrayByteBufferPool.Tracking clientBufferPool;
@@ -111,18 +109,10 @@ public static Collection transportsNoFCGI()
return transports;
}
- public static Collection transportsNoUnixDomain()
- {
- Collection transports = transports();
- transports.remove(Transport.UNIX_DOMAIN);
- return transports;
- }
-
public static Collection transportsTCP()
{
Collection transports = transports();
transports.remove(Transport.H3);
- transports.remove(Transport.UNIX_DOMAIN);
return transports;
}
@@ -177,7 +167,7 @@ private static boolean isLeakTrackingDisabled(TestInfo testInfo, String tagSubVa
String[] transportNames = transports.split("\\|");
boolean disabled = isAnnotatedWithTagValue(testInfo.getTestMethod().orElseThrow(), disableLeakTrackingTagValue) ||
- isAnnotatedWithTagValue(testInfo.getTestClass().orElseThrow(), disableLeakTrackingTagValue);
+ isAnnotatedWithTagValue(testInfo.getTestClass().orElseThrow(), disableLeakTrackingTagValue);
if (disabled)
{
System.err.println("Not tracking " + tagSubValue + " leaks");
@@ -187,7 +177,7 @@ private static boolean isLeakTrackingDisabled(TestInfo testInfo, String tagSubVa
for (String transportName : transportNames)
{
disabled = isAnnotatedWithTagValue(testInfo.getTestMethod().orElseThrow(), disableLeakTrackingTagValue + ":" + transportName) ||
- isAnnotatedWithTagValue(testInfo.getTestClass().orElseThrow(), disableLeakTrackingTagValue + ":" + transportName);
+ isAnnotatedWithTagValue(testInfo.getTestClass().orElseThrow(), disableLeakTrackingTagValue + ":" + transportName);
if (disabled)
{
System.err.println("Not tracking " + tagSubValue + " leaks for transport " + transportName);
@@ -196,7 +186,7 @@ private static boolean isLeakTrackingDisabled(TestInfo testInfo, String tagSubVa
}
disabled = isAnnotatedWithTagValue(testInfo.getTestMethod().orElseThrow(), disableLeakTrackingTagValue + ":" + tagSubValue) ||
- isAnnotatedWithTagValue(testInfo.getTestClass().orElseThrow(), disableLeakTrackingTagValue + ":" + tagSubValue);
+ isAnnotatedWithTagValue(testInfo.getTestClass().orElseThrow(), disableLeakTrackingTagValue + ":" + tagSubValue);
if (disabled)
{
System.err.println("Not tracking " + tagSubValue + " leaks");
@@ -206,7 +196,7 @@ private static boolean isLeakTrackingDisabled(TestInfo testInfo, String tagSubVa
for (String transportName : transportNames)
{
disabled = isAnnotatedWithTagValue(testInfo.getTestMethod().orElseThrow(), disableLeakTrackingTagValue + ":" + tagSubValue + ":" + transportName) ||
- isAnnotatedWithTagValue(testInfo.getTestClass().orElseThrow(), disableLeakTrackingTagValue + ":" + tagSubValue + ":" + transportName);
+ isAnnotatedWithTagValue(testInfo.getTestClass().orElseThrow(), disableLeakTrackingTagValue + ":" + tagSubValue + ":" + transportName);
if (disabled)
{
System.err.println("Not tracking " + tagSubValue + " leaks for transport " + transportName);
@@ -272,14 +262,8 @@ protected void startServer(Transport transport, Handler handler) throws Exceptio
protected void prepareServer(Transport transport, Handler handler) throws Exception
{
- if (transport == Transport.UNIX_DOMAIN)
- {
- String unixDomainDir = System.getProperty("jetty.unixdomain.dir", System.getProperty("java.io.tmpdir"));
- unixDomainPath = Files.createTempFile(Path.of(unixDomainDir), "unix_", ".sock");
- assertTrue(unixDomainPath.toAbsolutePath().toString().length() < UnixDomainServerConnector.MAX_UNIX_DOMAIN_PATH_LENGTH, "Unix-Domain path too long");
- Files.delete(unixDomainPath);
- }
sslContextFactoryServer = newSslContextFactoryServer();
+ serverQuicConfig = new ServerQuicConfiguration(sslContextFactoryServer, workDir.getEmptyPathDir());
if (server == null)
server = newServer();
connector = newConnector(transport, server);
@@ -295,27 +279,16 @@ protected Server newServer()
return new Server(serverThreads, null, serverBufferPool);
}
- protected SslContextFactory.Server newSslContextFactoryServer() throws Exception
+ protected SslContextFactory.Server newSslContextFactoryServer()
{
SslContextFactory.Server ssl = new SslContextFactory.Server();
- configureSslContextFactory(ssl);
+ ssl.setKeyStorePath(MavenPaths.findTestResourceFile("keystore.p12").toString());
+ ssl.setKeyStorePassword("storepwd");
+ ssl.setUseCipherSuitesOrder(true);
+ ssl.setCipherComparator(HTTP2Cipher.COMPARATOR);
return ssl;
}
- private void configureSslContextFactory(SslContextFactory sslContextFactory) throws Exception
- {
- KeyStore keystore = KeyStore.getInstance("PKCS12");
- try (InputStream is = Files.newInputStream(Path.of("src/test/resources/keystore.p12")))
- {
- keystore.load(is, "storepwd".toCharArray());
- }
- sslContextFactory.setTrustStore(keystore);
- sslContextFactory.setKeyStore(keystore);
- sslContextFactory.setKeyStorePassword("storepwd");
- sslContextFactory.setUseCipherSuitesOrder(true);
- sslContextFactory.setCipherComparator(HTTP2Cipher.COMPARATOR);
- }
-
protected void startClient(Transport transport) throws Exception
{
QueuedThreadPool clientThreads = new QueuedThreadPool();
@@ -339,13 +312,7 @@ public AbstractConnector newConnector(Transport transport, Server server)
case FCGI:
yield new ServerConnector(server, 1, 1, newServerConnectionFactory(transport));
case H3:
- HTTP3ServerConnector h3Connector = new HTTP3ServerConnector(server, sslContextFactoryServer, newServerConnectionFactory(transport));
- h3Connector.getQuicConfiguration().setPemWorkDirectory(workDir.getEmptyPathDir());
- yield h3Connector;
- case UNIX_DOMAIN:
- UnixDomainServerConnector unixConnector = new UnixDomainServerConnector(server, 1, 1, newServerConnectionFactory(transport));
- unixConnector.setUnixDomainPath(unixDomainPath);
- yield unixConnector;
+ yield new QuicServerConnector(server, serverQuicConfig, newServerConnectionFactory(transport));
};
}
@@ -353,7 +320,7 @@ protected ConnectionFactory[] newServerConnectionFactory(Transport transport)
{
List list = switch (transport)
{
- case HTTP, UNIX_DOMAIN -> List.of(new HttpConnectionFactory(httpConfig));
+ case HTTP -> List.of(new HttpConnectionFactory(httpConfig));
case HTTPS ->
{
httpConfig.addCustomizer(new SecureRequestCustomizer());
@@ -379,57 +346,47 @@ protected ConnectionFactory[] newServerConnectionFactory(Transport transport)
{
httpConfig.addCustomizer(new SecureRequestCustomizer());
httpConfig.addCustomizer(new HostHeaderCustomizer());
- yield List.of(new HTTP3ServerConnectionFactory(httpConfig));
+ yield List.of(new HTTP3ServerConnectionFactory(serverQuicConfig, httpConfig));
}
case FCGI -> List.of(new ServerFCGIConnectionFactory(httpConfig));
};
return list.toArray(ConnectionFactory[]::new);
}
- protected SslContextFactory.Client newSslContextFactoryClient() throws Exception
+ protected SslContextFactory.Client newSslContextFactoryClient()
{
- SslContextFactory.Client ssl = new SslContextFactory.Client();
- configureSslContextFactory(ssl);
- ssl.setEndpointIdentificationAlgorithm(null);
- return ssl;
+ return new SslContextFactory.Client(true);
}
protected HttpClientTransport newHttpClientTransport(Transport transport) throws Exception
{
return switch (transport)
+ {
+ case HTTP, HTTPS ->
{
- case HTTP, HTTPS ->
- {
- ClientConnector clientConnector = new ClientConnector();
- clientConnector.setSelectors(1);
- clientConnector.setSslContextFactory(newSslContextFactoryClient());
- yield new HttpClientTransportOverHTTP(clientConnector);
- }
- case H2C, H2 ->
- {
- ClientConnector clientConnector = new ClientConnector();
- clientConnector.setSelectors(1);
- clientConnector.setSslContextFactory(newSslContextFactoryClient());
- HTTP2Client http2Client = new HTTP2Client(clientConnector);
- yield new HttpClientTransportOverHTTP2(http2Client);
- }
- case H3 ->
- {
- HTTP3Client http3Client = new HTTP3Client();
- ClientConnector clientConnector = http3Client.getClientConnector();
- clientConnector.setSelectors(1);
- clientConnector.setSslContextFactory(newSslContextFactoryClient());
- yield new HttpClientTransportOverHTTP3(http3Client);
- }
- case FCGI -> new HttpClientTransportOverFCGI(1, "");
- case UNIX_DOMAIN ->
- {
- ClientConnector clientConnector = ClientConnector.forUnixDomain(unixDomainPath);
- clientConnector.setSelectors(1);
- clientConnector.setSslContextFactory(newSslContextFactoryClient());
- yield new HttpClientTransportOverHTTP(clientConnector);
- }
- };
+ ClientConnector clientConnector = new ClientConnector();
+ clientConnector.setSelectors(1);
+ clientConnector.setSslContextFactory(newSslContextFactoryClient());
+ yield new HttpClientTransportOverHTTP(clientConnector);
+ }
+ case H2C, H2 ->
+ {
+ ClientConnector clientConnector = new ClientConnector();
+ clientConnector.setSelectors(1);
+ clientConnector.setSslContextFactory(newSslContextFactoryClient());
+ HTTP2Client http2Client = new HTTP2Client(clientConnector);
+ yield new HttpClientTransportOverHTTP2(http2Client);
+ }
+ case H3 ->
+ {
+ ClientConnector clientConnector = new ClientConnector();
+ clientConnector.setSelectors(1);
+ SslContextFactory.Client sslClient = newSslContextFactoryClient();
+ HTTP3Client http3Client = new HTTP3Client(new ClientQuicConfiguration(sslClient, null), clientConnector);
+ yield new HttpClientTransportOverHTTP3(http3Client);
+ }
+ case FCGI -> new HttpClientTransportOverFCGI(1, "");
+ };
}
protected URI newURI(Transport transport)
@@ -474,13 +431,13 @@ protected void setMaxRequestsPerConnection(int maxRequestsPerConnection)
public enum Transport
{
- HTTP, HTTPS, H2C, H2, H3, FCGI, UNIX_DOMAIN;
+ HTTP, HTTPS, H2C, H2, H3, FCGI;
public boolean isSecure()
{
return switch (this)
{
- case HTTP, H2C, FCGI, UNIX_DOMAIN -> false;
+ case HTTP, H2C, FCGI -> false;
case HTTPS, H2, H3 -> true;
};
}
@@ -489,7 +446,7 @@ public boolean isMultiplexed()
{
return switch (this)
{
- case HTTP, HTTPS, FCGI, UNIX_DOMAIN -> false;
+ case HTTP, HTTPS, FCGI -> false;
case H2C, H2, H3 -> true;
};
}
diff --git a/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/EventsHandlerTest.java b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/EventsHandlerTest.java
index 90b0c8b095ba..026fa5702e80 100644
--- a/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/EventsHandlerTest.java
+++ b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/EventsHandlerTest.java
@@ -220,7 +220,6 @@ public void testUsingEventsResponseAsContentSourceFails(Transport transport) thr
case HTTP:
case HTTPS:
case FCGI:
- case UNIX_DOMAIN:
await().atMost(5, TimeUnit.SECONDS).until(() -> eventsHandler.exceptions.size() / 4, allOf(greaterThanOrEqualTo(10), lessThanOrEqualTo(11)));
break;
// One read, maybe one null read, one write, one write complete.
diff --git a/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HTTP1TransportProtocolTest.java b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HTTP1TransportProtocolTest.java
new file mode 100644
index 000000000000..911be28a5afe
--- /dev/null
+++ b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HTTP1TransportProtocolTest.java
@@ -0,0 +1,189 @@
+//
+// ========================================================================
+// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+// which is available at https://www.apache.org/licenses/LICENSE-2.0.
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+package org.eclipse.jetty.test.client.transport;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.client.ContentResponse;
+import org.eclipse.jetty.client.Destination;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.transport.HttpClientTransportOverHTTP;
+import org.eclipse.jetty.http.HttpScheme;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.io.ClientConnector;
+import org.eclipse.jetty.io.TransportProtocol;
+import org.eclipse.jetty.quic.client.ClientQuicConfiguration;
+import org.eclipse.jetty.quic.client.QuicTransportProtocol;
+import org.eclipse.jetty.quic.server.QuicServerConnector;
+import org.eclipse.jetty.quic.server.ServerQuicConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.MemoryConnector;
+import org.eclipse.jetty.server.MemoryTransportProtocol;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.toolchain.test.MavenPaths;
+import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
+import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
+import org.eclipse.jetty.unixdomain.server.UnixDomainServerConnector;
+import org.eclipse.jetty.util.component.LifeCycle;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.sameInstance;
+
+@ExtendWith(WorkDirExtension.class)
+public class HTTP1TransportProtocolTest
+{
+ private Server server;
+ private HttpClient httpClient;
+
+ @BeforeEach
+ public void prepare()
+ {
+ QueuedThreadPool serverThreads = new QueuedThreadPool();
+ serverThreads.setName("server");
+ server = new Server(serverThreads);
+
+ ClientConnector clientConnector = new ClientConnector();
+ QueuedThreadPool clientThreads = new QueuedThreadPool();
+ serverThreads.setName("client");
+ clientConnector.setExecutor(clientThreads);
+ clientConnector.setSelectors(1);
+ httpClient = new HttpClient(new HttpClientTransportOverHTTP(clientConnector));
+ server.addBean(httpClient);
+ }
+
+ @AfterEach
+ public void dispose()
+ {
+ LifeCycle.stop(server);
+ }
+
+ @Test
+ public void testDefaultTransportProtocol() throws Exception
+ {
+ ServerConnector connector = new ServerConnector(server, 1, 1, new HttpConnectionFactory());
+ server.addConnector(connector);
+ server.setHandler(new EmptyServerHandler());
+ server.start();
+
+ ContentResponse response = httpClient.newRequest("localhost", connector.getLocalPort())
+ .timeout(5, TimeUnit.SECONDS)
+ .send();
+
+ assertThat(response.getStatus(), is(HttpStatus.OK_200));
+
+ List destinations = httpClient.getDestinations();
+ assertThat(destinations.size(), is(1));
+ Destination destination = destinations.get(0);
+ assertThat(destination.getOrigin().getTransportProtocol(), sameInstance(TransportProtocol.TCP_IP));
+
+ HttpClientTransportOverHTTP httpClientTransport = (HttpClientTransportOverHTTP)httpClient.getTransport();
+ int networkConnections = httpClientTransport.getClientConnector().getSelectorManager().getTotalKeys();
+ assertThat(networkConnections, is(1));
+ }
+
+ @Test
+ public void testExplicitTransportProtocol() throws Exception
+ {
+ ServerConnector connector = new ServerConnector(server, 1, 1, new HttpConnectionFactory());
+ server.addConnector(connector);
+ server.setHandler(new EmptyServerHandler());
+ server.start();
+
+ ContentResponse response = httpClient.newRequest("localhost", connector.getLocalPort())
+ .transportProtocol(TransportProtocol.TCP_IP)
+ .timeout(5, TimeUnit.SECONDS)
+ .send();
+
+ assertThat(response.getStatus(), is(HttpStatus.OK_200));
+ }
+
+ @Test
+ public void testMemoryTransportProtocol() throws Exception
+ {
+ MemoryConnector connector = new MemoryConnector(server, new HttpConnectionFactory());
+ server.addConnector(connector);
+ server.setHandler(new EmptyServerHandler());
+ server.start();
+
+ ContentResponse response = httpClient.newRequest("http://localhost/")
+ .transportProtocol(new MemoryTransportProtocol(connector))
+ .timeout(5, TimeUnit.SECONDS)
+ .send();
+
+ assertThat(response.getStatus(), is(HttpStatus.OK_200));
+
+ HttpClientTransportOverHTTP httpClientTransport = (HttpClientTransportOverHTTP)httpClient.getTransport();
+ int networkConnections = httpClientTransport.getClientConnector().getSelectorManager().getTotalKeys();
+ assertThat(networkConnections, is(0));
+ }
+
+ @Test
+ public void testUnixDomainTransportProtocol() throws Exception
+ {
+ UnixDomainServerConnector connector = new UnixDomainServerConnector(server, 1, 1, new HttpConnectionFactory());
+ connector.setUnixDomainPath(Path.of(System.getProperty("java.io.tmpdir"), "jetty.sock"));
+ server.addConnector(connector);
+ server.setHandler(new EmptyServerHandler());
+ server.start();
+
+ ContentResponse response = httpClient.newRequest("http://localhost/")
+ .transportProtocol(new TransportProtocol.TCPUnix(connector.getUnixDomainPath()))
+ .timeout(5, TimeUnit.SECONDS)
+ .send();
+
+ assertThat(response.getStatus(), is(HttpStatus.OK_200));
+ }
+
+ @Test
+ public void testQUICTransportProtocol(WorkDir workDir) throws Exception
+ {
+ SslContextFactory.Server sslServer = new SslContextFactory.Server();
+ sslServer.setKeyStorePath(MavenPaths.findTestResourceFile("keystore.p12").toString());
+ sslServer.setKeyStorePassword("storepwd");
+
+ Path pemServerDir = workDir.getEmptyPathDir().resolve("server");
+ Files.createDirectories(pemServerDir);
+
+ ServerQuicConfiguration quicConfiguration = new ServerQuicConfiguration(sslServer, pemServerDir);
+ QuicServerConnector connector = new QuicServerConnector(server, quicConfiguration, new HttpConnectionFactory());
+ server.addConnector(connector);
+ server.setHandler(new EmptyServerHandler());
+
+ SslContextFactory.Client sslClient = new SslContextFactory.Client(true);
+ httpClient.setSslContextFactory(sslClient);
+ ClientQuicConfiguration clientQuicConfig = new ClientQuicConfiguration(sslClient, null);
+ httpClient.addBean(clientQuicConfig);
+
+ server.start();
+
+ ContentResponse response = httpClient.newRequest("localhost", connector.getLocalPort())
+ .transportProtocol(new QuicTransportProtocol(clientQuicConfig))
+ .scheme(HttpScheme.HTTPS.asString())
+ .timeout(5, TimeUnit.SECONDS)
+ .send();
+
+ assertThat(response.getStatus(), is(HttpStatus.OK_200));
+ }
+}
diff --git a/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HTTP2TransportProtocolTest.java b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HTTP2TransportProtocolTest.java
new file mode 100644
index 000000000000..36d4c36500e0
--- /dev/null
+++ b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HTTP2TransportProtocolTest.java
@@ -0,0 +1,359 @@
+//
+// ========================================================================
+// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+// which is available at https://www.apache.org/licenses/LICENSE-2.0.
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+package org.eclipse.jetty.test.client.transport;
+
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.client.ContentResponse;
+import org.eclipse.jetty.client.Destination;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpScheme;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpURI;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.http.MetaData;
+import org.eclipse.jetty.http2.api.Session;
+import org.eclipse.jetty.http2.api.Stream;
+import org.eclipse.jetty.http2.client.HTTP2Client;
+import org.eclipse.jetty.http2.client.transport.HttpClientTransportOverHTTP2;
+import org.eclipse.jetty.http2.frames.HeadersFrame;
+import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory;
+import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory;
+import org.eclipse.jetty.io.ClientConnector;
+import org.eclipse.jetty.io.TransportProtocol;
+import org.eclipse.jetty.quic.client.ClientQuicConfiguration;
+import org.eclipse.jetty.quic.client.QuicTransportProtocol;
+import org.eclipse.jetty.quic.server.QuicServerConnector;
+import org.eclipse.jetty.quic.server.ServerQuicConfiguration;
+import org.eclipse.jetty.server.MemoryConnector;
+import org.eclipse.jetty.server.MemoryTransportProtocol;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.toolchain.test.MavenPaths;
+import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
+import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
+import org.eclipse.jetty.unixdomain.server.UnixDomainServerConnector;
+import org.eclipse.jetty.util.component.LifeCycle;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.sameInstance;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@ExtendWith(WorkDirExtension.class)
+public class HTTP2TransportProtocolTest
+{
+ private Server server;
+ private HttpClient httpClient;
+ private HTTP2Client http2Client;
+
+ @BeforeEach
+ public void prepare()
+ {
+ QueuedThreadPool serverThreads = new QueuedThreadPool();
+ serverThreads.setName("server");
+ server = new Server(serverThreads);
+
+ ClientConnector clientConnector = new ClientConnector();
+ QueuedThreadPool clientThreads = new QueuedThreadPool();
+ serverThreads.setName("client");
+ clientConnector.setExecutor(clientThreads);
+ clientConnector.setSelectors(1);
+ http2Client = new HTTP2Client(clientConnector);
+ httpClient = new HttpClient(new HttpClientTransportOverHTTP2(http2Client));
+ server.addBean(httpClient);
+ }
+
+ @AfterEach
+ public void dispose()
+ {
+ LifeCycle.stop(server);
+ }
+
+ @Test
+ public void testDefaultTransportProtocol() throws Exception
+ {
+ ServerConnector connector = new ServerConnector(server, 1, 1, new HTTP2CServerConnectionFactory());
+ server.addConnector(connector);
+ server.setHandler(new EmptyServerHandler());
+ server.start();
+
+ ContentResponse response = httpClient.newRequest("localhost", connector.getLocalPort())
+ .timeout(5, TimeUnit.SECONDS)
+ .send();
+
+ assertThat(response.getStatus(), is(HttpStatus.OK_200));
+
+ List destinations = httpClient.getDestinations();
+ assertThat(destinations.size(), is(1));
+ Destination destination = destinations.get(0);
+ assertThat(destination.getOrigin().getTransportProtocol(), sameInstance(TransportProtocol.TCP_IP));
+
+ HttpClientTransportOverHTTP2 httpClientTransport = (HttpClientTransportOverHTTP2)httpClient.getTransport();
+ int networkConnections = httpClientTransport.getHTTP2Client().getClientConnector().getSelectorManager().getTotalKeys();
+ assertThat(networkConnections, is(1));
+ }
+
+ @Test
+ public void testExplicitTransportProtocol() throws Exception
+ {
+ ServerConnector connector = new ServerConnector(server, 1, 1, new HTTP2CServerConnectionFactory());
+ server.addConnector(connector);
+ server.setHandler(new EmptyServerHandler());
+ server.start();
+
+ ContentResponse response = httpClient.newRequest("localhost", connector.getLocalPort())
+ .transportProtocol(TransportProtocol.TCP_IP)
+ .timeout(5, TimeUnit.SECONDS)
+ .send();
+
+ assertThat(response.getStatus(), is(HttpStatus.OK_200));
+ }
+
+ @Test
+ public void testMemoryTransportProtocol() throws Exception
+ {
+ MemoryConnector connector = new MemoryConnector(server, new HTTP2CServerConnectionFactory());
+ server.addConnector(connector);
+ server.setHandler(new EmptyServerHandler());
+ server.start();
+
+ ContentResponse response = httpClient.newRequest("http://localhost/")
+ .transportProtocol(new MemoryTransportProtocol(connector))
+ .timeout(5, TimeUnit.SECONDS)
+ .send();
+
+ assertThat(response.getStatus(), is(HttpStatus.OK_200));
+
+ HttpClientTransportOverHTTP2 httpClientTransport = (HttpClientTransportOverHTTP2)httpClient.getTransport();
+ int networkConnections = httpClientTransport.getHTTP2Client().getClientConnector().getSelectorManager().getTotalKeys();
+ assertThat(networkConnections, is(0));
+ }
+
+ @Test
+ public void testUnixDomainTransportProtocol() throws Exception
+ {
+ UnixDomainServerConnector connector = new UnixDomainServerConnector(server, 1, 1, new HTTP2CServerConnectionFactory());
+ connector.setUnixDomainPath(Path.of(System.getProperty("java.io.tmpdir"), "jetty.sock"));
+ server.addConnector(connector);
+ server.setHandler(new EmptyServerHandler());
+ server.start();
+
+ ContentResponse response = httpClient.newRequest("http://localhost/")
+ .transportProtocol(new TransportProtocol.TCPUnix(connector.getUnixDomainPath()))
+ .timeout(5, TimeUnit.SECONDS)
+ .send();
+
+ assertThat(response.getStatus(), is(HttpStatus.OK_200));
+ }
+
+ @Test
+ public void testQUICTransportProtocolWithH2C(WorkDir workDir) throws Exception
+ {
+ SslContextFactory.Server sslServer = new SslContextFactory.Server();
+ sslServer.setKeyStorePath(MavenPaths.findTestResourceFile("keystore.p12").toString());
+ sslServer.setKeyStorePassword("storepwd");
+
+ Path pemServerDir = workDir.getEmptyPathDir().resolve("server");
+ Files.createDirectories(pemServerDir);
+
+ ServerQuicConfiguration quicConfiguration = new ServerQuicConfiguration(sslServer, pemServerDir);
+ QuicServerConnector connector = new QuicServerConnector(server, quicConfiguration, new HTTP2CServerConnectionFactory());
+ server.addConnector(connector);
+ server.setHandler(new EmptyServerHandler());
+
+ SslContextFactory.Client sslClient = new SslContextFactory.Client(true);
+ httpClient.setSslContextFactory(sslClient);
+ ClientQuicConfiguration clientQuicConfig = new ClientQuicConfiguration(sslClient, null);
+ httpClient.addBean(clientQuicConfig);
+
+ server.start();
+
+ ContentResponse response = httpClient.newRequest("localhost", connector.getLocalPort())
+ .transportProtocol(new QuicTransportProtocol(clientQuicConfig))
+ .timeout(5, TimeUnit.SECONDS)
+ .send();
+
+ assertThat(response.getStatus(), is(HttpStatus.OK_200));
+ }
+
+ @Test
+ public void testQUICTransportProtocolWithH2(WorkDir workDir) throws Exception
+ {
+ SslContextFactory.Server sslServer = new SslContextFactory.Server();
+ sslServer.setKeyStorePath(MavenPaths.findTestResourceFile("keystore.p12").toString());
+ sslServer.setKeyStorePassword("storepwd");
+
+ Path pemServerDir = workDir.getEmptyPathDir().resolve("server");
+ Files.createDirectories(pemServerDir);
+
+ ServerQuicConfiguration quicConfiguration = new ServerQuicConfiguration(sslServer, pemServerDir);
+ QuicServerConnector connector = new QuicServerConnector(server, quicConfiguration, new HTTP2ServerConnectionFactory());
+ server.addConnector(connector);
+ server.setHandler(new EmptyServerHandler());
+
+ SslContextFactory.Client sslClient = new SslContextFactory.Client(true);
+ httpClient.setSslContextFactory(sslClient);
+ HttpClientTransportOverHTTP2 httpClientTransport = (HttpClientTransportOverHTTP2)httpClient.getTransport();
+ // ALPN is negotiated by QUIC.
+ httpClientTransport.setUseALPN(false);
+ ClientQuicConfiguration clientQuicConfig = new ClientQuicConfiguration(sslClient, null);
+ httpClient.addBean(clientQuicConfig);
+
+ server.start();
+
+ ContentResponse response = httpClient.newRequest("localhost", connector.getLocalPort())
+ .transportProtocol(new QuicTransportProtocol(clientQuicConfig))
+ .scheme(HttpScheme.HTTPS.asString())
+ .timeout(5, TimeUnit.SECONDS)
+ .send();
+
+ assertThat(response.getStatus(), is(HttpStatus.OK_200));
+ }
+
+ @Test
+ public void testLowLevelH2COverTCPIP() throws Exception
+ {
+ ServerConnector connector = new ServerConnector(server, 1, 1, new HTTP2CServerConnectionFactory());
+ server.addConnector(connector);
+ server.setHandler(new EmptyServerHandler());
+ server.start();
+
+ InetSocketAddress socketAddress = new InetSocketAddress("localhost", connector.getLocalPort());
+ Session session = http2Client.connect(socketAddress, new Session.Listener() {}).get(5, TimeUnit.SECONDS);
+
+ CountDownLatch responseLatch = new CountDownLatch(1);
+ MetaData.Request request = new MetaData.Request("GET", HttpURI.from("http://localhost/"), HttpVersion.HTTP_2, HttpFields.EMPTY);
+ session.newStream(new HeadersFrame(request, null, true), new Stream.Listener()
+ {
+ @Override
+ public void onHeaders(Stream stream, HeadersFrame frame)
+ {
+ MetaData.Response response = (MetaData.Response)frame.getMetaData();
+ assertThat(response.getStatus(), is(HttpStatus.OK_200));
+ responseLatch.countDown();
+ }
+ });
+
+ assertTrue(responseLatch.await(5, TimeUnit.SECONDS));
+ }
+
+ @Test
+ public void testLowLevelH2COverMemory() throws Exception
+ {
+ MemoryConnector connector = new MemoryConnector(server, new HTTP2CServerConnectionFactory());
+ server.addConnector(connector);
+ server.setHandler(new EmptyServerHandler());
+ server.start();
+
+ Session session = http2Client.connect(new MemoryTransportProtocol(connector), null, connector.getLocalSocketAddress(), new Session.Listener() {}).get(5, TimeUnit.SECONDS);
+
+ CountDownLatch responseLatch = new CountDownLatch(1);
+ MetaData.Request request = new MetaData.Request("GET", HttpURI.from("http://localhost/"), HttpVersion.HTTP_2, HttpFields.EMPTY);
+ session.newStream(new HeadersFrame(request, null, true), new Stream.Listener()
+ {
+ @Override
+ public void onHeaders(Stream stream, HeadersFrame frame)
+ {
+ MetaData.Response response = (MetaData.Response)frame.getMetaData();
+ assertThat(response.getStatus(), is(HttpStatus.OK_200));
+ responseLatch.countDown();
+ }
+ });
+
+ assertTrue(responseLatch.await(5, TimeUnit.SECONDS));
+ }
+
+ @Test
+ public void testLowLevelH2COverUnixDomain() throws Exception
+ {
+ UnixDomainServerConnector connector = new UnixDomainServerConnector(server, new HTTP2CServerConnectionFactory());
+ connector.setUnixDomainPath(Path.of(System.getProperty("java.io.tmpdir"), "jetty.sock"));
+ server.addConnector(connector);
+ server.setHandler(new EmptyServerHandler());
+ server.start();
+
+ Session session = http2Client.connect(new TransportProtocol.TCPUnix(connector.getUnixDomainPath()), null, connector.getLocalSocketAddress(), new Session.Listener() {}).get(5, TimeUnit.SECONDS);
+
+ CountDownLatch responseLatch = new CountDownLatch(1);
+ MetaData.Request request = new MetaData.Request("GET", HttpURI.from("http://localhost/"), HttpVersion.HTTP_2, HttpFields.EMPTY);
+ session.newStream(new HeadersFrame(request, null, true), new Stream.Listener()
+ {
+ @Override
+ public void onHeaders(Stream stream, HeadersFrame frame)
+ {
+ MetaData.Response response = (MetaData.Response)frame.getMetaData();
+ assertThat(response.getStatus(), is(HttpStatus.OK_200));
+ responseLatch.countDown();
+ }
+ });
+
+ assertTrue(responseLatch.await(5, TimeUnit.SECONDS));
+ }
+
+ @Test
+ public void testLowLevelH2COverQUIC(WorkDir workDir) throws Exception
+ {
+ SslContextFactory.Server sslServer = new SslContextFactory.Server();
+ sslServer.setKeyStorePath(MavenPaths.findTestResourceFile("keystore.p12").toString());
+ sslServer.setKeyStorePassword("storepwd");
+
+ Path pemServerDir = workDir.getEmptyPathDir().resolve("server");
+ Files.createDirectories(pemServerDir);
+
+ ServerQuicConfiguration quicConfiguration = new ServerQuicConfiguration(sslServer, pemServerDir);
+ QuicServerConnector connector = new QuicServerConnector(server, quicConfiguration, new HTTP2CServerConnectionFactory());
+ server.addConnector(connector);
+ server.setHandler(new EmptyServerHandler());
+
+ SslContextFactory.Client sslClient = new SslContextFactory.Client(true);
+ http2Client.getClientConnector().setSslContextFactory(sslClient);
+ ClientQuicConfiguration clientQuicConfig = new ClientQuicConfiguration(sslClient, null);
+ clientQuicConfig.setProtocols(List.of("h2c"));
+ http2Client.addBean(clientQuicConfig);
+
+ server.start();
+
+ SocketAddress socketAddress = new InetSocketAddress("localhost", connector.getLocalPort());
+ Session session = http2Client.connect(new QuicTransportProtocol(clientQuicConfig), null, socketAddress, new Session.Listener() {}).get(5, TimeUnit.SECONDS);
+
+ CountDownLatch responseLatch = new CountDownLatch(1);
+ MetaData.Request request = new MetaData.Request("GET", HttpURI.from("http://localhost/"), HttpVersion.HTTP_2, HttpFields.EMPTY);
+ session.newStream(new HeadersFrame(request, null, true), new Stream.Listener()
+ {
+ @Override
+ public void onHeaders(Stream stream, HeadersFrame frame)
+ {
+ MetaData.Response response = (MetaData.Response)frame.getMetaData();
+ assertThat(response.getStatus(), is(HttpStatus.OK_200));
+ responseLatch.countDown();
+ }
+ });
+
+ assertTrue(responseLatch.await(5, TimeUnit.SECONDS));
+ }
+}
diff --git a/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HTTP3TransportProtocolTest.java b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HTTP3TransportProtocolTest.java
new file mode 100644
index 000000000000..f4d7367bdfc9
--- /dev/null
+++ b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HTTP3TransportProtocolTest.java
@@ -0,0 +1,248 @@
+//
+// ========================================================================
+// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+// which is available at https://www.apache.org/licenses/LICENSE-2.0.
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+package org.eclipse.jetty.test.client.transport;
+
+import java.net.InetSocketAddress;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.client.ContentResponse;
+import org.eclipse.jetty.client.Destination;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpURI;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.http.MetaData;
+import org.eclipse.jetty.http3.api.Session;
+import org.eclipse.jetty.http3.api.Stream;
+import org.eclipse.jetty.http3.client.HTTP3Client;
+import org.eclipse.jetty.http3.client.transport.HttpClientTransportOverHTTP3;
+import org.eclipse.jetty.http3.frames.HeadersFrame;
+import org.eclipse.jetty.http3.server.HTTP3ServerConnectionFactory;
+import org.eclipse.jetty.io.ClientConnector;
+import org.eclipse.jetty.io.TransportProtocol;
+import org.eclipse.jetty.quic.client.ClientQuicConfiguration;
+import org.eclipse.jetty.quic.client.QuicTransportProtocol;
+import org.eclipse.jetty.quic.server.QuicServerConnectionFactory;
+import org.eclipse.jetty.quic.server.QuicServerConnector;
+import org.eclipse.jetty.quic.server.ServerQuicConfiguration;
+import org.eclipse.jetty.server.MemoryConnector;
+import org.eclipse.jetty.server.MemoryTransportProtocol;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.toolchain.test.MavenPaths;
+import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
+import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
+import org.eclipse.jetty.util.component.LifeCycle;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.sameInstance;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+
+@ExtendWith(WorkDirExtension.class)
+public class HTTP3TransportProtocolTest
+{
+ private SslContextFactory.Server sslServer;
+ private Path pemServerDir;
+ private Server server;
+ private SslContextFactory.Client sslClient;
+ private HttpClient httpClient;
+ private HTTP3Client http3Client;
+
+ @BeforeEach
+ public void prepare(WorkDir workDir) throws Exception
+ {
+ sslServer = new SslContextFactory.Server();
+ sslServer.setKeyStorePath(MavenPaths.findTestResourceFile("keystore.p12").toString());
+ sslServer.setKeyStorePassword("storepwd");
+ pemServerDir = workDir.getEmptyPathDir().resolve("server");
+ Files.createDirectories(pemServerDir);
+
+ QueuedThreadPool serverThreads = new QueuedThreadPool();
+ serverThreads.setName("server");
+ server = new Server(serverThreads);
+
+ sslClient = new SslContextFactory.Client(true);
+
+ ClientQuicConfiguration quicConfiguration = new ClientQuicConfiguration(sslClient, null);
+ ClientConnector clientConnector = new ClientConnector();
+ QueuedThreadPool clientThreads = new QueuedThreadPool();
+ serverThreads.setName("client");
+ clientConnector.setExecutor(clientThreads);
+ clientConnector.setSelectors(1);
+ http3Client = new HTTP3Client(quicConfiguration, clientConnector);
+ httpClient = new HttpClient(new HttpClientTransportOverHTTP3(http3Client));
+ server.addBean(httpClient);
+ }
+
+ @AfterEach
+ public void dispose()
+ {
+ LifeCycle.stop(server);
+ }
+
+ @Test
+ public void testDefaultTransportProtocol() throws Exception
+ {
+ ServerQuicConfiguration serverQuicConfig = new ServerQuicConfiguration(sslServer, pemServerDir);
+ QuicServerConnector connector = new QuicServerConnector(server, serverQuicConfig, new HTTP3ServerConnectionFactory(serverQuicConfig));
+ server.addConnector(connector);
+ server.setHandler(new EmptyServerHandler());
+ server.start();
+
+ ContentResponse response = httpClient.newRequest("localhost", connector.getLocalPort())
+ .timeout(5, TimeUnit.SECONDS)
+ .send();
+
+ assertThat(response.getStatus(), is(HttpStatus.OK_200));
+
+ List destinations = httpClient.getDestinations();
+ assertThat(destinations.size(), is(1));
+ Destination destination = destinations.get(0);
+ TransportProtocol transportProtocol = destination.getOrigin().getTransportProtocol();
+ if (transportProtocol instanceof TransportProtocol.Wrapper wrapper)
+ transportProtocol = wrapper.unwrap();
+ assertThat(transportProtocol, sameInstance(TransportProtocol.UDP_IP));
+
+ HttpClientTransportOverHTTP3 httpClientTransport = (HttpClientTransportOverHTTP3)httpClient.getTransport();
+ int networkConnections = httpClientTransport.getHTTP3Client().getClientConnector().getSelectorManager().getTotalKeys();
+ assertThat(networkConnections, is(1));
+ }
+
+ @Test
+ public void testExplicitTransportProtocol() throws Exception
+ {
+ ServerQuicConfiguration serverQuicConfig = new ServerQuicConfiguration(sslServer, pemServerDir);
+ QuicServerConnector connector = new QuicServerConnector(server, serverQuicConfig, new HTTP3ServerConnectionFactory(serverQuicConfig));
+ server.addConnector(connector);
+ server.setHandler(new EmptyServerHandler());
+ server.start();
+
+ ContentResponse response = httpClient.newRequest("localhost", connector.getLocalPort())
+ .transportProtocol(new QuicTransportProtocol(http3Client.getQuicConfiguration()))
+ .timeout(5, TimeUnit.SECONDS)
+ .send();
+
+ assertThat(response.getStatus(), is(HttpStatus.OK_200));
+ }
+
+ @Test
+ public void testMemoryTransportProtocol() throws Exception
+ {
+ ServerQuicConfiguration quicConfiguration = new ServerQuicConfiguration(sslServer, pemServerDir);
+ QuicServerConnectionFactory quic = new QuicServerConnectionFactory(quicConfiguration);
+ HTTP3ServerConnectionFactory h3 = new HTTP3ServerConnectionFactory(quic.getQuicConfiguration());
+ MemoryConnector connector = new MemoryConnector(server, quic, h3);
+ server.addConnector(connector);
+ server.setHandler(new EmptyServerHandler());
+ server.start();
+
+ ContentResponse response = httpClient.newRequest("http://localhost/")
+ .transportProtocol(new QuicTransportProtocol(new MemoryTransportProtocol(connector), http3Client.getQuicConfiguration()))
+ .timeout(5, TimeUnit.SECONDS)
+ .send();
+
+ assertThat(response.getStatus(), is(HttpStatus.OK_200));
+
+ HttpClientTransportOverHTTP3 httpClientTransport = (HttpClientTransportOverHTTP3)httpClient.getTransport();
+ int networkConnections = httpClientTransport.getHTTP3Client().getClientConnector().getSelectorManager().getTotalKeys();
+ assertThat(networkConnections, is(0));
+ }
+
+ @Test
+ public void testUnixDomainTransportProtocol()
+ {
+ noUnixDomainForDatagramChannel();
+ }
+
+ @Test
+ public void testLowLevelH3OverUDPIP() throws Exception
+ {
+ ServerQuicConfiguration serverQuicConfig = new ServerQuicConfiguration(sslServer, pemServerDir);
+ QuicServerConnector connector = new QuicServerConnector(server, serverQuicConfig, new HTTP3ServerConnectionFactory(serverQuicConfig));
+ server.addConnector(connector);
+ server.setHandler(new EmptyServerHandler());
+ server.start();
+
+ InetSocketAddress socketAddress = new InetSocketAddress("localhost", connector.getLocalPort());
+ Session.Client session = http3Client.connect(socketAddress, new Session.Client.Listener() {}).get(5, TimeUnit.SECONDS);
+
+ CountDownLatch responseLatch = new CountDownLatch(1);
+ MetaData.Request request = new MetaData.Request("GET", HttpURI.from("http://localhost/"), HttpVersion.HTTP_3, HttpFields.EMPTY);
+ session.newRequest(new HeadersFrame(request, true), new Stream.Client.Listener()
+ {
+ @Override
+ public void onResponse(Stream.Client stream, HeadersFrame frame)
+ {
+ MetaData.Response response = (MetaData.Response)frame.getMetaData();
+ assertThat(response.getStatus(), is(HttpStatus.OK_200));
+ responseLatch.countDown();
+ }
+ });
+
+ assertTrue(responseLatch.await(5, TimeUnit.SECONDS));
+ }
+
+ @Test
+ public void testLowLevelH3OverMemory() throws Exception
+ {
+ ServerQuicConfiguration serverQuicConfig = new ServerQuicConfiguration(sslServer, pemServerDir);
+ QuicServerConnectionFactory quic = new QuicServerConnectionFactory(serverQuicConfig);
+ HTTP3ServerConnectionFactory h3 = new HTTP3ServerConnectionFactory(quic.getQuicConfiguration());
+ MemoryConnector connector = new MemoryConnector(server, quic, h3);
+ server.addConnector(connector);
+ server.setHandler(new EmptyServerHandler());
+ server.start();
+
+ TransportProtocol transportProtocol = new QuicTransportProtocol(new MemoryTransportProtocol(connector), http3Client.getQuicConfiguration());
+ Session.Client session = http3Client.connect(transportProtocol, connector.getLocalSocketAddress(), new Session.Client.Listener() {}, null).get(5, TimeUnit.SECONDS);
+
+ CountDownLatch responseLatch = new CountDownLatch(1);
+ MetaData.Request request = new MetaData.Request("GET", HttpURI.from("http://localhost/"), HttpVersion.HTTP_3, HttpFields.EMPTY);
+ session.newRequest(new HeadersFrame(request, true), new Stream.Client.Listener()
+ {
+ @Override
+ public void onResponse(Stream.Client stream, HeadersFrame frame)
+ {
+ MetaData.Response response = (MetaData.Response)frame.getMetaData();
+ assertThat(response.getStatus(), is(HttpStatus.OK_200));
+ responseLatch.countDown();
+ }
+ });
+
+ assertTrue(responseLatch.await(5, TimeUnit.SECONDS));
+ }
+
+ @Test
+ public void testLowLevelH3OverUnixDomain()
+ {
+ noUnixDomainForDatagramChannel();
+ }
+
+ private static void noUnixDomainForDatagramChannel()
+ {
+ assumeTrue(false, "DatagramChannel over Unix-Domain is not supported yet by Java");
+ }
+}
diff --git a/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HTTPDynamicTransportProtocolTest.java b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HTTPDynamicTransportProtocolTest.java
new file mode 100644
index 000000000000..9dacb381c567
--- /dev/null
+++ b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HTTPDynamicTransportProtocolTest.java
@@ -0,0 +1,561 @@
+//
+// ========================================================================
+// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+// which is available at https://www.apache.org/licenses/LICENSE-2.0.
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+package org.eclipse.jetty.test.client.transport;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory;
+import org.eclipse.jetty.client.ContentResponse;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.transport.HttpClientConnectionFactory;
+import org.eclipse.jetty.client.transport.HttpClientTransportDynamic;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpScheme;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpURI;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.http.MetaData;
+import org.eclipse.jetty.http2.api.Session;
+import org.eclipse.jetty.http2.api.Stream;
+import org.eclipse.jetty.http2.client.HTTP2Client;
+import org.eclipse.jetty.http2.client.HTTP2ClientConnectionFactory;
+import org.eclipse.jetty.http2.client.transport.ClientConnectionFactoryOverHTTP2;
+import org.eclipse.jetty.http2.frames.HeadersFrame;
+import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory;
+import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory;
+import org.eclipse.jetty.http3.client.HTTP3Client;
+import org.eclipse.jetty.http3.client.transport.ClientConnectionFactoryOverHTTP3;
+import org.eclipse.jetty.http3.server.HTTP3ServerConnectionFactory;
+import org.eclipse.jetty.io.ClientConnector;
+import org.eclipse.jetty.io.Content;
+import org.eclipse.jetty.io.TransportProtocol;
+import org.eclipse.jetty.quic.client.ClientQuicConfiguration;
+import org.eclipse.jetty.quic.client.QuicTransportProtocol;
+import org.eclipse.jetty.quic.server.QuicServerConnectionFactory;
+import org.eclipse.jetty.quic.server.QuicServerConnector;
+import org.eclipse.jetty.quic.server.ServerQuicConfiguration;
+import org.eclipse.jetty.server.ConnectionFactory;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.MemoryConnector;
+import org.eclipse.jetty.server.MemoryTransportProtocol;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.SslConnectionFactory;
+import org.eclipse.jetty.toolchain.test.MavenPaths;
+import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
+import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
+import org.eclipse.jetty.unixdomain.server.UnixDomainServerConnector;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.HostPort;
+import org.eclipse.jetty.util.Promise;
+import org.eclipse.jetty.util.component.LifeCycle;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+
+@ExtendWith(WorkDirExtension.class)
+public class HTTPDynamicTransportProtocolTest
+{
+ private SslContextFactory.Server sslServer;
+ private Path pemServerDir;
+ private Server server;
+ private ClientConnector clientConnector;
+ private HTTP2Client http2Client;
+ private HTTP3Client http3Client;
+
+ @BeforeEach
+ public void prepare(WorkDir workDir) throws Exception
+ {
+ sslServer = new SslContextFactory.Server();
+ sslServer.setKeyStorePath(MavenPaths.findTestResourceFile("keystore.p12").toString());
+ sslServer.setKeyStorePassword("storepwd");
+ pemServerDir = workDir.getEmptyPathDir().resolve("server");
+ Files.createDirectories(pemServerDir);
+
+ QueuedThreadPool serverThreads = new QueuedThreadPool();
+ serverThreads.setName("server");
+ server = new Server(serverThreads);
+
+ clientConnector = new ClientConnector();
+ QueuedThreadPool clientThreads = new QueuedThreadPool();
+ serverThreads.setName("client");
+ clientConnector.setExecutor(clientThreads);
+ clientConnector.setSelectors(1);
+
+ http2Client = new HTTP2Client(clientConnector);
+
+ SslContextFactory.Client sslClient = new SslContextFactory.Client(true);
+ ClientQuicConfiguration quicConfiguration = new ClientQuicConfiguration(sslClient, null);
+ http3Client = new HTTP3Client(quicConfiguration, clientConnector);
+ }
+
+ @AfterEach
+ public void dispose()
+ {
+ LifeCycle.stop(server);
+ }
+
+ @Test
+ public void testExplicitHTTPVersionWithSameHttpClientForAllHTTPVersions() throws Exception
+ {
+ int port = freePort();
+ ConnectionFactory h1 = new HttpConnectionFactory();
+ ConnectionFactory h2c = new HTTP2CServerConnectionFactory();
+ ServerConnector tcp = new ServerConnector(server, 1, 1, h1, h2c);
+ tcp.setPort(port);
+ server.addConnector(tcp);
+
+ ServerQuicConfiguration quicConfig = new ServerQuicConfiguration(sslServer, pemServerDir);
+ ConnectionFactory h3 = new HTTP3ServerConnectionFactory(quicConfig);
+ QuicServerConnector quic = new QuicServerConnector(server, quicConfig, h3);
+ quic.setPort(port);
+ server.addConnector(quic);
+
+ server.setHandler(new EmptyServerHandler());
+
+ HttpClientTransportDynamic httpClientTransport = new HttpClientTransportDynamic(
+ clientConnector,
+ HttpClientConnectionFactory.HTTP11,
+ new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client),
+ new ClientConnectionFactoryOverHTTP3.HTTP3(http3Client)
+ );
+ HttpClient httpClient = new HttpClient(httpClientTransport);
+ server.addBean(httpClient);
+
+ server.start();
+
+ for (HttpVersion httpVersion : List.of(HttpVersion.HTTP_1_1, HttpVersion.HTTP_2, HttpVersion.HTTP_3))
+ {
+ ContentResponse response = httpClient.newRequest("localhost", port)
+ .version(httpVersion)
+ .timeout(5, TimeUnit.SECONDS)
+ .send();
+
+ assertThat(httpVersion.toString(), response.getStatus(), is(HttpStatus.OK_200));
+ }
+ }
+
+ @Test
+ public void testNonExplicitHTTPVersionH3H2H1() throws Exception
+ {
+ int port = freePort();
+ ConnectionFactory h1 = new HttpConnectionFactory();
+ ConnectionFactory h2c = new HTTP2CServerConnectionFactory();
+ ServerConnector tcp = new ServerConnector(server, 1, 1, h1, h2c);
+ tcp.setPort(port);
+ server.addConnector(tcp);
+
+ ServerQuicConfiguration quicConfig = new ServerQuicConfiguration(sslServer, pemServerDir);
+ ConnectionFactory h3 = new HTTP3ServerConnectionFactory(quicConfig);
+ QuicServerConnector quic = new QuicServerConnector(server, quicConfig, h3);
+ quic.setPort(port);
+ server.addConnector(quic);
+
+ server.setHandler(new Handler.Abstract()
+ {
+ @Override
+ public boolean handle(Request request, Response response, Callback callback)
+ {
+ Content.Sink.write(response, true, request.getConnectionMetaData().getProtocol(), callback);
+ return true;
+ }
+ });
+
+ HttpClientTransportDynamic httpClientTransport = new HttpClientTransportDynamic(
+ clientConnector,
+ new ClientConnectionFactoryOverHTTP3.HTTP3(http3Client),
+ new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client),
+ HttpClientConnectionFactory.HTTP11
+ );
+ HttpClient httpClient = new HttpClient(httpClientTransport);
+ server.addBean(httpClient);
+
+ server.start();
+
+ // No explicit version, HttpClientTransport preference wins.
+ ContentResponse response = httpClient.newRequest("localhost", port)
+ .scheme(HttpScheme.HTTPS.asString())
+ .timeout(5, TimeUnit.SECONDS)
+ .send();
+
+ assertThat(response.getStatus(), is(HttpStatus.OK_200));
+ assertThat(response.getContentAsString(), containsString("/3"));
+
+ // Non-secure scheme, must not be HTTP/3.
+ response = httpClient.newRequest("localhost", port)
+ .timeout(5, TimeUnit.SECONDS)
+ .send();
+
+ assertThat(response.getStatus(), is(HttpStatus.OK_200));
+ assertThat(response.getContentAsString(), containsString("/2"));
+ }
+
+ @Test
+ public void testNonExplicitHTTPVersionH2H3H1() throws Exception
+ {
+ int port = freePort();
+ ConnectionFactory h1 = new HttpConnectionFactory();
+ ConnectionFactory h2c = new HTTP2CServerConnectionFactory();
+ ServerConnector tcp = new ServerConnector(server, 1, 1, h1, h2c);
+ tcp.setPort(port);
+ server.addConnector(tcp);
+
+ int securePort = freePort();
+ ConnectionFactory h2 = new HTTP2ServerConnectionFactory();
+ ALPNServerConnectionFactory alpn = new ALPNServerConnectionFactory();
+ alpn.setDefaultProtocol(h1.getProtocol());
+ ConnectionFactory ssl = new SslConnectionFactory(sslServer, alpn.getProtocol());
+ ServerConnector tcpSecure = new ServerConnector(server, 1, 1, ssl, alpn, h2, h1);
+ tcpSecure.setPort(securePort);
+ server.addConnector(tcpSecure);
+
+ ServerQuicConfiguration quicConfig = new ServerQuicConfiguration(sslServer, pemServerDir);
+ ConnectionFactory h3 = new HTTP3ServerConnectionFactory(quicConfig);
+ QuicServerConnector quic = new QuicServerConnector(server, quicConfig, h3);
+ quic.setPort(securePort);
+ server.addConnector(quic);
+
+ server.setHandler(new Handler.Abstract()
+ {
+ @Override
+ public boolean handle(Request request, Response response, Callback callback)
+ {
+ Content.Sink.write(response, true, request.getConnectionMetaData().getProtocol(), callback);
+ return true;
+ }
+ });
+
+ HttpClientTransportDynamic httpClientTransport = new HttpClientTransportDynamic(
+ clientConnector,
+ new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client),
+ new ClientConnectionFactoryOverHTTP3.HTTP3(http3Client),
+ HttpClientConnectionFactory.HTTP11
+ );
+ HttpClient httpClient = new HttpClient(httpClientTransport);
+ server.addBean(httpClient);
+
+ server.start();
+
+ // No explicit version, non-secure, HttpClientTransport preference wins.
+ ContentResponse response = httpClient.newRequest("localhost", port)
+ .timeout(5, TimeUnit.SECONDS)
+ .send();
+
+ assertThat(response.getStatus(), is(HttpStatus.OK_200));
+ assertThat(response.getContentAsString(), containsString("/2"));
+
+ // Secure scheme, but must not be HTTP/3.
+ response = httpClient.newRequest("localhost", securePort)
+ .scheme(HttpScheme.HTTPS.asString())
+ .timeout(5, TimeUnit.SECONDS)
+ .send();
+
+ assertThat(response.getStatus(), is(HttpStatus.OK_200));
+ assertThat(response.getContentAsString(), containsString("/2"));
+ }
+
+ @Test
+ public void testClientH2H3H1ServerALPNH1() throws Exception
+ {
+ int securePort = freePort();
+
+ ConnectionFactory h1 = new HttpConnectionFactory();
+ ALPNServerConnectionFactory alpn = new ALPNServerConnectionFactory();
+ alpn.setDefaultProtocol(h1.getProtocol());
+ ConnectionFactory ssl = new SslConnectionFactory(sslServer, alpn.getProtocol());
+ ServerConnector tcpSecure = new ServerConnector(server, 1, 1, ssl, alpn, h1);
+ tcpSecure.setPort(securePort);
+ server.addConnector(tcpSecure);
+
+ ServerQuicConfiguration quicConfig = new ServerQuicConfiguration(sslServer, pemServerDir);
+ ConnectionFactory h3 = new HTTP3ServerConnectionFactory(quicConfig);
+ QuicServerConnector quic = new QuicServerConnector(server, quicConfig, h3);
+ quic.setPort(securePort);
+ server.addConnector(quic);
+
+ server.setHandler(new Handler.Abstract()
+ {
+ @Override
+ public boolean handle(Request request, Response response, Callback callback)
+ {
+ Content.Sink.write(response, true, request.getConnectionMetaData().getProtocol(), callback);
+ return true;
+ }
+ });
+
+ HttpClientTransportDynamic httpClientTransport = new HttpClientTransportDynamic(
+ clientConnector,
+ new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client),
+ new ClientConnectionFactoryOverHTTP3.HTTP3(http3Client),
+ HttpClientConnectionFactory.HTTP11
+ );
+ HttpClient httpClient = new HttpClient(httpClientTransport);
+ server.addBean(httpClient);
+
+ server.start();
+
+ // Secure scheme, must negotiate HTTP/1.
+ ContentResponse response = httpClient.newRequest("localhost", securePort)
+ .scheme(HttpScheme.HTTPS.asString())
+ .timeout(5, TimeUnit.SECONDS)
+ .send();
+
+ assertThat(response.getStatus(), is(HttpStatus.OK_200));
+ assertThat(response.getContentAsString(), containsString("/1"));
+ }
+
+ @Test
+ public void testClientSendH3ServerDoesNotSupportH3() throws Exception
+ {
+ ConnectionFactory h2 = new HTTP2ServerConnectionFactory();
+ ALPNServerConnectionFactory alpn = new ALPNServerConnectionFactory();
+ alpn.setDefaultProtocol(h2.getProtocol());
+ ConnectionFactory ssl = new SslConnectionFactory(sslServer, alpn.getProtocol());
+ ServerConnector tcpSecure = new ServerConnector(server, 1, 1, ssl, alpn, h2);
+ server.addConnector(tcpSecure);
+
+ server.setHandler(new EmptyServerHandler());
+
+ HttpClientTransportDynamic httpClientTransport = new HttpClientTransportDynamic(
+ clientConnector,
+ new ClientConnectionFactoryOverHTTP3.HTTP3(http3Client),
+ new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client)
+ );
+ HttpClient httpClient = new HttpClient(httpClientTransport);
+ server.addBean(httpClient);
+
+ server.start();
+
+ // The client will attempt a request with H3 due to client preference.
+ // The attempt to connect via QUIC/UDP will time out (there is no immediate
+ // failure like would happen with TCP not listening on the connector port).
+ assertThrows(TimeoutException.class, () -> httpClient.newRequest("localhost", tcpSecure.getLocalPort())
+ .scheme(HttpScheme.HTTPS.asString())
+ .timeout(1, TimeUnit.SECONDS)
+ .send()
+ );
+
+ // Make sure the client can speak H2.
+ ContentResponse response = httpClient.newRequest("localhost", tcpSecure.getLocalPort())
+ .scheme(HttpScheme.HTTPS.asString())
+ .version(HttpVersion.HTTP_2)
+ .timeout(5, TimeUnit.SECONDS)
+ .send();
+
+ assertThat(response.getStatus(), is(HttpStatus.OK_200));
+ }
+
+ @Test
+ public void testHighLevelH1OverUNIX() throws Exception
+ {
+ ConnectionFactory h1 = new HttpConnectionFactory();
+ ServerConnector tcp = new ServerConnector(server, 1, 1, h1);
+ server.addConnector(tcp);
+
+ Path unixDomainPath = newUnixDomainPath();
+ UnixDomainServerConnector unix = new UnixDomainServerConnector(server, 1, 1, h1);
+ unix.setUnixDomainPath(unixDomainPath);
+ server.addConnector(unix);
+
+ server.setHandler(new EmptyServerHandler());
+
+ HttpClient httpClient = new HttpClient(new HttpClientTransportDynamic(new ClientConnector(), HttpClientConnectionFactory.HTTP11));
+ server.addBean(httpClient);
+
+ server.start();
+
+ ContentResponse response = httpClient.newRequest("localhost", tcp.getLocalPort())
+ .transportProtocol(new TransportProtocol.TCPUnix(unixDomainPath))
+ .timeout(5, TimeUnit.SECONDS)
+ .send();
+
+ assertThat(response.getStatus(), is(HttpStatus.OK_200));
+ assertThat(tcp.getConnectedEndPoints().size(), is(0));
+ assertThat(unix.getConnectedEndPoints().size(), is(1));
+ }
+
+ @Test
+ public void testLowLevelH2OverUNIX() throws Exception
+ {
+ HttpConfiguration httpConfig = new HttpConfiguration();
+ httpConfig.setServerAuthority(new HostPort("localhost"));
+ ConnectionFactory h2c = new HTTP2CServerConnectionFactory(httpConfig);
+ ServerConnector tcp = new ServerConnector(server, 1, 1, h2c);
+ server.addConnector(tcp);
+
+ Path unixDomainPath = newUnixDomainPath();
+ UnixDomainServerConnector unix = new UnixDomainServerConnector(server, 1, 1, h2c);
+ unix.setUnixDomainPath(unixDomainPath);
+ server.addConnector(unix);
+
+ server.setHandler(new EmptyServerHandler());
+
+ server.addBean(http2Client);
+
+ server.start();
+
+ TransportProtocol.TCPUnix transportProtocol = new TransportProtocol.TCPUnix(unixDomainPath);
+ Promise.Completable promise = new Promise.Completable<>();
+ http2Client.connect(transportProtocol, null, new HTTP2ClientConnectionFactory(), new Session.Listener() {}, promise, null);
+ Session session = promise.get(5, TimeUnit.SECONDS);
+
+ CountDownLatch responseLatch = new CountDownLatch(1);
+ MetaData.Request request = new MetaData.Request("GET", HttpURI.from("http://localhost/path"), HttpVersion.HTTP_2, HttpFields.EMPTY);
+ session.newStream(new HeadersFrame(request, null, true), new Stream.Listener()
+ {
+ @Override
+ public void onHeaders(Stream stream, HeadersFrame frame)
+ {
+ MetaData.Response response = (MetaData.Response)frame.getMetaData();
+ assertThat(response.getStatus(), is(HttpStatus.OK_200));
+ responseLatch.countDown();
+ }
+ });
+
+ assertTrue(responseLatch.await(5, TimeUnit.SECONDS));
+ }
+
+ @Test
+ public void testHighLevelH1OverMemory() throws Exception
+ {
+ ConnectionFactory h1 = new HttpConnectionFactory();
+ MemoryConnector local = new MemoryConnector(server, h1);
+ server.addConnector(local);
+
+ server.setHandler(new EmptyServerHandler());
+
+ HttpClient httpClient = new HttpClient(new HttpClientTransportDynamic());
+ server.addBean(httpClient);
+
+ server.start();
+
+ ContentResponse response = httpClient.newRequest("http://localhost/")
+ .transportProtocol(new MemoryTransportProtocol(local))
+ .timeout(5, TimeUnit.SECONDS)
+ .send();
+
+ assertThat(response.getStatus(), is(HttpStatus.OK_200));
+ }
+
+ @Test
+ public void testHighLevelH2OverQUIC(WorkDir workDir) throws Exception
+ {
+ SslContextFactory.Server sslServer = new SslContextFactory.Server();
+ sslServer.setKeyStorePath(MavenPaths.findTestResourceFile("keystore.p12").toString());
+ sslServer.setKeyStorePassword("storepwd");
+
+ ConnectionFactory h2c = new HTTP2CServerConnectionFactory(new HttpConfiguration());
+ ServerQuicConfiguration serverQuicConfiguration = new ServerQuicConfiguration(sslServer, null);
+ QuicServerConnector connector = new QuicServerConnector(server, serverQuicConfiguration, h2c);
+ connector.getQuicConfiguration().setPemWorkDirectory(workDir.getEmptyPathDir());
+ server.addConnector(connector);
+
+ server.setHandler(new EmptyServerHandler());
+
+ HttpClient httpClient = new HttpClient(new HttpClientTransportDynamic(clientConnector, new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client)));
+ server.addBean(httpClient);
+
+ SslContextFactory.Client sslClient = new SslContextFactory.Client(true);
+ httpClient.addBean(sslClient);
+
+ server.start();
+
+ ClientQuicConfiguration clientQuicConfiguration = new ClientQuicConfiguration(sslClient, null);
+ QuicTransportProtocol transportProtocol = new QuicTransportProtocol(clientQuicConfiguration);
+
+ ContentResponse response = httpClient.newRequest("localhost", connector.getLocalPort())
+ .transportProtocol(transportProtocol)
+ .timeout(5, TimeUnit.SECONDS)
+ .send();
+
+ assertThat(response.getStatus(), is(HttpStatus.OK_200));
+ }
+
+ @Test
+ public void testHighLevelH3OverMemory(WorkDir workDir) throws Exception
+ {
+ SslContextFactory.Server sslServer = new SslContextFactory.Server();
+ sslServer.setKeyStorePath(MavenPaths.findTestResourceFile("keystore.p12").toString());
+ sslServer.setKeyStorePassword("storepwd");
+
+ HttpConnectionFactory h1 = new HttpConnectionFactory();
+ ServerQuicConfiguration quicConfiguration = new ServerQuicConfiguration(sslServer, workDir.getEmptyPathDir());
+ QuicServerConnectionFactory quic = new QuicServerConnectionFactory(quicConfiguration);
+ HTTP3ServerConnectionFactory h3 = new HTTP3ServerConnectionFactory(quicConfiguration);
+
+ MemoryConnector connector = new MemoryConnector(server, quic, h1, h3);
+ server.addConnector(connector);
+
+ server.setHandler(new EmptyServerHandler());
+
+ HttpClient httpClient = new HttpClient(new HttpClientTransportDynamic(clientConnector, new ClientConnectionFactoryOverHTTP3.HTTP3(http3Client)));
+ server.addBean(httpClient);
+
+ server.start();
+
+ TransportProtocol transportProtocol = new QuicTransportProtocol(new MemoryTransportProtocol(connector), http3Client.getQuicConfiguration());
+
+ ContentResponse response = httpClient.newRequest("https://localhost/")
+ .transportProtocol(transportProtocol)
+ .timeout(5, TimeUnit.SECONDS)
+ .send();
+
+ assertThat(response.getStatus(), is(HttpStatus.OK_200));
+ }
+
+ @Test
+ public void testHighLevelH1OverProxyProtocolOverQUICOverMemory()
+ {
+ // TODO: UGH! :)
+ assumeTrue(false);
+ }
+
+ private static int freePort() throws IOException
+ {
+ try (ServerSocket server = new ServerSocket())
+ {
+ server.setReuseAddress(true);
+ server.bind(new InetSocketAddress("localhost", 0));
+ return server.getLocalPort();
+ }
+ }
+
+ private static Path newUnixDomainPath()
+ {
+ String unixDomainDir = System.getProperty("jetty.unixdomain.dir", System.getProperty("java.io.tmpdir"));
+ return Path.of(unixDomainDir, "jetty.sock");
+ }
+}
diff --git a/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HttpChannelAssociationTest.java b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HttpChannelAssociationTest.java
index 676f68bb0a7f..95c2d27bbf53 100644
--- a/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HttpChannelAssociationTest.java
+++ b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HttpChannelAssociationTest.java
@@ -41,7 +41,9 @@
import org.eclipse.jetty.http3.client.transport.internal.HttpConnectionOverHTTP3;
import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.quic.client.ClientQuicConfiguration;
import org.eclipse.jetty.util.Promise;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
@@ -169,9 +171,9 @@ public boolean associate(HttpExchange exchange)
}
case H3:
{
- HTTP3Client http3Client = new HTTP3Client();
+ SslContextFactory.Client sslClient = newSslContextFactoryClient();
+ HTTP3Client http3Client = new HTTP3Client(new ClientQuicConfiguration(sslClient, null));
http3Client.getClientConnector().setSelectors(1);
- http3Client.getClientConnector().setSslContextFactory(newSslContextFactoryClient());
yield new HttpClientTransportOverHTTP3(http3Client)
{
@Override
@@ -223,34 +225,6 @@ public boolean associate(HttpExchange exchange)
}
};
}
- case UNIX_DOMAIN:
- {
- ClientConnector clientConnector = ClientConnector.forUnixDomain(unixDomainPath);
- clientConnector.setSelectors(1);
- clientConnector.setSslContextFactory(newSslContextFactoryClient());
- yield new HttpClientTransportOverHTTP(clientConnector)
- {
- @Override
- public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map context)
- {
- return new HttpConnectionOverHTTP(endPoint, context)
- {
- @Override
- protected HttpChannelOverHTTP newHttpChannel()
- {
- return new HttpChannelOverHTTP(this)
- {
- @Override
- public boolean associate(HttpExchange exchange)
- {
- return code.test(exchange) && super.associate(exchange);
- }
- };
- }
- };
- }
- };
- }
};
}
diff --git a/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HttpClientStreamTest.java b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HttpClientStreamTest.java
index 08ca24c6e2df..de15afea3ec3 100644
--- a/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HttpClientStreamTest.java
+++ b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HttpClientStreamTest.java
@@ -879,9 +879,6 @@ public void onComplete(Result result)
@MethodSource("transports")
public void testUploadWithOutputStreamFailureToConnect(Transport transport) throws Exception
{
- // Failure to connect is based on InetSocketAddress failure, which Unix-Domain does not use.
- Assumptions.assumeTrue(transport != Transport.UNIX_DOMAIN);
-
long connectTimeout = 1000;
start(transport, new EmptyServerHandler());
client.setConnectTimeout(connectTimeout);
@@ -960,9 +957,6 @@ public void failed(Throwable x)
@MethodSource("transports")
public void testUploadWithConnectFailureClosesStream(Transport transport) throws Exception
{
- // Failure to connect is based on InetSocket address failure, which Unix-Domain does not use.
- Assumptions.assumeTrue(transport != Transport.UNIX_DOMAIN);
-
long connectTimeout = 1000;
start(transport, new EmptyServerHandler());
client.setConnectTimeout(connectTimeout);
diff --git a/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HttpClientTest.java b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HttpClientTest.java
index 8d4bea61158b..abe2c3f80e5a 100644
--- a/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HttpClientTest.java
+++ b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HttpClientTest.java
@@ -347,7 +347,7 @@ public void testClientCannotValidateServerCertificate(Transport transport) throw
// Use a SslContextFactory.Client that verifies server certificates,
// requests should fail because the server certificate is unknown.
- SslContextFactory.Client clientTLS = newSslContextFactoryClient();
+ SslContextFactory.Client clientTLS = new SslContextFactory.Client();
clientTLS.setEndpointIdentificationAlgorithm("HTTPS");
client.stop();
client.setSslContextFactory(clientTLS);
@@ -671,7 +671,6 @@ public void testOneDestinationPerUser(Transport transport) throws Exception
public void testIPv6Host(Transport transport) throws Exception
{
assumeTrue(Net.isIpv6InterfaceAvailable());
- assumeTrue(transport != Transport.UNIX_DOMAIN);
assumeTrue(transport != Transport.H3);
start(transport, new Handler.Abstract()
@@ -726,8 +725,6 @@ public boolean handle(Request request, org.eclipse.jetty.server.Response respons
@MethodSource("transports")
public void testRequestWithDifferentDestination(Transport transport) throws Exception
{
- assumeTrue(transport != Transport.UNIX_DOMAIN);
-
String requestScheme = newURI(transport).getScheme();
String requestHost = "otherHost.com";
int requestPort = 8888;
diff --git a/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HttpClientTimeoutTest.java b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HttpClientTimeoutTest.java
index bdc6e3a33c66..a7db5a19b982 100644
--- a/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HttpClientTimeoutTest.java
+++ b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HttpClientTimeoutTest.java
@@ -47,7 +47,6 @@
import org.hamcrest.MatcherAssert;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
@@ -86,7 +85,7 @@ public void testTimeoutOnListener(Transport transport) throws Exception
long timeout = 1000;
start(transport, new TimeoutHandler(2 * timeout));
- final CountDownLatch latch = new CountDownLatch(1);
+ CountDownLatch latch = new CountDownLatch(1);
Request request = client.newRequest(newURI(transport))
.timeout(timeout, TimeUnit.MILLISECONDS);
request.send(result ->
@@ -108,7 +107,7 @@ public void testTimeoutOnQueuedRequest(Transport transport) throws Exception
client.setMaxConnectionsPerDestination(1);
// The first request has a long timeout
- final CountDownLatch firstLatch = new CountDownLatch(1);
+ CountDownLatch firstLatch = new CountDownLatch(1);
Request request = client.newRequest(newURI(transport))
.timeout(4 * timeout, TimeUnit.MILLISECONDS);
request.send(result ->
@@ -118,7 +117,7 @@ public void testTimeoutOnQueuedRequest(Transport transport) throws Exception
});
// Second request has a short timeout and should fail in the queue
- final CountDownLatch secondLatch = new CountDownLatch(1);
+ CountDownLatch secondLatch = new CountDownLatch(1);
request = client.newRequest(newURI(transport))
.timeout(timeout, TimeUnit.MILLISECONDS);
request.send(result ->
@@ -140,8 +139,8 @@ public void testTimeoutIsCancelledOnSuccess(Transport transport) throws Exceptio
long timeout = 1000;
start(transport, new TimeoutHandler(timeout));
- final CountDownLatch latch = new CountDownLatch(1);
- final byte[] content = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+ CountDownLatch latch = new CountDownLatch(1);
+ byte[] content = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
Request request = client.newRequest(newURI(transport))
.body(new InputStreamRequestContent(new ByteArrayInputStream(content)))
.timeout(2 * timeout, TimeUnit.MILLISECONDS);
@@ -192,7 +191,6 @@ public void testTimeoutOnListenerWithExplicitConnection(Transport transport) thr
@MethodSource("transports")
public void testTimeoutIsCancelledOnSuccessWithExplicitConnection(Transport transport) throws Exception
{
-
long timeout = 1000;
start(transport, new TimeoutHandler(timeout));
@@ -287,8 +285,8 @@ public void testNonBlockingConnectTimeoutFailsRequest(Transport transport) throw
private void testConnectTimeoutFailsRequest(Transport transport, boolean blocking) throws Exception
{
// Using IANA hosted example.com:81 to reliably produce a Connect Timeout.
- final String host = "example.com";
- final int port = 81;
+ String host = "example.com";
+ int port = 81;
int connectTimeout = 1000;
assumeConnectTimeout(host, port, connectTimeout);
@@ -296,7 +294,7 @@ private void testConnectTimeoutFailsRequest(Transport transport, boolean blockin
client.setConnectTimeout(connectTimeout);
client.setConnectBlocking(blocking);
- final CountDownLatch latch = new CountDownLatch(1);
+ CountDownLatch latch = new CountDownLatch(1);
Request request = client.newRequest(host, port);
request.scheme(newURI(transport).getScheme())
.send(result ->
@@ -314,9 +312,6 @@ private void testConnectTimeoutFailsRequest(Transport transport, boolean blockin
@Tag("external")
public void testConnectTimeoutIsCancelledByShorterRequestTimeout(Transport transport) throws Exception
{
- // Failure to connect is based on InetSocket address failure, which Unix-Domain does not use.
- Assumptions.assumeTrue(transport != Transport.UNIX_DOMAIN);
-
// Using IANA hosted example.com:81 to reliably produce a Connect Timeout.
String host = "example.com";
int port = 81;
@@ -326,8 +321,8 @@ public void testConnectTimeoutIsCancelledByShorterRequestTimeout(Transport trans
start(transport, new EmptyServerHandler());
client.setConnectTimeout(connectTimeout);
- final AtomicInteger completes = new AtomicInteger();
- final CountDownLatch latch = new CountDownLatch(2);
+ AtomicInteger completes = new AtomicInteger();
+ CountDownLatch latch = new CountDownLatch(2);
Request request = client.newRequest(host, port);
request.scheme(newURI(transport).getScheme())
.timeout(connectTimeout / 2, TimeUnit.MILLISECONDS)
@@ -347,9 +342,6 @@ public void testConnectTimeoutIsCancelledByShorterRequestTimeout(Transport trans
@Tag("external")
public void testRetryAfterConnectTimeout(Transport transport) throws Exception
{
- // Failure to connect is based on InetSocket address failure, which Unix-Domain does not use.
- Assumptions.assumeTrue(transport != Transport.UNIX_DOMAIN);
-
// Using IANA hosted example.com:81 to reliably produce a Connect Timeout.
String host = "example.com";
int port = 81;
@@ -359,7 +351,7 @@ public void testRetryAfterConnectTimeout(Transport transport) throws Exception
start(transport, new EmptyServerHandler());
client.setConnectTimeout(connectTimeout);
- final CountDownLatch latch = new CountDownLatch(1);
+ CountDownLatch latch = new CountDownLatch(1);
Request request = client.newRequest(host, port);
String scheme = newURI(transport).getScheme();
request.scheme(scheme)
@@ -392,7 +384,7 @@ public void testVeryShortTimeout(Transport transport) throws Exception
{
start(transport, new EmptyServerHandler());
- final CountDownLatch latch = new CountDownLatch(1);
+ CountDownLatch latch = new CountDownLatch(1);
client.newRequest(newURI(transport))
.timeout(1, TimeUnit.MILLISECONDS) // Very short timeout
.send(result -> latch.countDown());
diff --git a/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/RoundRobinConnectionPoolTest.java b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/RoundRobinConnectionPoolTest.java
index 7447f21276a9..bb6ff64bd448 100644
--- a/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/RoundRobinConnectionPoolTest.java
+++ b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/RoundRobinConnectionPoolTest.java
@@ -99,7 +99,7 @@ public boolean handle(Request request, Response response, Callback callback)
int expected = remotePorts.get(base);
int candidate = remotePorts.get(i);
assertThat(client.dump() + System.lineSeparator() + remotePorts, expected, Matchers.equalTo(candidate));
- if (transport != Transport.UNIX_DOMAIN && i > 0)
+ if (i > 0)
assertThat(remotePorts.get(i - 1), Matchers.not(Matchers.equalTo(candidate)));
}
}
@@ -195,7 +195,7 @@ public boolean handle(Request request, Response response, Callback callback)
int expected = remotePorts.get(base);
int candidate = remotePorts.get(i);
assertThat(client.dump() + System.lineSeparator() + remotePorts, expected, Matchers.equalTo(candidate));
- if (transport != Transport.UNIX_DOMAIN && i > 0)
+ if (i > 0)
assertThat(remotePorts.get(i - 1), Matchers.not(Matchers.equalTo(candidate)));
}
}
@@ -248,10 +248,6 @@ public boolean handle(Request request, Response response, Callback callback)
assertTrue(clientLatch.await(count, TimeUnit.SECONDS));
assertEquals(count, remotePorts.size());
- // Unix Domain does not have ports.
- if (transport == Transport.UNIX_DOMAIN)
- return;
-
// UDP does not have TIME_WAIT so ports may be reused by different connections.
if (transport == Transport.H3)
return;
diff --git a/jetty-core/jetty-unixdomain-server/src/main/java/org/eclipse/jetty/unixdomain/server/UnixDomainServerConnector.java b/jetty-core/jetty-unixdomain-server/src/main/java/org/eclipse/jetty/unixdomain/server/UnixDomainServerConnector.java
index b1665e433f31..f9aa9ad4003a 100644
--- a/jetty-core/jetty-unixdomain-server/src/main/java/org/eclipse/jetty/unixdomain/server/UnixDomainServerConnector.java
+++ b/jetty-core/jetty-unixdomain-server/src/main/java/org/eclipse/jetty/unixdomain/server/UnixDomainServerConnector.java
@@ -145,6 +145,18 @@ public void setAcceptedSendBufferSize(int acceptedSendBufferSize)
this.acceptedSendBufferSize = acceptedSendBufferSize;
}
+ public SocketAddress getLocalSocketAddress()
+ {
+ try
+ {
+ return serverChannel == null ? null : serverChannel.getLocalAddress();
+ }
+ catch (Throwable x)
+ {
+ return null;
+ }
+ }
+
@Override
protected void doStart() throws Exception
{
diff --git a/jetty-core/jetty-unixdomain-server/src/test/java/org/eclipse/jetty/unixdomain/server/UnixDomainTest.java b/jetty-core/jetty-unixdomain-server/src/test/java/org/eclipse/jetty/unixdomain/server/UnixDomainTest.java
index a4fd8b4bfb96..ee172437e6f2 100644
--- a/jetty-core/jetty-unixdomain-server/src/test/java/org/eclipse/jetty/unixdomain/server/UnixDomainTest.java
+++ b/jetty-core/jetty-unixdomain-server/src/test/java/org/eclipse/jetty/unixdomain/server/UnixDomainTest.java
@@ -15,18 +15,21 @@
import java.io.IOException;
import java.net.SocketAddress;
+import java.net.UnixDomainSocketAddress;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.util.List;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.client.ContentResponse;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.HttpProxy;
+import org.eclipse.jetty.client.Origin;
import org.eclipse.jetty.client.transport.HttpClientTransportDynamic;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpURI;
-import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.TransportProtocol;
import org.eclipse.jetty.server.ConnectionFactory;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConnectionFactory;
@@ -40,11 +43,7 @@
import org.hamcrest.Matchers;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.Assumptions;
-import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.condition.EnabledForJreRange;
-import org.junit.jupiter.api.condition.JRE;
import static org.eclipse.jetty.client.ProxyProtocolClientConnectionFactory.V1;
import static org.eclipse.jetty.client.ProxyProtocolClientConnectionFactory.V2;
@@ -56,33 +55,12 @@
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
-@EnabledForJreRange(min = JRE.JAVA_16)
public class UnixDomainTest
{
- private static final Class> unixDomainSocketAddressClass = probe();
-
- private static Class> probe()
- {
- try
- {
- return ClassLoader.getPlatformClassLoader().loadClass("java.net.UnixDomainSocketAddress");
- }
- catch (Throwable x)
- {
- return null;
- }
- }
-
private ConnectionFactory[] factories = new ConnectionFactory[]{new HttpConnectionFactory()};
private Server server;
private Path unixDomainPath;
- @BeforeEach
- public void prepare()
- {
- Assumptions.assumeTrue(unixDomainSocketAddressClass != null);
- }
-
private void start(Handler handler) throws Exception
{
server = new Server();
@@ -120,9 +98,9 @@ public boolean handle(Request request, Response response, Callback callback)
// Verify the SocketAddresses.
SocketAddress local = endPoint.getLocalSocketAddress();
- assertThat(local, Matchers.instanceOf(unixDomainSocketAddressClass));
+ assertThat(local, Matchers.instanceOf(UnixDomainSocketAddress.class));
SocketAddress remote = endPoint.getRemoteSocketAddress();
- assertThat(remote, Matchers.instanceOf(unixDomainSocketAddressClass));
+ assertThat(remote, Matchers.instanceOf(UnixDomainSocketAddress.class));
// Verify that other address methods don't throw.
local = assertDoesNotThrow(endPoint::getLocalAddress);
@@ -137,12 +115,12 @@ public boolean handle(Request request, Response response, Callback callback)
}
});
- ClientConnector clientConnector = ClientConnector.forUnixDomain(unixDomainPath);
- HttpClient httpClient = new HttpClient(new HttpClientTransportDynamic(clientConnector));
+ HttpClient httpClient = new HttpClient(new HttpClientTransportDynamic());
httpClient.start();
try
{
ContentResponse response = httpClient.newRequest(uri)
+ .transportProtocol(new TransportProtocol.TCPUnix(unixDomainPath))
.timeout(5, TimeUnit.SECONDS)
.send();
@@ -173,14 +151,20 @@ public boolean handle(Request request, Response response, Callback callback)
}
});
- ClientConnector clientConnector = ClientConnector.forUnixDomain(unixDomainPath);
-
- HttpClient httpClient = new HttpClient(new HttpClientTransportDynamic(clientConnector));
- httpClient.getProxyConfiguration().addProxy(new HttpProxy("localhost", fakeProxyPort));
+ HttpClient httpClient = new HttpClient(new HttpClientTransportDynamic());
+ Origin proxyOrigin = new Origin(
+ "http",
+ new Origin.Address("localhost", fakeProxyPort),
+ null,
+ new Origin.Protocol(List.of("http/1.1"), false),
+ new TransportProtocol.TCPUnix(unixDomainPath)
+ );
+ httpClient.getProxyConfiguration().addProxy(new HttpProxy(proxyOrigin, null));
httpClient.start();
try
{
ContentResponse response = httpClient.newRequest("localhost", fakeServerPort)
+ .transportProtocol(new TransportProtocol.TCPUnix(unixDomainPath))
.timeout(5, TimeUnit.SECONDS)
.send();
@@ -205,19 +189,20 @@ public boolean handle(Request request, Response response, Callback callback)
{
EndPoint endPoint = request.getConnectionMetaData().getConnection().getEndPoint();
assertThat(endPoint, Matchers.instanceOf(ProxyConnectionFactory.ProxyEndPoint.class));
- assertThat(endPoint.getLocalSocketAddress(), Matchers.instanceOf(unixDomainSocketAddressClass));
- assertThat(endPoint.getRemoteSocketAddress(), Matchers.instanceOf(unixDomainSocketAddressClass));
+ assertThat(endPoint.getLocalSocketAddress(), Matchers.instanceOf(UnixDomainSocketAddress.class));
+ assertThat(endPoint.getRemoteSocketAddress(), Matchers.instanceOf(UnixDomainSocketAddress.class));
String target = Request.getPathInContext(request);
+ Path localPath = ((UnixDomainSocketAddress)endPoint.getLocalSocketAddress()).getPath();
if ("/v1".equals(target))
{
// As PROXYv1 does not support UNIX, the wrapped EndPoint data is used.
- Path localPath = toUnixDomainPath(endPoint.getLocalSocketAddress());
assertThat(localPath, Matchers.equalTo(unixDomainPath));
}
else if ("/v2".equals(target))
{
- assertThat(toUnixDomainPath(endPoint.getLocalSocketAddress()).toString(), Matchers.equalTo(FS.separators(dstAddr)));
- assertThat(toUnixDomainPath(endPoint.getRemoteSocketAddress()).toString(), Matchers.equalTo(FS.separators(srcAddr)));
+ assertThat(localPath.toString(), Matchers.equalTo(FS.separators(dstAddr)));
+ Path remotePath = ((UnixDomainSocketAddress)endPoint.getRemoteSocketAddress()).getPath();
+ assertThat(remotePath.toString(), Matchers.equalTo(FS.separators(srcAddr)));
}
else
{
@@ -228,16 +213,14 @@ else if ("/v2".equals(target))
}
});
- // Java 11+ portable way to implement SocketChannelWithAddress.Factory.
- ClientConnector clientConnector = ClientConnector.forUnixDomain(unixDomainPath);
-
- HttpClient httpClient = new HttpClient(new HttpClientTransportDynamic(clientConnector));
+ HttpClient httpClient = new HttpClient(new HttpClientTransportDynamic());
httpClient.start();
try
{
// Try PROXYv1 with the PROXY information retrieved from the EndPoint.
// PROXYv1 does not support the UNIX family.
ContentResponse response1 = httpClient.newRequest("localhost", 0)
+ .transportProtocol(new TransportProtocol.TCPUnix(unixDomainPath))
.path("/v1")
.tag(new V1.Tag())
.timeout(5, TimeUnit.SECONDS)
@@ -248,6 +231,7 @@ else if ("/v2".equals(target))
// Try PROXYv2 with explicit PROXY information.
var tag = new V2.Tag(V2.Tag.Command.PROXY, V2.Tag.Family.UNIX, V2.Tag.Protocol.STREAM, srcAddr, 0, dstAddr, 0, null);
ContentResponse response2 = httpClient.newRequest("localhost", 0)
+ .transportProtocol(new TransportProtocol.TCPUnix(unixDomainPath))
.path("/v2")
.tag(tag)
.timeout(5, TimeUnit.SECONDS)
@@ -270,18 +254,4 @@ public void testInvalidUnixDomainPath()
server.addConnector(connector);
assertThrows(IOException.class, () -> server.start());
}
-
- private static Path toUnixDomainPath(SocketAddress address)
- {
- try
- {
- Assertions.assertNotNull(unixDomainSocketAddressClass);
- return (Path)unixDomainSocketAddressClass.getMethod("getPath").invoke(address);
- }
- catch (Throwable x)
- {
- Assertions.fail(x);
- throw new AssertionError();
- }
- }
}
diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/component/ContainerLifeCycle.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/component/ContainerLifeCycle.java
index 143e43c45c01..2b8f639b271a 100644
--- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/component/ContainerLifeCycle.java
+++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/component/ContainerLifeCycle.java
@@ -49,7 +49,7 @@
* If the added bean is !running and the container is started, it will be added as an unmanaged bean.
*
* When the container is started, then all contained managed beans will also be started.
- * Any contained AUTO beans will be check for their status and if already started will be switched unmanaged beans,
+ * Any contained AUTO beans will be checked for their status and if already started will be switched unmanaged beans,
* else they will be started and switched to managed beans.
* Beans added after a container is started are not started and their state needs to be explicitly managed.
*
@@ -104,9 +104,8 @@ protected void doStart() throws Exception
{
if (!isStarting())
break;
- if (b._bean instanceof LifeCycle)
+ if (b._bean instanceof LifeCycle l)
{
- LifeCycle l = (LifeCycle)b._bean;
switch (b._managed)
{
case MANAGED:
@@ -139,9 +138,8 @@ protected void doStart() throws Exception
Collections.reverse(reverse);
for (Bean b : reverse)
{
- if (b._bean instanceof LifeCycle && b._managed == Managed.MANAGED)
+ if (b._bean instanceof LifeCycle l && b._managed == Managed.MANAGED)
{
- LifeCycle l = (LifeCycle)b._bean;
if (l.isRunning())
{
try
@@ -197,9 +195,8 @@ protected void doStop() throws Exception
{
if (!isStopping())
break;
- if (b._managed == Managed.MANAGED && b._bean instanceof LifeCycle)
+ if (b._managed == Managed.MANAGED && b._bean instanceof LifeCycle l)
{
- LifeCycle l = (LifeCycle)b._bean;
try
{
stop(l);
@@ -224,9 +221,8 @@ public void destroy()
Collections.reverse(reverse);
for (Bean b : reverse)
{
- if (b._bean instanceof Destroyable && (b._managed == Managed.MANAGED || b._managed == Managed.POJO))
+ if (b._bean instanceof Destroyable d && (b._managed == Managed.MANAGED || b._managed == Managed.POJO))
{
- Destroyable d = (Destroyable)b._bean;
try
{
d.destroy();
@@ -311,11 +307,8 @@ public boolean isUnmanaged(Object bean)
@Override
public boolean addBean(Object o)
{
- if (o instanceof LifeCycle)
- {
- LifeCycle l = (LifeCycle)o;
+ if (o instanceof LifeCycle l)
return addBean(o, l.isRunning() ? Managed.UNMANAGED : Managed.AUTO);
- }
return addBean(o, Managed.POJO);
}
@@ -324,7 +317,7 @@ public boolean addBean(Object o)
* Adds the given bean, explicitly managing it or not.
*
* @param o The bean object to add
- * @param managed whether to managed the lifecycle of the bean
+ * @param managed whether to manage the lifecycle of the bean
* @return true if the bean was added, false if it was already present
*/
@Override
@@ -374,9 +367,8 @@ private boolean addBean(Object o, Managed managed)
break;
case AUTO:
- if (o instanceof LifeCycle)
+ if (o instanceof LifeCycle l)
{
- LifeCycle l = (LifeCycle)o;
if (isStarting())
{
if (l.isRunning())
@@ -460,9 +452,8 @@ public boolean addEventListener(EventListener listener)
// already been added, so we will not enter this branch.
addBean(listener);
- if (listener instanceof Container.Listener)
+ if (listener instanceof Container.Listener cl)
{
- Container.Listener cl = (Container.Listener)listener;
_listeners.add(cl);
// tell it about existing beans
@@ -491,9 +482,8 @@ public boolean removeEventListener(EventListener listener)
if (super.removeEventListener(listener))
{
removeBean(listener);
- if (listener instanceof Container.Listener && _listeners.remove(listener))
+ if (listener instanceof Container.Listener cl && _listeners.remove(listener))
{
- Container.Listener cl = (Container.Listener)listener;
// remove existing beans
for (Bean b : _beans)
{
@@ -774,15 +764,12 @@ public boolean isManaged()
public boolean isManageable()
{
- switch (_managed)
+ return switch (_managed)
{
- case MANAGED:
- return true;
- case AUTO:
- return _bean instanceof LifeCycle && ((LifeCycle)_bean).isStopped();
- default:
- return false;
- }
+ case MANAGED -> true;
+ case AUTO -> _bean instanceof LifeCycle && ((LifeCycle)_bean).isStopped();
+ default -> false;
+ };
}
@Override
diff --git a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/AbstractTest.java b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/AbstractTest.java
index 05a1b755a023..c280e2ae4164 100644
--- a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/AbstractTest.java
+++ b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/AbstractTest.java
@@ -42,8 +42,10 @@
import org.eclipse.jetty.http3.client.transport.HttpClientTransportOverHTTP3;
import org.eclipse.jetty.http3.server.AbstractHTTP3ServerConnectionFactory;
import org.eclipse.jetty.http3.server.HTTP3ServerConnectionFactory;
-import org.eclipse.jetty.http3.server.HTTP3ServerConnector;
import org.eclipse.jetty.io.ClientConnector;
+import org.eclipse.jetty.quic.client.ClientQuicConfiguration;
+import org.eclipse.jetty.quic.server.QuicServerConnector;
+import org.eclipse.jetty.quic.server.ServerQuicConfiguration;
import org.eclipse.jetty.server.AbstractConnector;
import org.eclipse.jetty.server.ConnectionFactory;
import org.eclipse.jetty.server.HostHeaderCustomizer;
@@ -56,7 +58,6 @@
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
-import org.eclipse.jetty.unixdomain.server.UnixDomainServerConnector;
import org.eclipse.jetty.util.SocketAddressResolver;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.ssl.SslContextFactory;
@@ -64,8 +65,6 @@
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.extension.ExtendWith;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
@ExtendWith(WorkDirExtension.class)
public class AbstractTest
{
@@ -73,11 +72,11 @@ public class AbstractTest
protected final HttpConfiguration httpConfig = new HttpConfiguration();
protected SslContextFactory.Server sslContextFactoryServer;
+ protected ServerQuicConfiguration serverQuicConfig;
protected Server server;
protected AbstractConnector connector;
protected ServletContextHandler servletContextHandler;
protected HttpClient client;
- protected Path unixDomainPath;
public static Collection transports()
{
@@ -122,14 +121,8 @@ protected void startServer(Transport transport, HttpServlet servlet) throws Exce
protected void prepareServer(Transport transport, HttpServlet servlet) throws Exception
{
- if (transport == Transport.UNIX_DOMAIN)
- {
- String unixDomainDir = System.getProperty("jetty.unixdomain.dir", System.getProperty("java.io.tmpdir"));
- unixDomainPath = Files.createTempFile(Path.of(unixDomainDir), "unix_", ".sock");
- assertTrue(unixDomainPath.toAbsolutePath().toString().length() < UnixDomainServerConnector.MAX_UNIX_DOMAIN_PATH_LENGTH, "Unix-Domain path too long");
- Files.delete(unixDomainPath);
- }
sslContextFactoryServer = newSslContextFactoryServer();
+ serverQuicConfig = new ServerQuicConfiguration(sslContextFactoryServer, workDir.getEmptyPathDir());
if (server == null)
server = newServer();
connector = newConnector(transport, server);
@@ -194,17 +187,7 @@ public AbstractConnector newConnector(Transport transport, Server server)
case HTTP, HTTPS, H2C, H2, FCGI ->
new ServerConnector(server, 1, 1, newServerConnectionFactory(transport));
case H3 ->
- {
- HTTP3ServerConnector connector = new HTTP3ServerConnector(server, sslContextFactoryServer, newServerConnectionFactory(transport));
- connector.getQuicConfiguration().setPemWorkDirectory(workDir.getEmptyPathDir());
- yield connector;
- }
- case UNIX_DOMAIN ->
- {
- UnixDomainServerConnector connector = new UnixDomainServerConnector(server, 1, 1, newServerConnectionFactory(transport));
- connector.setUnixDomainPath(unixDomainPath);
- yield connector;
- }
+ new QuicServerConnector(server, serverQuicConfig, newServerConnectionFactory(transport));
};
}
@@ -212,7 +195,7 @@ protected ConnectionFactory[] newServerConnectionFactory(Transport transport)
{
List list = switch (transport)
{
- case HTTP, UNIX_DOMAIN ->
+ case HTTP ->
List.of(new HttpConnectionFactory(httpConfig));
case HTTPS ->
{
@@ -239,7 +222,7 @@ protected ConnectionFactory[] newServerConnectionFactory(Transport transport)
{
httpConfig.addCustomizer(new SecureRequestCustomizer());
httpConfig.addCustomizer(new HostHeaderCustomizer());
- yield List.of(new HTTP3ServerConnectionFactory(httpConfig));
+ yield List.of(new HTTP3ServerConnectionFactory(serverQuicConfig, httpConfig));
}
case FCGI -> List.of(new ServerFCGIConnectionFactory(httpConfig));
};
@@ -275,20 +258,14 @@ protected HttpClientTransport newHttpClientTransport(Transport transport) throws
}
case H3 ->
{
- HTTP3Client http3Client = new HTTP3Client();
- ClientConnector clientConnector = http3Client.getClientConnector();
+ ClientConnector clientConnector = new ClientConnector();
clientConnector.setSelectors(1);
- clientConnector.setSslContextFactory(newSslContextFactoryClient());
+ SslContextFactory.Client sslContextFactory = newSslContextFactoryClient();
+ clientConnector.setSslContextFactory(sslContextFactory);
+ HTTP3Client http3Client = new HTTP3Client(new ClientQuicConfiguration(sslContextFactory, null));
yield new HttpClientTransportOverHTTP3(http3Client);
}
case FCGI -> new HttpClientTransportOverFCGI(1, "");
- case UNIX_DOMAIN ->
- {
- ClientConnector clientConnector = ClientConnector.forUnixDomain(unixDomainPath);
- clientConnector.setSelectors(1);
- clientConnector.setSslContextFactory(newSslContextFactoryClient());
- yield new HttpClientTransportOverHTTP(clientConnector);
- }
};
}
@@ -320,13 +297,13 @@ protected void setStreamIdleTimeout(long idleTimeout)
public enum Transport
{
- HTTP, HTTPS, H2C, H2, H3, FCGI, UNIX_DOMAIN;
+ HTTP, HTTPS, H2C, H2, H3, FCGI;
public boolean isSecure()
{
return switch (this)
{
- case HTTP, H2C, FCGI, UNIX_DOMAIN -> false;
+ case HTTP, H2C, FCGI -> false;
case HTTPS, H2, H3 -> true;
};
}
diff --git a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/AsyncIOServletTest.java b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/AsyncIOServletTest.java
index 9f20da747fd0..41472d1fd7c0 100644
--- a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/AsyncIOServletTest.java
+++ b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/AsyncIOServletTest.java
@@ -1136,7 +1136,7 @@ public void onError(Throwable x)
.body(requestContent)
.onResponseSuccess(response ->
{
- if (transport == Transport.HTTP || transport == Transport.UNIX_DOMAIN)
+ if (transport == Transport.HTTP)
responseLatch.countDown();
})
.onResponseFailure((response, failure) ->
@@ -1155,7 +1155,6 @@ public void onError(Throwable x)
switch (transport)
{
case HTTP:
- case UNIX_DOMAIN:
assertThat(result.getResponse().getStatus(), Matchers.equalTo(responseCode));
break;
case H2C:
@@ -1173,7 +1172,6 @@ public void onError(Throwable x)
switch (transport)
{
case HTTP:
- case UNIX_DOMAIN:
((HttpConnectionOverHTTP)connection).getEndPoint().shutdownOutput();
break;
case H2C:
diff --git a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/HttpClientContinueTest.java b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/HttpClientContinueTest.java
index ac535ce011f3..11b33f6cc8b8 100644
--- a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/HttpClientContinueTest.java
+++ b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/HttpClientContinueTest.java
@@ -214,7 +214,7 @@ public long getLength()
assertNotNull(response);
assertEquals(200, response.getStatus());
- if (EnumSet.of(Transport.HTTP, Transport.HTTPS, Transport.UNIX_DOMAIN).contains(transport))
+ if (EnumSet.of(Transport.HTTP, Transport.HTTPS).contains(transport))
assertTrue(response.getHeaders().contains(HttpHeader.TRANSFER_ENCODING, "chunked"));
int index = 0;
diff --git a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-client-transports/src/test/java/org/eclipse/jetty/ee9/test/client/transport/AbstractTest.java b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-client-transports/src/test/java/org/eclipse/jetty/ee9/test/client/transport/AbstractTest.java
index e73d3ca6676d..216a11d5c7b0 100644
--- a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-client-transports/src/test/java/org/eclipse/jetty/ee9/test/client/transport/AbstractTest.java
+++ b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-client-transports/src/test/java/org/eclipse/jetty/ee9/test/client/transport/AbstractTest.java
@@ -46,8 +46,10 @@
import org.eclipse.jetty.http3.client.transport.HttpClientTransportOverHTTP3;
import org.eclipse.jetty.http3.server.AbstractHTTP3ServerConnectionFactory;
import org.eclipse.jetty.http3.server.HTTP3ServerConnectionFactory;
-import org.eclipse.jetty.http3.server.HTTP3ServerConnector;
import org.eclipse.jetty.io.ClientConnector;
+import org.eclipse.jetty.quic.client.ClientQuicConfiguration;
+import org.eclipse.jetty.quic.server.QuicServerConnector;
+import org.eclipse.jetty.quic.server.ServerQuicConfiguration;
import org.eclipse.jetty.server.AbstractConnector;
import org.eclipse.jetty.server.ConnectionFactory;
import org.eclipse.jetty.server.HostHeaderCustomizer;
@@ -60,7 +62,6 @@
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
-import org.eclipse.jetty.unixdomain.server.UnixDomainServerConnector;
import org.eclipse.jetty.util.SocketAddressResolver;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.ssl.SslContextFactory;
@@ -68,8 +69,6 @@
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.extension.ExtendWith;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
@ExtendWith(WorkDirExtension.class)
public class AbstractTest
{
@@ -77,11 +76,11 @@ public class AbstractTest
protected final HttpConfiguration httpConfig = new HttpConfiguration();
protected SslContextFactory.Server sslContextFactoryServer;
+ protected ServerQuicConfiguration serverQuicConfig;
protected Server server;
protected AbstractConnector connector;
protected ServletContextHandler servletContextHandler;
protected HttpClient client;
- protected Path unixDomainPath;
public static Collection transports()
{
@@ -131,14 +130,8 @@ protected void prepareServer(Transport transport, HttpServlet servlet) throws Ex
protected void prepareServer(Transport transport, HttpServlet servlet, String path) throws Exception
{
- if (transport == Transport.UNIX_DOMAIN)
- {
- String unixDomainDir = System.getProperty("jetty.unixdomain.dir", System.getProperty("java.io.tmpdir"));
- unixDomainPath = Files.createTempFile(Path.of(unixDomainDir), "unix_", ".sock");
- assertTrue(unixDomainPath.toAbsolutePath().toString().length() < UnixDomainServerConnector.MAX_UNIX_DOMAIN_PATH_LENGTH, "Unix-Domain path too long");
- Files.delete(unixDomainPath);
- }
sslContextFactoryServer = newSslContextFactoryServer();
+ serverQuicConfig = new ServerQuicConfiguration(sslContextFactoryServer, workDir.getEmptyPathDir());
if (server == null)
server = newServer();
connector = newConnector(transport, server);
@@ -148,7 +141,7 @@ protected void prepareServer(Transport transport, HttpServlet servlet, String pa
server.setHandler(servletContextHandler);
}
- protected void addServlet(HttpServlet servlet, String path) throws Exception
+ protected void addServlet(HttpServlet servlet, String path)
{
Objects.requireNonNull(servletContextHandler);
ServletHolder holder = new ServletHolder(servlet);
@@ -201,17 +194,7 @@ public AbstractConnector newConnector(Transport transport, Server server)
case HTTP, HTTPS, H2C, H2, FCGI ->
new ServerConnector(server, 1, 1, newServerConnectionFactory(transport));
case H3 ->
- {
- HTTP3ServerConnector http3ServerConnector = new HTTP3ServerConnector(server, sslContextFactoryServer, newServerConnectionFactory(transport));
- http3ServerConnector.getQuicConfiguration().setPemWorkDirectory(workDir.getEmptyPathDir());
- yield http3ServerConnector;
- }
- case UNIX_DOMAIN ->
- {
- UnixDomainServerConnector connector = new UnixDomainServerConnector(server, 1, 1, newServerConnectionFactory(transport));
- connector.setUnixDomainPath(unixDomainPath);
- yield connector;
- }
+ new QuicServerConnector(server, serverQuicConfig, newServerConnectionFactory(transport));
};
}
@@ -219,7 +202,7 @@ protected ConnectionFactory[] newServerConnectionFactory(Transport transport)
{
List list = switch (transport)
{
- case HTTP, UNIX_DOMAIN -> List.of(new HttpConnectionFactory(httpConfig));
+ case HTTP -> List.of(new HttpConnectionFactory(httpConfig));
case HTTPS ->
{
httpConfig.addCustomizer(new SecureRequestCustomizer());
@@ -245,7 +228,7 @@ protected ConnectionFactory[] newServerConnectionFactory(Transport transport)
{
httpConfig.addCustomizer(new SecureRequestCustomizer());
httpConfig.addCustomizer(new HostHeaderCustomizer());
- yield List.of(new HTTP3ServerConnectionFactory(httpConfig));
+ yield List.of(new HTTP3ServerConnectionFactory(serverQuicConfig, httpConfig));
}
case FCGI -> List.of(new ServerFCGIConnectionFactory(httpConfig));
};
@@ -281,20 +264,14 @@ protected HttpClientTransport newHttpClientTransport(Transport transport) throws
}
case H3 ->
{
- HTTP3Client http3Client = new HTTP3Client();
- ClientConnector clientConnector = http3Client.getClientConnector();
+ ClientConnector clientConnector = new ClientConnector();
clientConnector.setSelectors(1);
- clientConnector.setSslContextFactory(newSslContextFactoryClient());
+ SslContextFactory.Client sslContextFactory = newSslContextFactoryClient();
+ clientConnector.setSslContextFactory(sslContextFactory);
+ HTTP3Client http3Client = new HTTP3Client(new ClientQuicConfiguration(sslContextFactory, null));
yield new HttpClientTransportOverHTTP3(http3Client);
}
case FCGI -> new HttpClientTransportOverFCGI(1, "");
- case UNIX_DOMAIN ->
- {
- ClientConnector clientConnector = ClientConnector.forUnixDomain(unixDomainPath);
- clientConnector.setSelectors(1);
- clientConnector.setSslContextFactory(newSslContextFactoryClient());
- yield new HttpClientTransportOverHTTP(clientConnector);
- }
};
}
@@ -326,13 +303,13 @@ protected void setStreamIdleTimeout(long idleTimeout)
public enum Transport
{
- HTTP, HTTPS, H2C, H2, H3, FCGI, UNIX_DOMAIN;
+ HTTP, HTTPS, H2C, H2, H3, FCGI;
public boolean isSecure()
{
return switch (this)
{
- case HTTP, H2C, FCGI, UNIX_DOMAIN -> false;
+ case HTTP, H2C, FCGI -> false;
case HTTPS, H2, H3 -> true;
};
}
diff --git a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-client-transports/src/test/java/org/eclipse/jetty/ee9/test/client/transport/AsyncIOServletTest.java b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-client-transports/src/test/java/org/eclipse/jetty/ee9/test/client/transport/AsyncIOServletTest.java
index 3c455e8d2038..4a10ca2b01fe 100644
--- a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-client-transports/src/test/java/org/eclipse/jetty/ee9/test/client/transport/AsyncIOServletTest.java
+++ b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-client-transports/src/test/java/org/eclipse/jetty/ee9/test/client/transport/AsyncIOServletTest.java
@@ -1138,7 +1138,7 @@ public void onError(Throwable x)
.body(requestContent)
.onResponseSuccess(response ->
{
- if (transport == Transport.HTTP || transport == Transport.UNIX_DOMAIN)
+ if (transport == Transport.HTTP)
responseLatch.countDown();
})
.onResponseFailure((response, failure) ->
@@ -1157,7 +1157,6 @@ public void onError(Throwable x)
switch (transport)
{
case HTTP:
- case UNIX_DOMAIN:
assertThat(result.getResponse().getStatus(), Matchers.equalTo(responseCode));
break;
case H2C:
@@ -1175,7 +1174,6 @@ public void onError(Throwable x)
switch (transport)
{
case HTTP:
- case UNIX_DOMAIN:
((HttpConnectionOverHTTP)connection).getEndPoint().shutdownOutput();
break;
case H2C:
diff --git a/tests/test-distribution/test-distribution-common/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java b/tests/test-distribution/test-distribution-common/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java
index 607cc2fc1820..8d1a0dca7e14 100644
--- a/tests/test-distribution/test-distribution-common/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java
+++ b/tests/test-distribution/test-distribution-common/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java
@@ -40,6 +40,7 @@
import org.eclipse.jetty.client.ContentResponse;
import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.transport.HttpClientConnectionFactory;
import org.eclipse.jetty.client.transport.HttpClientTransportDynamic;
import org.eclipse.jetty.client.transport.HttpClientTransportOverHTTP;
import org.eclipse.jetty.http.HttpHeader;
@@ -53,7 +54,9 @@
import org.eclipse.jetty.http3.client.transport.HttpClientTransportOverHTTP3;
import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.io.Content;
+import org.eclipse.jetty.io.TransportProtocol;
import org.eclipse.jetty.io.content.ByteBufferContentSource;
+import org.eclipse.jetty.quic.client.ClientQuicConfiguration;
import org.eclipse.jetty.tests.testers.JettyHomeTester;
import org.eclipse.jetty.tests.testers.Tester;
import org.eclipse.jetty.toolchain.test.FS;
@@ -968,7 +971,6 @@ public void testDefaultLoggingProviderNotActiveWhenExplicitProviderIsPresent() t
}
@Test
- @EnabledForJreRange(min = JRE.JAVA_16)
public void testUnixDomain() throws Exception
{
Path jettyBase = newTestJettyBaseDirectory();
@@ -992,10 +994,12 @@ public void testUnixDomain() throws Exception
{
assertTrue(run2.awaitConsoleLogsFor("Started oejs.Server@", START_TIMEOUT, TimeUnit.SECONDS));
- ClientConnector connector = ClientConnector.forUnixDomain(path);
- client = new HttpClient(new HttpClientTransportDynamic(connector));
+ ClientConnector connector = new ClientConnector();
+ client = new HttpClient(new HttpClientTransportDynamic(connector, HttpClientConnectionFactory.HTTP11));
client.start();
- ContentResponse response = client.GET("http://localhost/path");
+ ContentResponse response = client.newRequest("http://localhost/path")
+ .transportProtocol(new TransportProtocol.TCPUnix(path))
+ .send();
assertEquals(HttpStatus.NOT_FOUND_404, response.getStatus());
}
}
@@ -1188,8 +1192,8 @@ public void testH3() throws Exception
{
assertTrue(run2.awaitConsoleLogsFor("Started oejs.Server@", START_TIMEOUT, TimeUnit.SECONDS));
- HTTP3Client http3Client = new HTTP3Client();
- http3Client.getClientConnector().setSslContextFactory(new SslContextFactory.Client(true));
+ SslContextFactory.Client sslContextFactory = new SslContextFactory.Client(true);
+ HTTP3Client http3Client = new HTTP3Client(new ClientQuicConfiguration(sslContextFactory, null));
this.client = new HttpClient(new HttpClientTransportOverHTTP3(http3Client));
this.client.start();
ContentResponse response = this.client.newRequest("localhost", h3Port)