Merge remote-tracking branch 'origin/jetty-12.0.x' into jetty-12.0.x-9066-MultiPart-getParameters
This commit is contained in:
commit
36e7f71def
File diff suppressed because it is too large
Load Diff
|
@ -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">
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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`.
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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].
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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[]
|
||||
----
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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]
|
||||
----
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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]
|
||||
----
|
||||
|
|
|
@ -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).
|
|
@ -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).
|
|
@ -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]
|
||||
----
|
|
@ -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]
|
||||
----
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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[]
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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]
|
||||
----
|
||||
|
|
|
@ -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[]
|
||||
----
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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].
|
|
@ -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.
|
||||
|
|
|
@ -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`.
|
|
@ -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]
|
||||
----
|
||||
|
|
|
@ -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]
|
||||
----
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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]
|
||||
----
|
||||
|
|
|
@ -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[]
|
||||
|
|
|
@ -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. +
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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[]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
*/
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
||||
<!-- =========================================================== -->
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -383,7 +383,7 @@ public class DefaultBundleClassLoaderHelper implements BundleClassLoaderHelper
|
|||
{
|
||||
try
|
||||
{
|
||||
/**
|
||||
/*
|
||||
* In Concierge:
|
||||
*
|
||||
* Option A:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -37,6 +37,7 @@ public class HotSwapHandler extends Handler.AbstractContainer implements Handler
|
|||
*/
|
||||
public HotSwapHandler()
|
||||
{
|
||||
super(true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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("secret password", false, true) });
|
||||
* server.setHandler(handlers);
|
||||
* ShutdownHandler shutdown = new ShutdownHandler("secret password", 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"))
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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))
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
|
|
|
@ -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("=========")));
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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"));
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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: ")));
|
||||
}
|
||||
);
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue