Merge remote-tracking branch 'origin/jetty-12.0.x' into jetty-12.0.x-9066-MultiPart-getParameters

This commit is contained in:
Lachlan Roberts 2023-01-25 13:46:43 +11:00
commit 36e7f71def
258 changed files with 7935 additions and 7924 deletions

File diff suppressed because it is too large Load Diff

View File

@ -20,6 +20,12 @@
<property name="fileNamePattern" value="module\-info\.java$"/>
</module>
<!-- Excludes all quiche files -->
<!-- See https://checkstyle.org/config_filefilters.html -->
<module name="BeforeExecutionExclusionFileFilter">
<property name="fileNamePattern" value="[/\\]org[/\\]eclipse[/\\]jetty[/\\]quic[/\\]quiche[/\\]"/>
</module>
<!-- Checks for whitespace -->
<!-- See http://checkstyle.sf.net/config_whitespace.html -->
<module name="FileTabCharacter">

View File

@ -49,7 +49,7 @@ The replacement `logging.mod` performs a number of tasks.
. The jetty-home is unpacked (and untouched) into `/opt/jetty-home/` and becomes the `${jetty.home}` directory for this demonstration.
. The `curl` command downloads the replacement `logging.mod` and puts it into the `${jetty.base}/modules/` directory for use by mybase only.
. The `start.jar --add-to-start=logging` command performs a number of steps to make the logging module available to the `${jetty.base}` configuration.
.. The `--module=logging` command is added to the `${jetty.base}/start.ini` configuration.
.. The `--modules=logging` command is added to the `${jetty.base}/start.ini` configuration.
.. Required `${jetty.base}` directories are created: `${jetty.base}/logs` and `${jetty.base}/resources`
.. Required libraries are downloaded (if not present already): slf4j-api, slf4j-log4j, and log4j itself.
* The libraries are put in the `${jetty.base}/lib/logging/` directory.

View File

@ -58,7 +58,7 @@ The replacement `logging.mod` performs a number of tasks.
. The `curl` command downloads the replacement `logging.mod` and puts it into the `${jetty.base}/modules/` directory for use by `mybase` only.
. The `start.jar --add-to-start=logging` command performs a number of steps to make the logging module available to the `${jetty.base}`
configuration.
.. The `--module=logging` command is added to the `${jetty.base}/start.ini` configuration.
.. The `--modules=logging` command is added to the `${jetty.base}/start.ini` configuration.
.. Required `${jetty.base}` directories are created: `${jetty.base}/logs` and `${jetty.base}/resources`.
.. Required configuration files are downloaded (if not present already): `jetty-logging.properties`, and `logging.properties`
* The configuration files are put in the `${jetty.base}/resources/` directory.

View File

@ -51,7 +51,7 @@ The replacement `logging.mod` performs a number of tasks.
. The jetty-home is unpacked (and untouched) into `/opt/jetty-home/` and becomes the `${jetty.home}` directory for this demonstration.
. The `curl` command downloads the replacement `logging.mod` and puts it into the `${jetty.base}/modules/` directory for use by `mybase` only.
. The `start.jar --add-to-start=logging` command performs a number of steps to make the logging module available to the `${jetty.base}` configuration.
.. The `--module=logging` command is added to the `${jetty.base}/start.ini` configuration.
.. The `--modules=logging` command is added to the `${jetty.base}/start.ini` configuration.
.. Required `${jetty.base}` directories are created: `${jetty.base}/logs` and `${jetty.base}/resources`.
.. Required libraries are downloaded (if not present already): slf4j-api, and slf4j-jdk14.
* The libraries are put in the `${jetty.base}/lib/logging/` directory.

View File

@ -47,7 +47,7 @@ The replacement `logging.mod` performs a number of tasks.
. The jetty-home is unpacked (and untouched) into `/opt/jetty-home/` and becomes the `${jetty.home}` directory for this demonstration.
. The `curl` command downloads the replacement `logging.mod` and puts it into the `${jetty.base}/modules/` directory for use by `mybase` only.
. The `start.jar --add-to-start=logging` command performs a number of steps to make the logging module available to the `${jetty.base}` configuration.
.. The `--module=logging` command is added to the `${jetty.base}/start.ini` configuration.
.. The `--modules=logging` command is added to the `${jetty.base}/start.ini` configuration.
.. Required `${jetty.base}` directories are created: `${jetty.base}/logs` and `${jetty.base}/resources`.
.. Required libraries are downloaded (if not present already) to the `${jetty.base}/lib/logging/` directory: slf4j-api, logback-core, and logback-classic.
.. Required configuration files are downloaded (if not present already) to the `${jetty.base}/lib/resources/` directory.: `jetty-logging.properties`, and `logback.xml`.

View File

@ -32,7 +32,7 @@ For SSL, parameters are now properties in the `start.ini` or `start.d\ssl.ini`,
Instead of explicitly listing all the libraries, properties, and XML files for a feature, Jetty 9.1 introduced a new module system.
A module is defined in a `modules/*.mod` file, including the libraries, dependencies, XML, and template INI files for a Jetty feature.
Thus you can use a single `--module=name` command line option as the equivalent of specifying many `--lib=location, feature.xml, name=value` arguments for a feature and all its dependencies.
Thus you can use a single `--modules=name` command line option as the equivalent of specifying many `--libs=location, feature.xml, name=value` arguments for a feature and all its dependencies.
Modules use their dependencies to control the ordering of libraries and XML files.
For more information, see xref:startup-modules[].
@ -205,7 +205,7 @@ Jetty Server Classpath:
-----------------------
Version Information on 11 entries in the classpath.
: order presented here is how they would appear on the classpath.
changes to the --module=name command line options will be reflected here.
changes to the --modules=name command line options will be reflected here.
0: 4.0.2 | ${jetty.home}/lib/jetty-jakarta-servlet-api-4.0.2.jar
2: {VERSION} | ${jetty.home}/lib/jetty-http-{VERSION}.jar
3: {VERSION} | ${jetty.home}/lib/jetty-continuation-{VERSION}.jar
@ -251,7 +251,7 @@ First notice the separation of `${jetty.base}` and `${jetty.home}`.
[[modules]]
===== Modules
Notice that you have `--module=<name>` here and there; you have wrapped up the goal of a module (libs, configuration XMLs, and properties) into a single unit, with dependencies on other modules.
Notice that you have `--modules=<name>` here and there; you have wrapped up the goal of a module (libs, configuration XMLs, and properties) into a single unit, with dependencies on other modules.
You can see the list of modules:
@ -446,7 +446,7 @@ If you want to start using a new module:
[my-base] $ java -jar ../jetty-home-{VERSION}/start.jar --add-to-start=https
....
This adds the `--module=` lines and associated properties (the parameterized values mentioned above), to your `start.ini`.
This adds the `--modules=` lines and associated properties (the parameterized values mentioned above), to your `start.ini`.
____
[IMPORTANT]
@ -494,7 +494,7 @@ For SSL they include `modules/http.mod`, `modules/https.mod`, `modules/ssl.mod`,
+
Ideally, this level of detail is not important to you.
What is important is that you want to use HTTPS and want to configure it.
You accomplish that by adding the `--module=https` to your `start.ini`.
You accomplish that by adding the `--modules=https` to your `start.ini`.
By default, the module system keeps things sane, and transitively includes all dependent modules as well.
You can see what the configuration looks like, after all of the modules are resolved, without starting Jetty via:

View File

@ -1,5 +1,8 @@
// Asciidoctor IDE configuration file.
// See https://github.com/asciidoctor/asciidoctor-intellij-plugin/wiki/Support-project-specific-configurations
:ee-all: ee{8,9,10}
:ee-current: ee10
:ee-current-caps: EE10
:experimental:
:imagesdir: images
:JETTY_HOME: ../../../../../../../jetty-home/target/jetty-home
:jetty-home: ../../../../../../../jetty-home/target/jetty-home

View File

@ -46,46 +46,47 @@ mywebapp.war
<5> `WEB-INF/web.xml` is the web application deployment descriptor, which defines the components and the configuration of your web application.
====
To deploy a standard web application, you need to enable the xref:og-module-deploy[`deploy` module]:
To deploy a standard web application, you need to enable the xref:og-module-eeN-deploy[`{ee-current}-deploy` module].
NOTE: The following examples assume you're deploying a Jakarta {ee-current-caps} application; for other versions of Jakarta EE, make sure to activate the corresponding `{ee-all}-deploy` module.
[subs=attributes]
----
$ java -jar $JETTY_HOME/start.jar --add-module=deploy
$ java -jar $JETTY_HOME/start.jar --add-module={ee-current}-deploy
----
[source,options=nowrap]
----
include::jetty[setupArgs="--add-module=http",args="--add-module=deploy"]
include::jetty[setupArgs="--add-module=http",args="--add-module={ee-current}-deploy"]
----
The `deploy` module creates `$JETTY_BASE/webapps`, which is the directory where Jetty looks for any `+*.war+` files or web application directories to deploy.
The `{ee-current}-deploy` module creates `$JETTY_BASE/webapps`, which is the directory where Jetty looks for any `+*.war+` files or web application directories to deploy.
[NOTE]
====
Activating Jetty's `deploy` module enables web application deployment.
Activating one of Jetty's `{ee-all}-deploy` modules enables web application deployment.
Whether these web applications are served via clear-text HTTP/1.1, or secure HTTP/1.1, or secure HTTP/2, or HTTP/3 (or even all of these protocols) depends on if the correspondent Jetty modules have been enabled.
Refer to the xref:og-protocols[section about protocols] for further information.
====
Now you're ready to copy a web application to the `$JETTY_BASE/webapps` directory.
You can use one of the demos shipped with Jetty:
[subs=attributes]
----
$ java -jar $JETTY_HOME/start.jar --add-module=ee10-demo-simple
$ java -jar $JETTY_HOME/start.jar --add-module={ee-current}-demo-simple
----
The `$JETTY_BASE` directory is now:
[subs=attributes]
----
$JETTY_BASE
├── resources
│ └── jetty-logging.properties
├── start.d
│ ├── deploy.ini
│ ├── ee10-demo-simple.ini
│ ├── {ee-current}-demo-simple.ini
│ └── http.ini
└── webapps
└── ee10-demo-simple.war
└── {ee-current}-demo-simple.war
----
Now start Jetty:
@ -96,12 +97,12 @@ $ java -jar $JETTY_HOME/start.jar
[source,subs=quotes,options=nowrap]
----
include::jetty[setupArgs="--add-modules=http,deploy,ee10-demo-simple",highlight="WebAppContext"]
include::jetty[setupArgs="--add-modules=http,deploy,{ee-current}-demo-simple",highlight="WebAppContext"]
----
Note the highlighted line that logs the deployment of `ee10-demo-simple.war`.
Note the highlighted line that logs the deployment of `{ee-current}-demo-simple.war`.
Now you can access the web application by pointing your browser to `+http://localhost:8080/ee10-demo-simple+`.
Now you can access the web application by pointing your browser to `pass:a[http://localhost:8080/{ee-current}-demo-simple]`.
[[og-begin-deploy-war-advanced]]
===== Advanced Deployment

View File

@ -16,7 +16,7 @@
Most of the times you want to be able to customize the deployment of your web applications, for example by changing the `contextPath`, or by adding JNDI entries, or by configuring virtual hosts, etc.
The customization is performed by the xref:og-module-deploy[`deploy` module] by processing xref:og-deploy-jetty[Jetty context XML files].
The customization is performed by the xref:og-module-eeN-deploy[`{ee-all}-deploy` module] by processing xref:og-deploy-jetty[Jetty context XML files].
The `deploy` module contains the `DeploymentManager` component that scans the `$JETTY_BASE/webapps` directory for changes, following the deployment rules described in xref:og-deploy-rules[this section].

View File

@ -36,10 +36,11 @@ own custom link:https://docs.oracle.com/javase/7/docs/api/javax/security/auth/sp
[[og-jaas-module]]
===== The `jaas` module
Enable the `ee{8,9,10}-jaas` module appropriate for your EE platform:
Enable the `{ee-all}-jaas` module appropriate for your EE platform:
[subs=attributes]
----
include::{JETTY_HOME}/modules/ee10-jaas.mod[]
include::{jetty-home}/modules/{ee-current}-jaas.mod[]
----
The configurable items in the resulting `$jetty.base/start.d/jaas.ini` file are:

View File

@ -27,28 +27,29 @@ Only modules conforming to the "Servlet Container Profile" with the ServerAuthMo
Enable the `jaspi` module:
----
include::{JETTY_HOME}/modules/ee10-jaspi.mod[]
include::{jetty-home}/modules/{ee-current}-jaspi.mod[]
----
[[og-jaspi-xml]]
===== Configure JASPI
Activate the `ee{9,10}-jaspi` module that matches your EE platform version.
Activate either the `ee9-jaspi` or `{ee-current}-jaspi` module, whichever matches your EE platform version.
[subs=attributes]
----
$ java -jar $JETTY_HOME/start.jar --add-modules=ee10-jaspi
$ java -jar $JETTY_HOME/start.jar --add-modules={ee-current}-jaspi
----
You can then register a `AuthConfigProvider` onto the static `AuthConfigFactory` obtained with `AuthConfigFactory.getFactory()`. This registration can be done in the XML configuration file which will be copied to `$JETTY_BASE/etc/jaspi/jaspi-authmoduleconfig.xml` when the module is enabled.
====== JASPI Demo
The `ee9-jaspi-demo` and `ee10-jaspi-demo` modules illustrate setting up HTTP Basic Authentication using the EE9 and EE10 Jakarta Authentication modules that come packaged with Jetty.
The `ee9-jaspi-demo` and `{ee-current}-jaspi-demo` modules illustrate setting up HTTP Basic Authentication using the EE9 and {ee-current-caps} Jakarta Authentication modules that come packaged with Jetty.
The following example uses Jetty's EE10 implementation of `AuthConfigProvider` to register a `ServerAuthModule` directly.
The following example uses Jetty's {ee-current-caps} implementation of `AuthConfigProvider` to register a `ServerAuthModule` directly.
[source, xml]
[source,xml]
----
include::{JETTY_HOME}/etc/jaspi/jetty-ee10-jaspi-demo.xml[]
include::{jetty-home}/etc/jaspi/jetty-{ee-current}-jaspi-demo.xml[]
----
Other custom or 3rd party modules that are compatible with the `ServerAuthModule` interface in JASPI can be registered in the same way.
@ -63,8 +64,8 @@ The `CallerPrincipalCallback` and `GroupPrincipalCallback` do not require use of
Jetty provides an implementation of the `AuthConfigFactory` interface which is used to register `AuthConfigProviders`. This can be replaced by a custom implementation by adding a custom module which provides `auth-config-factory`.
This custom module must reference an XML file which sets a new instance of the `AuthConfigFactory` with the static method `AuthConfigFactory.setFactory()`.
For an example of this see the `ee{9,10}-jaspi-default-auth-config-factory` module, which provides the default implementation used by Jetty.
For an example of this see the `{ee-current}-jaspi-default-auth-config-factory` module, which provides the default implementation used by Jetty.
----
include::{JETTY_HOME}/modules/ee10-jaspi-default-auth-config-factory.mod[]
include::{jetty-home}/modules/{ee-current}-jaspi-default-auth-config-factory.mod[]
----

View File

@ -14,7 +14,7 @@
[[og-jmx-local]]
==== Enabling Local JMX Support
As with many other Jetty features, local JMX support is enabled with the `jmx` Jetty module:
As with many other Jetty features, local JMX support is enabled with the xref:og-module-jmx[`jmx` Jetty module]:
----
$ java -jar $JETTY_HOME/start.jar --add-module=jmx

View File

@ -14,10 +14,11 @@
[[og-jsp]]
=== Java Server Pages
Jetty supports JSP via the `ee{8,9,10}-jsp` modules, which are based on Apache Jasper:
Jetty supports JSP via the `{ee-all}-jsp` modules, which are based on Apache Jasper:
[subs=attributes]
----
include::{JETTY_HOME}/modules/ee10-jsp.mod[]
include::{jetty-home}/modules/{ee-current}-jsp.mod[]
----
Logging has been bridged to Jetty logging, so you can enable logging for the `org.apache.jasper` package, subpackages and classes as usual.
@ -172,10 +173,11 @@ If the value you set doesn't take effect, try using all lower case instead of ca
=== JavaServer Pages Standard Tag Libraries
The JavaServer Pages Standard Tag Library (JSTL) is part of the Jetty distribution, and is available via the `ee{8,9,10}-jstl` modules:
The JavaServer Pages Standard Tag Library (JSTL) is part of the Jetty distribution, and is available via the `{ee-all}-jstl` modules:
[subs=attributes]
----
include::{JETTY_HOME}/modules/ee10-jstl.mod[]
include::{jetty-home}/modules/{ee-current}-jstl.mod[]
----
When enabled, Jetty will make the JSTL tags available for your webapps.

View File

@ -21,5 +21,5 @@ You can configure the list of application protocols negotiated by the ALPN mecha
The module properties are:
----
include::{JETTY_HOME}/modules/alpn.mod[tags=documentation]
include::{jetty-home}/modules/alpn.mod[tags=documentation]
----

View File

@ -28,7 +28,7 @@ Excess buffers will not be pooled and will be eventually garbage collected.
The module file is `$JETTY_HOME/modules/bytebufferpool.mod`:
----
include::{JETTY_HOME}/modules/bytebufferpool.mod[]
include::{jetty-home}/modules/bytebufferpool.mod[]
----
Among the configurable properties, the most relevant are:

View File

@ -22,5 +22,5 @@ Old, rolled files are kept for the number of days specified by the `jetty.consol
The module properties are:
----
include::{JETTY_HOME}/modules/console-capture.mod[tags=documentation]
include::{jetty-home}/modules/console-capture.mod[tags=documentation]
----

View File

@ -1,33 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2022 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
// ========================================================================
//
[[og-module-deploy]]
===== Module `deploy`
The `deploy` module provides the deployment feature through a `DeploymentManager` component that watches a directory for changes (see xref:og-deploy[how to deploy web applications] for more information).
Files or directories added in this monitored directory cause the `DeploymentManager` to deploy them as web applications; updating files already existing in this monitored directory cause the `DeploymentManager` to re-deploy the correspondent web application; removing files in this monitored directory cause the `DeploymentManager` to undeploy the correspondent web application (see also xref:og-deploy-rules[here] for more information).
The module file is `$JETTY_HOME/modules/deploy.mod`:
----
include::{JETTY_HOME}/modules/deploy.mod[]
----
Among the configurable properties, the most relevant are:
`jetty.deploy.monitoredDir`::
The name of the monitored directory.
`jetty.deploy.scanInterval`::
The scan period in seconds, that is how frequently the `DeploymentManager` wakes up to scan the monitored directory for changes.
Setting `jetty.deploy.scanInterval=0` disabled _hot_ deployment so that only static deployment will be possible (see also xref:og-deploy-hot-static[here] for more information).

View File

@ -0,0 +1,38 @@
//
// ========================================================================
// Copyright (c) 1995-2022 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
// ========================================================================
//
[[og-module-eeN-deploy]]
===== Module `{ee-all}-deploy`
include::{jetty-home}/modules/{ee-current}-deploy.mod[tags=description]
Deployment is managed via a `DeploymentManager` component that watches a directory for changes.
See xref:og-deploy[how to deploy web applications] for more information.
Adding files or directories to this monitored directory will cause the `DeploymentManager` to deploy them as web applications; updating files already existing in this monitored directory will cause the `DeploymentManager` to re-deploy the corresponding web application; removing files in this monitored directory will cause the `DeploymentManager` to "undeploy" the corresponding web application.
(You can find a more detailed discussion of these rules in the xref:og-deploy-rules[deployment rules] section.)
Multiple versions of this module exist (`{ee-all}-deploy`) to support each Jakarta EE platform's version of the Java Servlet specification.
Jetty's configuration properties are nearly identical across these versions; the configuration properties for the `{ee-current}-deploy` Jetty module are:
----
include::{jetty-home}/modules/{ee-current}-deploy.mod[tags=ini-template]
----
Among the configurable properties, the most relevant are:
`jetty.deploy.monitoredDir`::
The name of the monitored directory.
`jetty.deploy.scanInterval`::
The scan period in seconds, that is how frequently the `DeploymentManager` wakes up to scan the monitored directory for changes.
Setting `jetty.deploy.scanInterval=0` disabled _hot_ deployment so that only static deployment will be possible (see also xref:og-deploy-hot-static[here] for more information).

View File

@ -11,16 +11,14 @@
// ========================================================================
//
package org.eclipse.jetty.server.handler;
[[og-module-eeN-webapp]]
===== Module `{ee-all}-webapp`
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.util.annotation.ManagedObject;
include::{jetty-home}/modules/{ee-current}-webapp.mod[tags=description]
/**
* @deprecated Use {@link Handler.Wrapper}
*/
@ManagedObject("Handler wrapping another Handler")
@Deprecated
public class HandlerWrapper extends Handler.Wrapper
{
}
Multiple versions of this module exist (`{ee-all}-webapp`) to support each Jakarta EE platform's version of the Java Servlet specification.
Jetty's configuration properties are identical across all versions of this module, and are as follows:
----
include::{jetty-home}/modules/{ee-current}-webapp.mod[tags=ini-template]
----

View File

@ -19,5 +19,5 @@ The `http-forwarded` module provides support for processing the `Forwarded` HTTP
The module properties are:
----
include::{JETTY_HOME}/modules/http-forwarded.mod[tags=documentation]
include::{jetty-home}/modules/http-forwarded.mod[tags=documentation]
----

View File

@ -19,7 +19,7 @@ The `http` module provides the clear-text connector and support for the clear-te
The module properties to configure the clear-text connector are:
----
include::{JETTY_HOME}/modules/http.mod[tags=documentation]
include::{jetty-home}/modules/http.mod[tags=documentation]
----
Among the configurable properties, the most relevant are:

View File

@ -19,7 +19,7 @@ The `http2` module enables support for the secure HTTP/2 protocol.
The module properties are:
----
include::{JETTY_HOME}/modules/http2.mod[tags=documentation]
include::{jetty-home}/modules/http2.mod[tags=documentation]
----
// tag::rate-control[]

View File

@ -19,7 +19,7 @@ The `http2c` module enables support for the clear-text HTTP/2 protocol.
The module properties are:
----
include::{JETTY_HOME}/modules/http2c.mod[tags=documentation]
include::{jetty-home}/modules/http2c.mod[tags=documentation]
----
include::module-http2.adoc[tags=rate-control]

View File

@ -19,5 +19,5 @@ The `http3` module enables support for the HTTP/3 protocol.
The module properties are:
----
include::{JETTY_HOME}/modules/http3.mod[tags=documentation]
include::{jetty-home}/modules/http3.mod[tags=documentation]
----

View File

@ -19,5 +19,5 @@ The `https` module provides the HTTP/1.1 protocol to the xref:og-module-ssl[`ssl
The module file is `$JETTY_HOME/modules/https.mod`:
----
include::{JETTY_HOME}/modules/https.mod[]
include::{jetty-home}/modules/https.mod[]
----

