diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-authentication.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-authentication.adoc index fcdd2fc3b01..40a8addb58d 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-authentication.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-authentication.adoc @@ -37,10 +37,10 @@ participant Application participant HttpClient participant Server -Application -> Server: GET /path -Server -> HttpClient: 401 + WWW-Authenticate -HttpClient -> Server: GET + Authentication -Server -> Application: 200 OK +Application -> Server : GET /path +Server -> HttpClient : 401 + WWW-Authenticate +HttpClient -> Server : GET + Authentication +Server -> Application : 200 OK ---- Upon receiving a HTTP 401 response code, `HttpClient` looks at the diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-intro.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-intro.adoc index 6492e3fe1b8..acd412130c8 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-intro.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-intro.adoc @@ -108,8 +108,8 @@ following components: * a set of _destinations_. A _destination_ is the client-side component that represent an _origin_ on -a server, and manages a queue of requests for that origin, and a pool of -connections to that origin. +a server, and manages a queue of requests for that origin, and a +link:#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. @@ -145,9 +145,75 @@ connection pools. Therefore an origin is identified by the tuple `(scheme, host, port, tag, protocol)`. +[[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. + +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 destinations +for the same `host` and `port`. + +You can access the `ConnectionPool` in this way: + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tags=getConnectionPool] +---- + +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). +* `RoundRobinConnectionPool`, similar to `MultiplexConnectionPool` but where +connections are reused with a round-robin algorithm. + +The `ConnectionPool` implementation can be customized for each destination in +by setting a `ConnectionPool.Factory` on the `HttpClientTransport`: + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tags=setConnectionPool] +---- + [[client-http-request-processing]] ==== HttpClient Request Processing +[plantuml] +---- +skinparam backgroundColor transparent +skinparam monochrome true +skinparam shadowing false + +participant Application +participant Request +participant HttpClient +participant Destination +participant ConnectionPool +participant Connection + +Application -> HttpClient : newRequest() +HttpClient -> Request ** +Application -> Request : send() +Request -> HttpClient : send() +HttpClient -> Destination ** : get or create +Destination -> ConnectionPool ** : create +HttpClient -> Destination : send(Request) +Destination -> Destination : enqueue(Request) +Destination -> ConnectionPool : acquire() +ConnectionPool -> Connection ** : create +Destination -> Destination : dequeue(Request) +Destination -> Connection : send(Request) +---- + When a request is sent, an origin is computed from the request; `HttpClient` uses that origin to find (or create if it does not exist) the correspondent destination. @@ -160,8 +226,8 @@ and sends it over the connection. The first request to a destination triggers the opening of the first connection. -A second request with the same origin sent _after_ the first will reuse the -same connection. +A second request with the same origin sent _after_ the first request/response +cycle is completed will reuse the same connection. A second request with the same origin sent _concurrently_ with the first request will cause the opening of a second connection. The configuration parameter `HttpClient.maxConnectionsPerDestination` @@ -171,7 +237,7 @@ 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. -Each connection can handle a limited number of requests. +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. For HTTP/2 this number is determined by the server `max_concurrent_stream` diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-proxy.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-proxy.adoc index 88554725612..cea87ff73a1 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-proxy.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-proxy.adoc @@ -71,17 +71,17 @@ participant HttpClient participant Proxy participant Server -Application -> Proxy: GET /path -Proxy -> HttpClient: 407 + Proxy-Authenticate -HttpClient -> Proxy: GET /path + Proxy-Authorization -Proxy -> Server: GET /path -Server -> Proxy: 401 + WWW-Authenticate -Proxy -> HttpClient: 401 + WWW-Authenticate -HttpClient -> Proxy: GET /path + Proxy-Authorization + Authorization -Proxy -> Server: GET /path + Authorization -Server -> Proxy: 200 OK -Proxy -> HttpClient: 200 OK -HttpClient -> Application: 200 OK +Application -> Proxy : GET /path +Proxy -> HttpClient : 407 + Proxy-Authenticate +HttpClient -> Proxy : GET /path + Proxy-Authorization +Proxy -> Server : GET /path +Server -> Proxy : 401 + WWW-Authenticate +Proxy -> HttpClient : 401 + WWW-Authenticate +HttpClient -> Proxy : GET /path + Proxy-Authorization + Authorization +Proxy -> Server : GET /path + Authorization +Server -> Proxy : 200 OK +Proxy -> HttpClient : 200 OK +HttpClient -> Application : 200 OK ---- The application does not receive events related to the responses with code 407 diff --git a/jetty-documentation/src/main/java/embedded/client/http/HTTPClientDocs.java b/jetty-documentation/src/main/java/embedded/client/http/HTTPClientDocs.java index f72655f916f..60161cf3c21 100644 --- a/jetty-documentation/src/main/java/embedded/client/http/HTTPClientDocs.java +++ b/jetty-documentation/src/main/java/embedded/client/http/HTTPClientDocs.java @@ -31,9 +31,13 @@ import java.util.List; import java.util.concurrent.TimeUnit; import java.util.function.LongConsumer; +import org.eclipse.jetty.client.ConnectionPool; import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.HttpClientTransport; +import org.eclipse.jetty.client.HttpDestination; import org.eclipse.jetty.client.HttpProxy; import org.eclipse.jetty.client.ProxyConfiguration; +import org.eclipse.jetty.client.RoundRobinConnectionPool; import org.eclipse.jetty.client.api.Authentication; import org.eclipse.jetty.client.api.AuthenticationStore; import org.eclipse.jetty.client.api.ContentResponse; @@ -800,4 +804,47 @@ public class HTTPClientDocs .send(); // end::dynamicClearText[] } + + public void getConnectionPool() throws Exception + { + // tag::getConnectionPool[] + HttpClient httpClient = new HttpClient(); + httpClient.start(); + + ConnectionPool connectionPool = httpClient.getDestinations().stream() + // Cast to HttpDestination. + .map(HttpDestination.class::cast) + // Find the destination by filtering on the Origin. + .filter(destination -> destination.getOrigin().getAddress().getHost().equals("domain.com")) + .findAny() + // Get the ConnectionPool. + .map(HttpDestination::getConnectionPool) + .orElse(null); + // end::getConnectionPool[] + } + + public void setConnectionPool() throws Exception + { + // tag::setConnectionPool[] + HttpClient httpClient = new HttpClient(); + httpClient.start(); + + // The max number of connections in the pool. + int maxConnectionsPerDestination = httpClient.getMaxConnectionsPerDestination(); + + // The max number of requests per connection (multiplexing). + // Start with 1, since this value is dynamically set to larger values if + // the transport supports multiplexing requests on the same connection. + int maxRequestsPerConnection = 1; + + HttpClientTransport transport = httpClient.getTransport(); + + // Set the ConnectionPool.Factory using a lambda. + transport.setConnectionPoolFactory(destination -> + new RoundRobinConnectionPool(destination, + maxConnectionsPerDestination, + destination, + maxRequestsPerConnection)); + // end::setConnectionPool[] + } }