Improvements to the Jetty server documentation.

Documented HttpChannel.Listener events.

Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
This commit is contained in:
Simone Bordet 2020-04-14 16:01:37 +02:00
parent 474fa8b8e9
commit 79c90402e5
3 changed files with 154 additions and 3 deletions

View File

@ -40,7 +40,7 @@ connections on the listening port.
When a connection is accepted, `ServerConnector` wraps the accepted
`SocketChannel` and passes it to the
xref:eg-io-arch-selector-manager[`SelectorManager`].
Therefore there is a little moment where the acceptor thread is not accepting
Therefore, there is a little moment where the acceptor thread is not accepting
new connections because it is busy wrapping the just accepted one to pass it
to the `SelectorManager`.
Connections that are ready to be accepted but are not accepted yet are queued

View File

@ -74,8 +74,110 @@ by the Servlet specification.
All these features are provided by the Jetty Server Libraries and server
applications only need to put the required components together to provide
all the required features.
// TODO: link to a place where we discuss the handlers in more details.
all the required features, and it is discussed in details in
xref:eg-server-http-handler-use[this section].
[[eg-server-http-request-processing]]
==== Server Request Processing
The Jetty HTTP request processing is outlined below in the diagram below.
Request handing is slightly different for each protocol; in HTTP/2 Jetty
takes into account multiplexing, something that is not present in HTTP/1.1.
However, the diagram below captures the essence of request handling that
is common among all protocols that carry HTTP requests.
First, the Jetty I/O layer emits an event that a socket has data to read.
This event is converted to a call to `AbstractConnection.onFillable()`,
where the `Connection` first reads from the `EndPoint` into a `ByteBuffer`,
and then calls a protocol specific parser to parse the bytes in the
`ByteBuffer`.
The parser emit events such that are protocol specific; the HTTP/2 parser,
for example, emits events for each HTTP/2 frame that has been parsed.
The parser events are then converted to protocol independent events such
as _"request start"_, _"request headers"_, _"request content chunk"_, etc.
that in turn are converted into method calls to `HttpChannel`.
When enough of the HTTP request is arrived, the `Connection` calls
`HttpChannel.handle()` that calls the `Handler` chain, that eventually
calls the server application code.
[plantuml]
----
skinparam backgroundColor transparent
skinparam monochrome true
skinparam shadowing false
participant ManagedSelector
participant EndPoint
participant Connection
participant Parser
participant HttpChannel
participant Server
participant Handlers
ManagedSelector -> EndPoint : read ready
EndPoint -> Connection : onFillable()
Connection -> EndPoint : fill()
EndPoint --> Connection
Connection -> Parser : parse()
Parser -> HttpChannel : events
Connection -> HttpChannel : handle()
HttpChannel -> Server : handle()
Server -> Handlers : handle()
----
[[eg-server-http-channel-events]]
===== HttpChannel Events
The central component processing HTTP requests is `HttpChannel`.
There is a 1-to-1 relationship between an HTTP request/response and an
`HttpChannel`, no matter what is the specific protocol that carries the
HTTP request over the network (HTTP/1.1, HTTP/2 or FastCGI).
Advanced server applications may be interested in the progress of the
processing of an HTTP request/response by `HttpChannel`.
A typical case is to know exactly _when_ the HTTP request/response
processing is complete, for example to monitor processing times.
NOTE: A `Handler` or a Servlet `Filter` may not report precisely when
an HTTP request/response processing is finished.
A server application may write a small enough content that is aggregated
by Jetty for efficiency reasons; the write returns immediately, but
nothing has been written to the network yet.
`HttpChannel` notifies ``HttpChannel.Listener``s of the progress of the
HTTP request/response handling.
Currently, the following events are available:
* `requestBegin`
* `beforeDispatch`
* `dispatchFailure`
* `afterDispatch`
* `requestContent`
* `requestContentEnd`
* `requestTrailers`
* `requestEnd`
* `responseBegin`
* `responseCommit`
* `responseContent`
* `responseFailure`
* `responseEnd`
* `complete`
Please refer to the `HttpChannel.Listener`
link:{JDURL}/org/eclipse/jetty/server/HttpChannel.Listener.html[javadocs]
for the complete list of events.
Server applications can register `HttpChannel.Listener` by adding them as
beans to the `Connector`:
[source,java,indent=0]
----
include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=httpChannelListener]
----
include::server-http-connector.adoc[]
include::server-http-handler.adoc[]

View File

@ -20,6 +20,8 @@ package embedded.server.http;
import java.io.IOException;
import java.util.EnumSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import javax.servlet.DispatcherType;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
@ -37,6 +39,7 @@ import org.eclipse.jetty.rewrite.handler.RedirectRegexRule;
import org.eclipse.jetty.rewrite.handler.RewriteHandler;
import org.eclipse.jetty.rewrite.handler.RewriteRegexRule;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.HttpChannel;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.ProxyConnectionFactory;
@ -68,6 +71,8 @@ import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.webapp.WebAppContext;
import static java.lang.System.Logger.Level.INFO;
@SuppressWarnings("unused")
public class HTTPServerDocs
{
@ -104,6 +109,50 @@ public class HTTPServerDocs
// end::simple[]
}
public void httpChannelListener() throws Exception
{
// tag::httpChannelListener[]
class TimingHttpChannelListener implements HttpChannel.Listener
{
private final ConcurrentMap<Request, Long> times = new ConcurrentHashMap<>();
@Override
public void onRequestBegin(Request request)
{
times.put(request, System.nanoTime());
}
@Override
public void onComplete(Request request)
{
long begin = times.remove(request);
long elapsed = System.nanoTime() - begin;
System.getLogger("timing").log(INFO, "Request {0} took {1} ns", request, elapsed);
}
}
Server server = new Server();
Connector connector = new ServerConnector(server);
server.addConnector(connector);
// Add the HttpChannel.Listener as bean to the connector.
connector.addBean(new TimingHttpChannelListener());
// Set a simple Handler to handle requests/responses.
server.setHandler(new AbstractHandler()
{
@Override
public void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response)
{
jettyRequest.setHandled(true);
}
});
server.start();
// end::httpChannelListener[]
}
public void configureConnector() throws Exception
{
// tag::configureConnector[]