View File

@ -19,7 +19,7 @@ The `jmx-remote` module provides remote access to JMX clients.
The module properties to configure remote JMX connector are:
----
include::{JETTY_HOME}/modules/jmx-remote.mod[tags=documentation]
include::{jetty-home}/modules/jmx-remote.mod[tags=documentation]
----
The system property `java.rmi.server.hostname` is specified with the usual notation, prepending a `-D` in front of the system property name.

View File

@ -11,23 +11,10 @@
// ========================================================================
//
package org.eclipse.jetty.server.handler;
[[og-module-jmx]]
===== Module `jmx`
import org.eclipse.jetty.server.Handler;
include::{jetty-home}/modules/{ee-current}-webapp.mod[tags=description]
/**
* HandlerList.
* @deprecated
*/
@Deprecated
public class HandlerList extends Handler.Collection
{
public HandlerList()
{
}
public HandlerList(Handler... handlers)
{
super(handlers);
}
}
This configuration is useful for xref:og-jmx-local[local development and testing].
If you need to xref:og-jmx-remote[enable remote access], use the xref:og-jmx-remote[`jmx-remote` module].

View File

@ -19,7 +19,7 @@ The `requestlog` module provides HTTP request/response logging in the standard l
The module properties are:
----
include::{JETTY_HOME}/modules/requestlog.mod[tags=documentation]
include::{jetty-home}/modules/requestlog.mod[tags=documentation]
----
The property `jetty.requestlog.formatString` can be customized using format codes.

View File

