From 9a6ad8af62dbe5956465ab9cc071a459c151fbda Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Tue, 31 Mar 2020 17:53:28 +0200 Subject: [PATCH] Improvements to the Jetty client documentation. Signed-off-by: Simone Bordet --- jetty-documentation/pom.xml | 16 + ...ient-concepts.adoc => client-io-arch.adoc} | 123 +++++--- .../embedded-guide/client/client.adoc | 20 +- .../http/client-http-api.adoc} | 0 .../http/client-http-authentication.adoc} | 4 +- .../http/client-http-configuration.adoc | 86 +++++ .../http/client-http-cookie.adoc} | 2 +- .../client/http/client-http-intro.adoc | 175 +++++++++++ .../http/client-http-proxy.adoc} | 8 +- .../http/client-http-transport.adoc} | 0 .../client/http/client-http.adoc | 9 + .../main/asciidoc/embedded-guide/io-arch.adoc | 2 + .../server/clients/http/chapter.adoc | 27 -- .../clients/http/http-client-intro.adoc | 105 ------- .../embedded/client/ClientConnectorDocs.java | 293 +++++++++++++++++- .../embedded/client/http/HTTPClientDocs.java | 82 +++++ .../org/eclipse/jetty/io/ClientConnector.java | 23 +- .../org/eclipse/jetty/io/ManagedSelector.java | 5 +- .../org/eclipse/jetty/io/SelectorManager.java | 4 +- 19 files changed, 781 insertions(+), 203 deletions(-) rename jetty-documentation/src/main/asciidoc/embedded-guide/client/{client-concepts.adoc => client-io-arch.adoc} (54%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server/clients/http/http-client-api.adoc => client/http/client-http-api.adoc} (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server/clients/http/http-client-authentication.adoc => client/http/client-http-authentication.adoc} (97%) create mode 100644 jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-configuration.adoc rename jetty-documentation/src/main/asciidoc/embedded-guide/{server/clients/http/http-client-cookie.adoc => client/http/client-http-cookie.adoc} (99%) create mode 100644 jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-intro.adoc rename jetty-documentation/src/main/asciidoc/embedded-guide/{server/clients/http/http-client-proxy.adoc => client/http/client-http-proxy.adoc} (96%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server/clients/http/http-client-transport.adoc => client/http/client-http-transport.adoc} (100%) delete mode 100644 jetty-documentation/src/main/asciidoc/embedded-guide/server/clients/http/chapter.adoc delete mode 100644 jetty-documentation/src/main/asciidoc/embedded-guide/server/clients/http/http-client-intro.adoc create mode 100644 jetty-documentation/src/main/java/embedded/client/http/HTTPClientDocs.java diff --git a/jetty-documentation/pom.xml b/jetty-documentation/pom.xml index 4661ac8c479..000675ded84 100644 --- a/jetty-documentation/pom.xml +++ b/jetty-documentation/pom.xml @@ -49,6 +49,11 @@ + + org.eclipse.jetty + jetty-client + ${project.version} + org.eclipse.jetty jetty-io @@ -111,6 +116,13 @@ org.asciidoctor asciidoctor-maven-plugin ${asciidoctor.maven.plugin.version} + + + org.asciidoctor + asciidoctorj-diagram + 2.0.1 + + http://www.eclipse.org/jetty/javadoc/${javadoc.version} @@ -183,6 +195,10 @@ ${basedir}/src/main/asciidoc/embedded-guide index.adoc ${project.build.directory}/html/embedded-guide + coderay + + asciidoctor-diagram + diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/client/client-concepts.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/client-io-arch.adoc similarity index 54% rename from jetty-documentation/src/main/asciidoc/embedded-guide/client/client-concepts.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/client/client-io-arch.adoc index bf63bce5013..f2afffd946e 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/client/client-concepts.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/client-io-arch.adoc @@ -16,32 +16,25 @@ // ======================================================================== // -[[client-concepts]] -=== Client Libraries Concepts +[[client-io-arch]] +=== Client Libraries Architecture -The Jetty client libraries implement a network client speaking different protocols -such as HTTP/1.1, HTTP/2, WebSocket and FastCGI. +The Jetty client libraries provide the basic components and APIs to implement +a network client. -It is possible to implement your own custom protocol on top of the Jetty client -libraries. +They build on the common link:#io-arch[Jetty I/O Architecture] and provide client +specific concepts (such as establishing a connection to a server). -NOTE: TODO: perhaps add a section about this. +There are conceptually two layers that compose the Jetty client libraries: -There are conceptually three layers that compose the Jetty client libraries, from -more abstract to more concrete: +. link:#client-io-arch-network[The network layer], that handles the low level +I/O and deals with buffers, threads, etc. +. link:#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. -. The API layer, that exposes semantic APIs to applications so that they can write -code such as "GET me the resource at this URI". -. The protocol layer, where the API request is converted into the appropriate -protocol bytes, for example encrypted HTTP/2. -. The infrastructure layer, that handles the low level I/O and deals with network, -buffer, threads, etc. - -Let's look at these layers starting from the more concrete (and low level) one -and build up to the more abstract layer. - -[[client-concepts-infrastructure]] -==== Client Libraries Infrastructure Layer +[[client-io-arch-network]] +==== Client Libraries Network Layer The Jetty client libraries use the common I/O design described in link:#io-arch[this section]. @@ -78,7 +71,7 @@ include::../{doc_code}/embedded/client/ClientConnectorDocs.java[tags=typical] ---- A more advanced example that customizes the `ClientConnector` by overriding -factory methods: +some of its methods: [source,java,indent=0] ---- @@ -86,7 +79,7 @@ include::../{doc_code}/embedded/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-leven network configuration. +is also the component where you want to configure the low-level network configuration. The most common parameters are: @@ -122,28 +115,82 @@ Please refer to the `ClientConnector` link:{JDURL}/org/eclipse/jetty/io/ClientConnector.html[javadocs] for the complete list of configurable parameters. -Once the `ClientConnector` is configured and started, it can be used to connect -to the server via `ClientConnector.connect(SocketAddress, Map)` -which in turn will call `SocketChannel.connect(SocketAddress)`. +[[client-io-arch-protocol]] +==== Client Libraries Protocol Layer -When establishing a TCP connection to a server, applications need to tell +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. + +Recall from link:#io-arch-connection[this section] that Jetty uses the +`Connection` abstraction to produce and interpret the network 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. -This is done via a -link:{JDURL}/org/eclipse/jetty/io/ClientConnectionFactory.html[`ClientConnectionFactory`]. -that must be passed in the context `Map` as follows: +TCP connection, and how to notify back the application when the connection +creation succeeds or fails. + +This is done by passing a +link:{JDURL}/org/eclipse/jetty/io/ClientConnectionFactory.html[`ClientConnectionFactory`] +(that creates `Connection` instances) and a +link:{JDURL}/org/eclipse/jetty/util/Promise.html[`Promise`] (that is notified +of connection creation success or failure) in the context `Map` as follows: [source,java,indent=0] ---- include::../{doc_code}/embedded/client/ClientConnectorDocs.java[tags=connect] ---- -TODO: expand on what is the API to use, what parameters the context Map must -have, and basically how we can write a generic network client with it. +When a `Connection` is created successfully, its `onOpen()` method is invoked, +and then the promise is completed successfully. -[[client-concepts-protocol]] -==== Client Libraries Protocol Layer +It is now possible to write a super-simple `telnet` client that reads and writes +string lines: -The protocol layer builds on top of the infrastructure layer to generate the -bytes to be written to the network and to parse the bytes received from the -network. +[source,java,indent=0] +---- +include::../{doc_code}/embedded/client/ClientConnectorDocs.java[tags=telnet] +---- + +Note how a very basic "telnet" API that applications could use is implemented +in the form of the `onLine(Consumer)` for the non-blocking receiving +side and `writeLine(String, Callback)` for the non-blocking sending side. +Note also how the `onFillable()` method implements some basic "parsing" +by looking up the `\n` character in the buffer. + +NOTE: The "telnet" client above looks like a super-simple HTTP client because +HTTP/1.0 can be seen as a line-based protocol. HTTP/1.0 was used just as an +example, but we could have used any other line-based protocol such as +link:https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol[SMTP], +provided that the server was able to understand it. + +This is very similar to what the Jetty client implementation does for real +network protocols. +Real network protocols are of course more complicated and so is the implementation +code that handles them, but the general ideas are similar. + +The Jetty client implementation provides a number of `ClientConnectionFactory` +implementations that can be composed to produce and interpret the network bytes. + +For example, it is simple to modify the above example to use the TLS protocol +so that you will be able to connect to the server on port `443`, typically +reserved for the encrypted HTTP protocol. + +The differences between the clear-text version and the TLS encrypted version +are minimal: + +[source,java,indent=0] +---- +include::../{doc_code}/embedded/client/ClientConnectorDocs.java[tags=tlsTelnet] +---- + +The differences with the clear-text version are only: + +* Change the port from `80` to `443`. +* Wrap the `ClientConnectionFactory` with `SslClientConnectionFactory`. +* Unwrap the `SslConnection` to access `TelnetConnection`. diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/client/client.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/client.adoc index 4673ae1e6b5..ac1bd6e9170 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/client/client.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/client.adoc @@ -17,12 +17,15 @@ // [[client]] -== Jetty Client Libraries +== Client Libraries -The Eclipse Jetty Project provides not only server-side libraries so that you -can embed a server in your code, but it also provides client-side libraries -that allow you to embed a client - for example a HTTP client invoking a third -party HTTP service - in your application. +The Eclipse Jetty Project provides also provides client-side libraries +that allow you to embed a client in your applications. +A typical example is an application that needs to contact a third party +service via HTTP (for example a REST service). +Another example is a proxy application that receives HTTP requests and +forwards them as FCGI requests to a PHP application such as WordPress, +or receives HTTP/1.1 requests and converts them to HTTP/2. The client libraries are designed to be non-blocking and offer both synchronous and asynchronous APIs and come with a large number of configuration options. @@ -32,4 +35,9 @@ There are primarily two client libraries: * link:#client-http[The HTTP client library] * link:#client-websocket[The WebSocket client library] -include::client-concepts.adoc[] +If you are interested in the low-level details of how the Eclipse Jetty +client libraries work, or are interested in writing a custom protocol, +look at the link:#client-io-arch[Client I/O Architecture]. + +include::http/client-http.adoc[] +include::client-io-arch.adoc[] diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/clients/http/http-client-api.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-api.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/clients/http/http-client-api.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-api.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/clients/http/http-client-authentication.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-authentication.adoc similarity index 97% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/clients/http/http-client-authentication.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-authentication.adoc index 8b7515f315f..4e3e48704ae 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/server/clients/http/http-client-authentication.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-authentication.adoc @@ -16,7 +16,7 @@ // ======================================================================== // -[[http-client-authentication]] +[[client-http-authentication]] === Authentication Support Jetty's HTTP client supports the `BASIC` and `DIGEST` authentication mechanisms defined by link:https://tools.ietf.org/html/rfc7235[RFC 7235]. @@ -88,4 +88,4 @@ authn.apply(request); request.send(); ---- -See also the link:#http-client-proxy-authentication[proxy authentication section] for further information about how authentication works with HTTP proxies. +See also the link:#client-http-proxy-authentication[proxy authentication section] for further information about how authentication works with HTTP proxies. diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-configuration.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-configuration.adoc new file mode 100644 index 00000000000..e9a6f0ab7b1 --- /dev/null +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-configuration.adoc @@ -0,0 +1,86 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +[[client-http-configuration]] +=== HttpClient Configuration + +`HttpClient` has a quite large number of configuration parameters. +Please refer to the `HttpClient` +link:{JDURL}/org/eclipse/jetty/client/HttpClient.html[javadocs] +for the complete list of configurable parameters. +The most common parameters are: + +* `HttpClient.idleTimeout`: same as `ClientConnector.idleTimeout` +described in link:#client-io-arch-network[this section]. +* `HttpClient.connectBlocking`: same as `ClientConnector.connectBlocking` +described in link:#client-io-arch-network[this section]. +* `HttpClient.connectTimeout`: same as `ClientConnector.connectTimeout` +described in link:#client-io-arch-network[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). + +[[client-http-configuration-tls]] +==== HttpClient TLS Configuration + +`HttpClient` supports HTTPS requests out-of-the-box like a browser does. + +The support for HTTPS request is provided by a `SslContextFactory.Client`, +typically configured in the `ClientConnector`. +If not explicitly configured, the `ClientConnector` will allocate a default +one when started. + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tags=tlsExplicit] +---- + +The default `SslContextFactory.Client` verifies the certificate sent by the +server by verifying the certificate chain. +This means that requests to public websites that have a valid certificates +(such as ``https://google.com``) will work out-of-the-box. + +However, requests made to sites (typically ``localhost``) that have invalid +(for example, expired or with a wrong host) or self-signed certificates will +fail (like they will in a browser). + +Certificate validation is performed at two levels: at the TLS implementation +level (in the JDK) and - optionally - at the application level. + +By default, certificate validation at the TLS level is enabled, while +certificate validation at the application level is disabled. + +You can configure the `SslContextFactory.Client` to skip certificate validation +at the TLS level: + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tags=tlsNoValidation] +---- + +You can enable certificate validation at the application level: + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tags=tlsAppValidation] +---- + +Please refer to the `SslContextFactory.Client` +link:{JDURL}/org/eclipse/jetty/util/ssl/SslContextFactory.Client.html[javadocs] +for the complete list of configurable parameters. diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/clients/http/http-client-cookie.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-cookie.adoc similarity index 99% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/clients/http/http-client-cookie.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-cookie.adoc index dbe3193b5c8..3802218cdc8 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/server/clients/http/http-client-cookie.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-cookie.adoc @@ -16,7 +16,7 @@ // ======================================================================== // -[[http-client-cookie]] +[[client-http-cookie]] === Cookies Support Jetty HTTP client supports cookies out of the box. 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 new file mode 100644 index 00000000000..34f8c78547f --- /dev/null +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-intro.adoc @@ -0,0 +1,175 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +[[client-http-intro]] +=== HttpClient Introduction + +The Jetty HTTP client module provides easy-to-use APIs and utility classes to perform HTTP (or HTTPS) requests. + +Jetty's HTTP client is non-blocking and asynchronous. +It offers an asynchronous API that never blocks for I/O, making it very efficient in thread utilization and well suited for high performance scenarios such as load testing or parallel computation. + +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 link:#http-client-transport[different transports]: HTTP/1.1, FastCGI and HTTP/2. +This means that the semantic of a HTTP request (that is, " `GET` me 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 FastCGI format or the HTTP/2 format. + +The FastCGI transport is heavily used in Jetty's link:#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. + +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 link:#http2[HTTP/2 support]. + +Out of the box features that you get with the Jetty HTTP client include: + +* Redirect support - redirect codes such as 302 or 303 are automatically followed. +* Cookies support - cookies sent by servers are stored and sent back to servers in matching requests. +* Authentication support - HTTP "Basic" and "Digest" authentications are supported, others are pluggable. +* Forward proxy support - HTTP proxying and SOCKS4 proxying. + +[[client-http-start]] +==== Starting HttpClient + +The main class is named `org.eclipse.jetty.client.HttpClient`. + +You can think of a `HttpClient` instance as a browser instance. +Like a browser it can make requests to different domains, it manages redirects, cookies and authentication, you can configure it with a proxy, and +it provides you with the responses to the requests you make. + +In order to use `HttpClient`, you must instantiate it, configure it, and then start it: + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tags=start] +---- + +You may create multiple instances of `HttpClient`, but typically one instance is enough for an application. +There are several reasons for having multiple `HttpClient` instances including, but not limited to: + +* 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 link:#http-client-transport[different transports]. + +Like browsers, HTTPS requests are supported out-of-the-box, 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 +link:#client-http-configuration-tls[this section]. + +[[client-http-stop]] +==== Stopping HttpClient + +It is recommended that when your application stops, you also stop the `HttpClient` instance (or instances) that you are using. + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tags=stop] +---- + +Stopping `HttpClient` makes sure that the memory it holds (for example, authentication credentials, cookies, etc.) is released, and that the thread pool and scheduler are properly stopped allowing all threads used by `HttpClient` to exit. + +[[client-http-arch]] +==== HttpClient Architecture + +A `HttpClient` instance can be thought as a browser instance, and it manages the +following components: + +* a `CookieStore` (see link:#client-http-cookie[this section]). +* a `AuthenticationStore` (see link:#client-http-authentication[this section]). +* a `ProxyConfiguration` (see link:#client-http-proxy[this section]). +* 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. + +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. +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. +However, also this is not enough. + +It is possible that a server speaks 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. + +Therefore an origin is identified by the tuple +`(scheme, host, port, tag, protocol)`. + +[[client-http-request-processing]] +==== HttpClient Request Processing + +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. +The request is then queued onto the destination, and this causes the +destination to ask its connection pool for a free connection. +If a connection is available, it is returned, otherwise a new connection is +created. +Once the destination has obtained the connection, it dequeues the request +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 _concurrently_ with the first +request will cause the opening of a second connection. +The configuration parameter `HttpClient.maxConnectionsPerDestination` +(see also the link:#client-http-configuration[configuration section]) 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. + +Each connection can handle a limited number of 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` +setting (typically around `100`, i.e. there can be up to `100` outstanding +requests for every connection). + +When a destination has maxed out its number of connections, and all +connections have maxed out their number of outstanding requests, more requests +sent to that destination will be queued. +When the request queue is full, the request will be failed. +The configuration parameter `HttpClient.maxRequestsQueuedPerDestination` +(see also the link:#client-http-configuration[configuration section]) controls +the max number of requests that can be queued for a destination. diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/clients/http/http-client-proxy.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-proxy.adoc similarity index 96% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/clients/http/http-client-proxy.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-proxy.adoc index 1d5acd92475..13f53584e93 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/server/clients/http/http-client-proxy.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-proxy.adoc @@ -16,7 +16,7 @@ // ======================================================================== // -[[http-client-proxy]] +[[client-http-proxy]] === Proxy Support Jetty's HTTP client can be configured to use proxies to connect to destinations. @@ -44,10 +44,10 @@ You specify the proxy host and port, and optionally also the addresses that you Configured in this way, `HttpClient` makes requests to the HTTP proxy (for plain-text HTTP requests) or establishes a tunnel via `HTTP CONNECT` (for encrypted HTTPS requests). -[[http-client-proxy-authentication]] +[[client-http-proxy-authentication]] ==== Proxy Authentication Support -Jetty's HTTP client support proxy authentication in the same way it supports link:#http-client-authentication[server authentication]. +Jetty's HTTP client support proxy authentication in the same way it supports link:#client-http-authentication[server authentication]. In the example below, the proxy requires Basic authentication, but the server requires Digest authentication, and therefore: @@ -100,4 +100,4 @@ Application HttpClient Proxy Server The application does not receive events related to the responses with code 407 and 401 since they are handled internally by `HttpClient`. -Similarly to the link:#http-client-authentication[authentication section], the proxy authentication result and the server authentication result can be preempted to avoid, respectively, the 407 and 401 roundtrips. +Similarly to the link:#client-http-authentication[authentication section], the proxy authentication result and the server authentication result can be preempted to avoid, respectively, the 407 and 401 roundtrips. diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/clients/http/http-client-transport.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-transport.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/clients/http/http-client-transport.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-transport.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http.adoc index c1f403bd05e..8d0d71a911e 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http.adoc @@ -18,3 +18,12 @@ [[client-http]] === HTTP Client + +include::client-http-intro.adoc[] +include::client-http-configuration.adoc[] +include::client-http-api.adoc[] +include::client-http-cookie.adoc[] +include::client-http-authentication.adoc[] +include::client-http-proxy.adoc[] +include::client-http-transport.adoc[] + diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/io-arch.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/io-arch.adoc index e083aa1d6ee..178e04eaaa0 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/io-arch.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/io-arch.adoc @@ -261,3 +261,5 @@ Otherwise, the write is now `PENDING` and waiting for the callback to be notified of the completion at a later time. When the callback is notified of the `write()` completion, it checks whether the `write()` was `PENDING`, and if it was it resumes reading. + +NOTE: TODO: Introduce IteratingCallback? diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/clients/http/chapter.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/server/clients/http/chapter.adoc deleted file mode 100644 index fe028abc9b0..00000000000 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/server/clients/http/chapter.adoc +++ /dev/null @@ -1,27 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under -// the terms of the Eclipse Public License 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0 -// -// This Source Code may also be made available under the following -// Secondary Licenses when the conditions for such availability set -// forth in the Eclipse Public License, v. 2.0 are satisfied: -// the Apache License v2.0 which is available at -// https://www.apache.org/licenses/LICENSE-2.0 -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -[[http-client]] -== HTTP Client - -include::http-client-intro.adoc[] -include::http-client-api.adoc[] -include::http-client-cookie.adoc[] -include::http-client-authentication.adoc[] -include::http-client-proxy.adoc[] -include::http-client-transport.adoc[] diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/clients/http/http-client-intro.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/server/clients/http/http-client-intro.adoc deleted file mode 100644 index 3a2f70d9c1b..00000000000 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/server/clients/http/http-client-intro.adoc +++ /dev/null @@ -1,105 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under -// the terms of the Eclipse Public License 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0 -// -// This Source Code may also be made available under the following -// Secondary Licenses when the conditions for such availability set -// forth in the Eclipse Public License, v. 2.0 are satisfied: -// the Apache License v2.0 which is available at -// https://www.apache.org/licenses/LICENSE-2.0 -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -[[http-client-intro]] -=== Introduction - -The Jetty HTTP client module provides easy-to-use APIs and utility classes to perform HTTP (or HTTPS) requests. - -Jetty's HTTP client is non-blocking and asynchronous. -It offers an asynchronous API that never blocks for I/O, making it very efficient in thread utilization and well suited for high performance scenarios such as load testing or parallel computation. - -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 link:#http-client-transport[transports]: HTTP/1.1, FastCGI and HTTP/2. -This means that the semantic of a HTTP request (that is, " `GET` me 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 FastCGI format or the new HTTP/2 format. - -The FastCGI transport is heavily used in Jetty's link:#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. - -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 link:#http2[HTTP/2 support]. - -Out of the box features that you get with the Jetty HTTP client include: - -* Redirect support - redirect codes such as 302 or 303 are automatically followed. -* Cookies support - cookies sent by servers are stored and sent back to servers in matching requests. -* Authentication support - HTTP "Basic" and "Digest" authentications are supported, others are pluggable. -* Forward proxy support - HTTP proxying and SOCKS4 proxying. - -[[http-client-init]] -==== Starting HttpClient - -The main class is named `org.eclipse.jetty.client.HttpClient`. - -You can think of a `HttpClient` instance as a browser instance. -Like a browser it can make requests to different domains, it manages redirects, cookies and authentication, you can configure it with a proxy, and -it provides you with the responses to the requests you make. - -In order to use `HttpClient`, you must instantiate it, configure it, and then start it: - -[source, java, subs="{sub-order}"] ----- -// Instantiate HttpClient -HttpClient httpClient = new HttpClient(); - -// Configure HttpClient, for example: -httpClient.setFollowRedirects(false); - -// Start HttpClient -httpClient.start(); ----- - -You may create multiple instances of `HttpClient`, but typically one instance is enough for an application. -There are several reasons for having multiple `HttpClient` instances including, but not limited to: - -* 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 different transports - -When you create a `HttpClient` instance using the parameterless constructor, you will only be able to perform plain HTTP requests and you will not be able to perform HTTPS requests. - -In order to perform HTTPS requests, you should create first a link:{JDURL}/org/eclipse/jetty/util/ssl/SslContextFactory.Client.html[`SslContextFactory.Client`], configure it, and pass it to the `HttpClient` constructor. -When created with a `SslContextFactory`, the `HttpClient` will be able to perform both HTTP and HTTPS requests to any domain. - -[source, java, subs="{sub-order}"] ----- -// Instantiate and configure the SslContextFactory -SslContextFactory.Client sslContextFactory = new SslContextFactory.Client(); - -// Instantiate HttpClient with the SslContextFactory -HttpClient httpClient = new HttpClient(sslContextFactory); - -// Configure HttpClient, for example: -httpClient.setFollowRedirects(false); - -// Start HttpClient -httpClient.start(); ----- - -==== Stopping HttpClient - -It is recommended that when your application stops, you also stop the `HttpClient` instance (or instances) that you are using. - -[source, java, subs="{sub-order}"] ----- -httpClient.stop(); ----- - -Stopping `HttpClient` makes sure that the memory it holds (for example, authentication credentials, cookies, etc.) is released, and that the thread pool and scheduler are properly stopped allowing all threads used by `HttpClient` to exit. diff --git a/jetty-documentation/src/main/java/embedded/client/ClientConnectorDocs.java b/jetty-documentation/src/main/java/embedded/client/ClientConnectorDocs.java index 840bb63a0b2..5fcd35c789e 100644 --- a/jetty-documentation/src/main/java/embedded/client/ClientConnectorDocs.java +++ b/jetty-documentation/src/main/java/embedded/client/ClientConnectorDocs.java @@ -18,17 +18,27 @@ package embedded.client; +import java.io.ByteArrayOutputStream; import java.net.InetSocketAddress; import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; +import java.util.function.Consumer; import org.eclipse.jetty.io.AbstractConnection; import org.eclipse.jetty.io.ClientConnectionFactory; import org.eclipse.jetty.io.ClientConnector; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.SelectorManager; +import org.eclipse.jetty.io.ssl.SslClientConnectionFactory; +import org.eclipse.jetty.io.ssl.SslConnection; +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.Promise; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler; @@ -108,9 +118,9 @@ public class ClientConnectorDocs public void connect() throws Exception { // tag::connect[] - class CustomHTTPConnection extends AbstractConnection + class CustomConnection extends AbstractConnection { - public CustomHTTPConnection(EndPoint endPoint, Executor executor) + public CustomConnection(EndPoint endPoint, Executor executor) { super(endPoint, executor); } @@ -119,6 +129,7 @@ public class ClientConnectorDocs public void onOpen() { super.onOpen(); + System.getLogger("connection").log(INFO, "Opened connection {0}", this); } @Override @@ -130,23 +141,289 @@ public class ClientConnectorDocs ClientConnector clientConnector = new ClientConnector(); clientConnector.start(); + String host = "serverHost"; + int port = 8080; + SocketAddress address = new InetSocketAddress(host, port); + + // The ClientConnectionFactory that creates CustomConnection instances. + ClientConnectionFactory connectionFactory = (endPoint, context) -> + { + System.getLogger("connection").log(INFO, "Creating connection for {0}", endPoint); + return new CustomConnection(endPoint, clientConnector.getExecutor()); + }; + + // The Promise to notify of connection creation success or failure. + CompletableFuture connectionPromise = new Promise.Completable<>(); + + // Populate the context with the mandatory keys to create and obtain connections. + Map context = new HashMap<>(); + context.put(ClientConnector.CLIENT_CONNECTION_FACTORY_CONTEXT_KEY, connectionFactory); + context.put(ClientConnector.CONNECTION_PROMISE_CONTEXT_KEY, connectionPromise); + clientConnector.connect(address, context); + + // Use the Connection when it's available. + + // Use it in a non-blocking way via CompletableFuture APIs. + connectionPromise.whenComplete((connection, failure) -> + { + System.getLogger("connection").log(INFO, "Created connection for {0}", connection); + }); + + // Alternatively, you can block waiting for the connection (or a failure). + // CustomConnection connection = connectionPromise.get(); + // end::connect[] + } + + public void telnet() throws Exception + { + // tag::telnet[] + class TelnetConnection extends AbstractConnection + { + private final ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + private Consumer consumer; + + public TelnetConnection(EndPoint endPoint, Executor executor) + { + super(endPoint, executor); + } + + @Override + public void onOpen() + { + super.onOpen(); + + // Declare interest for fill events. + fillInterested(); + } + + @Override + public void onFillable() + { + try + { + ByteBuffer buffer = BufferUtil.allocate(1024); + while (true) + { + int filled = getEndPoint().fill(buffer); + if (filled > 0) + { + while (buffer.hasRemaining()) + { + // Search for newline. + byte read = buffer.get(); + if (read == '\n') + { + // Notify the consumer of the line. + consumer.accept(bytes.toString(StandardCharsets.UTF_8)); + bytes.reset(); + } + else + { + bytes.write(read); + } + } + } + else if (filled == 0) + { + // No more bytes to fill, declare + // again interest for fill events. + fillInterested(); + return; + } + else + { + // The other peer closed the + // connection, close it back. + getEndPoint().close(); + return; + } + } + } + catch (Exception x) + { + getEndPoint().close(x); + } + } + + public void onLine(Consumer consumer) + { + this.consumer = consumer; + } + + public void writeLine(String line, Callback callback) + { + line = line + "\r\n"; + getEndPoint().write(callback, ByteBuffer.wrap(line.getBytes(StandardCharsets.UTF_8))); + } + } + + ClientConnector clientConnector = new ClientConnector(); + clientConnector.start(); + String host = "wikipedia.org"; int port = 80; SocketAddress address = new InetSocketAddress(host, port); ClientConnectionFactory connectionFactory = (endPoint, context) -> - { - System.getLogger("connection").log(INFO, "Creating connection for {0}", endPoint); - return new CustomHTTPConnection(endPoint, clientConnector.getExecutor()); - }; + new TelnetConnection(endPoint, clientConnector.getExecutor()); + + CompletableFuture connectionPromise = new Promise.Completable<>(); + Map context = new HashMap<>(); context.put(ClientConnector.CLIENT_CONNECTION_FACTORY_CONTEXT_KEY, connectionFactory); + context.put(ClientConnector.CONNECTION_PROMISE_CONTEXT_KEY, connectionPromise); clientConnector.connect(address, context); - // end::connect[] + + connectionPromise.whenComplete((connection, failure) -> + { + if (failure == null) + { + // Register a listener that receives string lines. + connection.onLine(line -> System.getLogger("app").log(INFO, "line: {0}", line)); + + // Write a line. + connection.writeLine("" + + "GET / HTTP/1.0\r\n" + + "", Callback.NOOP); + } + else + { + failure.printStackTrace(); + } + }); + // end::telnet[] + } + + public void tlsTelnet() throws Exception + { + // tag::tlsTelnet[] + class TelnetConnection extends AbstractConnection + { + private final ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + private Consumer consumer; + + public TelnetConnection(EndPoint endPoint, Executor executor) + { + super(endPoint, executor); + } + + @Override + public void onOpen() + { + super.onOpen(); + + // Declare interest for fill events. + fillInterested(); + } + + @Override + public void onFillable() + { + try + { + ByteBuffer buffer = BufferUtil.allocate(1024); + while (true) + { + int filled = getEndPoint().fill(buffer); + if (filled > 0) + { + while (buffer.hasRemaining()) + { + // Search for newline. + byte read = buffer.get(); + if (read == '\n') + { + // Notify the consumer of the line. + consumer.accept(bytes.toString(StandardCharsets.UTF_8)); + bytes.reset(); + } + else + { + bytes.write(read); + } + } + } + else if (filled == 0) + { + // No more bytes to fill, declare + // again interest for fill events. + fillInterested(); + return; + } + else + { + // The other peer closed the + // connection, close it back. + getEndPoint().close(); + return; + } + } + } + catch (Exception x) + { + getEndPoint().close(x); + } + } + + public void onLine(Consumer consumer) + { + this.consumer = consumer; + } + + public void writeLine(String line, Callback callback) + { + line = line + "\r\n"; + getEndPoint().write(callback, ByteBuffer.wrap(line.getBytes(StandardCharsets.UTF_8))); + } + } + + ClientConnector clientConnector = new ClientConnector(); + clientConnector.start(); + + // Use port 443 to contact the server using encrypted HTTP. + String host = "wikipedia.org"; + int port = 443; + SocketAddress address = new InetSocketAddress(host, port); + + ClientConnectionFactory connectionFactory = (endPoint, context) -> + new TelnetConnection(endPoint, clientConnector.getExecutor()); + + // Wrap the "telnet" ClientConnectionFactory with the SslClientConnectionFactory. + connectionFactory = new SslClientConnectionFactory(clientConnector.getSslContextFactory(), + clientConnector.getByteBufferPool(), clientConnector.getExecutor(), connectionFactory); + + // We will obtain a SslConnection now. + CompletableFuture connectionPromise = new Promise.Completable<>(); + + Map context = new HashMap<>(); + context.put(ClientConnector.CLIENT_CONNECTION_FACTORY_CONTEXT_KEY, connectionFactory); + context.put(ClientConnector.CONNECTION_PROMISE_CONTEXT_KEY, connectionPromise); + clientConnector.connect(address, context); + + connectionPromise.whenComplete((sslConnection, failure) -> + { + if (failure == null) + { + // Unwrap the SslConnection to access the "line" APIs in TelnetConnection. + TelnetConnection connection = (TelnetConnection)sslConnection.getDecryptedEndPoint().getConnection(); + // Register a listener that receives string lines. + connection.onLine(line -> System.getLogger("app").log(INFO, "line: {0}", line)); + + // Write a line. + connection.writeLine("" + + "GET / HTTP/1.0\r\n" + + "", Callback.NOOP); + } + else + { + failure.printStackTrace(); + } + }); + // end::tlsTelnet[] } public static void main(String[] args) throws Exception { - new ClientConnectorDocs().connect(); + new ClientConnectorDocs().tlsTelnet(); } } diff --git a/jetty-documentation/src/main/java/embedded/client/http/HTTPClientDocs.java b/jetty-documentation/src/main/java/embedded/client/http/HTTPClientDocs.java new file mode 100644 index 00000000000..f531283b41a --- /dev/null +++ b/jetty-documentation/src/main/java/embedded/client/http/HTTPClientDocs.java @@ -0,0 +1,82 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package embedded.client.http; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.dynamic.HttpClientTransportDynamic; +import org.eclipse.jetty.io.ClientConnector; +import org.eclipse.jetty.util.ssl.SslContextFactory; + +public class HTTPClientDocs +{ + public void start() throws Exception + { + // tag::start[] + // Instantiate HttpClient. + HttpClient httpClient = new HttpClient(); + + // Configure HttpClient, for example: + httpClient.setFollowRedirects(false); + + // Start HttpClient. + httpClient.start(); + // end::start[] + } + + public void stop() throws Exception + { + HttpClient httpClient = new HttpClient(); + httpClient.start(); + // tag::stop[] + // Stop HttpClient. + httpClient.stop(); + // end::stop[] + } + + public void tlsExplicit() throws Exception + { + // tag::tlsExplicit[] + SslContextFactory.Client sslContextFactory = new SslContextFactory.Client(); + + ClientConnector clientConnector = new ClientConnector(); + clientConnector.setSslContextFactory(sslContextFactory); + + HttpClient httpClient = new HttpClient(new HttpClientTransportDynamic(clientConnector)); + httpClient.start(); + // end::tlsExplicit[] + } + + public void tlsNoValidation() throws Exception + { + // tag::tlsNoValidation[] + SslContextFactory.Client sslContextFactory = new SslContextFactory.Client(); + // Disable certificate validation at the TLS level. + sslContextFactory.setEndpointIdentificationAlgorithm(null); + // end::tlsNoValidation[] + } + + public void tlsAppValidation() throws Exception + { + // tag::tlsAppValidation[] + SslContextFactory.Client sslContextFactory = new SslContextFactory.Client(); + // Only allow subdomains of domain.com. + sslContextFactory.setHostnameVerifier((hostName, session) -> hostName.endsWith(".domain.com")); + // end::tlsAppValidation[] + } +} diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java index abe77a5673e..5d215abf78a 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java @@ -30,6 +30,7 @@ import java.util.HashMap; import java.util.Map; import java.util.concurrent.Executor; +import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.Promise; import org.eclipse.jetty.util.component.ContainerLifeCycle; import org.eclipse.jetty.util.ssl.SslContextFactory; @@ -281,15 +282,7 @@ public class ClientConnector extends ContainerLifeCycle protected void safeClose(Closeable closeable) { - try - { - if (closeable != null) - closeable.close(); - } - catch (Throwable x) - { - LOG.trace("IGNORED", x); - } + IO.close(closeable); } protected void configure(SocketChannel channel) throws IOException @@ -330,6 +323,18 @@ public class ClientConnector extends ContainerLifeCycle return factory.newConnection(endPoint, context); } + @Override + public void connectionOpened(Connection connection, Object context) + { + super.connectionOpened(connection, context); + @SuppressWarnings("unchecked") + Map contextMap = (Map)context; + @SuppressWarnings("unchecked") + Promise promise = (Promise)contextMap.get(CONNECTION_PROMISE_CONTEXT_KEY); + if (promise != null) + promise.succeeded(connection); + } + @Override protected void connectionFailed(SelectableChannel channel, Throwable failure, Object attachment) { diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ManagedSelector.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ManagedSelector.java index afa3410f882..fbdd71ad77b 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/ManagedSelector.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ManagedSelector.java @@ -268,12 +268,13 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable private void createEndPoint(SelectableChannel channel, SelectionKey selectionKey) throws IOException { EndPoint endPoint = _selectorManager.newEndPoint(channel, this, selectionKey); - Connection connection = _selectorManager.newConnection(channel, endPoint, selectionKey.attachment()); + Object context = selectionKey.attachment(); + Connection connection = _selectorManager.newConnection(channel, endPoint, context); endPoint.setConnection(connection); selectionKey.attach(endPoint); endPoint.onOpen(); endPointOpened(endPoint); - _selectorManager.connectionOpened(connection); + _selectorManager.connectionOpened(connection, context); if (LOG.isDebugEnabled()) LOG.debug("Created {}", endPoint); } diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java b/jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java index fb427e68291..1bcd0080ed8 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java @@ -302,8 +302,10 @@ public abstract class SelectorManager extends ContainerLifeCycle implements Dump *

Callback method invoked when a connection is opened.

* * @param connection the connection just opened + * @param context the attachment associated with the creation of the connection + * @see #newConnection(SelectableChannel, EndPoint, Object) */ - public void connectionOpened(Connection connection) + public void connectionOpened(Connection connection, Object context) { try {