Fixes #11597 - Document Request Customizers.

Documented the request customizers in the programming guide.

Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
This commit is contained in:
Simone Bordet 2024-04-24 18:57:16 +02:00
parent e491fc3d30
commit c10ec98b5b
No known key found for this signature in database
GPG Key ID: 1677D141BCF3584D
6 changed files with 218 additions and 65 deletions

View File

@ -0,0 +1,112 @@
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
[[pg-server-http-request-customizers]]
==== Request Customizers
A request customizer is an instance of `HttpConfiguration.Customizer`, that can customize the HTTP request and/or the HTTP response headers _before_ the `Handler` chain is invoked.
Request customizers are added to a particular `HttpConfiguration` instance, and therefore are specific to a `Connector` instance: you can have two different ``Connector``s configured with different request customizers.
For example, it is common to configure a secure `Connector` with the `SecureRequestCustomizer` that customizes the HTTP request by adding attributes that expose TLS data associated with the secure communication.
A request customizer may:
* Inspect the received HTTP request method, URI, version and headers.
* Wrap the `Request` object to allow any method to be overridden and customized. Typically this is done to synthesize additional HTTP request headers, or to change the return value of overridden methods.
* Add or modify the HTTP response headers.
The out-of-the-box request customizers include:
* `ForwardedRequestCustomizer` -- to interpret the `Forwarded` (or the the obsolete ``+X-Forwarded-*+``) HTTP header added by a reverse proxy; see xref:pg-server-http-request-customizer-forwarded[this section].
* `HostHeaderCustomizer` -- to customize, or synthesize it when original absent, the HTTP `Host` header; see xref:pg-server-http-request-customizer-host[this section].
* `ProxyCustomizer` -- to expose as `Request` attributes the `ip:port` information carried by the PROXY protocol; see xref:pg-server-http-request-customizer-proxy[this section].
* `RewriteCustomizer` -- to rewrite the request URI; see xref:pg-server-http-request-customizer-rewrite[this section].
* `SecureRequestCustomizer` -- to expose TLS data via `Request` attributes; see xref:pg-server-http-request-customizer-secure[this section].
You can also write your own request customizers and add them to the `HttpConfiguration` instance along existing request customizers.
Multiple request customizers will be invoked in the order they have been added.
Below you can find an example of how to add a request customizer:
[source,java,indent=0]
----
include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/http/HTTPServerDocs.java[tags=requestCustomizer]
----
[[pg-server-http-request-customizer-forwarded]]
===== `ForwardedRequestCustomizer`
`ForwardedRequestCustomizer` should be added when Jetty receives requests from a reverse proxy on behalf of a remote client, and web applications need to access the remote client information.
The reverse proxy adds the `Forwarded` (or the obsolete ``+X-Forwarded-*+``) HTTP header to the request, and may offload TLS so that the request arrives in clear-text to Jetty.
Applications deployed in Jetty may need to access information related to the remote client, for example the remote IP address and port, or whether the request was sent through a secure communication channel.
However, the request is forwarded by the reverse proxy, so the direct information about the remote IP address is that of the proxy, not of the remote client.
Furthermore, the proxy may offload TLS and forward the request in clear-text, so that the URI scheme would be `http` as forwarded by the reverse proxy, not `https` as sent by the remote client.
`ForwardedRequestCustomizer` reads the `Forwarded` header where the reverse proxy saved the remote client information, and wraps the original `Request` so that applications will transparently see the remote client information when calling methods such as `Request.isSecure()`, or `Request.getConnectionMetaData().getRemoteSocketAddress()`, etc.
For more information about how to configure `ForwardedRequestCustomizer`, see also link:{javadoc-url}/org/eclipse/jetty/server/ForwardedRequestCustomizer.html[the javadocs].
[[pg-server-http-request-customizer-host]]
===== `HostHeaderCustomizer`
`HostHeaderCustomizer` should be added when Jetty receives requests that may lack the `Host` HTTP header, such as HTTP/1.0, HTTP/2 or HTTP/3 requests, and web applications have logic that depends on the value of the `Host` HTTP header.
For HTTP/2 and HTTP/3, the `Host` HTTP header is missing because the authority information is carried by the `:authority` pseudo-header, as per the respective specifications.
`HostHeaderCustomizer` will look at the `:authority` pseudo-header, then wrap the original `Request` adding a `Host` HTTP header synthesized from the `:authority` pseudo-header.
In this way, web applications that rely on the presence of the `Host` HTTP header will work seamlessly in any HTTP protocol version.
`HostHeaderCustomizer` works also for the WebSocket protocol.
WebSocket over HTTP/2 or over HTTP/3 initiate the WebSocket communication with an HTTP request that only has the `:authority` pseudo-header.
`HostHeaderCustomizer` synthesizes the `Host` HTTP header for such requests, so that WebSocket web applications that inspect the initial HTTP request before the WebSocket communication will work seamlessly in any HTTP protocol version.
For more information about how to configure `HostHeaderCustomizer`, see also link:{javadoc-url}/org/eclipse/jetty/server/HostHeaderCustomizer.html[the javadocs].
[[pg-server-http-request-customizer-proxy]]
===== `ProxyCustomizer`
`ProxyCustomizer` should be added when Jetty receives requests from a reverse proxy on behalf of a remote client, prefixed by the PROXY protocol (see also this section about the xref:pg-server-http-connector-protocol-proxy-http11[PROXY protocol]).
`ProxyCustomizer` adds the reverse proxy IP address and port as `Request` attributes.
Web applications may use these attributes in conjunction with the data exposed by `ForwardedRequestCustomizer` (see xref:pg-server-http-request-customizer-forwarded[this section]).
For more information about how to configure `ProxyCustomizer`, see also link:{javadoc-url}/org/eclipse/jetty/server/ProxyCustomizer.html[the javadocs].
[[pg-server-http-request-customizer-rewrite]]
===== `RewriteCustomizer`
`RewriteCustomizer` is similar to `RewriteHandler` (see xref:pg-server-http-handler-use-rewrite[this section]), but a `RewriteCustomizer` cannot send a response or otherwise complete the request/response processing.
A `RewriteCustomizer` is mostly useful if you want to rewrite the request URI _before_ the `Handler` chain is invoked.
However, a very similar effect can be achieved by having the `RewriteHandler` as the first `Handler` (the child `Handler` of the `Server` instance).
Since `RewriteCustomizer` cannot send a response or complete the request/response processing, ``Rule``s that do so such as redirect rules have no effect and are ignored; only ``Rule``s that modify or wrap the `Request` will have effect and be applied.
Due to this limitation, it is often a better choice to use `RewriteHandler` instead of `RewriteCustomizer`.
For more information about how to configure `RewriteCustomizer`, see also link:{javadoc-url}/org/eclipse/jetty/rewrite/RewriteCustomizer.html[the javadocs].
[[pg-server-http-request-customizer-secure]]
===== `SecureRequestCustomizer`
`SecureRequestCustomizer` should be added when Jetty receives requests over a secure `Connector`.
`SecureRequestCustomizer` adds TLS information as request attributes, in particular an instance of `EndPoint.SslSessionData` that contains information about the negotiated TLS cipher suite and possibly client certificates, and an instance of `org.eclipse.jetty.util.ssl.X509` that contains information about the server certificate.
`SecureRequestCustomizer` also adds, if configured so, the `Strict-Transport-Security` HTTP response header (for more information about this header, see link:https://datatracker.ietf.org/doc/html/rfc6797[its specification]).
For more information about how to configure `SecureRequestCustomizer`, see also link:{javadoc-url}/org/eclipse/jetty/server/SecureRequestCustomizer.html[the javadocs].

View File

@ -0,0 +1,48 @@
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
[[pg-server-http-request-logging]]
==== Request Logging
HTTP requests and responses can be logged to provide data that can be later analyzed with other tools.
These tools can provide information such as the most frequently accessed request URIs, the response status codes, the request/response content lengths, geographical information about the clients, etc.
The default request/response log line format is the link:https://en.wikipedia.org/wiki/Common_Log_Format[NCSA Format] extended with referrer data and user-agent data.
[NOTE]
====
Typically, the extended NCSA format is the is enough and it's the standard used and understood by most log parsing tools and monitoring tools.
To customize the request/response log line format see the link:{javadoc-url}/org/eclipse/jetty/server/CustomRequestLog.html[`CustomRequestLog` javadocs].
====
Request logging can be enabled at the `Server` level.
The request logging output can be directed to an SLF4J logger named `"org.eclipse.jetty.server.RequestLog"` at `INFO` level, and therefore to any logging library implementation of your choice (see also xref:pg-troubleshooting-logging[this section] about logging).
[source,java,indent=0]
----
include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/http/HTTPServerDocs.java[tags=serverRequestLogSLF4J]
----
Alternatively, the request logging output can be directed to a daily rolling file of your choice, and the file name must contain `yyyy_MM_dd` so that rolled over files retain their date:
[source,java,indent=0]
----
include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/http/HTTPServerDocs.java[tags=serverRequestLogFile]
----
For maximum flexibility, you can log to multiple ``RequestLog``s using class `RequestLog.Collection`, for example by logging with different formats or to different outputs.
You can use `CustomRequestLog` with a custom `RequestLog.Writer` to direct the request logging output to your custom targets (for example, an RDBMS).
You can implement your own `RequestLog` if you want to have functionalities that are not implemented by `CustomRequestLog`.

View File

@ -123,9 +123,13 @@ 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 that are protocol specific; the HTTP/2 parser, for example, emits events for each HTTP/2 frame that has been parsed, and similarly does the HTTP/3 parser.
The parser events are then converted to protocol independent events such as _"request start"_, _"request headers"_, _"request content chunk"_, etc.
The parser events are then converted to protocol independent events such as _"request start"_, _"request headers"_, _"request content chunk"_, etc. detailed in xref:pg-server-http-request-processing-events[this section].
When enough of the HTTP request is arrived, the `Connection` calls `HttpChannel.onRequest()` that calls the `Handler` chain starting from the `Server` instance, that eventually calls your web application code.
When enough of the HTTP request is arrived, the `Connection` calls `HttpChannel.onRequest()`.
`HttpChannel.onRequest()` calls the xref:pg-server-http-request-customizers[request customizers], that allow to customize the request and/or the response headers on a per-``Connector`` basis.
After request customization, if any, the `Handler` chain is invoked, starting from the `Server` instance, and eventually your web application code is invoked.
[[pg-server-http-request-processing-events]]
===== Request Processing Events
@ -135,42 +139,8 @@ A typical case is to know exactly _when_ the HTTP request/response processing st
This is conveniently implemented by `org.eclipse.jetty.server.handler.EventsHandler`, described in more details in xref:pg-server-http-handler-use-events[this section].
[[pg-server-http-request-logging]]
==== Request Logging
HTTP requests and responses can be logged to provide data that can be later analyzed with other tools.
These tools can provide information such as the most frequently accessed request URIs, the response status codes, the request/response content lengths, geographical information about the clients, etc.
The default request/response log line format is the link:https://en.wikipedia.org/wiki/Common_Log_Format[NCSA Format] extended with referrer data and user-agent data.
[NOTE]
====
Typically, the extended NCSA format is the is enough and it's the standard used and understood by most log parsing tools and monitoring tools.
To customize the request/response log line format see the link:{javadoc-url}/org/eclipse/jetty/server/CustomRequestLog.html[`CustomRequestLog` javadocs].
====
Request logging can be enabled at the `Server` level.
The request logging output can be directed to an SLF4J logger named `"org.eclipse.jetty.server.RequestLog"` at `INFO` level, and therefore to any logging library implementation of your choice (see also xref:pg-troubleshooting-logging[this section] about logging).
[source,java,indent=0]
----
include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/http/HTTPServerDocs.java[tags=serverRequestLogSLF4J]
----
Alternatively, the request logging output can be directed to a daily rolling file of your choice, and the file name must contain `yyyy_MM_dd` so that rolled over files retain their date:
[source,java,indent=0]
----
include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/http/HTTPServerDocs.java[tags=serverRequestLogFile]
----
For maximum flexibility, you can log to multiple ``RequestLog``s using class `RequestLog.Collection`, for example by logging with different formats or to different outputs.
You can use `CustomRequestLog` with a custom `RequestLog.Writer` to direct the request logging output to your custom targets (for example, an RDBMS).
You can implement your own `RequestLog` if you want to have functionalities that are not implemented by `CustomRequestLog`.
include::server-http-request-logging.adoc[]
include::server-http-request-customizers.adoc[]
include::server-http-connector.adoc[]
include::server-http-handler.adoc[]
include::server-http-session.adoc[]

View File

@ -1593,4 +1593,32 @@ public class HTTPServerDocs
}
// end::continue100[]
}
public void requestCustomizer() throws Exception
{
// tag::requestCustomizer[]
Server server = new Server();
// Configure the secure connector.
HttpConfiguration httpsConfig = new HttpConfiguration();
// Add the SecureRequestCustomizer.
httpsConfig.addCustomizer(new SecureRequestCustomizer());
// Configure the SslContextFactory with the KeyStore information.
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
sslContextFactory.setKeyStorePath("/path/to/keystore");
sslContextFactory.setKeyStorePassword("secret");
// Configure the Connector to speak HTTP/1.1 and HTTP/2.
HttpConnectionFactory h1 = new HttpConnectionFactory(httpsConfig);
HTTP2ServerConnectionFactory h2 = new HTTP2ServerConnectionFactory(httpsConfig);
ALPNServerConnectionFactory alpn = new ALPNServerConnectionFactory();
alpn.setDefaultProtocol(h1.getProtocol());
SslConnectionFactory ssl = new SslConnectionFactory(sslContextFactory, alpn.getProtocol());
ServerConnector connector = new ServerConnector(server, ssl, alpn, h2, h1);
server.addConnector(connector);
server.start();
// end::requestCustomizer[]
}
}

