Skip to content

Commit

Permalink
Improved various Handler documentation
Browse files Browse the repository at this point in the history
* Added and documented missing modules for QoSHandler, SizeLimitHandler and ThreadLimitHandler.
* Moved SizeLimitHandler from oej.server to oej.server.handler.

Signed-off-by: Simone Bordet <[email protected]>
  • Loading branch information
sbordet authored Nov 17, 2024
1 parent 24580b3 commit dd31feb
Show file tree
Hide file tree
Showing 18 changed files with 575 additions and 194 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1489,8 +1489,8 @@ public boolean handle(Request request, Response response, Callback callback)
// Set the max number of concurrent requests,
// for example in relation to the thread pool.
qosHandler.setMaxRequestCount(maxThreads / 2);
// A suspended request may stay suspended for at most 15 seconds.
qosHandler.setMaxSuspend(Duration.ofSeconds(15));
// A suspended request may stay suspended for at most 5 seconds.
qosHandler.setMaxSuspend(Duration.ofSeconds(5));
server.setHandler(qosHandler);

// Provide quality of service to the shop
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,17 @@ However, we the RMI server is configured to bind to `localhost`, i.e. `127.0.0.1

If the system property `java.rmi.server.hostname` is not specified, the RMI client will try to connect to `127.0.1.1` (because that's what in the RMI stub) and fail because nothing is listening on that address.

[[qos]]
== Module `qos`

The `qos` module installs the `QoSHandler` at the root of the `Handler` tree; the `QoSHandler` applies limits to the number of concurrent requests, as explained in xref:programming-guide:server/http.adoc#handler-use-qos[this section].

The module properties are:

----
include::{jetty-home}/modules/qos.mod[tags=documentation]
----

[[requestlog]]
== Module `requestlog`

Expand Down Expand Up @@ -545,6 +556,17 @@ The module properties to configure the Jetty server scheduler are:
include::{jetty-home}/modules/server.mod[tags=documentation-scheduler-config]
----

[[size-limit]]
== Module `size-limit`

The `size-limit` module installs the `SizeLimitHandler` at the root of the `Handler` tree; the `SizeLimitHandler` applies limits to the request content and the response content, as explained in xref:programming-guide:server/http.adoc#handler-use-size-limit[this section].

The module properties are:

----
include::{jetty-home}/modules/size-limit.mod[tags=documentation]
----

[[ssl]]
== Module `ssl`

Expand Down Expand Up @@ -702,6 +724,17 @@ include::{jetty-home}/modules/test-keystore.mod[]
Note how properties `jetty.sslContext.keyStorePath` and `jetty.sslContext.keyStorePassword` are configured, only if not already set (via the `?=` operator), directly in the module file, rather than in a `+*.ini+` file.
This is done to avoid that these properties accidentally overwrite a real KeyStore configuration.

[[thread-limit]]
== Module `thread-limit`

The `thread-limit` module installs the `ThreadLimitHandler` at the root of the `Handler` tree; the `ThreadLimitHandler` applies limits to the number of concurrent threads per remote IP address, as explained in xref:programming-guide:server/http.adoc#handler-use-thread-limit[this section].

The module properties are:

----
include::{jetty-home}/modules/thread-limit.mod[tags=documentation]
----

[[threadpool]]
== Module `threadpool`

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -814,6 +814,44 @@ include::code:example$src/main/java/org/eclipse/jetty/docs/programming/server/ht
If the resource is not found, `ResourceHandler` will not return `true` from the `handle(\...)` method, so what happens next depends on the `Handler` tree structure.
See also <<handler-use-default,how to use>> `DefaultHandler`.

[[handler-use-conditional]]
==== ConditionalHandler

`ConditionalHandler` is an abstract `Handler` that matches conditions about the request, and allows subclasses to handle the request differently depending on whether the conditions have been met or not.

You may subclass `ConditionalHandler.Abstract` and override these methods:

* `boolean onConditionsMet(Request, Response, Callback)`, to handle the request when the conditions are met.
* `boolean onConditionsNotMet(Request, Response, Callback)`, to handle the request when the conditions are not met.

Alternatively, you can use the following `ConditionalHandler` subclasses that implement common behaviors:

* `ConditionalHandler.ElseNext`:
** When conditions are met, you have to write the implementation.
** When conditions are not met, the handling of the request is forwarded to the next `Handler`.
* `ConditionalHandler.Reject`:
** When conditions are met, a response is written with a configurable, non-2xx, HTTP status code.
** When conditions are not met, the handling of the request is forwarded to the next `Handler`.
* `ConditionalHandler.SkipNext`:
** When conditions are met, the handling of the request is forwarded to the next-next `Handler`, skipping the next `Handler`.
** When conditions are not met, the handling of the request is forwarded to the next `Handler`.
* `ConditionalHandler.DontHandle`:
** When conditions are met, the request is not handled.
** When conditions are not met, the handling of the request is forwarded to the next `Handler`.

Conditions can be specified using an include/exclude mechanism, where a condition match if it is not excluded, and either it is included or the include set is empty.

For example, you can specify to match only the specific HTTP request method `DELETE` by adding it to the include set; or you can specify to match all HTTP request methods apart `TRACE` by adding it to the exclude set.

`ConditionalHandler` allows you to specify conditions for the following request properties:

* The request HTTP method.
* The request link:{javadoc-url}/org/eclipse/jetty/server/Request.html#getPathInContext(org.eclipse.jetty.server.Request)[path in context], that is the request URI relative to the context path.
* The request remote address.
* Custom predicates that receive the request to match a custom condition.

Notable subclasses of `ConditionalHandler` are, for example, <<handler-use-qos,`QoSHandler`>> and <<handler-use-thread-limit,`ThreadLimitHandler`>>.

[[handler-use-gzip]]
==== GzipHandler

Expand Down Expand Up @@ -905,7 +943,7 @@ Server
└── ContextHandler N
----

[[handler-use-sizelimit]]
[[handler-use-size-limit]]
==== SizeLimitHandler

`SizeLimitHandler` tracks the sizes of request content and response content, and fails the request processing with an HTTP status code of https://www.rfc-editor.org/rfc/rfc9110.html#name-413-content-too-large[`413 Content Too Large`].
Expand Down Expand Up @@ -1038,9 +1076,13 @@ Refer to the `EventsHandler` link:{javadoc-url}/org/eclipse/jetty/server/handler
[[handler-use-qos]]
==== QoSHandler

`QoSHandler` allows web applications to limit the number of concurrent requests, therefore implementing a quality of service (QoS) mechanism for end users.
`QoSHandler` allows web applications to limit the number of _active_ concurrent requests, that is requests that are currently being handled by a thread, and therefore are not waiting asynchronously, for example reading request content or writing response content.

`QoSHandler` extends xref:handler-use-conditional[`ConditionalHandler`], so you may be able to restrict what `QoSHandler` does to only requests that match the conditions (for example, only to `POST` requests, or only for certain request URIs, etc.)

Web applications may need to access resources with limited capacity, for example a relational database accessed through a JDBC connection pool.
Limiting the number of active concurrent requests helps to control the load on the server, so that it does not become overloaded and possibly unresponsive.
In turn, this improves the quality of service (QoS) for end users of the web application, because either their request is being handled actively by the server, or it is rejected sooner (after a configurable timeout in `QoSHandler`) rather than later (after a possibly longer idle timeout).

Consider the case where each HTTP request results in a JDBC query, and the capacity of the database is of 400 queries/s.
Allowing more than 400 HTTP requests/s into the system, for example 500 requests/s, results in 100 requests blocking waiting for a JDBC connection _for every second_.
Expand All @@ -1049,7 +1091,7 @@ When no more threads are available, additional requests will queue up as tasks i
This situation affects the whole server, so one bad behaving web application may affect other well behaving web applications.
From the end user perspective the quality of service is terrible, because requests will take a lot of time to be served and eventually time out.

In cases of load spikes, caused for example by popular events (weather or social events), usage bursts (Black Friday sales), or even denial of service attacks, it is desirable to give priority to certain requests rather than others.
In cases of load spikes, caused for example by popular events (weather or social events), usage bursts (Black Friday sales), or even denial-of-service attacks, it is desirable to give priority to certain requests rather than others.
For example, in an e-commerce site requests that lead to the checkout and to the payments should have higher priorities than requests to browse the products.
Another example is to prioritize requests for certain users such as paying users or administrative users.

Expand Down Expand Up @@ -1198,6 +1240,20 @@ In the example above, `ContextHandlerCollection` will try to match a request to
NOTE: `DefaultHandler` just sends a nicer HTTP `404` response in case of wrong requests from clients.
Jetty will send an HTTP `404` response anyway if `DefaultHandler` has not been set.

[[handler-use-thread-limit]]
==== ThreadLimitHandler

`ThreadLimitHandler` tracks remote IP addresses and limits the number of concurrent requests (and therefore threads) for each remote IP address to protect against denial-of-service attacks.

`ThreadLimitHandler` extends xref:handler-use-conditional[`ConditionalHandler`], so you may be able to restrict what `ThreadLimitHandler` does to only requests that match the conditions (for example, only to `POST` requests, or only for certain request URIs, etc.)

The remote IP address can be derived from the network, or from the `Forwarded` (or the now obsolete `X-Forwarded-For`) HTTP header.
The `Forwarded` header is typically present in requests that have been forwarded to Jetty by a reverse proxy such as HAProxy, Nginx, Apache, etc.

// TODO: mention the DoSHandler in Jetty 12.1.x.

Note that `ThreadLimitHandler` is different from xref:handler-use-qos[`QoSHandler`] in that it limits the number of concurrent requests per remote IP address, while `QoSHandler` limits the total number of concurrent requests.

[[handler-use-servlet]]
=== Servlet API Handlers

Expand Down
62 changes: 62 additions & 0 deletions jetty-core/jetty-server/src/main/config/etc/jetty-qos.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://jetty.org/configure_10_0.dtd">

<Configure id="Server" class="org.eclipse.jetty.server.Server">
<Call name="insertHandler">
<Arg>
<New class="org.eclipse.jetty.server.handler.QoSHandler">
<Set name="maxRequestCount" property="jetty.qos.maxRequestCount" />
<Set name="maxSuspendedRequestCount" property="jetty.qos.maxSuspendedRequestCount" />
<Set name="maxSuspend">
<Call class="java.time.Duration" name="ofMillis">
<Arg type="long">
<Property name="jetty.qos.maxSuspendDuration" default="0" />
</Arg>
</Call>
</Set>
<Call name="includeMethod">
<Arg>
<Call class="org.eclipse.jetty.util.StringUtil" name="csvSplit">
<Arg><Property name="jetty.qos.include.method" default="" /></Arg>
</Call>
</Arg>
</Call>
<Call name="excludeMethod">
<Arg>
<Call class="org.eclipse.jetty.util.StringUtil" name="csvSplit">
<Arg><Property name="jetty.qos.exclude.method" default="" /></Arg>
</Call>
</Arg>
</Call>
<Call name="includePath">
<Arg>
<Call class="org.eclipse.jetty.util.StringUtil" name="csvSplit">
<Arg><Property name="jetty.qos.include.path" default="" /></Arg>
</Call>
</Arg>
</Call>
<Call name="excludePath">
<Arg>
<Call class="org.eclipse.jetty.util.StringUtil" name="csvSplit">
<Arg><Property name="jetty.qos.exclude.path" default="" /></Arg>
</Call>
</Arg>
</Call>
<Call name="includeInetAddressPattern">
<Arg>
<Call class="org.eclipse.jetty.util.StringUtil" name="csvSplit">
<Arg><Property name="jetty.qos.include.inet" default="" /></Arg>
</Call>
</Arg>
</Call>
<Call name="excludeInetAddressPattern">
<Arg>
<Call class="org.eclipse.jetty.util.StringUtil" name="csvSplit">
<Arg><Property name="jetty.qos.exclude.inet" default="" /></Arg>
</Call>
</Arg>
</Call>
</New>
</Arg>
</Call>
</Configure>
13 changes: 13 additions & 0 deletions jetty-core/jetty-server/src/main/config/etc/jetty-size-limit.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://jetty.org/configure_10_0.dtd">

<Configure id="Server" class="org.eclipse.jetty.server.Server">
<Call name="insertHandler">
<Arg>
<New class="org.eclipse.jetty.server.handler.SizeLimitHandler">
<Arg type="long"><Property name="jetty.sizeLimit.maxRequestContentSize" default="-1" /></Arg>
<Arg type="long"><Property name="jetty.sizeLimit.maxResponseContentSize" default="-1" /></Arg>
</New>
</Arg>
</Call>
</Configure>
56 changes: 56 additions & 0 deletions jetty-core/jetty-server/src/main/config/etc/jetty-thread-limit.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://jetty.org/configure_10_0.dtd">

<Configure id="Server" class="org.eclipse.jetty.server.Server">
<Call name="insertHandler">
<Arg>
<New id="ThreadLimitHandler" class="org.eclipse.jetty.server.handler.ThreadLimitHandler">
<Arg name="forwardedHeader"><Property name="jetty.threadlimit.forwardedHeader"/></Arg>
<Set name="enabled"><Property name="jetty.threadlimit.enabled" default="true"/></Set>
<Set name="threadLimit" property="jetty.threadlimit.threadLimit"/>
<Call name="includeMethod">
<Arg>
<Call class="org.eclipse.jetty.util.StringUtil" name="csvSplit">
<Arg><Property name="jetty.threadlimit.include.method" default="" /></Arg>
</Call>
</Arg>
</Call>
<Call name="excludeMethod">
<Arg>
<Call class="org.eclipse.jetty.util.StringUtil" name="csvSplit">
<Arg><Property name="jetty.threadlimit.exclude.method" default="" /></Arg>
</Call>
</Arg>
</Call>
<Call name="includePath">
<Arg>
<Call class="org.eclipse.jetty.util.StringUtil" name="csvSplit">
<Arg><Property name="jetty.threadlimit.include.path" default="" /></Arg>
</Call>
</Arg>
</Call>
<Call name="excludePath">
<Arg>
<Call class="org.eclipse.jetty.util.StringUtil" name="csvSplit">
<Arg><Property name="jetty.threadlimit.exclude.path" default="" /></Arg>
</Call>
</Arg>
</Call>
<Call name="includeInetAddressPattern">
<Arg>
<Call class="org.eclipse.jetty.util.StringUtil" name="csvSplit">
<Arg><Property name="jetty.threadlimit.include.inet" default="" /></Arg>
</Call>
</Arg>
</Call>
<Call name="excludeInetAddressPattern">
<Arg>
<Call class="org.eclipse.jetty.util.StringUtil" name="csvSplit">
<Arg><Property name="jetty.threadlimit.exclude.inet" default="" /></Arg>
</Call>
</Arg>
</Call>
</New>
</Arg>
</Call>
</Configure>
20 changes: 0 additions & 20 deletions jetty-core/jetty-server/src/main/config/etc/jetty-threadlimit.xml

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ server
server

[after]
threadlimit
thread-limit

[xml]
etc/jetty-delayed.xml
Expand Down
47 changes: 47 additions & 0 deletions jetty-core/jetty-server/src/main/config/modules/qos.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
[description]
Installs QoSHandler at the root of the `Handler` tree,
to limit the number of concurrent requests, for resource management.

[tags]
server

[before]
compression
gzip

[depends]
server

[xml]
etc/jetty-qos.xml

[ini-template]
#tag::documentation[]
## The maximum number of concurrent requests allowed; use 0 for a default
## value calculated from the ThreadPool configuration or the number of CPU cores.
# jetty.qos.maxRequestCount=0

## The maximum number of requests that may be suspended.
# jetty.qos.maxSuspendedRequestCount=1024

## The maximum duration that a request may remain suspended, in milliseconds; use 0 for unlimited time.
# jetty.qos.maxSuspendDuration=0

## A comma-separated list of HTTP methods to include when matching a request.
# jetty.qos.include.method=

## A comma-separated list of HTTP methods to exclude when matching a request.
# jetty.qos.exclude.method=

## A comma-separated list of URI path patterns to include when matching a request.
# jetty.qos.include.path=

## A comma-separated list of URI path patterns to exclude when matching a request.
# jetty.qos.exclude.path=

## A comma-separated list of remote addresses patterns to include when matching a request.
# jetty.qos.include.inet=

## A comma-separated list of remote addresses patterns to exclude when matching a request.
# jetty.qos.exclude.inet=
#end::documentation[]
Loading

0 comments on commit dd31feb

Please sign in to comment.