From b238577faf084ec75d7ec2e3daba1fcd37ebaffe Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Thu, 28 Sep 2023 17:24:10 +0200 Subject: [PATCH] Added `Connection.Listener` documentation. Signed-off-by: Simone Bordet --- .../asciidoc/programming-guide/arch-io.adoc | 43 +++++++++--- .../docs/programming/SelectorManagerDocs.java | 65 ++++++++++++++++++- 2 files changed, 97 insertions(+), 11 deletions(-) diff --git a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/arch-io.adoc b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/arch-io.adoc index f83b8b666b39..c0f9026c3c5c 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/arch-io.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/arch-io.adoc @@ -14,19 +14,19 @@ [[pg-arch-io]] === Jetty I/O Architecture -Jetty libraries (both client and server) use Java NIO to handle I/O, so that at its core Jetty I/O is completely non-blocking. +The Jetty libraries (both client and server) use Java NIO to handle I/O, so that at its core Jetty I/O is completely non-blocking. [[pg-arch-io-selector-manager]] ==== Jetty I/O: `SelectorManager` -The core class of Jetty I/O is link:{javadoc-url}/org/eclipse/jetty/io/SelectorManager.html[`SelectorManager`]. +The main class of The Jetty I/O library is link:{javadoc-url}/org/eclipse/jetty/io/SelectorManager.html[`SelectorManager`]. `SelectorManager` manages internally a configurable number of link:{javadoc-url}/org/eclipse/jetty/io/ManagedSelector.html[`ManagedSelector`]s. Each `ManagedSelector` wraps an instance of `java.nio.channels.Selector` that in turn manages a number of `java.nio.channels.SocketChannel` instances. NOTE: TODO: add image -`SocketChannel` instances can be created by clients when connecting to a server and by a server when accepting connections from clients. +`SocketChannel` instances are typically created by the Jetty implementation, on client-side when connecting to a server and on server-side when accepting connections from clients. In both cases the `SocketChannel` instance is passed to `SelectorManager` (which passes it to `ManagedSelector` and eventually to `java.nio.channels.Selector`) to be registered for use within Jetty. It is possible for an application to create the `SocketChannel` instances outside Jetty, even perform some initial network traffic also outside Jetty (for example for authentication purposes), and then pass the `SocketChannel` instance to `SelectorManager` for use within Jetty. @@ -56,9 +56,9 @@ include::{doc_code}/org/eclipse/jetty/docs/programming/SelectorManagerDocs.java[ For example, an HTTP/1.1 server-side `Connection` implementation is responsible to deserialize HTTP/1.1 request bytes into an HTTP request object. Conversely, an HTTP/1.1 client-side `Connection` implementation is responsible to deserialize HTTP/1.1 response bytes into an HTTP response object. -`Connection` is the abstraction that implements the reading side of a specific protocol such as HTTP/1.1, or HTTP/2, or HTTP/3, or WebSocket: it is able to read incoming communication in that protocol. +`Connection` is the abstraction that implements the reading side of a specific protocol such as HTTP/1.1, or HTTP/2, or HTTP/3, or WebSocket: it is able to read and parse incoming bytes in that protocol. -The writing side for a specific protocol _may_ be implemented in the `Connection` but may also be implemented in other components, although eventually the bytes to be written will be written through the `EndPoint`. +The writing side for a specific protocol _may_ be implemented in the `Connection` but may also be implemented in other components, although eventually the bytes to write will be written through the `EndPoint`. While there are primarily only two implementations of `EndPoint`,link:{javadoc-url}/org/eclipse/jetty/io/SocketChannelEndPoint.html[`SocketChannelEndPoint`] for TCP and link:{javadoc-url}/org/eclipse/jetty/io/DatagramChannelEndPoint.html[`DatagramChannelEndPoint`] for UDP (used both on the client-side and on the server-side), there are many implementations of `Connection`, typically two for each protocol (one for the client-side and one for the server-side). @@ -103,13 +103,13 @@ The `EndPoint` APIs are typically called by `Connection` implementations, see xr `Connection` instances have two lifecycle methods: -* `Connection.onOpen()`, invoked when the `Connection` is associated with the `EndPoint` -* `Connection.onClose(Throwable)`, invoked when the `Connection` is disassociated from the `EndPoint`, where the `Throwable` parameter indicates whether the disassociation was normal (when the parameter is `null`) or was due to an error (when the parameter is not `null`) +* `Connection.onOpen()`, invoked when the `Connection` is associated with the `EndPoint`. +* `Connection.onClose(Throwable)`, invoked when the `Connection` is disassociated from the `EndPoint`, where the `Throwable` parameter indicates whether the disassociation was normal (when the parameter is `null`) or was due to an error (when the parameter is not `null`). When a `Connection` is first created, it is not registered for any Java NIO event. -It is therefore typical to implement `onOpen()` to call `EndPoint.fillInterested(Callback)` so that the `Connection` declares interest for read events and it is invoked (via the `Callback`) when the read event happens. +It is therefore typical to implement `onOpen()` to call `EndPoint.fillInterested(Callback)` so that the `Connection` declares interest for read events, and it is invoked (via the `Callback`) when the read event happens. -Abstract class `AbstractConnection` partially implements `Connection` and provides simpler APIs. +The abstract class `AbstractConnection` partially implements `Connection` and provides simpler APIs. The example below shows a typical implementation that extends `AbstractConnection`: [source,java,indent=0] @@ -120,7 +120,30 @@ include::{doc_code}/org/eclipse/jetty/docs/programming/SelectorManagerDocs.java[ [[pg-arch-io-connection-listener]] ===== Jetty I/O: `Connection.Listener` -TODO +The Jetty I/O library allows applications to register xref:pg-arch-listener[event listeners] for the `Connection` events "opened" and "closed" via the interface `Connection.Listener`. + +This is useful in many cases, for example: + +* Gather statistics about connection lifecycle, such as time of creation and duration. +* Gather statistics about the number of concurrent connections, and take action if a threshold is exceeded. +* Gather statistics about the number of bytes read and written, and the number of "messages" read and written, where "messages" may mean HTTP/1.1 requests or responses, or WebSocket frames, or HTTP/2 frames, etc. +* Gather statistics about the different types of connections being opened (TLS, HTTP/1.1, HTTP/2, WebSocket, etc.). +* Etc. + +`Connection.Listener` implementations must be added as beans to a server-side `Connector`, or to client-side `HttpClient`, `WebSocketClient`, `HTTP2Client` or `HTTP3Client`. +You can add as beans many `Connection.Listener` objects, each with its own logic, so that you can separate different logics into different `Connection.Listener` implementations. + +The Jetty I/O library provides useful `Connection.Listener` implementations that you should evaluate before writing your own: + +* link:{javadoc-url}/org/eclipse/jetty/io/ConnectionStatistics[`ConnectionStatistics`] +* link:{javadoc-url}/org/eclipse/jetty/server/ConnectionLimit[`ConnectionLimit`] + +Here is a simple example of a `Connection.Listener` used both on the client and on the server: + +[source,java,indent=0] +---- +include::{doc_code}/org/eclipse/jetty/docs/programming/SelectorManagerDocs.java[tags=connectionListener] +---- [[pg-arch-io-echo]] ==== Jetty I/O: TCP Network Echo diff --git a/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/SelectorManagerDocs.java b/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/SelectorManagerDocs.java index 74ced90a8887..cc60b78e7f92 100644 --- a/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/SelectorManagerDocs.java +++ b/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/SelectorManagerDocs.java @@ -20,10 +20,16 @@ import java.nio.channels.SocketChannel; import java.util.Map; import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicInteger; +import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.io.AbstractConnection; +import org.eclipse.jetty.io.Connection; +import org.eclipse.jetty.io.ConnectionStatistics; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.SelectorManager; +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.IteratingCallback; @@ -79,18 +85,75 @@ public void onOpen() super.onOpen(); // Declare interest for fill events. + // When the fill event happens, method onFillable() below is invoked. fillInterested(); } @Override public void onFillable() { - // Called when a fill event happens. + // Invoked when a fill event happens. } } // end::connection[] } + public void connectionListener() throws Exception + { + // tag::connectionListener[] + class ThresholdConnectionListener implements Connection.Listener + { + private final AtomicInteger connections = new AtomicInteger(); + + private int threshold; + private boolean notified; + + public ThresholdConnectionListener(int threshold) + { + this.threshold = threshold; + } + + @Override + public void onOpened(Connection connection) + { + int count = connections.incrementAndGet(); + if (count > threshold && !notified) + { + notified = true; + System.getLogger("connection.threshold").log(System.Logger.Level.WARNING, "Connection threshold exceeded"); + } + } + + @Override + public void onClosed(Connection connection) + { + int count = connections.decrementAndGet(); + // Reset the alert when we are below 90% of the threshold. + if (count < threshold * 0.9F) + notified = false; + } + } + + // Configure server-side connectors with Connection.Listeners. + Server server = new Server(); + ServerConnector connector = new ServerConnector(server); + server.addConnector(connector); + // Add statistics. + connector.addBean(new ConnectionStatistics()); + // Add your own Connection.Listener. + connector.addBean(new ThresholdConnectionListener(2048)); + server.start(); + + // Configure client-side HttpClient with Connection.Listeners. + HttpClient httpClient = new HttpClient(); + // Add statistics. + httpClient.addBean(new ConnectionStatistics()); + // Add your own Connection.Listener. + httpClient.addBean(new ThresholdConnectionListener(512)); + httpClient.start(); + // end::connectionListener[] + } + public void echoWrong() { // tag::echo-wrong[]