From 21f2f2acea41c4aaea561a8da62308cf0da6d999 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Mon, 26 Aug 2024 12:10:04 +0300 Subject: [PATCH] Fixes #3553 - Support sslSession() in Jetty Client. (#12179) Implemented Connection.getSslSessionData(), where the Connection can be obtained from the Request: request.getConnection().getSslSessionData(). Updated documentation. Signed-off-by: Simone Bordet --- .../client/http/HTTPClientDocs.java | 28 ++++++++++++++++++ .../programming-guide/pages/client/http.adoc | 29 ++++++++++++++++++- .../org/eclipse/jetty/client/Connection.java | 10 +++++++ .../org/eclipse/jetty/client/HttpProxy.java | 6 ++++ .../internal/HttpConnectionOverHTTP.java | 12 ++++++++ .../internal/HttpConnectionOverFCGI.java | 12 ++++++++ .../internal/HttpConnectionOverHTTP2.java | 7 +++++ .../internal/HttpConnectionOverHTTP3.java | 9 ++++++ .../test/client/transport/HttpClientTest.java | 14 ++++++++- 9 files changed, 125 insertions(+), 2 deletions(-) diff --git a/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/client/http/HTTPClientDocs.java b/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/client/http/HTTPClientDocs.java index 693e23aa4895..1782d1de9f12 100644 --- a/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/client/http/HTTPClientDocs.java +++ b/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/client/http/HTTPClientDocs.java @@ -17,6 +17,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.net.SocketAddress; import java.net.URI; import java.nio.ByteBuffer; import java.nio.file.Path; @@ -34,6 +35,7 @@ import org.eclipse.jetty.client.BufferingResponseListener; import org.eclipse.jetty.client.BytesRequestContent; import org.eclipse.jetty.client.CompletableResponseListener; +import org.eclipse.jetty.client.Connection; import org.eclipse.jetty.client.ConnectionPool; import org.eclipse.jetty.client.ContentResponse; import org.eclipse.jetty.client.Destination; @@ -72,6 +74,7 @@ import org.eclipse.jetty.io.ClientConnectionFactory; import org.eclipse.jetty.io.ClientConnector; import org.eclipse.jetty.io.Content; +import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.Transport; import org.eclipse.jetty.io.ssl.SslHandshakeListener; import org.eclipse.jetty.quic.client.ClientQuicConfiguration; @@ -1180,4 +1183,29 @@ public void mixedTransports() throws Exception .send(); // end::mixedTransports[] } + + public void connectionInformation() throws Exception + { + // tag::connectionInformation[] + HttpClient httpClient = new HttpClient(); + httpClient.start(); + + ContentResponse response = httpClient.newRequest("http://domain.com/path") + // The connection information is only available starting from the request begin event. + .onRequestBegin(request -> + { + Connection connection = request.getConnection(); + + // Obtain the address of the server. + SocketAddress remoteAddress = connection.getRemoteSocketAddress(); + System.getLogger("connection").log(INFO, "Server address: %s", remoteAddress); + + // Obtain the SslSessionData. + EndPoint.SslSessionData sslSessionData = connection.getSslSessionData(); + if (sslSessionData != null) + System.getLogger("connection").log(INFO, "SslSessionData: %s", sslSessionData); + }) + .send(); + // end::connectionInformation[] + } } diff --git a/documentation/jetty/modules/programming-guide/pages/client/http.adoc b/documentation/jetty/modules/programming-guide/pages/client/http.adoc index 38fef9c2f889..e09b2ef1ea60 100644 --- a/documentation/jetty/modules/programming-guide/pages/client/http.adoc +++ b/documentation/jetty/modules/programming-guide/pages/client/http.adoc @@ -235,7 +235,12 @@ A second request with the same origin sent _after_ the first request/response cy A second request with the same origin sent _concurrently_ with the first request will likely cause the opening of a second connection, depending on the connection pool implementation. The configuration parameter `HttpClient.maxConnectionsPerDestination` (see also the <>) controls the max number of connections that can be opened for a destination. -NOTE: If opening connections to a given origin takes a long time, then requests for that origin will queue up in the corresponding destination until the connections are established. +[NOTE] +==== +If opening connections to a given origin takes a long time, then requests for that origin will queue up in the corresponding destination until the connections are established. + +To save the time spent opening connections, you can xref:connection-pool-precreate-connections[pre-create connections]. +==== Each connection can handle a limited number of concurrent requests. For HTTP/1.1, this number is always `1`: there can only be one outstanding request for each connection. @@ -528,6 +533,28 @@ This is a fancy example of how to mix HTTP versions and low-level transports: include::code:example$src/main/java/org/eclipse/jetty/docs/programming/client/http/HTTPClientDocs.java[tag=mixedTransports] ---- +[[connection-information]] +=== Request Connection Information + +In order to send a request, it is necessary to obtain a connection, as explained in the xref:request-processing[request processing section]. + +The HTTP/1.1 protocol may send only one request at a time on a single connection, while multiplexed protocols such as HTTP/2 may send many requests at a time on a single connection. + +You can access the connection information, for example the local and remote `SocketAddress`, or the `SslSessionData` if the connection is secured, in the following way: + +[,java,indent=0] +---- +include::code:example$src/main/java/org/eclipse/jetty/docs/programming/client/http/HTTPClientDocs.java[tag=connectionInformation] +---- + +[NOTE] +==== +The connection information is only available when the request is associated with a connection. + +This means that the connection is not available in the _request queued_ event, but only starting from the _request begin_ event. +For more information about request events, see xref:non-blocking[this section]. +==== + [[configuration]] == HttpClient Configuration diff --git a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/Connection.java b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/Connection.java index 981a8d00d84e..400f6af9801b 100644 --- a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/Connection.java +++ b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/Connection.java @@ -16,6 +16,7 @@ import java.io.Closeable; import java.net.SocketAddress; +import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.util.Promise; /** @@ -63,4 +64,13 @@ default SocketAddress getRemoteSocketAddress() { return null; } + + /** + * @return the {@link EndPoint.SslSessionData} associated with + * the connection, or {@code null} if the connection is not secure. + */ + default EndPoint.SslSessionData getSslSessionData() + { + return null; + } } 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 8187a32e8738..d0a43da40c11 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 @@ -346,6 +346,12 @@ public SocketAddress getRemoteSocketAddress() return connection.getRemoteSocketAddress(); } + @Override + public EndPoint.SslSessionData getSslSessionData() + { + return connection.getSslSessionData(); + } + @Override public void send(Request request, Response.CompleteListener listener) { diff --git a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/transport/internal/HttpConnectionOverHTTP.java b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/transport/internal/HttpConnectionOverHTTP.java index e9913fa7c5ad..018fe81df0cc 100644 --- a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/transport/internal/HttpConnectionOverHTTP.java +++ b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/transport/internal/HttpConnectionOverHTTP.java @@ -116,6 +116,12 @@ public SocketAddress getRemoteSocketAddress() return delegate.getRemoteSocketAddress(); } + @Override + public EndPoint.SslSessionData getSslSessionData() + { + return delegate.getSslSessionData(); + } + @Override public long getBytesIn() { @@ -350,6 +356,12 @@ public SocketAddress getRemoteSocketAddress() return getEndPoint().getRemoteSocketAddress(); } + @Override + public EndPoint.SslSessionData getSslSessionData() + { + return getEndPoint().getSslSessionData(); + } + @Override public SendFailure send(HttpExchange exchange) { diff --git a/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/transport/internal/HttpConnectionOverFCGI.java b/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/transport/internal/HttpConnectionOverFCGI.java index 24fbcf4b80ea..0309c2872b68 100644 --- a/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/transport/internal/HttpConnectionOverFCGI.java +++ b/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/transport/internal/HttpConnectionOverFCGI.java @@ -101,6 +101,12 @@ public SocketAddress getRemoteSocketAddress() return delegate.getRemoteSocketAddress(); } + @Override + public EndPoint.SslSessionData getSslSessionData() + { + return delegate.getSslSessionData(); + } + protected Flusher getFlusher() { return flusher; @@ -359,6 +365,12 @@ public SocketAddress getRemoteSocketAddress() return getEndPoint().getRemoteSocketAddress(); } + @Override + public EndPoint.SslSessionData getSslSessionData() + { + return getEndPoint().getSslSessionData(); + } + @Override public SendFailure send(HttpExchange exchange) { diff --git a/jetty-core/jetty-http2/jetty-http2-client-transport/src/main/java/org/eclipse/jetty/http2/client/transport/internal/HttpConnectionOverHTTP2.java b/jetty-core/jetty-http2/jetty-http2-client-transport/src/main/java/org/eclipse/jetty/http2/client/transport/internal/HttpConnectionOverHTTP2.java index d7957e1e05b6..ade527797cb0 100644 --- a/jetty-core/jetty-http2/jetty-http2-client-transport/src/main/java/org/eclipse/jetty/http2/client/transport/internal/HttpConnectionOverHTTP2.java +++ b/jetty-core/jetty-http2/jetty-http2-client-transport/src/main/java/org/eclipse/jetty/http2/client/transport/internal/HttpConnectionOverHTTP2.java @@ -44,6 +44,7 @@ import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.frames.HeadersFrame; +import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.thread.Sweeper; import org.slf4j.Logger; @@ -85,6 +86,12 @@ public SocketAddress getRemoteSocketAddress() return session.getRemoteSocketAddress(); } + @Override + public EndPoint.SslSessionData getSslSessionData() + { + return connection.getEndPoint().getSslSessionData(); + } + public boolean isRecycleHttpChannels() { return recycleHttpChannels; diff --git a/jetty-core/jetty-http3/jetty-http3-client-transport/src/main/java/org/eclipse/jetty/http3/client/transport/internal/HttpConnectionOverHTTP3.java b/jetty-core/jetty-http3/jetty-http3-client-transport/src/main/java/org/eclipse/jetty/http3/client/transport/internal/HttpConnectionOverHTTP3.java index 2e211bd84c2d..4f9e92910d74 100644 --- a/jetty-core/jetty-http3/jetty-http3-client-transport/src/main/java/org/eclipse/jetty/http3/client/transport/internal/HttpConnectionOverHTTP3.java +++ b/jetty-core/jetty-http3/jetty-http3-client-transport/src/main/java/org/eclipse/jetty/http3/client/transport/internal/HttpConnectionOverHTTP3.java @@ -30,6 +30,8 @@ import org.eclipse.jetty.client.transport.SendFailure; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http3.client.HTTP3SessionClient; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.quic.common.QuicSession; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -64,6 +66,13 @@ public SocketAddress getRemoteSocketAddress() return session.getRemoteSocketAddress(); } + @Override + public EndPoint.SslSessionData getSslSessionData() + { + QuicSession quicSession = getSession().getProtocolSession().getQuicSession(); + return EndPoint.SslSessionData.from(null, null, null, quicSession.getPeerCertificates()); + } + @Override public int getMaxMultiplex() { 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 abe2c3f80e5a..0d3723f14358 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 @@ -33,6 +33,7 @@ import org.eclipse.jetty.client.BytesRequestContent; import org.eclipse.jetty.client.CompletableResponseListener; +import org.eclipse.jetty.client.Connection; import org.eclipse.jetty.client.ContentResponse; import org.eclipse.jetty.client.Destination; import org.eclipse.jetty.client.InputStreamResponseListener; @@ -45,6 +46,7 @@ import org.eclipse.jetty.http.HttpURI; import org.eclipse.jetty.http2.FlowControlStrategy; import org.eclipse.jetty.io.Content; +import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.NetworkConnector; import org.eclipse.jetty.server.Request; @@ -1024,9 +1026,19 @@ public void testRequestConnection(Transport transport) throws Exception ContentResponse response = client.newRequest(newURI(transport)) .onRequestBegin(r -> { - if (r.getConnection() == null) + Connection connection = r.getConnection(); + if (connection == null) r.abort(new IllegalStateException()); }) + .onRequestHeaders(r -> + { + if (transport.isSecure()) + { + EndPoint.SslSessionData sslSessionData = r.getConnection().getSslSessionData(); + if (sslSessionData == null) + r.abort(new IllegalStateException()); + } + }) .send(); assertEquals(200, response.getStatus());