Improvements to the Jetty server documentation.
Written HTTP/2 low-level section. Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
This commit is contained in:
parent
34a2d6e673
commit
14fda86944
|
@ -33,6 +33,8 @@ 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:eg-server-http2[HTTP/2 server library].
|
||||
|
||||
[[eg-client-http2-intro]]
|
||||
==== Introducing HTTP2Client
|
||||
|
||||
|
@ -63,79 +65,24 @@ it should stop the `HTTP2Client` instance (or instances) that were started:
|
|||
include::../../{doc_code}/embedded/client/http2/HTTP2ClientDocs.java[tags=stop]
|
||||
----
|
||||
|
||||
`HTTP2Client` allows client applications to connect to a HTTP/2 server.
|
||||
A _session_ represents a single TCP connection to a HTTP/2 server and is defined
|
||||
by class `org.eclipse.jetty.http2.api.Session`.
|
||||
`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.
|
||||
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.
|
||||
include::../../http2.adoc[tag=multiplex]
|
||||
|
||||
[[eg-client-http2-flow-control]]
|
||||
===== HTTP/2 Flow Control
|
||||
|
||||
The HTTP/2 protocol is _flow controlled_ (see
|
||||
link: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 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 until it receives a message from the receiver that the data bytes
|
||||
have been consumed, which enlarges the 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` despite both have 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`.
|
||||
This is discussed in details in xref:eg-client-http2-response[this section].
|
||||
|
||||
|
||||
include::../../http2.adoc[tag=flowControl]
|
||||
|
||||
How a client application should handle HTTP/2 flow control is discussed in
|
||||
details in xref:eg-client-http2-response[this section].
|
||||
|
||||
[[eg-client-http2-connect]]
|
||||
==== Connecting to the Server
|
||||
|
@ -192,7 +139,7 @@ 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 a HTTP request to the server, the client must send a
|
||||
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.
|
||||
|
@ -230,9 +177,9 @@ Use the `Callback` APIs or `CompletableFuture` APIs to ensure that the second
|
|||
Response events are delivered to the `Stream.Listener` passed to
|
||||
`Session.newStream(...)`.
|
||||
|
||||
A 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.
|
||||
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.
|
||||
|
@ -245,38 +192,7 @@ by implementing the relevant methods in `Stream.Listener`:
|
|||
include::../../{doc_code}/embedded/client/http2/HTTP2ClientDocs.java[tags=responseListener]
|
||||
----
|
||||
|
||||
NOTE: Returning from the `onData(...)` method implicitly demands for
|
||||
more `DATA` frames (unless the one just delivered was the last).
|
||||
Additional `DATA` frames may be delivered immediately if they are available
|
||||
or later, asynchronously, when they arrive.
|
||||
|
||||
Client applications that consume the content buffer within `onData(...)`
|
||||
(for example, writing it to a file, or copying the bytes to another storage)
|
||||
should succeed the callback 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 response content.
|
||||
|
||||
Alternatively, a client application may store away _both_ the buffer and the
|
||||
callback to consume the buffer bytes later.
|
||||
|
||||
IMPORTANT: Completing the `Callback` is very important not only to allow the
|
||||
implementation to reuse the buffer, but also tells the implementation to
|
||||
enlarge the stream and session flow control windows so that the server will
|
||||
be able to send more `DATA` frames without stalling.
|
||||
|
||||
Client applications can also precisely control _when_ to demand more `DATA`
|
||||
frames, by implementing the `onDataDemanded(...)` method instead of
|
||||
`onData(...)`:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../../{doc_code}/embedded/client/http2/HTTP2ClientDocs.java[tags=responseDataDemanded]
|
||||
----
|
||||
|
||||
IMPORTANT: Applications that implement `onDataDemanded(...)` must remember
|
||||
to call `Stream.demand(...)`. If they don't, the implementation will not
|
||||
deliver `DATA` frames and the application will stall threadlessly until an
|
||||
idle timeout fires to close the stream or the session.
|
||||
include::../../http2.adoc[tag=apiFlowControl]
|
||||
|
||||
[[eg-client-http2-reset]]
|
||||
==== Resetting a Request or Response
|
||||
|
@ -298,9 +214,9 @@ include::../../{doc_code}/embedded/client/http2/HTTP2ClientDocs.java[tags=reset]
|
|||
|
||||
HTTP/2 servers have the ability to push resources related to a primary
|
||||
resource.
|
||||
When a HTTP/2 server pushes a resource, it send to the client a `PUSH_PROMISE`
|
||||
frame that contains the request URI and headers that a client would use to
|
||||
request explicitly that 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 xref:eg-client-http2-configure[this section].
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
// Snippets of HTTP/2 documentation that are common between client and server.
|
||||
|
||||
tag::multiplex[]
|
||||
HTTP/2 is a multiplexed protocol: it allows multiple HTTP/2 requests to be sent
|
||||
on the same TCP connection.
|
||||
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.
|
||||
end::multiplex[]
|
||||
|
||||
tag::flowControl[]
|
||||
The HTTP/2 protocol is _flow controlled_ (see
|
||||
link: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 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 until it receives a message from the receiver that the data bytes
|
||||
have been consumed, which enlarges the 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` despite both have 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`.
|
||||
end::flowControl[]
|
||||
|
||||
tag::apiFlowControl[]
|
||||
NOTE: Returning from the `onData(...)` method implicitly demands for
|
||||
more `DATA` frames (unless the one just delivered was the last).
|
||||
Additional `DATA` frames may be delivered immediately if they are available
|
||||
or later, asynchronously, when they arrive.
|
||||
|
||||
Applications that consume the content buffer within `onData(...)`
|
||||
(for example, writing it to a file, or copying the bytes to another storage)
|
||||
should succeed the callback 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, a client application may store away _both_ the buffer and the
|
||||
callback to consume the buffer bytes later, or pass _both_ the buffer and
|
||||
the callback to another asynchronous API (this is typical in proxy
|
||||
applications).
|
||||
|
||||
IMPORTANT: Completing the `Callback` is very important not only to allow the
|
||||
implementation to reuse the buffer, but also 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 also precisely control _when_ to demand more `DATA`
|
||||
frames, by implementing the `onDataDemanded(...)` method instead of
|
||||
`onData(...)`:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::{doc_code}/embedded/HTTP2Docs.java[tags=dataDemanded]
|
||||
----
|
||||
|
||||
IMPORTANT: Applications that implement `onDataDemanded(...)` must remember
|
||||
to call `Stream.demand(...)`. If they don't, the implementation will not
|
||||
deliver `DATA` frames and the application will stall threadlessly until an
|
||||
idle timeout fires to close the stream or the session.
|
||||
end::apiFlowControl[]
|
|
@ -17,6 +17,172 @@
|
|||
//
|
||||
|
||||
[[eg-server-http2]]
|
||||
=== HTTP/2 Server Libraries
|
||||
=== HTTP/2 Server Library
|
||||
|
||||
TODO
|
||||
In the vast majority of cases, server applications should use the generic,
|
||||
high-level, xref:eg-server-http[HTTP server library] that also provides
|
||||
HTTP/2 support via the HTTP/2 ``ConnectionFactory``s as described in details
|
||||
xref:eg-server-http-connector-protocol-http2[here].
|
||||
|
||||
The low-level HTTP/2 server 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:eg-client-http2[HTTP/2 client library].
|
||||
|
||||
[[eg-server-http2-intro]]
|
||||
==== Introduction
|
||||
|
||||
The Maven artifact coordinates for the HTTP/2 client library are the following:
|
||||
|
||||
[source,xml,subs=normal]
|
||||
----
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.http2</groupId>
|
||||
<artifactId>http2-server</artifactId>
|
||||
<version>{version}</version>
|
||||
</dependency>
|
||||
----
|
||||
|
||||
include::../../http2.adoc[tag=multiplex]
|
||||
|
||||
[[eg-server-http2-flow-control]]
|
||||
===== HTTP/2 Flow Control
|
||||
|
||||
include::../../http2.adoc[tag=flowControl]
|
||||
|
||||
How a server application should handle HTTP/2 flow control is discussed in
|
||||
details in xref:eg-server-http2-request[this section].
|
||||
|
||||
[[eg-server-http2-setup]]
|
||||
==== Server Setup
|
||||
|
||||
The low-level HTTP/2 support is provided by
|
||||
`org.eclipse.jetty.http2.server.RawHTTP2ServerConnectionFactory` and
|
||||
`org.eclipse.jetty.http2.api.server.ServerSessionListener`:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../../{doc_code}/embedded/server/http2/HTTP2ServerDocs.java[tags=setup]
|
||||
----
|
||||
|
||||
Where server applications using the
|
||||
xref:eg-server-http[high-level server library] deal with HTTP
|
||||
requests and responses in ``Handler``s, server applications using the
|
||||
low-level HTTP/2 server library deal directly with HTTP/2 __session__s,
|
||||
__stream__s and __frame__s in a `ServerSessionListener` implementation.
|
||||
|
||||
The `ServerSessionListener` interface defines a number of methods that are
|
||||
invoked by the implementation upon the occurrence of HTTP/2 events, and that
|
||||
server applications can override to react to those events.
|
||||
|
||||
Please refer to the `ServerSessionListener`
|
||||
link:{JDURL}/org/eclipse/jetty/http2/api/server/ServerSessionListener.html[javadocs]
|
||||
for the complete list of events.
|
||||
|
||||
The first event is the _accept_ event and happens when a client opens a new
|
||||
TCP connection to the server and the server accepts the connection.
|
||||
This is the first occasion where server applications have access to the HTTP/2
|
||||
`Session` object:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../../{doc_code}/embedded/server/http2/HTTP2ServerDocs.java[tags=accept]
|
||||
----
|
||||
|
||||
After connecting to the server, a compliant HTTP/2 client must send the
|
||||
link:https://tools.ietf.org/html/rfc7540#section-3.5[HTTP/2 client preface],
|
||||
and when the server receives it, it generates the _preface_ event on the server.
|
||||
This is where server applications can customize the connection settings by
|
||||
returning a map of settings that the implementation will send to the client:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../../{doc_code}/embedded/server/http2/HTTP2ServerDocs.java[tags=preface]
|
||||
----
|
||||
|
||||
[[eg-server-http2-request]]
|
||||
==== Receiving a Request
|
||||
|
||||
Receiving an HTTP request from the client, and sending a response, creates
|
||||
a _stream_ that encapsulates the exchange of HTTP/2 frames that compose the
|
||||
request and the response.
|
||||
|
||||
An HTTP request is made of a `HEADERS` frame, that carries the request method,
|
||||
the request URI and the request headers, and optional `DATA` frames that carry
|
||||
the request content.
|
||||
|
||||
Receiving the `HEADERS` frame opens the `Stream`:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../../{doc_code}/embedded/server/http2/HTTP2ServerDocs.java[tags=request]
|
||||
----
|
||||
|
||||
Server applications should return a `Stream.Listener` implementation from
|
||||
`onNewStream(...)` to be notified of events generated by the client, such as
|
||||
`DATA` frames carrying request content, or a `RST_STREAM` frame indicating
|
||||
that the client wants to _reset_ the request, or an idle timeout event
|
||||
indicating that the client was supposed to send more frames but it did not.
|
||||
|
||||
The example below shows how to receive request content:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../../{doc_code}/embedded/server/http2/HTTP2ServerDocs.java[tags=requestContent]
|
||||
----
|
||||
|
||||
include::../../http2.adoc[tag=apiFlowControl]
|
||||
|
||||
[[eg-server-http2-response]]
|
||||
==== Sending a Response
|
||||
|
||||
After receiving an HTTP request, a server application must send an HTTP
|
||||
response.
|
||||
|
||||
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 server application can send a response in this way:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../../{doc_code}/embedded/server/http2/HTTP2ServerDocs.java[tags=response;!exclude]
|
||||
----
|
||||
|
||||
[[eg-server-http2-reset]]
|
||||
==== Resetting a Request
|
||||
|
||||
A server application may decide that it does not want to accept the request.
|
||||
For example, it may throttle the client because it sent too many requests in
|
||||
a time window, or the request is invalid (and does not deserve a proper HTTP
|
||||
response), etc.
|
||||
|
||||
A request can be reset in this way:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../../{doc_code}/embedded/server/http2/HTTP2ServerDocs.java[tags=reset;!exclude]
|
||||
----
|
||||
|
||||
[[eg-server-http2-push]]
|
||||
==== HTTP/2 Push of Resources
|
||||
|
||||
A server application may _push_ secondary resources related to a primary
|
||||
resource.
|
||||
|
||||
A client may inform the server that it does not accept pushed resources
|
||||
(see link:https://tools.ietf.org/html/rfc7540#section-8.2[this section]
|
||||
of the specification) via a `SETTINGS` frame.
|
||||
Server applications must track `SETTINGS` frames and verify whether the
|
||||
client supports HTTP/2 push, and only push if the client supports it:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../../{doc_code}/embedded/server/http2/HTTP2ServerDocs.java[tags=push]
|
||||
----
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpURI;
|
||||
import org.eclipse.jetty.http.HttpVersion;
|
||||
import org.eclipse.jetty.http.MetaData;
|
||||
import org.eclipse.jetty.http2.api.Session;
|
||||
import org.eclipse.jetty.http2.api.Stream;
|
||||
import org.eclipse.jetty.http2.client.HTTP2Client;
|
||||
import org.eclipse.jetty.http2.frames.DataFrame;
|
||||
import org.eclipse.jetty.http2.frames.HeadersFrame;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class HTTP2Docs
|
||||
{
|
||||
public void dataDemanded() throws Exception
|
||||
{
|
||||
HTTP2Client http2Client = new HTTP2Client();
|
||||
http2Client.start();
|
||||
SocketAddress serverAddress = new InetSocketAddress("localhost", 8080);
|
||||
CompletableFuture<Session> sessionCF = http2Client.connect(serverAddress, new Session.Listener.Adapter());
|
||||
Session session = sessionCF.get();
|
||||
|
||||
HttpFields requestHeaders = new HttpFields();
|
||||
requestHeaders.put(HttpHeader.USER_AGENT, "Jetty HTTP2Client {version}");
|
||||
MetaData.Request request = new MetaData.Request("GET", new HttpURI("http://localhost:8080/path"), HttpVersion.HTTP_2, requestHeaders);
|
||||
HeadersFrame headersFrame = new HeadersFrame(request, null, true);
|
||||
|
||||
// tag::dataDemanded[]
|
||||
class Chunk
|
||||
{
|
||||
private final ByteBuffer buffer;
|
||||
private final Callback callback;
|
||||
|
||||
Chunk(ByteBuffer buffer, Callback callback)
|
||||
{
|
||||
this.buffer = buffer;
|
||||
this.callback = callback;
|
||||
}
|
||||
}
|
||||
|
||||
// A queue that consumers poll to consume content asynchronously.
|
||||
Queue<Chunk> dataQueue = new ConcurrentLinkedQueue<>();
|
||||
|
||||
// Implementation of Stream.Listener.onDataDemanded(...)
|
||||
// in case of asynchronous content consumption and demand.
|
||||
Stream.Listener listener = new Stream.Listener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onDataDemanded(Stream stream, DataFrame frame, Callback callback)
|
||||
{
|
||||
// Get the content buffer.
|
||||
ByteBuffer buffer = frame.getData();
|
||||
|
||||
// Store buffer to consume it asynchronously, and wrap the callback.
|
||||
dataQueue.offer(new Chunk(buffer, Callback.from(() ->
|
||||
{
|
||||
// When the buffer has been consumed, then:
|
||||
// A) succeed the nested callback.
|
||||
callback.succeeded();
|
||||
// B) demand more DATA frames.
|
||||
stream.demand(1);
|
||||
}, callback::failed)));
|
||||
|
||||
// Do not demand more content here, to avoid to overflow the queue.
|
||||
}
|
||||
};
|
||||
// end::dataDemanded[]
|
||||
}
|
||||
}
|
|
@ -24,9 +24,7 @@ import java.nio.ByteBuffer;
|
|||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
|
@ -199,11 +197,11 @@ public class HTTP2ClientDocs
|
|||
|
||||
// Send the first DATA frame on the stream, with endStream=false
|
||||
// to signal that there are more frames in this stream.
|
||||
CompletableFuture<Void> dataCF1 = stream.data(new DataFrame(stream.getId(), buffer1, false));
|
||||
CompletableFuture<Stream> dataCF1 = stream.data(new DataFrame(stream.getId(), buffer1, false));
|
||||
|
||||
// Only when the first chunk has been sent we can send the second,
|
||||
// with endStream=true to signal that there are no more frames.
|
||||
dataCF1.thenCompose(ignored -> stream.data(new DataFrame(stream.getId(), buffer2, true)));
|
||||
dataCF1.thenCompose(s -> s.data(new DataFrame(s.getId(), buffer2, true)));
|
||||
// end::newStreamWithData[]
|
||||
}
|
||||
|
||||
|
@ -260,60 +258,6 @@ public class HTTP2ClientDocs
|
|||
// end::responseListener[]
|
||||
}
|
||||
|
||||
public void responseDataDemanded() throws Exception
|
||||
{
|
||||
HTTP2Client http2Client = new HTTP2Client();
|
||||
http2Client.start();
|
||||
SocketAddress serverAddress = new InetSocketAddress("localhost", 8080);
|
||||
CompletableFuture<Session> sessionCF = http2Client.connect(serverAddress, new Session.Listener.Adapter());
|
||||
Session session = sessionCF.get();
|
||||
|
||||
HttpFields requestHeaders = new HttpFields();
|
||||
requestHeaders.put(HttpHeader.USER_AGENT, "Jetty HTTP2Client {version}");
|
||||
MetaData.Request request = new MetaData.Request("GET", new HttpURI("http://localhost:8080/path"), HttpVersion.HTTP_2, requestHeaders);
|
||||
HeadersFrame headersFrame = new HeadersFrame(request, null, true);
|
||||
|
||||
// tag::responseDataDemanded[]
|
||||
class Chunk
|
||||
{
|
||||
private final ByteBuffer buffer;
|
||||
private final Callback callback;
|
||||
|
||||
Chunk(ByteBuffer buffer, Callback callback)
|
||||
{
|
||||
this.buffer = buffer;
|
||||
this.callback = callback;
|
||||
}
|
||||
}
|
||||
|
||||
// A queue that consumers poll to consume content asynchronously.
|
||||
Queue<Chunk> dataQueue = new ConcurrentLinkedQueue<>();
|
||||
|
||||
// Open a Stream by sending the HEADERS frame.
|
||||
session.newStream(headersFrame, new Stream.Listener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onDataDemanded(Stream stream, DataFrame frame, Callback callback)
|
||||
{
|
||||
// Get the content buffer.
|
||||
ByteBuffer buffer = frame.getData();
|
||||
|
||||
// Store buffer to consume it asynchronously, and wrap the callback.
|
||||
dataQueue.offer(new Chunk(buffer, Callback.from(() ->
|
||||
{
|
||||
// When the buffer has been consumed, then:
|
||||
// A) succeed the nested callback.
|
||||
callback.succeeded();
|
||||
// B) demand more DATA frames.
|
||||
stream.demand(1);
|
||||
}, callback::failed)));
|
||||
|
||||
// Do not demand more content here, to avoid to overflow the queue.
|
||||
}
|
||||
});
|
||||
// end::responseDataDemanded[]
|
||||
}
|
||||
|
||||
public void reset() throws Exception
|
||||
{
|
||||
HTTP2Client http2Client = new HTTP2Client();
|
||||
|
|
|
@ -0,0 +1,316 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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.server.http2;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http.HttpURI;
|
||||
import org.eclipse.jetty.http.HttpVersion;
|
||||
import org.eclipse.jetty.http.MetaData;
|
||||
import org.eclipse.jetty.http2.ErrorCode;
|
||||
import org.eclipse.jetty.http2.api.Session;
|
||||
import org.eclipse.jetty.http2.api.Stream;
|
||||
import org.eclipse.jetty.http2.api.server.ServerSessionListener;
|
||||
import org.eclipse.jetty.http2.frames.DataFrame;
|
||||
import org.eclipse.jetty.http2.frames.HeadersFrame;
|
||||
import org.eclipse.jetty.http2.frames.PushPromiseFrame;
|
||||
import org.eclipse.jetty.http2.frames.ResetFrame;
|
||||
import org.eclipse.jetty.http2.frames.SettingsFrame;
|
||||
import org.eclipse.jetty.http2.server.RawHTTP2ServerConnectionFactory;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.ServerConnector;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.resource.Resource;
|
||||
|
||||
import static java.lang.System.Logger.Level.INFO;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class HTTP2ServerDocs
|
||||
{
|
||||
public void setup() throws Exception
|
||||
{
|
||||
// tag::setup[]
|
||||
// Create a Server instance.
|
||||
Server server = new Server();
|
||||
|
||||
ServerSessionListener sessionListener = new ServerSessionListener.Adapter();
|
||||
|
||||
// Create a ServerConnector with RawHTTP2ServerConnectionFactory.
|
||||
RawHTTP2ServerConnectionFactory http2 = new RawHTTP2ServerConnectionFactory(sessionListener);
|
||||
|
||||
// Configure RawHTTP2ServerConnectionFactory, for example:
|
||||
|
||||
// Configure the max number of concurrent requests.
|
||||
http2.setMaxConcurrentStreams(128);
|
||||
// Enable support for CONNECT.
|
||||
http2.setConnectProtocolEnabled(true);
|
||||
|
||||
// Create the ServerConnector.
|
||||
ServerConnector connector = new ServerConnector(server, http2);
|
||||
|
||||
// Add the Connector to the Server
|
||||
server.addConnector(connector);
|
||||
|
||||
// Start the Server so it starts accepting connections from clients.
|
||||
server.start();
|
||||
// end::setup[]
|
||||
}
|
||||
|
||||
public void accept()
|
||||
{
|
||||
// tag::accept[]
|
||||
ServerSessionListener sessionListener = new ServerSessionListener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onAccept(Session session)
|
||||
{
|
||||
InetSocketAddress remoteAddress = session.getRemoteAddress();
|
||||
System.getLogger("http2").log(INFO, "Connection from {0}", remoteAddress);
|
||||
}
|
||||
};
|
||||
// end::accept[]
|
||||
}
|
||||
|
||||
public void preface()
|
||||
{
|
||||
// tag::preface[]
|
||||
ServerSessionListener sessionListener = new ServerSessionListener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public Map<Integer, Integer> onPreface(Session session)
|
||||
{
|
||||
// Customize the settings, for example:
|
||||
Map<Integer, Integer> settings = new HashMap<>();
|
||||
|
||||
// Tell the client that HTTP/2 push is disabled.
|
||||
settings.put(SettingsFrame.ENABLE_PUSH, 0);
|
||||
|
||||
return settings;
|
||||
}
|
||||
};
|
||||
// end::preface[]
|
||||
}
|
||||
|
||||
public void request()
|
||||
{
|
||||
// tag::request[]
|
||||
ServerSessionListener sessionListener = new ServerSessionListener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
|
||||
{
|
||||
// This is the "new stream" event, so it's guaranteed to be a request.
|
||||
MetaData.Request request = (MetaData.Request)frame.getMetaData();
|
||||
|
||||
// Return a Stream.Listener to handle the request events,
|
||||
// for example request content events or a request reset.
|
||||
return new Stream.Listener.Adapter();
|
||||
}
|
||||
};
|
||||
// end::request[]
|
||||
}
|
||||
|
||||
public void requestContent()
|
||||
{
|
||||
// tag::requestContent[]
|
||||
ServerSessionListener sessionListener = new ServerSessionListener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
|
||||
{
|
||||
MetaData.Request request = (MetaData.Request)frame.getMetaData();
|
||||
// Return a Stream.Listener to handle the request events.
|
||||
return new Stream.Listener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onData(Stream stream, DataFrame frame, Callback callback)
|
||||
{
|
||||
// Get the content buffer.
|
||||
ByteBuffer buffer = frame.getData();
|
||||
|
||||
// Consume the buffer, here - as an example - just log it.
|
||||
System.getLogger("http2").log(INFO, "Consuming buffer {0}", buffer);
|
||||
|
||||
// Tell the implementation that the buffer has been consumed.
|
||||
callback.succeeded();
|
||||
|
||||
// By returning from the method, implicitly tell the implementation
|
||||
// to deliver to this method more DATA frames when they are available.
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
// end::requestContent[]
|
||||
}
|
||||
|
||||
public void response()
|
||||
{
|
||||
// tag::response[]
|
||||
ServerSessionListener sessionListener = new ServerSessionListener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
|
||||
{
|
||||
// Send a response after reading the request.
|
||||
MetaData.Request request = (MetaData.Request)frame.getMetaData();
|
||||
if (frame.isEndStream())
|
||||
{
|
||||
respond(stream, request);
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new Stream.Listener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onData(Stream stream, DataFrame frame, Callback callback)
|
||||
{
|
||||
// Consume the request content.
|
||||
callback.succeeded();
|
||||
if (frame.isEndStream())
|
||||
respond(stream, request);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private void respond(Stream stream, MetaData.Request request)
|
||||
{
|
||||
// Prepare the response HEADERS frame.
|
||||
|
||||
// The response HTTP status and HTTP headers.
|
||||
MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, new HttpFields());
|
||||
|
||||
if (HttpMethod.GET.is(request.getMethod()))
|
||||
{
|
||||
// The response content.
|
||||
ByteBuffer resourceBytes = getResourceBytes(request);
|
||||
|
||||
// Send the HEADERS frame with the response status and headers,
|
||||
// and a DATA frame with the response content bytes.
|
||||
stream.headers(new HeadersFrame(stream.getId(), response, null, false))
|
||||
.thenCompose(s -> s.data(new DataFrame(s.getId(), resourceBytes, true)));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Send just the HEADERS frame with the response status and headers.
|
||||
stream.headers(new HeadersFrame(stream.getId(), response, null, true));
|
||||
}
|
||||
}
|
||||
// tag::exclude[]
|
||||
|
||||
private ByteBuffer getResourceBytes(MetaData.Request request)
|
||||
{
|
||||
return ByteBuffer.allocate(1024);
|
||||
}
|
||||
// end::exclude[]
|
||||
};
|
||||
// end::response[]
|
||||
}
|
||||
|
||||
public void reset()
|
||||
{
|
||||
float maxRequestRate = 0F;
|
||||
// tag::reset[]
|
||||
ServerSessionListener sessionListener = new ServerSessionListener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
|
||||
{
|
||||
float requestRate = calculateRequestRate();
|
||||
|
||||
if (requestRate > maxRequestRate)
|
||||
{
|
||||
stream.reset(new ResetFrame(stream.getId(), ErrorCode.REFUSED_STREAM_ERROR.code), Callback.NOOP);
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
// The request is accepted.
|
||||
MetaData.Request request = (MetaData.Request)frame.getMetaData();
|
||||
// Return a Stream.Listener to handle the request events.
|
||||
return new Stream.Listener.Adapter();
|
||||
}
|
||||
}
|
||||
// tag::exclude[]
|
||||
|
||||
private float calculateRequestRate()
|
||||
{
|
||||
return 0F;
|
||||
}
|
||||
// end::exclude[]
|
||||
};
|
||||
// end::reset[]
|
||||
}
|
||||
|
||||
public void push() throws Exception
|
||||
{
|
||||
// tag::push[]
|
||||
// The favicon bytes.
|
||||
ByteBuffer faviconBuffer = BufferUtil.toBuffer(Resource.newResource("/path/to/favicon.ico"), true);
|
||||
|
||||
ServerSessionListener sessionListener = new ServerSessionListener.Adapter()
|
||||
{
|
||||
// By default, push is enabled.
|
||||
private boolean pushEnabled = true;
|
||||
|
||||
@Override
|
||||
public void onSettings(Session session, SettingsFrame frame)
|
||||
{
|
||||
// Check whether the client sent an ENABLE_PUSH setting.
|
||||
Map<Integer, Integer> settings = frame.getSettings();
|
||||
Integer enablePush = settings.get(SettingsFrame.ENABLE_PUSH);
|
||||
if (enablePush != null)
|
||||
pushEnabled = enablePush == 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
|
||||
{
|
||||
MetaData.Request request = (MetaData.Request)frame.getMetaData();
|
||||
if (pushEnabled && request.getURIString().endsWith("/index.html"))
|
||||
{
|
||||
// Push the favicon.
|
||||
HttpURI pushedURI = new HttpURI(request.getURI());
|
||||
pushedURI.setPath("/favicon.ico");
|
||||
MetaData.Request pushedRequest = new MetaData.Request("GET", pushedURI, HttpVersion.HTTP_2, new HttpFields());
|
||||
PushPromiseFrame promiseFrame = new PushPromiseFrame(stream.getId(), 0, pushedRequest);
|
||||
stream.push(promiseFrame, new Stream.Listener.Adapter())
|
||||
.thenCompose(pushedStream ->
|
||||
{
|
||||
// Send the favicon "response".
|
||||
MetaData.Response pushedResponse = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, new HttpFields());
|
||||
return pushedStream.headers(new HeadersFrame(pushedStream.getId(), pushedResponse, null, false))
|
||||
.thenCompose(pushed -> pushed.data(new DataFrame(pushed.getId(), faviconBuffer, true)));
|
||||
});
|
||||
}
|
||||
// Return a Stream.Listener to handle the request events.
|
||||
return new Stream.Listener.Adapter();
|
||||
}
|
||||
};
|
||||
// end::push[]
|
||||
}
|
||||
}
|
|
@ -52,6 +52,19 @@ public interface Stream
|
|||
*/
|
||||
public Session getSession();
|
||||
|
||||
/**
|
||||
* <p>Sends the given HEADERS {@code frame} representing an HTTP response.</p>
|
||||
*
|
||||
* @param frame the HEADERS frame to send
|
||||
* @return the CompletableFuture that gets notified when the frame has been sent
|
||||
*/
|
||||
public default CompletableFuture<Stream> headers(HeadersFrame frame)
|
||||
{
|
||||
Promise.Completable<Stream> result = new Promise.Completable<>();
|
||||
headers(frame, Callback.from(() -> result.succeeded(this), result::failed));
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Sends the given HEADERS {@code frame} representing an HTTP response.</p>
|
||||
*
|
||||
|
@ -60,6 +73,20 @@ public interface Stream
|
|||
*/
|
||||
public void headers(HeadersFrame frame, Callback callback);
|
||||
|
||||
/**
|
||||
* <p>Sends the given PUSH_PROMISE {@code frame}.</p>
|
||||
*
|
||||
* @param frame the PUSH_PROMISE frame to send
|
||||
* @param listener the listener that gets notified of stream events
|
||||
* @return the CompletableFuture that gets notified of the pushed stream creation
|
||||
*/
|
||||
public default CompletableFuture<Stream> push(PushPromiseFrame frame, Listener listener)
|
||||
{
|
||||
Promise.Completable<Stream> result = new Promise.Completable<>();
|
||||
push(frame, result, listener);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Sends the given PUSH_PROMISE {@code frame}.</p>
|
||||
*
|
||||
|
@ -75,10 +102,10 @@ public interface Stream
|
|||
* @param frame the DATA frame to send
|
||||
* @return the CompletableFuture that gets notified when the frame has been sent
|
||||
*/
|
||||
public default CompletableFuture<Void> data(DataFrame frame)
|
||||
public default CompletableFuture<Stream> data(DataFrame frame)
|
||||
{
|
||||
Callback.Completable result = new Callback.Completable();
|
||||
data(frame, result);
|
||||
Promise.Completable<Stream> result = new Promise.Completable<>();
|
||||
data(frame, Callback.from(() -> result.succeeded(this), result::failed));
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
@ -37,6 +37,11 @@ public class RawHTTP2ServerConnectionFactory extends AbstractHTTP2ServerConnecti
|
|||
{
|
||||
private final ServerSessionListener listener;
|
||||
|
||||
public RawHTTP2ServerConnectionFactory(ServerSessionListener listener)
|
||||
{
|
||||
this(new HttpConfiguration(), listener);
|
||||
}
|
||||
|
||||
public RawHTTP2ServerConnectionFactory(HttpConfiguration httpConfiguration, ServerSessionListener listener)
|
||||
{
|
||||
super(httpConfiguration);
|
||||
|
|
Loading…
Reference in New Issue