From 79c90402e5ca602fa3d58bad3493ce3fa4064f84 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Tue, 14 Apr 2020 16:01:37 +0200 Subject: [PATCH] Improvements to the Jetty server documentation. Documented HttpChannel.Listener events. Signed-off-by: Simone Bordet --- .../server/http/server-http-connector.adoc | 2 +- .../server/http/server-http.adoc | 106 +++++++++++++++++- .../embedded/server/http/HTTPServerDocs.java | 49 ++++++++ 3 files changed, 154 insertions(+), 3 deletions(-) diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-connector.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-connector.adoc index d5598d5adeb..7af8e7dc4df 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-connector.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-connector.adoc @@ -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 diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http.adoc index 39334067a3b..982ed874200 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http.adoc @@ -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[] diff --git a/jetty-documentation/src/main/java/embedded/server/http/HTTPServerDocs.java b/jetty-documentation/src/main/java/embedded/server/http/HTTPServerDocs.java index d9a8395a693..f3e5f786764 100644 --- a/jetty-documentation/src/main/java/embedded/server/http/HTTPServerDocs.java +++ b/jetty-documentation/src/main/java/embedded/server/http/HTTPServerDocs.java @@ -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 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[]