Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/jetty-12.0.x' into jetty-12.0.x-…
Browse files Browse the repository at this point in the history
…FormLimitDocs
  • Loading branch information
lachlan-roberts committed Dec 16, 2024
2 parents b9c6ad4 + cd4a09b commit af860cf
Show file tree
Hide file tree
Showing 69 changed files with 1,471 additions and 376 deletions.
4 changes: 4 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -246,3 +246,7 @@ updates:
- dependency-name: "org.apache.avro:*"
versions: [ ">=1.12" ]

- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
12 changes: 6 additions & 6 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4

# Install and setup JDK 11
- name: Setup JDK 11
uses: actions/setup-java@v3
uses: actions/setup-java@v4
if: ${{
startsWith(github.ref, 'refs/heads/jetty-10.') ||
startsWith(github.ref, 'refs/heads/jetty-11.') ||
Expand All @@ -49,7 +49,7 @@ jobs:

# Install and setup JDK 17
- name: Setup JDK 17
uses: actions/setup-java@v3
uses: actions/setup-java@v4
if: ${{
startsWith(github.ref, 'refs/heads/jetty-12.') ||
startsWith(github.base_ref, 'jetty-12.')
Expand All @@ -61,7 +61,7 @@ jobs:

# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.languages }}
# If you wish to specify custom queries, you can do so here or in a config file.
Expand All @@ -74,7 +74,7 @@ jobs:

- name: Set up Maven
run:
mvn -e -B -V org.apache.maven.plugins:maven-wrapper-plugin:3.1.0:wrapper "-Dmaven=3.9.6"
mvn -e -B -V org.apache.maven.plugins:maven-wrapper-plugin:3.1.0:wrapper "-Dmaven=3.9.9"

- name: Clean install dependencies and build
env:
Expand All @@ -94,4 +94,4 @@ jobs:
# ./location_of_script_within_repo/buildscript.sh

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
uses: github/codeql-action/analyze@v3
2 changes: 1 addition & 1 deletion .github/workflows/stale-action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
pull-requests: write # for actions/stale to close stale PRs
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v4
- uses: actions/stale@v9
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 365
Expand Down
18 changes: 1 addition & 17 deletions Jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,6 @@ def mavenBuild(jdk, cmdline, mvnName) {
extraArgs = " -Dmaven.test.failure.ignore=true "
}
}
runLaunchable ("verify")
runLaunchable ("record build --name jetty-12.0.x")
dashProfile = ""
if(useEclipseDash()) {
dashProfile = " -Peclipse-dash "
Expand All @@ -140,9 +138,7 @@ def mavenBuild(jdk, cmdline, mvnName) {
}
finally
{
junit testDataPublishers: [[$class: 'JUnitFlakyTestDataPublisher']], testResults: '**/target/surefire-reports/**/*.xml,**/target/invoker-reports/TEST*.xml', allowEmptyResults: true
echo "Launchable record tests"
runLaunchable ("record tests --build jetty-12.0.x maven '**/target/surefire-reports/**/*.xml' '**/target/invoker-reports/TEST*.xml'")
junit testResults: '**/target/surefire-reports/**/*.xml,**/target/invoker-reports/TEST*.xml', allowEmptyResults: true
}
}
}
Expand Down Expand Up @@ -187,17 +183,5 @@ def websiteBuild() {
}
}
}
/**
* run launchable with args and ignore any errors
* @param args
*/
def runLaunchable(args) {
try {
sh "launchable $args"
} catch (Exception e) {
e.printStackTrace()
echo "skip failure running Launchable: " + e.getMessage()
}
}

// vim: et:ts=2:sw=2:ft=groovy
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,13 @@
import java.net.SocketAddress;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLException;
Expand Down Expand Up @@ -1233,4 +1236,38 @@ public void connectionInformation() throws Exception
.send();
// end::connectionInformation[]
}

public void connectListener() throws Exception
{
// tag::connectListener[]
ClientConnector clientConnector = new ClientConnector();
clientConnector.addEventListener(new ClientConnector.ConnectListener()
{
private final ConcurrentMap<SocketChannel, Long> times = new ConcurrentHashMap<>();

@Override
public void onConnectBegin(SocketChannel socketChannel, SocketAddress socketAddress)
{
times.put(socketChannel, System.nanoTime());
}

@Override
public void onConnectSuccess(SocketChannel socketChannel)
{
Long begin = times.remove(socketChannel);
System.getLogger("connection").log(INFO, "established in %d ns", System.nanoTime() - begin);
}

@Override
public void onConnectFailure(SocketChannel socketChannel, SocketAddress socketAddress, Throwable failure)
{
Long begin = times.remove(socketChannel);
System.getLogger("connection").log(INFO, "failed in %d ns", System.nanoTime() - begin);
}
});

HttpClient httpClient = new HttpClient(new HttpClientTransportOverHTTP(clientConnector));
httpClient.start();
// end::connectListener[]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
import org.eclipse.jetty.server.handler.CrossOriginHandler;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.EventsHandler;
import org.eclipse.jetty.server.handler.GracefulHandler;
import org.eclipse.jetty.server.handler.QoSHandler;
import org.eclipse.jetty.server.handler.ResourceHandler;
import org.eclipse.jetty.server.handler.SecuredRedirectHandler;
Expand Down Expand Up @@ -1637,6 +1638,38 @@ public void defaultHandler() throws Exception
// end::defaultHandler[]
}