@ -0,0 +1,22 @@
//
// ========================================================================
// Copyright (c) 1995-2022 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
// ========================================================================
//
[[og-module-resources]]
===== Module `resources`
include::{jetty-home}/modules/resources.mod[tags=description]
A common use-case for this module is to provide resources for third-party libraries via the xref:og-start-start-class-path[server classpath].
For instance, many logging libraries (including https://logging.apache.org/log4j/2.x/[Log4j2] and https://logback.qos.ch/[Logback]) look for their configuration files on the classpath.
Jetty provides a logging library implementation -- enabled via the `logging-jetty` module -- whose configuration file is `$JETTY_BASE/resources/jetty-logging.properties`.

View File

@ -31,7 +31,7 @@ See also the xref:og-protocols[protocols section] for more information about the
The module properties to configure generic HTTP properties are listed below. Mostly they frequently apply to HTTP/1, HTTP/2 and HTTP/3, but some parameters are version specific:
----
include::{JETTY_HOME}/modules/server.mod[tags=documentation-http-config]
include::{jetty-home}/modules/server.mod[tags=documentation-http-config]
----
Among the configurable properties, the most relevant are:
@ -58,7 +58,7 @@ Server: Jetty({version})
The module properties to configure the Jetty server are:
----
include::{JETTY_HOME}/modules/server.mod[tags=documentation-server-config]
include::{jetty-home}/modules/server.mod[tags=documentation-server-config]
----
Among the configurable properties, the most relevant are:
@ -83,7 +83,7 @@ The Jetty server strives to keep up with the latest link:https://en.wikipedia.or
The module properties to configure the Jetty server compliance are:
----
include::{JETTY_HOME}/modules/server.mod[tags=documentation-server-compliance]
include::{jetty-home}/modules/server.mod[tags=documentation-server-compliance]
----
Among the configurable properties, the most relevant are:
@ -128,5 +128,5 @@ For more information about `CookieCompliance` see also xref:{prog-guide}#pg-serv
The module properties to configure the Jetty server scheduler are:
----
include::{JETTY_HOME}/modules/server.mod[tags=documentation-scheduler-config]
include::{jetty-home}/modules/server.mod[tags=documentation-scheduler-config]
----

View File

@ -20,5 +20,5 @@ When the scanning detects a change to the KeyStore file, the correspondent `SslC
The module properties are:
----
include::{JETTY_HOME}/modules/ssl-reload.mod[tags=documentation]
include::{jetty-home}/modules/ssl-reload.mod[tags=documentation]
----

View File

@ -22,7 +22,7 @@ The `ssl` module provides the secure connector, and allows you to configure the
The module properties to configure the secure connector are:
----
include::{JETTY_HOME}/modules/ssl.mod[tags=documentation-connector]
include::{jetty-home}/modules/ssl.mod[tags=documentation-connector]
----
Among the configurable properties, the most relevant are:
@ -41,7 +41,7 @@ Refer to xref:og-module-http-selectors[this section] for more information about
The module properties to configure the KeyStore and TLS parameters are:
----
include::{JETTY_HOME}/modules/ssl.mod[tags=documentation-ssl-context]
include::{jetty-home}/modules/ssl.mod[tags=documentation-ssl-context]
----
[[og-module-ssl-keystore-tls]]

View File

@ -20,7 +20,7 @@ The KeyStore file is automatically deleted when the JVM exits, and re-created wh
The module file is `$JETTY_HOME/modules/test-keystore.mod`:
----
include::{JETTY_HOME}/modules/test-keystore.mod[]
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.

View File

@ -23,7 +23,7 @@ Refer to the xref:og-module-threadpool[`threadpool`] Jetty module for the genera
The module properties to configure the thread pool are:
----
include::{JETTY_HOME}/modules/threadpool-virtual-preview.mod[tags=documentation]
include::{jetty-home}/modules/threadpool-virtual-preview.mod[tags=documentation]
----
The specific properties to configure virtual threads are:

View File

@ -24,7 +24,7 @@ Start with the default value of `maxThreads`, and tune for larger values if need
The module properties to configure the thread pool are:
----
include::{JETTY_HOME}/modules/threadpool.mod[tags=documentation]
include::{jetty-home}/modules/threadpool.mod[tags=documentation]
----
Among the configurable properties, the most relevant are:

View File

@ -25,5 +25,5 @@ See link:https://www.iana.org/assignments/well-known-uris/well-known-uris.xhtml[
The module properties are:
----
include::{JETTY_HOME}/modules/well-known.mod[tags=documentation]
include::{jetty-home}/modules/well-known.mod[tags=documentation]
----

View File

@ -17,19 +17,22 @@
include::module-alpn.adoc[]
include::module-bytebufferpool.adoc[]
include::module-console-capture.adoc[]
include::module-deploy.adoc[]
include::module-eeN-deploy.adoc[]
include::module-http.adoc[]
include::module-http2.adoc[]
include::module-http2c.adoc[]
include::module-http3.adoc[]
include::module-http-forwarded.adoc[]
include::module-https.adoc[]
include::module-jmx.adoc[]
include::module-jmx-remote.adoc[]
include::module-requestlog.adoc[]
include::module-resources.adoc[]
include::module-server.adoc[]
include::module-ssl.adoc[]
include::module-ssl-reload.adoc[]
include::module-test-keystore.adoc[]
include::module-threadpool.adoc[]
include::module-threadpool-virtual-preview.adoc[]
include::module-eeN-webapp.adoc[]
include::module-well-known.adoc[]

View File

@ -79,7 +79,7 @@ startJVM --> forkedJVM: " waits for"
It is worth mentioning that there are two standard Jetty modules that allow you to easily add entries to the Jetty server class-path:
* The `resources` module, that adds the `$JETTY_BASE/resources/` directory to the server class-path.
* The xref:og-module-resources[`resources` module], which adds the `$JETTY_BASE/resources` directory to the server class-path.
This is useful if you have third party libraries that lookup resources from the class-path: just put those resources in the `$JETTY_BASE/resources/` directory. +
Logging libraries often perform class-path lookup of their configuration files (for example, `log4j.properties`, `log4j.xml`, `logging.properties`, and `logback.xml`), so `$JETTY_BASE/resources/` is the ideal place to add those files. +
* The the `ext` module, that adds all the `+*.jar+` files under the `$JETTY_BASE/lib/ext/` directory, and subdirectories recursively, to the server class-path. +

View File

@ -50,5 +50,5 @@ A filtering `Handler` is a handler that perform some modification to the request
include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/http/HTTPServerDocs.java[tags=handlerFilter]
----
Note how a filtering `Handler` extends from `HandlerWrapper` and as such needs another handler to forward the request processing to, and how the two ``Handler``s needs to be linked together to work properly.
Note how a filtering `Handler` extends from `Handler.Wrapper` and as such needs another handler to forward the request processing to, and how the two ``Handler``s needs to be linked together to work properly.

View File

@ -70,14 +70,14 @@ Server
Server applications may need to deploy to Jetty more than one web application.
Recall from the xref:pg-server-http-handler[introduction] that Jetty offers `HandlerCollection` and `HandlerList` that may contain a sequence of children ``Handler``s.
However, both of these have no knowledge of the concept of _context_ and just iterate through the sequence of ``Handler``s.
Recall from the xref:pg-server-http-handler[introduction] that Jetty offers `Handler.Collection` that contains a sequence of child ``Handler``s.
However, this has no knowledge of the concept of _context_ and just iterates through the sequence of ``Handler``s.
A better choice for multiple web application is `ContextHandlerCollection`, that matches a _context_ from either its _context path_ or _virtual host_, without iterating through the ``Handler``s.
If `ContextHandlerCollection` does not find a match, it just returns.
If `ContextHandlerCollection` does not find a match, it just returns `false` from its `process(\...)` method.
What happens next depends on the `Handler` tree structure: other ``Handler``s may be invoked after `ContextHandlerCollection`, for example `DefaultHandler` (see xref:pg-server-http-handler-use-util-default-handler[this section]).
Eventually, if `Request.setHandled(true)` is not called, Jetty returns an HTTP `404` response to the client.
Eventually, if no `Handler` returns `true` from their own `process(\...)` method, then Jetty returns an HTTP `404` response to the client.
[source,java,indent=0]
----
@ -132,7 +132,7 @@ If you need to serve static resources from multiple directories:
include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/http/HTTPServerDocs.java[tags=multipleResourcesHandler]
----
If the resource is not found, `ResourceHandler` will not call `Request.setHandled(true)` so what happens next depends on the `Handler` tree structure.
If the resource is not found, `ResourceHandler` will not return `true` from the `process(\...)` method, so what happens next depends on the `Handler` tree structure.
See also xref:pg-server-http-handler-use-util-default-handler[how to use] `DefaultHandler`.
[[pg-server-http-handler-use-util-gzip-handler]]
@ -277,7 +277,7 @@ include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/http/HTTPSer
[[pg-server-http-handler-use-util-default-handler]]
====== DefaultHandler
`DefaultHandler` is a terminal `Handler` that always calls `Request.setHandled(true)` and performs the following:
`DefaultHandler` is a terminal `Handler` that always returns `true` from its `process(\...)` method and performs the following:
* Serves the `favicon.ico` Jetty icon when it is requested
* Sends a HTTP `404` response for any other request
@ -295,12 +295,11 @@ The `Handler` tree structure looks like the following:
[source,screen]
----
Server
└── HandlerList
├── ContextHandlerCollection
│ ├── ContextHandler 1
│ :── ...
│ └── ContextHandler N
└── DefaultHandler
├── ContextHandlerCollection
│ ├── ContextHandler 1
│ :── ...
│ └── ContextHandler N
└── DefaultHandler
----
In the example above, `ContextHandlerCollection` will try to match a request to one of the contexts; if the match fails, `HandlerList` will call the next `Handler` which is `DefaultHandler` that will return a HTTP `404` with an HTML page showing the existing contexts deployed on the `Server`.
@ -350,8 +349,8 @@ Note also how adding a `Servlet` or a `Filter` returns a _holder_ object that ca
When a request arrives to `ServletContextHandler` the request URI will be matched against the ``Filter``s and ``Servlet`` mappings and only those that match will process the request, as dictated by the Servlet specification.
IMPORTANT: `ServletContextHandler` is a terminal `Handler`, that is it always calls `Request.setHandled(true)` when invoked.
Server applications must be careful when creating the `Handler` tree to put ``ServletContextHandler``s as last ``Handler``s in a `HandlerList` or as children of `ContextHandlerCollection`.
IMPORTANT: `ServletContextHandler` is a terminal `Handler`, that is it always returns `true` from its `process(\...)` method when invoked.
Server applications must be careful when creating the `Handler` tree to put ``ServletContextHandler``s as last ``Handler``s in any `Handler.Collection` or as children of a `ContextHandlerCollection`.
// TODO: revise what above, as ServletContextHandler is not a terminal handler.
// TODO: introduce the fact that ServletContextHandler can have a class loader that may be used to "isolate" web application classes.

View File

@ -16,21 +16,15 @@
An `org.eclipse.jetty.server.Handler` is the component that processes incoming HTTP requests and eventually produces HTTP responses.
``Handler``s can be organized in different ways:
``Handler``s can process the HTTP request themselves, or they can be ``Handler.Container``s that delegate HTTP request processing to one or more contained ``Handler``s.
This allows ``Handler``s to be organised as a tree comprised of:
* in a sequence, where ``Handler``s are invoked one after the other
** `HandlerCollection` invokes _all_ ``Handler``s one after the other
** `HandlerList` invokes ``Handlers``s until one calls `Request.setHandled(true)` to indicate that the request has been handled and no further `Handler` should be invoked
* nested, where one `Handler` invokes the next, nested, `Handler`
** `HandlerWrapper` implements this behavior
* Leaf ``Handler``s that return `true` from the `process(\...)` method, generate a response and succeed the `Callback`.
* A `Handler.Wrapper` can be used to form a chain of ``Handler``s where request, response or callback objects are wrapped in the `process(\...)` method before being passed down the chain.
* A `Handler.Collection` that contains a sequence of ``Handler``s, with each `Handler` being called in sequence until one returns `true` from its `process(\..)` method.
* A specialized `Handler.Container` that may use properties of the request (for example, the URI, or a header, etc.) to select from one or more contained ``Handler``s to delegate the HTTP request processing to.
The `HandlerCollection` behavior (invoking _all_ handlers) is useful when for example the last `Handler` is a logging `Handler` that logs the request (that may have been modified by previous handlers).
The `HandlerList` behavior (invoking handlers up to the first that calls `Request.setHandled(true)`) is useful when each handler processes a different URIs or a different virtual hosts: ``Handler``s are invoked one after the other until one matches the URI or virtual host.
The nested behavior is useful to enrich the request with additional services such as HTTP session support (`SessionHandler`), or with specific behaviors dictated by the Servlet specification (`ServletHandler`).
``Handler``s can be organized in a tree by composing them together:
A `Handler` tree is created by composing ``Handler``s together:
[source,java,indent=0]
----
@ -42,7 +36,7 @@ The corresponding `Handler` tree structure looks like the following:
[source,screen]
----
Server
└── LoggingHandler
└── GzipHandler
└── Handler.Collection
├── App1Handler
└── App2Handler

View File

@ -36,6 +36,7 @@ import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpHeaderValue;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory;
import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory;
import org.eclipse.jetty.http3.server.HTTP3ServerConnectionFactory;
@ -205,7 +206,7 @@ public class HTTPServerDocs
ServletContextHandler otherContext = new ServletContextHandler();
mainContext.setContextPath("/other");
server.setHandler(new HandlerList(requestLogHandler, otherContext));
server.setHandler(new Handler.Collection(requestLogHandler, otherContext));
*/
// end::contextRequestLog[]
}
@ -505,14 +506,14 @@ public class HTTPServerDocs
}
// tag::handlerTree[]
// Create a Server instance.
Server server = new Server();
LoggingHandler loggingHandler = new LoggingHandler();
// Link the root Handler with the Server.
server.setHandler(loggingHandler);
GzipHandler gzipHandler = new GzipHandler();
server.setHandler(gzipHandler);
Handler.Collection collection = new Handler.Collection();
gzipHandler.setHandler(collection);
collection.addHandler(new App1Handler());
collection.addHandler(new App2Handler());
// end::handlerTree[]
@ -582,29 +583,32 @@ public class HTTPServerDocs
}
// tag::handlerFilter[]
// TODO: This needs to be rewritten using a custom Processor
/*
class FilterHandler extends HandlerWrapper
class FilterHandler extends Handler.Wrapper
{
@Override
public void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
public boolean process(Request request, Response response, Callback callback) throws Exception
{
String path = request.getRequestURI();
String path = Request.getPathInContext(request);
if (path.startsWith("/old_path/"))
{
// Rewrite old paths to new paths.
HttpURI uri = jettyRequest.getHttpURI();
HttpURI uri = request.getHttpURI();
String newPath = "/new_path/" + path.substring("/old_path/".length());
HttpURI newURI = HttpURI.build(uri).path(newPath);
// Modify the request object.
jettyRequest.setHttpURI(newURI);
HttpURI newURI = HttpURI.build(uri).path(newPath).asImmutable();
// Modify the request object by wrapping the HttpURI
request = new Request.Wrapper(request)
{
@Override
public HttpURI getHttpURI()
{
return newURI;
}
};
}
// This Handler is not handling the request, so
// it does not call jettyRequest.setHandled(true).
// Forward to the next Handler.
super.handle(target, jettyRequest, request, response);
return super.process(request, response, callback);
}
}
@ -618,7 +622,6 @@ public class HTTPServerDocs
server.setHandler(filter);
server.start();
*/
// end::handlerFilter[]
}
@ -999,22 +1002,16 @@ public class HTTPServerDocs
{
// tag::defaultHandler[]
Server server = new Server();
server.setDefaultHandler(new DefaultHandler(false, true));
Connector connector = new ServerConnector(server);
server.addConnector(connector);
// Create a Handler collection.
Handler.Collection handlerList = new Handler.Collection();
// Add as first a ContextHandlerCollection to manage contexts.
// Add a ContextHandlerCollection to manage contexts.
ContextHandlerCollection contexts = new ContextHandlerCollection();
handlerList.addHandler(contexts);
// Add as last a DefaultHandler.
DefaultHandler defaultHandler = new DefaultHandler();
handlerList.addHandler(defaultHandler);
// Link the HandlerList to the Server.
server.setHandler(handlerList);
// Link the contexts to the Server.
server.setHandler(contexts);
server.start();
// end::defaultHandler[]

View File

@ -217,7 +217,6 @@ public class InputStreamResponseListener extends Listener.Adapter
/**
* Waits for the given timeout for the whole request/response cycle to be finished,
* then returns the corresponding result.
* <p>
*
* @param timeout the time to wait
* @param unit the timeout unit

View File

@ -129,7 +129,7 @@ public class ProxyConfiguration
}
/**
* @return the list of origins that must be proxied
* @return the set of origins that must be proxied
* @see #matches(Origin)
* @see #getExcludedAddresses()
*/
@ -139,7 +139,7 @@ public class ProxyConfiguration
}
/**
* @return the list of origins that must not be proxied.
* @return the set of origins that must not be proxied.
* @see #matches(Origin)
* @see #getIncludedAddresses()
*/

View File

@ -40,7 +40,6 @@ import org.slf4j.LoggerFactory;
* <p>Works in conjunction with {@link HttpClientTransportDynamic}
* so that the protocol to upgrade to must be one of the application
* protocols supported by HttpClientTransportDynamic.</p>
* <p></p>
*/
public class ProtocolHttpUpgrader implements HttpUpgrader
{

View File

@ -1,5 +1,5 @@
[description]
Enables web application deployment from the $JETTY_BASE/webapps/ directory.
This module enables web application deployment from the `$JETTY_BASE/webapps` directory.
[depend]
server
@ -7,6 +7,9 @@ server
[lib]
lib/jetty-deploy-${jetty.version}.jar
[files]
webapps/
[xml]
etc/jetty-deploy.xml

View File

@ -93,29 +93,11 @@
-->
</New>
<!-- =========================================================== -->
<!-- Set the default handler structure for the Server -->
<!-- A handler collection is used to pass received requests to -->
<!-- both the ContextHandlerCollection, which selects the next -->
<!-- handler by context path and virtual host, and the -->
<!-- DefaultHandler, which handles any requests not handled by -->
<!-- the context handlers. -->
<!-- Other handlers may be added to the "Handlers" collection. -->
<!-- =========================================================== -->
<Set name="handler">
<New id="Handlers" class="org.eclipse.jetty.server.Handler$Collection">
<Set name="handlers">
<Array type="org.eclipse.jetty.server.Handler">
<Item>
<New id="Contexts" class="org.eclipse.jetty.server.handler.ContextHandlerCollection"/>
</Item>
<Item>
<New id="DefaultHandler" class="org.eclipse.jetty.server.handler.DefaultHandler"/>
</Item>
</Array>
</Set>
</New>
<New id="Contexts" class="org.eclipse.jetty.server.handler.ContextHandlerCollection"/>
</Set>
<!-- =========================================================== -->

View File

@ -228,13 +228,13 @@ public class JettyHttpServer extends com.sun.net.httpserver.HttpServer
JettyHttpContext context = new JettyHttpContext(this, path, httpHandler);
HttpSpiContextHandler jettyContextHandler = context.getJettyContextHandler();
ContextHandlerCollection chc = _server.getDescendant(ContextHandlerCollection.class);
ContextHandlerCollection contexts = _server.getDescendant(ContextHandlerCollection.class);
if (chc == null)
if (contexts == null)
throw new RuntimeException("could not find ContextHandlerCollection, you must configure one");
chc.addHandler(jettyContextHandler);
if (chc.isStarted())
contexts.addHandler(jettyContextHandler);
if (contexts.isStarted())
{
try
{

View File

@ -21,8 +21,6 @@ import com.sun.net.httpserver.HttpsServer;
import com.sun.net.httpserver.spi.HttpServerProvider;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerList;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.util.thread.ThreadPool;
@ -31,7 +29,6 @@ import org.eclipse.jetty.util.thread.ThreadPool;
*/
public class JettyHttpServerProvider extends HttpServerProvider
{
private static Server _server;
public static void setServer(Server server)
@ -51,8 +48,7 @@ public class JettyHttpServerProvider extends HttpServerProvider
ThreadPool threadPool = new DelegatingThreadPool(new QueuedThreadPool());
server = new Server(threadPool);
HandlerList handlerCollection = new HandlerList(new ContextHandlerCollection(), new DefaultHandler());
server.setHandler(handlerCollection);
server.setHandler(new ContextHandlerCollection(true));
shared = false;
}

View File

@ -28,6 +28,9 @@ import org.eclipse.jetty.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Jetty Management of RFC6265 HTTP Cookies (with fallback support for RFC2965)
*/
public class HttpCookie
{
private static final Logger LOG = LoggerFactory.getLogger(HttpCookie.class);
@ -71,6 +74,11 @@ public class HttpCookie
}
}
/**
* Names of well-known Attributes that are parsed by the constructors and shouldn't be present in the stored {@link #getAttributes()} Map.
*/
private static final List<String> PARSED_ATTRIBUTE_NAMES = List.of("Domain", "Path", "Max-Age", "HttpOnly", "Secure", "Comment");
private final String _name;
private final String _value;
private final String _comment;
@ -83,37 +91,109 @@ public class HttpCookie
private final long _expiration;
private final Map<String, String> _attributes;
/**
* Create new HttpCookie from specific values.
*
* @param name the name of the cookie
* @param value the value of the cookie
*/
public HttpCookie(String name, String value)
{
this(name, value, -1);
}
/**
* Create new HttpCookie from specific values.
*
* @param name the name of the cookie
* @param value the value of the cookie
* @param domain the {@code Domain} value used for Domain-Matching rules on the cookie
* @param path the {@code Path} value to use for Path-Matching rules on the cookie
*/
public HttpCookie(String name, String value, String domain, String path)
{
this(name, value, domain, path, -1, false, false);
}
/**
* Create new HttpCookie from specific values.
*
* @param name the name of the cookie
* @param value the value of the cookie
* @param maxAge the {@code Max-Age} attribute value (in seconds) for the cookie
*/
public HttpCookie(String name, String value, long maxAge)
{
this(name, value, null, null, maxAge, false, false);
}
/**
* Create new HttpCookie from specific values.
*
* @param name the name of the cookie
* @param value the value of the cookie
* @param domain the {@code Domain} value used for Domain-Matching rules on the cookie
* @param path the {@code Path} value to use for Path-Matching rules on the cookie
* @param maxAge the {@code Max-Age} attribute value (in seconds) for the cookie
* @param httpOnly the {@code HttpOnly} attribute of the cookie
* @param secure the {@code Secure} attribute of the cookie
*/
public HttpCookie(String name, String value, String domain, String path, long maxAge, boolean httpOnly, boolean secure)
{
this(name, value, domain, path, maxAge, httpOnly, secure, null, 0);
}
/**
* Create new HttpCookie from specific values.
*
* @param name the name of the cookie
* @param value the value of the cookie
* @param domain the {@code Domain} value used for Domain-Matching rules on the cookie
* @param path the {@code Path} value to use for Path-Matching rules on the cookie
* @param maxAge the {@code Max-Age} attribute value (in seconds) for the cookie
* @param httpOnly the {@code HttpOnly} attribute of the cookie
* @param secure the {@code Secure} attribute of the cookie
* @param comment the comment of the cookie (not used in RFC6265 or Servlet 6+, only in RFC2965 mode)
* @param version the version of the cookie (not used in RFC6265 or Servlet 6+, only in RFC2965 mode)
*/
public HttpCookie(String name, String value, String domain, String path, long maxAge, boolean httpOnly, boolean secure, String comment, int version)
{
this(name, value, domain, path, maxAge, httpOnly, secure, comment, version, (SameSite)null);
}
/**
* Create new HttpCookie from specific values and {@link SameSite}.
*
* @param name the name of the cookie
* @param value the value of the cookie
* @param domain the {@code Domain} value used for Domain-Matching rules on the cookie
* @param path the {@code Path} value to use for Path-Matching rules on the cookie
* @param maxAge the {@code Max-Age} attribute value (in seconds) for the cookie
* @param httpOnly the {@code HttpOnly} attribute of the cookie
* @param secure the {@code Secure} attribute of the cookie
* @param comment the comment of the cookie (not used in RFC6265 or Servlet 6+, only in RFC2965 mode)
* @param version the version of the cookie (not used in RFC6265 or Servlet 6+, only in RFC2965 mode)
* @param sameSite the <a href="https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis">{@code SameSite} attribute</a> value to use for this cookie (only for RFC6265 mode)
*/
public HttpCookie(String name, String value, String domain, String path, long maxAge, boolean httpOnly, boolean secure, String comment, int version, SameSite sameSite)
{
this(name, value, domain, path, maxAge, httpOnly, secure, comment, version, Collections.singletonMap("SameSite", sameSite == null ? null : sameSite.getAttributeValue()));
}
/**
* Create new HttpCookie from specific values and attributes.
*
* @param name the name of the cookie
* @param value the value of the cookie
* @param domain the {@code Domain} value used for Domain-Matching rules on the cookie
* @param path the {@code Path} value to use for Path-Matching rules on the cookie
* @param maxAge the {@code Max-Age} attribute value (in seconds) for the cookie
* @param httpOnly the {@code HttpOnly} attribute of the cookie
* @param secure the {@code Secure} attribute of the cookie
* @param comment the comment of the cookie (not used in RFC6265 or Servlet 6+, only in RFC2965 mode)
* @param version the version of the cookie (not used in RFC6265 or Servlet 6+, only in RFC2965 mode)
* @param attributes the map of attributes to use with this cookie (this map is copied over into the resulting HttpCookie, but without map entries that are also parameters on this constructor)
*/
public HttpCookie(String name, String value, String domain, String path, long maxAge, boolean httpOnly, boolean secure, String comment, int version, Map<String, String> attributes)
{
_name = name;
@ -126,26 +206,60 @@ public class HttpCookie
_comment = comment;
_version = version;
_expiration = maxAge < 0 ? -1 : NanoTime.now() + TimeUnit.SECONDS.toNanos(maxAge);
_attributes = (attributes == null ? Collections.emptyMap() : attributes);
Map<String, String> attrs = null;
if (attributes == null || attributes.isEmpty())
attrs = Collections.emptyMap(); // unmodifiable empty map
else
{
// create new map, to only capture relevant attributes
attrs = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
attrs.putAll(attributes);
PARSED_ATTRIBUTE_NAMES.forEach(attrs::remove); // remove names that are also fields
attrs = Collections.unmodifiableMap(attrs); // don't allow attributes to be modified
}
_attributes = attrs;
}
/**
* Create new HttpCookie from specific values and attributes.
*
* @param name the name of the cookie
* @param value the value of the cookie
* @param version the version of the cookie (not used in RFC6265 or Servlet 6+, only in RFC2965 mode)
* @param attributes the map of attributes to use with this cookie (this map is used for field values
* such as {@link #getDomain()}, {@link #getPath()}, {@link #getMaxAge()}, {@link #isHttpOnly()},
* {@link #isSecure()}, {@link #getComment()}. These attributes are removed from the stored
* attributes returned from {@link #getAttributes()}.
*/
public HttpCookie(String name, String value, int version, Map<String, String> attributes)
{
_name = name;
_value = value;
_version = version;
_attributes = (attributes == null ? Collections.emptyMap() : new TreeMap<>(attributes));
//remove all of the well-known attributes, leaving only those pass-through ones
_domain = _attributes.remove("Domain");
_path = _attributes.remove("Path");
Map<String, String> attrs = null;
if (attributes == null || attributes.isEmpty())
attrs = Collections.emptyMap(); // unmodifiable empty map
else
{
// create new map, to only capture relevant attributes
attrs = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
attrs.putAll(attributes);
}
String tmp = _attributes.remove("Max-Age");
// remove all the well-known attributes, leaving only those unique to attributes as pass-through ones
_domain = attrs.remove("Domain");
_path = attrs.remove("Path");
String tmp = attrs.remove("Max-Age");
_maxAge = StringUtil.isBlank(tmp) ? -1L : Long.valueOf(tmp);
_expiration = _maxAge < 0 ? -1 : NanoTime.now() + TimeUnit.SECONDS.toNanos(_maxAge);
_httpOnly = Boolean.parseBoolean(_attributes.remove("HttpOnly"));
_secure = Boolean.parseBoolean(_attributes.remove("Secure"));
_comment = _attributes.remove("Comment");
_httpOnly = Boolean.parseBoolean(attrs.remove("HttpOnly"));
_secure = Boolean.parseBoolean(attrs.remove("Secure"));
_comment = attrs.remove("Comment");
// don't allow attributes to be modified
_attributes = attrs.isEmpty() ? Collections.emptyMap() : Collections.unmodifiableMap(attrs);
}
/**
@ -213,7 +327,7 @@ public class HttpCookie
}
/**
* @return the cookie SameSite enum attribute
* @return the cookie {@code SameSite} attribute value
*/
public SameSite getSameSite()
{
@ -240,6 +354,14 @@ public class HttpCookie
return _expiration != -1 && NanoTime.isBefore(_expiration, timeNanos);
}
/**
* @return the attributes associated with this cookie
*/
public Map<String, String> getAttributes()
{
return _attributes;
}
/**
* @return a string representation of this cookie
*/
@ -272,7 +394,7 @@ public class HttpCookie
*
* @param s value string
* @return true if quoted;
* @throws IllegalArgumentException If there a control characters in the string
* @throws IllegalArgumentException If there is a String contains unexpected / illegal characters
*/
private static boolean isQuoteNeededForCookie(String s)
{
@ -511,10 +633,12 @@ public class HttpCookie
/**
* Extract the bare minimum of info from a Set-Cookie header string.
*
* <p>
* Ideally this method should not be necessary, however as java.net.HttpCookie
* does not yet support generic attributes, we have to use it in a minimal
* fashion. When it supports attributes, we could look at reverting to a
* constructor on o.e.j.h.HttpCookie to take the set-cookie header string.
* </p>
*
* @param setCookieHeader the header as a string
* @return a map containing the name, value, domain, path. max-age of the set cookie header
@ -562,7 +686,7 @@ public class HttpCookie
* @param name the cookie name to check
* @param domain the cookie domain to check
* @param path the cookie path to check
* @return true if all of the name, domain and path all match the HttpCookie, false otherwise
* @return true if name, domain, and path, match all match the HttpCookie, false otherwise
*/
public static boolean match(HttpCookie cookie, String name, String domain, String path)
{
@ -574,12 +698,6 @@ public class HttpCookie
/**
* Check if all old parameters match the new parameters.
*
* @param oldName
* @param oldDomain
* @param oldPath
* @param newName
* @param newDomain
* @param newPath
* @return true if old and new names match exactly and the old and new domains match case-insensitively and the paths match exactly
*/
private static boolean match(String oldName, String oldDomain, String oldPath, String newName, String newDomain, String newPath)

View File

@ -154,21 +154,6 @@ public enum HttpMethod
public static final int POST_AS_INT = ('P' & 0xff) << 24 | ('O' & 0xFF) << 16 | ('S' & 0xFF) << 8 | ('T' & 0xFF);
public static final int HEAD_AS_INT = ('H' & 0xff) << 24 | ('E' & 0xFF) << 16 | ('A' & 0xFF) << 8 | ('D' & 0xFF);
/**
* Optimized lookup to find a method name and trailing space in a byte array.
*
* @param bytes Array containing ISO-8859-1 characters
* @param position The first valid index
* @param limit The first non valid index
* @return An HttpMethod if a match or null if no easy match.
* @deprecated Not used
*/
@Deprecated
public static HttpMethod lookAheadGet(byte[] bytes, final int position, int limit)
{
return LOOK_AHEAD.getBest(bytes, position, limit - position);
}
/**
* Optimized lookup to find a method name and trailing space in a byte array.
*

View File

@ -38,6 +38,8 @@ import static org.eclipse.jetty.http.HttpCompliance.Violation.MULTIPLE_CONTENT_L
import static org.eclipse.jetty.http.HttpCompliance.Violation.NO_COLON_AFTER_FIELD_NAME;
import static org.eclipse.jetty.http.HttpCompliance.Violation.TRANSFER_ENCODING_WITH_CONTENT_LENGTH;
import static org.eclipse.jetty.http.HttpCompliance.Violation.WHITESPACE_AFTER_FIELD_NAME;
import static org.eclipse.jetty.http.HttpTokens.CARRIAGE_RETURN;
import static org.eclipse.jetty.http.HttpTokens.LINE_FEED;
/**
* A Parser for 1.0 and 1.1 as defined by RFC7230
@ -126,6 +128,7 @@ public class HttpParser
.with(new HttpField(HttpHeader.CONTENT_LENGTH, "0"))
.with(new HttpField(HttpHeader.CONTENT_ENCODING, "gzip"))
.with(new HttpField(HttpHeader.CONTENT_ENCODING, "deflate"))
.with(new HostPortHttpField("localhost"))
.with(new HttpField(HttpHeader.TRANSFER_ENCODING, "chunked"))
.with(new HttpField(HttpHeader.EXPIRES, "Fri, 01 Jan 1990 00:00:00 GMT"))
.withAll(() ->
@ -164,12 +167,19 @@ public class HttpParser
return map;
})
.build();
private static final Index.Mutable<HttpField> NO_CACHE = new Index.Builder<HttpField>()
.caseSensitive(false)
.mutable()
.maxCapacity(0)
.build();
private static final int HTTP_AS_INT = ('H' & 0xFF) << 24 | ('T' & 0xFF) << 16 | ('T' & 0xFF) << 8 | ('P' & 0xFF);
private static final int HTTP_1_0_AS_INT = ('/' & 0xFF) << 24 | ('1' & 0xFF) << 16 | ('.' & 0xFF) << 8 | ('0' & 0xFF);
private static final int HTTP_1_1_AS_INT = ('/' & 0xFF) << 24 | ('1' & 0xFF) << 16 | ('.' & 0xFF) << 8 | ('1' & 0xFF);
private static final int HTTP_VERSION_LEN = 8;
private static final int INT_LEN = 4;
// States
public enum FieldState
{
@ -454,15 +464,21 @@ public class HttpParser
if (_cr)
throw new BadMessageException("Bad EOL");
_cr = true;
if (buffer.hasRemaining())
{
// Don't count the CRs and LFs of the chunked encoding.
if (_maxHeaderBytes > 0 && (_state == State.HEADER || _state == State.TRAILER))
_headerBytes++;
return next(buffer);
ch = buffer.get();
t = HttpTokens.TOKENS[0xff & ch];
return switch (t.getType())
{
case CNTL -> throw new IllegalCharacterException(_state, t, buffer);
case LF -> t;
default -> throw new BadMessageException("Bad EOL");
};
}
_cr = true;
return null;
case ALPHA:
@ -489,24 +505,28 @@ public class HttpParser
*/
private boolean quickStart(ByteBuffer buffer)
{
int position = buffer.position();
if (_requestHandler != null)
{
_method = HttpMethod.lookAheadGet(buffer);
if (_method != null)
{
_methodString = _method.asString();
buffer.position(buffer.position() + _methodString.length() + 1);
// The lookAheadGet method above checks for the trailing space,
// so it is safe to move the position 1 more than the method length.
buffer.position(position + _methodString.length() + 1);
setState(State.SPACE1);
return false;
}
}
else if (_responseHandler != null)
else if (_responseHandler != null && buffer.remaining() >= (HTTP_VERSION_LEN + 1) && buffer.getInt(position) == HTTP_AS_INT)
{
_version = HttpVersion.lookAheadGet(buffer);
if (_version != null)
int v = buffer.getInt(position + INT_LEN);
_version = v == HTTP_1_1_AS_INT ? HttpVersion.HTTP_1_1 : v == HTTP_1_0_AS_INT ? HttpVersion.HTTP_1_0 : null;
if (_version != null && buffer.get(position + HTTP_VERSION_LEN) == ' ')
{
buffer.position(buffer.position() + _version.asString().length() + 1);
buffer.position(position + HTTP_VERSION_LEN + 1);
setState(State.SPACE1);
return false;
}
@ -677,6 +697,9 @@ public class HttpParser
case COLON:
_string.append(t.getChar());
break;
case CR:
case LF:
throw new BadMessageException(HttpStatus.BAD_REQUEST_400, "No Status");
default:
throw new IllegalCharacterException(_state, t, buffer);
}
@ -738,6 +761,7 @@ public class HttpParser
break;
case STATUS:
assert _responseHandler != null;
switch (t.getType())
{
case SPACE:
@ -762,9 +786,38 @@ public class HttpParser
break;
case URI:
assert _requestHandler != null;
int position = buffer.position();
int remaining = buffer.remaining();
switch (t.getType())
{
case SPACE:
if (remaining >= (HTTP_VERSION_LEN + 1) && buffer.getInt(position) == HTTP_AS_INT)
{
// try look ahead for request HTTP Version
int v = buffer.getInt(position + INT_LEN);
HttpVersion version = v == HTTP_1_1_AS_INT ? HttpVersion.HTTP_1_1 : v == HTTP_1_0_AS_INT ? HttpVersion.HTTP_1_0 : null;
if (version != null)
{
int p = position + HTTP_VERSION_LEN;
byte next = buffer.get(p++);
if (next == CARRIAGE_RETURN && remaining >= (HTTP_VERSION_LEN + 2))
next = buffer.get(p++);
if (next == LINE_FEED)
{
buffer.position(p);
_version = version;
_string.setLength(0);
checkVersion();
_fieldCache.prepare();
setState(State.HEADER);
_requestHandler.startRequest(_methodString, _uri.toString(), _version);
continue;
}
}
}
setState(State.SPACE2);
break;
@ -812,46 +865,8 @@ public class HttpParser
case COLON:
_string.setLength(0);
_string.append(t.getChar());
if (_responseHandler != null)
{
_length = 1;
setState(State.REASON);
}
else
{
setState(State.REQUEST_VERSION);
// try quick look ahead for HTTP Version
HttpVersion version;
if (buffer.position() > 0 && buffer.hasArray())
version = HttpVersion.lookAheadGet(buffer.array(), buffer.arrayOffset() + buffer.position() - 1, buffer.arrayOffset() + buffer.limit());
else
version = HttpVersion.CACHE.getBest(buffer, 0, buffer.remaining());
if (version != null)
{
int pos = buffer.position() + version.asString().length() - 1;
if (pos < buffer.limit())
{
byte n = buffer.get(pos);
if (n == HttpTokens.CARRIAGE_RETURN)
{
_cr = true;
_version = version;
checkVersion();
_string.setLength(0);
buffer.position(pos + 1);
}
else if (n == HttpTokens.LINE_FEED)
{
_version = version;
checkVersion();
_string.setLength(0);
buffer.position(pos);
}
}
}
}
_length = 1;
setState(_responseHandler != null ? State.REASON : State.REQUEST_VERSION);
break;
case LF:
@ -908,6 +923,7 @@ public class HttpParser
break;
case REASON:
assert _responseHandler != null;
switch (t.getType())
{
case LF:
@ -1232,10 +1248,12 @@ public class HttpParser
// handle new header
if (buffer.hasRemaining())
{
// Try a look ahead for the known header name and value.
HttpField cachedField = _fieldCache.getBest(buffer, -1, buffer.remaining());
// Try a look ahead for the known header name and value in dynamic, then static cache.
// Need to use an offset of -1 and to increase the remaining since we have already consumed
// the first ALPHA/DIGIT/TCHAR byte to switch to this case.
HttpField cachedField = _fieldCache.getBest(buffer, -1, buffer.remaining() + 1);
if (cachedField == null)
cachedField = CACHE.getBest(buffer, -1, buffer.remaining());
cachedField = CACHE.getBest(buffer, -1, buffer.remaining() + 1);
if (cachedField != null)
{
@ -1267,37 +1285,38 @@ public class HttpParser
_header = cachedField.getHeader();
_headerString = n;
if (v == null)
int posAfterName = buffer.position() + n.length() + 1;
if (v == null || (posAfterName + v.length()) >= buffer.limit())
{
// Header only
setState(FieldState.VALUE);
_string.setLength(0);
_length = 0;
buffer.position(buffer.position() + n.length() + 1);
buffer.position(posAfterName);
break;
}
// Header and value
int pos = buffer.position() + n.length() + v.length() + 1;
byte peek = buffer.get(pos);
if (peek == HttpTokens.CARRIAGE_RETURN || peek == HttpTokens.LINE_FEED)
int posAfterValue = posAfterName + v.length();
byte peek = buffer.get(posAfterValue);
if (peek == CARRIAGE_RETURN || peek == LINE_FEED)
{
_field = cachedField;
_valueString = v;
setState(FieldState.IN_VALUE);
if (peek == HttpTokens.CARRIAGE_RETURN)
buffer.position(posAfterValue + 1);
if (peek == LINE_FEED)
{
_cr = true;
buffer.position(pos + 1);
setState(FieldState.FIELD);
break;
}
else
buffer.position(pos);
setState(FieldState.IN_VALUE);
_cr = true;
break;
}
setState(FieldState.IN_VALUE);
setString(v);
buffer.position(pos);
buffer.position(posAfterValue);
break;
}
}
@ -1367,7 +1386,6 @@ public class HttpParser
break;
case WS_AFTER_NAME:
switch (t.getType())
{
case SPACE:
@ -1540,7 +1558,7 @@ public class HttpParser
while (buffer.remaining() > 0)
{
byte b = buffer.get(buffer.position());
if (b != HttpTokens.CARRIAGE_RETURN && b != HttpTokens.LINE_FEED)
if (b != CARRIAGE_RETURN && b != LINE_FEED)
break;
buffer.get();
++whiteSpace;

View File

@ -31,73 +31,6 @@ public enum HttpVersion
.withAll(HttpVersion.values(), HttpVersion::toString)
.build();
/**
* Optimised lookup to find an Http Version and whitespace in a byte array.
*
* @param bytes Array containing ISO-8859-1 characters
* @param position The first valid index
* @param limit The first non valid index
* @return An HttpMethod if a match or null if no easy match.
*/
public static HttpVersion lookAheadGet(byte[] bytes, int position, int limit)
{
int length = limit - position;
if (length < 9)
return null;
if (bytes[position + 4] == '/' && bytes[position + 6] == '.' && Character.isWhitespace((char)bytes[position + 8]) &&
((bytes[position] == 'H' && bytes[position + 1] == 'T' && bytes[position + 2] == 'T' && bytes[position + 3] == 'P') ||
(bytes[position] == 'h' && bytes[position + 1] == 't' && bytes[position + 2] == 't' && bytes[position + 3] == 'p')))
{
switch (bytes[position + 5])
{
case '1':
switch (bytes[position + 7])
{
case '0':
return HTTP_1_0;
case '1':
return HTTP_1_1;
default:
return null;
}
case '2':
switch (bytes[position + 7])
{
case '0':
return HTTP_2;
default:
return null;
}
case '3':
switch (bytes[position + 7])
{
case '0':
return HTTP_3;
default:
return null;
}
default:
return null;
}
}
return null;
}
/**
* Optimised lookup to find an HTTP Version and trailing white space in a byte array.
*
* @param buffer buffer containing ISO-8859-1 characters
* @return An HttpVersion if a match or null if no easy match.
*/
public static HttpVersion lookAheadGet(ByteBuffer buffer)
{
if (buffer.hasArray())
return lookAheadGet(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.arrayOffset() + buffer.limit());
return null;
}
private final String _string;
private final byte[] _bytes;
private final ByteBuffer _buffer;

View File

@ -30,7 +30,7 @@ import org.eclipse.jetty.util.annotation.ManagedOperation;
/**
* The {@code maxHeapMemory} and {@code maxDirectMemory} default heuristic is to use {@link Runtime#maxMemory()}
* divided by 4.</p>
* divided by 4.
*/
@ManagedObject
abstract class AbstractByteBufferPool implements ByteBufferPool

View File

@ -1,5 +1,7 @@
[description]
Enables local JMX support for Jetty components.
# tag::description[]
This module enables local Java Management Extension (JMX) support for Jetty components.
# end::description[]
[depend]
server

View File

@ -33,8 +33,10 @@ import org.slf4j.LoggerFactory;
/**
* AbstractContextProvider
*
* <p>
* Base class for DeploymentManager Providers that can deploy ContextHandlers into
* Jetty that have been discovered via OSGI either as bundles or services.
* </p>
*/
public abstract class AbstractContextProvider extends AbstractLifeCycle implements AppProvider
{

View File

@ -14,9 +14,7 @@
package org.eclipse.jetty.osgi;
import java.io.File;
import java.net.URI;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Dictionary;
@ -30,7 +28,6 @@ import org.eclipse.jetty.deploy.DeploymentManager;
import org.eclipse.jetty.ee.Deployable;
import org.eclipse.jetty.osgi.util.BundleFileLocatorHelperFactory;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.FileID;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceFactory;
@ -60,7 +57,6 @@ public class OSGiApp extends App
* Get the install location of a Bundle as a Path
* @param bundle the Bundle whose location to return
* @return the installed location of the Bundle as a Path
* @throws Exception
*/
private static Path getBundlePath(Bundle bundle) throws Exception
{
@ -77,7 +73,6 @@ public class OSGiApp extends App
* as found on equinox. Eg file:///a/b/c/org.eclipse.osgi/89/0/bundleFile
* @param bundle the bundle
* @return a Resource representing the bundle's installed location
* @throws Exception
*/
private static Resource getBundleAsResource(Bundle bundle) throws Exception
{
@ -93,7 +88,7 @@ public class OSGiApp extends App
/**
* Get or create a contextPath from bundle headers and information
*
* @param bundle
* @param bundle the bundle
* @return a contextPath
*/
private static String getContextPath(Bundle bundle)
@ -229,8 +224,6 @@ public class OSGiApp extends App
/**
* Register the Jetty deployed context/webapp as a service, as
* according to the OSGi Web Application Specification.
*
* @throws Exception
*/
public void registerAsOSGiService() throws Exception
{

View File

@ -383,7 +383,7 @@ public class DefaultBundleClassLoaderHelper implements BundleClassLoaderHelper
{
try
{
/**
/*
* In Concierge:
*
* Option A:

View File

@ -108,8 +108,6 @@ public class Util
* @param bundleSymbolicNames comma separated list of symbolic bundle names
* @param bundleContext the bundle on whose behalf to resolve
* @return List of resolved Paths matching the bundle symbolic names
*
* @throws Exception
*/
public static List<Path> getPathsToBundlesBySymbolicNames(String bundleSymbolicNames, BundleContext bundleContext)
throws Exception

View File

@ -96,7 +96,6 @@ public abstract class QuicheConnection
* @param local the local address on which the buffer was received.
* @param peer the address of the peer from which the buffer was received.
* @return how many bytes were consumed.
* @throws IOException
*/
public abstract int feedCipherBytes(ByteBuffer buffer, SocketAddress local, SocketAddress peer) throws IOException;
@ -104,7 +103,6 @@ public abstract class QuicheConnection
* Fill the given buffer with cipher text to be sent.
* @param buffer the buffer to fill.
* @return how many bytes were added to the buffer.
* @throws IOException
*/
public abstract int drainCipherBytes(ByteBuffer buffer) throws IOException;

View File

@ -441,7 +441,6 @@ public class JnaQuicheConnection extends QuicheConnection
* Fill the given buffer with cipher text to be sent.
* @param buffer the buffer to fill.
* @return how many bytes were added to the buffer.
* @throws IOException
*/
@Override
public int drainCipherBytes(ByteBuffer buffer) throws IOException

View File

@ -22,7 +22,6 @@ import org.eclipse.jetty.util.annotation.Name;
/**
* <p>A rule to rewrite the path and query that match a regular expression pattern with a fixed string.</p>
* <p>The replacement String follows standard {@link Matcher#replaceAll(String)} behavior, including named groups</p>
* <p></p>
*/
public class RewriteRegexRule extends RegexRule
{

View File

@ -156,18 +156,7 @@
</Set>
<Set name="handler">
<New id="Handlers" class="org.eclipse.jetty.server.handler.HandlerList">
<Set name="handlers">
<Array type="org.eclipse.jetty.server.Handler">
<Item>
<New id="Contexts" class="org.eclipse.jetty.server.handler.ContextHandlerCollection" />
</Item>
<Item>
<New id="DefaultHandler" class="org.eclipse.jetty.server.handler.DefaultHandler" />
</Item>
</Array>
</Set>
</New>
<New id="Contexts" class="org.eclipse.jetty.server.handler.ContextHandlerCollection" />
</Set>
</New>
</Set>

View File

@ -81,24 +81,17 @@
</New>
<!-- =========================================================== -->
<!-- Set the default handler structure for the Server -->
<!-- A handler list is used to pass received requests to -->
<!-- both the ContextHandlerCollection, which selects the next -->
<!-- handler by context path and virtual host, and then only if -->
<!-- the request is not handled is the DefaultHandler invoked -->
<!-- Set the handler structure for the Server -->
<!-- =========================================================== -->
<Set name="defaultHandler">
<New id="DefaultHandler" class="org.eclipse.jetty.server.handler.DefaultHandler">
<Arg name="serveFavIcon" type="boolean"><Property name="jetty.server.default.serveFavIcon" default="true"/></Arg>
<Arg name="showContexts" type="boolean"><Property name="jetty.server.default.showContexts" default="true"/></Arg>
</New>
</Set>
<Set name="handler">
<New id="Handlers" class="org.eclipse.jetty.server.Handler$Collection">
<Set name="handlers">
<Array type="org.eclipse.jetty.server.Handler">
<Item>
<New id="Contexts" class="org.eclipse.jetty.server.handler.ContextHandlerCollection"/>
</Item>
<Item>
<New id="DefaultHandler" class="org.eclipse.jetty.server.handler.DefaultHandler"/>
</Item>
</Array>
</Set>
<New id="Contexts" class="org.eclipse.jetty.server.handler.ContextHandlerCollection">
<Set name="dynamic" property="jetty.server.contexts.dynamic"/>
</New>
</Set>

View File

@ -1,8 +1,9 @@
# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html
[description]
Adds the $JETTY_BASE/resources directory to the server classpath.
Useful for configuration property files (eg jetty-logging.properties)
# tag::description[]
This module adds the `$JETTY_BASE/resources` directory to the server's classpath.
# end::description[]
[tags]
classpath

View File

@ -111,3 +111,13 @@ etc/jetty.xml
## The number of server scheduler threads.
# jetty.scheduler.threads=1
# end::documentation-scheduler-config[]
## Whether the handlers of the ContextHandlerCollection can be updated once the server is started
## If set to false, then eeN-deploy module jetty.deploy.scanInterval should also be set to 0.
# jetty.server.contexts.dynamic=true
## Should the DefaultHandler serve the jetty favicon.ico from the root.
# jetty.server.default.serveFavIcon=true
## Should the DefaultHandler show a list of known contexts in a root 404 response.
# jetty.server.default.showContexts=true

View File

@ -51,8 +51,8 @@ public interface Context extends Attributes, Decorator, Executor
MimeTypes getMimeTypes();
@Override
/** execute runnable in container thread scoped to context */
@Override
void execute(Runnable runnable);
/** scope the calling thread to the context and run the runnable. */

View File

@ -297,9 +297,17 @@ public interface Handler extends LifeCycle, Destroyable, Invocable, Request.Proc
{
// check state
Server server = nested.getServer();
if (server != null && server.isStarted() && handler != null &&
server.getInvocationType() != Invocable.combine(server.getInvocationType(), handler.getInvocationType()))
throw new IllegalArgumentException("Cannot change invocation type of started server");
// If the collection is changed whilst started, then the risk is that if we switch from NON_BLOCKING to BLOCKING
// whilst the execution strategy may have already dispatched the very last available thread, thinking it would
// never block, only for it to lose the race and find a newly added BLOCKING handler.
if (server != null && server.isStarted() && handler != null)
{
InvocationType serverInvocationType = server.getInvocationType();
if (serverInvocationType != Invocable.combine(serverInvocationType, handler.getInvocationType()) &&
serverInvocationType != InvocationType.BLOCKING)
throw new IllegalArgumentException("Cannot change invocation type of started server");
}
// Check for loops.
if (handler == nested || (handler instanceof Handler.Container container &&
@ -401,13 +409,33 @@ public interface Handler extends LifeCycle, Destroyable, Invocable, Request.Proc
*/
abstract class AbstractContainer extends Abstract implements Container
{
private boolean _dynamic;
protected AbstractContainer()
{
this(true);
}
protected AbstractContainer(InvocationType invocationType)
/**
* @param dynamic If true, then handlers may be added to the container dynamically when started,
* thus the InvocationType is assumed to be BLOCKING,
* otherwise handlers cannot be modified once the container is started.
*/
protected AbstractContainer(boolean dynamic)
{
super(invocationType);
_dynamic = dynamic;
}
public boolean isDynamic()
{
return _dynamic;
}
public void setDynamic(boolean dynamic)
{
if (isStarted())
throw new IllegalStateException(getState());
_dynamic = dynamic;
}
@Override
@ -468,6 +496,9 @@ public interface Handler extends LifeCycle, Destroyable, Invocable, Request.Proc
@Override
public InvocationType getInvocationType()
{
// Dynamic is always BLOCKING, as a blocking handler can be added at any time.
if (_dynamic)
return InvocationType.BLOCKING;
InvocationType invocationType = InvocationType.NON_BLOCKING;
for (Handler child : getHandlers())
invocationType = Invocable.combine(invocationType, child.getInvocationType());
@ -512,8 +543,19 @@ public interface Handler extends LifeCycle, Destroyable, Invocable, Request.Proc
this(null);
}
public Wrapper(boolean dynamic)
{
this(dynamic, null);
}
public Wrapper(Handler handler)
{
this(false, handler);
}
public Wrapper(boolean dynamic, Handler handler)
{
super(dynamic);
_handler = handler == null ? null : Nested.updateHandler(this, handler);
}
@ -524,6 +566,8 @@ public interface Handler extends LifeCycle, Destroyable, Invocable, Request.Proc
public void setHandler(Handler handler)
{
if (!isDynamic() && isStarted())
throw new IllegalStateException(getState());
_handler = Nested.updateHandler(this, handler);
}
@ -544,6 +588,8 @@ public interface Handler extends LifeCycle, Destroyable, Invocable, Request.Proc
@Override
public InvocationType getInvocationType()
{
if (isDynamic())
return InvocationType.BLOCKING;
Handler next = getHandler();
return next == null ? InvocationType.NON_BLOCKING : next.getInvocationType();
}
@ -557,15 +603,32 @@ public interface Handler extends LifeCycle, Destroyable, Invocable, Request.Proc
class Collection extends AbstractContainer
{
private volatile List<Handler> _handlers = new ArrayList<>();
private volatile InvocationType _invocationType = InvocationType.BLOCKING;
public Collection(Handler... handlers)
{
this(List.of(handlers));
this(handlers.length == 0, List.of(handlers));
}
public Collection(boolean dynamic)
{
this(dynamic, Collections.emptyList());
}
public Collection(List<Handler> handlers)
{
this(handlers == null || handlers.size() == 0, handlers);
}
/**
* @param dynamic If true, then handlers may be added dynamically once started,
* so the InvocationType is assumed to be BLOCKING, otherwise
* handlers cannot be modified once started.
*
* @param handlers The handlers to add.
*/
public Collection(boolean dynamic, List<Handler> handlers)
{
super(dynamic);
setHandlers(handlers);
}
@ -593,11 +656,14 @@ public interface Handler extends LifeCycle, Destroyable, Invocable, Request.Proc
public void setHandlers(List<Handler> handlers)
{
if (!isDynamic() && isStarted())
throw new IllegalStateException(getState());
List<Handler> newHandlers = newHandlers(handlers);
Server server = getServer();
InvocationType invocationType = server == null ? null : server.getInvocationType();
_invocationType = InvocationType.BLOCKING; // switch to blocking invocation type whilst updating handlers;
InvocationType serverInvocationType = server == null ? null : server.getInvocationType();
InvocationType invocationType = InvocationType.NON_BLOCKING;
// Check for loops && InvocationType changes.
for (Handler handler : newHandlers)
@ -609,24 +675,31 @@ public interface Handler extends LifeCycle, Destroyable, Invocable, Request.Proc
container.getDescendants().contains(this)))
throw new IllegalStateException("setHandler loop");
invocationType = Invocable.combine(invocationType, handler.getInvocationType());
if (server != null && server.isStarted() &&
server.getInvocationType() != Invocable.combine(server.getInvocationType(), handler.getInvocationType()))
throw new IllegalArgumentException("Cannot change invocation type of started server");
if (server != null)
handler.setServer(server);
}
// If the collection can be changed dynamically, then the risk is that if we switch from NON_BLOCKING to BLOCKING
// whilst the execution strategy may have already dispatched the very last available thread, thinking it would
// never block, only for it to lose the race and find a newly added BLOCKING handler.
if (isDynamic() && server != null && server.isStarted() && serverInvocationType != invocationType && serverInvocationType != InvocationType.BLOCKING)
throw new IllegalArgumentException("Cannot change invocation type of started server");
updateBeans(_handlers, handlers);
_handlers = newHandlers;
_invocationType = invocationType;
}
@Override
public InvocationType getInvocationType()
{
return _invocationType == null ? super.getInvocationType() : _invocationType;
if (isDynamic())
return InvocationType.BLOCKING;
InvocationType invocationType = InvocationType.NON_BLOCKING;
for (Handler handler : _handlers)
invocationType = Invocable.combine(invocationType, handler.getInvocationType());
return invocationType;
}
protected List<Handler> newHandlers(List<Handler> handlers)

View File

@ -39,6 +39,7 @@ import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ErrorProcessor;
import org.eclipse.jetty.util.Attributes;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.DecoratedObjectFactory;
import org.eclipse.jetty.util.ExceptionUtil;
import org.eclipse.jetty.util.IO;
@ -56,6 +57,7 @@ import org.eclipse.jetty.util.resource.FileSystemPool;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceFactory;
import org.eclipse.jetty.util.thread.AutoLock;
import org.eclipse.jetty.util.thread.Invocable;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.util.thread.ShutdownThread;
import org.eclipse.jetty.util.thread.ThreadPool;
@ -77,6 +79,7 @@ public class Server extends Handler.Wrapper implements Attributes
private boolean _stopAtShutdown;
private boolean _dumpAfterStart;
private boolean _dumpBeforeStop;
private Handler _defaultHandler;
private Request.Processor _errorProcessor;
private RequestLog _requestLog;
private boolean _dryRun;
@ -130,6 +133,31 @@ public class Server extends Handler.Wrapper implements Attributes
addBean(FileSystemPool.INSTANCE, false);
}
public Handler getDefaultHandler()
{
return _defaultHandler;
}
/**
* @param defaultHandler The handler to use if no other handler is set or has processed the request. This handler should
* always accept the request, even if only to send a 404.
*/
public void setDefaultHandler(Handler defaultHandler)
{
if (!isDynamic() && isStarted())
throw new IllegalStateException(getState());
Handler old = _defaultHandler;
_defaultHandler = defaultHandler;
updateBean(old, defaultHandler);
}
@Override
public boolean process(Request request, Response response, Callback callback) throws Exception
{
// Handle either with normal handler or default handler
return super.process(request, response, callback) || _defaultHandler != null && _defaultHandler.process(request, response, callback);
}
public String getServerInfo()
{
return _serverInfo;
@ -195,11 +223,22 @@ public class Server extends Handler.Wrapper implements Attributes
@Override
public InvocationType getInvocationType()
{
Handler handler = getHandler();
if (handler == null)
return InvocationType.NON_BLOCKING;
if (isDynamic())
return InvocationType.BLOCKING;
// Return cached type to avoid a full handler tree walk.
return isRunning() ? _invocationType : handler.getInvocationType();
if (isStarted())
return _invocationType;
InvocationType type = InvocationType.NON_BLOCKING;
Handler handler = getHandler();
if (handler != null)
type = Invocable.combine(type, handler.getInvocationType());
handler = getDefaultHandler();
if (handler != null)
type = Invocable.combine(type, handler.getInvocationType());
return type;
}
public boolean isDryRun()
@ -475,8 +514,7 @@ public class Server extends Handler.Wrapper implements Attributes
// Cache the invocation type to avoid runtime walk of handler tree
// Handlers must check they don't change the InvocationType of a started server
Handler handler = getHandler();
_invocationType = handler == null ? InvocationType.NON_BLOCKING : handler.getInvocationType();
_invocationType = getInvocationType();
if (_dryRun)
{

View File

@ -0,0 +1,88 @@
//
// ========================================================================
// Copyright (c) 1995-2022 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
// ========================================================================
//
package org.eclipse.jetty.server.handler;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpStream;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.NanoTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>A <code>Handler</code> that allows recording the latency of the requests executed by the wrapped handler.</p>
* <p>The latency reported by {@link #onRequestComplete(long)} is the delay between the first notice of the request
* (obtained from {@link HttpStream#getNanoTime()}) until the stream completion event has been handled by
* {@link HttpStream#succeeded()} or {@link HttpStream#failed(Throwable)}.</p>
*/
public abstract class AbstractLatencyRecordingHandler extends Handler.Wrapper
{
private static final Logger LOG = LoggerFactory.getLogger(AbstractLatencyRecordingHandler.class);
public AbstractLatencyRecordingHandler()
{
}
private HttpStream recordingWrapper(HttpStream httpStream)
{
return new HttpStream.Wrapper(httpStream)
{
@Override
public void succeeded()
{
// Take the httpStream nano timestamp before calling super.
long begin = httpStream.getNanoTime();
super.succeeded();
fireOnRequestComplete(begin);
}
@Override
public void failed(Throwable x)
{
// Take the httpStream nano timestamp before calling super.
long begin = httpStream.getNanoTime();
super.failed(x);
fireOnRequestComplete(begin);
}
private void fireOnRequestComplete(long begin)
{
try
{
onRequestComplete(NanoTime.since(begin));
}
catch (Throwable t)
{
if (LOG.isDebugEnabled())
LOG.debug("Error thrown by onRequestComplete", t);
}
}
};
}
@Override
public boolean process(Request request, Response response, Callback callback) throws Exception
{
request.addHttpStreamWrapper(this::recordingWrapper);
return super.process(request, response, callback);
}
/**
* Called back for each completed request with its execution's latency.
* @param durationInNs the duration in nanoseconds of the completed request
*/
protected abstract void onRequestComplete(long durationInNs);
}

View File

@ -232,11 +232,11 @@ public class ConnectHandler extends Handler.Wrapper
sendConnectResponse(request, response, callback, HttpStatus.PROXY_AUTHENTICATION_REQUIRED_407);
return;
}
HostPort hostPort = new HostPort(serverAddress);
String host = hostPort.getHost();
int port = hostPort.getPort(80);
if (!validateDestination(host, port))
{
if (LOG.isDebugEnabled())
@ -247,7 +247,7 @@ public class ConnectHandler extends Handler.Wrapper
if (LOG.isDebugEnabled())
LOG.debug("Connecting to {}:{}", host, port);
connectToServer(request, host, port, new Promise<>()
{
@Override
@ -259,7 +259,7 @@ public class ConnectHandler extends Handler.Wrapper
else
selector.connect(channel, connectContext);
}
@Override
public void failed(Throwable x)
{

View File

@ -372,7 +372,7 @@ public class ContextHandler extends Handler.Wrapper implements Attributes, Grace
* virtual host name. A context with no virtual host names or a null virtual host name is available to all requests that are not served by a context with a
* matching virtual host name.
*
* @return Array of virtual hosts that this context responds to. A null/empty array means any hostname is acceptable. Host names may be String
* @return list of virtual hosts that this context responds to. A null/empty array means any hostname is acceptable. Host names may be String
* representation of IP addresses. Host names may start with '*.' to wildcard one level of names. Hosts and wildcard hosts may be followed with
* '@connectorname', in which case they will match only if the the {@link Connector#getName()} for the request also matches. If an entry is just
* '@connectorname' it will match any host if that connector was used. Note - In previous versions if one or more connectorname only entries existed

View File

@ -50,6 +50,19 @@ public class ContextHandlerCollection extends Handler.Collection
public ContextHandlerCollection(ContextHandler... contexts)
{
this(true, contexts);
}
/**
* @param dynamic If true, then contexts may be added dynamically once started,
* so the InvocationType is assumed to be BLOCKING, otherwise
* the InvocationType is fixed once started and handlers cannot be
* subsequently added.
* @param contexts The contexts to add.
*/
public ContextHandlerCollection(boolean dynamic, ContextHandler... contexts)
{
super(dynamic);
if (contexts.length > 0)
setHandlers(contexts);
}

View File

@ -38,6 +38,7 @@ import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.Name;
import org.eclipse.jetty.util.resource.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -59,11 +60,19 @@ public class DefaultHandler extends Handler.Abstract
private final long _faviconModifiedMs = (System.currentTimeMillis() / 1000) * 1000L;
private final HttpField _faviconModified = new PreEncodedHttpField(HttpHeader.LAST_MODIFIED, DateGenerator.formatDate(_faviconModifiedMs));
private ByteBuffer _favicon;
private boolean _serveIcon = true;
private boolean _serveFavIcon = true;
private boolean _showContexts = true;
public DefaultHandler()
{
this(true, true);
}
public DefaultHandler(@Name("serveFavIcon") boolean serveFavIcon, @Name("showContexts")boolean showContexts)
{
super(InvocationType.NON_BLOCKING);
_serveFavIcon = serveFavIcon;
_showContexts = showContexts;
}
@Override
@ -109,7 +118,7 @@ public class DefaultHandler extends Handler.Abstract
String method = request.getMethod();
// little cheat for common request
if (isServeIcon() && _favicon != null && HttpMethod.GET.is(method) && Request.getPathInContext(request).equals("/favicon.ico"))
if (isServeFavIcon() && _favicon != null && HttpMethod.GET.is(method) && Request.getPathInContext(request).equals("/favicon.ico"))
{
ByteBuffer content = BufferUtil.EMPTY_BUFFER;
if (_faviconModifiedMs > 0 && request.getHeaders().getDateField(HttpHeader.IF_MODIFIED_SINCE) == _faviconModifiedMs)
@ -218,18 +227,18 @@ public class DefaultHandler extends Handler.Abstract
/**
* @return Returns true if the handle can server the jetty favicon.ico
*/
@ManagedAttribute("True if the favicon.ico should be served")
public boolean isServeIcon()
@ManagedAttribute("True if the favicon.ico is served")
public boolean isServeFavIcon()
{
return _serveIcon;
return _serveFavIcon;
}
/**
* @param serveIcon true if the handle can server the jetty favicon.ico
* @param serveFavIcon true if the handle can server the jetty favicon.ico
*/
public void setServeIcon(boolean serveIcon)
public void setServeFavIcon(boolean serveFavIcon)
{
_serveIcon = serveIcon;
_serveFavIcon = serveFavIcon;
}
@ManagedAttribute("True if the contexts should be shown in the default 404 page")

View File

@ -0,0 +1,148 @@
//
// ========================================================================
// Copyright (c) 1995-2022 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
// ========================================================================
//
package org.eclipse.jetty.server.handler;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.LongAdder;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.component.Graceful;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Handler to track active requests and allow them to gracefully complete.
*/
public class GracefulShutdownHandler extends Handler.Wrapper implements Graceful
{
private static final Logger LOG = LoggerFactory.getLogger(GracefulShutdownHandler.class);
private final LongAdder dispatchedStats = new LongAdder();
private final Shutdown shutdown;
public GracefulShutdownHandler()
{
shutdown = new Shutdown(this)
{
@Override
public boolean isShutdownDone()
{
long count = dispatchedStats.sum();
if (LOG.isDebugEnabled())
LOG.debug("isShutdownDone: count {}", count);
return count == 0;
}
};
}
/**
* Flag indicating that Graceful shutdown has been initiated.
*
* @return whether the graceful shutdown has been initiated
* @see Graceful
*/
@Override
public boolean isShutdown()
{
return shutdown.isShutdown();
}
@Override
public boolean process(Request request, Response response, Callback callback) throws Exception
{
Handler handler = getHandler();
if (handler == null || !isStarted())
{
// Nothing to do here, skip it
return false;
}
// Increment the counter before the test for isShutdown(), to avoid race conditions.
ShutdownTrackingCallback shutdownCallback = new ShutdownTrackingCallback(request, response, callback);
if (isShutdown())
{
if (LOG.isDebugEnabled())
LOG.debug("Service Unavailable: {}", request.getHttpURI());
Response.writeError(request, response, shutdownCallback, HttpStatus.SERVICE_UNAVAILABLE_503);
return true;
}
try
{
boolean handled = super.process(request, response, shutdownCallback);
if (!handled)
shutdownCallback.decrement();
return handled;
}
catch (Throwable t)
{
shutdownCallback.decrement();
throw t;
}
finally
{
if (isShutdown())
shutdown.check();
}
}
@Override
public CompletableFuture<Void> shutdown()
{
if (LOG.isDebugEnabled())
LOG.debug("Shutdown requested");
return shutdown.shutdown();
}
private class ShutdownTrackingCallback extends Callback.Nested
{
final Request request;
final Response response;
public ShutdownTrackingCallback(Request request, Response response, Callback callback)
{
super(callback);
this.request = request;
this.response = response;
dispatchedStats.increment();
}
public void decrement()
{
dispatchedStats.decrement();
}
@Override
public void failed(Throwable x)
{
decrement();
super.failed(x);
if (isShutdown())
shutdown.check();
}
@Override
public void succeeded()
{
decrement();
super.succeeded();
if (isShutdown())
shutdown.check();
}
}
}

View File

@ -37,6 +37,7 @@ public class HotSwapHandler extends Handler.AbstractContainer implements Handler
*/
public HotSwapHandler()
{
super(true);
}
/**

View File

@ -39,6 +39,16 @@ public class PathMappingsHandler extends Handler.AbstractContainer
private final PathMappings<Handler> mappings = new PathMappings<>();
public PathMappingsHandler()
{
this(true);
}
public PathMappingsHandler(boolean dynamic)
{
super(dynamic);
}
@Override
public void addHandler(Handler handler)
{

View File

@ -41,10 +41,9 @@ import org.slf4j.LoggerFactory;
*
* <pre>
* Server server = new Server(8080);
* HandlerList handlers = new HandlerList();
* handlers.setHandlers(new Handler[]
* { someOtherHandler, new ShutdownHandler(&quot;secret password&quot;, false, true) });
* server.setHandler(handlers);
* ShutdownHandler shutdown = new ShutdownHandler(&quot;secret password&quot;, false, true) });
* server.setHandler(shutdown);
* shutdown.setHandler(someOtherHandler);
* server.start();
* </pre>
*
@ -151,7 +150,7 @@ public class ShutdownHandler extends Handler.Wrapper
@Override
public boolean process(Request request, Response response, Callback callback) throws Exception
{
return false;
return super.process(request, response, callback);
/* TODO
if (!target.equals("/shutdown"))
{

View File

@ -1366,7 +1366,10 @@ public class HttpChannelState implements HttpChannel, Components
if (httpChannelState._callbackCompleted)
{
if (LOG.isDebugEnabled())
{
LOG.debug("already completed {} by", _request, _completedBy);
LOG.debug("Second complete", new Throwable("second complete"));
}
return false;
}

View File

@ -831,7 +831,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Writ
BufferUtil.clear(_content);
}
byte gatherWrite = 0;
int gatherWrite = 0;
long bytes = 0;
if (BufferUtil.hasContent(_header))
{

View File

@ -13,10 +13,8 @@
package org.eclipse.jetty.server;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.ByteBuffer;
@ -27,20 +25,17 @@ import java.util.concurrent.Exchanger;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLHandshakeException;
import org.eclipse.jetty.io.AbstractConnection;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.io.ByteBufferAccumulator;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.ssl.SslConnection;
import org.eclipse.jetty.logging.StacklessLogging;
import org.eclipse.jetty.server.handler.EchoHandler;
import org.eclipse.jetty.server.internal.HttpChannelState;
import org.eclipse.jetty.util.Blocker;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.NanoTime;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -48,12 +43,9 @@ import org.slf4j.LoggerFactory;
import static java.time.Duration.ofSeconds;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.lessThan;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively;
@ -101,7 +93,7 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
"GET / HTTP/1.0\r\n" +
"host: localhost:" + _serverURI.getPort() + "\r\n" +
"connection: keep-alive\r\n" +
"\r\n").getBytes("utf-8"));
"\r\n").getBytes(StandardCharsets.UTF_8));
os.flush();
IO.toString(is);
@ -132,13 +124,13 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
assertTimeoutPreemptively(ofSeconds(10), () ->
{
String content = "Wibble";
byte[] contentB = content.getBytes("utf-8");
byte[] contentB = content.getBytes(StandardCharsets.UTF_8);
os.write((
"POST /echo HTTP/1.1\r\n" +
"host: localhost:" + _serverURI.getPort() + "\r\n" +
"content-type: text/plain; charset=utf-8\r\n" +
"content-length: " + contentB.length + "\r\n" +
"\r\n").getBytes("utf-8"));
"\r\n").getBytes(StandardCharsets.UTF_8));
os.write(contentB);
os.flush();
@ -185,7 +177,7 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
"GET / HTTP/1.0\r\n" +
"host: localhost:" + _serverURI.getPort() + "\r\n" +
"connection: close\r\n" +
"\r\n").getBytes("utf-8"));
"\r\n").getBytes(StandardCharsets.UTF_8));
os.flush();
// Get the server side endpoint
@ -240,14 +232,14 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
InputStream is = client.getInputStream();
String content = "Wibble";
byte[] contentB = content.getBytes("utf-8");
byte[] contentB = content.getBytes(StandardCharsets.UTF_8);
os.write((
"POST /echo HTTP/1.1\r\n" +
"host: localhost:" + _serverURI.getPort() + "\r\n" +
"content-type: text/plain; charset=utf-8\r\n" +
"content-length: " + contentB.length + "\r\n" +
"connection: close\r\n" +
"\r\n").getBytes("utf-8"));
"\r\n").getBytes(StandardCharsets.UTF_8));
os.write(contentB);
os.flush();
@ -273,212 +265,6 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
assertFalse(((Channel)transport).isOpen());
}
@Test
@Tag("Unstable")
@Disabled // TODO make more stable
public void testNoBlockingTimeoutRead() throws Exception
{
startServer(new EchoHandler());
Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort());
client.setSoTimeout(10000);
InputStream is = client.getInputStream();
assertFalse(client.isClosed());
long start = NanoTime.now();
OutputStream os = client.getOutputStream();
os.write(("GET / HTTP/1.1\r\n" +
"host: localhost:" + _serverURI.getPort() + "\r\n" +
"Transfer-Encoding: chunked\r\n" +
"Content-Type: text/plain\r\n" +
"Connection: close\r\n" +
"\r\n" +
"5\r\n" +
"LMNOP\r\n")
.getBytes("utf-8"));
os.flush();
try
{
Thread.sleep(250);
os.write("1".getBytes("utf-8"));
os.flush();
Thread.sleep(250);
os.write("0".getBytes("utf-8"));
os.flush();
Thread.sleep(250);
os.write("\r".getBytes("utf-8"));
os.flush();
Thread.sleep(250);
os.write("\n".getBytes("utf-8"));
os.flush();
Thread.sleep(250);
os.write("0123456789ABCDEF\r\n".getBytes("utf-8"));
os.write("0\r\n".getBytes("utf-8"));
os.write("\r\n".getBytes("utf-8"));
os.flush();
}
catch (Exception e)
{
e.printStackTrace();
}
long duration = NanoTime.millisSince(start);
assertThat(duration, greaterThan(500L));
assertTimeoutPreemptively(ofSeconds(10), () ->
{
// read the response
String response = IO.toString(is);
assertThat(response, startsWith("HTTP/1.1 200 OK"));
assertThat(response, containsString("LMNOP0123456789ABCDEF"));
});
}
@Test
@Tag("Unstable")
@Disabled // TODO make more stable
public void testBlockingTimeoutRead() throws Exception
{
startServer(new EchoHandler());
Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort());
client.setSoTimeout(10000);
InputStream is = client.getInputStream();
assertFalse(client.isClosed());
OutputStream os = client.getOutputStream();
long start = NanoTime.now();
os.write(("GET / HTTP/1.1\r\n" +
"host: localhost:" + _serverURI.getPort() + "\r\n" +
"Transfer-Encoding: chunked\r\n" +
"Content-Type: text/plain\r\n" +
"Connection: close\r\n" +
"\r\n" +
"5\r\n" +
"LMNOP\r\n")
.getBytes("utf-8"));
os.flush();
try (StacklessLogging stackless = new StacklessLogging(HttpChannelState.class))
{
Thread.sleep(300);
os.write("1".getBytes("utf-8"));
os.flush();
Thread.sleep(300);
os.write("0".getBytes("utf-8"));
os.flush();
Thread.sleep(300);
os.write("\r".getBytes("utf-8"));
os.flush();
Thread.sleep(300);
os.write("\n".getBytes("utf-8"));
os.flush();
Thread.sleep(300);
os.write("0123456789ABCDEF\r\n".getBytes("utf-8"));
os.write("0\r\n".getBytes("utf-8"));
os.write("\r\n".getBytes("utf-8"));
os.flush();
}
long duration = NanoTime.millisSince(start);
assertThat(duration, greaterThan(500L));
// read the response
String response = IO.toString(is);
assertThat(response, startsWith("HTTP/1.1 500 "));
assertThat(response, containsString("InterruptedIOException"));
}
@Test
@Tag("Unstable")
@Disabled // TODO make more stable
public void testNoBlockingTimeoutWrite() throws Exception
{
startServer(new HugeResponseHandler());
Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort());
client.setSoTimeout(10000);
assertFalse(client.isClosed());
OutputStream os = client.getOutputStream();
BufferedReader is = new BufferedReader(new InputStreamReader(client.getInputStream(), StandardCharsets.ISO_8859_1), 2048);
os.write((
"GET / HTTP/1.0\r\n" +
"host: localhost:" + _serverURI.getPort() + "\r\n" +
"connection: keep-alive\r\n" +
"Connection: close\r\n" +
"\r\n").getBytes("utf-8"));
os.flush();
// read the header
String line = is.readLine();
assertThat(line, startsWith("HTTP/1.1 200 OK"));
while (line.length() != 0)
{
line = is.readLine();
}
for (int i = 0; i < (128 * 1024); i++)
{
if (i % 1028 == 0)
{
Thread.sleep(20);
}
line = is.readLine();
assertThat(line, notNullValue());
assertEquals(1022, line.length());
}
}
@Test
@Tag("Unstable")
@Disabled // TODO make more stable
public void testBlockingTimeoutWrite() throws Exception
{
startServer(new HugeResponseHandler());
Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort());
client.setSoTimeout(10000);
assertFalse(client.isClosed());
OutputStream os = client.getOutputStream();
BufferedReader is = new BufferedReader(new InputStreamReader(client.getInputStream(), StandardCharsets.ISO_8859_1), 2048);
os.write((
"GET / HTTP/1.0\r\n" +
"host: localhost:" + _serverURI.getPort() + "\r\n" +
"connection: keep-alive\r\n" +
"Connection: close\r\n" +
"\r\n").getBytes("utf-8"));
os.flush();
// read the header
String line = is.readLine();
assertThat(line, startsWith("HTTP/1.1 200 OK"));
while (line.length() != 0)
{
line = is.readLine();
}
long start = NanoTime.now();
try (StacklessLogging stackless = new StacklessLogging(HttpChannelState.class, AbstractConnection.class))
{
for (int i = 0; i < (128 * 1024); i++)
{
if (i % 1028 == 0)
{
Thread.sleep(20);
}
line = is.readLine();
if (line == null)
break;
}
}
assertThat(NanoTime.millisSince(start), lessThan(20L * 128L));
}
@Test
public void testMaxIdleNoRequest() throws Exception
{
@ -490,7 +276,7 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
OutputStream os = client.getOutputStream();
long start = NanoTime.now();
os.write("GET ".getBytes("utf-8"));
os.write("GET ".getBytes(StandardCharsets.UTF_8));
os.flush();
Thread.sleep(sleepTime);
@ -559,7 +345,7 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
"Content-Length: 20\r\n" +
"Content-Type: text/plain\r\n" +
"Connection: close\r\n" +
"\r\n").getBytes("utf-8"));
"\r\n").getBytes(StandardCharsets.UTF_8));
os.flush();
assertTimeoutPreemptively(ofSeconds(10), () ->
@ -583,41 +369,51 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
@Test
public void testMaxIdleDispatch() throws Exception
{
startServer(new EchoHandler());
Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort());
client.setSoTimeout(10000);
InputStream is = client.getInputStream();
assertFalse(client.isClosed());
startServer(new EchoWholeHandler());
OutputStream os = client.getOutputStream();
long start = NanoTime.now();
os.write((
"GET / HTTP/1.1\r\n" +
"host: localhost:" + _serverURI.getPort() + "\r\n" +
"connection: keep-alive\r\n" +
"Content-Length: 20\r\n" +
"Content-Type: text/plain\r\n" +
"Connection: close\r\n" +
"\r\n" +
"1234567890").getBytes("utf-8"));
os.flush();
assertTimeoutPreemptively(ofSeconds(10), () ->
try (Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()))
{
try
client.setSoTimeout(10000);
try (InputStream is = client.getInputStream();
OutputStream os = client.getOutputStream())
{
String response = IO.toString(is);
assertThat(response, containsString("500"));
assertEquals(-1, is.read());
assertFalse(client.isClosed());
long start = NanoTime.now();
byte[] requestBody = "1234567890".getBytes(StandardCharsets.UTF_8);
// We want a situation where the request says it has a body,
// but the request hasn't sent all of it.
int requestBodyLength = requestBody.length * 2;
String rawRequest = ("""
GET / HTTP/1.1\r
host: localhost:%d\r
connection: keep-alive\r
Content-Length: %d\r
Content-Type: text/plain\r
Connection: close\r
\r
""").formatted(_serverURI.getPort(), requestBodyLength);
os.write(rawRequest.getBytes(StandardCharsets.UTF_8));
os.write(requestBody);
os.flush();
assertTimeoutPreemptively(ofSeconds(10), () ->
{
// We expect a 500 response to occur due to the idle timeout triggering.
// See: ServerConnector.setIdleTimeout(long ms);
String response = IO.toString(is);
assertThat(response, containsString("500"));
assertEquals(-1, is.read());
});
long duration = NanoTime.millisSince(start);
assertThat(duration + 100, greaterThanOrEqualTo(MAX_IDLE_TIME));
assertThat(duration - 100, lessThan(maximumTestRuntime));
}
catch (Exception e)
{
e.printStackTrace();
}
});
long duration = NanoTime.millisSince(start);
assertThat(duration + 100, greaterThanOrEqualTo(MAX_IDLE_TIME));
assertThat(duration - 100, lessThan(maximumTestRuntime));
}
}
@Test
@ -633,7 +429,7 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
InputStream is = client.getInputStream();
String content = "Wibble\r\n";
byte[] contentB = content.getBytes("utf-8");
byte[] contentB = content.getBytes(StandardCharsets.UTF_8);
os.write((
"GET / HTTP/1.0\r\n" +
"host: localhost:" + _serverURI.getPort() + "\r\n" +
@ -641,7 +437,7 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
"Content-Length: " + (contentB.length * 20) + "\r\n" +
"Content-Type: text/plain\r\n" +
"Connection: close\r\n" +
"\r\n").getBytes("utf-8"));
"\r\n").getBytes(StandardCharsets.UTF_8));
os.flush();
assertTimeoutPreemptively(ofSeconds(10), () ->
@ -680,7 +476,7 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
"host: localhost:" + _serverURI.getPort() + "\r\n" +
"connection: keep-alive\r\n" +
"Connection: close\r\n" +
"\r\n").getBytes("utf-8"));
"\r\n").getBytes(StandardCharsets.UTF_8));
os.flush();
assertTimeoutPreemptively(ofSeconds(10), () ->
@ -712,7 +508,7 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
"host: localhost:" + _serverURI.getPort() + "\r\n" +
"connection: keep-alive\r\n" +
"Connection: close\r\n" +
"\r\n").getBytes("utf-8"));
"\r\n").getBytes(StandardCharsets.UTF_8));
os.flush();
assertTimeoutPreemptively(ofSeconds(10), () ->
@ -750,17 +546,26 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
protected static class HugeResponseHandler extends Handler.Abstract
{
private final int iterations;
public HugeResponseHandler(int iterations)
{
this.iterations = iterations;
}
@Override
public boolean process(Request request, Response response, Callback callback) throws Exception
{
response.setStatus(200);
byte[] buffer = new byte[128 * 1024 * 1024];
// Create a big single buffer
byte[] buffer = new byte[iterations * 1024 * 1024];
Arrays.fill(buffer, (byte)'x');
for (int i = 0; i < 128 * 1024; i++)
// Toss in an LF after every iteration
for (int i = 0; i < iterations * 1024; i++)
{
buffer[i * 1024 + 1022] = '\r';
buffer[i * 1024 + 1023] = '\n';
}
// Write it as a single buffer
response.write(true, ByteBuffer.wrap(buffer), callback);
return true;
}
@ -784,4 +589,73 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
return true;
}
}
/**
* A handler that will echo the request body to the response body, but only
* once the entire body content has been received.
*/
public static class EchoWholeHandler extends Handler.Abstract
{
@Override
public boolean process(Request request, Response response, Callback callback) throws Exception
{
long expectedContentLength = request.getHeaders().getLongField(HttpHeader.CONTENT_LENGTH);
if (expectedContentLength <= 0)
{
callback.succeeded();
return true;
}
request.demand(new WholeProcess(request, response, callback));
return true;
}
/**
* Accumulate the Request body until it's entirely received,
* then write the body back to the response body.
*/
private static class WholeProcess implements Runnable
{
Request request;
Response response;
Callback callback;
ByteBufferAccumulator bufferAccumulator;
public WholeProcess(Request request, Response response, Callback callback)
{
this.request = request;
this.response = response;
this.callback = callback;
this.bufferAccumulator = new ByteBufferAccumulator();
}
@Override
public void run()
{
while (true)
{
Content.Chunk chunk = request.read();
if (chunk == null)
{
request.demand(this);
return;
}
if (chunk instanceof Content.Chunk.Error error)
{
callback.failed(error.getCause());
return;
}
// copy buffer
bufferAccumulator.copyBuffer(chunk.getByteBuffer().slice());
chunk.release();
if (chunk.isLast())
{
// write accumulated buffers
response.write(true, bufferAccumulator.toByteBuffer(), callback);
return;
}
}
}
}
}
}

View File

@ -21,6 +21,7 @@ import java.net.Socket;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
@ -32,14 +33,12 @@ import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.logging.StacklessLogging;
import org.eclipse.jetty.server.handler.DumpHandler;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.toolchain.test.MavenPaths;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
@ -91,9 +90,9 @@ public class DetectorConnectionTest
private String getResponseOverSsl(String request) throws Exception
{
String keystore = MavenTestingUtils.getTestResourceFile("keystore.p12").getAbsolutePath();
Path keystoreFile = MavenPaths.findTestResourceFile("keystore.p12");
SslContextFactory sslContextFactory = new SslContextFactory.Server();
sslContextFactory.setKeyStorePath(keystore);
sslContextFactory.setKeyStorePath(keystoreFile.toString());
sslContextFactory.setKeyStorePassword("storepwd");
sslContextFactory.start();
@ -140,6 +139,7 @@ public class DetectorConnectionTest
socket.getOutputStream().write("OX".getBytes(StandardCharsets.US_ASCII));
socket.getOutputStream().close();
//noinspection ResultOfMethodCallIgnored
assertThrows(SocketException.class, () -> socket.getInputStream().read());
}
}
@ -160,6 +160,7 @@ public class DetectorConnectionTest
socket.getOutputStream().write(" ".getBytes(StandardCharsets.US_ASCII));
socket.getOutputStream().close();
//noinspection ResultOfMethodCallIgnored
assertThrows(SocketException.class, () -> socket.getInputStream().read());
}
}
@ -175,11 +176,12 @@ public class DetectorConnectionTest
try (Socket socket = new Socket(_server.getURI().getHost(), _server.getURI().getPort()))
{
socket.getOutputStream().write(TypeUtil.fromHexString("0D0A0D0A000D0A515549540A")); // proxy V2 Preamble
socket.getOutputStream().write(StringUtil.fromHexString("0D0A0D0A000D0A515549540A")); // proxy V2 Preamble
Thread.sleep(100); // make sure the onFillable callback gets called
socket.getOutputStream().write(TypeUtil.fromHexString("21")); // V2, PROXY
socket.getOutputStream().write(StringUtil.fromHexString("21")); // V2, PROXY
socket.getOutputStream().close();
//noinspection ResultOfMethodCallIgnored
assertThrows(SocketException.class, () -> socket.getInputStream().read());
}
}
@ -195,7 +197,7 @@ public class DetectorConnectionTest
try (Socket socket = new Socket(_server.getURI().getHost(), _server.getURI().getPort()))
{
socket.getOutputStream().write(TypeUtil.fromHexString(
socket.getOutputStream().write(StringUtil.fromHexString(
// proxy V2 Preamble
"0D0A0D0A000D0A515549540A" +
// V2, PROXY
@ -207,23 +209,23 @@ public class DetectorConnectionTest
"000C"
));
Thread.sleep(100); // make sure the onFillable callback gets called
socket.getOutputStream().write(TypeUtil.fromHexString(
socket.getOutputStream().write(StringUtil.fromHexString(
// uint32_t src_addr; uint32_t dst_addr; uint16_t src_port; uint16_t dst_port;
"C0A80001" // 8080
));
socket.getOutputStream().close();
//noinspection ResultOfMethodCallIgnored
assertThrows(SocketException.class, () -> socket.getInputStream().read());
}
}
@Test
@Disabled // TODO
public void testDetectingSslProxyToHttpNoSslWithProxy() throws Exception
{
String keystore = MavenTestingUtils.getTestResourceFile("keystore.p12").getAbsolutePath();
Path keystoreFile = MavenPaths.findTestResourceFile("keystore.p12");
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
sslContextFactory.setKeyStorePath(keystore);
sslContextFactory.setKeyStorePath(keystoreFile.toString());
sslContextFactory.setKeyStorePassword("storepwd");
HttpConnectionFactory http = new HttpConnectionFactory();
@ -233,18 +235,18 @@ public class DetectorConnectionTest
start(detector, http);
String request = "PROXY TCP 1.2.3.4 5.6.7.8 111 222\r\n" +
"GET /path HTTP/1.1\n" +
"Host: server:80\n" +
"Connection: close\n" +
"\n";
String request = """
PROXY TCP 1.2.3.4 5.6.7.8 111 222\r
GET /path HTTP/1.1\r
Host: server:80\r
Connection: close\r
\r
""";
String response = getResponse(request);
assertThat(response, Matchers.containsString("HTTP/1.1 200"));
assertThat(response, Matchers.containsString("pathInContext=/path"));
assertThat(response, Matchers.containsString("servername=server"));
assertThat(response, Matchers.containsString("serverport=80"));
assertThat(response, Matchers.containsString("localname=5.6.7.8"));
assertThat(response, Matchers.containsString("local=5.6.7.8:222"));
assertThat(response, Matchers.containsString("remote=1.2.3.4:111"));
}
@ -252,9 +254,9 @@ public class DetectorConnectionTest
@Test
public void testDetectingSslProxyToHttpWithSslNoProxy() throws Exception
{
String keystore = MavenTestingUtils.getTestResourceFile("keystore.p12").getAbsolutePath();
Path keystoreFile = MavenPaths.findTestResourceFile("keystore.p12");
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
sslContextFactory.setKeyStorePath(keystore);
sslContextFactory.setKeyStorePath(keystoreFile.toString());
sslContextFactory.setKeyStorePassword("storepwd");
HttpConnectionFactory http = new HttpConnectionFactory();
@ -264,10 +266,12 @@ public class DetectorConnectionTest
start(detector, http);
String request = "GET /path HTTP/1.1\n" +
"Host: server:80\n" +
"Connection: close\n" +
"\n";
String request = """
GET /path HTTP/1.1\r
Host: server:80\r
Connection: close\r
\r
""";
String response = getResponseOverSsl(request);
assertThat(response, Matchers.containsString("HTTP/1.1 200"));
@ -276,9 +280,9 @@ public class DetectorConnectionTest
@Test
public void testDetectingSslProxyToHttpWithSslWithProxy() throws Exception
{
String keystore = MavenTestingUtils.getTestResourceFile("keystore.p12").getAbsolutePath();
Path keystoreFile = MavenPaths.findTestResourceFile("keystore.p12");
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
sslContextFactory.setKeyStorePath(keystore);
sslContextFactory.setKeyStorePath(keystoreFile.toString());
sslContextFactory.setKeyStorePassword("storepwd");
HttpConnectionFactory http = new HttpConnectionFactory();
@ -288,11 +292,13 @@ public class DetectorConnectionTest
start(detector, http);
String request = "PROXY TCP 1.2.3.4 5.6.7.8 111 222\r\n" +
"GET /path HTTP/1.1\n" +
"Host: server:80\n" +
"Connection: close\n" +
"\n";
String request = """
PROXY TCP 1.2.3.4 5.6.7.8 111 222\r
GET /path HTTP/1.1\r
Host: server:80\r
Connection: close\r
\r
""";
String response = getResponseOverSsl(request);
// SSL matched, so the upgrade was made to HTTP which does not understand the proxy request
@ -302,9 +308,9 @@ public class DetectorConnectionTest
@Test
public void testDetectionUnsuccessfulUpgradesToNextProtocol() throws Exception
{
String keystore = MavenTestingUtils.getTestResourceFile("keystore.p12").getAbsolutePath();
Path keystoreFile = MavenPaths.findTestResourceFile("keystore.p12");
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
sslContextFactory.setKeyStorePath(keystore);
sslContextFactory.setKeyStorePath(keystoreFile.toString());
sslContextFactory.setKeyStorePassword("storepwd");
HttpConnectionFactory http = new HttpConnectionFactory();
@ -314,10 +320,12 @@ public class DetectorConnectionTest
start(detector, http);
String request = "GET /path HTTP/1.1\n" +
"Host: server:80\n" +
"Connection: close\n" +
"\n";
String request = """
GET /path HTTP/1.1\r
Host: server:80\r
Connection: close\r
\r
""";
String response = getResponse(request);
assertThat(response, Matchers.containsString("HTTP/1.1 200"));
@ -326,9 +334,9 @@ public class DetectorConnectionTest
@Test
public void testDetectorToNextDetector() throws Exception
{
String keystore = MavenTestingUtils.getTestResourceFile("keystore.p12").getAbsolutePath();
Path keystoreFile = MavenPaths.findTestResourceFile("keystore.p12");
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
sslContextFactory.setKeyStorePath(keystore);
sslContextFactory.setKeyStorePath(keystoreFile.toString());
sslContextFactory.setKeyStorePassword("storepwd");
HttpConnectionFactory http = new HttpConnectionFactory();
@ -339,11 +347,13 @@ public class DetectorConnectionTest
start(sslDetector, proxyDetector, http);
String request = "PROXY TCP 1.2.3.4 5.6.7.8 111 222\r\n" +
"GET /path HTTP/1.1\n" +
"Host: server:80\n" +
"Connection: close\n" +
"\n";
String request = """
PROXY TCP 1.2.3.4 5.6.7.8 111 222\r
GET /path HTTP/1.1\r
Host: server:80\r
Connection: close\r
\r
""";
String response = getResponseOverSsl(request);
// SSL matched, so the upgrade was made to proxy which itself upgraded to HTTP
@ -377,10 +387,12 @@ public class DetectorConnectionTest
start(detector, http);
String request = "GET /path HTTP/1.1\n" +
"Host: server:80\n" +
"Connection: close\n" +
"\n";
String request = """
GET /path HTTP/1.1\r
Host: server:80\r
Connection: close\r
\r
""";
String response = getResponse(request);
assertEquals("No upgrade for you", response);
@ -390,9 +402,9 @@ public class DetectorConnectionTest
@Test
public void testDetectorWithProxyThatHasNoNextProto() throws Exception
{
String keystore = MavenTestingUtils.getTestResourceFile("keystore.p12").getAbsolutePath();
Path keystoreFile = MavenPaths.findTestResourceFile("keystore.p12");
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
sslContextFactory.setKeyStorePath(keystore);
sslContextFactory.setKeyStorePath(keystoreFile.toString());
sslContextFactory.setKeyStorePassword("storepwd");
HttpConnectionFactory http = new HttpConnectionFactory();
@ -402,11 +414,13 @@ public class DetectorConnectionTest
start(detector, http);
String request = "PROXY TCP 1.2.3.4 5.6.7.8 111 222\r\n" +
"GET /path HTTP/1.1\n" +
"Host: server:80\n" +
"Connection: close\n" +
"\n";
String request = """
PROXY TCP 1.2.3.4 5.6.7.8 111 222\r
GET /path HTTP/1.1\r
Host: server:80\r
Connection: close\r
\r
""";
String response = getResponse(request);
// ProxyConnectionFactory has no next protocol -> it cannot upgrade
@ -416,9 +430,9 @@ public class DetectorConnectionTest
@Test
public void testOptionalSsl() throws Exception
{
String keystore = MavenTestingUtils.getTestResourceFile("keystore.p12").getAbsolutePath();
Path keystoreFile = MavenPaths.findTestResourceFile("keystore.p12");
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
sslContextFactory.setKeyStorePath(keystore);
sslContextFactory.setKeyStorePath(keystoreFile.toString());
sslContextFactory.setKeyStorePassword("storepwd");
HttpConnectionFactory http = new HttpConnectionFactory();
@ -427,11 +441,12 @@ public class DetectorConnectionTest
start(detector, http);
String request =
"GET /path HTTP/1.1\n" +
"Host: server:80\n" +
"Connection: close\n" +
"\n";
String request = """
GET /path HTTP/1.1\r
Host: server:80\r
Connection: close\r
\r
""";
String clearTextResponse = getResponse(request);
String sslResponse = getResponseOverSsl(request);
@ -443,9 +458,9 @@ public class DetectorConnectionTest
@Test
public void testDetectorThatHasNoConfiguredNextProto() throws Exception
{
String keystore = MavenTestingUtils.getTestResourceFile("keystore.p12").getAbsolutePath();
Path keystoreFile = MavenPaths.findTestResourceFile("keystore.p12");
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
sslContextFactory.setKeyStorePath(keystore);
sslContextFactory.setKeyStorePath(keystoreFile.toString());
sslContextFactory.setKeyStorePassword("storepwd");
SslConnectionFactory ssl = new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString());
@ -453,11 +468,12 @@ public class DetectorConnectionTest
start(detector);
String request =
"GET /path HTTP/1.1\n" +
"Host: server:80\n" +
"Connection: close\n" +
"\n";
String request = """
GET /path HTTP/1.1\r
Host: server:80\r
Connection: close\r
\r
""";
String response = getResponse(request);
assertThat(response, Matchers.nullValue());
@ -489,11 +505,11 @@ public class DetectorConnectionTest
"1F90"; // 8080
String httpReq = """
GET /path HTTP/1.1
Host: server:80
Connection: close
""";
GET /path HTTP/1.1\r
Host: server:80\r
Connection: close\r
\r
""";
try (StacklessLogging ignore = new StacklessLogging(DetectorConnectionFactory.class))
{
String response = getResponse(StringUtil.fromHexString(proxyReq), httpReq.getBytes(StandardCharsets.US_ASCII));
@ -558,12 +574,13 @@ public class DetectorConnectionTest
"3039" + // 12345
"1F90"; // 8080
String httpReq =
"GET /path HTTP/1.1\n" +
"Host: server:80\n" +
"Connection: close\n" +
"\n";
String response = getResponse(TypeUtil.fromHexString(proxyReq), httpReq.getBytes(StandardCharsets.US_ASCII));
String httpReq = """
GET /path HTTP/1.1\r
Host: server:80\r
Connection: close\r
\r
""";
String response = getResponse(StringUtil.fromHexString(proxyReq), httpReq.getBytes(StandardCharsets.US_ASCII));
assertThat(response, Matchers.nullValue());
}
@ -604,11 +621,12 @@ public class DetectorConnectionTest
start(detector, noUpgradeTo);
String request =
"GET /path HTTP/1.1\n" +
"Host: server:80\n" +
"Connection: close\n" +
"\n";
String request = """
GET /path HTTP/1.1\r
Host: server:80\r
Connection: close\r
\r
""";
String response = getResponse(request);
assertThat(response, Matchers.nullValue());
@ -617,9 +635,9 @@ public class DetectorConnectionTest
@Test
public void testGeneratedProtocolNames()
{
String keystore = MavenTestingUtils.getTestResourceFile("keystore.p12").getAbsolutePath();
Path keystoreFile = MavenPaths.findTestResourceFile("keystore.p12");
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
sslContextFactory.setKeyStorePath(keystore);
sslContextFactory.setKeyStorePath(keystoreFile.toString());
sslContextFactory.setKeyStorePassword("storepwd");
ProxyConnectionFactory proxy = new ProxyConnectionFactory(HttpVersion.HTTP_1_1.asString());

View File

@ -0,0 +1,744 @@
//
// ========================================================================
// Copyright (c) 1995-2022 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
// ========================================================================
//
package org.eclipse.jetty.server;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpTester;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.logging.StacklessLogging;
import org.eclipse.jetty.server.handler.GracefulShutdownHandler;
import org.eclipse.jetty.util.Blocker;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.component.LifeCycle;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.awaitility.Awaitility.await;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.lessThan;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class GracefulShutdownTest
{
private static final Logger LOG = LoggerFactory.getLogger(GracefulShutdownTest.class);
private Server server;
private ServerConnector connector;
public Server createServer(Handler handler) throws Exception
{
server = new Server();
connector = new ServerConnector(server, 1, 1);
connector.setIdleTimeout(10000);
connector.setShutdownIdleTimeout(1000);
connector.setPort(0);
server.addConnector(connector);
server.setHandler(handler);
return server;
}
@AfterEach
public void teardown()
{
// cleanup any unstopped servers (due to test failure)
LifeCycle.stop(server);
}
private Socket newSocketToServer(String id) throws IOException
{
URI serverURI = server.getURI();
Socket socket = new Socket(serverURI.getHost(), serverURI.getPort());
LOG.debug("{} : l={},r={}", id, socket.getLocalSocketAddress(), socket.getRemoteSocketAddress());
return socket;
}
private CompletableFuture<Long> runAsyncServerStop()
{
CompletableFuture<Long> stopFuture = new CompletableFuture<>();
CompletableFuture.runAsync(() ->
{
// Graceful shutdown + stop
try (StacklessLogging ignore = new StacklessLogging(Response.class))
{
// Trigger graceful stop
long beginStop = NanoTime.now();
try
{
server.stop();
long duration = NanoTime.millisSince(beginStop);
stopFuture.complete(duration);
}
catch (Throwable t)
{
server.dumpStdErr();
stopFuture.completeExceptionally(t);
}
}
});
return stopFuture;
}
/**
* Test for when a Handler throws an unhandled Exception from {@link Handler#process(Request, Response, Callback)}
* when in normal mode (not during graceful mode). This test exists to ensure that the Callback management of
* the {@link GracefulShutdownHandler} doesn't mess with normal operations of requests.
*/
@Test
public void testHandlerNormalUnhandledException() throws Exception
{
GracefulShutdownHandler gracefulShutdownHandler = new GracefulShutdownHandler();
gracefulShutdownHandler.setHandler(new Handler.Abstract()
{
@Override
public boolean process(Request request, Response response, Callback callback) throws Exception
{
throw new RuntimeException("Intentional Exception");
}
});
server = createServer(gracefulShutdownHandler);
server.setStopTimeout(10000);
server.start();
String rawRequest = """
POST /?hint=intentional_failure HTTP/1.1\r
Host: localhost\r
Content-Type: text/plain\r
Content-Length: 10\r
\r
1234567890""";
Socket client0 = newSocketToServer("client0");
OutputStream output0 = client0.getOutputStream();
InputStream input0 = client0.getInputStream();
try (StacklessLogging ignore = new StacklessLogging(Response.class))
{
// Intentional Failure request
output0.write(rawRequest.getBytes(StandardCharsets.UTF_8));
output0.flush();
HttpTester.Response response = HttpTester.parseResponse(input0);
assertNotNull(response);
assertThat(response.getStatus(), is(HttpStatus.INTERNAL_SERVER_ERROR_500));
assertThat(response.get(HttpHeader.CONNECTION), is(nullValue()));
}
// Perform Stop
CompletableFuture<Long> stopFuture = runAsyncServerStop();
long stopDuration = stopFuture.get();
assertThat(stopDuration, lessThan(5000L));
// Ensure client connection is closed
long beginClose = NanoTime.now();
// The socket should have been closed
assertThat(client0 + " not closed", input0.read(), is(-1));
assertThat(client0 + " close took too long", NanoTime.millisSince(beginClose), lessThan(2000L));
}
/**
* Test for when a Handler throws an unhandled Exception {@link Handler#process(Request, Response, Callback)}
* when in graceful mode.
*/
@Test
public void testHandlerGracefulUnhandledException() throws Exception
{
CountDownLatch dispatchLatch = new CountDownLatch(1);
GracefulShutdownHandler gracefulShutdownHandler = new GracefulShutdownHandler();
gracefulShutdownHandler.setHandler(new Handler.Abstract()
{
@Override
public boolean process(Request request, Response response, Callback callback) throws Exception
{
LOG.info("process: request={}", request);
// let main thread know that we've reach this handler
dispatchLatch.countDown();
// now wait for graceful stop to begin
await().atMost(5, TimeUnit.SECONDS).until(() -> gracefulShutdownHandler.isShutdown());
throw new RuntimeException("Intentional Failure");
}
});
server = createServer(gracefulShutdownHandler);
server.setStopTimeout(10000);
server.start();
String rawRequest = """
POST /?hint=intentional_failure HTTP/1.1\r
Host: localhost\r
Content-Type: text/plain\r
Content-Length: 10\r
\r
1234567890""";
Socket client0 = newSocketToServer("client0");
OutputStream output0 = client0.getOutputStream();
InputStream input0 = client0.getInputStream();
// Write request
output0.write(rawRequest.getBytes(StandardCharsets.UTF_8));
output0.flush();
// Wait for request to reach handler
assertTrue(dispatchLatch.await(3, TimeUnit.SECONDS), "Request didn't reach handler");
// Perform stop
CompletableFuture<Long> stopFuture = runAsyncServerStop();
// Verify response
HttpTester.Response response = HttpTester.parseResponse(input0);
assertNotNull(response);
assertThat(response.getStatus(), is(HttpStatus.INTERNAL_SERVER_ERROR_500));
assertThat(response.get(HttpHeader.CONNECTION), is("close"));
// Verify Stop duration
long stopDuration = stopFuture.get();
assertThat(stopDuration, lessThan(5000L));
// Ensure client connection is closed
long beginClose = NanoTime.now();
// The socket should have been closed
assertThat(client0 + " not closed", input0.read(), is(-1));
assertThat(client0 + " close took too long", NanoTime.millisSince(beginClose), lessThan(2000L));
}
/**
* Test for when a Handler uses {@link Callback#failed(Throwable)} when in normal mode (not during graceful mode).
* This test exists to ensure that the Callback management of the {@link GracefulShutdownHandler} doesn't
* mess with normal operations of requests.
*/
@Test
public void testHandlerNormalCallbackFailure() throws Exception
{
GracefulShutdownHandler gracefulShutdownHandler = new GracefulShutdownHandler();
gracefulShutdownHandler.setHandler(new Handler.Abstract()
{
@Override
public boolean process(Request request, Response response, Callback callback) throws Exception
{
callback.failed(new RuntimeException("Intentional Failure"));
return true;
}
});
server = createServer(gracefulShutdownHandler);
server.setStopTimeout(10000);
server.start();
String rawRequest = """
POST /?hint=intentional_failure HTTP/1.1\r
Host: localhost\r
Content-Type: text/plain\r
Content-Length: 10\r
\r
1234567890""";
Socket client0 = newSocketToServer("client0");
OutputStream output0 = client0.getOutputStream();
InputStream input0 = client0.getInputStream();
try (StacklessLogging ignore = new StacklessLogging(Response.class))
{
// Write request
output0.write(rawRequest.getBytes(StandardCharsets.UTF_8));
output0.flush();
// Verify response
HttpTester.Response response = HttpTester.parseResponse(input0);
assertNotNull(response);
assertThat(response.getStatus(), is(HttpStatus.INTERNAL_SERVER_ERROR_500));
assertThat(response.get(HttpHeader.CONNECTION), is(nullValue()));
}
// Perform stop
CompletableFuture<Long> stopFuture = runAsyncServerStop();
long stopDuration = stopFuture.get();
assertThat(stopDuration, lessThan(5000L));
// Ensure client connection is closed
long beginClose = NanoTime.now();
// The socket should have been closed
assertThat(client0 + " not closed", input0.read(), is(-1));
assertThat(client0 + " close took too long", NanoTime.millisSince(beginClose), lessThan(2000L));
}
/**
* Test for when a Handler uses {@link Callback#failed(Throwable)} when in graceful mode.
*/
@Test
public void testHandlerGracefulCallbackFailure() throws Exception
{
CountDownLatch dispatchLatch = new CountDownLatch(1);
GracefulShutdownHandler gracefulShutdownHandler = new GracefulShutdownHandler();
gracefulShutdownHandler.setHandler(new Handler.Abstract()
{
@Override
public boolean process(Request request, Response response, Callback callback) throws Exception
{
dispatchLatch.countDown();
// wait for graceful to kick in
await().atMost(5, TimeUnit.SECONDS).until(() -> gracefulShutdownHandler.isShutdown());
callback.failed(new RuntimeException("Intentional Failure"));
return true;
}
});
server = createServer(gracefulShutdownHandler);
server.setStopTimeout(10000);
server.start();
String rawRequest = """
POST /?hint=intentional_failure HTTP/1.1\r
Host: localhost\r
Content-Type: text/plain\r
Content-Length: 10\r
\r
1234567890""";
Socket client0 = newSocketToServer("client0");
OutputStream output0 = client0.getOutputStream();
InputStream input0 = client0.getInputStream();
// Intentional Failure request
output0.write(rawRequest.getBytes(StandardCharsets.UTF_8));
output0.flush();
// Wait for request to reach handler
assertTrue(dispatchLatch.await(3, TimeUnit.SECONDS), "Request didn't reach handler");
// Perform stop
CompletableFuture<Long> stopFuture = runAsyncServerStop();
// Verify response
HttpTester.Response response = HttpTester.parseResponse(input0);
assertNotNull(response);
assertThat(response.getStatus(), is(HttpStatus.INTERNAL_SERVER_ERROR_500));
assertThat(response.get(HttpHeader.CONNECTION), is("close"));
// Verify stop
long stopDuration = stopFuture.get();
assertThat(stopDuration, lessThan(5000L));
// Ensure client connection is closed
long beginClose = NanoTime.now();
// The socket should have been closed
assertThat(client0 + " not closed", input0.read(), is(-1));
assertThat(client0 + " close took too long", NanoTime.millisSince(beginClose), lessThan(2000L));
}
/**
* Test for when a Handler returns false from {@link Handler#process(Request, Response, Callback)}
* when in normal mode (not during graceful mode).
* This test exists to ensure that the Callback management of the {@link GracefulShutdownHandler} doesn't
* mess with normal operations of requests.
*/
@Test
public void testHandlerNormalProcessingFalse() throws Exception
{
GracefulShutdownHandler gracefulShutdownHandler = new GracefulShutdownHandler();
gracefulShutdownHandler.setHandler(new Handler.Abstract()
{
@Override
public boolean process(Request request, Response response, Callback callback) throws Exception
{
return false;
}
});
server = createServer(gracefulShutdownHandler);
server.setStopTimeout(10000);
server.start();
String rawRequest = """
GET / HTTP/1.1\r
Host: localhost\r
Content-Type: text/plain\r
\r
""";
Socket client0 = newSocketToServer("client0");
OutputStream output0 = client0.getOutputStream();
InputStream input0 = client0.getInputStream();
// Intentional Failure request
output0.write(rawRequest.getBytes(StandardCharsets.UTF_8));
output0.flush();
HttpTester.Response response = HttpTester.parseResponse(input0);
assertNotNull(response);
assertThat(response.getStatus(), is(HttpStatus.NOT_FOUND_404));
assertThat(response.get(HttpHeader.CONNECTION), is(nullValue()));
// Verify Stop duration
CompletableFuture<Long> stopFuture = runAsyncServerStop();
long stopDuration = stopFuture.get();
assertThat(stopDuration, lessThan(5000L));
// Ensure client connection is closed
long beginClose = NanoTime.now();
// The socket should have been closed
assertThat(client0 + " not closed", input0.read(), is(-1));
assertThat(client0 + " close took too long", NanoTime.millisSince(beginClose), lessThan(2000L));
}
/**
* Test for when a Handler returns false from {@link Handler#process(Request, Response, Callback)}
* when in graceful mode.
*/
@Test
public void testHandlerGracefulProcessingFalse() throws Exception
{
AtomicReference<CompletableFuture<Long>> stopFuture = new AtomicReference<>();
GracefulShutdownHandler gracefulShutdownHandler = new GracefulShutdownHandler();
gracefulShutdownHandler.setHandler(new Handler.Abstract()
{
@Override
public boolean process(Request request, Response response, Callback callback) throws Exception
{
stopFuture.set(runAsyncServerStop());
await().atMost(5, TimeUnit.SECONDS).until(() -> gracefulShutdownHandler.isShutdown());
return false;
}
});
server = createServer(gracefulShutdownHandler);
server.setStopTimeout(10000);
server.start();
String rawRequest = """
POST / HTTP/1.1\r
Host: localhost\r
Content-Type: text/plain\r
Content-Length: 10\r
\r
12345""";
Socket client0 = newSocketToServer("client0");
OutputStream output0 = client0.getOutputStream();
InputStream input0 = client0.getInputStream();
// Intentional Failure request
output0.write(rawRequest.getBytes(StandardCharsets.UTF_8));
output0.flush();
HttpTester.Response response = HttpTester.parseResponse(input0);
assertNotNull(response);
assertThat(response.getStatus(), is(HttpStatus.NOT_FOUND_404));
assertThat(response.get(HttpHeader.CONNECTION), is("close"));
// Verify Stop duration
long stopDuration = stopFuture.get().get();
assertThat(stopDuration, lessThan(5000L));
// Ensure client connection is closed
long beginClose = NanoTime.now();
// The socket should have been closed
assertThat(client0 + " not closed", input0.read(), is(-1));
assertThat(client0 + " close took too long", NanoTime.millisSince(beginClose), lessThan(2000L));
}
/**
* Test for behavior where the Handler is actively processing
* a request when the server goes into graceful mode.
*/
@Test
public void testHandlerGracefulBlocked() throws Exception
{
CountDownLatch dispatchedToHandlerLatch = new CountDownLatch(1);
GracefulShutdownHandler gracefulShutdownHandler = new GracefulShutdownHandler();
gracefulShutdownHandler.setHandler(new BlockingReadHandler()
{
@Override
protected void onBeforeRead(Request request, Response response)
{
dispatchedToHandlerLatch.countDown();
}
});
server = createServer(gracefulShutdownHandler);
server.setStopTimeout(10000);
server.start();
// Body is incomplete (send 5 bytes out of 10)
String rawRequest = """
POST /?hint=incomplete_body HTTP/1.1\r
Host: localhost\r
Content-Type: text/plain\r
Content-Length: 10\r
\r
12345""";
Socket client0 = newSocketToServer("client0");
OutputStream output0 = client0.getOutputStream();
InputStream input0 = client0.getInputStream();
// Write incomplete request
output0.write(rawRequest.getBytes(StandardCharsets.UTF_8));
output0.flush();
// Wait for request to reach handler
assertTrue(dispatchedToHandlerLatch.await(2, TimeUnit.SECONDS), "Request didn't reach handler");
// Trigger stop
CompletableFuture<Long> stopFuture = runAsyncServerStop();
// Wait till we enter graceful mode
await().atMost(5, TimeUnit.SECONDS).until(() -> gracefulShutdownHandler.isShutdown());
// Send rest of data
output0.write("67890".getBytes(StandardCharsets.UTF_8));
output0.flush();
// Test response
HttpTester.Response response = HttpTester.parseResponse(input0);
assertNotNull(response);
assertThat(response.getStatus(), is(HttpStatus.OK_200));
assertThat(response.get(HttpHeader.CONNECTION), is("close"));
assertThat(response.getContent(), is("(Read:10) (Content-Length:10)"));
// Verify Stop duration
long stopDuration = stopFuture.get();
assertThat(stopDuration, lessThan(5000L));
// Ensure client connection is closed
long beginClose = NanoTime.now();
// The socket should have been closed
assertThat(client0 + " should have been closed", input0.read(), is(-1));
assertThat(client0 + " close took too long", NanoTime.millisSince(beginClose), lessThan(2000L));
}
/**
* Test for behavior where the Handler is actively processing
* a request when the server goes into graceful mode.
* <p>
* This variation has the response already committed
* when the server enters Graceful stop mode.
* </p>
*/
@Test
public void testHandlerGracefulBlockedEarlyCommit() throws Exception
{
CountDownLatch dispatchedToHandlerLatch = new CountDownLatch(1);
GracefulShutdownHandler gracefulShutdownHandler = new GracefulShutdownHandler();
gracefulShutdownHandler.setHandler(new BlockingReadHandler()
{
@Override
protected void onBeforeRead(Request request, Response response) throws Exception
{
try (Blocker.Callback block = Blocker.callback())
{
LOG.debug("Response commit (early): {}", request.getHttpURI());
Content.Sink.write(response, false, "", block);
block.block();
}
assertTrue(response.isCommitted(), "Response expected to be committed");
dispatchedToHandlerLatch.countDown();
}
});
server = createServer(gracefulShutdownHandler);
server.setStopTimeout(10000);
server.start();
// Body is incomplete (send 5 bytes out of 10)
String rawRequest = """
POST /?hint=incomplete_body HTTP/1.1\r
Host: localhost\r
Content-Type: text/plain\r
Content-Length: 10\r
\r
12345""";
Socket client0 = newSocketToServer("client0");
OutputStream output0 = client0.getOutputStream();
InputStream input0 = client0.getInputStream();
// Write incomplete request
output0.write(rawRequest.getBytes(StandardCharsets.UTF_8));
output0.flush();
// Wait for request to reach handler
assertTrue(dispatchedToHandlerLatch.await(2, TimeUnit.SECONDS), "Request didn't reach handler");
// Trigger stop
CompletableFuture<Long> stopFuture = runAsyncServerStop();
// Wait till we enter graceful mode
await().atMost(5, TimeUnit.SECONDS).until(() -> gracefulShutdownHandler.isShutdown());
// Send rest of data
output0.write("67890".getBytes(StandardCharsets.UTF_8));
output0.flush();
// Test response (should not report as closed, due to early commit)
HttpTester.Response response = HttpTester.parseResponse(input0);
assertNotNull(response);
assertThat(response.getStatus(), is(HttpStatus.OK_200));
assertThat(response.get(HttpHeader.CONNECTION), is(nullValue()));
assertEquals("chunked", response.get(HttpHeader.TRANSFER_ENCODING), client0 + " transfer-encoding header");
assertThat(response.getContent(), is("(Read:10) (Content-Length:10)"));
// Verify Stop duration
long stopDuration = stopFuture.get();
assertThat(stopDuration, lessThan(5000L));
// Ensure client connection is closed
long beginClose = NanoTime.now();
// The socket should have been closed
assertThat(client0 + " should have been closed", input0.read(), is(-1));
assertThat(client0 + " close took too long", NanoTime.millisSince(beginClose), lessThan(2000L));
}
/**
* Test of how the {@link GracefulShutdownHandler} should behave if it
* receives a request on an active connection after graceful starts.
*/
@Test
public void testRequestAfterGraceful() throws Exception
{
GracefulShutdownHandler gracefulShutdownHandler = new GracefulShutdownHandler();
gracefulShutdownHandler.setHandler(new BlockingReadHandler());
server = createServer(gracefulShutdownHandler);
server.setStopTimeout(10000);
server.start();
// Complete request
String rawRequest = """
POST /?num=%d HTTP/1.1\r
Host: localhost\r
Content-Type: text/plain\r
Content-Length: 10\r
\r
1234567890""";
Socket client0 = newSocketToServer("client0");
OutputStream output0 = client0.getOutputStream();
InputStream input0 = client0.getInputStream();
HttpTester.Response response;
// Send one normal request to server
output0.write(rawRequest.formatted(1).getBytes(StandardCharsets.UTF_8));
output0.flush();
// Verify response
response = HttpTester.parseResponse(client0.getInputStream());
assertNotNull(response);
assertThat(response.getStatus(), is(HttpStatus.OK_200));
assertThat(response.get(HttpHeader.CONNECTION), is(nullValue()));
assertThat(response.getContent(), is("(Read:10) (Content-Length:10)"));
// Trigger stop
CompletableFuture<Long> stopFuture = runAsyncServerStop();
// Wait till we enter graceful mode
await().atMost(5, TimeUnit.SECONDS).until(() -> gracefulShutdownHandler.isShutdown());
// Send another request on same connection
output0.write(rawRequest.formatted(2).getBytes(StandardCharsets.UTF_8));
output0.flush();
// Verify response (should be a 503)
response = HttpTester.parseResponse(client0.getInputStream());
assertNotNull(response);
assertThat(response.getStatus(), is(HttpStatus.SERVICE_UNAVAILABLE_503));
assertThat(response.get(HttpHeader.CONNECTION), is("close"));
// Verify Stop duration
long stopDuration = stopFuture.get();
assertThat(stopDuration, lessThan(5000L));
// Ensure client connection is closed
InputStream in = client0.getInputStream();
long beginClose = NanoTime.now();
// The socket should have been closed
assertThat(client0 + " not closed", in.read(), is(-1));
assertThat(client0 + " close took too long", NanoTime.millisSince(beginClose), lessThan(2000L));
}
/**
* Simply reads the entire request body content, and replies with
* how many bytes read, and what the request Content-Length said
*/
static class BlockingReadHandler extends Handler.Abstract
{
private static final Logger LOG = LoggerFactory.getLogger(BlockingReadHandler.class);
@Override
public boolean process(Request request, Response response, Callback callback) throws Exception
{
LOG.debug("process: request={}", request);
onBeforeRead(request, response);
// Read request content (completely)
int bytesRead = 0;
long contentLength = request.getLength();
Blocker.Shared blocking = new Blocker.Shared();
if (contentLength > 0)
{
while (true)
{
Content.Chunk chunk = request.read();
if (chunk == null)
{
try (Blocker.Runnable block = blocking.runnable())
{
request.demand(block);
block.block();
continue;
}
}
LOG.debug("chunk = {}", chunk);
if (chunk instanceof Content.Chunk.Error error)
{
Response.writeError(request, response, callback, error.getCause());
return true;
}
bytesRead += chunk.remaining();
chunk.release();
if (chunk.isLast())
break;
}
}
String responseBody = "(Read:%d) (Content-Length:%d)".formatted(bytesRead, contentLength);
LOG.debug("Content.Sink.Write: {}", responseBody);
Content.Sink.write(response, true, responseBody, callback);
return true;
}
/**
* Event indicating that this exchange is about to read from the request body
*
* @param request the request
* @param response the response
* @throws Exception if unable to perform action
*/
protected void onBeforeRead(Request request, Response response) throws Exception
{
// override to trigger extra behavior in test case
}
}
}

View File

@ -1,455 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2022 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
// ========================================================================
//
package org.eclipse.jetty.server;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpTester;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.handler.HandlerList;
import org.eclipse.jetty.server.handler.StatisticsHandler;
import org.eclipse.jetty.util.Blocker;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Fields;
import org.eclipse.jetty.util.FuturePromise;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.component.Graceful;
import org.eclipse.jetty.util.component.LifeCycle;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.lessThan;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
@Disabled // TODO
public class GracefulStopTest
{
static byte[] POST_12345 = ("POST / HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Content-Type: plain/text\r\n" +
"Content-Length: 10\r\n" +
"\r\n" +
"12345").getBytes(StandardCharsets.ISO_8859_1);
static byte[] POST_A_12345 = ("POST /a/ HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Content-Type: plain/text\r\n" +
"Content-Length: 10\r\n" +
"\r\n" +
"12345").getBytes(StandardCharsets.ISO_8859_1);
static byte[] POST_B_12345 = ("POST /b/ HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Content-Type: plain/text\r\n" +
"Content-Length: 10\r\n" +
"\r\n" +
"12345").getBytes(StandardCharsets.ISO_8859_1);
static byte[] POST_12345_C = ("POST /?commit=true HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Content-Type: plain/text\r\n" +
"Content-Length: 10\r\n" +
"\r\n" +
"12345").getBytes(StandardCharsets.ISO_8859_1);
static byte[] POST_A_12345_C = ("POST /a/?commit=true HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Content-Type: plain/text\r\n" +
"Content-Length: 10\r\n" +
"\r\n" +
"12345").getBytes(StandardCharsets.ISO_8859_1);
static byte[] POST_B_12345_C = ("POST /b/?commit=true HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Content-Type: plain/text\r\n" +
"Content-Length: 10\r\n" +
"\r\n" +
"12345").getBytes(StandardCharsets.ISO_8859_1);
static byte[] BODY_67890 = "67890".getBytes(StandardCharsets.ISO_8859_1);
Server server = new Server();
ServerConnector connector = new ServerConnector(server);
HandlerList handlers = new HandlerList();
ContextHandlerCollection contexts = new ContextHandlerCollection();
ContextHandler contextA = new ContextHandler(contexts, "/a");
TestHandler handlerA = new TestHandler();
ContextHandler contextB = new ContextHandler(contexts, "/b");
StatisticsHandler statsB = new StatisticsHandler();
TestHandler handlerB = new TestHandler();
TestHandler handler = new TestHandler();
@BeforeEach
public void beforeEach() throws Exception
{
connector.setIdleTimeout(10000);
connector.setShutdownIdleTimeout(1000);
connector.setPort(0);
server.addConnector(connector);
server.setHandler(handlers);
handlers.addHandler(contexts);
handlers.addHandler(handler);
contextA.setHandler(handlerA);
contextB.setHandler(statsB);
statsB.setHandler(handlerB);
server.setStopTimeout(10000);
server.start();
}
Socket newClientBusy(byte[] post, TestHandler handler) throws Exception
{
handler.latch = new CountDownLatch(1);
final int port = connector.getLocalPort();
Socket client = new Socket("127.0.0.1", port);
client.getOutputStream().write(post);
client.getOutputStream().flush();
assertTrue(handler.latch.await(5, TimeUnit.SECONDS));
return client;
}
Socket newClientIdle(byte[] post, TestHandler handler) throws Exception
{
handler.latch = new CountDownLatch(1);
final int port = connector.getLocalPort();
Socket client = new Socket("127.0.0.1", port);
client.getOutputStream().write(concat(post, BODY_67890));
client.getOutputStream().flush();
assertTrue(handler.latch.await(5, TimeUnit.SECONDS));
HttpTester.Response response = HttpTester.parseResponse(client.getInputStream());
assertNotNull(response);
assertThat(response.getStatus(), is(200));
assertThat(response.getContent(), is("read [10/10]"));
assertThat(response.get(HttpHeader.CONNECTION), nullValue());
return client;
}
void assertAvailable(Socket client, byte[] post, TestHandler handler) throws Exception
{
handler.latch = new CountDownLatch(1);
client.getOutputStream().write(concat(post, BODY_67890));
client.getOutputStream().flush();
assertTrue(handler.latch.await(5, TimeUnit.SECONDS));
HttpTester.Response response = HttpTester.parseResponse(client.getInputStream());
assertNotNull(response);
assertThat(response.getStatus(), is(200));
assertThat(response.getContent(), is("read [10/10]"));
assertThat(response.get(HttpHeader.CONNECTION), nullValue());
}
Future<Integer> backgroundUnavailable(Socket client, byte[] post, ContextHandler context, TestHandler handler) throws Exception
{
FuturePromise<Integer> future = new FuturePromise<>();
long start = NanoTime.now();
new Thread(() ->
{
try
{
while (context.isAvailable())
{
assertThat(NanoTime.millisSince(start), lessThan(5000L));
Thread.sleep(100);
}
client.getOutputStream().write(concat(post, BODY_67890));
client.getOutputStream().flush();
HttpTester.Response response = HttpTester.parseResponse(client.getInputStream());
assertNotNull(response);
future.succeeded(response.getStatus());
}
catch (Exception e)
{
future.failed(e);
}
}).start();
return future;
}
void assertQuickStop() throws Exception
{
long start = NanoTime.now();
server.stop();
assertThat(NanoTime.millisSince(start), lessThan(2000L));
}
void assertGracefulStop(LifeCycle lifecycle) throws Exception
{
long start = NanoTime.now();
lifecycle.stop();
long duration = NanoTime.millisSince(start);
assertThat(duration, greaterThan(50L));
assertThat(duration, lessThan(5000L));
}
void assertResponse(Socket client, boolean close) throws Exception
{
HttpTester.Response response = HttpTester.parseResponse(client.getInputStream());
assertNotNull(response);
assertThat(response.getStatus(), is(200));
if (close)
assertThat(response.get(HttpHeader.CONNECTION), is("close"));
else
assertThat(response.get(HttpHeader.CONNECTION), nullValue());
assertThat(response.getContent(), is("read [10/10]"));
}
void assert500Response(Socket client) throws Exception
{
HttpTester.Response response = HttpTester.parseResponse(client.getInputStream());
if (response != null)
{
assertThat(response.getStatus(), is(500));
assertThat(response.get(HttpHeader.CONNECTION), is("close"));
}
}
void assertQuickClose(Socket client) throws Exception
{
long start = NanoTime.now();
assertThat(client.getInputStream().read(), is(-1));
assertThat(NanoTime.millisSince(start), lessThan(2000L));
}
void assertHandled(TestHandler handler, boolean error)
{
assertThat(handler.handling.get(), is(false));
if (error)
assertThat(handler.thrown.get(), Matchers.notNullValue());
else
assertThat(handler.thrown.get(), Matchers.nullValue());
}
void backgroundComplete(Socket client, TestHandler handler) throws Exception
{
long start = NanoTime.now();
new Thread(() ->
{
try
{
handler.latch.await();
Thread.sleep(100 - NanoTime.millisSince(start));
client.getOutputStream().write(BODY_67890);
}
catch (Exception e)
{
e.printStackTrace();
}
}).start();
}
private byte[] concat(byte[] bytes1, byte[] bytes2)
{
byte[] bytes = Arrays.copyOf(bytes1, bytes1.length + bytes2.length);
System.arraycopy(bytes2, 0, bytes, bytes1.length, bytes2.length);
return bytes;
}
@Test
public void testNotGraceful() throws Exception
{
server.setStopTimeout(0);
server.start();
Socket client0 = newClientBusy(POST_12345, handler);
Socket client1 = newClientIdle(POST_12345, handler);
assertQuickStop();
assertQuickClose(client0);
assertQuickClose(client1);
assertHandled(handler, true);
}
@Test
public void testGracefulConnection() throws Exception
{
Socket client0 = newClientBusy(POST_12345, handler);
Socket client1 = newClientBusy(POST_12345_C, handler);
Socket client2 = newClientIdle(POST_12345, handler);
backgroundComplete(client0, handler);
backgroundComplete(client1, handler);
assertGracefulStop(server);
assertResponse(client0, true);
assertResponse(client1, false);
assertQuickClose(client0);
assertQuickClose(client1);
assertQuickClose(client2);
assertHandled(handler, false);
}
@Test
public void testGracefulConnectionNotComplete() throws Exception
{
server.setStopTimeout(3000L);
Socket client0 = newClientBusy(POST_12345, handler);
Socket client1 = newClientBusy(POST_12345_C, handler);
Socket client2 = newClientIdle(POST_12345, handler);
assertGracefulStop(server);
assert500Response(client0);
assert500Response(client1);
assertQuickClose(client0);
assertQuickClose(client1);
assertQuickClose(client2);
assertHandled(handler, true);
}
@Test
public void testGracefulWithContext() throws Exception
{
Socket client0 = newClientBusy(POST_A_12345, handlerA);
Socket client1 = newClientBusy(POST_A_12345_C, handlerA);
Socket client2 = newClientIdle(POST_A_12345, handlerA);
backgroundComplete(client0, handlerA);
backgroundComplete(client1, handlerA);
Future<Integer> status2 = backgroundUnavailable(client2, POST_A_12345, contextA, handlerA);
assertGracefulStop(server);
assertResponse(client0, true);
assertResponse(client1, false);
assertThat(status2.get(), is(503));
assertQuickClose(client0);
assertQuickClose(client1);
assertQuickClose(client2);
assertHandled(handlerA, false);
}
@Test
public void testGracefulContext() throws Exception
{
Socket client0 = newClientBusy(POST_B_12345, handlerB);
Socket client1 = newClientBusy(POST_B_12345_C, handlerB);
Socket client2 = newClientIdle(POST_B_12345, handlerB);
backgroundComplete(client0, handlerB);
backgroundComplete(client1, handlerB);
Future<Integer> status2 = backgroundUnavailable(client2, POST_B_12345, contextB, handlerB);
Graceful.shutdown(contextB).orTimeout(10, TimeUnit.SECONDS).get();
assertResponse(client0, false);
assertResponse(client1, false);
assertThat(status2.get(), is(503));
assertAvailable(client0, POST_A_12345, handlerA);
assertAvailable(client1, POST_A_12345_C, handlerA);
assertAvailable(client2, POST_A_12345, handlerA);
assertHandled(handlerA, false);
assertHandled(handlerB, false);
}
static class TestHandler extends Handler.Abstract
{
final AtomicReference<Throwable> thrown = new AtomicReference<Throwable>();
final AtomicBoolean handling = new AtomicBoolean(false);
volatile CountDownLatch latch;
@Override
public boolean process(Request request, Response response, Callback callback) throws Exception
{
// Log.getRootLogger().info("Handle {} / {} ? {}", request.getContextPath(), request.getPathInfo(), request.getQueryString());
handling.set(true);
response.setStatus(200);
Fields fields = Request.extractQueryParameters(request);
if ("true".equals(fields.getValue("commit")))
{
try (Blocker.Callback block = Blocker.callback())
{
response.write(false, null, block);
block.block();
}
}
CountDownLatch l = latch;
if (l != null)
l.countDown();
int c = 0;
try
{
long contentLength = request.getLength();
Blocker.Shared blocking = new Blocker.Shared();
if (contentLength > 0)
{
while (true)
{
Content.Chunk chunk = request.read();
if (chunk == null)
{
try (Blocker.Runnable block = blocking.runnable())
{
request.demand(block);
block.block();
continue;
}
}
c += chunk.remaining();
chunk.release();
if (chunk.isLast())
break;
}
}
try (Blocker.Callback block = blocking.callback())
{
Content.Sink.write(response, true, "read [%d/%d]".formatted(c, contentLength), block);
block.block();
}
}
catch (Throwable th)
{
thrown.set(th);
callback.failed(th);
}
finally
{
callback.succeeded();
handling.set(false);
}
return true;
}
}
}

View File

@ -43,7 +43,6 @@ import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.http.UriCompliance;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.logging.StacklessLogging;
import org.eclipse.jetty.server.handler.ContextRequest;
import org.eclipse.jetty.server.handler.DumpHandler;
import org.eclipse.jetty.server.internal.HttpChannelState;
import org.eclipse.jetty.server.internal.HttpConnection;
@ -1036,7 +1035,7 @@ public class HttpConnectionTest
"\r\n" +
"abcdefghij\r\n";
try (StacklessLogging ignored = new StacklessLogging(ContextRequest.class))
try (StacklessLogging ignored = new StacklessLogging(Response.class))
{
LOG.info("EXPECTING: java.lang.IllegalStateException...");
String response = _connector.getResponse(requests);

View File

@ -43,7 +43,6 @@ import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.RetainableByteBufferPool;
import org.eclipse.jetty.logging.StacklessLogging;
import org.eclipse.jetty.server.handler.ContextRequest;
import org.eclipse.jetty.server.handler.EchoHandler;
import org.eclipse.jetty.server.handler.HelloHandler;
import org.eclipse.jetty.server.internal.HttpConnection;
@ -406,7 +405,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort());
OutputStream os = client.getOutputStream();
try (StacklessLogging ignored = new StacklessLogging(ContextRequest.class))
try (StacklessLogging ignored = new StacklessLogging(Response.class))
{
LOG.info("Expecting Exception: TEST handler exception...");
os.write(request.toString().getBytes());
@ -435,7 +434,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort());
OutputStream os = client.getOutputStream();
try (StacklessLogging ignored = new StacklessLogging(ContextRequest.class))
try (StacklessLogging ignored = new StacklessLogging(Response.class))
{
LOG.info("Expecting Exception: TEST handler exception...");
os.write(request.toString().getBytes());

View File

@ -0,0 +1,124 @@
//
// ========================================================================
// Copyright (c) 1995-2022 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
// ========================================================================
//
package org.eclipse.jetty.server;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import org.awaitility.Awaitility;
import org.eclipse.jetty.logging.JettyLevel;
import org.eclipse.jetty.logging.JettyLogger;
import org.eclipse.jetty.server.handler.AbstractLatencyRecordingHandler;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.Callback;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.slf4j.LoggerFactory;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.is;
public class LatencyRecordingHandlerTest
{
private JettyLevel _oldLevel;
private Server _server;
private LocalConnector _local;
private final List<Long> _latencies = new CopyOnWriteArrayList<>();
@BeforeEach
public void setUp() throws Exception
{
_server = new Server();
_local = new LocalConnector(_server, new HttpConnectionFactory());
_server.addConnector(_local);
Handler handler = new Handler.Abstract()
{
@Override
public boolean process(Request request, Response response, Callback callback)
{
String path = request.getHttpURI().getPath();
if (path.endsWith("/fail"))
callback.failed(new Exception());
else
callback.succeeded();
return true;
}
};
AbstractLatencyRecordingHandler latencyRecordingHandler = new AbstractLatencyRecordingHandler()
{
@Override
protected void onRequestComplete(long durationInNs)
{
_latencies.add(durationInNs);
}
};
latencyRecordingHandler.setHandler(handler);
ContextHandler contextHandler = new ContextHandler("/ctx");
contextHandler.setHandler(latencyRecordingHandler);
_server.setHandler(contextHandler);
_server.start();
// Disable WARN logs of failed requests.
JettyLogger logger = (JettyLogger)LoggerFactory.getLogger(Response.class);
_oldLevel = logger.getLevel();
logger.setLevel(JettyLevel.OFF);
}
@AfterEach
public void tearDown() throws Exception
{
_server.stop();
_latencies.clear();
JettyLogger logger = (JettyLogger)LoggerFactory.getLogger(Response.class);
logger.setLevel(_oldLevel);
}
@Test
public void testLatenciesRecodingUponSuccess() throws Exception
{
for (int i = 0; i < 100; i++)
{
String response = _local.getResponse("GET /ctx/succeed HTTP/1.1\r\nHost: localhost\r\n\r\n");
assertThat(response, containsString(" 200 OK"));
}
Awaitility.await().atMost(5, TimeUnit.SECONDS).until(_latencies::size, is(100));
for (Long latency : _latencies)
{
assertThat(latency, greaterThan(0L));
}
}
@Test
public void testLatenciesRecodingUponFailure() throws Exception
{
for (int i = 0; i < 100; i++)
{
String response = _local.getResponse("GET /ctx/fail HTTP/1.1\r\nHost: localhost\r\n\r\n");
assertThat(response, containsString(" 500 Server Error"));
}
Awaitility.await().atMost(5, TimeUnit.SECONDS).until(_latencies::size, is(100));
for (Long latency : _latencies)
{
assertThat(latency, greaterThan(0L));
}
}
}

View File

@ -24,7 +24,6 @@ import org.eclipse.jetty.logging.StacklessLogging;
import org.eclipse.jetty.server.LocalConnector.LocalEndPoint;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.DumpHandler;
import org.eclipse.jetty.server.handler.HandlerList;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
@ -39,7 +38,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
@Disabled // TODO
public class PartialRFC2616Test
{
private Server server;
@ -62,7 +60,7 @@ public class PartialRFC2616Test
context.setContextPath("/");
context.setHandler(new DumpHandler());
server.setHandler(new HandlerList(vcontext, context));
server.setHandler(new Handler.Collection(vcontext, context));
server.start();
}
@ -406,6 +404,7 @@ public class PartialRFC2616Test
}
@Test
@Disabled // TODO
public void test521() throws Exception
{
// Default Host
@ -413,7 +412,7 @@ public class PartialRFC2616Test
String response = connector.getResponse("GET http://VirtualHost:8888/path/R1 HTTP/1.1\n" + "Host: wronghost\n" + "Connection: close\n" + "\n");
offset = checkContains(response, offset, "HTTP/1.1 200", "Virtual host") + 1;
offset = checkContains(response, offset, "Virtual Dump", "Virtual host") + 1;
offset = checkContains(response, offset, "pathInfo=/path/R1", "Virtual host") + 1;
offset = checkContains(response, offset, "pathInContext=/path/R1", "Virtual host") + 1;
offset = checkContains(response, offset, "servername=VirtualHost", "Virtual host") + 1;
}
@ -424,15 +423,15 @@ public class PartialRFC2616Test
int offset = 0;
String response = connector.getResponse("GET /path/R1 HTTP/1.1\n" + "Host: localhost\n" + "Connection: close\n" + "\n");
offset = checkContains(response, offset, "HTTP/1.1 200", "Default host") + 1;
offset = checkContains(response, offset, "Dump HttpHandler", "Default host") + 1;
offset = checkContains(response, offset, "pathInfo=/path/R1", "Default host") + 1;
offset = checkContains(response, offset, "Dump Handler", "Default host") + 1;
offset = checkContains(response, offset, "pathInContext=/path/R1", "Default host") + 1;
// Virtual Host
offset = 0;
response = connector.getResponse("GET /path/R2 HTTP/1.1\n" + "Host: VirtualHost\n" + "Connection: close\n" + "\n");
offset = checkContains(response, offset, "HTTP/1.1 200", "Default host") + 1;
offset = checkContains(response, offset, "Virtual Dump", "virtual host") + 1;
offset = checkContains(response, offset, "pathInfo=/path/R2", "Default host") + 1;
offset = checkContains(response, offset, "pathInContext=/path/R2", "Default host") + 1;
}
@Test
@ -443,14 +442,14 @@ public class PartialRFC2616Test
String response = connector.getResponse("GET /path/R1 HTTP/1.1\n" + "Host: VirtualHost\n" + "Connection: close\n" + "\n");
offset = checkContains(response, offset, "HTTP/1.1 200", "2. virtual host field") + 1;
offset = checkContains(response, offset, "Virtual Dump", "2. virtual host field") + 1;
offset = checkContains(response, offset, "pathInfo=/path/R1", "2. virtual host field") + 1;
offset = checkContains(response, offset, "pathInContext=/path/R1", "2. virtual host field") + 1;
// Virtual Host case insensitive
offset = 0;
response = connector.getResponse("GET /path/R1 HTTP/1.1\n" + "Host: ViRtUalhOst\n" + "Connection: close\n" + "\n");
offset = checkContains(response, offset, "HTTP/1.1 200", "2. virtual host field") + 1;
offset = checkContains(response, offset, "Virtual Dump", "2. virtual host field") + 1;
offset = checkContains(response, offset, "pathInfo=/path/R1", "2. virtual host field") + 1;
offset = checkContains(response, offset, "pathInContext=/path/R1", "2. virtual host field") + 1;
// Virtual Host
offset = 0;
@ -486,6 +485,7 @@ public class PartialRFC2616Test
}
@Test
@Disabled // TODO
public void test10418() throws Exception
{
// Expect Failure
@ -541,6 +541,7 @@ public class PartialRFC2616Test
}
@Test
@Disabled // TODO
public void test824() throws Exception
{
// Expect 100 not sent
@ -599,6 +600,7 @@ public class PartialRFC2616Test
}
@Test
@Disabled // TODO
public void test1423() throws Exception
{
try (StacklessLogging stackless = new StacklessLogging(HttpParser.class))

View File

@ -14,13 +14,11 @@
package org.eclipse.jetty.server;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import static java.time.Duration.ofSeconds;
import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively;
@Disabled // TODO
public class ServerConnectorTimeoutTest extends ConnectorTimeoutTest
{
@BeforeEach
@ -28,11 +26,11 @@ public class ServerConnectorTimeoutTest extends ConnectorTimeoutTest
{
ServerConnector connector = new ServerConnector(_server, 1, 1);
connector.setIdleTimeout(MAX_IDLE_TIME);
_server.addConnector(connector);
initServer(connector);
}
@Test
public void testStartStopStart() throws Exception
public void testStartStopStart()
{
assertTimeoutPreemptively(ofSeconds(10), () ->
{
@ -40,165 +38,4 @@ public class ServerConnectorTimeoutTest extends ConnectorTimeoutTest
_server.start();
});
}
/* TODO
@Test
public void testIdleTimeoutAfterSuspend() throws Exception
{
_server.stop();
SuspendHandler handler = new SuspendHandler();
SessionHandler session = new SessionHandler();
session.setHandler(handler);
_server.setHandler(session);
_server.start();
handler.setSuspendFor(100);
handler.setResumeAfter(25);
assertTimeoutPreemptively(ofSeconds(10), () ->
{
String process = process(null).toUpperCase(Locale.ENGLISH);
assertThat(process, containsString("RESUMED"));
});
}
@Test
public void testIdleTimeoutAfterTimeout() throws Exception
{
SuspendHandler handler = new SuspendHandler();
_server.stop();
SessionHandler session = new SessionHandler();
session.setHandler(handler);
_server.setHandler(session);
_server.start();
handler.setSuspendFor(50);
assertTimeoutPreemptively(ofSeconds(10), () ->
{
String process = process(null).toUpperCase(Locale.ENGLISH);
assertThat(process, containsString("TIMEOUT"));
});
}
@Test
public void testIdleTimeoutAfterComplete() throws Exception
{
SuspendHandler handler = new SuspendHandler();
_server.stop();
SessionHandler session = new SessionHandler();
session.setHandler(handler);
_server.setHandler(session);
_server.start();
handler.setSuspendFor(100);
handler.setCompleteAfter(25);
assertTimeoutPreemptively(ofSeconds(10), () ->
{
String process = process(null).toUpperCase(Locale.ENGLISH);
assertThat(process, containsString("COMPLETED"));
});
}
private String process(String content) throws IOException, InterruptedException
{
synchronized (this)
{
String request = "GET / HTTP/1.1\r\n" + "Host: localhost\r\n";
if (content == null)
request += "\r\n";
else
request += "Content-Length: " + content.length() + "\r\n" + "\r\n" + content;
return getResponse(request);
}
}
private String getResponse(String request) throws IOException, InterruptedException
{
try (Socket socket = new Socket((String)null, _connector.getLocalPort()))
{
socket.setSoTimeout((int)(10 * MAX_IDLE_TIME));
socket.getOutputStream().write(request.getBytes(StandardCharsets.UTF_8));
InputStream inputStream = socket.getInputStream();
long start = NanoTime.now();
String response = IO.toString(inputStream);
assertThat(NanoTime.millisSince(start), greaterThanOrEqualTo(MAX_IDLE_TIME - 100L));
return response;
}
}
@Test
public void testHttpWriteIdleTimeout() throws Exception
{
_httpConfiguration.setIdleTimeout(500);
configureServer(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
IO.copy(request.getInputStream(), response.getOutputStream());
}
});
Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort());
client.setSoTimeout(10000);
assertFalse(client.isClosed());
final OutputStream os = client.getOutputStream();
final InputStream is = client.getInputStream();
final StringBuilder response = new StringBuilder();
CompletableFuture<Void> responseFuture = CompletableFuture.runAsync(() ->
{
try (InputStreamReader reader = new InputStreamReader(is, StandardCharsets.UTF_8))
{
int c;
while ((c = reader.read()) != -1)
{
response.append((char)c);
}
}
catch (IOException e)
{
// Valid path (as connection is forcibly closed)
// t.printStackTrace(System.err);
}
});
CompletableFuture<Void> requestFuture = CompletableFuture.runAsync(() ->
{
try
{
os.write((
"POST /echo HTTP/1.0\r\n" +
"host: " + _serverURI.getHost() + ":" + _serverURI.getPort() + "\r\n" +
"content-type: text/plain; charset=utf-8\r\n" +
"content-length: 20\r\n" +
"\r\n").getBytes("utf-8"));
os.flush();
os.write("123456789\n".getBytes("utf-8"));
os.flush();
TimeUnit.SECONDS.sleep(1);
os.write("=========\n".getBytes("utf-8"));
os.flush();
}
catch (InterruptedException | IOException e)
{
// Valid path, as write of second half of content can fail
// e.printStackTrace(System.err);
}
});
try (StacklessLogging ignore = new StacklessLogging(HttpChannel.class))
{
requestFuture.get(2, TimeUnit.SECONDS);
responseFuture.get(3, TimeUnit.SECONDS);
assertThat(response.toString(), containsString(" 500 "));
assertThat(response.toString(), not(containsString("=========")));
}
}
*/
}

View File

@ -285,7 +285,7 @@ public class ContextHandlerCollectionTest
String rawResponse = connector.getResponse(rawRequest);
HttpTester.Response response = HttpTester.parseResponse(rawResponse);
assertThat("Response status for [GET " + requestHost + "]", response.getStatus(), is(HttpStatus.NOT_FOUND_404));
assertThat("Response body for [GET " + requestHost + "]", response.getContent(), containsString("<h2>HTTP ERROR 404 Not Found</h2>"));
assertThat("Response body for [GET " + requestHost + "]", response.getContent(), containsString("Not Found"));
assertThat("Response Header for [GET " + requestHost + "]", response.get("X-IsHandled-Name"), nullValue());
connector.getResponse(rawRequest);

View File

@ -48,11 +48,7 @@ public class DefaultHandlerTest
server.addConnector(connector);
ContextHandlerCollection contexts = new ContextHandlerCollection();
handler = new DefaultHandler();
server.setHandler(new Handler.Collection(contexts, handler));
handler.setServeIcon(true);
handler.setShowContexts(true);
server.setHandler(new Handler.Collection(contexts, new DefaultHandler(true, true)));
contexts.addHandler(new ContextHandler("/foo"));
contexts.addHandler(new ContextHandler("/bar"));

View File

@ -400,7 +400,7 @@ public class NcsaRequestLogTest
setup(logType);
RequestLogHandler handler = new RequestLogHandler();
handler.setRequestLog(_log);
HandlerList handlers = new HandlerList(handler, testHandler);
Handler.Collection handlers = new Handler.Collection(handler, testHandler);
_server.setHandler(handlers);
startServer();
makeRequest(requestPath);

View File

@ -355,7 +355,7 @@ public class ResourceHandlerTest
(response) ->
{
String body = response.getContent();
assertThat(body, containsString("/../../"));
assertThat(body, containsString("Not Found"));
assertThat(body, not(containsString("Directory: ")));
}
);

View File

@ -149,7 +149,7 @@ public class SecuredRedirectHandlerCodeTest
contextHandlers.setHandlers(redirectHandler);
// Create server level handler tree
server.setHandler(new HandlerList(contextHandlers, new DefaultHandler()));
server.setHandler(contextHandlers);
server.start();

View File

@ -118,7 +118,7 @@ public class SecuredRedirectHandlerTest
contextHandlers.setHandlers(redirectHandler, rootContext, test1Context, test2Context);
// Create server level handler tree
server.setHandler(new HandlerList(contextHandlers, new DefaultHandler()));
server.setHandler(contextHandlers);
server.start();

View File

@ -40,7 +40,7 @@ public class ShutdownHandlerTest
private ServerConnector connector;
private String shutdownToken = "asdlnsldgnklns";
public void start(HandlerWrapper wrapper) throws Exception
public void start(Handler.Wrapper wrapper) throws Exception
{
server = new Server();
connector = new ServerConnector(server);
@ -94,7 +94,7 @@ public class ShutdownHandlerTest
public void testShutdownRequestNotFromLocalhost() throws Exception
{
/* TODO
start(new HandlerWrapper()
start(new Handler.Wrapper()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException

View File

@ -79,7 +79,6 @@ import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.junit.platform.commons.util.StringUtils;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.hamcrest.MatcherAssert.assertThat;
@ -1896,7 +1895,7 @@ public class GzipHandlerTest
@Override
public boolean process(Request request, Response response, Callback callback) throws Exception
{
if (StringUtils.isNotBlank(etag))
if (StringUtil.isNotBlank(etag))
{
response.getHeaders().put("ETag", etag);
String ifnm = request.getHeaders().get("If-None-Match");
@ -1944,7 +1943,7 @@ public class GzipHandlerTest
@Override
public boolean process(Request request, Response response, Callback callback) throws Exception
{
if (StringUtils.isNotBlank(etag))
if (StringUtil.isNotBlank(etag))
{
response.getHeaders().put("ETag", etag);
String ifnm = request.getHeaders().get("If-None-Match");

View File

@ -13,43 +13,37 @@
package org.eclipse.jetty.server.ssl;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.Socket;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.KeyStore;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import org.eclipse.jetty.server.ConnectorTimeoutTest;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.toolchain.test.MavenPaths;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
@Disabled // TODO
public class SslSelectChannelTimeoutTest extends ConnectorTimeoutTest
public class ServerConnectorSslTimeoutTest extends ConnectorTimeoutTest
{
static SSLContext __sslContext;
@Override
protected Socket newSocket(String host, int port) throws Exception
{
return __sslContext.getSocketFactory().createSocket(host, port);
}
@BeforeEach
public void init() throws Exception
{
String keystorePath = System.getProperty("basedir", ".") + "/src/test/resources/keystore.p12";
Path keystorePath = MavenPaths.findTestResourceFile("keystore.p12");
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
sslContextFactory.setKeyStorePath(keystorePath);
sslContextFactory.setKeyStorePath(keystorePath.toString());
sslContextFactory.setKeyStorePassword("storepwd");
ServerConnector connector = new ServerConnector(_server, 1, 1, sslContextFactory);
connector.setIdleTimeout(MAX_IDLE_TIME); //250 msec max idle
connector.setIdleTimeout(MAX_IDLE_TIME);
_server.addConnector(connector);
KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
try (InputStream stream = new FileInputStream(keystorePath))
try (InputStream stream = Files.newInputStream(keystorePath))
{
keystore.load(stream, "storepwd".toCharArray());
}
@ -58,4 +52,10 @@ public class SslSelectChannelTimeoutTest extends ConnectorTimeoutTest
__sslContext = SSLContext.getInstance("SSL");
__sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
}
@Override
protected Socket newSocket(String host, int port) throws Exception
{
return __sslContext.getSocketFactory().createSocket(host, port);
}
}

Some files were not shown because too many files have changed in this diff Show More