1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287
|
//
// ========================================================================
// 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
// ========================================================================
//
= HTTP/2 Client Library
In the vast majority of cases, client applications should use the generic, high-level, xref:client/http.adoc[HTTP client library] that also provides HTTP/2 support via the pluggable xref:client/http.adoc#transport-http2[HTTP/2 transport] or the xref:client/http.adoc#transport-dynamic[dynamic transport].
The high-level HTTP library supports cookies, authentication, redirection, connection pooling and a number of other features that are absent in the low-level HTTP/2 library.
The HTTP/2 client library has been designed for those applications that need low-level access to HTTP/2 features such as _sessions_, _streams_ and _frames_, and this is quite a rare use case.
See also the correspondent xref:server/http2.adoc[HTTP/2 server library].
[[intro]]
== Introducing HTTP2Client
The Maven artifact coordinates for the HTTP/2 client library are the following:
[,xml,subs=attributes+]
----
<dependency>
<groupId>org.eclipse.jetty.http2</groupId>
<artifactId>jetty-http2-client</artifactId>
<version>{jetty-version}</version>
</dependency>
----
The main class is named `org.eclipse.jetty.http2.client.HTTP2Client`, and must be created, configured and started before use:
[,java,indent=0]
----
include::code:example$src/main/java/org/eclipse/jetty/docs/programming/client/http2/HTTP2ClientDocs.java[tags=start]
----
When your application stops, or otherwise does not need `HTTP2Client` anymore, it should stop the `HTTP2Client` instance (or instances) that were started:
[,java,indent=0]
----
include::code:example$src/main/java/org/eclipse/jetty/docs/programming/client/http2/HTTP2ClientDocs.java[tags=stop]
----
`HTTP2Client` allows client applications to connect to an HTTP/2 server.
A _session_ represents a single TCP connection to an HTTP/2 server and is defined by class `org.eclipse.jetty.http2.api.Session`.
A _session_ typically has a long life -- once the TCP connection is established, it remains open until it is not used anymore (and therefore it is closed by the idle timeout mechanism), until a fatal error occurs (for example, a network failure), or if one of the peers decides unilaterally to close the TCP connection.
HTTP/2 is a multiplexed protocol: it allows multiple HTTP/2 requests to be sent on the same TCP connection, or _session_.
Each request/response cycle is represented by a _stream_.
Therefore, a single _session_ manages multiple concurrent _streams_.
A _stream_ has typically a very short life compared to the _session_: a _stream_ only exists for the duration of the request/response cycle and then disappears.
[[flow-control]]
== HTTP/2 Flow Control
The HTTP/2 protocol is _flow controlled_ (see https://tools.ietf.org/html/rfc7540#section-5.2[the specification]).
This means that a sender and a receiver maintain a _flow control window_ that tracks the number of data bytes sent and received, respectively.
When a sender sends data bytes, it reduces its flow control window.
When a receiver receives data bytes, it also reduces its flow control window, and then passes the received data bytes to the application.
The application consumes the data bytes and tells back the receiver that it has consumed the data bytes.
The receiver then enlarges the flow control window, and the implementation arranges to send a message to the sender with the number of bytes consumed, so that the sender can enlarge its flow control window.
A sender can send data bytes up to its whole flow control window, then it must stop sending.
The sender may resume sending data bytes when it receives a message from the receiver that the data bytes sent previously have been consumed.
This message enlarges the sender flow control window, which allows the sender to send more data bytes.
HTTP/2 defines _two_ flow control windows: one for each _session_, and one for each _stream_.
Let's see with an example how they interact, assuming that in this example the session flow control window is 120 bytes and the stream flow control window is 100 bytes.
The sender opens a session, and then opens `stream_1` on that session, and sends `80` data bytes.
At this point the session flow control window is `40` bytes (`120 - 80`), and ``stream_1``'s flow control window is `20` bytes (`100 - 80`).
The sender now opens `stream_2` on the same session and sends `40` data bytes.
At this point, the session flow control window is `0` bytes (`40 - 40`), while ``stream_2``'s flow control window is `60` (`100 - 40`).
Since now the session flow control window is `0`, the sender cannot send more data bytes, neither on `stream_1` nor on `stream_2`, nor on other streams, despite all the streams having their stream flow control windows greater than `0`.
The receiver consumes ``stream_2``'s `40` data bytes and sends a message to the sender with this information.
At this point, the session flow control window is `40` (``0 + 40``), ``stream_1``'s flow control window is still `20` and ``stream_2``'s flow control window is `100` (``60 + 40``).
If the sender opens `stream_3` and would like to send `50` data bytes, it would only be able to send `40` because that is the maximum allowed by the session flow control window at this point.
It is therefore very important that applications notify the fact that they have consumed data bytes as soon as possible, so that the implementation (the receiver) can send a message to the sender (in the form of a `WINDOW_UPDATE` frame) with the information to enlarge the flow control window, therefore reducing the possibility that sender stalls due to the flow control windows being reduced to `0`.
How a client application should handle HTTP/2 flow control is discussed in details in <<response,this section>>.
[[connect]]
== Connecting to the Server
The first thing an application should do is to connect to the server and obtain a `Session`.
The following example connects to the server on a clear-text port:
[,java,indent=0]
----
include::code:example$src/main/java/org/eclipse/jetty/docs/programming/client/http2/HTTP2ClientDocs.java[tags=clearTextConnect]
----
The following example connects to the server on an encrypted port:
[,java,indent=0]
----
include::code:example$src/main/java/org/eclipse/jetty/docs/programming/client/http2/HTTP2ClientDocs.java[tags=encryptedConnect]
----
IMPORTANT: Applications must know in advance whether they want to connect to a clear-text or encrypted port, and pass the `SslContextFactory` parameter accordingly to the `connect(\...)` method.
[[configure]]
== Configuring the Session
The `connect(\...)` method takes a `Session.Listener` parameter.
This listener's `onPreface(\...)` method is invoked just before establishing the connection to the server to gather the client configuration to send to the server.
Client applications can override this method to change the default configuration:
[,java,indent=0]
----
include::code:example$src/main/java/org/eclipse/jetty/docs/programming/client/http2/HTTP2ClientDocs.java[tags=configure]
----
The `Session.Listener` is notified of session events originated by the server such as receiving a `SETTINGS` frame from the server, or the server closing the connection, or the client timing out the connection due to idleness.
Please refer to the `Session.Listener` link:{javadoc-url}/org/eclipse/jetty/http2/api/Session.Listener.html[javadocs] for the complete list of events.
Once a `Session` has been established, the communication with the server happens by exchanging _frames_, as specified in the https://tools.ietf.org/html/rfc7540#section-4[HTTP/2 specification].
[[request]]
== Sending a Request
Sending an HTTP request to the server, and receiving a response, creates a _stream_ that encapsulates the exchange of HTTP/2 frames that compose the request and the response.
In order to send an HTTP request to the server, the client must send a `HEADERS` frame.
`HEADERS` frames carry the request method, the request URI and the request headers.
Sending the `HEADERS` frame opens the `Stream`:
[,java,indent=0,subs=attributes+]
----
include::code:example$src/main/java/org/eclipse/jetty/docs/programming/client/http2/HTTP2ClientDocs.java[tags=newStream]
----
Note how `Session.newStream(\...)` takes a `Stream.Listener` parameter.
This listener is notified of stream events originated by the server such as receiving `HEADERS` or `DATA` frames that are part of the response, discussed in more details in the <<response,section below>>.
Please refer to the `Stream.Listener` link:{javadoc-url}/org/eclipse/jetty/http2/api/Stream.Listener.html[javadocs] for the complete list of events.
HTTP requests may have content, which is sent using the `Stream` APIs:
[,java,indent=0]
----
include::code:example$src/main/java/org/eclipse/jetty/docs/programming/client/http2/HTTP2ClientDocs.java[tags=newStreamWithData]
----
IMPORTANT: When sending two `DATA` frames consecutively, the second call to `Stream.data(\...)` must be done only when the first is completed, or a `WritePendingException` will be thrown.
Use the `Callback` APIs or `CompletableFuture` APIs to ensure that the second `Stream.data(\...)` call is performed when the first completed successfully.
[[response]]
== Receiving a Response
Response events are delivered to the `Stream.Listener` passed to `Session.newStream(\...)`.
An HTTP response is typically composed of a `HEADERS` frame containing the HTTP status code and the response headers, and optionally one or more `DATA` frames containing the response content bytes.
The HTTP/2 protocol also supports response trailers (that is, headers that are sent after the response content) that also are sent using a `HEADERS` frame.
A client application can therefore receive the HTTP/2 frames sent by the server by implementing the relevant methods in `Stream.Listener`:
[,java,indent=0]
----
include::code:example$src/main/java/org/eclipse/jetty/docs/programming/client/http2/HTTP2ClientDocs.java[tags=responseListener]
----
[NOTE]
====
When `onDataAvailable(Stream stream)` is invoked, the demand is implicitly cancelled.
Just returning from the `onDataAvailable(Stream stream)` method does _not_ implicitly demand for more `DATA` frames.
Applications must call `Stream.demand()` to explicitly require that `onDataAvailable(Stream stream)` is invoked again when more `DATA` frames are available.
====
Applications that consume the content buffer within `onDataAvailable(Stream stream)` (for example, writing it to a file, or copying the bytes to another storage) should call `Data.release()` as soon as they have consumed the content buffer.
This allows the implementation to reuse the buffer, reducing the memory requirements needed to handle the content buffers.
Alternatively, an application may store away the `Data` object to consume the buffer bytes later, or pass the `Data` object to another asynchronous API (this is typical in proxy applications).
[IMPORTANT]
====
The call to `Stream.readData()` tells the implementation to enlarge the stream and session flow control windows so that the sender will be able to send more `DATA` frames without stalling.
====
Applications can unwrap the `Data` object into some other object that may be used later, provided that the _release_ semantic is maintained:
[,java,indent=0]
----
include::code:example$src/main/java/org/eclipse/jetty/docs/programming/HTTP2Docs.java[tags=dataUnwrap]
----
[IMPORTANT]
====
Applications that implement `onDataAvailable(Stream stream)` must remember to call `Stream.demand()` eventually.
If they do not call `Stream.demand()`, the implementation will not invoke `onDataAvailable(Stream stream)` to deliver more `DATA` frames and the application will stall threadlessly until an idle timeout fires to close the stream or the session.
====
[[reset]]
== Resetting a Request or Response
In HTTP/2, clients and servers have the ability to tell to the other peer that they are not interested anymore in either the request or the response, using a `RST_STREAM` frame.
The `HTTP2Client` APIs allow client applications to send and receive this "reset" frame:
[,java,indent=0]
----
include::code:example$src/main/java/org/eclipse/jetty/docs/programming/client/http2/HTTP2ClientDocs.java[tags=reset]
----
[[push]]
== Receiving HTTP/2 Pushes
HTTP/2 servers have the ability to push resources related to a primary resource.
When an HTTP/2 server pushes a resource, it sends to the client a `PUSH_PROMISE` frame that contains the request URI and headers that a client would use to request explicitly that resource.
Client applications can be configured to tell the server to never push resources, see <<configure,this section>>.
Client applications can listen to the push events, and act accordingly:
[,java,indent=0]
----
include::code:example$src/main/java/org/eclipse/jetty/docs/programming/client/http2/HTTP2ClientDocs.java[tags=push]
----
If a client application does not want to handle a particular HTTP/2 push, it can just reset the pushed stream to tell the server to stop sending bytes for the pushed stream:
[,java,indent=0]
----
include::code:example$src/main/java/org/eclipse/jetty/docs/programming/client/http2/HTTP2ClientDocs.java[tags=pushReset]
----
[[goaway]]
== Session Closing
Once a `Session` has been established (see <<connect,here>>), it may be closed in the following way, providing an error code and a short textual reason:
[,java,indent=0]
----
include::code:example$src/main/java/org/eclipse/jetty/docs/programming/client/http2/HTTP2ClientDocs.java[tags=close]
----
Closing a `Session` will send a `GOAWAY` frame to the server.
When the server closes the `Session`, the `GOAWAY` frame received by the client carries the identifier of the last stream that was processed by the server.
Streams that have an identifier higher than the one specified in the `GOAWAY` frame are not processed by the server.
A client application may be sending requests while the server is closing the `Session`, so it is possible that the client opens a stream that will not be processed by the server, because it has an identifier that is greater than the one specified in the `GOAWAY` frame sent by the server.
In this case the stream is failed on the client with a `RetryableStreamException`, and client applications may decide whether to retry the request (if the request can be retried):
[,java,indent=0]
----
include::code:example$src/main/java/org/eclipse/jetty/docs/programming/client/http2/HTTP2ClientDocs.java[tags=retryStream]
----
[[listeners]]
== Listening to HTTP/2 Events
The HTTP/2 low-level APIs allow you to listen and react to semantic events such as HTTP requests and HTTP responses.
You can use `HTTP2Session.LifeCycleListener` to be notified of lifecycle events of a `Session` (when it is opened and when it is closed), and `HTTP2Session.FrameListener` to be notified of what HTTP/2 frames have been sent and received.
These listeners are useful to implement cross-cutting concerns that are not related to the handling of HTTP requests and responses.
For example, you can use a `HTTP2Session.LifeCycleListener` to record and log how long sessions remain open:
[,java,indent=0]
----
include::code:example$src/main/java/org/eclipse/jetty/docs/programming/client/http2/HTTP2ClientDocs.java[tags=listenerLifeCycle]
----
Another example is to use a `HTTP2Session.FrameListener` to log HTTP/2 frames for HTTP/2 protocol troubleshooting:
[,java,indent=0]
----
include::code:example$src/main/java/org/eclipse/jetty/docs/programming/client/http2/HTTP2ClientDocs.java[tags=listenerLogging]
----
In limited cases, you can use these listeners to access the low-level HTTP/2 APIs even if your application uses the xref:client/http.adoc[high-level APIs] with the xref:client/http.adoc#transport-http2[HTTP/2 transport].
|