public void gracefulHandler() throws Exception
{
// tag::gracefulHandler[]
Server server = new Server();

// Install the GracefulHandler.
GracefulHandler gracefulHandler = new GracefulHandler();
server.setHandler(gracefulHandler);

// Set the Server stopTimeout to wait at most
// 10 seconds for existing requests to complete.
server.setStopTimeout(10_000);

// Add one web application.
class MyWebApp extends Handler.Abstract
{
@Override
public boolean handle(Request request, Response response, Callback callback) throws Exception
{
// Implement your web application.
callback.succeeded();
return true;
}
}

ContextHandler contextHandler = new ContextHandler(new MyWebApp(), "/app");
gracefulHandler.setHandler(contextHandler);

server.start();
// end::gracefulHandler[]
}

public void continue100()
{
// tag::continue100[]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,11 +215,11 @@ To configure access to `javax.mail.Session` from within a webapp, declare an `or
[,xml,subs=attributes+]
----
<Configure id='wac' class="org.eclipse.jetty.{ee-current}.webapp.WebAppContext">
<New class="org.eclipse.jetty.{ee-current}.jndi.Resource">
<New class="org.eclipse.jetty.plus.jndi.Resource">
<Arg><Ref refid="wac"/></Arg>
<Arg>mail/Session</Arg>
<Arg>
<New class="org.eclipse.jetty.{ee-current}.factories.MailSessionReference"> <!--1-->
<New class="org.eclipse.jetty.{ee-current}.jndi.factories.MailSessionReference"> <!--1-->
<Set name="user">fred</Set> <!--2-->
<Set name="password">OBF:1xmk1w261z0f1w1c1xmq</Set> <!--3-->
<Set name="properties"> <!--4-->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,19 @@ Jetty's configuration properties are identical across all versions of this modul
include::{jetty-home}/modules/ee10-webapp.mod[tags=ini-template]
----

[[graceful]]
== Module `graceful`

The `graceful` module allows to shut down gracefully the Jetty server when it is stopped (see xref:start/index.adoc#stop[this section] for more information about stopping Jetty).

The `graceful` module installs the `GracefulHandler` at the root of the `Handler` tree; the `GracefulHandler` rejects new requests, but allows current requests to terminate within a configurable timeout, as explained in xref:programming-guide:server/http.adoc#handler-use-graceful[this section].

The module properties are:

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

[[http]]
== Module `http`

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,18 @@ The `jetty.server.stopAtShutdown` property configures a JVM shutdown hook that i

Obviously, the JVM can also be stopped with `kill -KILL <pid>` that exits the process abruptly without running the JVM shutdown hooks.

[[stop-graceful]]
=== Stopping Gracefully

Stopping Jetty abruptly when there are active HTTP requests being handled may result in a variety or errors, because Jetty components that are used to handle requests are being stopped concurrently.

For example, when the Jetty thread pool is stopped, an attempt to submit a task would throw `RejectedExecutionException`; when a component is stopped, its fields may be nulled-out, resulting in a `NullPointerException` being thrown if the component is used; etc.

You can stop Jetty _gracefully_ by adding the `graceful` Jetty module (see xref:modules/standard.adoc#graceful[this section] for more information).

When Jetty is stopped, the `graceful` module organizes to reject new requests, but allows existing requests to finish within a configurable timeout; then closes all the connections, stops all the Jetty components, and then exits the JVM.
In this way, existing requests are not responded with an error caused by the server stopping, provided they complete within the timeout.

[[stop-remote]]
=== Stopping Jetty from Remote

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -541,7 +541,7 @@ include::code:example$src/main/java/org/eclipse/jetty/docs/programming/client/ht
----

[[connection-information]]
=== Request Connection Information
=== Connection Information

In order to send a request, it is necessary to obtain a connection, as explained in the xref:request-processing[request processing section].

Expand All @@ -562,6 +562,25 @@ This means that the connection is not available in the _request queued_ event, b
For more information about request events, see xref:non-blocking[this section].
====

[[connection-events]]
=== Connection Events

In order to send HTTP requests, a connection is necessary, as explained in the xref:request-processing[request processing section].

HTTP/1.1 and HTTP/2 use `SocketChannel.connect(SocketAddress)` to establish a connection with the server, either via the TCP transport or via the Unix-Domain transport.

You can listen to these `connect` events using a `ClientConnector.ConnectListener`, for example to record connection establishment times:

[,java,indent=0]
----
include::code:example$src/main/java/org/eclipse/jetty/docs/programming/client/http/HTTPClientDocs.java[tags=connectListener]
----

This could be particularly useful when you notice that your client application seem "slow" to send requests.
The `connect begin` and `connect success` events, along with the `request queued` and `request begin` event (detailed xref:non-blocking[here]), allow you to understand whether it is the server being slow at accepting connections, or it is the client being slow at processing queued requests.

Once the low-level connection has been established, you can be notified of connection events using a `Connection.Listener`, or more concretely `ConnectionStatistics`, as described in xref:arch/io.adoc#connection-listener[this section].

[[configuration]]
== HttpClient Configuration

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,17 @@ If you want to customize the violations that you want to allow, you can create y
include::code:example$src/main/java/org/eclipse/jetty/docs/programming/server/ServerDocs.java[tags=uriComplianceCustom]
----

[[servleturi]]
=== Servlet URI Compliance Modes

Even if the server has been configured (as above) to allow ambiguous URIs to be received, individual Servlet contexts may not allow such ambiguous URIs to be returned via some specific methods.

Specifically the `HttpServletRequest` methods: link:https://jakarta.ee/specifications/servlet/6.0/apidocs/jakarta.servlet/jakarta/servlet/http/httpservletrequest#getServletPath()[getServletPath()] and link:https://jakarta.ee/specifications/servlet/6.0/apidocs/jakarta.servlet/jakarta/servlet/http/httpservletrequest#getPathInfo()[getPathInfo()], may throw `IllegalArgumentException` for such URIs.

The intention is for safer methods, such as link:https://jakarta.ee/specifications/servlet/6.0/apidocs/jakarta.servlet/jakarta/servlet/http/httpservletrequest#getRequestURI()[getRequestURI] to be used instead.

If necessary, the `ServletHandler` can be configured to allow ambiguous URIs from all methods with link:{javadoc-url}/org/eclipse/jetty/ee10/servlet/ServletHandler.html#setDecodeAmbiguousURIs(boolean)[setDecodeAmbiguousURIs(boolean)].

[[cookie]]
== Cookie Compliance Modes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1254,6 +1254,41 @@ The `Forwarded` header is typically present in requests that have been forwarded

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-graceful]]
==== GracefulHandler

`GracefulHandler` allows to stop the Jetty server in a graceful way, by rejecting new requests but allowing existing requests to complete within a configurable timeout.

In this way, existing requests can be completed normally rather than with failures caused by the fact that the server is stopping; for example, stopping the server causes all TCP connections to be closed, so trying to write a response will result in a failure.

In order to stop Jetty gracefully, you need the following:

* Install the `GracefulHandler`, typically just after the `Server` at the root of the `Handler` tree.
* Configure `Server.stopTimeout` to a positive value.

When the `Server` component is stopped, it will check the `Server.stopTimeout`, and if positive, it will initiate a graceful shutdown by notifying all components that implement the `Graceful` interface that the shutdown has been initiated.

`GracefulHandler` implements `Graceful`, so it will start rejecting new requests with status code `503 Service Available`, but will allow existing requests to complete for a period of time up to `Server.stopTimeout`.

When all existing requests have completed, the `Server` stops all ``Connector``s, closes all connections, and finally stops all the components.

This is how you configure and use `GracefulHandler`:

[,java,indent=0]
----
include::code:example$src/main/java/org/eclipse/jetty/docs/programming/server/http/HTTPServerDocs.java[tags=gracefulHandler]
----

The `Handler` tree structure looks like the following:

[,screen]
----
Server
└── GracefulHandler
└── ContextHandler
└── MyWebApp
----

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ public class ConscryptHTTP2ServerTest
Security.addProvider(new OpenSSLProvider());
}

private final HttpConfiguration httpsConfig = new HttpConfiguration();
private final Server server = new Server();

private SslContextFactory.Server newServerSslContextFactory()
Expand Down Expand Up @@ -90,9 +91,7 @@ private void configureSslContextFactory(SslContextFactory sslContextFactory)
@BeforeEach
public void startServer() throws Exception
{
HttpConfiguration httpsConfig = new HttpConfiguration();
httpsConfig.setSecureScheme("https");

httpsConfig.setSendXPoweredBy(true);
httpsConfig.setSendServerVersion(true);
httpsConfig.addCustomizer(new SecureRequestCustomizer());
Expand Down Expand Up @@ -140,4 +139,12 @@ public void testSimpleRequest() throws Exception
assertEquals(200, contentResponse.getStatus());
}
}

@Test
public void testSNIRequired() throws Exception
{
// The KeyStore contains 1 certificate with two DNS names.
httpsConfig.getCustomizer(SecureRequestCustomizer.class).setSniRequired(true);
testSimpleRequest();
}
}
Binary file not shown.
Loading

0 comments on commit af860cf

Please sign in to comment.