View File

@ -43,7 +43,7 @@ import static java.lang.invoke.MethodType.methodType;
/**
* Customize Requests for Proxy Forwarding.
* <p>
* This customizer looks at at HTTP request for headers that indicate
* This customizer looks at HTTP request for headers that indicate
* it has been forwarded by one or more proxies. Specifically handled are
* <ul>
* <li>{@code Forwarded}, as defined by <a href="https://tools.ietf.org/html/rfc7239">rfc7239</a>
@ -80,7 +80,7 @@ import static java.lang.invoke.MethodType.methodType;
* <tbody style="text-align: left; vertical-align: top;">
* <tr>
* <td>1</td>
* <td><code>Forwarded</code> Header</td>
* <td>{@code Forwarded} Header</td>
* <td>"{@code host=<host>}" param (Required)</td>
* <td>"{@code host=<host>:<port>} param (Implied)</td>
* <td>"{@code proto=<value>}" param (Optional)</td>
@ -88,7 +88,7 @@ import static java.lang.invoke.MethodType.methodType;
* </tr>
* <tr>
* <td>2</td>
* <td><code>X-Forwarded-Host</code> Header</td>
* <td>{@code X-Forwarded-Host} Header</td>
* <td>Required</td>
* <td>Implied</td>
* <td>n/a</td>
@ -96,7 +96,7 @@ import static java.lang.invoke.MethodType.methodType;
* </tr>
* <tr>
* <td>3</td>
* <td><code>X-Forwarded-Port</code> Header</td>
* <td>{@code X-Forwarded-Port} Header</td>
* <td>n/a</td>
* <td>Required</td>
* <td>n/a</td>
@ -104,7 +104,7 @@ import static java.lang.invoke.MethodType.methodType;
* </tr>
* <tr>
* <td>4</td>
* <td><code>X-Forwarded-Server</code> Header</td>
* <td>{@code X-Forwarded-Server} Header</td>
* <td>Required</td>
* <td>Optional</td>
* <td>n/a</td>
@ -112,29 +112,29 @@ import static java.lang.invoke.MethodType.methodType;
* </tr>
* <tr>
* <td>5</td>
* <td><code>X-Forwarded-Proto</code> Header</td>
* <td>{@code X-Forwarded-Proto} Header</td>
* <td>n/a</td>
* <td>Implied from value</td>
* <td>Required</td>
* <td>
* <p>left-most value becomes protocol.</p>
* <ul>
* <li>Value of "<code>http</code>" means port=80.</li>
* <li>Value of "{@code http}" means port=80.</li>
* <li>Value of "{@link HttpConfiguration#getSecureScheme()}" means port={@link HttpConfiguration#getSecurePort()}.</li>
* </ul>
* </td>
* </tr>
* <tr>
* <td>6</td>
* <td><code>X-Proxied-Https</code> Header</td>
* <td>{@code X-Proxied-Https} Header</td>
* <td>n/a</td>
* <td>Implied from value</td>
* <td>boolean</td>
* <td>
* <p>left-most value determines protocol and port.</p>
* <ul>
* <li>Value of "<code>on</code>" means port={@link HttpConfiguration#getSecurePort()}, and protocol={@link HttpConfiguration#getSecureScheme()}).</li>
* <li>Value of "<code>off</code>" means port=80, and protocol=http.</li>
* <li>Value of "{@code on}" means port={@link HttpConfiguration#getSecurePort()}, and protocol={@link HttpConfiguration#getSecureScheme()}).</li>
* <li>Value of "{@code off}" means port=80, and protocol=http.</li>
* </ul>
* </td>
* </tr>
@ -799,12 +799,7 @@ public class ForwardedRequestCustomizer implements HttpConfiguration.Customizer
@Override
public String toString()
{
final StringBuilder sb = new StringBuilder("MutableHostPort{");
sb.append("host='").append(_host).append("'/").append(_hostSource);
sb.append(", port=").append(_port);
sb.append("/").append(_portSource);
sb.append('}');
return sb.toString();
return "%s@%x{host='%s'/%s, port=%d/%s}".formatted(getClass().getSimpleName(), hashCode(), _host, _hostSource, _port, _portSource);
}
}
@ -888,7 +883,7 @@ public class ForwardedRequestCustomizer implements HttpConfiguration.Customizer
}
/**
* Called if header is <code>Proxy-auth-cert</code>
* Called if header is {@code Proxy-auth-cert}
*/
public void handleCipherSuite(HttpField field)
{
@ -904,7 +899,7 @@ public class ForwardedRequestCustomizer implements HttpConfiguration.Customizer
}
/**
* Called if header is <code>Proxy-Ssl-Id</code>
* Called if header is {@code Proxy-Ssl-Id}
*/
public void handleSslSessionId(HttpField field)
{
@ -920,7 +915,7 @@ public class ForwardedRequestCustomizer implements HttpConfiguration.Customizer
}
/**
* Called if header is <code>X-Forwarded-Host</code>
* Called if header is {@code X-Forwarded-Host}
*/
public void handleForwardedHost(HttpField field)
{
@ -928,7 +923,7 @@ public class ForwardedRequestCustomizer implements HttpConfiguration.Customizer
}
/**
* Called if header is <code>X-Forwarded-For</code>
* Called if header is {@code X-Forwarded-For}
*/
public void handleForwardedFor(HttpField field)
{
@ -937,7 +932,7 @@ public class ForwardedRequestCustomizer implements HttpConfiguration.Customizer
}
/**
* Called if header is <code>X-Forwarded-Server</code>
* Called if header is {@code X-Forwarded-Server}
*/
public void handleForwardedServer(HttpField field)
{
@ -947,7 +942,7 @@ public class ForwardedRequestCustomizer implements HttpConfiguration.Customizer
}
/**
* Called if header is <code>X-Forwarded-Port</code>
* Called if header is {@code X-Forwarded-Port}
*/
public void handleForwardedPort(HttpField field)
{
@ -957,7 +952,7 @@ public class ForwardedRequestCustomizer implements HttpConfiguration.Customizer
}
/**
* Called if header is <code>X-Forwarded-Proto</code>
* Called if header is {@code X-Forwarded-Proto}
*/
public void handleProto(HttpField field)
{
@ -965,7 +960,7 @@ public class ForwardedRequestCustomizer implements HttpConfiguration.Customizer
}
/**
* Called if header is <code>X-Proxied-Https</code>
* Called if header is {@code X-Proxied-Https}
*/
public void handleHttps(HttpField field)
{
@ -988,7 +983,7 @@ public class ForwardedRequestCustomizer implements HttpConfiguration.Customizer
}
/**
* Called if header is <code>Forwarded</code>
* Called if header is {@code Forwarded}
*/
public void handleRFC7239(HttpField field)
{

View File

@ -67,7 +67,7 @@ public class SecureRequestCustomizer implements HttpConfiguration.Customizer
/**
* @param sniHostCheck True if the SNI Host name must match.
* @param stsMaxAgeSeconds The max age in seconds for a Strict-Transport-Security response header. If set less than zero then no header is sent.
* @param stsIncludeSubdomains If true, a include subdomain property is sent with any Strict-Transport-Security header
* @param stsIncludeSubdomains If true, an include subdomain property is sent with any Strict-Transport-Security header
*/
public SecureRequestCustomizer(
@Name("sniHostCheck") boolean sniHostCheck,
@ -81,7 +81,7 @@ public class SecureRequestCustomizer implements HttpConfiguration.Customizer
* @param sniRequired True if a SNI certificate is required.
* @param sniHostCheck True if the SNI Host name must match.
* @param stsMaxAgeSeconds The max age in seconds for a Strict-Transport-Security response header. If set less than zero then no header is sent.
* @param stsIncludeSubdomains If true, a include subdomain property is sent with any Strict-Transport-Security header
* @param stsIncludeSubdomains If true, an include subdomain property is sent with any Strict-Transport-Security header
*/
public SecureRequestCustomizer(
@Name("sniRequired") boolean sniRequired,