From 7461adaad7b6a31649dbc0b2832f6840318c7ab1 Mon Sep 17 00:00:00 2001
From: Simone Bordet
Date: Wed, 10 Jan 2024 10:20:45 +0100
Subject: [PATCH 01/13] Fixes #8979 - Jetty 12 - HttpClientTransport network
"modes".
* Introduced oej.io.TransportProtocol as the abstraction for the low-level transport of high-level protocols.
Now protocols such as HTTP/1.1 or HTTP/2 can be transported over QUIC, Unix-Domain, memory, and possibly over other low-level custom protocols too.
* Introduced oej.client.Request.transportProtocol(TransportProtocol) to specify TransportProtocol for each request.
* Introduced TransportProtocol to [HTTP2Client|HTTP3Client].connect(...) methods.
* Introduced [Client|Server]QuicConfiguration so that it can be used in other Connectors such as MemoryConnector.
* Introduced oej.server.MemoryConnector and EndPoint.Pipe for memory communication between peers, along with a MemoryTransportProtocol.
* Introduced QuicTransportProtocol as a wrapper for other TransportProtocols, so that QUIC can now also be transported over memory.
Signed-off-by: Simone Bordet
---
.../client/http/client-http-transport.adoc | 6 +-
.../server/http/server-http-connector.adoc | 14 +-
.../client/ClientConnectorDocs.java | 3 +-
.../client/http/HTTPClientDocs.java | 27 +-
.../client/http3/HTTP3ClientDocs.java | 26 +-
.../client/websocket/WebSocketClientDocs.java | 6 +-
.../server/http/HTTPServerDocs.java | 22 +-
.../server/http3/HTTP3ServerDocs.java | 16 +-
.../AbstractConnectorHttpClientTransport.java | 3 +-
.../org/eclipse/jetty/client/HttpClient.java | 79 ++-
.../org/eclipse/jetty/client/HttpProxy.java | 14 +-
.../java/org/eclipse/jetty/client/Origin.java | 40 +-
.../org/eclipse/jetty/client/Request.java | 18 +
.../HttpClientConnectionFactory.java | 7 +
.../transport/HttpClientTransportDynamic.java | 182 ++++--
.../HttpClientTransportOverHTTP.java | 4 +-
.../client/transport/HttpDestination.java | 24 +-
.../jetty/client/transport/HttpRequest.java | 15 +
.../jetty/client/ConnectionPoolTest.java | 2 +-
.../client/DuplexHttpDestinationTest.java | 12 +-
.../client/HttpClientAsyncContentTest.java | 2 +-
.../client/HttpClientIdleTimeoutTest.java | 2 +-
.../client/http/HttpReceiverOverHTTPTest.java | 2 +-
.../client/http/HttpSenderOverHTTPTest.java | 14 +-
.../HttpClientTransportOverFCGI.java | 4 +-
.../jetty/fcgi/proxy/FastCGIProxyHandler.java | 12 +-
.../ClientConnectionFactoryOverHTTP2.java | 21 +-
.../HttpClientTransportOverHTTP2.java | 56 +-
.../jetty/http2/client/HTTP2Client.java | 30 +-
.../server/HTTP2CServerConnectionFactory.java | 5 +
.../server/HTTP2ServerConnectionFactory.java | 5 +
.../jetty/http2/tests/HTTP2ServerTest.java | 2 +-
.../ClientConnectionFactoryOverHTTP3.java | 28 +-
.../HttpClientTransportOverHTTP3.java | 32 +-
.../jetty/http3/client/HTTP3Client.java | 68 +--
.../AbstractHTTP3ServerConnectionFactory.java | 38 +-
.../server/HTTP3ServerConnectionFactory.java | 35 +-
.../http3/server/HTTP3ServerConnector.java | 22 +-
.../RawHTTP3ServerConnectionFactory.java | 9 +-
.../http3/tests/AbstractClientServerTest.java | 52 +-
.../jetty/http3/tests/ExternalServerTest.java | 10 +-
.../eclipse/jetty/http3/tests/GoAwayTest.java | 2 +-
.../http3/tests/HTTP3ServerConnectorTest.java | 21 +-
.../http3/tests/HandlerClientServerTest.java | 6 +-
.../HttpClientTransportOverHTTP3Test.java | 4 +
.../jetty/http3/tests/IdleTimeoutTest.java | 26 +-
.../jetty/io/ClientConnectionFactory.java | 29 +-
.../org/eclipse/jetty/io/ClientConnector.java | 120 +++-
.../jetty/io/DatagramChannelEndPoint.java | 33 +-
.../java/org/eclipse/jetty/io/EndPoint.java | 69 +++
.../eclipse/jetty/io/MemoryEndPointPipe.java | 344 +++++++++++
.../eclipse/jetty/io/TransportProtocol.java | 402 +++++++++++++
.../quic/client/ClientQuicConfiguration.java | 106 ++++
.../quic/client/ClientQuicConnection.java | 80 ++-
.../QuicClientConnectorConfigurator.java | 99 +---
.../quic/client/QuicTransportProtocol.java | 81 +++
.../jetty/quic/client/End2EndClientTest.java | 6 +-
.../End2EndClientWithClientCertAuthTest.java | 4 +-
.../jetty/quic/common/QuicConfiguration.java | 55 +-
.../jetty/quic/common/QuicConnection.java | 16 +-
.../jetty/quic/common/QuicSession.java | 2 +-
.../server/QuicServerConnectionFactory.java | 74 +++
.../quic/server/QuicServerConnector.java | 162 ++---
.../quic/server/ServerQuicConfiguration.java | 111 ++++
.../quic/server/ServerQuicConnection.java | 74 ++-
.../server/AbstractConnectionFactory.java | 10 +-
.../eclipse/jetty/server/MemoryConnector.java | 203 +++++++
.../jetty/server/MemoryTransportProtocol.java | 89 +++
.../jetty/server/SslConnectionFactory.java | 2 +-
.../test/client/transport/AbstractTest.java | 137 ++---
.../client/transport/EventsHandlerTest.java | 1 -
.../transport/HTTP1TransportProtocolTest.java | 189 ++++++
.../transport/HTTP2TransportProtocolTest.java | 359 +++++++++++
.../transport/HTTP3TransportProtocolTest.java | 248 ++++++++
.../HTTPDynamicTransportProtocolTest.java | 561 ++++++++++++++++++
.../transport/HttpChannelAssociationTest.java | 34 +-
.../transport/HttpClientStreamTest.java | 6 -
.../test/client/transport/HttpClientTest.java | 5 +-
.../transport/HttpClientTimeoutTest.java | 32 +-
.../RoundRobinConnectionPoolTest.java | 8 +-
.../server/UnixDomainServerConnector.java | 12 +
.../unixdomain/server/UnixDomainTest.java | 84 +--
.../util/component/ContainerLifeCycle.java | 43 +-
.../test/client/transport/AbstractTest.java | 51 +-
.../client/transport/AsyncIOServletTest.java | 4 +-
.../transport/HttpClientContinueTest.java | 2 +-
.../test/client/transport/AbstractTest.java | 53 +-
.../client/transport/AsyncIOServletTest.java | 4 +-
.../tests/distribution/DistributionTests.java | 16 +-
89 files changed, 4056 insertions(+), 992 deletions(-)
create mode 100644 jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/MemoryEndPointPipe.java
create mode 100644 jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/TransportProtocol.java
create mode 100644 jetty-core/jetty-quic/jetty-quic-client/src/main/java/org/eclipse/jetty/quic/client/ClientQuicConfiguration.java
create mode 100644 jetty-core/jetty-quic/jetty-quic-client/src/main/java/org/eclipse/jetty/quic/client/QuicTransportProtocol.java
create mode 100644 jetty-core/jetty-quic/jetty-quic-server/src/main/java/org/eclipse/jetty/quic/server/QuicServerConnectionFactory.java
create mode 100644 jetty-core/jetty-quic/jetty-quic-server/src/main/java/org/eclipse/jetty/quic/server/ServerQuicConfiguration.java
create mode 100644 jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/MemoryConnector.java
create mode 100644 jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/MemoryTransportProtocol.java
create mode 100644 jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HTTP1TransportProtocolTest.java
create mode 100644 jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HTTP2TransportProtocolTest.java
create mode 100644 jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HTTP3TransportProtocolTest.java
create mode 100644 jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HTTPDynamicTransportProtocolTest.java
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)
From 8f2d28f1025225310c85d417a66c31af584e8c39 Mon Sep 17 00:00:00 2001
From: Simone Bordet
Date: Fri, 2 Feb 2024 19:36:09 +0100
Subject: [PATCH 02/13] * Improved javadocs and documentation. * Removed usage
of ClientConnector.forUnixDomain() from FastCGIProxyServlet (ee10 and ee9). *
Replaced usage of HTTP3ServerConnector with QuicServerConnector in
jetty-http3.xml.
Signed-off-by: Simone Bordet
---
.../server/http/server-http-connector.adoc | 59 +++++++++++--------
.../server/http/HTTPServerDocs.java | 29 +++++++++
.../org/eclipse/jetty/client/HttpClient.java | 13 ++--
.../java/org/eclipse/jetty/client/Origin.java | 25 ++++----
.../transport/HttpClientTransportDynamic.java | 6 +-
.../jetty/http2/client/HTTP2Client.java | 34 +++++------
.../jetty/http3/client/HTTP3Client.java | 41 +++++++------
.../src/main/config/etc/jetty-http3.xml | 9 ++-
...Test.java => QuicServerConnectorTest.java} | 8 ++-
.../transport/HTTP1TransportProtocolTest.java | 8 ++-
.../transport/HTTP2TransportProtocolTest.java | 10 +++-
.../unixdomain/server/UnixDomainTest.java | 6 +-
.../ee10/fcgi/proxy/FastCGIProxyServlet.java | 31 +++++-----
.../ee9/fcgi/proxy/FastCGIProxyServlet.java | 31 +++++-----
14 files changed, 186 insertions(+), 124 deletions(-)
rename jetty-core/jetty-quic/jetty-quic-server/src/test/java/org/eclipse/jetty/quic/server/{ServerQuicConnectorTest.java => QuicServerConnectorTest.java} (92%)
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 e74a42b3fb59..8bb9f7c8e0ca 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
@@ -25,35 +25,43 @@ The available implementations are:
`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
+`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 TCP port to listen to, the IP address to bind to, etc.:
-[source,java,indent=0]
+[source,java,indent=0,options=nowrap]
----
include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/http/HTTPServerDocs.java[tags=configureConnector]
----
`UnixDomainServerConnector` also wraps a `ServerSocketChannel` and can be configured with the Unix-Domain path to listen to:
-[source,java,indent=0]
+[source,java,indent=0,options=nowrap]
----
include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/http/HTTPServerDocs.java[tags=configureConnectorUnix]
----
[IMPORTANT]
====
-You can use Unix-Domain sockets support only when you run your server with Java 16 or later.
+You can use Unix-Domain sockets only when you run your server with Java 16 or later.
====
-`QuicServerConnector` wraps a `DatagramChannel` and can be configured in a similar way:
+`QuicServerConnector` wraps a `DatagramChannel` and can be configured in a similar way, as shown in the example below.
+Since the communication via UDP does not require to "accept" connections like TCP does, the number of xref:pg-server-http-connector-acceptors[acceptors] is set to `0` and there is no API to configure their number.
-[source,java,indent=0]
+[source,java,indent=0,options=nowrap]
----
include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/http/HTTPServerDocs.java[tags=configureConnectorQuic]
----
-// TODO: add a section on MemoryConnector.
+`MemoryConnector` uses in-process memory, not sockets, for the communication between client and server, that therefore must be in the same process.
+
+Typical usage of `MemoryConnector` is the following:
+
+[source,java,indent=0,options=nowrap]
+----
+include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/http/HTTPServerDocs.java[tags=memoryConnector]
+----
[[pg-server-http-connector-acceptors]]
===== Acceptors
@@ -65,13 +73,13 @@ When a TCP connection is accepted, `ServerConnector` wraps the accepted `SocketC
Therefore, there is a little moment where the acceptor thread is not accepting new connections because it is busy wrapping the just accepted connection to pass it to the `SelectorManager`.
Connections that are ready to be accepted but are not accepted yet are queued in a bounded queue (at the OS level) whose capacity can be configured with the `acceptQueueSize` parameter.
-If your application must withstand a very high rate of connections opened, configuring more than one acceptor thread may be beneficial: when one acceptor thread accepts one connection, another acceptor thread can take over accepting connections.
+If your application must withstand a very high rate of connection opening, configuring more than one acceptor thread may be beneficial: when one acceptor thread accepts one connection, another acceptor thread can take over accepting connections.
[[pg-server-http-connector-selectors]]
===== Selectors
The _selectors_ are components that manage a set of accepted TCP sockets, implemented by xref:pg-arch-io-selector-manager[`ManagedSelector`].
-For QUIC or HTTP/3, there are no accepted TCP sockets, but only one `DatagramChannel` and therefore there is only 1 selector.
+For QUIC or HTTP/3, there are no accepted TCP sockets, but only one `DatagramChannel` and therefore there is only one selector.
Each selector requires one thread and uses the Java NIO mechanism to efficiently handle a set of registered channels.
@@ -87,25 +95,26 @@ In this case a single selector may be able to manage less TCP sockets because ch
It is possible to configure more than one `Connector` per `Server`.
Typical cases are a `ServerConnector` for clear-text HTTP, and another `ServerConnector` for secure HTTP.
-Another case could be a publicly exposed `ServerConnector` for secure HTTP, and an internally exposed `UnixDomainServerConnector` for clear-text HTTP.
-Yet another example could be a `ServerConnector` for clear-text HTTP, a `ServerConnector` for secure HTTP/2, and an `HTTP3ServerConnector` for QUIC+HTTP/3.
+Another case could be a publicly exposed `ServerConnector` for secure HTTP, and an internally exposed `UnixDomainServerConnector` or `MemoryConnector` for clear-text HTTP.
+Yet another example could be a `ServerConnector` for clear-text HTTP, a `ServerConnector` for secure HTTP/2, and an `QuicServerConnector` for QUIC+HTTP/3.
For example:
-[source,java,indent=0]
+[source,java,indent=0,options=nowrap]
----
include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/http/HTTPServerDocs.java[tags=configureConnectors]
----
If you do not specify the port the connector listens to explicitly, the OS will allocate one randomly when the connector starts.
-You may need to use the randomly allocated port to configure other components, for example the port to use for secure redirects (when redirecting from a URI with the `http` scheme to the `https` scheme).
-Another example is to bind both the HTTP/2 connector and the HTTP/3 connector to the same port.
-This is possible since the HTTP/2 connector uses TCP, while the HTTP/3 connector uses UDP.
+You may need to use the randomly allocated port to configure other components.
+One example is to use the randomly allocated port to configure secure redirects (when redirecting from a URI with the `http` scheme to the `https` scheme).
+Another example is to bind both the HTTP/2 connector and the HTTP/3 connector to the same randomly allocated port.
+It is possible that the HTTP/2 connector and the HTTP/3 connector share the same port, because one uses TCP, while the other uses UDP.
For example:
-[source,java,indent=0]
+[source,java,indent=0,options=nowrap]
----
include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/http/HTTPServerDocs.java[tags=sameRandomPort]
----
@@ -125,7 +134,7 @@ If no `ConnectionFactory` is specified then `HttpConnectionFactory` is implicitl
This is how you configure Jetty to support clear-text HTTP/1.1:
-[source,java,indent=0]
+[source,java,indent=0,options=nowrap]
----
include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/http/HTTPServerDocs.java[tags=http11]
----
@@ -135,7 +144,7 @@ include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/http/HTTPSer
Supporting encrypted HTTP/1.1 (that is, requests with the `https` scheme) is supported by configuring an `SslContextFactory` that has access to the KeyStore containing the private server key and public server certificate, in this way:
-[source,java,indent=0]
+[source,java,indent=0,options=nowrap]
----
include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/http/HTTPServerDocs.java[tags=tlsHttp11]
----
@@ -154,7 +163,7 @@ Therefore, with HTTP/2, clients that connect to port `80` (or to a specific Unix
Jetty can support both HTTP/1.1 and HTTP/2 on the same clear-text port by configuring both the HTTP/1.1 and the HTTP/2 ``ConnectionFactory``s:
-[source,java,indent=0]
+[source,java,indent=0,options=nowrap]
----
include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/http/HTTPServerDocs.java[tags=http11H2C]
----
@@ -172,7 +181,7 @@ When using encrypted HTTP/2, the unencrypted protocol is negotiated by client an
Jetty supports ALPN and encrypted HTTP/2 with this configuration:
-[source,java,indent=0]
+[source,java,indent=0,options=nowrap]
----
include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/http/HTTPServerDocs.java[tags=tlsALPNHTTP]
----
@@ -235,7 +244,7 @@ The code necessary to configure HTTP/2 is described in xref:pg-server-http-conne
To setup HTTP/3, for example on port `843`, you need the following code (some of which could be shared with other connectors such as HTTP/2's):
-[source,java,indent=0]
+[source,java,indent=0,options=nowrap]
----
include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/http/HTTPServerDocs.java[tags=h3]
----
@@ -247,7 +256,7 @@ The use of the Quiche native library requires the private key and public certifi
It is therefore mandatory to configure the PEM directory as shown above.
The PEM directory must also be adequately protected using file system permissions, because it stores the private key PEM file.
-You want to grant as few permissions as possible, typically only the equivalent of POSIX `rwx` to the user that runs the Jetty process. Using `/tmp` or any other directory accessible by any user is not a secure choice.
+You want to grant as few permissions as possible, typically the equivalent of POSIX `rwx` only to the user that runs the Jetty process. Using `/tmp` or any other directory accessible by any user is not a secure choice.
====
[[pg-server-http-connector-protocol-tls-conscrypt]]
@@ -267,7 +276,7 @@ To use Conscrypt as TLS provider, you must have the Conscrypt jar and the Jetty
Then, you must configure the JDK with the Conscrypt provider, and configure Jetty to use the Conscrypt provider, in this way:
-[source,java,indent=0]
+[source,java,indent=0,options=nowrap]
----
include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/http/HTTPServerDocs.java[tags=conscrypt]
----
@@ -283,7 +292,7 @@ NOTE: The PROXY protocol is widely supported by load balancers such as link:http
To support this case, Jetty can be configured in this way:
-[source,java,indent=0]
+[source,java,indent=0,options=nowrap]
----
include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/http/HTTPServerDocs.java[tags=proxyHTTP]
----
@@ -297,7 +306,7 @@ Therefore the `ProxyConnection` will handle the PROXY protocol bytes and `HttpCo
The load balancer may be configured to communicate with Jetty backend servers via Unix-Domain sockets (requires Java 16 or later).
For example:
-[source,java,indent=0]
+[source,java,indent=0,options=nowrap]
----
include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/http/HTTPServerDocs.java[tags=proxyHTTPUnix]
----
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 e1a8e0b621a8..bb8aab1009ff 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
@@ -29,6 +29,8 @@
import jakarta.servlet.http.HttpServletResponse;
import org.conscrypt.OpenSSLProvider;
import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory;
+import org.eclipse.jetty.client.ContentResponse;
+import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.ee10.servlet.DefaultServlet;
import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
import org.eclipse.jetty.ee10.servlet.ServletHolder;
@@ -60,6 +62,8 @@
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.NetworkConnector;
import org.eclipse.jetty.server.ProxyConnectionFactory;
import org.eclipse.jetty.server.Request;
@@ -239,6 +243,31 @@ public void configureConnectorQuic() throws Exception
// end::configureConnectorQuic[]
}
+ public void memoryConnector() throws Exception
+ {
+ // tag::memoryConnector[]
+ Server server = new Server();
+
+ // Create a MemoryConnector instance that speaks HTTP/1.1.
+ MemoryConnector connector = new MemoryConnector(server, new HttpConnectionFactory());
+
+ server.addConnector(connector);
+ server.start();
+
+ // The code above is the server-side.
+ // ----
+ // The code below is the client-side.
+
+ HttpClient httpClient = new HttpClient();
+ httpClient.start();
+
+ ContentResponse response = httpClient.newRequest("http://localhost/")
+ // Use the memory TransportProtocol to communicate with the server-side.
+ .transportProtocol(new MemoryTransportProtocol(connector))
+ .send();
+ // end::memoryConnector[]
+ }
+
public void configureConnectors() throws Exception
{
// tag::configureConnectors[]
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 c7ede005975b..4785046748d7 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
@@ -81,7 +81,7 @@
* for cases where this is needed.
* HttpClient also acts as a central configuration point for cookies, via {@link #getHttpCookieStore()}.
* Typical usage:
- *
+ * {@code
* HttpClient httpClient = new HttpClient();
* httpClient.start();
*
@@ -95,15 +95,12 @@
* int status = response.status();
*
* // Asynchronously
- * httpClient.newRequest("http://localhost:8080").send(new Response.CompleteListener()
+ * httpClient.newRequest("http://localhost:8080").send(result ->
* {
- * @Override
- * public void onComplete(Result result)
- * {
- * ...
- * }
+ * Response response = result.getResponse();
+ * ...
* });
- *
+ * }
*/
@ManagedObject("The HTTP client")
public class HttpClient extends ContainerLifeCycle
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 7d48dd5c6997..e5b7b4712e34 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
@@ -29,17 +29,22 @@
/**
* Class that groups the elements that uniquely identify a destination.
* The elements are {@code scheme}, {@code host}, {@code port}, a
- * {@link Origin.Protocol} and a tag object that further distinguishes
- * destinations that have the same origin and protocol.
- * In general it is possible that, for the same origin, the server can
- * speak different protocols (for example, clear-text HTTP/1.1 and clear-text
- * HTTP/2), so the {@link Origin.Protocol} makes that distinction.
+ * {@link Origin.Protocol}, a tag object that further distinguishes
+ * destinations that have the same scheme, host, port and protocol,
+ * and a {@link TransportProtocol}.
+ * In general it is possible that, for the same scheme, host and port,
+ * the server can speak different protocols (for example, clear-text HTTP/1.1
+ * and clear-text HTTP/2), so the {@link Origin.Protocol} makes that distinction.
* Furthermore, it may be desirable to have different destinations for
- * the same origin and protocol (for example, when using the PROXY protocol
- * in a reverse proxy server, you want to be able to map the client ip:port
- * to the destination {@code tag}, so that all the connections to the server
- * associated to that destination can specify the PROXY protocol bytes for
- * that particular client connection.
+ * the same scheme, host, port and protocol.
+ * For example, when using the PROXY protocol in a reverse proxy server, you
+ * may want to be able to map the client ip:port to the destination {@code tag},
+ * so that all the connections to the server associated to that destination can
+ * specify the PROXY protocol bytes for that particular client connection.
+ * Finally, it is necessary to have different destinations for the same
+ * scheme, host, port, and protocol, but having different {@link TransportProtocol},
+ * for example when the same server may be reached via TCP/IP but also via
+ * Unix-Domain sockets.
*/
public class Origin
{
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 14f457d34c95..dcf30eb7a4c2 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
@@ -37,6 +37,7 @@
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.TransportProtocol;
+import org.eclipse.jetty.util.annotation.ManagedObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -46,7 +47,7 @@
* it supports, in order of preference. The typical case is when the server supports both HTTP/1.1 and
* HTTP/2, but the client does not know that. In this case, the application will create a
* HttpClientTransportDynamic in this way:
- *
+ * {@code
* ClientConnector clientConnector = new ClientConnector();
* // Configure the clientConnector.
*
@@ -60,7 +61,7 @@
*
* // Create the HttpClient.
* client = new HttpClient(transport);
- *
+ * }
* Note how in the code above the HttpClientTransportDynamic has been created with the application
* protocols {@code h2} and {@code h1}, without the need to specify TLS (which is implied by the request
* scheme) or ALPN (which is implied by HTTP/2 over TLS).
@@ -75,6 +76,7 @@
* version, or request headers, or request attributes, or even request path) by overriding
* {@link HttpClientTransport#newOrigin(Request)}.
*/
+@ManagedObject("The HTTP client transport that supports many HTTP versions")
public class HttpClientTransportDynamic extends AbstractConnectorHttpClientTransport
{
private static final Logger LOG = LoggerFactory.getLogger(HttpClientTransportDynamic.class);
diff --git a/jetty-core/jetty-http2/jetty-http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2Client.java b/jetty-core/jetty-http2/jetty-http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2Client.java
index bde0a54ef292..baf5c0de356d 100644
--- a/jetty-core/jetty-http2/jetty-http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2Client.java
+++ b/jetty-core/jetty-http2/jetty-http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2Client.java
@@ -45,40 +45,39 @@
* HTTP2Client provides an asynchronous, non-blocking implementation
* to send HTTP/2 frames to a server.
* Typical usage:
- *
+ * {@code
* // Create and start HTTP2Client.
- * HTTP2Client client = new HTTP2Client();
- * client.start();
- * SslContextFactory sslContextFactory = client.getClientConnector().getSslContextFactory();
+ * HTTP2Client http2Client = new HTTP2Client();
+ * http2Client.start();
+ * SslContextFactory sslContextFactory = http2Client.getClientConnector().getSslContextFactory();
*
* // Connect to host.
* String host = "webtide.com";
* int port = 443;
*
- * FuturePromise<Session> sessionPromise = new FuturePromise<>();
- * client.connect(sslContextFactory, new InetSocketAddress(host, port), new ServerSessionListener() {}, sessionPromise);
+ * CompletableFuture sessionPromise = http2Client.connect(sslContextFactory, new InetSocketAddress(host, port), new ServerSessionListener() {});
*
- * // Obtain the client Session object.
+ * // Obtain the client-side Session object.
* Session session = sessionPromise.get(5, TimeUnit.SECONDS);
*
* // Prepare the HTTP request headers.
- * HttpFields requestFields = HttpFields.build();
- * requestFields.put("User-Agent", client.getClass().getName() + "/" + Jetty.VERSION);
+ * HttpFields.Mutable requestFields = HttpFields.build();
+ * requestFields.put("User-Agent", http2Client.getClass().getName() + "/" + Jetty.VERSION);
* // Prepare the HTTP request object.
* MetaData.Request request = new MetaData.Request("PUT", HttpURI.from("https://" + host + ":" + port + "/"), HttpVersion.HTTP_2, requestFields);
* // Create the HTTP/2 HEADERS frame representing the HTTP request.
* HeadersFrame headersFrame = new HeadersFrame(request, null, false);
*
* // Prepare the listener to receive the HTTP response frames.
- * Stream.Listener responseListener = new new Stream.Listener()
+ * Stream.Listener responseListener = new Stream.Listener()
* {
- * @Override
+ * @Override
* public void onHeaders(Stream stream, HeadersFrame frame)
* {
* System.err.println(frame);
* }
*
- * @Override
+ * @Override
* public void onData(Stream stream, DataFrame frame, Callback callback)
* {
* System.err.println(frame);
@@ -87,18 +86,17 @@
* };
*
* // Send the HEADERS frame to create a stream.
- * FuturePromise<Stream> streamPromise = new FuturePromise<>();
- * session.newStream(headersFrame, streamPromise, responseListener);
+ * CompletableFuture streamPromise = session.newStream(headersFrame, responseListener);
* Stream stream = streamPromise.get(5, TimeUnit.SECONDS);
*
* // Use the Stream object to send request content, if any, using a DATA frame.
- * ByteBuffer content = ...;
+ * ByteBuffer content = UTF_8.encode("hello");
* DataFrame requestContent = new DataFrame(stream.getId(), content, true);
* stream.data(requestContent, Callback.NOOP);
*
- * // When done, stop the client.
- * client.stop();
- *
+ * // When done, stop the HTTP2Client.
+ * http2Client.stop();
+ *}
*/
@ManagedObject
public class HTTP2Client extends ContainerLifeCycle
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 76ed05eb81d1..eda8e93f9d0c 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
@@ -38,31 +38,30 @@
* HTTP3Client provides an asynchronous, non-blocking implementation to send
* HTTP/3 frames to a server.
* Typical usage:
- *
- * // HTTP3Client setup.
+ * {@code
+ * // Client-side QUIC configuration to configure QUIC properties.
+ * ClientQuicConfiguration quicConfig = new ClientQuicConfiguration(sslClient, null);
*
- * HTTP3Client client = new HTTP3Client();
- *
- * // To configure QUIC properties.
- * QuicConfiguration quicConfig = client.getQuicConfiguration();
+ * // Create the HTTP3Client instance.
+ * HTTP3Client http3Client = new HTTP3Client(quicConfig);
*
* // To configure HTTP/3 properties.
- * HTTP3Configuration h3Config = client.getHTTP3Configuration();
+ * HTTP3Configuration h3Config = http3Client.getHTTP3Configuration();
*
- * client.start();
+ * http3Client.start();
*
* // HTTP3Client request/response usage.
*
* // Connect to host.
* String host = "webtide.com";
* int port = 443;
- * Session.Client session = client
+ * Session.Client session = http3Client
* .connect(new InetSocketAddress(host, port), new Session.Client.Listener() {})
* .get(5, TimeUnit.SECONDS);
*
* // Prepare the HTTP request headers.
- * HttpFields requestFields = new HttpFields();
- * requestFields.put("User-Agent", client.getClass().getName() + "/" + Jetty.VERSION);
+ * HttpFields.Mutable requestFields = HttpFields.build();
+ * requestFields.put("User-Agent", http3Client.getClass().getName() + "/" + Jetty.VERSION);
*
* // Prepare the HTTP request object.
* MetaData.Request request = new MetaData.Request("PUT", HttpURI.from("https://" + host + ":" + port + "/"), HttpVersion.HTTP_3, requestFields);
@@ -73,7 +72,7 @@
* // Send the HEADERS frame to create a request stream.
* Stream stream = session.newRequest(headersFrame, new Stream.Listener()
* {
- * @Override
+ * @Override
* public void onResponse(Stream stream, HeadersFrame frame)
* {
* // Inspect the response status and headers.
@@ -83,7 +82,7 @@
* stream.demand();
* }
*
- * @Override
+ * @Override
* public void onDataAvailable(Stream stream)
* {
* Stream.Data data = stream.readData();
@@ -97,15 +96,15 @@
* }).get(5, TimeUnit.SECONDS);
*
* // Use the Stream object to send request content, if any, using a DATA frame.
- * ByteBuffer requestChunk1 = ...;
+ * ByteBuffer requestChunk1 = UTF_8.encode("hello");
* stream.data(new DataFrame(requestChunk1, false))
* // Subsequent sends must wait for previous sends to complete.
- * .thenCompose(s ->
+ * .thenCompose(s ->
* {
- * ByteBuffer requestChunk2 = ...;
- * s.data(new DataFrame(requestChunk2, true)));
- * }
- *
+ * ByteBuffer requestChunk2 = UTF_8.encode("world");
+ * s.data(new DataFrame(requestChunk2, true));
+ * });
+ * }
*
* IMPLEMENTATION NOTES.
* Each call to {@link #connect(SocketAddress, Session.Client.Listener)} creates a new
@@ -114,14 +113,14 @@
* corresponding {@link ClientHTTP3Session}.
* Each {@link ClientHTTP3Session} manages the mandatory encoder, decoder and control
* streams, plus zero or more request/response streams.
- *
+ * {@code
* GENERIC, TCP-LIKE, SETUP FOR HTTP/1.1 AND HTTP/2
* HTTP3Client - dgramEP - ClientQuiConnection - ClientQuicSession - ClientProtocolSession - TCPLikeStream
*
* SPECIFIC SETUP FOR HTTP/3
* /- [Control|Decoder|Encoder]Stream
* HTTP3Client - dgramEP - ClientQuiConnection - ClientQuicSession - ClientHTTP3Session -* HTTP3Streams
- *
+ * }
*
* HTTP/3+QUIC support is experimental and not suited for production use.
* APIs may change incompatibly between releases.
diff --git a/jetty-core/jetty-http3/jetty-http3-server/src/main/config/etc/jetty-http3.xml b/jetty-core/jetty-http3/jetty-http3-server/src/main/config/etc/jetty-http3.xml
index 7dd5b5f3ccb6..9d6b420433e8 100644
--- a/jetty-core/jetty-http3/jetty-http3-server/src/main/config/etc/jetty-http3.xml
+++ b/jetty-core/jetty-http3/jetty-http3-server/src/main/config/etc/jetty-http3.xml
@@ -4,9 +4,14 @@
-
+
-
+
+
+
+
+
+
-
diff --git a/jetty-core/jetty-quic/jetty-quic-server/src/test/java/org/eclipse/jetty/quic/server/ServerQuicConnectorTest.java b/jetty-core/jetty-quic/jetty-quic-server/src/test/java/org/eclipse/jetty/quic/server/QuicServerConnectorTest.java
similarity index 92%
rename from jetty-core/jetty-quic/jetty-quic-server/src/test/java/org/eclipse/jetty/quic/server/ServerQuicConnectorTest.java
rename to jetty-core/jetty-quic/jetty-quic-server/src/test/java/org/eclipse/jetty/quic/server/QuicServerConnectorTest.java
index d625c18c1c61..7e9f776642a1 100644
--- a/jetty-core/jetty-quic/jetty-quic-server/src/test/java/org/eclipse/jetty/quic/server/ServerQuicConnectorTest.java
+++ b/jetty-core/jetty-quic/jetty-quic-server/src/test/java/org/eclipse/jetty/quic/server/QuicServerConnectorTest.java
@@ -27,7 +27,7 @@
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
-public class ServerQuicConnectorTest
+public class QuicServerConnectorTest
{
@Disabled
@Test
@@ -43,7 +43,8 @@ public void testSmall() throws Exception
config.setHttpCompliance(HttpCompliance.LEGACY); // enable HTTP/0.9
HttpConnectionFactory connectionFactory = new HttpConnectionFactory(config);
- QuicServerConnector connector = new QuicServerConnector(server, sslContextFactory, connectionFactory);
+ ServerQuicConfiguration quicConfig = new ServerQuicConfiguration(sslContextFactory, null);
+ QuicServerConnector connector = new QuicServerConnector(server, quicConfig, connectionFactory);
connector.setPort(8443);
server.addConnector(connector);
@@ -85,7 +86,8 @@ public void testBig() throws Exception
config.setHttpCompliance(HttpCompliance.LEGACY); // enable HTTP/0.9
HttpConnectionFactory connectionFactory = new HttpConnectionFactory(config);
- QuicServerConnector connector = new QuicServerConnector(server, sslContextFactory, connectionFactory);
+ ServerQuicConfiguration quicConfig = new ServerQuicConfiguration(sslContextFactory, null);
+ QuicServerConnector connector = new QuicServerConnector(server, quicConfig, connectionFactory);
connector.setPort(8443);
server.addConnector(connector);
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
index 911be28a5afe..185dc7f49869 100644
--- 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
@@ -143,7 +143,7 @@ public void testMemoryTransportProtocol() throws Exception
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"));
+ connector.setUnixDomainPath(newUnixDomainPath());
server.addConnector(connector);
server.setHandler(new EmptyServerHandler());
server.start();
@@ -186,4 +186,10 @@ public void testQUICTransportProtocol(WorkDir workDir) throws Exception
assertThat(response.getStatus(), is(HttpStatus.OK_200));
}
+
+ 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/HTTP2TransportProtocolTest.java b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HTTP2TransportProtocolTest.java
index 36d4c36500e0..69ec2c3a8018 100644
--- 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
@@ -158,7 +158,7 @@ public void testMemoryTransportProtocol() throws Exception
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"));
+ connector.setUnixDomainPath(newUnixDomainPath());
server.addConnector(connector);
server.setHandler(new EmptyServerHandler());
server.start();
@@ -292,7 +292,7 @@ public void onHeaders(Stream stream, HeadersFrame frame)
public void testLowLevelH2COverUnixDomain() throws Exception
{
UnixDomainServerConnector connector = new UnixDomainServerConnector(server, new HTTP2CServerConnectionFactory());
- connector.setUnixDomainPath(Path.of(System.getProperty("java.io.tmpdir"), "jetty.sock"));
+ connector.setUnixDomainPath(newUnixDomainPath());
server.addConnector(connector);
server.setHandler(new EmptyServerHandler());
server.start();
@@ -356,4 +356,10 @@ public void onHeaders(Stream stream, HeadersFrame frame)
assertTrue(responseLatch.await(5, TimeUnit.SECONDS));
}
+
+ 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-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 ee172437e6f2..24d230bb9405 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
@@ -28,6 +28,7 @@
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;
@@ -115,12 +116,13 @@ public boolean handle(Request request, Response response, Callback callback)
}
});
- HttpClient httpClient = new HttpClient(new HttpClientTransportDynamic());
+ // Use the deprecated APIs for backwards compatibility testing.
+ ClientConnector clientConnector = ClientConnector.forUnixDomain(unixDomainPath);
+ HttpClient httpClient = new HttpClient(new HttpClientTransportDynamic(clientConnector));
httpClient.start();
try
{
ContentResponse response = httpClient.newRequest(uri)
- .transportProtocol(new TransportProtocol.TCPUnix(unixDomainPath))
.timeout(5, TimeUnit.SECONDS)
.send();
diff --git a/jetty-ee10/jetty-ee10-fcgi-proxy/src/main/java/org/eclipse/jetty/ee10/fcgi/proxy/FastCGIProxyServlet.java b/jetty-ee10/jetty-ee10-fcgi-proxy/src/main/java/org/eclipse/jetty/ee10/fcgi/proxy/FastCGIProxyServlet.java
index 47cd4e5683a4..b6762bdb5dd6 100644
--- a/jetty-ee10/jetty-ee10-fcgi-proxy/src/main/java/org/eclipse/jetty/ee10/fcgi/proxy/FastCGIProxyServlet.java
+++ b/jetty-ee10/jetty-ee10-fcgi-proxy/src/main/java/org/eclipse/jetty/ee10/fcgi/proxy/FastCGIProxyServlet.java
@@ -39,6 +39,7 @@
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.io.ClientConnector;
+import org.eclipse.jetty.io.TransportProtocol;
import org.eclipse.jetty.util.ProcessorUtils;
/**
@@ -93,6 +94,7 @@ public class FastCGIProxyServlet extends AsyncProxyServlet.Transparent
private String originalQueryAttribute;
private boolean fcgiHTTPS;
private Set fcgiEnvNames;
+ private Path unixDomainPath;
@Override
public void init() throws ServletException
@@ -117,6 +119,10 @@ public void init() throws ServletException
.map(String::trim)
.collect(Collectors.toSet());
}
+
+ String path = getInitParameter("unixDomainPath");
+ if (path != null)
+ unixDomainPath = Path.of(path);
}
@Override
@@ -127,21 +133,12 @@ protected HttpClient newHttpClient()
if (scriptRoot == null)
throw new IllegalArgumentException("Mandatory parameter '" + SCRIPT_ROOT_INIT_PARAM + "' not configured");
- ClientConnector connector;
- String unixDomainPath = config.getInitParameter("unixDomainPath");
- if (unixDomainPath != null)
- {
- connector = ClientConnector.forUnixDomain(Path.of(unixDomainPath));
- }
- else
- {
- int selectors = Math.max(1, ProcessorUtils.availableProcessors() / 2);
- String value = config.getInitParameter("selectors");
- if (value != null)
- selectors = Integer.parseInt(value);
- connector = new ClientConnector();
- connector.setSelectors(selectors);
- }
+ int selectors = Math.max(1, ProcessorUtils.availableProcessors() / 2);
+ String value = config.getInitParameter("selectors");
+ if (value != null)
+ selectors = Integer.parseInt(value);
+ ClientConnector connector = new ClientConnector();
+ connector.setSelectors(selectors);
return new HttpClient(new ProxyHttpClientTransportOverFCGI(connector, scriptRoot));
}
@@ -218,6 +215,10 @@ protected void sendProxyRequest(HttpServletRequest request, HttpServletResponse
proxyRequest.headers(headers -> headers.put(HttpHeader.COOKIE, builder.toString()));
}
+ Path unixDomain = unixDomainPath;
+ if (unixDomain != null)
+ proxyRequest.transportProtocol(new TransportProtocol.TCPUnix(unixDomain));
+
super.sendProxyRequest(request, proxyResponse, proxyRequest);
}
diff --git a/jetty-ee9/jetty-ee9-fcgi-proxy/src/main/java/org/eclipse/jetty/ee9/fcgi/proxy/FastCGIProxyServlet.java b/jetty-ee9/jetty-ee9-fcgi-proxy/src/main/java/org/eclipse/jetty/ee9/fcgi/proxy/FastCGIProxyServlet.java
index 8f59797fa666..103e31f8303e 100644
--- a/jetty-ee9/jetty-ee9-fcgi-proxy/src/main/java/org/eclipse/jetty/ee9/fcgi/proxy/FastCGIProxyServlet.java
+++ b/jetty-ee9/jetty-ee9-fcgi-proxy/src/main/java/org/eclipse/jetty/ee9/fcgi/proxy/FastCGIProxyServlet.java
@@ -39,6 +39,7 @@
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.io.ClientConnector;
+import org.eclipse.jetty.io.TransportProtocol;
import org.eclipse.jetty.util.ProcessorUtils;
/**
@@ -93,6 +94,7 @@ public class FastCGIProxyServlet extends AsyncProxyServlet.Transparent
private String originalQueryAttribute;
private boolean fcgiHTTPS;
private Set fcgiEnvNames;
+ private Path unixDomainPath;
@Override
public void init() throws ServletException
@@ -117,6 +119,10 @@ public void init() throws ServletException
.map(String::trim)
.collect(Collectors.toSet());
}
+
+ String path = getInitParameter("unixDomainPath");
+ if (path != null)
+ unixDomainPath = Path.of(path);
}
@Override
@@ -127,21 +133,12 @@ protected HttpClient newHttpClient()
if (scriptRoot == null)
throw new IllegalArgumentException("Mandatory parameter '" + SCRIPT_ROOT_INIT_PARAM + "' not configured");
- ClientConnector connector;
- String unixDomainPath = config.getInitParameter("unixDomainPath");
- if (unixDomainPath != null)
- {
- connector = ClientConnector.forUnixDomain(Path.of(unixDomainPath));
- }
- else
- {
- int selectors = Math.max(1, ProcessorUtils.availableProcessors() / 2);
- String value = config.getInitParameter("selectors");
- if (value != null)
- selectors = Integer.parseInt(value);
- connector = new ClientConnector();
- connector.setSelectors(selectors);
- }
+ int selectors = Math.max(1, ProcessorUtils.availableProcessors() / 2);
+ String value = config.getInitParameter("selectors");
+ if (value != null)
+ selectors = Integer.parseInt(value);
+ ClientConnector connector = new ClientConnector();
+ connector.setSelectors(selectors);
return new HttpClient(new ProxyHttpClientTransportOverFCGI(connector, scriptRoot));
}
@@ -218,6 +215,10 @@ protected void sendProxyRequest(HttpServletRequest request, HttpServletResponse
proxyRequest.headers(headers -> headers.put(HttpHeader.COOKIE, builder.toString()));
}
+ Path unixDomain = unixDomainPath;
+ if (unixDomain != null)
+ proxyRequest.transportProtocol(new TransportProtocol.TCPUnix(unixDomain));
+
super.sendProxyRequest(request, proxyResponse, proxyRequest);
}
From d698f0e6a6a83a14686afaf0f319c29e553c3d09 Mon Sep 17 00:00:00 2001
From: Simone Bordet
Date: Sun, 4 Feb 2024 16:22:19 +0100
Subject: [PATCH 03/13] Fixed invocation to HTTP3ServerConnectionFactory
constructor in jetty-http3.xml.
Signed-off-by: Simone Bordet
---
.../jetty-http3-server/src/main/config/etc/jetty-http3.xml | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/jetty-core/jetty-http3/jetty-http3-server/src/main/config/etc/jetty-http3.xml b/jetty-core/jetty-http3/jetty-http3-server/src/main/config/etc/jetty-http3.xml
index 9d6b420433e8..044b86ad909f 100644
--- a/jetty-core/jetty-http3/jetty-http3-server/src/main/config/etc/jetty-http3.xml
+++ b/jetty-core/jetty-http3/jetty-http3-server/src/main/config/etc/jetty-http3.xml
@@ -7,7 +7,7 @@
-
+
@@ -16,6 +16,7 @@
-
+
From 20371c0fbe934d7ef2ee7cebe1d347353d07cf25 Mon Sep 17 00:00:00 2001
From: Simone Bordet
Date: Sun, 4 Feb 2024 20:22:05 +0100
Subject: [PATCH 04/13] * Added documentation about TransportProtocol. *
Updated client documentation to consider TransportProtocol.
Signed-off-by: Simone Bordet
---
.../client/client-io-arch.adoc | 74 +++++--
.../client/http/client-http-api.adoc | 30 +++
.../http/client-http-configuration.adoc | 6 +-
.../client/http/client-http-intro.adoc | 45 ++--
.../client/http/client-http-transport.adoc | 95 ++++----
.../client/ClientConnectorDocs.java | 35 +--
.../client/http/HTTPClientDocs.java | 205 ++++++++++++++++--
7 files changed, 349 insertions(+), 141 deletions(-)
diff --git a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/client/client-io-arch.adoc b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/client/client-io-arch.adoc
index 504ab9606e6e..2cb4a8a9aa0b 100644
--- a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/client/client-io-arch.adoc
+++ b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/client/client-io-arch.adoc
@@ -14,22 +14,44 @@
[[pg-client-io-arch]]
=== I/O Architecture
-The Jetty client libraries provide the basic components and APIs to implement a network client.
+The Jetty client libraries provide the basic components and APIs to implement a client application.
They build on the common xref:pg-arch-io[Jetty I/O Architecture] and provide client specific concepts (such as establishing a connection to a server).
There are conceptually two layers that compose the Jetty client libraries:
-. xref:pg-client-io-arch-network[The network layer], that handles the low level I/O and deals with buffers, threads, etc.
-. xref:pg-client-io-arch-protocol[The protocol layer], that handles the parsing of bytes read from the network and the generation of bytes to write to the network.
+. xref:pg-client-io-arch-transport[The transport layer], that handles the low-level communication with the server, and deals with buffers, threads, etc.
+. xref:pg-client-io-arch-protocol[The protocol layer], that handles the high-level protocol by parsing the bytes read from the transport layer and by generating the bytes to write to the transport layer.
-[[pg-client-io-arch-network]]
-==== Network Layer
+[[pg-client-io-arch-transport]]
+==== Transport Layer
+
+The transport layer is the low-level layer that communicates with the server.
+
+Protocols such as HTTP/1.1 and HTTP/2 are typically transported over TCP, while the newer HTTP/3 is transported over QUIC, which is itself transported over UDP.
+
+However, there are other means of communication supported by the Jetty client libraries, in particular over xref:pg-client-io-arch-unix-domain[Unix-Domain sockets] (for inter-process communication), and over xref:pg-client-io-arch-memory[memory] (for intra-process communication).
+
+The same high-level protocol can be carried by different low-level transports.
+For example, the high-level HTTP/1.1 protocol can be transported over either TCP (the default), or QUIC, or Unix-Domain sockets, or memory, because all these low-level transport provide reliable and ordered communication between client and server.
+
+Similarly, the high-level HTTP/3 protocol can be transported over either QUIC (the default) or memory.
+It would be possible to transport HTTP/3 also over Unix-Domain sockets, but the current version of Java only supports Unix-Domain sockets for `SocketChannel` and not for `DatagramChannel`.
The Jetty client libraries use the common I/O design described in xref:pg-arch-io[this section].
-The main client-side component is the link:{javadoc-url}/org/eclipse/jetty/io/ClientConnector.html[`ClientConnector`].
-The `ClientConnector` primarily wraps the link:{javadoc-url}/org/eclipse/jetty/io/SelectorManager.html[`SelectorManager`] and aggregates other four components:
+The common I/O components and concepts are used for all low-level transports.
+The only partial exception is the xref:pg-client-io-arch-memory[memory transport], which is not based on network components; as such it does not need a `SelectorManager`, but it exposes `EndPoint` so that high-level protocols have a common interface to interact with the low-level transport.
+
+The client-side abstraction for the low-level transport is `org.eclipse.jetty.io.TransportProtocol`.
+
+`TransportProtocol` represents how high-level protocols can be transported; there is `TransportProtocol.TCP_IP` that represents communication over TCP, but also `TransportProtocol.TCPUnix` for Unix-Domain sockets, `QuicTransportProtocol` for QUIC and `MemoryTransportProtocol` for memory.
+
+Applications can specify the `TransportProtocol` to use for each request as described in xref:pg-client-http-api-protocol[this section].
+
+When the `TransportProtocol` implementation uses the network, it delegates to `org.eclipse.jetty.io.ClientConnector`.
+
+`ClientConnector` primarily wraps `org.eclipse.jetty.io.SelectorManager` to provide network functionalities, and aggregates other four components:
* a thread pool (in form of an `java.util.concurrent.Executor`)
* a scheduler (in form of `org.eclipse.jetty.util.thread.Scheduler`)
@@ -39,6 +61,8 @@ The `ClientConnector` primarily wraps the link:{javadoc-url}/org/eclipse/jetty/i
The `ClientConnector` is where you want to set those components after you have configured them.
If you don't explicitly set those components on the `ClientConnector`, then appropriate defaults will be chosen when the `ClientConnector` starts.
+`ClientConnector` manages all network-related components, and therefore it is used for TCP, UDP, QUIC and xref:pg-client-io-arch-unix-domain[Unix-Domain sockets].
+
The simplest example that creates and starts a `ClientConnector` is the following:
[source,java,indent=0]
@@ -60,7 +84,7 @@ A more advanced example that customizes the `ClientConnector` by overriding some
include::../{doc_code}/org/eclipse/jetty/docs/programming/client/ClientConnectorDocs.java[tags=advanced]
----
-Since `ClientConnector` is the component that handles the low-level network, it is also the component where you want to configure the low-level network configuration.
+Since `ClientConnector` is the component that handles the low-level network transport, it is also the component where you want to configure the low-level network configuration.
The most common parameters are:
@@ -81,32 +105,38 @@ Please refer to the `ClientConnector` link:{javadoc-url}/org/eclipse/jetty/io/Cl
[[pg-client-io-arch-unix-domain]]
===== Unix-Domain Support
-link:https://openjdk.java.net/jeps/380[JEP 380] introduced Unix-Domain sockets support in Java 16, on all operative systems.
+link:https://openjdk.java.net/jeps/380[JEP 380] introduced Unix-Domain sockets support in Java 16, on all operative systems, for `SocketChannel`.
-`ClientConnector` can be configured to support Unix-Domain sockets in the following way:
+`ClientConnector` handles Unix-Domain sockets exactly like it handles regular TCP sockets, so there is no additional configuration necessary -- Unix-Domain sockets are supported out-of-the-box.
-[source,java,indent=0]
-----
-include::../{doc_code}/org/eclipse/jetty/docs/programming/client/ClientConnectorDocs.java[tags=unixDomain]
-----
+Applications can specify the `TransportProtocol` to use for each request as described in xref:pg-client-http-api-protocol[this section].
-[IMPORTANT]
-====
-You can use Unix-Domain sockets support only when you run your client application with Java 16 or later.
-====
+[[pg-client-io-arch-memory]]
+===== Memory Support
+
+In addition to support communication between client and server via network or Unix-Domain, the Jetty client libraries also support communication between client and server via memory for intra-process communication.
+This means that the client and server must be in the same JVM process.
+
+This functionality is provided by `org.eclipse.jetty.server.MemoryTransportProtocol`, which does not delegate to `ClientConnector`, but instead delegates to the server-side `MemoryConnector` and its related classes.
+
+Applications can specify the `TransportProtocol` to use for each request as described in xref:pg-client-http-api-protocol[this section].
[[pg-client-io-arch-protocol]]
==== Protocol Layer
-The protocol layer builds on top of the network layer to generate the bytes to be written to the network and to parse the bytes read from the network.
+The protocol layer builds on top of the transport layer to generate the bytes to be written to the low-level transport and to parse the bytes read from the low-level transport.
-Recall from xref:pg-arch-io-connection[this section] that Jetty uses the `Connection` abstraction to produce and interpret the network bytes.
+Recall from xref:pg-arch-io-connection[this section] that Jetty uses the `Connection` abstraction to produce and interpret the low-level transport bytes.
On the client side, a `ClientConnectionFactory` implementation is the component that creates `Connection` instances based on the protocol that the client wants to "speak" with the server.
-Applications use `ClientConnector.connect(SocketAddress, Map)` to establish a TCP connection to the server, and must tell `ClientConnector` how to create the `Connection` for that particular TCP connection, and how to notify back the application when the connection creation succeeds or fails.
+Applications may use `ClientConnector.connect(SocketAddress, Map)` to establish a TCP connection to the server, and must provide `ClientConnector` with the following information in the context map:
+
+* A `TransportProtocol` instance that specifies the low-level transport to use.
+* A `ClientConnectionFactory` that creates `Connection` instances for the high-level protocol.
+* A `Promise` that is notified when the connection creation succeeds or fails.
-This is done by passing a link:{javadoc-url}/org/eclipse/jetty/io/ClientConnectionFactory.html[`ClientConnectionFactory`] (that creates `Connection` instances) and a link:{javadoc-url}/org/eclipse/jetty/util/Promise.html[`Promise`] (that is notified of connection creation success or failure) in the context `Map` as follows:
+For example:
[source,java,indent=0]
----
diff --git a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/client/http/client-http-api.adoc b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/client/http/client-http-api.adoc
index 1f4d763b8972..395e45751f39 100644
--- a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/client/http/client-http-api.adoc
+++ b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/client/http/client-http-api.adoc
@@ -270,3 +270,33 @@ An application that implements a forwarder between two servers can be implemente
----
include::../../{doc_code}/org/eclipse/jetty/docs/programming/client/http/HTTPClientDocs.java[tag=forwardContent]
----
+
+[[pg-client-http-api-protocol]]
+===== Request `TransportProtocol`
+
+The communication between client and server happens over a xref:pg-client-io-arch-transport[low-level transport], and applications can specify the low-level transport to use for each request.
+
+This gives client applications great flexibility, because they can use the same `HttpClient` instance to communicate, for example, with an external third party web application via TCP, to a different process via Unix-Domain sockets, and efficiently to the same process via memory.
+
+Client application can also choose more esoteric configurations such as using QUIC, typically used to transport HTTP/3, to transport HTTP/1.1 or HTTP/2, because QUIC provides reliable and ordered communication like TCP does.
+
+Provided you have configured a xref:pg-server-http-connector[`UnixDomainServerConnector`] on the server, this is how you can configure a request to use Unix-Domain sockets:
+
+[source,java,indent=0]
+----
+include::../../{doc_code}/org/eclipse/jetty/docs/programming/client/http/HTTPClientDocs.java[tag=unixDomain]
+----
+
+In the same way, if you have configured a xref:pg-server-http-connector[`MemoryConnector`] on the server, this is how you can configure a request to use memory for communication:
+
+[source,java,indent=0]
+----
+include::../../{doc_code}/org/eclipse/jetty/docs/programming/client/http/HTTPClientDocs.java[tag=memory]
+----
+
+This is a fancy example of how to mix HTTP versions and low-level transports:
+
+[source,java,indent=0]
+----
+include::../../{doc_code}/org/eclipse/jetty/docs/programming/client/http/HTTPClientDocs.java[tag=mixedTransportProtocols]
+----
diff --git a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/client/http/client-http-configuration.adoc b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/client/http/client-http-configuration.adoc
index 62e47d66c1ad..cf9d0f632792 100644
--- a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/client/http/client-http-configuration.adoc
+++ b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/client/http/client-http-configuration.adoc
@@ -19,9 +19,9 @@ Please refer to the `HttpClient` link:{javadoc-url}/org/eclipse/jetty/client/Htt
The most common parameters are:
-* `HttpClient.idleTimeout`: same as `ClientConnector.idleTimeout` described in xref:pg-client-io-arch-network[this section].
-* `HttpClient.connectBlocking`: same as `ClientConnector.connectBlocking` described in xref:pg-client-io-arch-network[this section].
-* `HttpClient.connectTimeout`: same as `ClientConnector.connectTimeout` described in xref:pg-client-io-arch-network[this section].
+* `HttpClient.idleTimeout`: same as `ClientConnector.idleTimeout` described in xref:pg-client-io-arch-transport[this section].
+* `HttpClient.connectBlocking`: same as `ClientConnector.connectBlocking` described in xref:pg-client-io-arch-transport[this section].
+* `HttpClient.connectTimeout`: same as `ClientConnector.connectTimeout` described in xref:pg-client-io-arch-transport[this section].
* `HttpClient.maxConnectionsPerDestination`: the max number of TCP connections that are opened for a particular destination (defaults to 64).
* `HttpClient.maxRequestsQueuedPerDestination`: the max number of requests queued (defaults to 1024).
diff --git a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/client/http/client-http-intro.adoc b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/client/http/client-http-intro.adoc
index eb9e737f5236..dfc24643ae0e 100644
--- a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/client/http/client-http-intro.adoc
+++ b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/client/http/client-http-intro.adoc
@@ -21,17 +21,21 @@ It offers an asynchronous API that never blocks for I/O, making it very efficien
However, when all you need to do is to perform a `GET` request to a resource, Jetty's HTTP client offers also a synchronous API; a programming interface where the thread that issued the request blocks until the request/response conversation is complete.
-Jetty's HTTP client supports different xref:pg-client-http-transport[transports protocols]: HTTP/1.1, HTTP/2, HTTP/3 and FastCGI. This means that the semantic of an HTTP request such as: " ``GET`` the resource ``/index.html`` " can be carried over the network in different formats.
-The most common and default format is HTTP/1.1. That said, Jetty's HTTP client can carry the same request using the HTTP/2 format, the HTTP/3 format, or the FastCGI format.
+Jetty's HTTP client supports different xref:pg-client-http-transport[HTTP formats]: HTTP/1.1, HTTP/2, HTTP/3 and FastCGI.
+Each format has a different `HttpClientTransport` implementation, that in turn use a xref:pg-client-io-arch-transport[low-level transport] to communicate with the server.
-Furthermore, every transport protocol can be sent either over the network or via Unix-Domain sockets.
+This means that the semantic of an HTTP request such as: " ``GET`` the resource ``/index.html`` " can be carried over the low-level transport in different formats.
+The most common and default format is HTTP/1.1.
+That said, Jetty's HTTP client can carry the same request using the HTTP/2 format, the HTTP/3 format, or the FastCGI format.
+
+Furthermore, every format can be transported over different low-level transport, such as TCP, Unix-Domain sockets, QUIC or memory.
Supports for Unix-Domain sockets requires Java 16 or later, since Unix-Domain sockets support has been introduced in OpenJDK with link:https://openjdk.java.net/jeps/380[JEP 380].
-The xref:pg-client-http-transport-fcgi[FastCGI transport] is heavily used in Jetty's xref:pg-server-fastcgi[FastCGI support] that allows Jetty to work as a reverse proxy to PHP (exactly like Apache or Nginx do) and therefore be able to serve, for example, WordPress websites, often in conjunction with Unix-Domain sockets (although it's possible to use FastCGI via network too).
+The xref:pg-client-http-transport-fcgi[FastCGI format] is used in Jetty's xref:pg-server-fastcgi[FastCGI support] that allows Jetty to work as a reverse proxy to PHP (exactly like Apache or Nginx do) and therefore be able to serve, for example, WordPress websites, often in conjunction with Unix-Domain sockets (although it is possible to use FastCGI via network too).
-The HTTP/2 transport allows Jetty's HTTP client to perform requests using HTTP/2 to HTTP/2 enabled web sites, see also Jetty's xref:pg-client-http2[HTTP/2 support].
+The HTTP/2 format allows Jetty's HTTP client to perform requests using HTTP/2 to HTTP/2 enabled websites, see also Jetty's xref:pg-client-http2[HTTP/2 support].
-The HTTP/3 transport allows Jetty's HTTP client to perform requests using HTTP/3 to HTTP/3 enabled web sites, see also Jetty's xref:pg-client-http3[HTTP/3 support].
+The HTTP/3 format allows Jetty's HTTP client to perform requests using HTTP/3 to HTTP/3 enabled websites, see also Jetty's xref:pg-client-http3[HTTP/3 support].
Out of the box features that you get with the Jetty HTTP client include:
@@ -72,7 +76,7 @@ There are several reasons for having multiple `HttpClient` instances including,
* You want to specify different configuration parameters (for example, one instance is configured with a forward proxy while another is not).
* You want the two instances to behave like two different browsers and hence have different cookies, different authentication credentials, etc.
-* You want to use xref:pg-client-http-transport[different transports].
+* You want to use xref:pg-client-http-transport[different ``HttpClientTransport``s].
Like browsers, HTTPS requests are supported out-of-the-box (see xref:pg-client-http-configuration-tls[this section] for the TLS configuration), as long as the server provides a valid certificate.
In case the server does not provide a valid certificate (or in case it is self-signed) you want to customize ``HttpClient``'s TLS configuration as described in xref:pg-client-http-configuration-tls[this section].
@@ -105,18 +109,18 @@ include::../../{doc_code}/org/eclipse/jetty/docs/programming/client/http/HTTPCli
A `HttpClient` instance can be thought as a browser instance, and it manages the following components:
-* a `CookieStore` (see xref:pg-client-http-cookie[this section]).
-* a `AuthenticationStore` (see xref:pg-client-http-authentication[this section]).
-* a `ProxyConfiguration` (see xref:pg-client-http-proxy[this section]).
-* a set of ``Destination``s
+* A `CookieStore` (see xref:pg-client-http-cookie[this section]).
+* A `AuthenticationStore` (see xref:pg-client-http-authentication[this section]).
+* A `ProxyConfiguration` (see xref:pg-client-http-proxy[this section]).
+* A set of ``Destination``s
-A `Destination` is the client-side component that represents an _origin_ server, and manages a queue of requests for that origin, and a xref:pg-client-http-connection-pool[pool of TCP connections] to that origin.
+A `Destination` is the client-side component that represents an _origin_ server, and manages a queue of requests for that origin, and a xref:pg-client-http-connection-pool[pool of connections] to that origin.
An _origin_ may be simply thought as the tuple `(scheme, host, port)` and it is where the client connects to in order to communicate with the server.
However, this is not enough.
If you use `HttpClient` to write a proxy you may have different clients that want to contact the same server.
-In this case, you may not want to use the same proxy-to-server connection to proxy requests for both clients, for example for authentication reasons: the server may associate the connection with authentication credentials and you do not want to use the same connection for two different users that have different credentials.
+In this case, you may not want to use the same proxy-to-server connection to proxy requests for both clients, for example for authentication reasons: the server may associate the connection with authentication credentials, and you do not want to use the same connection for two different users that have different credentials.
Instead, you want to use different connections for different clients and this can be achieved by "tagging" a destination with a tag object that represents the remote client (for example, it could be the remote client IP address).
Two origins with the same `(scheme, host, port)` but different `tag` create two different destinations and therefore two different connection pools.
@@ -125,17 +129,20 @@ However, also this is not enough.
It is possible for a server to speak different protocols on the same `port`.
A connection may start by speaking one protocol, for example HTTP/1.1, but then be upgraded to speak a different protocol, for example HTTP/2. After a connection has been upgraded to a second protocol, it cannot speak the first protocol anymore, so it can only be used to communicate using the second protocol.
-Two origins with the same `(scheme, host, port)` but different `protocol` create two different destinations and therefore two different connection pools.
+Two origins with the same `(scheme, host, port, tag)` but different `protocol` create two different destinations and therefore two different connection pools.
+
+Finally, it is possible for a server to speak the same protocol over different xref:pg-client-io-arch-transport[low-level transports] (represented by `TransportProtocol`), for example TCP and Unix-Domain.
+
+Two origins with the same `(scheme, host, port, tag, protocol)` but different low-level transports create two different destinations and therefore two different connection pools.
-Therefore an origin is identified by the tuple `(scheme, host, port, tag, protocol)`.
+Therefore, an origin is identified by the tuple `(scheme, host, port, tag, protocol, transport)`.
[[pg-client-http-connection-pool]]
==== HttpClient Connection Pooling
-A `Destination` manages a `org.eclipse.jetty.client.ConnectionPool`, where connections to a particular origin are pooled for performance reasons:
-opening a connection is a costly operation and it's better to reuse them for multiple requests.
+A `Destination` manages a `org.eclipse.jetty.client.ConnectionPool`, where connections to a particular origin are pooled for performance reasons: opening a connection is a costly operation, and it's better to reuse them for multiple requests.
-NOTE: Remember that to select a specific `Destination` you must select a specific origin, and that an origin is identified by the tuple `(scheme, host, port, tag, protocol)`, so you can have multiple ``Destination``s for the same `host` and `port`, and therefore multiple ``ConnectionPool``s
+NOTE: Remember that to select a specific `Destination` you must select a specific origin, and that an origin is identified by the tuple `(scheme, host, port, tag, protocol, transport)`, so you can have multiple ``Destination``s for the same `host` and `port`, and therefore multiple ``ConnectionPool``s
You can access the `ConnectionPool` in this way:
@@ -147,7 +154,7 @@ include::../../{doc_code}/org/eclipse/jetty/docs/programming/client/http/HTTPCli
Jetty's client library provides the following `ConnectionPool` implementations:
* `DuplexConnectionPool`, historically the first implementation, only used by the HTTP/1.1 transport.
-* `MultiplexConnectionPool`, the generic implementation valid for any transport where connections are reused with a MRU (most recently used) algorithm (that is, the connections most recently returned to the connection pool are the more likely to be used again).
+* `MultiplexConnectionPool`, the generic implementation valid for any transport where connections are reused with a most recently used algorithm (that is, the connections most recently returned to the connection pool are the more likely to be used again).
* `RoundRobinConnectionPool`, similar to `MultiplexConnectionPool` but where connections are reused with a round-robin algorithm.
* `RandomRobinConnectionPool`, similar to `MultiplexConnectionPool` but where connections are reused with an algorithm that chooses them randomly.
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 f66c0a15849e..95d3469ca9c1 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
@@ -14,11 +14,11 @@
[[pg-client-http-transport]]
==== HttpClient Pluggable Transports
-Jetty's `HttpClient` can be configured to use different transport protocols to carry the semantic of HTTP requests and responses.
+Jetty's `HttpClient` can be configured to use different HTTP formats to carry the semantic of HTTP requests and responses, by specifying different `HttpClientTransport` implementations.
-This means that the intention of a client to request resource `/index.html` using the `GET` method can be carried over the network in different formats.
+This means that the intention of a client to request resource `/index.html` using the `GET` method can be carried over a xref:pg-client-http-api-protocol[low-level transport] in different formats.
-An `HttpClient` transport is the component that is in charge of converting a high-level, semantic, HTTP requests such as " ``GET`` resource ``/index.html`` " into the specific format understood by the server (for example, HTTP/2 or HTTP/3), and to convert the server response from the specific format (HTTP/2 or HTTP/3) into high-level, semantic objects that can be used by applications.
+An `HttpClientTransport` is the component that is in charge of converting a high-level, semantic, HTTP requests such as " ``GET`` resource ``/index.html`` " into the specific format understood by the server (for example, HTTP/2 or HTTP/3), and to convert the server response from the specific format (HTTP/2 or HTTP/3) into high-level, semantic objects that can be used by applications.
The most common protocol format is HTTP/1.1, a textual protocol with lines separated by `\r\n`:
@@ -44,25 +44,25 @@ x0C x0B D O C U M E
...
----
-Similarly, HTTP/2 is a binary protocol that transports the same information in a yet different format via TCP, while HTTP/3 is a binary protocol that transports the same information in yet another format via UDP.
+Similarly, HTTP/2 is a binary protocol that transports the same information in a yet different format via TCP, while HTTP/3 is a binary protocol that transports the same information in yet another format via QUIC.
-A protocol may be _negotiated_ between client and server.
+The HTTP protocol version may be _negotiated_ between client and server.
A request for a resource may be sent using one protocol (for example, HTTP/1.1), but the response may arrive in a different protocol (for example, HTTP/2).
-`HttpClient` supports these static transports, each speaking only one protocol:
+`HttpClient` supports these `HttpClientTransport` implementations, each speaking only one protocol:
-* xref:pg-client-http-transport-http11[HTTP/1.1] (both clear-text and TLS encrypted)
-* xref:pg-client-http-transport-http2[HTTP/2] (both clear-text and TLS encrypted)
-* xref:pg-client-http-transport-http3[HTTP/3] (only encrypted via QUIC+TLS)
-* xref:pg-client-http-transport-fcgi[FastCGI] (both clear-text and TLS encrypted)
+* `HttpClientTransportOverHTTP`, for xref:pg-client-http-transport-http11[HTTP/1.1] (both clear-text and TLS encrypted)
+* `HttpClientTransportOverHTTP2`, for xref:pg-client-http-transport-http2[HTTP/2] (both clear-text and TLS encrypted)
+* `HttpClientTransportOverHTTP3`, for xref:pg-client-http-transport-http3[HTTP/3] (only encrypted via QUIC)
+* `HttpClientTransportOverFCGI`, for xref:pg-client-http-transport-fcgi[FastCGI] (both clear-text and TLS encrypted)
-`HttpClient` also supports one xref:pg-client-http-transport-dynamic[dynamic transport], that can speak different protocols and can select the right protocol by negotiating it with the server or by explicit indication from applications.
+`HttpClient` also supports `HttpClientTransportDynamic`, a xref:pg-client-http-transport-dynamic[dynamic transport] that can speak different HTTP formats and can select the right protocol by negotiating it with the server or by explicit indication from applications.
-Furthermore, every transport protocol can be sent either over the network or via Unix-Domain sockets.
+Furthermore, every HTTP format can be sent over different xref:pg-client-http-api-protocol[low-level transports] such as TCP, Unix-Domain, QUIC or memory.
Supports for Unix-Domain sockets requires Java 16 or later, since Unix-Domain sockets support has been introduced in OpenJDK with link:https://openjdk.java.net/jeps/380[JEP 380].
-Applications are typically not aware of the actual protocol being used.
-This allows them to write their logic against a high-level API that hides the details of the specific protocol being used over the network.
+Applications are typically not aware of the actual HTTP format or low-level transport being used.
+This allows them to write their logic against a high-level API that hides the details of the specific HTTP format and low-level transport being used.
[[pg-client-http-transport-http11]]
===== HTTP/1.1 Transport
@@ -93,7 +93,7 @@ include::../../{doc_code}/org/eclipse/jetty/docs/programming/client/http/HTTPCli
`HTTP2Client` is the lower-level client that provides an API based on HTTP/2 concepts such as _sessions_, _streams_ and _frames_ that are specific to HTTP/2. See xref:pg-client-http2[the HTTP/2 client section] for more information.
-`HttpClientTransportOverHTTP2` uses `HTTP2Client` to format high-level semantic HTTP requests (like "GET resource /index.html") into the HTTP/2 specific format.
+`HttpClientTransportOverHTTP2` uses `HTTP2Client` to format high-level semantic HTTP requests into the HTTP/2 specific format.
[[pg-client-http-transport-http3]]
===== HTTP/3 Transport
@@ -107,7 +107,7 @@ include::../../{doc_code}/org/eclipse/jetty/docs/programming/client/http/HTTPCli
`HTTP3Client` is the lower-level client that provides an API based on HTTP/3 concepts such as _sessions_, _streams_ and _frames_ that are specific to HTTP/3. See xref:pg-client-http3[the HTTP/3 client section] for more information.
-`HttpClientTransportOverHTTP3` uses `HTTP3Client` to format high-level semantic HTTP requests (like "GET resource /index.html") into the HTTP/3 specific format.
+`HttpClientTransportOverHTTP3` uses `HTTP3Client` to format high-level semantic HTTP requests into the HTTP/3 specific format.
[[pg-client-http-transport-fcgi]]
===== FastCGI Transport
@@ -126,14 +126,15 @@ The FastCGI transport is primarily used by Jetty's xref:pg-server-fastcgi[FastCG
[[pg-client-http-transport-dynamic]]
===== Dynamic Transport
-The static transports work well if you know in advance the protocol you want to speak with the server, or if the server only supports one protocol (such as FastCGI).
+The static `HttpClientTransport` implementations work well if you know in advance the protocol you want to speak with the server, or if the server only supports one protocol (such as FastCGI).
-With the advent of HTTP/2 and HTTP/3, however, servers are now able to support multiple protocols, at least both HTTP/1.1 and HTTP/2.
+With the advent of HTTP/2 and HTTP/3, however, servers are now able to support multiple protocols.
The HTTP/2 protocol is typically negotiated between client and server.
This negotiation can happen via ALPN, a TLS extension that allows the client to tell the server the list of protocol that the client supports, so that the server can pick one of the client supported protocols that also the server supports; or via HTTP/1.1 upgrade by means of the `Upgrade` header.
-Applications can configure the dynamic transport with one or more _application_ protocols such as HTTP/1.1 or HTTP/2. The implementation will take care of using TLS for HTTPS URIs, using ALPN if necessary, negotiating protocols, upgrading from one protocol to another, etc.
+Applications can configure the dynamic transport with one or more HTTP versions such as HTTP/1.1, HTTP/2 or HTTP/3.
+The implementation will take care of using TLS for HTTPS URIs, using ALPN if necessary, negotiating protocols, upgrading from one protocol to another, etc.
By default, the dynamic transport only speaks HTTP/1.1:
@@ -149,58 +150,52 @@ The dynamic transport can be configured with just one protocol, making it equiva
include::../../{doc_code}/org/eclipse/jetty/docs/programming/client/http/HTTPClientDocs.java[tag=dynamicOneProtocol]
----
-The dynamic transport, however, has been implemented to support multiple transports, in particular both HTTP/1.1 and HTTP/2:
+The dynamic transport, however, has been implemented to support multiple transports, in particular HTTP/1.1, HTTP/2 and HTTP/3:
[source,java,indent=0]
----
-include::../../{doc_code}/org/eclipse/jetty/docs/programming/client/http/HTTPClientDocs.java[tag=dynamicH1H2]
+include::../../{doc_code}/org/eclipse/jetty/docs/programming/client/http/HTTPClientDocs.java[tag=dynamicH1H2H3]
----
-NOTE: The order in which the protocols are specified to `HttpClientTransportDynamic` indicates what is the client preference.
+The order in which the protocols are specified to `HttpClientTransportDynamic` indicates what is the client preference.
-IMPORTANT: When using TLS (i.e. URIs with the `https` scheme), the application protocol is _negotiated_ between client and server via ALPN, and it is the server that decides what is the application protocol to use for the communication, regardless of the client preference.
+When clear-text communication is used (i.e. URIs with the `http` scheme) there is no HTTP protocol version negotiation, and therefore the application must know _a priori_ whether the server supports the HTTP version or not.
+For example, if the server only supports clear-text HTTP/2, and `HttpClientTransportDynamic` is configured as in the example above, where HTTP/1.1 has precedence over HTTP/2, the client will send, by default, a clear-text HTTP/1.1 request to a clear-text HTTP/2 only server, which will result in a communication failure.
-When clear-text communication is used (i.e. URIs with the `http` scheme) there is no application protocol negotiation, and therefore the application must know _a priori_ whether the server supports the protocol or not.
-For example, if the server only supports clear-text HTTP/2, and `HttpClientTransportDynamic` is configured as in the example above, the client will send, by default, a clear-text HTTP/1.1 request to a clear-text HTTP/2 only server, which will result in a communication failure.
+When using TLS (i.e. URIs with the `https` scheme), the HTTP protocol version is _negotiated_ between client and server via ALPN, and it is the server that decides what is the application protocol to use for the communication, regardless of the client preference.
-Provided that the server supports both HTTP/1.1 and HTTP/2 clear-text, client applications can explicitly hint the version they want to use:
+[IMPORTANT]
+====
+HTTP/1.1 and HTTP/2 are _compatible_ because they both use TCP, while HTTP/3 is incompatible with previous HTTP versions because it uses QUIC.
-[source,java,indent=0]
+Only compatible HTTP versions can negotiate the HTTP protocol version to use via ALPN, and only compatible HTTP versions can be upgraded from an older version to a newer version.
+====
+
+Provided that the server supports HTTP/1.1, HTTP/2 and HTTP/3, client applications can explicitly hint the version they want to use:
+
+[source,java,indent=0,options=nowrap]
----
-include::../../{doc_code}/org/eclipse/jetty/docs/programming/client/http/HTTPClientDocs.java[tag=dynamicClearText]
+include::../../{doc_code}/org/eclipse/jetty/docs/programming/client/http/HTTPClientDocs.java[tag=dynamicExplicitVersion]
----
-In case of TLS encrypted communication using the `https` scheme, things are a little more complicated.
-
If the client application explicitly specifies the HTTP version, then ALPN is not used by the client.
By specifying the HTTP version explicitly, the client application has prior-knowledge of what HTTP version the server supports, and therefore ALPN is not needed.
If the server does not support the HTTP version chosen by the client, then the communication will fail.
-If the client application does not explicitly specify the HTTP version, then ALPN will be used by the client.
+If the client application does not explicitly specify the HTTP version, then ALPN will be used by the client, but only for compatible protocols.
If the server also supports ALPN, then the protocol will be negotiated via ALPN and the server will choose the protocol to use.
If the server does not support ALPN, the client will try to use the first protocol configured in `HttpClientTransportDynamic`, and the communication may succeed or fail depending on whether the server supports the protocol chosen by the client.
-[[pg-client-http-transport-unix-domain]]
-===== Unix-Domain Configuration
-
-All the transports can be configured with a `ClientConnector`, the component that is responsible for the transmission of the bytes generated by the transport to the server.
-
-By default, `ClientConnector` uses TCP networking to send bytes to the server and receive bytes from the server.
+For example, HTTP/3 is not compatible with previous HTTP version; if `HttpClientTransportDynamic` is configured to prefer HTTP/3, it will be the only protocol attempted by the client:
-When you are using Java 16 or later, `ClientConnector` also support xref:pg-client-io-arch-unix-domain[Unix-Domain sockets].
-
-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]
+[source,java,indent=0,options=nowrap]
----
-include::../../{doc_code}/org/eclipse/jetty/docs/programming/client/http/HTTPClientDocs.java[tag=unixDomain]
+include::../../{doc_code}/org/eclipse/jetty/docs/programming/client/http/HTTPClientDocs.java[tag=dynamicPreferH3]
----
-[IMPORTANT]
-====
-You can use Unix-Domain sockets support only when you run your client application with Java 16 or later.
-====
+When the client application configures `HttpClientTransportDynamic` to prefer HTTP/2, there could be ALPN negotiation between HTTP/2 and HTTP/1.1 (but not HTTP/3 because it is incompatible); HTTP/3 will only be possible by specifying the HTTP version explicitly:
-You can configure a Jetty server to use Unix-Domain sockets, as explained in xref:pg-server-http-connector[this section].
+[source,java,indent=0,options=nowrap]
+----
+include::../../{doc_code}/org/eclipse/jetty/docs/programming/client/http/HTTPClientDocs.java[tag=dynamicPreferH2]
+----
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 60dff979f791..8f29d16780da 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
@@ -18,7 +18,6 @@
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
-import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
@@ -31,6 +30,7 @@
import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.SelectorManager;
+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.BufferUtil;
@@ -143,6 +143,9 @@ public void onFillable()
int port = 8080;
SocketAddress address = new InetSocketAddress(host, port);
+ // The TransportProtocol instance.
+ TransportProtocol transportProtocol = TransportProtocol.TCP_IP;
+
// The ClientConnectionFactory that creates CustomConnection instances.
ClientConnectionFactory connectionFactory = (endPoint, context) ->
{
@@ -155,6 +158,7 @@ public void onFillable()
// Populate the context with the mandatory keys to create and obtain connections.
Map context = new ConcurrentHashMap<>();
+ context.put(TransportProtocol.class.getName(), transportProtocol);
context.put(ClientConnector.CLIENT_CONNECTION_FACTORY_CONTEXT_KEY, connectionFactory);
context.put(ClientConnector.CONNECTION_PROMISE_CONTEXT_KEY, connectionPromise);
clientConnector.connect(address, context);
@@ -258,7 +262,7 @@ public void writeLine(String line, Callback callback)
ClientConnector clientConnector = new ClientConnector();
clientConnector.start();
- String host = "wikipedia.org";
+ String host = "example.org";
int port = 80;
SocketAddress address = new InetSocketAddress(host, port);
@@ -268,6 +272,7 @@ public void writeLine(String line, Callback callback)
CompletableFuture connectionPromise = new Promise.Completable<>();
Map context = new HashMap<>();
+ context.put(TransportProtocol.class.getName(), TransportProtocol.TCP_IP);
context.put(ClientConnector.CLIENT_CONNECTION_FACTORY_CONTEXT_KEY, connectionFactory);
context.put(ClientConnector.CONNECTION_PROMISE_CONTEXT_KEY, connectionPromise);
clientConnector.connect(address, context);
@@ -280,9 +285,7 @@ public void writeLine(String line, Callback callback)
connection.onLine(line -> System.getLogger("app").log(INFO, "line: {0}", line));
// Write a line.
- connection.writeLine("" +
- "GET / HTTP/1.0\r\n" +
- "", Callback.NOOP);
+ connection.writeLine("GET / HTTP/1.0\r\n", Callback.NOOP);
}
else
{
@@ -379,7 +382,7 @@ public void writeLine(String line, Callback callback)
clientConnector.start();
// Use port 443 to contact the server using encrypted HTTP.
- String host = "wikipedia.org";
+ String host = "example.org";
int port = 443;
SocketAddress address = new InetSocketAddress(host, port);
@@ -393,7 +396,8 @@ public void writeLine(String line, Callback callback)
// We will obtain a SslConnection now.
CompletableFuture connectionPromise = new Promise.Completable<>();
- Map context = new HashMap<>();
+ Map context = new ConcurrentHashMap<>();
+ context.put(TransportProtocol.class.getName(), TransportProtocol.TCP_IP);
context.put(ClientConnector.CLIENT_CONNECTION_FACTORY_CONTEXT_KEY, connectionFactory);
context.put(ClientConnector.CONNECTION_PROMISE_CONTEXT_KEY, connectionPromise);
clientConnector.connect(address, context);
@@ -408,9 +412,7 @@ public void writeLine(String line, Callback callback)
connection.onLine(line -> System.getLogger("app").log(INFO, "line: {0}", line));
// Write a line.
- connection.writeLine("" +
- "GET / HTTP/1.0\r\n" +
- "", Callback.NOOP);
+ connection.writeLine("GET / HTTP/1.0\r\n", Callback.NOOP);
}
else
{
@@ -420,19 +422,6 @@ public void writeLine(String line, Callback callback)
// end::tlsTelnet[]
}
- public void unixDomain() throws Exception
- {
- // tag::unixDomain[]
- // 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 clientConnector = ClientConnector.forUnixDomain(unixDomainPath);
- clientConnector.start();
- // end::unixDomain[]
- }
-
public static void main(String[] args) throws Exception
{
new ClientConnectorDocs().tlsTelnet();
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 1d9a6e195b1f..64b389a01ac7 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
@@ -65,12 +65,17 @@
import org.eclipse.jetty.http2.client.transport.ClientConnectionFactoryOverHTTP2;
import org.eclipse.jetty.http2.client.transport.HttpClientTransportOverHTTP2;
import org.eclipse.jetty.http3.client.HTTP3Client;
+import org.eclipse.jetty.http3.client.transport.ClientConnectionFactoryOverHTTP3;
import org.eclipse.jetty.http3.client.transport.HttpClientTransportOverHTTP3;
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.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.MemoryConnector;
+import org.eclipse.jetty.server.MemoryTransportProtocol;
+import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.ssl.SslContextFactory;
@@ -858,11 +863,11 @@ public void http2Transport() throws Exception
{
// tag::http2Transport[]
// The HTTP2Client powers the HTTP/2 transport.
- HTTP2Client h2Client = new HTTP2Client();
- h2Client.setInitialSessionRecvWindow(64 * 1024 * 1024);
+ HTTP2Client http2Client = new HTTP2Client();
+ http2Client.setInitialSessionRecvWindow(64 * 1024 * 1024);
// Create and configure the HTTP/2 transport.
- HttpClientTransportOverHTTP2 transport = new HttpClientTransportOverHTTP2(h2Client);
+ HttpClientTransportOverHTTP2 transport = new HttpClientTransportOverHTTP2(http2Client);
transport.setUseALPN(true);
HttpClient client = new HttpClient(transport);
@@ -877,11 +882,11 @@ public void http3Transport() throws Exception
SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
// The HTTP3Client powers the HTTP/3 transport.
ClientQuicConfiguration clientQuicConfig = new ClientQuicConfiguration(sslContextFactory, null);
- HTTP3Client h3Client = new HTTP3Client(clientQuicConfig);
- h3Client.getQuicConfiguration().setSessionRecvWindow(64 * 1024 * 1024);
+ HTTP3Client http3Client = new HTTP3Client(clientQuicConfig);
+ http3Client.getQuicConfiguration().setSessionRecvWindow(64 * 1024 * 1024);
// Create and configure the HTTP/3 transport.
- HttpClientTransportOverHTTP3 transport = new HttpClientTransportOverHTTP3(h3Client);
+ HttpClientTransportOverHTTP3 transport = new HttpClientTransportOverHTTP3(http3Client);
HttpClient client = new HttpClient(transport);
client.start();
@@ -924,57 +929,134 @@ public void dynamicOneProtocol()
// end::dynamicOneProtocol[]
}
- public void dynamicH1H2() throws Exception
+ public void dynamicH1H2H3() throws Exception
{
- // tag::dynamicH1H2[]
+ // tag::dynamicH1H2H3[]
+ SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
+
ClientConnector connector = new ClientConnector();
+ connector.setSslContextFactory(sslContextFactory);
ClientConnectionFactory.Info http1 = HttpClientConnectionFactory.HTTP11;
HTTP2Client http2Client = new HTTP2Client(connector);
ClientConnectionFactoryOverHTTP2.HTTP2 http2 = new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client);
- HttpClientTransportDynamic transport = new HttpClientTransportDynamic(connector, http1, http2);
+ ClientQuicConfiguration quicConfiguration = new ClientQuicConfiguration(sslContextFactory, null);
+ HTTP3Client http3Client = new HTTP3Client(quicConfiguration, connector);
+ ClientConnectionFactoryOverHTTP3.HTTP3 http3 = new ClientConnectionFactoryOverHTTP3.HTTP3(http3Client);
+
+ // The order of the protocols indicates the client's preference.
+ HttpClientTransportDynamic transport = new HttpClientTransportDynamic(connector, http1, http2, http3);
HttpClient client = new HttpClient(transport);
client.start();
- // end::dynamicH1H2[]
+ // end::dynamicH1H2H3[]
}
- public void dynamicClearText() throws Exception
+ public void dynamicExplicitVersion() throws Exception
{
- // tag::dynamicClearText[]
+ SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
+
ClientConnector connector = new ClientConnector();
+ connector.setSslContextFactory(sslContextFactory);
+
ClientConnectionFactory.Info http1 = HttpClientConnectionFactory.HTTP11;
+
HTTP2Client http2Client = new HTTP2Client(connector);
ClientConnectionFactoryOverHTTP2.HTTP2 http2 = new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client);
- HttpClientTransportDynamic transport = new HttpClientTransportDynamic(connector, http1, http2);
+
+ ClientQuicConfiguration quicConfiguration = new ClientQuicConfiguration(sslContextFactory, null);
+ HTTP3Client http3Client = new HTTP3Client(quicConfiguration, connector);
+ ClientConnectionFactoryOverHTTP3.HTTP3 http3 = new ClientConnectionFactoryOverHTTP3.HTTP3(http3Client);
+ // tag::dynamicExplicitVersion[]
+ HttpClientTransportDynamic transport = new HttpClientTransportDynamic(connector, http1, http2, http3);
HttpClient client = new HttpClient(transport);
client.start();
- // The server supports both HTTP/1.1 and HTTP/2 clear-text on port 8080.
+ // The server supports HTTP/1.1, HTTP/2 and HTTP/3.
- // Make a clear-text request without explicit version.
- // The first protocol specified to HttpClientTransportDynamic
- // is picked, in this example will be HTTP/1.1.
- ContentResponse http1Response = client.newRequest("host", 8080).send();
+ ContentResponse http1Response = client.newRequest("https://host/")
+ // Specify the version explicitly.
+ .version(HttpVersion.HTTP_1_1)
+ .send();
- // Make a clear-text request with explicit version.
- // Clear-text HTTP/2 is used for this request.
- ContentResponse http2Response = client.newRequest("host", 8080)
+ ContentResponse http2Response = client.newRequest("https://host/")
// Specify the version explicitly.
.version(HttpVersion.HTTP_2)
.send();
+ ContentResponse http3Response = client.newRequest("https://host/")
+ // Specify the version explicitly.
+ .version(HttpVersion.HTTP_3)
+ .send();
+
// Make a clear-text upgrade request from HTTP/1.1 to HTTP/2.
// The request will start as HTTP/1.1, but the response will be HTTP/2.
- ContentResponse upgradedResponse = client.newRequest("host", 8080)
+ ContentResponse upgradedResponse = client.newRequest("https://host/")
.headers(headers -> headers
.put(HttpHeader.UPGRADE, "h2c")
.put(HttpHeader.HTTP2_SETTINGS, "")
.put(HttpHeader.CONNECTION, "Upgrade, HTTP2-Settings"))
.send();
- // end::dynamicClearText[]
+ // end::dynamicExplicitVersion[]
+ }
+
+ public void dynamicPreferH3() throws Exception
+ {
+ SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
+
+ ClientConnector connector = new ClientConnector();
+ connector.setSslContextFactory(sslContextFactory);
+
+ ClientConnectionFactory.Info http1 = HttpClientConnectionFactory.HTTP11;
+
+ HTTP2Client http2Client = new HTTP2Client(connector);
+ ClientConnectionFactoryOverHTTP2.HTTP2 http2 = new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client);
+
+ ClientQuicConfiguration quicConfiguration = new ClientQuicConfiguration(sslContextFactory, null);
+ HTTP3Client http3Client = new HTTP3Client(quicConfiguration, connector);
+ ClientConnectionFactoryOverHTTP3.HTTP3 http3 = new ClientConnectionFactoryOverHTTP3.HTTP3(http3Client);
+ // tag::dynamicPreferH3[]
+ // Client prefers HTTP/3.
+ HttpClientTransportDynamic transport = new HttpClientTransportDynamic(connector, http3, http2, http1);
+ HttpClient client = new HttpClient(transport);
+ client.start();
+
+ // No explicit HTTP version specified.
+ // Either HTTP/3 succeeds, or communication failure.
+ ContentResponse httpResponse = client.newRequest("https://host/")
+ .send();
+ // end::dynamicPreferH3[]
+ }
+
+ public void dynamicPreferH2() throws Exception
+ {
+ SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
+
+ ClientConnector connector = new ClientConnector();
+ connector.setSslContextFactory(sslContextFactory);
+
+ ClientConnectionFactory.Info http1 = HttpClientConnectionFactory.HTTP11;
+
+ HTTP2Client http2Client = new HTTP2Client(connector);
+ ClientConnectionFactoryOverHTTP2.HTTP2 http2 = new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client);
+
+ ClientQuicConfiguration quicConfiguration = new ClientQuicConfiguration(sslContextFactory, null);
+ HTTP3Client http3Client = new HTTP3Client(quicConfiguration, connector);
+ ClientConnectionFactoryOverHTTP3.HTTP3 http3 = new ClientConnectionFactoryOverHTTP3.HTTP3(http3Client);
+ // tag::dynamicPreferH2[]
+ // Client prefers HTTP/2.
+ HttpClientTransportDynamic transport = new HttpClientTransportDynamic(connector, http2, http1, http3);
+ HttpClient client = new HttpClient(transport);
+ client.start();
+
+ // No explicit HTTP version specified.
+ // Either HTTP/1.1 or HTTP/2 will be negotiated via ALPN.
+ // HTTP/3 only possible by specifying the version explicitly.
+ ContentResponse httpResponse = client.newRequest("https://host/")
+ .send();
+ // end::dynamicPreferH2[]
}
public void getConnectionPool() throws Exception
@@ -1042,10 +1124,85 @@ public void unixDomain() throws Exception
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)
+ // Specify that the request must be sent over Unix-Domain.
.transportProtocol(new TransportProtocol.TCPUnix(unixDomainPath))
.send();
// end::unixDomain[]
}
+
+ public void memory() throws Exception
+ {
+ // tag::memory[]
+ // The server-side MemoryConnector speaking HTTP/1.1.
+ Server server = new Server();
+ MemoryConnector memoryConnector = new MemoryConnector(server, new HttpConnectionFactory());
+ server.addConnector(memoryConnector);
+ // ...
+
+ // The code above is the server-side.
+ // ----
+ // The code below is the client-side.
+
+ HttpClient httpClient = new HttpClient();
+ httpClient.start();
+
+ // Use the MemoryTransportProtocol to communicate with the server-side.
+ TransportProtocol transportProtocol = new MemoryTransportProtocol(memoryConnector);
+
+ httpClient.newRequest("http://localhost/")
+ // Specify the TransportProtocol to use.
+ .transportProtocol(transportProtocol)
+ .send();
+ // end::memory[]
+ }
+
+ public void mixedTransportProtocols() throws Exception
+ {
+ Path unixDomainPath = Path.of("/path/to/server.sock");
+
+ Server server = new Server();
+ MemoryConnector memoryConnector = new MemoryConnector(server, new HttpConnectionFactory());
+
+ SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
+
+ ClientConnector clientConnector = new ClientConnector();
+ clientConnector.setSslContextFactory(sslContextFactory);
+
+ ClientConnectionFactory.Info http1 = HttpClientConnectionFactory.HTTP11;
+
+ HTTP2Client http2Client = new HTTP2Client(clientConnector);
+ ClientConnectionFactoryOverHTTP2.HTTP2 http2 = new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client);
+
+ ClientQuicConfiguration quicConfiguration = new ClientQuicConfiguration(sslContextFactory, null);
+ HTTP3Client http3Client = new HTTP3Client(quicConfiguration, clientConnector);
+ ClientConnectionFactoryOverHTTP3.HTTP3 http3 = new ClientConnectionFactoryOverHTTP3.HTTP3(http3Client);
+
+ // tag::mixedTransportProtocols[]
+ HttpClient httpClient = new HttpClient(new HttpClientTransportDynamic(clientConnector, http2, http1, http3));
+ httpClient.start();
+
+ // Make a TCP request to a 3rd party web application.
+ ContentResponse thirdPartyResponse = httpClient.newRequest("https://third-party.com/api")
+ // No need to specify the TransportProtocol, TCP will be used by default.
+ .send();
+
+ // Upload the third party response content to a validation process.
+ ContentResponse validatedResponse = httpClient.newRequest("http://localhost/validate")
+ // The validation process is available via Unix-Domain.
+ .transportProtocol(new TransportProtocol.TCPUnix(unixDomainPath))
+ .method(HttpMethod.POST)
+ .body(new BytesRequestContent(thirdPartyResponse.getContent()))
+ .send();
+
+ // Process the validated response intra-process by sending
+ // it to another web application in the same Jetty server.
+ ContentResponse response = httpClient.newRequest("http://localhost/process")
+ // The processing is in-memory.
+ .transportProtocol(new MemoryTransportProtocol(memoryConnector))
+ .method(HttpMethod.POST)
+ .body(new BytesRequestContent(validatedResponse.getContent()))
+ .send();
+ // end::mixedTransportProtocols[]
+ }
}
From ef5bc369f5c57b40d985c4fede1991fab43dbe0c Mon Sep 17 00:00:00 2001
From: Simone Bordet
Date: Tue, 6 Feb 2024 11:28:04 +0100
Subject: [PATCH 05/13] Improved javadocs.
Signed-off-by: Simone Bordet
---
.../java/org/eclipse/jetty/io/EndPoint.java | 156 ++++++++----------
.../eclipse/jetty/io/TransportProtocol.java | 8 +-
2 files changed, 75 insertions(+), 89 deletions(-)
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 e06fdce80afa..63c1e553e64a 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
@@ -24,71 +24,45 @@
import javax.net.ssl.SSLSession;
import org.eclipse.jetty.util.Callback;
-import org.eclipse.jetty.util.FutureCallback;
-import org.eclipse.jetty.util.IteratingCallback;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.Invocable;
/**
- * EndPoint is the abstraction for an I/O channel that transports bytes.
+ * EndPoint is the abstraction for I/O communication using bytes.
+ * All the I/O methods are non-blocking; reads may return {@code 0}
+ * bytes read, and flushes/writes may write {@code 0} bytes.
+ * Applications are notified of read readiness by registering a
+ * {@link Callback} via {@link #fillInterested(Callback)}, and then
+ * using {@link #fill(ByteBuffer)} to read the available bytes.
+ * Application may use {@link #flush(ByteBuffer...)} to transmit bytes;
+ * if the flush does not transmit all the bytes, applications must
+ * arrange to resume flushing when it will be possible to transmit more
+ * bytes.
+ * Alternatively, applications may use {@link #write(Callback, ByteBuffer...)}
+ * and be notified via the {@link Callback} when the write completes,
+ * either successfully or with a failure.
+ * Connection-less reads are performed using {@link #receive(ByteBuffer)}.
+ * Similarly, connection-less flushes are performed using
+ * {@link #send(SocketAddress, ByteBuffer...)} and connection-less writes
+ * using {@link #write(Callback, SocketAddress, ByteBuffer...)}.
+ * While all the I/O methods are non-blocking, they can be easily
+ * converted to blocking using {@link org.eclipse.jetty.util.Blocker} or
+ * {@link Callback.Completable}:
+ * {@code
+ * EndPoint endPoint = ...;
*
- * Asynchronous Methods
- * The asynchronous scheduling methods of {@link EndPoint}
- * has been influenced by NIO.2 Futures and Completion
- * handlers, but does not use those actual interfaces because they have
- * some inefficiencies.
- * This class will frequently be used in conjunction with some of the utility
- * implementations of {@link Callback}, such as {@link FutureCallback} and
- * {@link IteratingCallback}.
- *
- * Reads
- * A {@link FutureCallback} can be used to block until an endpoint is ready
- * to fill bytes - the notification will be emitted by the NIO subsystem:
- *
- * FutureCallback callback = new FutureCallback();
- * endPoint.fillInterested(callback);
- *
- * // Blocks until read to fill bytes.
- * callback.get();
- *
- * // Now bytes can be filled in a ByteBuffer.
- * int filled = endPoint.fill(byteBuffer);
- *
- *
- * Asynchronous Reads
- * A {@link Callback} can be used to read asynchronously in its own dispatched
- * thread:
- *
- * endPoint.fillInterested(new Callback()
+ * // Block until read ready with Blocker.
+ * try (Blocker.Callback blocker = Blocker.callback())
* {
- * public void onSucceeded()
- * {
- * executor.execute(() ->
- * {
- * // Fill bytes in a different thread.
- * int filled = endPoint.fill(byteBuffer);
- * });
- * }
- * public void onFailed(Throwable failure)
- * {
- * endPoint.close();
- * }
- * });
- *
- *
- * Blocking Writes
- * The write contract is that the callback is completed when all the bytes
- * have been written or there is a failure.
- * Blocking writes look like this:
- *
- * FutureCallback callback = new FutureCallback();
- * endpoint.write(callback, headerBuffer, contentBuffer);
+ * endPoint.fillInterested(blocker);
+ * blocker.block();
+ * }
*
- * // Blocks until the write succeeds or fails.
- * future.get();
- *
- * Note also that multiple buffers may be passed in {@link #write(Callback, ByteBuffer...)}
- * so that gather writes can be performed for efficiency.
+ * // Block until write complete with Callback.Completable.
+ * Callback.Completable completable = new Callback.Completable();
+ * endPoint.write(completable, byteBuffer);
+ * completable.get();
+ * }
*/
public interface EndPoint extends Closeable
{
@@ -153,39 +127,39 @@ default SocketAddress getRemoteSocketAddress()
long getCreatedTimeStamp();
/**
- * Shutdown the output.
- * This call indicates that no more data will be sent on this endpoint that
- * that the remote end should read an EOF once all previously sent data has been
- * consumed. Shutdown may be done either at the TCP/IP level, as a protocol exchange (Eg
- * TLS close handshake) or both.
- *
- * If the endpoint has {@link #isInputShutdown()} true, then this call has the same effect
- * as {@link #close()}.
+ *
Shuts down the output.
+ * This call indicates that no more data will be sent from this endpoint and
+ * that the remote endpoint should read an EOF once all previously sent data has been
+ * read. Shutdown may be done either at the TCP/IP level, as a protocol exchange
+ * (for example, TLS close handshake) or both.
+ * If the endpoint has {@link #isInputShutdown()} true, then this call has the
+ * same effect as {@link #close()}.
*/
void shutdownOutput();
/**
- * Test if output is shutdown.
- * The output is shutdown by a call to {@link #shutdownOutput()}
- * or {@link #close()}.
+ * Tests if output is shutdown.
+ * The output is shutdown by a call to {@link #shutdownOutput()}
+ * or {@link #close()}.
*
* @return true if the output is shutdown or the endpoint is closed.
*/
boolean isOutputShutdown();
/**
- * Test if the input is shutdown.
- * The input is shutdown if an EOF has been read while doing
- * a {@link #fill(ByteBuffer)}. Once the input is shutdown, all calls to
+ * Tests if the input is shutdown.
+ * The input is shutdown if an EOF has been read while doing
+ * a {@link #fill(ByteBuffer)}.
+ * Once the input is shutdown, all calls to
* {@link #fill(ByteBuffer)} will return -1, until such time as the
- * end point is close, when they will return {@link EofException}.
+ * end point is close, when they will return {@link EofException}.
*
- * @return True if the input is shutdown or the endpoint is closed.
+ * @return true if the input is shutdown or the endpoint is closed.
*/
boolean isInputShutdown();
/**
- * Close any backing stream associated with the endpoint
+ * Closes any backing stream associated with the endpoint.
*/
@Override
default void close()
@@ -194,16 +168,18 @@ default void close()
}
/**
- * Close any backing stream associated with the endpoint, passing a cause
+ * Closes any backing stream associated with the endpoint, passing a
+ * possibly {@code null} failure cause.
*
* @param cause the reason for the close or null
*/
void close(Throwable cause);
/**
- * Fill the passed buffer with data from this endpoint. The bytes are appended to any
- * data already in the buffer by writing from the buffers limit up to it's capacity.
- * The limit is updated to include the filled bytes.
+ * Fills the passed buffer with data from this endpoint.
+ * The bytes are appended to any data already in the buffer
+ * by writing from the buffers limit up to its capacity.
+ * The limit is updated to include the filled bytes.
*
* @param buffer The buffer to fill. The position and limit are modified during the fill. After the
* operation, the position is unchanged and the limit is increased to reflect the new data filled.
@@ -235,9 +211,11 @@ default SocketAddress receive(ByteBuffer buffer) throws IOException
}
/**
- * 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
- * is updated to indicate how many bytes have been consumed.
+ * Flushes 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 is updated to indicate how many bytes
+ * have been consumed.
*
* @param buffer the buffers to flush
* @return True IFF all the buffers have been consumed and the endpoint has flushed the data to its
@@ -270,16 +248,17 @@ default boolean send(SocketAddress address, ByteBuffer... buffers) throws IOExce
Object getTransport();
/**
- * Get the max idle time in ms.
- * The max idle time is the time the endpoint can be idle before
- * extraordinary handling takes place.
+ *
Returns the idle timeout in ms.
+ * The idle timeout is the time the endpoint can be idle before
+ * its close is initiated.
+ * A timeout less than or equal to {@code 0} implies an infinite timeout.
*
- * @return the max idle time in ms or if ms <= 0 implies an infinite timeout
+ * @return the idle timeout in ms
*/
long getIdleTimeout();
/**
- * Set the idle timeout.
+ * Sets the idle timeout.
*
* @param idleTimeout the idle timeout in MS. Timeout <= 0 implies an infinite timeout
*/
@@ -383,7 +362,8 @@ default void write(Callback callback, SocketAddress address, ByteBuffer... buffe
void upgrade(Connection newConnection);
/**
- * Get the SslSessionData of a secure end point.
+ * Returns the SslSessionData of a secure end point.
+ *
* @return A {@link SslSessionData} instance (with possibly null field values) if secure, else {@code null}.
*/
default SslSessionData getSslSessionData()
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
index 487264320676..31709aa3f56e 100644
--- 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
@@ -87,6 +87,12 @@ default boolean requiresDomainNamesResolution()
/**
* Establishes a connection to the given socket address.
+ * For transport protocols that {@link #requiresDomainNamesResolution()
+ * require domain name resolution}, this is the IP address resolved from
+ * the domain name.
+ * For transport protocols that do not require domain name resolution
+ * (for example Unix-Domain sockets, or memory) this is the socket address
+ * to connect to.
*
* @param socketAddress the socket address to connect to
* @param context the context information to establish the connection
@@ -96,7 +102,7 @@ default void connect(SocketAddress socketAddress, Map context)
}
/**
- * @return the socket address to use in case domain name resolution is not required
+ * @return the socket address to connect to in case domain name resolution is not required
*/
default SocketAddress getSocketAddress()
{
From 91fb6ec6cf8312ae57fcee64cd257b1b1b96990f Mon Sep 17 00:00:00 2001
From: Simone Bordet
Date: Tue, 6 Feb 2024 15:17:28 +0100
Subject: [PATCH 06/13] Fixed javadocs.
Signed-off-by: Simone Bordet
---
.../src/main/java/org/eclipse/jetty/io/EndPoint.java | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
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 63c1e553e64a..06b056a9ad63 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
@@ -39,15 +39,16 @@
* arrange to resume flushing when it will be possible to transmit more
* bytes.
* Alternatively, applications may use {@link #write(Callback, ByteBuffer...)}
- * and be notified via the {@link Callback} when the write completes,
- * either successfully or with a failure.
+ * and be notified via the {@link Callback} when the write completes
+ * (i.e. all the buffers have been flushed), either successfully or
+ * with a failure.
* Connection-less reads are performed using {@link #receive(ByteBuffer)}.
* Similarly, connection-less flushes are performed using
* {@link #send(SocketAddress, ByteBuffer...)} and connection-less writes
* using {@link #write(Callback, SocketAddress, ByteBuffer...)}.
* While all the I/O methods are non-blocking, they can be easily
- * converted to blocking using {@link org.eclipse.jetty.util.Blocker} or
- * {@link Callback.Completable}:
+ * converted to blocking using either {@link org.eclipse.jetty.util.Blocker}
+ * or {@link Callback.Completable}:
* {@code
* EndPoint endPoint = ...;
*
@@ -212,7 +213,7 @@ default SocketAddress receive(ByteBuffer buffer) throws IOException
/**
* Flushes data from the passed header/buffer to this endpoint.
- * As many bytes as can be consumed are taken from the header/buffer
+ * As many bytes as can be consumed are taken from the header/buffer
* position up until the buffer limit.
* The header/buffers position is updated to indicate how many bytes
* have been consumed.
From 63c9951ea667d30328b5d8fce9f0877fc37e833d Mon Sep 17 00:00:00 2001
From: Simone Bordet
Date: Thu, 8 Feb 2024 11:04:33 +0100
Subject: [PATCH 07/13] Fixed handling of Instruction notifications in case of
re-entrance. Now first clear the list, then notify to avoid that when
re-entering the same instruction is notified multiple times.
Signed-off-by: Simone Bordet
---
.../org/eclipse/jetty/http3/qpack/QpackDecoder.java | 11 ++++++++---
.../org/eclipse/jetty/http3/qpack/QpackEncoder.java | 7 +++++--
2 files changed, 13 insertions(+), 5 deletions(-)
diff --git a/jetty-core/jetty-http3/jetty-http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/QpackDecoder.java b/jetty-core/jetty-http3/jetty-http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/QpackDecoder.java
index a3ed361683e7..03ded66c7ec9 100644
--- a/jetty-core/jetty-http3/jetty-http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/QpackDecoder.java
+++ b/jetty-core/jetty-http3/jetty-http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/QpackDecoder.java
@@ -332,16 +332,21 @@ public String toString()
private void notifyInstructionHandler()
{
- if (!_instructions.isEmpty())
- _handler.onInstructions(_instructions);
+ if (_instructions.isEmpty())
+ return;
+ // Copy the list to avoid re-entrance.
+ List instructions = List.copyOf(_instructions);
_instructions.clear();
+ _handler.onInstructions(instructions);
}
private void notifyMetaDataHandler(boolean wasBlocked)
{
+ if (_metaDataNotifications.isEmpty())
+ return;
// Copy the list to avoid re-entrance, where the call to
// notifyHandler() may end up calling again this method.
- List notifications = new ArrayList<>(_metaDataNotifications);
+ List notifications = List.copyOf(_metaDataNotifications);
_metaDataNotifications.clear();
for (MetaDataNotification notification : notifications)
{
diff --git a/jetty-core/jetty-http3/jetty-http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/QpackEncoder.java b/jetty-core/jetty-http3/jetty-http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/QpackEncoder.java
index c6f4556eee76..4e69605b0c50 100644
--- a/jetty-core/jetty-http3/jetty-http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/QpackEncoder.java
+++ b/jetty-core/jetty-http3/jetty-http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/QpackEncoder.java
@@ -499,9 +499,12 @@ private static int encodeInsertCount(int reqInsertCount, int maxTableCapacity)
private void notifyInstructionHandler()
{
- if (!_instructions.isEmpty())
- _handler.onInstructions(_instructions);
+ if (_instructions.isEmpty())
+ return;
+ // Copy the list to avoid re-entrance.
+ List instructions = List.copyOf(_instructions);
_instructions.clear();
+ _handler.onInstructions(instructions);
}
InstructionHandler getInstructionHandler()
From 7e4250065fc6d60490c9c10c227579a54d5df538 Mon Sep 17 00:00:00 2001
From: Simone Bordet
Date: Thu, 8 Feb 2024 17:04:49 +0100
Subject: [PATCH 08/13] Added test case for custom TransportProtocol.
Introduced ContentSourceRequestContent, and updated ProxyHandler to use it.
Signed-off-by: Simone Bordet
---
.../client/ContentSourceRequestContent.java | 85 ++++
.../jetty/client/MultiPartRequestContent.java | 12 +-
.../org/eclipse/jetty/proxy/ProxyHandler.java | 41 +-
.../CustomTransportProtocolTest.java | 395 ++++++++++++++++++
4 files changed, 494 insertions(+), 39 deletions(-)
create mode 100644 jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/ContentSourceRequestContent.java
create mode 100644 jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/CustomTransportProtocolTest.java
diff --git a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/ContentSourceRequestContent.java b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/ContentSourceRequestContent.java
new file mode 100644
index 000000000000..0765aaec8fae
--- /dev/null
+++ b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/ContentSourceRequestContent.java
@@ -0,0 +1,85 @@
+//
+// ========================================================================
+// 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.client;
+
+import java.util.Objects;
+
+import org.eclipse.jetty.io.Content;
+
+/**
+ * A {@link Request.Content} that wraps a {@link Content.Source}.
+ */
+public class ContentSourceRequestContent implements Request.Content
+{
+ private final Content.Source source;
+ private final String contentType;
+
+ public ContentSourceRequestContent(Content.Source source)
+ {
+ this(source, "application/octet-stream");
+ }
+
+ public ContentSourceRequestContent(Content.Source source, String contentType)
+ {
+ this.source = Objects.requireNonNull(source);
+ this.contentType = contentType;
+ }
+
+ public Content.Source getContentSource()
+ {
+ return source;
+ }
+
+ @Override
+ public String getContentType()
+ {
+ return contentType;
+ }
+
+ @Override
+ public long getLength()
+ {
+ return getContentSource().getLength();
+ }
+
+ @Override
+ public Content.Chunk read()
+ {
+ return getContentSource().read();
+ }
+
+ @Override
+ public void demand(Runnable demandCallback)
+ {
+ getContentSource().demand(demandCallback);
+ }
+
+ @Override
+ public void fail(Throwable failure)
+ {
+ fail(failure, true);
+ }
+
+ @Override
+ public void fail(Throwable failure, boolean last)
+ {
+ getContentSource().fail(failure, last);
+ }
+
+ @Override
+ public boolean rewind()
+ {
+ return getContentSource().rewind();
+ }
+}
diff --git a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/MultiPartRequestContent.java b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/MultiPartRequestContent.java
index 3992f03f33db..388a9610a21a 100644
--- a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/MultiPartRequestContent.java
+++ b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/MultiPartRequestContent.java
@@ -34,12 +34,12 @@
* .send();
*
* The above example would be the equivalent of submitting this form:
- *
- * <form method="POST" enctype="multipart/form-data" accept-charset="UTF-8">
- * <input type="text" name="field" value="foo" />
- * <input type="file" name="icon" />
- * </form>
- *
+ * {@code
+ *
+ * }
*/
public class MultiPartRequestContent extends MultiPartFormData.ContentSource implements Request.Content
{
diff --git a/jetty-core/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ProxyHandler.java b/jetty-core/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ProxyHandler.java
index dfecfc051b78..99baae9f5910 100644
--- a/jetty-core/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ProxyHandler.java
+++ b/jetty-core/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ProxyHandler.java
@@ -28,6 +28,7 @@
import java.util.stream.Stream;
import org.eclipse.jetty.client.AsyncRequestContent;
+import org.eclipse.jetty.client.ContentSourceRequestContent;
import org.eclipse.jetty.client.ContinueProtocolHandler;
import org.eclipse.jetty.client.EarlyHintsProtocolHandler;
import org.eclipse.jetty.client.HttpClient;
@@ -347,7 +348,7 @@ protected void addViaHeader(Request clientToProxyRequest, org.eclipse.jetty.clie
.flatMap(field -> Stream.of(field.getValues()))
.filter(value -> !StringUtil.isBlank(value))
.collect(Collectors.joining(separator));
- if (newValue.length() > 0)
+ if (!newValue.isEmpty())
newValue += separator;
newValue += viaHeaderValue;
return new HttpField(HttpHeader.VIA, newValue);
@@ -578,53 +579,27 @@ protected HttpURI rewriteHttpURI(Request clientToProxyRequest)
}
}
- protected static class ProxyRequestContent implements org.eclipse.jetty.client.Request.Content
+ protected static class ProxyRequestContent extends ContentSourceRequestContent
{
- private final Request clientToProxyRequest;
-
public ProxyRequestContent(Request clientToProxyRequest)
{
- this.clientToProxyRequest = clientToProxyRequest;
+ super(clientToProxyRequest, clientToProxyRequest.getHeaders().get(HttpHeader.CONTENT_TYPE));
}
@Override
- public long getLength()
+ public Request getContentSource()
{
- return clientToProxyRequest.getLength();
+ return (Request)super.getContentSource();
}
@Override
public Content.Chunk read()
{
- Content.Chunk chunk = clientToProxyRequest.read();
+ Content.Chunk chunk = super.read();
if (LOG.isDebugEnabled())
- LOG.debug("{} C2P read content {}", requestId(clientToProxyRequest), chunk);
+ LOG.debug("{} C2P read content {}", requestId(getContentSource()), chunk);
return chunk;
}
-
- @Override
- public void demand(Runnable demandCallback)
- {
- clientToProxyRequest.demand(demandCallback);
- }
-
- @Override
- public void fail(Throwable failure)
- {
- clientToProxyRequest.fail(failure);
- }
-
- @Override
- public String getContentType()
- {
- return clientToProxyRequest.getHeaders().get(HttpHeader.CONTENT_TYPE);
- }
-
- @Override
- public boolean rewind()
- {
- return clientToProxyRequest.rewind();
- }
}
protected class ProxyResponseListener extends Callback.Completable implements org.eclipse.jetty.client.Response.Listener
diff --git a/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/CustomTransportProtocolTest.java b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/CustomTransportProtocolTest.java
new file mode 100644
index 000000000000..9ed2e2cbf9c4
--- /dev/null
+++ b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/CustomTransportProtocolTest.java
@@ -0,0 +1,395 @@
+//
+// ========================================================================
+// 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.SocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.client.CompletableResponseListener;
+import org.eclipse.jetty.client.ContentResponse;
+import org.eclipse.jetty.client.ContentSourceRequestContent;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.StringRequestContent;
+import org.eclipse.jetty.client.transport.HttpClientTransportOverHTTP;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.io.ClientConnector;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.MemoryEndPointPipe;
+import org.eclipse.jetty.io.TransportProtocol;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+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.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.IteratingCallback;
+import org.eclipse.jetty.util.Promise;
+import org.eclipse.jetty.util.component.LifeCycle;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.eclipse.jetty.util.thread.Scheduler;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+/**
+ * Tests a proxy scenario where the proxy HttpClient wants to send the request bytes to an in-memory gateway,
+ * and receive response bytes from it. The in-memory gateway sends the request bytes over the network
+ * for example via SSH, and then receives the response bytes from SSH, which should be relayed back to HttpClient.
+ * Simulates the following flows:
+ * {@code
+ * Client -> Proxy -> HttpClient.newRequest() -> Local MemoryEndPoint -> Request Bytes -> In-Memory Gateway (SSH) - -> Remote Server
+ * |
+ * Remote Server (SSH) - - > In-Memory Gateway -> Response Bytes -> Remote MemoryEndPoint -> HttpClient -> Proxy -> Client
+ * }
+ */
+public class CustomTransportProtocolTest
+{
+ private static final String CONTENT = "CONTENT";
+
+ 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 testCustomTransportProtocol() throws Exception
+ {
+ Gateway gateway = new Gateway();
+
+ ServerConnector connector = new ServerConnector(server, 1, 1, new HttpConnectionFactory());
+ server.addConnector(connector);
+ server.setHandler(new Handler.Abstract()
+ {
+ @Override
+ public boolean handle(Request request, Response response, Callback callback)
+ {
+ var gatewayRequest = httpClient.newRequest("http://localhost/")
+ .transportProtocol(new GatewayTransportProtocol(httpClient.getScheduler(), gateway))
+ .method(request.getMethod())
+ .path(request.getHttpURI().getPathQuery())
+ .timeout(5, TimeUnit.SECONDS);
+
+ // Copy some of the headers.
+ String contentType = request.getHeaders().get(HttpHeader.CONTENT_TYPE);
+ if (contentType != null)
+ gatewayRequest.headers(headers -> headers.put(HttpHeader.CONTENT_TYPE, contentType));
+
+ // Copy the request content.
+ if (request.getLength() != 0)
+ gatewayRequest.body(new ContentSourceRequestContent(request));
+
+ // Send the request.
+ // It will be serialized into bytes and sent to the Gateway.
+ CompletableFuture completable = new CompletableResponseListener(gatewayRequest).send();
+ completable.whenComplete((r, x) ->
+ {
+ if (x == null)
+ {
+ // Copy the response headers.
+ response.getHeaders().add(r.getHeaders());
+ // Remote Content-Encoding, as the content has already been decoded.
+ response.getHeaders().remove(HttpHeader.CONTENT_ENCODING);
+ // Copy the response content.
+ response.write(true, ByteBuffer.wrap(r.getContent()), callback);
+ }
+ else
+ {
+ Response.writeError(request, response, callback, x);
+ }
+ });
+
+ return true;
+ }
+ });
+ server.start();
+
+ // Make a request to the server, it will be forwarded to the external system in bytes.
+ CompletableFuture completable = new CompletableResponseListener(httpClient.newRequest("localhost", connector.getLocalPort())
+ .method(HttpMethod.POST)
+ .body(new StringRequestContent("REQUEST"))
+ .timeout(5, TimeUnit.SECONDS)
+ ).send();
+
+ // After a while, simulate that the Gateway sends back data on Channel 1.
+ Thread.sleep(500);
+ gateway.onData(1);
+
+ ContentResponse response = completable.get(5, TimeUnit.SECONDS);
+ assertThat(response.getStatus(), is(HttpStatus.OK_200));
+ assertThat(response.getContentAsString(), is(CONTENT));
+ }
+
+ private static class GatewayTransportProtocol implements TransportProtocol
+ {
+ private final Scheduler scheduler;
+ private final Gateway gateway;
+
+ private GatewayTransportProtocol(Scheduler scheduler, Gateway gateway)
+ {
+ this.scheduler = scheduler;
+ this.gateway = gateway;
+ }
+
+ @Override
+ public void connect(SocketAddress socketAddress, Map context)
+ {
+ @SuppressWarnings("unchecked")
+ Promise promise = (Promise)context.get(ClientConnector.CONNECTION_PROMISE_CONTEXT_KEY);
+ try
+ {
+ // Create the Pipe to connect client and server.
+ MemoryEndPointPipe pipe = new MemoryEndPointPipe(scheduler, Runnable::run, socketAddress);
+
+ // Set up the server-side.
+ EndPoint remoteEndPoint = pipe.getRemoteEndPoint();
+ gateway.onConnect(remoteEndPoint);
+
+ // Set up the client-side.
+ EndPoint localEndPoint = pipe.getLocalEndPoint();
+
+ ClientConnector clientConnector = (ClientConnector)context.get(ClientConnector.CLIENT_CONNECTOR_CONTEXT_KEY);
+ localEndPoint.setIdleTimeout(clientConnector.getIdleTimeout().toMillis());
+
+ TransportProtocol transportProtocol = (TransportProtocol)context.get(TransportProtocol.class.getName());
+ Connection connection = transportProtocol.newConnection(localEndPoint, context);
+ localEndPoint.setConnection(connection);
+
+ localEndPoint.onOpen();
+ connection.onOpen();
+ }
+ catch (Throwable x)
+ {
+ promise.failed(x);
+ }
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return Objects.hash(gateway);
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ return true;
+ if (obj instanceof GatewayTransportProtocol that)
+ return Objects.equals(gateway, that.gateway);
+ return false;
+ }
+ }
+
+ private static class Gateway
+ {
+ private final Map channels = new ConcurrentHashMap<>();
+
+ public void onConnect(EndPoint endPoint)
+ {
+ // For every new connection, generate a new Channel,
+ // and associate the Channel with the EndPoint.
+ Channel channel = new Channel(endPoint);
+ channels.put(channel.id, channel);
+
+ // Register for read interest with the EndPoint.
+ endPoint.fillInterested(new EndPointToChannelCallback(channel));
+ }
+
+ // Called when there data to read from the Gateway on the given Channel.
+ public void onData(int id)
+ {
+ Channel channel = channels.get(id);
+ // Simulate the data to read.
+ channel.data = StandardCharsets.UTF_8.encode("""
+ HTTP/1.1 200 OK
+ Content-Length: %d
+
+ """.formatted(CONTENT.length()) + CONTENT);
+ new ChannelToEndPointCallback(channel).iterate();
+ }
+
+ private class Channel
+ {
+ // Channels should have different ids,
+ // hard-coding the id just for the test.
+ private final int id = 1;
+ private final EndPoint endPoint;
+ private ByteBuffer data;
+
+ public Channel(EndPoint endPoint)
+ {
+ this.endPoint = endPoint;
+ }
+
+ public void close(Throwable failure)
+ {
+ // Close the Gateway Channel, possibly due to a failure.
+ channels.remove(id);
+ endPoint.close(failure);
+ }
+
+ public void demand()
+ {
+ // Demands to be notified by calling Gateway.onData()
+ // when there is data to read from the Gateway.
+ }
+
+ public int read(ByteBuffer buffer)
+ {
+ // This simulates response data arriving from the Gateway.
+ if (data == null)
+ return 0;
+ ByteBuffer received = data;
+ data = null;
+ int length = received.remaining();
+ buffer.put(received).flip();
+ return length;
+ }
+
+ public void write(Callback callback, ByteBuffer byteBuffer)
+ {
+ // Write the buffer and simulate that the write succeeded.
+ byteBuffer.position(byteBuffer.limit());
+ callback.succeeded();
+ }
+ }
+
+ // Reads from the EndPoint, and writes to the Gateway Channel.
+ private static class EndPointToChannelCallback extends IteratingCallback
+ {
+ private final Channel channel;
+
+ private EndPointToChannelCallback(Channel channel)
+ {
+ this.channel = channel;
+ }
+
+ @Override
+ protected Action process() throws Throwable
+ {
+ EndPoint endPoint = channel.endPoint;
+ ByteBuffer buffer = BufferUtil.allocate(1024);
+ int filled = endPoint.fill(buffer);
+ if (filled < 0)
+ return Action.SUCCEEDED;
+ if (filled == 0)
+ {
+ endPoint.fillInterested(this);
+ return Action.IDLE;
+ }
+ channel.write(this, buffer);
+ return Action.SCHEDULED;
+ }
+
+ @Override
+ public void succeeded()
+ {
+ // There is data to read from the EndPoint.
+ // Iterate to read it and send it to the Gateway.
+ iterate();
+ }
+
+ @Override
+ protected void onCompleteSuccess()
+ {
+ // Nothing more to read, close the Gateway Channel.
+ channel.close(null);
+ }
+
+ @Override
+ protected void onCompleteFailure(Throwable cause)
+ {
+ // There was a write error, close the Gateway Channel.
+ channel.close(cause);
+ }
+ }
+
+ // Reads from the Gateway Channel, and writes to the EndPoint.
+ private static class ChannelToEndPointCallback extends IteratingCallback
+ {
+ private final Channel channel;
+
+ private ChannelToEndPointCallback(Channel channel)
+ {
+ this.channel = channel;
+ }
+
+ @Override
+ protected Action process()
+ {
+ ByteBuffer buffer = ByteBuffer.allocate(1024);
+ // Read from the Gateway Channel.
+ int read = channel.read(buffer);
+ if (read < 0)
+ return Action.SUCCEEDED;
+ if (read == 0)
+ {
+ channel.demand();
+ return Action.IDLE;
+ }
+ // Write to the EndPoint.
+ channel.endPoint.write(this, buffer);
+ return Action.SCHEDULED;
+ }
+
+ @Override
+ protected void onCompleteSuccess()
+ {
+ // Nothing more to read, close the Gateway Channel.
+ channel.close(null);
+ }
+
+ @Override
+ protected void onCompleteFailure(Throwable cause)
+ {
+ // There was a write error, close the Gateway Channel.
+ channel.close(cause);
+ }
+ }
+ }
+}
From d015ee175a5224671c1b99d83a0fb8bc9c9a5f77 Mon Sep 17 00:00:00 2001
From: Simone Bordet
Date: Fri, 9 Feb 2024 18:04:46 +0100
Subject: [PATCH 09/13] Refactored TransportProtocol tests. Now unix-domain
files are temp files, so their name is unique.
Signed-off-by: Simone Bordet
---
.../AbstractTransportProtocolTest.java | 54 +++++++++++++++++++
.../CustomTransportProtocolTest.java | 2 +-
.../transport/HTTP1TransportProtocolTest.java | 27 +---------
.../transport/HTTP2TransportProtocolTest.java | 27 +---------
.../transport/HTTP3TransportProtocolTest.java | 24 ++-------
.../HTTPDynamicTransportProtocolTest.java | 35 +-----------
6 files changed, 64 insertions(+), 105 deletions(-)
create mode 100644 jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/AbstractTransportProtocolTest.java
diff --git a/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/AbstractTransportProtocolTest.java b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/AbstractTransportProtocolTest.java
new file mode 100644
index 000000000000..052296db4cf2
--- /dev/null
+++ b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/AbstractTransportProtocolTest.java
@@ -0,0 +1,54 @@
+//
+// ========================================================================
+// 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.nio.file.Files;
+import java.nio.file.Path;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
+import org.eclipse.jetty.util.component.LifeCycle;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+@ExtendWith(WorkDirExtension.class)
+public abstract class AbstractTransportProtocolTest
+{
+ protected Server server;
+
+ @BeforeEach
+ public void setup()
+ {
+ QueuedThreadPool serverThreads = new QueuedThreadPool();
+ serverThreads.setName("server");
+ server = new Server(serverThreads);
+ }
+
+ @AfterEach
+ public void dispose()
+ {
+ LifeCycle.stop(server);
+ }
+
+ protected static Path newUnixDomainPath() throws IOException
+ {
+ String unixDomainDir = System.getProperty("jetty.unixdomain.dir", System.getProperty("java.io.tmpdir"));
+ Path unixDomainFile = Files.createTempFile(Path.of(unixDomainDir), "jetty-", ".sock");
+ Files.delete(unixDomainFile);
+ return unixDomainFile;
+ }
+}
diff --git a/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/CustomTransportProtocolTest.java b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/CustomTransportProtocolTest.java
index 9ed2e2cbf9c4..02ad1e4c5401 100644
--- a/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/CustomTransportProtocolTest.java
+++ b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/CustomTransportProtocolTest.java
@@ -132,7 +132,7 @@ public boolean handle(Request request, Response response, Callback callback)
{
// Copy the response headers.
response.getHeaders().add(r.getHeaders());
- // Remote Content-Encoding, as the content has already been decoded.
+ // Remove Content-Encoding, as the content has already been decoded.
response.getHeaders().remove(HttpHeader.CONTENT_ENCODING);
// Copy the response content.
response.write(true, ByteBuffer.wrap(r.getContent()), callback);
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
index 185dc7f49869..e5025b9fbde6 100644
--- 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
@@ -33,52 +33,35 @@
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
+public class HTTP1TransportProtocolTest extends AbstractTransportProtocolTest
{
- 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");
+ clientThreads.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
{
@@ -186,10 +169,4 @@ public void testQUICTransportProtocol(WorkDir workDir) throws Exception
assertThat(response.getStatus(), is(HttpStatus.OK_200));
}
-
- 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/HTTP2TransportProtocolTest.java b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HTTP2TransportProtocolTest.java
index 69ec2c3a8018..1c31be7a80da 100644
--- 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
@@ -45,42 +45,31 @@
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
+public class HTTP2TransportProtocolTest extends AbstractTransportProtocolTest
{
- 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");
+ clientThreads.setName("client");
clientConnector.setExecutor(clientThreads);
clientConnector.setSelectors(1);
http2Client = new HTTP2Client(clientConnector);
@@ -88,12 +77,6 @@ public void prepare()
server.addBean(httpClient);
}
- @AfterEach
- public void dispose()
- {
- LifeCycle.stop(server);
- }
-
@Test
public void testDefaultTransportProtocol() throws Exception
{
@@ -356,10 +339,4 @@ public void onHeaders(Stream stream, HeadersFrame frame)
assertTrue(responseLatch.await(5, TimeUnit.SECONDS));
}
-
- 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/HTTP3TransportProtocolTest.java b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HTTP3TransportProtocolTest.java
index f4d7367bdfc9..9e8e9aeea57d 100644
--- 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
@@ -43,17 +43,12 @@
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;
@@ -61,13 +56,10 @@
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
-@ExtendWith(WorkDirExtension.class)
-public class HTTP3TransportProtocolTest
+public class HTTP3TransportProtocolTest extends AbstractTransportProtocolTest
{
private SslContextFactory.Server sslServer;
private Path pemServerDir;
- private Server server;
- private SslContextFactory.Client sslClient;
private HttpClient httpClient;
private HTTP3Client http3Client;
@@ -80,16 +72,12 @@ public void prepare(WorkDir workDir) throws Exception
pemServerDir = workDir.getEmptyPathDir().resolve("server");
Files.createDirectories(pemServerDir);
- QueuedThreadPool serverThreads = new QueuedThreadPool();
- serverThreads.setName("server");
- server = new Server(serverThreads);
-
- sslClient = new SslContextFactory.Client(true);
+ SslContextFactory.Client sslClient = new SslContextFactory.Client(true);
ClientQuicConfiguration quicConfiguration = new ClientQuicConfiguration(sslClient, null);
ClientConnector clientConnector = new ClientConnector();
QueuedThreadPool clientThreads = new QueuedThreadPool();
- serverThreads.setName("client");
+ clientThreads.setName("client");
clientConnector.setExecutor(clientThreads);
clientConnector.setSelectors(1);
http3Client = new HTTP3Client(quicConfiguration, clientConnector);
@@ -97,12 +85,6 @@ public void prepare(WorkDir workDir) throws Exception
server.addBean(httpClient);
}
- @AfterEach
- public void dispose()
- {
- LifeCycle.stop(server);
- }
-
@Test
public void testDefaultTransportProtocol() throws Exception
{
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
index 9dacb381c567..0326949896b7 100644
--- 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
@@ -61,37 +61,29 @@
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
+public class HTTPDynamicTransportProtocolTest extends AbstractTransportProtocolTest
{
private SslContextFactory.Server sslServer;
private Path pemServerDir;
- private Server server;
private ClientConnector clientConnector;
private HTTP2Client http2Client;
private HTTP3Client http3Client;
@@ -105,13 +97,9 @@ public void prepare(WorkDir workDir) throws Exception
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");
+ clientThreads.setName("client");
clientConnector.setExecutor(clientThreads);
clientConnector.setSelectors(1);
@@ -122,12 +110,6 @@ public void prepare(WorkDir workDir) throws Exception
http3Client = new HTTP3Client(quicConfiguration, clientConnector);
}
- @AfterEach
- public void dispose()
- {
- LifeCycle.stop(server);
- }
-
@Test
public void testExplicitHTTPVersionWithSameHttpClientForAllHTTPVersions() throws Exception
{
@@ -536,13 +518,6 @@ public void testHighLevelH3OverMemory(WorkDir workDir) throws Exception
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())
@@ -552,10 +527,4 @@ private static int freePort() throws IOException
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");
- }
}
From 2dc58df4293622b4c851cadbb2bce61960f06892 Mon Sep 17 00:00:00 2001
From: Simone Bordet
Date: Wed, 14 Feb 2024 10:18:56 +0100
Subject: [PATCH 10/13] Clarified protocol preference.
Signed-off-by: Simone Bordet
---
.../client/http/client-http-transport.adoc | 2 +-
.../jetty/docs/programming/client/http/HTTPClientDocs.java | 2 ++
.../jetty/client/transport/HttpClientTransportDynamic.java | 6 ++++--
3 files changed, 7 insertions(+), 3 deletions(-)
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 95d3469ca9c1..7362000e8fdf 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
@@ -157,7 +157,7 @@ The dynamic transport, however, has been implemented to support multiple transpo
include::../../{doc_code}/org/eclipse/jetty/docs/programming/client/http/HTTPClientDocs.java[tag=dynamicH1H2H3]
----
-The order in which the protocols are specified to `HttpClientTransportDynamic` indicates what is the client preference.
+The order in which the protocols are specified to `HttpClientTransportDynamic` indicates what is the client preference (first the most preferred).
When clear-text communication is used (i.e. URIs with the `http` scheme) there is no HTTP protocol version negotiation, and therefore the application must know _a priori_ whether the server supports the HTTP version or not.
For example, if the server only supports clear-text HTTP/2, and `HttpClientTransportDynamic` is configured as in the example above, where HTTP/1.1 has precedence over HTTP/2, the client will send, by default, a clear-text HTTP/1.1 request to a clear-text HTTP/2 only server, which will result in a communication failure.
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 64b389a01ac7..ffe8a8b2d230 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
@@ -947,6 +947,8 @@ public void dynamicH1H2H3() throws Exception
ClientConnectionFactoryOverHTTP3.HTTP3 http3 = new ClientConnectionFactoryOverHTTP3.HTTP3(http3Client);
// The order of the protocols indicates the client's preference.
+ // The first is the most preferred, the last is the least preferred, but
+ // the protocol version to use can be explicitly specified in the request.
HttpClientTransportDynamic transport = new HttpClientTransportDynamic(connector, http1, http2, http3);
HttpClient client = new HttpClient(transport);
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 dcf30eb7a4c2..f2dc17931def 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
@@ -92,7 +92,8 @@ public HttpClientTransportDynamic()
}
/**
- * Creates a dynamic transport that speaks the given protocols.
+ * Creates a dynamic transport that speaks the given protocols, in order of preference
+ * (first the most preferred).
*
* @param infos the protocols this dynamic transport speaks
* @deprecated use {@link #HttpClientTransportDynamic(ClientConnector, ClientConnectionFactory.Info...)}
@@ -104,7 +105,8 @@ public HttpClientTransportDynamic(ClientConnectionFactory.Info... infos)
}
/**
- * Creates a dynamic transport with the given {@link ClientConnector} and the given application protocols.
+ * Creates a dynamic transport with the given {@link ClientConnector} and the given protocols,
+ * in order of preference (first the most preferred).
*
* @param connector the ClientConnector used by this transport
* @param infos the application protocols that this transport can speak
From 6ef5ce9d5194e151740db9b3bdc389cd1c30165d Mon Sep 17 00:00:00 2001
From: Simone Bordet
Date: Mon, 19 Feb 2024 22:23:23 +0100
Subject: [PATCH 11/13] Renamed `TransportProtocol` to `Transport`.
Signed-off-by: Simone Bordet
---
.../client/client-io-arch.adoc | 16 ++---
.../client/http/client-http-api.adoc | 4 +-
.../client/http/client-http-intro.adoc | 2 +-
.../client/ClientConnectorDocs.java | 12 ++--
.../client/http/HTTPClientDocs.java | 26 +++----
.../server/http/HTTPServerDocs.java | 6 +-
.../AbstractConnectorHttpClientTransport.java | 2 +-
.../org/eclipse/jetty/client/HttpClient.java | 24 +++----
.../org/eclipse/jetty/client/HttpProxy.java | 6 +-
.../java/org/eclipse/jetty/client/Origin.java | 16 ++---
.../org/eclipse/jetty/client/Request.java | 10 +--
.../HttpClientConnectionFactory.java | 6 +-
.../transport/HttpClientTransportDynamic.java | 16 ++---
.../client/transport/HttpDestination.java | 2 +-
.../jetty/client/transport/HttpRequest.java | 8 +--
.../jetty/fcgi/proxy/FastCGIProxyHandler.java | 4 +-
.../ClientConnectionFactoryOverHTTP2.java | 6 +-
.../HttpClientTransportOverHTTP2.java | 2 +-
.../jetty/http2/client/HTTP2Client.java | 26 +++----
.../ClientConnectionFactoryOverHTTP3.java | 8 +--
.../HttpClientTransportOverHTTP3.java | 12 ++--
.../jetty/http3/client/HTTP3Client.java | 12 ++--
.../jetty/io/ClientConnectionFactory.java | 4 +-
.../org/eclipse/jetty/io/ClientConnector.java | 38 +++++-----
...{TransportProtocol.java => Transport.java} | 70 +++++++++----------
.../QuicClientConnectorConfigurator.java | 8 +--
...nsportProtocol.java => QuicTransport.java} | 14 ++--
...portProtocol.java => MemoryTransport.java} | 16 ++---
...olTest.java => AbstractTransportTest.java} | 2 +-
...ocolTest.java => CustomTransportTest.java} | 18 ++---
...tocolTest.java => HTTP1TransportTest.java} | 28 ++++----
...tocolTest.java => HTTP2TransportTest.java} | 38 +++++-----
...tocolTest.java => HTTP3TransportTest.java} | 32 ++++-----
...est.java => HTTPDynamicTransportTest.java} | 24 +++----
.../unixdomain/server/UnixDomainTest.java | 10 +--
.../ee10/fcgi/proxy/FastCGIProxyServlet.java | 4 +-
.../ee9/fcgi/proxy/FastCGIProxyServlet.java | 4 +-
.../tests/distribution/DistributionTests.java | 4 +-
38 files changed, 270 insertions(+), 270 deletions(-)
rename jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/{TransportProtocol.java => Transport.java} (83%)
rename jetty-core/jetty-quic/jetty-quic-client/src/main/java/org/eclipse/jetty/quic/client/{QuicTransportProtocol.java => QuicTransport.java} (81%)
rename jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/{MemoryTransportProtocol.java => MemoryTransport.java} (81%)
rename jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/{AbstractTransportProtocolTest.java => AbstractTransportTest.java} (97%)
rename jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/{CustomTransportProtocolTest.java => CustomTransportTest.java} (95%)
rename jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/{HTTP1TransportProtocolTest.java => HTTP1TransportTest.java} (86%)
rename jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/{HTTP2TransportProtocolTest.java => HTTP2TransportTest.java} (89%)
rename jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/{HTTP3TransportProtocolTest.java => HTTP3TransportTest.java} (87%)
rename jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/{HTTPDynamicTransportProtocolTest.java => HTTPDynamicTransportTest.java} (95%)
diff --git a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/client/client-io-arch.adoc b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/client/client-io-arch.adoc
index 2cb4a8a9aa0b..ee2d930f3e1f 100644
--- a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/client/client-io-arch.adoc
+++ b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/client/client-io-arch.adoc
@@ -43,13 +43,13 @@ The Jetty client libraries use the common I/O design described in xref:pg-arch-i
The common I/O components and concepts are used for all low-level transports.
The only partial exception is the xref:pg-client-io-arch-memory[memory transport], which is not based on network components; as such it does not need a `SelectorManager`, but it exposes `EndPoint` so that high-level protocols have a common interface to interact with the low-level transport.
-The client-side abstraction for the low-level transport is `org.eclipse.jetty.io.TransportProtocol`.
+The client-side abstraction for the low-level transport is `org.eclipse.jetty.io.Transport`.
-`TransportProtocol` represents how high-level protocols can be transported; there is `TransportProtocol.TCP_IP` that represents communication over TCP, but also `TransportProtocol.TCPUnix` for Unix-Domain sockets, `QuicTransportProtocol` for QUIC and `MemoryTransportProtocol` for memory.
+`Transport` represents how high-level protocols can be transported; there is `Transport.TCP_IP` that represents communication over TCP, but also `Transport.TCPUnix` for Unix-Domain sockets, `QuicTransport` for QUIC and `MemoryTransport` for memory.
-Applications can specify the `TransportProtocol` to use for each request as described in xref:pg-client-http-api-protocol[this section].
+Applications can specify the `Transport` to use for each request as described in xref:pg-client-http-api-protocol[this section].
-When the `TransportProtocol` implementation uses the network, it delegates to `org.eclipse.jetty.io.ClientConnector`.
+When the `Transport` implementation uses the network, it delegates to `org.eclipse.jetty.io.ClientConnector`.
`ClientConnector` primarily wraps `org.eclipse.jetty.io.SelectorManager` to provide network functionalities, and aggregates other four components:
@@ -109,7 +109,7 @@ link:https://openjdk.java.net/jeps/380[JEP 380] introduced Unix-Domain sockets s
`ClientConnector` handles Unix-Domain sockets exactly like it handles regular TCP sockets, so there is no additional configuration necessary -- Unix-Domain sockets are supported out-of-the-box.
-Applications can specify the `TransportProtocol` to use for each request as described in xref:pg-client-http-api-protocol[this section].
+Applications can specify the `Transport` to use for each request as described in xref:pg-client-http-api-protocol[this section].
[[pg-client-io-arch-memory]]
===== Memory Support
@@ -117,9 +117,9 @@ Applications can specify the `TransportProtocol` to use for each request as desc
In addition to support communication between client and server via network or Unix-Domain, the Jetty client libraries also support communication between client and server via memory for intra-process communication.
This means that the client and server must be in the same JVM process.
-This functionality is provided by `org.eclipse.jetty.server.MemoryTransportProtocol`, which does not delegate to `ClientConnector`, but instead delegates to the server-side `MemoryConnector` and its related classes.
+This functionality is provided by `org.eclipse.jetty.server.MemoryTransport`, which does not delegate to `ClientConnector`, but instead delegates to the server-side `MemoryConnector` and its related classes.
-Applications can specify the `TransportProtocol` to use for each request as described in xref:pg-client-http-api-protocol[this section].
+Applications can specify the `Transport` to use for each request as described in xref:pg-client-http-api-protocol[this section].
[[pg-client-io-arch-protocol]]
==== Protocol Layer
@@ -132,7 +132,7 @@ On the client side, a `ClientConnectionFactory` implementation is the component
Applications may use `ClientConnector.connect(SocketAddress, Map)` to establish a TCP connection to the server, and must provide `ClientConnector` with the following information in the context map:
-* A `TransportProtocol` instance that specifies the low-level transport to use.
+* A `Transport` instance that specifies the low-level transport to use.
* A `ClientConnectionFactory` that creates `Connection` instances for the high-level protocol.
* A `Promise` that is notified when the connection creation succeeds or fails.
diff --git a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/client/http/client-http-api.adoc b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/client/http/client-http-api.adoc
index 395e45751f39..56513dc3ec7e 100644
--- a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/client/http/client-http-api.adoc
+++ b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/client/http/client-http-api.adoc
@@ -272,7 +272,7 @@ include::../../{doc_code}/org/eclipse/jetty/docs/programming/client/http/HTTPCli
----
[[pg-client-http-api-protocol]]
-===== Request `TransportProtocol`
+===== Request `Transport`
The communication between client and server happens over a xref:pg-client-io-arch-transport[low-level transport], and applications can specify the low-level transport to use for each request.
@@ -298,5 +298,5 @@ This is a fancy example of how to mix HTTP versions and low-level transports:
[source,java,indent=0]
----
-include::../../{doc_code}/org/eclipse/jetty/docs/programming/client/http/HTTPClientDocs.java[tag=mixedTransportProtocols]
+include::../../{doc_code}/org/eclipse/jetty/docs/programming/client/http/HTTPClientDocs.java[tag=mixedTransports]
----
diff --git a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/client/http/client-http-intro.adoc b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/client/http/client-http-intro.adoc
index dfc24643ae0e..fb4840dff7a6 100644
--- a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/client/http/client-http-intro.adoc
+++ b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/client/http/client-http-intro.adoc
@@ -131,7 +131,7 @@ A connection may start by speaking one protocol, for example HTTP/1.1, but then
Two origins with the same `(scheme, host, port, tag)` but different `protocol` create two different destinations and therefore two different connection pools.
-Finally, it is possible for a server to speak the same protocol over different xref:pg-client-io-arch-transport[low-level transports] (represented by `TransportProtocol`), for example TCP and Unix-Domain.
+Finally, it is possible for a server to speak the same protocol over different xref:pg-client-io-arch-transport[low-level transports] (represented by `Transport`), for example TCP and Unix-Domain.
Two origins with the same `(scheme, host, port, tag, protocol)` but different low-level transports create two different destinations and therefore two different connection pools.
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 8f29d16780da..d621cc99f6eb 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
@@ -30,7 +30,7 @@
import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.SelectorManager;
-import org.eclipse.jetty.io.TransportProtocol;
+import org.eclipse.jetty.io.Transport;
import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
import org.eclipse.jetty.io.ssl.SslConnection;
import org.eclipse.jetty.util.BufferUtil;
@@ -143,8 +143,8 @@ public void onFillable()
int port = 8080;
SocketAddress address = new InetSocketAddress(host, port);
- // The TransportProtocol instance.
- TransportProtocol transportProtocol = TransportProtocol.TCP_IP;
+ // The Transport instance.
+ Transport transport = Transport.TCP_IP;
// The ClientConnectionFactory that creates CustomConnection instances.
ClientConnectionFactory connectionFactory = (endPoint, context) ->
@@ -158,7 +158,7 @@ public void onFillable()
// Populate the context with the mandatory keys to create and obtain connections.
Map context = new ConcurrentHashMap<>();
- context.put(TransportProtocol.class.getName(), transportProtocol);
+ context.put(Transport.class.getName(), transport);
context.put(ClientConnector.CLIENT_CONNECTION_FACTORY_CONTEXT_KEY, connectionFactory);
context.put(ClientConnector.CONNECTION_PROMISE_CONTEXT_KEY, connectionPromise);
clientConnector.connect(address, context);
@@ -272,7 +272,7 @@ public void writeLine(String line, Callback callback)
CompletableFuture connectionPromise = new Promise.Completable<>();
Map context = new HashMap<>();
- context.put(TransportProtocol.class.getName(), TransportProtocol.TCP_IP);
+ context.put(Transport.class.getName(), Transport.TCP_IP);
context.put(ClientConnector.CLIENT_CONNECTION_FACTORY_CONTEXT_KEY, connectionFactory);
context.put(ClientConnector.CONNECTION_PROMISE_CONTEXT_KEY, connectionPromise);
clientConnector.connect(address, context);
@@ -397,7 +397,7 @@ public void writeLine(String line, Callback callback)
CompletableFuture connectionPromise = new Promise.Completable<>();
Map context = new ConcurrentHashMap<>();
- context.put(TransportProtocol.class.getName(), TransportProtocol.TCP_IP);
+ context.put(Transport.class.getName(), Transport.TCP_IP);
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 5610aa7764f1..816c4f3427f1 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
@@ -72,12 +72,12 @@
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.io.Transport;
import org.eclipse.jetty.io.ssl.SslHandshakeListener;
import org.eclipse.jetty.quic.client.ClientQuicConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.MemoryConnector;
-import org.eclipse.jetty.server.MemoryTransportProtocol;
+import org.eclipse.jetty.server.MemoryTransport;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.component.LifeCycle;
@@ -1160,7 +1160,7 @@ public void unixDomain() throws Exception
ContentResponse response = httpClient.newRequest("jetty.org", 80)
// Specify that the request must be sent over Unix-Domain.
- .transportProtocol(new TransportProtocol.TCPUnix(unixDomainPath))
+ .transport(new Transport.TCPUnix(unixDomainPath))
.send();
// end::unixDomain[]
}
@@ -1181,17 +1181,17 @@ public void memory() throws Exception
HttpClient httpClient = new HttpClient();
httpClient.start();
- // Use the MemoryTransportProtocol to communicate with the server-side.
- TransportProtocol transportProtocol = new MemoryTransportProtocol(memoryConnector);
+ // Use the MemoryTransport to communicate with the server-side.
+ Transport transport = new MemoryTransport(memoryConnector);
httpClient.newRequest("http://localhost/")
- // Specify the TransportProtocol to use.
- .transportProtocol(transportProtocol)
+ // Specify the Transport to use.
+ .transport(transport)
.send();
// end::memory[]
}
- public void mixedTransportProtocols() throws Exception
+ public void mixedTransports() throws Exception
{
Path unixDomainPath = Path.of("/path/to/server.sock");
@@ -1212,19 +1212,19 @@ public void mixedTransportProtocols() throws Exception
HTTP3Client http3Client = new HTTP3Client(quicConfiguration, clientConnector);
ClientConnectionFactoryOverHTTP3.HTTP3 http3 = new ClientConnectionFactoryOverHTTP3.HTTP3(http3Client);
- // tag::mixedTransportProtocols[]
+ // tag::mixedTransports[]
HttpClient httpClient = new HttpClient(new HttpClientTransportDynamic(clientConnector, http2, http1, http3));
httpClient.start();
// Make a TCP request to a 3rd party web application.
ContentResponse thirdPartyResponse = httpClient.newRequest("https://third-party.com/api")
- // No need to specify the TransportProtocol, TCP will be used by default.
+ // No need to specify the Transport, TCP will be used by default.
.send();
// Upload the third party response content to a validation process.
ContentResponse validatedResponse = httpClient.newRequest("http://localhost/validate")
// The validation process is available via Unix-Domain.
- .transportProtocol(new TransportProtocol.TCPUnix(unixDomainPath))
+ .transport(new Transport.TCPUnix(unixDomainPath))
.method(HttpMethod.POST)
.body(new BytesRequestContent(thirdPartyResponse.getContent()))
.send();
@@ -1233,10 +1233,10 @@ public void mixedTransportProtocols() throws Exception
// it to another web application in the same Jetty server.
ContentResponse response = httpClient.newRequest("http://localhost/process")
// The processing is in-memory.
- .transportProtocol(new MemoryTransportProtocol(memoryConnector))
+ .transport(new MemoryTransport(memoryConnector))
.method(HttpMethod.POST)
.body(new BytesRequestContent(validatedResponse.getContent()))
.send();
- // end::mixedTransportProtocols[]
+ // end::mixedTransports[]
}
}
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 cc561eed6dd8..1572e0ceb686 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
@@ -66,7 +66,7 @@
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.MemoryTransport;
import org.eclipse.jetty.server.NetworkConnector;
import org.eclipse.jetty.server.ProxyConnectionFactory;
import org.eclipse.jetty.server.Request;
@@ -266,8 +266,8 @@ public void memoryConnector() throws Exception
httpClient.start();
ContentResponse response = httpClient.newRequest("http://localhost/")
- // Use the memory TransportProtocol to communicate with the server-side.
- .transportProtocol(new MemoryTransportProtocol(connector))
+ // Use the memory Transport to communicate with the server-side.
+ .transport(new MemoryTransport(connector))
.send();
// end::memoryConnector[]
}
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 3794710e5520..2845f1fbf929 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
@@ -71,7 +71,7 @@ public void connect(SocketAddress address, Map context)
Promise promise = (Promise)context.get(HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
context.put(ClientConnector.CONNECTION_PROMISE_CONTEXT_KEY, Promise.from(ioConnection -> {}, promise::failed));
context.put(ClientConnector.CLIENT_CONNECTOR_CONTEXT_KEY, connector);
- destination.getOrigin().getTransportProtocol().connect(address, context);
+ destination.getOrigin().getTransport().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 4785046748d7..67f95a677dc3 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,7 +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.Transport;
import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
import org.eclipse.jetty.util.Fields;
import org.eclipse.jetty.util.Jetty;
@@ -468,16 +468,16 @@ public Origin createOrigin(Request request, Origin.Protocol protocol)
host = host.toLowerCase(Locale.ENGLISH);
int port = request.getPort();
port = normalizePort(scheme, port);
- TransportProtocol transportProtocol = request.getTransportProtocol();
- if (transportProtocol == null)
+ Transport transport = request.getTransport();
+ if (transport == null)
{
// Ask the ClientConnector for backwards compatibility
// until ClientConnector.Configurator is removed.
- transportProtocol = connector.newTransportProtocol();
- if (transportProtocol == null)
- transportProtocol = TransportProtocol.TCP_IP;
+ transport = connector.newTransport();
+ if (transport == null)
+ transport = Transport.TCP_IP;
}
- return new Origin(scheme, new Origin.Address(host, port), request.getTag(), protocol, transportProtocol);
+ return new Origin(scheme, new Origin.Address(host, port), request.getTag(), protocol, transport);
}
/**
@@ -540,10 +540,10 @@ public void newConnection(Destination destination, Promise promise)
if (proxy != null)
origin = proxy.getOrigin();
- TransportProtocol transportProtocol = origin.getTransportProtocol();
- context.put(TransportProtocol.class.getName(), transportProtocol);
+ Transport transport = origin.getTransport();
+ context.put(Transport.class.getName(), transport);
- if (transportProtocol.requiresDomainNamesResolution())
+ if (transport.requiresDomainNamesResolution())
{
Origin.Address address = origin.getAddress();
getSocketAddressResolver().resolve(address.getHost(), address.getPort(), new Promise<>()
@@ -574,14 +574,14 @@ public void failed(Throwable x)
connect(socketAddresses, nextIndex, context);
}
});
- transport.connect((SocketAddress)socketAddresses.get(index), context);
+ HttpClient.this.transport.connect((SocketAddress)socketAddresses.get(index), context);
}
});
}
else
{
context.put(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY, promise);
- transport.connect(transportProtocol.getSocketAddress(), context);
+ this.transport.connect(transport.getSocketAddress(), context);
}
}
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 4de7c7dc60c8..830f6425ea33 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
@@ -31,7 +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.io.Transport;
import org.eclipse.jetty.util.Attachable;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.ssl.SslContextFactory;
@@ -54,7 +54,7 @@ public HttpProxy(Origin.Address address, boolean secure)
public HttpProxy(Origin.Address address, boolean secure, Origin.Protocol protocol)
{
- this(new Origin(secure ? "https" : "http", address, null, protocol, TransportProtocol.TCP_IP), null);
+ this(new Origin(secure ? "https" : "http", address, null, protocol, Transport.TCP_IP), null);
}
public HttpProxy(Origin.Address address, SslContextFactory.Client sslContextFactory)
@@ -64,7 +64,7 @@ public HttpProxy(Origin.Address address, SslContextFactory.Client sslContextFact
public HttpProxy(Origin.Address address, SslContextFactory.Client sslContextFactory, Origin.Protocol protocol)
{
- this(new Origin(sslContextFactory == null ? "http" : "https", address, null, protocol, TransportProtocol.TCP_IP), sslContextFactory);
+ this(new Origin(sslContextFactory == null ? "http" : "https", address, null, protocol, Transport.TCP_IP), sslContextFactory);
}
public HttpProxy(Origin origin, SslContextFactory.Client sslContextFactory)
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 e5b7b4712e34..c4bf5686bee7 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,7 +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.io.Transport;
import org.eclipse.jetty.util.HostPort;
import org.eclipse.jetty.util.URIUtil;
@@ -31,7 +31,7 @@
* The elements are {@code scheme}, {@code host}, {@code port}, a
* {@link Origin.Protocol}, a tag object that further distinguishes
* destinations that have the same scheme, host, port and protocol,
- * and a {@link TransportProtocol}.
+ * and a {@link Transport}.
* In general it is possible that, for the same scheme, host and port,
* the server can speak different protocols (for example, clear-text HTTP/1.1
* and clear-text HTTP/2), so the {@link Origin.Protocol} makes that distinction.
@@ -42,7 +42,7 @@
* so that all the connections to the server associated to that destination can
* specify the PROXY protocol bytes for that particular client connection.
* Finally, it is necessary to have different destinations for the same
- * scheme, host, port, and protocol, but having different {@link TransportProtocol},
+ * scheme, host, port, and protocol, but having different {@link Transport},
* for example when the same server may be reached via TCP/IP but also via
* Unix-Domain sockets.
*/
@@ -52,7 +52,7 @@ public class Origin
private final Address address;
private final Object tag;
private final Protocol protocol;
- private final TransportProtocol transport;
+ private final Transport transport;
public Origin(String scheme, String host, int port)
{
@@ -81,10 +81,10 @@ 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);
+ this(scheme, address, tag, protocol, Transport.TCP_IP);
}
- public Origin(String scheme, Address address, Object tag, Protocol protocol, TransportProtocol transport)
+ public Origin(String scheme, Address address, Object tag, Protocol protocol, Transport transport)
{
this.scheme = Objects.requireNonNull(scheme);
this.address = address;
@@ -113,7 +113,7 @@ public Protocol getProtocol()
return protocol;
}
- public TransportProtocol getTransportProtocol()
+ public Transport getTransport()
{
return transport;
}
@@ -155,7 +155,7 @@ public String toString()
asString(),
getTag(),
getProtocol(),
- getTransportProtocol()
+ getTransport()
);
}
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 1bd11eaea2e9..4a7982d801a1 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,7 +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.io.Transport;
import org.eclipse.jetty.util.Fields;
/**
@@ -105,18 +105,18 @@ default Request port(int port)
}
/**
- * @param transport the {@link TransportProtocol} of this request
+ * @param transport the {@link Transport} of this request
* @return this request object
*/
- default Request transportProtocol(TransportProtocol transport)
+ default Request transport(Transport transport)
{
return this;
}
/**
- * @return the {@link TransportProtocol} of this request
+ * @return the {@link Transport} of this request
*/
- default TransportProtocol getTransportProtocol()
+ default Transport getTransport()
{
return null;
}
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 e2a1ee43c850..c5e9f081a857 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,7 +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;
+import org.eclipse.jetty.io.Transport;
public class HttpClientConnectionFactory implements ClientConnectionFactory
{
@@ -51,9 +51,9 @@ public List getProtocols(boolean secure)
}
@Override
- public TransportProtocol newTransportProtocol()
+ public Transport newTransport()
{
- return TransportProtocol.TCP_IP;
+ return Transport.TCP_IP;
}
@Override
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 f2dc17931def..8f7da5848174 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
@@ -36,7 +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.eclipse.jetty.io.Transport;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -213,15 +213,15 @@ else if (matches == 1)
if (matchingInfos.isEmpty())
return getHttpClient().createOrigin(request, null);
- TransportProtocol transportProtocol = request.getTransportProtocol();
- if (transportProtocol == null)
+ Transport transport = request.getTransport();
+ if (transport == 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);
+ transport = getClientConnector().newTransport();
+ if (transport == null)
+ transport = matchingInfos.get(0).newTransport();
+ request.transport(transport);
}
List protocols = matchingInfos.stream()
@@ -253,7 +253,7 @@ public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map getProtocols(boolean secure)
}
@Override
- public TransportProtocol newTransportProtocol()
+ public Transport newTransport()
{
- return TransportProtocol.TCP_IP;
+ return Transport.TCP_IP;
}
@Override
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 0b0e3fc27c67..e6a2d2dda5f6 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
@@ -132,7 +132,7 @@ public void connect(InetSocketAddress address, Map context)
protected void connect(SocketAddress address, ClientConnectionFactory factory, Session.Listener listener, Promise promise, Map context)
{
HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
- getHTTP2Client().connect(destination.getOrigin().getTransportProtocol(), address, factory, listener, promise, context);
+ getHTTP2Client().connect(destination.getOrigin().getTransport(), address, factory, listener, promise, context);
}
protected void connect(InetSocketAddress address, ClientConnectionFactory factory, Session.Listener listener, Promise promise, Map context)
diff --git a/jetty-core/jetty-http2/jetty-http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2Client.java b/jetty-core/jetty-http2/jetty-http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2Client.java
index baf5c0de356d..77263270b914 100644
--- a/jetty-core/jetty-http2/jetty-http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2Client.java
+++ b/jetty-core/jetty-http2/jetty-http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2Client.java
@@ -32,7 +32,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.Transport;
import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
@@ -423,31 +423,31 @@ 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);
+ connect(Transport.TCP_IP, sslContextFactory, address, listener, promise, context);
}
- public CompletableFuture connect(TransportProtocol transportProtocol, SslContextFactory.Client sslContextFactory, SocketAddress address, Session.Listener listener)
+ public CompletableFuture connect(Transport transport, SslContextFactory.Client sslContextFactory, SocketAddress address, Session.Listener listener)
{
- return Promise.Completable.with(p -> connect(transportProtocol, sslContextFactory, address, listener, p, null));
+ return Promise.Completable.with(p -> connect(transport, sslContextFactory, address, listener, p, null));
}
- public void connect(TransportProtocol transportProtocol, SslContextFactory.Client sslContextFactory, SocketAddress address, Session.Listener listener, Promise promise, Map context)
+ public void connect(Transport transport, SslContextFactory.Client sslContextFactory, SocketAddress address, Session.Listener listener, Promise promise, Map context)
{
ClientConnectionFactory factory = newClientConnectionFactory(sslContextFactory);
- connect(transportProtocol, address, factory, listener, promise, context);
+ connect(transport, 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);
+ connect(Transport.TCP_IP, address, factory, listener, promise, context);
}
- public void connect(TransportProtocol transportProtocol, SocketAddress address, ClientConnectionFactory factory, Session.Listener listener, Promise promise, Map context)
+ public void connect(Transport transport, 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(Transport.class.getName(), transport);
context.put(ClientConnector.CONNECTION_PROMISE_CONTEXT_KEY, Promise.from(ioConnection -> {}, promise::failed));
- transportProtocol.connect(address, context);
+ transport.connect(address, context);
}
public void accept(SslContextFactory.Client sslContextFactory, SocketChannel channel, Session.Listener listener, Promise promise)
@@ -458,13 +458,13 @@ 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);
+ accept(Transport.TCP_IP, channel, factory, listener, promise);
}
- public void accept(TransportProtocol transportProtocol, SocketChannel channel, ClientConnectionFactory factory, Session.Listener listener, Promise promise)
+ public void accept(Transport transport, SocketChannel channel, ClientConnectionFactory factory, Session.Listener listener, Promise promise)
{
Map context = contextFrom(factory, listener, promise, null);
- context.put(TransportProtocol.class.getName(), transportProtocol);
+ context.put(Transport.class.getName(), transport);
context.put(ClientConnector.CONNECTION_PROMISE_CONTEXT_KEY, Promise.from(ioConnection -> {}, promise::failed));
connector.accept(channel, context);
}
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 a71c5960079e..f6fa9a5a6dcd 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,8 +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.io.Transport;
+import org.eclipse.jetty.quic.client.QuicTransport;
import org.eclipse.jetty.quic.common.ProtocolSession;
import org.eclipse.jetty.quic.common.QuicSession;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
@@ -73,9 +73,9 @@ public List getProtocols(boolean secure)
}
@Override
- public TransportProtocol newTransportProtocol()
+ public Transport newTransport()
{
- return new QuicTransportProtocol(getHTTP3Client().getQuicConfiguration());
+ return new QuicTransport(getHTTP3Client().getQuicConfiguration());
}
@Override
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 1b3390ee144a..ef2b3bc65234 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,8 +36,8 @@
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.io.Transport;
+import org.eclipse.jetty.quic.client.QuicTransport;
import org.eclipse.jetty.quic.common.ProtocolSession;
import org.eclipse.jetty.quic.common.QuicSession;
@@ -87,9 +87,9 @@ protected void doStart() throws Exception
@Override
public Origin newOrigin(Request request)
{
- TransportProtocol transportProtocol = request.getTransportProtocol();
- if (transportProtocol == null)
- request.transportProtocol(new QuicTransportProtocol(http3Client.getQuicConfiguration()));
+ Transport transport = request.getTransport();
+ if (transport == null)
+ request.transport(new QuicTransport(http3Client.getQuicConfiguration()));
return getHttpClient().createOrigin(request, new Origin.Protocol(List.of("h3"), false));
}
@@ -112,7 +112,7 @@ public void connect(SocketAddress address, Map context)
context.put(ClientConnector.CLIENT_CONNECTION_FACTORY_CONTEXT_KEY, destination.getClientConnectionFactory());
SessionClientListener listener = new TransportSessionClientListener(context);
- getHTTP3Client().connect(destination.getOrigin().getTransportProtocol(), address, listener, context)
+ getHTTP3Client().connect(destination.getOrigin().getTransport(), 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 eda8e93f9d0c..b8bfe7f32010 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
@@ -23,11 +23,11 @@
import org.eclipse.jetty.http3.api.Session;
import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.io.DatagramChannelEndPoint;
-import org.eclipse.jetty.io.TransportProtocol;
+import org.eclipse.jetty.io.Transport;
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.QuicTransportProtocol;
+import org.eclipse.jetty.quic.client.QuicTransport;
import org.eclipse.jetty.quic.common.QuicSessionContainer;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
@@ -190,10 +190,10 @@ public CompletableFuture connect(SocketAddress socketAddress, Se
{
if (context == null)
context = new ConcurrentHashMap<>();
- return connect(new QuicTransportProtocol(getQuicConfiguration()), socketAddress, listener, context);
+ return connect(new QuicTransport(getQuicConfiguration()), socketAddress, listener, context);
}
- public CompletableFuture connect(TransportProtocol transportProtocol, SocketAddress socketAddress, Session.Client.Listener listener, Map context)
+ public CompletableFuture connect(Transport transport, SocketAddress socketAddress, Session.Client.Listener listener, Map context)
{
if (context == null)
context = new ConcurrentHashMap<>();
@@ -204,12 +204,12 @@ public CompletableFuture connect(TransportProtocol transportProt
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);
+ context.put(Transport.class.getName(), transport);
if (LOG.isDebugEnabled())
LOG.debug("connecting to {}", socketAddress);
- transportProtocol.connect(socketAddress, context);
+ transport.connect(socketAddress, context);
return completable;
}
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 9fbeb7d14ef4..82a29c2cbffe 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
@@ -92,9 +92,9 @@ public ClientConnectionFactory getClientConnectionFactory()
}
/**
- * @return the default {@link TransportProtocol} used by the protocol
+ * @return the default {@link Transport} used by the protocol
*/
- public abstract TransportProtocol newTransportProtocol();
+ public abstract Transport newTransport();
/**
* Tests whether one of the protocol identifiers of this
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 0049e6e56ca8..1225a4b634b1 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
@@ -85,7 +85,7 @@ 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 replaced by {@link Transport.TCPUnix}
*/
@Deprecated(since = "12.0.7", forRemoval = true)
public static ClientConnector forUnixDomain(Path path)
@@ -117,7 +117,7 @@ public ClientConnector()
/**
* @param configurator the {@link Configurator}
- * @deprecated replaced by {@link TransportProtocol}
+ * @deprecated replaced by {@link Transport}
*/
@Deprecated(since = "12.0.7", forRemoval = true)
public ClientConnector(Configurator configurator)
@@ -132,7 +132,7 @@ public ClientConnector(Configurator configurator)
* @return whether the connection to the given SocketAddress is intrinsically secure
* @see Configurator#isIntrinsicallySecure(ClientConnector, SocketAddress)
*
- * @deprecated replaced by {@link TransportProtocol#isIntrinsicallySecure()}
+ * @deprecated replaced by {@link Transport#isIntrinsicallySecure()}
*/
@Deprecated(since = "12.0.7", forRemoval = true)
public boolean isIntrinsicallySecure(SocketAddress address)
@@ -151,18 +151,18 @@ public Executor getExecutor()
}
/**
- *
Returns the default {@link TransportProtocol} for this connector.
+ * Returns the default {@link Transport} 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
+ * @return the default {@link Transport} for this connector
+ * @deprecated use {@link Transport} instead
*/
@Deprecated(since = "12.0.7", forRemoval = true)
- public TransportProtocol newTransportProtocol()
+ public Transport newTransport()
{
- return configurator.newTransportProtocol();
+ return configurator.newTransport();
}
public void setExecutor(Executor executor)
@@ -433,7 +433,7 @@ public void connect(SocketAddress address, Map context)
{
context.put(ClientConnector.CLIENT_CONNECTOR_CONTEXT_KEY, this);
- TransportProtocol transport = (TransportProtocol)context.get(TransportProtocol.class.getName());
+ Transport transport = (Transport)context.get(Transport.class.getName());
if (address == null)
address = transport.getSocketAddress();
@@ -554,14 +554,14 @@ protected EndPoint newEndPoint(SelectableChannel selectable, ManagedSelector sel
{
@SuppressWarnings("unchecked")
Map context = (Map)selectionKey.attachment();
- TransportProtocol transportProtocol = (TransportProtocol)context.get(TransportProtocol.class.getName());
- return transportProtocol.newEndPoint(getScheduler(), selector, selectable, selectionKey);
+ Transport transport = (Transport)context.get(Transport.class.getName());
+ return transport.newEndPoint(getScheduler(), selector, selectable, selectionKey);
}
protected Connection newConnection(EndPoint endPoint, Map context) throws IOException
{
- TransportProtocol transportProtocol = (TransportProtocol)context.get(TransportProtocol.class.getName());
- return transportProtocol.newConnection(endPoint, context);
+ Transport transport = (Transport)context.get(Transport.class.getName());
+ return transport.newConnection(endPoint, context);
}
protected void acceptFailed(Throwable failure, SelectableChannel channel, Map context)
@@ -637,15 +637,15 @@ protected void connectionFailed(SelectableChannel channel, Throwable failure, Ob
/**
* Configures a {@link ClientConnector}.
*
- * @deprecated replaced by {@link TransportProtocol}
+ * @deprecated replaced by {@link Transport}
*/
@Deprecated(since = "12.0.7", forRemoval = true)
public static class Configurator extends ContainerLifeCycle
{
/**
- * @return the default {@link TransportProtocol} for this configurator
+ * @return the default {@link Transport} for this configurator
*/
- public TransportProtocol newTransportProtocol()
+ public Transport newTransport()
{
return null;
}
@@ -709,7 +709,7 @@ 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 replaced by {@link Transport}
*/
@Deprecated(since = "12.0.7", forRemoval = true)
public static class ChannelWithAddress
@@ -739,9 +739,9 @@ private static Configurator forUnixDomain(Path path)
return new Configurator()
{
@Override
- public TransportProtocol newTransportProtocol()
+ public Transport newTransport()
{
- return new TransportProtocol.TCPUnix(path);
+ return new Transport.TCPUnix(path);
}
@Override
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/Transport.java
similarity index 83%
rename from jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/TransportProtocol.java
rename to jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/Transport.java
index 31709aa3f56e..0caef553c566 100644
--- 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/Transport.java
@@ -28,7 +28,7 @@
import org.eclipse.jetty.util.thread.Scheduler;
/**
- * The low-level transport protocol used by clients.
+ * The low-level transport 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
@@ -38,7 +38,7 @@
* 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,
+ * {@code Transport}, 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
@@ -47,20 +47,20 @@
* 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
+public interface Transport
{
/**
- * The transport protocol TCP/IP.
+ * The TCP/IP {@code Transport}.
*/
- TransportProtocol TCP_IP = new TCPIP();
+ Transport TCP_IP = new TCPIP();
/**
- * The transport protocol UDP/IP.
+ * The UDP/IP {@code Transport}.
*/
- TransportProtocol UDP_IP = new UDPIP();
+ Transport UDP_IP = new UDPIP();
/**
- * @return whether this transport protocol is intrinsically secure.
+ * @return whether this {@code Transport} is intrinsically secure.
*/
default boolean isIntrinsicallySecure()
{
@@ -68,7 +68,7 @@ default boolean isIntrinsicallySecure()
}
/**
- * Returns whether this transport protocol requires resolution of domain
+ *
Returns whether this {@code Transport} 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()}
@@ -78,7 +78,7 @@ default boolean isIntrinsicallySecure()
* by {@link #getSocketAddress()} is eventually passed to
* {@link #connect(SocketAddress, Map)}.
*
- * @return whether this transport protocol requires domain names resolution
+ * @return whether this {@code Transport} requires domain names resolution
*/
default boolean requiresDomainNamesResolution()
{
@@ -87,10 +87,10 @@ default boolean requiresDomainNamesResolution()
/**
* Establishes a connection to the given socket address.
- * For transport protocols that {@link #requiresDomainNamesResolution()
+ *
For {@code Transport}s that {@link #requiresDomainNamesResolution()
* require domain name resolution}, this is the IP address resolved from
* the domain name.
- * For transport protocols that do not require domain name resolution
+ * For {@code Transport}s that do not require domain name resolution
* (for example Unix-Domain sockets, or memory) this is the socket address
* to connect to.
*
@@ -110,8 +110,8 @@ default SocketAddress getSocketAddress()
}
/**
- * 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,
+ *
For {@code Transport}s that are based on sockets, or for {@code Transport}s
+ * that are layered on top of another {@code Transport} that is based on sockets,
* this method is invoked to create a new {@link SelectableChannel} used for the
* socket communication.
*
@@ -125,8 +125,8 @@ default SelectableChannel newSelectableChannel() throws IOException
}
/**
- * 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,
+ *
For {@code Transport}s that are based on sockets, or for {@code Transport}s
+ * that are layered on top of another {@code Transport} that is based on sockets,
* this method is invoked to create a new {@link EndPoint} that wraps the
* {@link SelectableChannel} created by {@link #newSelectableChannel()}.
*
@@ -143,10 +143,10 @@ default EndPoint newEndPoint(Scheduler scheduler, ManagedSelector selector, Sele
/**
* 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
+ *
For non-layered {@code Transport}s 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.
+ * For layered {@code Transport}s such as QUIC, the {@link Connection} is typically that of the
+ * layered {@code Transport}.
*
* @param endPoint the {@link EndPoint} to associate the {@link Connection} to
* @param context the context information to create the connection
@@ -164,9 +164,9 @@ default Connection newConnection(EndPoint endPoint, Map context)
boolean equals(Object obj);
/**
- * Abstract implementation of transport protocols based on sockets.
+ * Abstract implementation of {@code Transport} based on sockets.
*/
- abstract class Socket implements TransportProtocol
+ abstract class Socket implements Transport
{
@Override
public void connect(SocketAddress socketAddress, Map context)
@@ -183,7 +183,7 @@ public String toString()
}
/**
- * Abstract implementation of transport protocols based on IP.
+ * Abstract implementation of {@code Transport} based on IP.
*/
abstract class IP extends Socket
{
@@ -195,7 +195,7 @@ public boolean requiresDomainNamesResolution()
}
/**
- * The TCP/IP transport protocol.
+ * The TCP/IP {@code Transport}.
*/
class TCPIP extends IP
{
@@ -218,9 +218,9 @@ public EndPoint newEndPoint(Scheduler scheduler, ManagedSelector selector, Selec
}
/**
- * The UDP/IP transport protocol.
+ * The UDP/IP {@code Transport}.
*/
- class UDPIP extends TransportProtocol.IP
+ class UDPIP extends Transport.IP
{
protected UDPIP()
{
@@ -241,7 +241,7 @@ public EndPoint newEndPoint(Scheduler scheduler, ManagedSelector selector, Selec
}
/**
- * Abstract implementation of transport protocols based on Unix-Domain sockets.
+ * Abstract implementation of {@code Transport} based on Unix-Domain sockets.
*/
abstract class Unix extends Socket
{
@@ -282,7 +282,7 @@ public String toString()
}
/**
- * The stream Unix-Domain socket transport protocol.
+ * The stream Unix-Domain socket {@code Transport}.
*/
class TCPUnix extends Unix
{
@@ -305,7 +305,7 @@ public EndPoint newEndPoint(Scheduler scheduler, ManagedSelector selector, Selec
}
/**
- * The datagram Unix-Domain socket transport protocol.
+ * The datagram Unix-Domain socket {@code Transport}.
*/
class UDPUnix extends Unix
{
@@ -328,25 +328,25 @@ public EndPoint newEndPoint(Scheduler scheduler, ManagedSelector selector, Selec
}
/**
- * A wrapper for {@link TransportProtocol} instances to allow layering of transport protocols.
+ * A wrapper for {@link Transport} instances to allow layering of {@code Transport}s.
*/
- class Wrapper implements TransportProtocol
+ class Wrapper implements Transport
{
- private final TransportProtocol wrapped;
+ private final Transport wrapped;
- public Wrapper(TransportProtocol wrapped)
+ public Wrapper(Transport wrapped)
{
this.wrapped = Objects.requireNonNull(wrapped);
}
- public TransportProtocol getWrapped()
+ public Transport getWrapped()
{
return wrapped;
}
- public TransportProtocol unwrap()
+ public Transport unwrap()
{
- TransportProtocol result = getWrapped();
+ Transport result = getWrapped();
while (true)
{
if (result instanceof Wrapper wrapper)
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 6a4bcd01a2d8..bd9aa1986894 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
@@ -29,7 +29,7 @@
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.io.Transport;
import org.eclipse.jetty.quic.common.QuicConfiguration;
import org.eclipse.jetty.util.ssl.SslContextFactory;
@@ -40,7 +40,7 @@
* {@link SocketChannelEndPoint}s.
*
* @see QuicConfiguration
- * @deprecated replaced by {@link TransportProtocol}
+ * @deprecated replaced by {@link Transport}
*/
@Deprecated(since = "12.0.7", forRemoval = true)
public class QuicClientConnectorConfigurator extends ClientConnector.Configurator
@@ -97,9 +97,9 @@ protected void doStart() throws Exception
}
@Override
- public TransportProtocol newTransportProtocol()
+ public Transport newTransport()
{
- return new QuicTransportProtocol(quicConfig);
+ return new QuicTransport(quicConfig);
}
@Override
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/QuicTransport.java
similarity index 81%
rename from jetty-core/jetty-quic/jetty-quic-client/src/main/java/org/eclipse/jetty/quic/client/QuicTransportProtocol.java
rename to jetty-core/jetty-quic/jetty-quic-client/src/main/java/org/eclipse/jetty/quic/client/QuicTransport.java
index 6ae1d5da8ea5..a8d0999adae1 100644
--- 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/QuicTransport.java
@@ -20,24 +20,24 @@
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.io.Transport;
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
+ *
A {@link Transport} for QUIC that delegates to another {@code Transport}.
+ * By default, the delegate is {@link Transport#UDP_IP}, but it may be a different
* implementation.
*/
-public class QuicTransportProtocol extends TransportProtocol.Wrapper
+public class QuicTransport extends Transport.Wrapper
{
private final ClientQuicConfiguration quicConfiguration;
- public QuicTransportProtocol(ClientQuicConfiguration quicConfiguration)
+ public QuicTransport(ClientQuicConfiguration quicConfiguration)
{
this(UDP_IP, quicConfiguration);
}
- public QuicTransportProtocol(TransportProtocol wrapped, ClientQuicConfiguration quicConfiguration)
+ public QuicTransport(Transport wrapped, ClientQuicConfiguration quicConfiguration)
{
super(wrapped);
this.quicConfiguration = quicConfiguration;
@@ -74,7 +74,7 @@ public boolean equals(Object obj)
{
if (this == obj)
return true;
- if (obj instanceof QuicTransportProtocol that)
+ if (obj instanceof QuicTransport that)
return Objects.equals(getWrapped(), that.getWrapped());
return false;
}
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/MemoryTransport.java
similarity index 81%
rename from jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/MemoryTransportProtocol.java
rename to jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/MemoryTransport.java
index 9ad145357a45..c2c3e29f84f4 100644
--- 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/MemoryTransport.java
@@ -20,17 +20,17 @@
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.io.Transport;
import org.eclipse.jetty.util.Promise;
/**
- * A {@link TransportProtocol} suitable to be used when using a {@link MemoryConnector}.
+ * A {@link Transport} suitable to be used when using a {@link MemoryConnector}.
*/
-public class MemoryTransportProtocol implements TransportProtocol
+public class MemoryTransport implements Transport
{
private final MemoryConnector connector;
- public MemoryTransportProtocol(MemoryConnector connector)
+ public MemoryTransport(MemoryConnector connector)
{
this.connector = connector;
}
@@ -46,10 +46,10 @@ public void connect(SocketAddress socketAddress, Map context)
ClientConnector clientConnector = (ClientConnector)context.get(ClientConnector.CLIENT_CONNECTOR_CONTEXT_KEY);
endPoint.setIdleTimeout(clientConnector.getIdleTimeout().toMillis());
- // This instance may be nested inside other TransportProtocol instances.
+ // This instance may be nested inside other Transport instances.
// Retrieve the outermost instance to call newConnection().
- TransportProtocol transportProtocol = (TransportProtocol)context.get(TransportProtocol.class.getName());
- Connection connection = transportProtocol.newConnection(endPoint, context);
+ Transport transport = (Transport)context.get(Transport.class.getName());
+ Connection connection = transport.newConnection(endPoint, context);
endPoint.setConnection(connection);
endPoint.onOpen();
@@ -82,7 +82,7 @@ public boolean equals(Object obj)
{
if (this == obj)
return true;
- if (obj instanceof MemoryTransportProtocol that)
+ if (obj instanceof MemoryTransport that)
return Objects.equals(connector, that.connector);
return false;
}
diff --git a/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/AbstractTransportProtocolTest.java b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/AbstractTransportTest.java
similarity index 97%
rename from jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/AbstractTransportProtocolTest.java
rename to jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/AbstractTransportTest.java
index 052296db4cf2..c4b1713bb999 100644
--- a/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/AbstractTransportProtocolTest.java
+++ b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/AbstractTransportTest.java
@@ -26,7 +26,7 @@
import org.junit.jupiter.api.extension.ExtendWith;
@ExtendWith(WorkDirExtension.class)
-public abstract class AbstractTransportProtocolTest
+public abstract class AbstractTransportTest
{
protected Server server;
diff --git a/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/CustomTransportProtocolTest.java b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/CustomTransportTest.java
similarity index 95%
rename from jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/CustomTransportProtocolTest.java
rename to jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/CustomTransportTest.java
index 02ad1e4c5401..ed8e2b9c4837 100644
--- a/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/CustomTransportProtocolTest.java
+++ b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/CustomTransportTest.java
@@ -35,7 +35,7 @@
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.MemoryEndPointPipe;
-import org.eclipse.jetty.io.TransportProtocol;
+import org.eclipse.jetty.io.Transport;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.Request;
@@ -67,7 +67,7 @@
* Remote Server (SSH) - - > In-Memory Gateway -> Response Bytes -> Remote MemoryEndPoint -> HttpClient -> Proxy -> Client
* }
*/
-public class CustomTransportProtocolTest
+public class CustomTransportTest
{
private static final String CONTENT = "CONTENT";
@@ -97,7 +97,7 @@ public void dispose()
}
@Test
- public void testCustomTransportProtocol() throws Exception
+ public void testCustomTransport() throws Exception
{
Gateway gateway = new Gateway();
@@ -109,7 +109,7 @@ public void testCustomTransportProtocol() throws Exception
public boolean handle(Request request, Response response, Callback callback)
{
var gatewayRequest = httpClient.newRequest("http://localhost/")
- .transportProtocol(new GatewayTransportProtocol(httpClient.getScheduler(), gateway))
+ .transport(new GatewayTransport(httpClient.getScheduler(), gateway))
.method(request.getMethod())
.path(request.getHttpURI().getPathQuery())
.timeout(5, TimeUnit.SECONDS);
@@ -164,12 +164,12 @@ public boolean handle(Request request, Response response, Callback callback)
assertThat(response.getContentAsString(), is(CONTENT));
}
- private static class GatewayTransportProtocol implements TransportProtocol
+ private static class GatewayTransport implements Transport
{
private final Scheduler scheduler;
private final Gateway gateway;
- private GatewayTransportProtocol(Scheduler scheduler, Gateway gateway)
+ private GatewayTransport(Scheduler scheduler, Gateway gateway)
{
this.scheduler = scheduler;
this.gateway = gateway;
@@ -195,8 +195,8 @@ public void connect(SocketAddress socketAddress, Map context)
ClientConnector clientConnector = (ClientConnector)context.get(ClientConnector.CLIENT_CONNECTOR_CONTEXT_KEY);
localEndPoint.setIdleTimeout(clientConnector.getIdleTimeout().toMillis());
- TransportProtocol transportProtocol = (TransportProtocol)context.get(TransportProtocol.class.getName());
- Connection connection = transportProtocol.newConnection(localEndPoint, context);
+ Transport transport = (Transport)context.get(Transport.class.getName());
+ Connection connection = transport.newConnection(localEndPoint, context);
localEndPoint.setConnection(connection);
localEndPoint.onOpen();
@@ -219,7 +219,7 @@ public boolean equals(Object obj)
{
if (this == obj)
return true;
- if (obj instanceof GatewayTransportProtocol that)
+ if (obj instanceof GatewayTransport that)
return Objects.equals(gateway, that.gateway);
return false;
}
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/HTTP1TransportTest.java
similarity index 86%
rename from jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HTTP1TransportProtocolTest.java
rename to jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HTTP1TransportTest.java
index e5025b9fbde6..a403cf1eb145 100644
--- 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/HTTP1TransportTest.java
@@ -25,14 +25,14 @@
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.io.Transport;
import org.eclipse.jetty.quic.client.ClientQuicConfiguration;
-import org.eclipse.jetty.quic.client.QuicTransportProtocol;
+import org.eclipse.jetty.quic.client.QuicTransport;
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.MemoryTransport;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.toolchain.test.MavenPaths;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
@@ -46,7 +46,7 @@
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.sameInstance;
-public class HTTP1TransportProtocolTest extends AbstractTransportProtocolTest
+public class HTTP1TransportTest extends AbstractTransportTest
{
private HttpClient httpClient;
@@ -63,7 +63,7 @@ public void prepare()
}
@Test
- public void testDefaultTransportProtocol() throws Exception
+ public void testDefaultTransport() throws Exception
{
ServerConnector connector = new ServerConnector(server, 1, 1, new HttpConnectionFactory());
server.addConnector(connector);
@@ -79,7 +79,7 @@ public void testDefaultTransportProtocol() throws Exception
List destinations = httpClient.getDestinations();
assertThat(destinations.size(), is(1));
Destination destination = destinations.get(0);
- assertThat(destination.getOrigin().getTransportProtocol(), sameInstance(TransportProtocol.TCP_IP));
+ assertThat(destination.getOrigin().getTransport(), sameInstance(Transport.TCP_IP));
HttpClientTransportOverHTTP httpClientTransport = (HttpClientTransportOverHTTP)httpClient.getTransport();
int networkConnections = httpClientTransport.getClientConnector().getSelectorManager().getTotalKeys();
@@ -87,7 +87,7 @@ public void testDefaultTransportProtocol() throws Exception
}
@Test
- public void testExplicitTransportProtocol() throws Exception
+ public void testExplicitTransport() throws Exception
{
ServerConnector connector = new ServerConnector(server, 1, 1, new HttpConnectionFactory());
server.addConnector(connector);
@@ -95,7 +95,7 @@ public void testExplicitTransportProtocol() throws Exception
server.start();
ContentResponse response = httpClient.newRequest("localhost", connector.getLocalPort())
- .transportProtocol(TransportProtocol.TCP_IP)
+ .transport(Transport.TCP_IP)
.timeout(5, TimeUnit.SECONDS)
.send();
@@ -103,7 +103,7 @@ public void testExplicitTransportProtocol() throws Exception
}
@Test
- public void testMemoryTransportProtocol() throws Exception
+ public void testMemoryTransport() throws Exception
{
MemoryConnector connector = new MemoryConnector(server, new HttpConnectionFactory());
server.addConnector(connector);
@@ -111,7 +111,7 @@ public void testMemoryTransportProtocol() throws Exception
server.start();
ContentResponse response = httpClient.newRequest("http://localhost/")
- .transportProtocol(new MemoryTransportProtocol(connector))
+ .transport(new MemoryTransport(connector))
.timeout(5, TimeUnit.SECONDS)
.send();
@@ -123,7 +123,7 @@ public void testMemoryTransportProtocol() throws Exception
}
@Test
- public void testUnixDomainTransportProtocol() throws Exception
+ public void testUnixDomainTransport() throws Exception
{
UnixDomainServerConnector connector = new UnixDomainServerConnector(server, 1, 1, new HttpConnectionFactory());
connector.setUnixDomainPath(newUnixDomainPath());
@@ -132,7 +132,7 @@ public void testUnixDomainTransportProtocol() throws Exception
server.start();
ContentResponse response = httpClient.newRequest("http://localhost/")
- .transportProtocol(new TransportProtocol.TCPUnix(connector.getUnixDomainPath()))
+ .transport(new Transport.TCPUnix(connector.getUnixDomainPath()))
.timeout(5, TimeUnit.SECONDS)
.send();
@@ -140,7 +140,7 @@ public void testUnixDomainTransportProtocol() throws Exception
}
@Test
- public void testQUICTransportProtocol(WorkDir workDir) throws Exception
+ public void testQUICTransport(WorkDir workDir) throws Exception
{
SslContextFactory.Server sslServer = new SslContextFactory.Server();
sslServer.setKeyStorePath(MavenPaths.findTestResourceFile("keystore.p12").toString());
@@ -162,7 +162,7 @@ public void testQUICTransportProtocol(WorkDir workDir) throws Exception
server.start();
ContentResponse response = httpClient.newRequest("localhost", connector.getLocalPort())
- .transportProtocol(new QuicTransportProtocol(clientQuicConfig))
+ .transport(new QuicTransport(clientQuicConfig))
.scheme(HttpScheme.HTTPS.asString())
.timeout(5, TimeUnit.SECONDS)
.send();
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/HTTP2TransportTest.java
similarity index 89%
rename from jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HTTP2TransportProtocolTest.java
rename to jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HTTP2TransportTest.java
index 1c31be7a80da..b44963701dd7 100644
--- 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/HTTP2TransportTest.java
@@ -38,13 +38,13 @@
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.io.Transport;
import org.eclipse.jetty.quic.client.ClientQuicConfiguration;
-import org.eclipse.jetty.quic.client.QuicTransportProtocol;
+import org.eclipse.jetty.quic.client.QuicTransport;
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.MemoryTransport;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.toolchain.test.MavenPaths;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
@@ -59,7 +59,7 @@
import static org.hamcrest.Matchers.sameInstance;
import static org.junit.jupiter.api.Assertions.assertTrue;
-public class HTTP2TransportProtocolTest extends AbstractTransportProtocolTest
+public class HTTP2TransportTest extends AbstractTransportTest
{
private HttpClient httpClient;
private HTTP2Client http2Client;
@@ -78,7 +78,7 @@ public void prepare()
}
@Test
- public void testDefaultTransportProtocol() throws Exception
+ public void testDefaultTransport() throws Exception
{
ServerConnector connector = new ServerConnector(server, 1, 1, new HTTP2CServerConnectionFactory());
server.addConnector(connector);
@@ -94,7 +94,7 @@ public void testDefaultTransportProtocol() throws Exception
List destinations = httpClient.getDestinations();
assertThat(destinations.size(), is(1));
Destination destination = destinations.get(0);
- assertThat(destination.getOrigin().getTransportProtocol(), sameInstance(TransportProtocol.TCP_IP));
+ assertThat(destination.getOrigin().getTransport(), sameInstance(Transport.TCP_IP));
HttpClientTransportOverHTTP2 httpClientTransport = (HttpClientTransportOverHTTP2)httpClient.getTransport();
int networkConnections = httpClientTransport.getHTTP2Client().getClientConnector().getSelectorManager().getTotalKeys();
@@ -102,7 +102,7 @@ public void testDefaultTransportProtocol() throws Exception
}
@Test
- public void testExplicitTransportProtocol() throws Exception
+ public void testExplicitTransport() throws Exception
{
ServerConnector connector = new ServerConnector(server, 1, 1, new HTTP2CServerConnectionFactory());
server.addConnector(connector);
@@ -110,7 +110,7 @@ public void testExplicitTransportProtocol() throws Exception
server.start();
ContentResponse response = httpClient.newRequest("localhost", connector.getLocalPort())
- .transportProtocol(TransportProtocol.TCP_IP)
+ .transport(Transport.TCP_IP)
.timeout(5, TimeUnit.SECONDS)
.send();
@@ -118,7 +118,7 @@ public void testExplicitTransportProtocol() throws Exception
}
@Test
- public void testMemoryTransportProtocol() throws Exception
+ public void testMemoryTransport() throws Exception
{
MemoryConnector connector = new MemoryConnector(server, new HTTP2CServerConnectionFactory());
server.addConnector(connector);
@@ -126,7 +126,7 @@ public void testMemoryTransportProtocol() throws Exception
server.start();
ContentResponse response = httpClient.newRequest("http://localhost/")
- .transportProtocol(new MemoryTransportProtocol(connector))
+ .transport(new MemoryTransport(connector))
.timeout(5, TimeUnit.SECONDS)
.send();
@@ -138,7 +138,7 @@ public void testMemoryTransportProtocol() throws Exception
}
@Test
- public void testUnixDomainTransportProtocol() throws Exception
+ public void testUnixDomainTransport() throws Exception
{
UnixDomainServerConnector connector = new UnixDomainServerConnector(server, 1, 1, new HTTP2CServerConnectionFactory());
connector.setUnixDomainPath(newUnixDomainPath());
@@ -147,7 +147,7 @@ public void testUnixDomainTransportProtocol() throws Exception
server.start();
ContentResponse response = httpClient.newRequest("http://localhost/")
- .transportProtocol(new TransportProtocol.TCPUnix(connector.getUnixDomainPath()))
+ .transport(new Transport.TCPUnix(connector.getUnixDomainPath()))
.timeout(5, TimeUnit.SECONDS)
.send();
@@ -155,7 +155,7 @@ public void testUnixDomainTransportProtocol() throws Exception
}
@Test
- public void testQUICTransportProtocolWithH2C(WorkDir workDir) throws Exception
+ public void testQUICTransportWithH2C(WorkDir workDir) throws Exception
{
SslContextFactory.Server sslServer = new SslContextFactory.Server();
sslServer.setKeyStorePath(MavenPaths.findTestResourceFile("keystore.p12").toString());
@@ -177,7 +177,7 @@ public void testQUICTransportProtocolWithH2C(WorkDir workDir) throws Exception
server.start();
ContentResponse response = httpClient.newRequest("localhost", connector.getLocalPort())
- .transportProtocol(new QuicTransportProtocol(clientQuicConfig))
+ .transport(new QuicTransport(clientQuicConfig))
.timeout(5, TimeUnit.SECONDS)
.send();
@@ -185,7 +185,7 @@ public void testQUICTransportProtocolWithH2C(WorkDir workDir) throws Exception
}
@Test
- public void testQUICTransportProtocolWithH2(WorkDir workDir) throws Exception
+ public void testQUICTransportWithH2(WorkDir workDir) throws Exception
{
SslContextFactory.Server sslServer = new SslContextFactory.Server();
sslServer.setKeyStorePath(MavenPaths.findTestResourceFile("keystore.p12").toString());
@@ -210,7 +210,7 @@ public void testQUICTransportProtocolWithH2(WorkDir workDir) throws Exception
server.start();
ContentResponse response = httpClient.newRequest("localhost", connector.getLocalPort())
- .transportProtocol(new QuicTransportProtocol(clientQuicConfig))
+ .transport(new QuicTransport(clientQuicConfig))
.scheme(HttpScheme.HTTPS.asString())
.timeout(5, TimeUnit.SECONDS)
.send();
@@ -253,7 +253,7 @@ public void testLowLevelH2COverMemory() throws Exception
server.setHandler(new EmptyServerHandler());
server.start();
- Session session = http2Client.connect(new MemoryTransportProtocol(connector), null, connector.getLocalSocketAddress(), new Session.Listener() {}).get(5, TimeUnit.SECONDS);
+ Session session = http2Client.connect(new MemoryTransport(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);
@@ -280,7 +280,7 @@ public void testLowLevelH2COverUnixDomain() throws Exception
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);
+ Session session = http2Client.connect(new Transport.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);
@@ -322,7 +322,7 @@ public void testLowLevelH2COverQUIC(WorkDir workDir) throws Exception
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);
+ Session session = http2Client.connect(new QuicTransport(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);
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/HTTP3TransportTest.java
similarity index 87%
rename from jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HTTP3TransportProtocolTest.java
rename to jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HTTP3TransportTest.java
index 9e8e9aeea57d..8d020b0aeae0 100644
--- 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/HTTP3TransportTest.java
@@ -35,14 +35,14 @@
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.io.Transport;
import org.eclipse.jetty.quic.client.ClientQuicConfiguration;
-import org.eclipse.jetty.quic.client.QuicTransportProtocol;
+import org.eclipse.jetty.quic.client.QuicTransport;
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.MemoryTransport;
import org.eclipse.jetty.toolchain.test.MavenPaths;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
import org.eclipse.jetty.util.ssl.SslContextFactory;
@@ -56,7 +56,7 @@
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
-public class HTTP3TransportProtocolTest extends AbstractTransportProtocolTest
+public class HTTP3TransportTest extends AbstractTransportTest
{
private SslContextFactory.Server sslServer;
private Path pemServerDir;
@@ -86,7 +86,7 @@ public void prepare(WorkDir workDir) throws Exception
}
@Test
- public void testDefaultTransportProtocol() throws Exception
+ public void testDefaultTransport() throws Exception
{
ServerQuicConfiguration serverQuicConfig = new ServerQuicConfiguration(sslServer, pemServerDir);
QuicServerConnector connector = new QuicServerConnector(server, serverQuicConfig, new HTTP3ServerConnectionFactory(serverQuicConfig));
@@ -103,10 +103,10 @@ public void testDefaultTransportProtocol() throws Exception
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));
+ Transport transport = destination.getOrigin().getTransport();
+ if (transport instanceof Transport.Wrapper wrapper)
+ transport = wrapper.unwrap();
+ assertThat(transport, sameInstance(Transport.UDP_IP));
HttpClientTransportOverHTTP3 httpClientTransport = (HttpClientTransportOverHTTP3)httpClient.getTransport();
int networkConnections = httpClientTransport.getHTTP3Client().getClientConnector().getSelectorManager().getTotalKeys();
@@ -114,7 +114,7 @@ public void testDefaultTransportProtocol() throws Exception
}
@Test
- public void testExplicitTransportProtocol() throws Exception
+ public void testExplicitTransport() throws Exception
{
ServerQuicConfiguration serverQuicConfig = new ServerQuicConfiguration(sslServer, pemServerDir);
QuicServerConnector connector = new QuicServerConnector(server, serverQuicConfig, new HTTP3ServerConnectionFactory(serverQuicConfig));
@@ -123,7 +123,7 @@ public void testExplicitTransportProtocol() throws Exception
server.start();
ContentResponse response = httpClient.newRequest("localhost", connector.getLocalPort())
- .transportProtocol(new QuicTransportProtocol(http3Client.getQuicConfiguration()))
+ .transport(new QuicTransport(http3Client.getQuicConfiguration()))
.timeout(5, TimeUnit.SECONDS)
.send();
@@ -131,7 +131,7 @@ public void testExplicitTransportProtocol() throws Exception
}
@Test
- public void testMemoryTransportProtocol() throws Exception
+ public void testMemoryTransport() throws Exception
{
ServerQuicConfiguration quicConfiguration = new ServerQuicConfiguration(sslServer, pemServerDir);
QuicServerConnectionFactory quic = new QuicServerConnectionFactory(quicConfiguration);
@@ -142,7 +142,7 @@ public void testMemoryTransportProtocol() throws Exception
server.start();
ContentResponse response = httpClient.newRequest("http://localhost/")
- .transportProtocol(new QuicTransportProtocol(new MemoryTransportProtocol(connector), http3Client.getQuicConfiguration()))
+ .transport(new QuicTransport(new MemoryTransport(connector), http3Client.getQuicConfiguration()))
.timeout(5, TimeUnit.SECONDS)
.send();
@@ -154,7 +154,7 @@ public void testMemoryTransportProtocol() throws Exception
}
@Test
- public void testUnixDomainTransportProtocol()
+ public void testUnixDomainTransport()
{
noUnixDomainForDatagramChannel();
}
@@ -198,8 +198,8 @@ public void testLowLevelH3OverMemory() throws Exception
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);
+ Transport transport = new QuicTransport(new MemoryTransport(connector), http3Client.getQuicConfiguration());
+ Session.Client session = http3Client.connect(transport, 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);
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/HTTPDynamicTransportTest.java
similarity index 95%
rename from jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HTTPDynamicTransportProtocolTest.java
rename to jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HTTPDynamicTransportTest.java
index 0326949896b7..4fa820bcc601 100644
--- 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/HTTPDynamicTransportTest.java
@@ -47,9 +47,9 @@
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.io.Transport;
import org.eclipse.jetty.quic.client.ClientQuicConfiguration;
-import org.eclipse.jetty.quic.client.QuicTransportProtocol;
+import org.eclipse.jetty.quic.client.QuicTransport;
import org.eclipse.jetty.quic.server.QuicServerConnectionFactory;
import org.eclipse.jetty.quic.server.QuicServerConnector;
import org.eclipse.jetty.quic.server.ServerQuicConfiguration;
@@ -58,7 +58,7 @@
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.MemoryTransport;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.ServerConnector;
@@ -80,7 +80,7 @@
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
-public class HTTPDynamicTransportProtocolTest extends AbstractTransportProtocolTest
+public class HTTPDynamicTransportTest extends AbstractTransportTest
{
private SslContextFactory.Server sslServer;
private Path pemServerDir;
@@ -380,7 +380,7 @@ public void testHighLevelH1OverUNIX() throws Exception
server.start();
ContentResponse response = httpClient.newRequest("localhost", tcp.getLocalPort())
- .transportProtocol(new TransportProtocol.TCPUnix(unixDomainPath))
+ .transport(new Transport.TCPUnix(unixDomainPath))
.timeout(5, TimeUnit.SECONDS)
.send();
@@ -409,9 +409,9 @@ public void testLowLevelH2OverUNIX() throws Exception
server.start();
- TransportProtocol.TCPUnix transportProtocol = new TransportProtocol.TCPUnix(unixDomainPath);
+ Transport.TCPUnix transport = new Transport.TCPUnix(unixDomainPath);
Promise.Completable promise = new Promise.Completable<>();
- http2Client.connect(transportProtocol, null, new HTTP2ClientConnectionFactory(), new Session.Listener() {}, promise, null);
+ http2Client.connect(transport, null, new HTTP2ClientConnectionFactory(), new Session.Listener() {}, promise, null);
Session session = promise.get(5, TimeUnit.SECONDS);
CountDownLatch responseLatch = new CountDownLatch(1);
@@ -445,7 +445,7 @@ public void testHighLevelH1OverMemory() throws Exception
server.start();
ContentResponse response = httpClient.newRequest("http://localhost/")
- .transportProtocol(new MemoryTransportProtocol(local))
+ .transport(new MemoryTransport(local))
.timeout(5, TimeUnit.SECONDS)
.send();
@@ -476,10 +476,10 @@ public void testHighLevelH2OverQUIC(WorkDir workDir) throws Exception
server.start();
ClientQuicConfiguration clientQuicConfiguration = new ClientQuicConfiguration(sslClient, null);
- QuicTransportProtocol transportProtocol = new QuicTransportProtocol(clientQuicConfiguration);
+ QuicTransport transport = new QuicTransport(clientQuicConfiguration);
ContentResponse response = httpClient.newRequest("localhost", connector.getLocalPort())
- .transportProtocol(transportProtocol)
+ .transport(transport)
.timeout(5, TimeUnit.SECONDS)
.send();
@@ -508,10 +508,10 @@ public void testHighLevelH3OverMemory(WorkDir workDir) throws Exception
server.start();
- TransportProtocol transportProtocol = new QuicTransportProtocol(new MemoryTransportProtocol(connector), http3Client.getQuicConfiguration());
+ Transport transport = new QuicTransport(new MemoryTransport(connector), http3Client.getQuicConfiguration());
ContentResponse response = httpClient.newRequest("https://localhost/")
- .transportProtocol(transportProtocol)
+ .transport(transport)
.timeout(5, TimeUnit.SECONDS)
.send();
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 24d230bb9405..65fa6b8b9e39 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
@@ -30,7 +30,7 @@
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.io.Transport;
import org.eclipse.jetty.server.ConnectionFactory;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConnectionFactory;
@@ -159,14 +159,14 @@ public boolean handle(Request request, Response response, Callback callback)
new Origin.Address("localhost", fakeProxyPort),
null,
new Origin.Protocol(List.of("http/1.1"), false),
- new TransportProtocol.TCPUnix(unixDomainPath)
+ new Transport.TCPUnix(unixDomainPath)
);
httpClient.getProxyConfiguration().addProxy(new HttpProxy(proxyOrigin, null));
httpClient.start();
try
{
ContentResponse response = httpClient.newRequest("localhost", fakeServerPort)
- .transportProtocol(new TransportProtocol.TCPUnix(unixDomainPath))
+ .transport(new Transport.TCPUnix(unixDomainPath))
.timeout(5, TimeUnit.SECONDS)
.send();
@@ -222,7 +222,7 @@ else if ("/v2".equals(target))
// 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))
+ .transport(new Transport.TCPUnix(unixDomainPath))
.path("/v1")
.tag(new V1.Tag())
.timeout(5, TimeUnit.SECONDS)
@@ -233,7 +233,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))
+ .transport(new Transport.TCPUnix(unixDomainPath))
.path("/v2")
.tag(tag)
.timeout(5, TimeUnit.SECONDS)
diff --git a/jetty-ee10/jetty-ee10-fcgi-proxy/src/main/java/org/eclipse/jetty/ee10/fcgi/proxy/FastCGIProxyServlet.java b/jetty-ee10/jetty-ee10-fcgi-proxy/src/main/java/org/eclipse/jetty/ee10/fcgi/proxy/FastCGIProxyServlet.java
index b6762bdb5dd6..cca9ac8bf13e 100644
--- a/jetty-ee10/jetty-ee10-fcgi-proxy/src/main/java/org/eclipse/jetty/ee10/fcgi/proxy/FastCGIProxyServlet.java
+++ b/jetty-ee10/jetty-ee10-fcgi-proxy/src/main/java/org/eclipse/jetty/ee10/fcgi/proxy/FastCGIProxyServlet.java
@@ -39,7 +39,7 @@
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.io.ClientConnector;
-import org.eclipse.jetty.io.TransportProtocol;
+import org.eclipse.jetty.io.Transport;
import org.eclipse.jetty.util.ProcessorUtils;
/**
@@ -217,7 +217,7 @@ protected void sendProxyRequest(HttpServletRequest request, HttpServletResponse
Path unixDomain = unixDomainPath;
if (unixDomain != null)
- proxyRequest.transportProtocol(new TransportProtocol.TCPUnix(unixDomain));
+ proxyRequest.transport(new Transport.TCPUnix(unixDomain));
super.sendProxyRequest(request, proxyResponse, proxyRequest);
}
diff --git a/jetty-ee9/jetty-ee9-fcgi-proxy/src/main/java/org/eclipse/jetty/ee9/fcgi/proxy/FastCGIProxyServlet.java b/jetty-ee9/jetty-ee9-fcgi-proxy/src/main/java/org/eclipse/jetty/ee9/fcgi/proxy/FastCGIProxyServlet.java
index 103e31f8303e..5b07c578f25c 100644
--- a/jetty-ee9/jetty-ee9-fcgi-proxy/src/main/java/org/eclipse/jetty/ee9/fcgi/proxy/FastCGIProxyServlet.java
+++ b/jetty-ee9/jetty-ee9-fcgi-proxy/src/main/java/org/eclipse/jetty/ee9/fcgi/proxy/FastCGIProxyServlet.java
@@ -39,7 +39,7 @@
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.io.ClientConnector;
-import org.eclipse.jetty.io.TransportProtocol;
+import org.eclipse.jetty.io.Transport;
import org.eclipse.jetty.util.ProcessorUtils;
/**
@@ -217,7 +217,7 @@ protected void sendProxyRequest(HttpServletRequest request, HttpServletResponse
Path unixDomain = unixDomainPath;
if (unixDomain != null)
- proxyRequest.transportProtocol(new TransportProtocol.TCPUnix(unixDomain));
+ proxyRequest.transport(new Transport.TCPUnix(unixDomain));
super.sendProxyRequest(request, proxyResponse, proxyRequest);
}
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 8d1a0dca7e14..d477b1c6f8a6 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
@@ -54,7 +54,7 @@
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.Transport;
import org.eclipse.jetty.io.content.ByteBufferContentSource;
import org.eclipse.jetty.quic.client.ClientQuicConfiguration;
import org.eclipse.jetty.tests.testers.JettyHomeTester;
@@ -998,7 +998,7 @@ public void testUnixDomain() throws Exception
client = new HttpClient(new HttpClientTransportDynamic(connector, HttpClientConnectionFactory.HTTP11));
client.start();
ContentResponse response = client.newRequest("http://localhost/path")
- .transportProtocol(new TransportProtocol.TCPUnix(path))
+ .transport(new Transport.TCPUnix(path))
.send();
assertEquals(HttpStatus.NOT_FOUND_404, response.getStatus());
}
From a9ad7e76e0bc45bc68e50f22681b5f8c5f5440ba Mon Sep 17 00:00:00 2001
From: Simone Bordet
Date: Tue, 20 Feb 2024 16:15:55 +0100
Subject: [PATCH 12/13] Improved documentation.
Signed-off-by: Simone Bordet
---
.../programming-guide/client/client-io-arch.adoc | 12 ++++++------
.../client/http/client-http-api.adoc | 2 +-
.../client/http/client-http-transport.adoc | 4 ++--
3 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/client/client-io-arch.adoc b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/client/client-io-arch.adoc
index ee2d930f3e1f..9b3405a84400 100644
--- a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/client/client-io-arch.adoc
+++ b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/client/client-io-arch.adoc
@@ -36,7 +36,7 @@ The same high-level protocol can be carried by different low-level transports.
For example, the high-level HTTP/1.1 protocol can be transported over either TCP (the default), or QUIC, or Unix-Domain sockets, or memory, because all these low-level transport provide reliable and ordered communication between client and server.
Similarly, the high-level HTTP/3 protocol can be transported over either QUIC (the default) or memory.
-It would be possible to transport HTTP/3 also over Unix-Domain sockets, but the current version of Java only supports Unix-Domain sockets for `SocketChannel` and not for `DatagramChannel`.
+It would be possible to transport HTTP/3 also over Unix-Domain sockets, but the current version of Java only supports Unix-Domain sockets for ``SocketChannel``s and not for ``DatagramChannel``s.
The Jetty client libraries use the common I/O design described in xref:pg-arch-io[this section].
@@ -47,7 +47,7 @@ The client-side abstraction for the low-level transport is `org.eclipse.jetty.io
`Transport` represents how high-level protocols can be transported; there is `Transport.TCP_IP` that represents communication over TCP, but also `Transport.TCPUnix` for Unix-Domain sockets, `QuicTransport` for QUIC and `MemoryTransport` for memory.
-Applications can specify the `Transport` to use for each request as described in xref:pg-client-http-api-protocol[this section].
+Applications can specify the `Transport` to use for each request as described in xref:pg-client-http-api-transport[this section].
When the `Transport` implementation uses the network, it delegates to `org.eclipse.jetty.io.ClientConnector`.
@@ -88,7 +88,7 @@ Since `ClientConnector` is the component that handles the low-level network tran
The most common parameters are:
-* `ClientConnector.selectors`: the number of ``java.nio.Selector``s components (defaults to `1`) that are present to handle the ``SocketChannel``s opened by the `ClientConnector`.
+* `ClientConnector.selectors`: the number of ``java.nio.Selector``s components (defaults to `1`) that are present to handle the ``SocketChannel``s and ``DatagramChannel``s opened by the `ClientConnector`.
You typically want to increase the number of selectors only for those use cases where each selector should handle more than few hundreds _concurrent_ socket events.
For example, one selector typically runs well for `250` _concurrent_ socket events; as a rule of thumb, you can multiply that number by `10` to obtain the number of opened sockets a selector can handle (`2500`), based on the assumption that not all the `2500` sockets will be active _at the same time_.
* `ClientConnector.idleTimeout`: the duration of time after which `ClientConnector` closes a socket due to inactivity (defaults to `30` seconds).
@@ -105,11 +105,11 @@ Please refer to the `ClientConnector` link:{javadoc-url}/org/eclipse/jetty/io/Cl
[[pg-client-io-arch-unix-domain]]
===== Unix-Domain Support
-link:https://openjdk.java.net/jeps/380[JEP 380] introduced Unix-Domain sockets support in Java 16, on all operative systems, for `SocketChannel`.
+link:https://openjdk.java.net/jeps/380[JEP 380] introduced Unix-Domain sockets support in Java 16, on all operative systems, but only for ``SocketChannel``s (not for ``DatagramChannel``s).
`ClientConnector` handles Unix-Domain sockets exactly like it handles regular TCP sockets, so there is no additional configuration necessary -- Unix-Domain sockets are supported out-of-the-box.
-Applications can specify the `Transport` to use for each request as described in xref:pg-client-http-api-protocol[this section].
+Applications can specify the `Transport` to use for each request as described in xref:pg-client-http-api-transport[this section].
[[pg-client-io-arch-memory]]
===== Memory Support
@@ -119,7 +119,7 @@ This means that the client and server must be in the same JVM process.
This functionality is provided by `org.eclipse.jetty.server.MemoryTransport`, which does not delegate to `ClientConnector`, but instead delegates to the server-side `MemoryConnector` and its related classes.
-Applications can specify the `Transport` to use for each request as described in xref:pg-client-http-api-protocol[this section].
+Applications can specify the `Transport` to use for each request as described in xref:pg-client-http-api-transport[this section].
[[pg-client-io-arch-protocol]]
==== Protocol Layer
diff --git a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/client/http/client-http-api.adoc b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/client/http/client-http-api.adoc
index 56513dc3ec7e..6339e011cd2a 100644
--- a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/client/http/client-http-api.adoc
+++ b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/client/http/client-http-api.adoc
@@ -271,7 +271,7 @@ An application that implements a forwarder between two servers can be implemente
include::../../{doc_code}/org/eclipse/jetty/docs/programming/client/http/HTTPClientDocs.java[tag=forwardContent]
----
-[[pg-client-http-api-protocol]]
+[[pg-client-http-api-transport]]
===== Request `Transport`
The communication between client and server happens over a xref:pg-client-io-arch-transport[low-level transport], and applications can specify the low-level transport to use for each request.
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 7362000e8fdf..a06a69284d85 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
@@ -16,7 +16,7 @@
Jetty's `HttpClient` can be configured to use different HTTP formats to carry the semantic of HTTP requests and responses, by specifying different `HttpClientTransport` implementations.
-This means that the intention of a client to request resource `/index.html` using the `GET` method can be carried over a xref:pg-client-http-api-protocol[low-level transport] in different formats.
+This means that the intention of a client to request resource `/index.html` using the `GET` method can be carried over a xref:pg-client-http-api-transport[low-level transport] in different formats.
An `HttpClientTransport` is the component that is in charge of converting a high-level, semantic, HTTP requests such as " ``GET`` resource ``/index.html`` " into the specific format understood by the server (for example, HTTP/2 or HTTP/3), and to convert the server response from the specific format (HTTP/2 or HTTP/3) into high-level, semantic objects that can be used by applications.
@@ -58,7 +58,7 @@ A request for a resource may be sent using one protocol (for example, HTTP/1.1),
`HttpClient` also supports `HttpClientTransportDynamic`, a xref:pg-client-http-transport-dynamic[dynamic transport] that can speak different HTTP formats and can select the right protocol by negotiating it with the server or by explicit indication from applications.
-Furthermore, every HTTP format can be sent over different xref:pg-client-http-api-protocol[low-level transports] such as TCP, Unix-Domain, QUIC or memory.
+Furthermore, every HTTP format can be sent over different xref:pg-client-http-api-transport[low-level transports] such as TCP, Unix-Domain, QUIC or memory.
Supports for Unix-Domain sockets requires Java 16 or later, since Unix-Domain sockets support has been introduced in OpenJDK with link:https://openjdk.java.net/jeps/380[JEP 380].
Applications are typically not aware of the actual HTTP format or low-level transport being used.
From 0306cd78fd1e7de1712c723b27e00f9200f3d8a3 Mon Sep 17 00:00:00 2001
From: Simone Bordet
Date: Mon, 26 Feb 2024 17:20:07 +0100
Subject: [PATCH 13/13] Renamed Transport.requiresDomainNamesResolution() ->
requiresDomainNameResolution(). Marked HttpDestination constructor as
deprecated for removal.
Signed-off-by: Simone Bordet
---
.../main/java/org/eclipse/jetty/client/HttpClient.java | 2 +-
.../jetty/client/transport/HttpDestination.java | 2 +-
.../src/main/java/org/eclipse/jetty/io/Transport.java | 10 +++++-----
3 files changed, 7 insertions(+), 7 deletions(-)
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 145ad68d90e5..100376aff51d 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
@@ -544,7 +544,7 @@ public void newConnection(Destination destination, Promise promise)
Transport transport = origin.getTransport();
context.put(Transport.class.getName(), transport);
- if (transport.requiresDomainNamesResolution())
+ if (transport.requiresDomainNameResolution())
{
Origin.Address address = origin.getAddress();
getSocketAddressResolver().resolve(address.getHost(), address.getPort(), new Promise<>()
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 ea93e39e7190..8ba6035f2e51 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
@@ -76,7 +76,7 @@ public class HttpDestination extends ContainerLifeCycle implements Destination,
* @param intrinsicallySecure whether the destination is intrinsically secure
* @deprecated use {@link #HttpDestination(HttpClient, Origin)} instead
*/
- @Deprecated
+ @Deprecated(since = "12.0.7", forRemoval = true)
public HttpDestination(HttpClient client, Origin origin, boolean intrinsicallySecure)
{
this(client, origin);
diff --git a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/Transport.java b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/Transport.java
index 0caef553c566..a46ebf1a84a1 100644
--- a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/Transport.java
+++ b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/Transport.java
@@ -80,14 +80,14 @@ default boolean isIntrinsicallySecure()
*
* @return whether this {@code Transport} requires domain names resolution
*/
- default boolean requiresDomainNamesResolution()
+ default boolean requiresDomainNameResolution()
{
return false;
}
/**
* Establishes a connection to the given socket address.
- * For {@code Transport}s that {@link #requiresDomainNamesResolution()
+ *
For {@code Transport}s that {@link #requiresDomainNameResolution()
* require domain name resolution}, this is the IP address resolved from
* the domain name.
* For {@code Transport}s that do not require domain name resolution
@@ -188,7 +188,7 @@ public String toString()
abstract class IP extends Socket
{
@Override
- public boolean requiresDomainNamesResolution()
+ public boolean requiresDomainNameResolution()
{
return true;
}
@@ -364,9 +364,9 @@ public boolean isIntrinsicallySecure()
}
@Override
- public boolean requiresDomainNamesResolution()
+ public boolean requiresDomainNameResolution()
{
- return wrapped.requiresDomainNamesResolution();
+ return wrapped.requiresDomainNameResolution();
}
@Override