Merge remote-tracking branch 'origin/jetty-10.0.x' into jetty-11.0.x

This commit is contained in:
Joakim Erdfelt 2020-10-08 09:01:34 -05:00
commit 57a9e5236a
No known key found for this signature in database
GPG Key ID: 2D0E1FB8FE4B68B4
32 changed files with 1167 additions and 1595 deletions

View File

@ -22,5 +22,3 @@
This chapter discusses various options for configuring Jetty connectors.
include::configuring-connectors.adoc[]
include::configuring-ssl.adoc[]
include::configuring-ssl-distribution.adoc[]

View File

@ -1,129 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
[[jetty-ssl-distribution]]
=== SSL in the Jetty Distribution
When making use of the Jetty Distribution, enabling SSL support is as easy as activating the appropriate module.
Jetty supports both the default https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html[JSSE]
provider and the https://github.com/google/conscrypt/[Conscrypt] provider as SSL implementations.
==== Default JSSE SSL Configuration
For the default SSL support, simply activate the `ssl` link:#startup-modules[module]:
[source, plain, subs="{sub-order}"]
----
$ cd /path/to/mybase
$ java -jar ${JETTY_HOME}/start.jar --add-to-startd=ssl
INFO : server initialised (transitively) in ${jetty.base}/start.d/server.ini
INFO : ssl initialised in ${jetty.base}/start.d/ssl.ini
INFO : Base directory was modified
$ tree
.
├── etc
│   └── keystore
└── start.d
├── server.ini
└── ssl.ini
----
When you open `start.d/ssl.ini`, you will see several commented properties ready for use when configuring `SslContextFactory` basics.
To highlight some of the more commonly used properties:
jetty.ssl.host::
Configures which interfaces the SSL/TLS Connector should listen on.
jetty.ssl.port::
Configures which port the SSL/TLS Connector should listen on.
jetty.httpConfig.securePort::
If a webapp needs to redirect to a secure version of the same resource, then this is the port reported back on the response `location` line (having this be separate is useful if you have something sitting in front of Jetty, such as a Load Balancer or proxy).
jetty.sslContext.keyStorePath::
Sets the location of the `keystore` that you configured with your certificates.
jetty.sslContext.keyStorePassword::
Sets the Password for the `keystore`.
[[jetty-conscrypt-distribution]]
==== Conscrypt SSL Configuration
Enabling Conscrypt SSL is just as easy as default SSL - enable both the `conscrypt` and `ssl` link:#startup-modules[modules]:
[source,plain,subs="{sub-order}"]
----
$ cd ${JETTY_BASE}
$ java -jar /path/to/jetty-home/start.jar --add-to-start=ssl,conscrypt
ALERT: There are enabled module(s) with licenses.
The following 1 module(s):
+ contains software not provided by the Eclipse Foundation!
+ contains software not covered by the Eclipse Public License!
+ has not been audited for compliance with its license
Module: conscrypt
+ Conscrypt is distributed under the Apache Licence 2.0
+ https://github.com/google/conscrypt/blob/master/LICENSE
Proceed (y/N)? y
INFO : server transitively enabled, ini template available with --add-to-start=server
INFO : conscrypt initialized in ${jetty.base}/start.d/conscrypt.ini
INFO : ssl initialized in ${jetty.base}/start.d/ssl.ini
MKDIR : ${jetty.base}/lib/conscrypt
DOWNLD: https://repo1.maven.org/maven2/org/conscrypt/conscrypt-openjdk-uber/1.0.0.RC11/conscrypt-openjdk-uber-1.0.0.RC11.jar to ${jetty.base}/lib/conscrypt/conscrypt-uber-1.0.0.RC11.jar
MKDIR : ${jetty.base}/etc
COPY : ${jetty.home}/modules/conscrypt/conscrypt.xml to ${jetty.base}/etc/conscrypt.xml
COPY : ${jetty.home}/modules/ssl/keystore to ${jetty.base}/etc/keystore
INFO : Base directory was modified
----
No additional Conscrypt configuration is needed.
SSL-specific parameters, like `keyStorePath` and `keyStorePassword` can still configured as in the example above, making use of the `${JETTY_BASE}/start.d/ssl.ini` file.
[[client-certificate-authentication]]
==== Client Certificate Authentication
To enable client certificate authentication in the Jetty Distribution, you need to enable the both the `ssl` and `https` modules.
[source, plain, subs="{sub-order}"]
----
$ cd /path/to/mybase
$ java -jar /path/to/jetty-dist/start.jar --add-to-startd=ssl,https
----
[source%nowrap,ini,linenums]
.$JETTY_BASE/start.d/ssl.ini
----
# Module: ssl
--module=ssl
jetty.ssl.host=0.0.0.0
jetty.ssl.port=8583
jetty.sslContext.keyStorePath=etc/keystore
jetty.sslContext.trustStorePath=etc/truststore
jetty.sslContext.keyStorePassword=OBF:
jetty.sslContext.keyManagerPassword=OBF:
jetty.sslContext.trustStorePassword=OBF:
# Enable client certificate authentication.
jetty.sslContext.needClientAuth=true
----
[source%nowrap,ini,linenums]
.$JETTY_BASE/start.d/https.ini
----
# Module: https
--module=https
----

View File

@ -29,6 +29,7 @@ include::begin/chapter.adoc[]
include::start/chapter.adoc[]
include::deploy/chapter.adoc[]
include::protocols/chapter.adoc[]
include::keystore/chapter.adoc[]
include::modules/chapter.adoc[]
include::sessions/chapter.adoc[]
include::xml/chapter.adoc[]

View File

@ -19,5 +19,13 @@
[[og-keystore]]
=== Configuring KeyStores
TODO
// TODO: see old_docs/connectors/configuring-ssl.adoc
A KeyStore is a file on the file system that contains a private key and a public certificate, along with the certificate chain of the certificate authorities that issued the certificate.
The private key, the public certificate and the certificate chain, but more generally the items present in a KeyStore, are typically referred to as "cryptographic material".
Keystores may encode the cryptographic material with different encodings, the most common being link:https://en.wikipedia.org/wiki/PKCS_12[PKCS12], and are typically protected by a password.
Refer to the xref:og-protocols-ssl[secure protocols section] for more information about how to configure a secure connector using a KeyStore.
include::keystore-create.adoc[]
include::keystore-csr.adoc[]
include::keystore-client-authn.adoc[]

View File

@ -0,0 +1,146 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
[[og-keystore-client-authn]]
==== Creating a KeyStore for Client Certificate Authentication
For the majority of secure web sites, it is the client (typically the browser) that validates the certificate sent by the server (by verifying the certificate chain).
This is the _server domain certificate_.
However, the TLS protocol supports a _mutual authentication_ mode where also the client must send a certificate to the server, that the server validates.
You typically want to sign the client certificate(s) with a server certificate that you control, and you must distribute the client certificate(s) to all the clients that need it, and redistribute the client certificates when they expire.
The _server authentication certificate_ may be different from the _server domain certificate_, but it's typically stored in the same KeyStore for simplicity (although under a different alias).
First, you want to create the private key and server authentication certificate that you will use to sign client certificates:
[source,subs=verbatim]
----
keytool
-genkeypair
-alias server_authn <1>
-validity 90
-keyalg RSA
-keysize 2048
-keystore keystore.p12 <2>
-storetype pkcs12
-dname "CN=server_authn, OU=Unit, O=Company, L=City, S=State, C=Country" <3>
-ext bc=ca:true <4>
-v
----
<1> use the `server_authn` alias to differentiate from the alias of the server certificate
<2> the KeyStore file
<3> the CN is not that important, since this certificate will not be validated by clients
<4> the extension with the basic constraints (more below)
IMPORTANT: The important difference with the xref:og-keystore-create[creation of a server certificate] is the _basic constraints_ extension (`bc`) that indicates that this certificates acts as a certificate authority (`ca:true`).
Now you want to export both the private key and server authentication certificate.
Unfortunately, the `keytool` program cannot export private keys, so you need to use a different command line program like `openssl`, or a graphical program like link:https://keystore-explorer.org/[KeyStore Explorer].
Let's use `openssl` to export the server authentication private key:
----
openssl
pkcs12
-in keystore.p12
-nodes
-nocerts
-out server_authn.key
----
Now let's export the server authentication certificate:
----
keytool
-exportcert
-keystore keystore.p12
-rfc
-file server_authn.crt
-v
----
At this point, you want to create a client KeyStore, so that you can sign the client certificate with the server authentication cryptographic material:
[source,subs=verbatim]
----
keytool
-genkeypair
-validity 90
-keyalg RSA
-keysize 2048
-keystore client_keystore.p12 <1>
-storetype pkcs12
-dname "CN=client, OU=Unit, O=Company, L=City, S=State, C=Country" <2>
-v
----
<1> the client KeyStore file
<2> the CN is not that important, since it will not be validated by the server
Now produce a certificate signing request (CSR):
----
keytool
-certreq
-file client.csr
-keystore client_keystore.p12
----
Now you need to sign the CSR, but again the `keytool` program does not have this functionality, and you must resort again to use `openssl`:
----
openssl
x509
-req
-days 90
-in client.csr
-CA server_authn.crt
-CAkey server_authn.key
-CAcreateserial
-sha256
-out signed.crt
----
Now you need to import the server authentication certificate and the signed client certificate into the client KeyStore.
First, the server authentication certificate:
----
keytool
-importcert
-alias ca
-file server_authn.crt
-keystore client_keystore.p12
-v
----
Then, the signed client certificate:
----
keytool
-importcert
-file signed.crt
-keystore client_keystore.p12
-v
----
Now you can distribute `client_keystore.p12` to your client(s).
// TODO: add a section about renewal?
Refer to the section about configuring xref:og-protocols-ssl[secure protocols] to configure the secure connector to require client authentication.

View File

@ -0,0 +1,60 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
[[og-keystore-create]]
==== Creating a KeyStore
KeyStores are created with the JDK tool `$JAVA_HOME/bin/keytool`.
The following command creates a KeyStore file containing a private key and a self-signed certificate:
[source,subs=verbatim]
----
keytool
-genkeypair <1>
-validity 90 <2>
-keyalg RSA <3>
-keysize 2048 <4>
-keystore /path/to/keystore.p12 <5>
-storetype pkcs12 <6>
-dname "CN=domain.com, OU=Unit, O=Company, L=City, S=State, C=Country" <7>
-ext san=dns:www.domain.com,dns:domain.org <8>
-v <9>
----
<1> the command to generate a key and certificate pair
<2> specifies the number of days after which the certificate expires
<3> the algorithm _must_ be RSA (the DSA algorithm does not work for web sites)
<4> indicates the strength of the key
<5> the keyStore file
<6> the keyStore type, stick with the standard PKCS12
<7> the distinguished name (more below) -- customize it with your values for CN, OU, O, L, S and C
<8> the extension with the subject alternative names (more below)
<9> verbose output
The command prompts for the KeyStore password that you must choose to protect the access to the KeyStore.
[IMPORTANT]
====
The important parts of the command above are the _Common Name_ (CN) part of the distinguished name, and the subject alternative names (SAN).
The CN value must be the main domain you own and that you want to use for your web applications.
For example, if you have bought domains `domain.com` and `domain.org`, you want to specify `CN=domain.com` as your main domain.
Furthermore, to specify additional domains or subdomains within the same certificate, you must specify the SAN extension.
In the example above, `san=dns:www.domain.com,dns:domain.org` specifies `www.domain.com` and `domain.org` as alternative names for your web applications (that you can configure using xref:og-deploy-virtual-hosts[virtual hosts]).
====

View File

@ -0,0 +1,83 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
[[og-keystore-csr]]
==== Creating a Certificate Signing Request
Self-signed certificates are not trusted by browsers and generic clients: you need to establish a trust chain by having your self-signed certificate signed by a certificate authority (CA).
Browsers and generic clients (e.g. Java clients) have an internal list of trusted certificate authorities root certificates; they use these trusted certificates to verify the certificate they received from the server when they connect to your web applications.
To have your self-signed certificate signed by a certificate authority you first need to produce a _certificate signing request_ (CSR):
[source,subs=verbatim]
----
keytool
-certreq <1>
-file domain.com.csr <2>
-keystore keystore.p12 <3>
----
<1> the command to generate a certificate signing request
<2> the file to save the CSR
<3> the keystore that contains the self-signed certificate
Then, you have to send the CSR file to the certificate authority of your choice, and wait for their reply (they will probably require a proof that you really own the domains indicated in your certificate).
Eventually, the certificate authority will reply to you with one or more files containing the CA certificate chain, and your certificate signed by their certificate chain.
[[og-keystore-csr-import]]
==== Importing the Signed Certificate
The file you receive from the CA is typically in PEM format, and you *must* import it back into the same KeyStore file you used to generate the CSR.
You must import *both* the certificate chain and your signed certificate.
First, import the certificate chain:
[source,subs=verbatim]
----
keytool
-importcert <1>
-alias ca <2>
-file chain_from_ca.pem <3>
-keystore keystore.p12 <4>
-trustcacerts <5>
-v <6>
----
<1> the command to import certificates
<2> use the `ca` alias to differentiate from the alias of the server certificate
<3> the file containing the certificate chain received from the CA
<4> your KeyStore file
<5> specify that you trust CA certificates
<6> verbose output
Then, import the signed certificate:
----
keytool
-importcert
-file signed_certificate.pem
-keystore keystore.p12
-trustcacerts
-v
----
Now you have a trusted certificate in your KeyStore that you can use for the domains of your web applications.
// TODO: add a section about renewal?
Refer to the section about configuring xref:og-protocols-ssl[secure protocols] to configure the secure connector with your newly created KeyStore.

View File

@ -19,12 +19,12 @@
[[og-module-http]]
==== Module `http`
The `http` module provides support for the clear-text HTTP/1.1 protocol and depends on the xref:og-module-server[`server` module].
The `http` module provides the clear-text connector and support for the clear-text HTTP/1.1 protocol, and depends on the xref:og-module-server[`server` module].
The module file is `$JETTY_HOME/modules/http.mod`:
The module properties to configure the clear-text connector are:
----
include::{JETTY_HOME}/modules/http.mod[]
include::{JETTY_HOME}/modules/http.mod[tags=documentation]
----
Among the configurable properties, the most relevant are:

View File

@ -0,0 +1,29 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.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-ssl-reload]]
==== Module `ssl-reload`
The `ssl-reload` module provides a periodic scanning of the directory where the KeyStore file resides.
When the scanning detects a change to the KeyStore file, the correspondent `SslContextFactory.Server` component is reloaded with the new KeyStore configuration.
The module properties are:
----
include::{JETTY_HOME}/modules/ssl-reload.mod[tags=documentation]
----

View File

@ -19,14 +19,54 @@
[[og-module-ssl]]
==== Module `ssl`
The `ssl` module provides the secure connector, and allows you to configure the keystore properties and the TLS parameters.
The `ssl` module provides the secure connector, and allows you to configure the KeyStore properties and the TLS parameters, and depends on the xref:og-module-server[`server` module].
The module file is `$JETTY_HOME/modules/ssl.mod`:
[[og-module-ssl-connector]]
===== Secure Connector Properties
The module properties to configure the secure connector are:
----
include::{JETTY_HOME}/modules/ssl.mod[]
include::{JETTY_HOME}/modules/ssl.mod[tags=documentation-connector]
----
Among the configurable properties, the most relevant are:
TODO
`jetty.ssl.port`::
the network port that Jetty listens to for secure connections -- default `8443`.
`jetty.http.idleTimeout`::
the amount of time a connection can be idle (i.e. no bytes received and no bytes sent) until the server decides to close it to save resources -- default `30000` milliseconds.
`jetty.http.acceptors`::
the number of threads that compete to accept connections -- default -1 (i.e. an accept heuristic decides the value based on the number of cores).
Refer to xref:og-module-http-acceptors[this section] for more information about acceptor threads.
`jetty.http.selectors`::
the number of NIO selectors (with an associated thread) that manage connections -- default -1 (i.e. a select heuristic decides the value based on the number of cores).
Refer to xref:og-module-http-selectors[this section] for more information about selector threads.
The module properties to configure the KeyStore and TLS parameters are:
----
include::{JETTY_HOME}/modules/ssl.mod[tags=documentation-ssl-context]
----
[[og-module-ssl-keystore-tls]]
===== KeyStore Properties and TLS Properties
Among the configurable properties, the most relevant are:
jetty.sslContext.keyStorePath::
the KeyStore path on the file system.
If it is a relative path, it is relative to `$JETTY_BASE`.
Defaults to `$JETTY_BASE/etc/keystore.p12`.
`jetty.sslContext.keyStorePassword`::
the KeyStore password, which you want to explicitly configure.
The password may be obfuscated with the xref:og-password[Jetty Password Tool].
If you need to configure client certificate authentication, you want to configure one of these properties (they are mutually exclusive):
`jetty.sslContext.needClientAuth`::
whether client certificate authentication should be required.
`jetty.sslContext.wantClientAuth`::
whether client certificate authentication should be requested.
If you configure client certificate authentication, you need to configure and distribute a client KeyStore as explained in xref:og-keystore-client-authn[this section].

View File

@ -19,7 +19,7 @@
[[og-module-test-keystore]]
==== Module `test-keystore`
The `test-keystore` module provides a keystore containing a self-signed certificate for domain `localhost`.
The `test-keystore` module provides a KeyStore containing a self-signed certificate for domain `localhost`.
The module file is `$JETTY_HOME/modules/test-keystore.mod`:
@ -28,4 +28,4 @@ include::{JETTY_HOME}/modules/test-keystore.mod[]
----
Note how properties `jetty.sslContext.keyStorePath` and `jetty.sslContext.keyStorePassword` are configured, only if not already set (via the `?=` operator), directly in the module file, rather than in a `+*.ini+` file.
This is done to avoid that these properties accidentally overwrite a real keystore configuration.
This is done to avoid that these properties accidentally overwrite a real KeyStore configuration.

View File

@ -23,7 +23,7 @@ When you enable secure HTTP/2 you typically want to enable also secure HTTP/1.1,
You need to enable:
* the `ssl` Jetty module, which provides the secure connector and the keystore and TLS configuration
* the `ssl` Jetty module, which provides the secure connector and the KeyStore and TLS configuration
* the `http2` Jetty module, which adds ALPN handling and adds the HTTP/2 protocol to the secured connector
* optionally, the `https` Jetty module, which adds the HTTP/1.1 protocol to the secured connector
@ -33,9 +33,9 @@ Use the following command (issued from within the `$JETTY_BASE` directory):
$ java -jar $JETTY_HOME/start.jar --add-modules=ssl,http2,https
----
As when enabling the `https` Jetty module, you need a valid keystore (read xref:og-keystore[this section] to create your own keystore).
As when enabling the `https` Jetty module, you need a valid KeyStore (read xref:og-keystore[this section] to create your own KeyStore).
As a quick example, you can enable the xref:og-module-test-keystore[`test-keystore` module], that provides a keystore containing a self-signed certificate:
As a quick example, you can enable the xref:og-module-test-keystore[`test-keystore` module], that provides a KeyStore containing a self-signed certificate:
----
$ java -jar $JETTY_HOME/start.jar --add-modules=test-keystore

View File

@ -40,7 +40,7 @@ INFO : copy ${jetty.home}/modules/logging/jetty/resources/jetty-logging.propert
INFO : Base directory was modified
----
The command above enables the `ssl` module, that provides the secure network connector, the keystore configuration and TLS configuration -- for more details see xref:og-protocols-ssl[this section].
The command above enables the `ssl` module, that provides the secure network connector, the KeyStore configuration and TLS configuration -- for more details see xref:og-protocols-ssl[this section].
Then, the xref:og-module-https[`https` module] adds HTTP/1.1 as the protocol secured by TLS.
The `$JETTY_BASE` directory looks like this:
@ -55,13 +55,13 @@ $JETTY_BASE
└── ssl.ini
----
Note that the keystore file is missing, because you have to provide one with the cryptographic material you want (read xref:og-keystore[this section] to create your own keystore).
Note that the KeyStore file is missing, because you have to provide one with the cryptographic material you want (read xref:og-keystore[this section] to create your own KeyStore).
You need to configure these two properties by editing `ssl.ini`:
* `jetty.sslContext.keyStorePath`
* `jetty.sslContext.keyStorePassword`
As a quick example, you can enable the xref:og-module-test-keystore[`test-keystore` module], that provides a keystore containing a self-signed certificate:
As a quick example, you can enable the xref:og-module-test-keystore[`test-keystore` module], that provides a KeyStore containing a self-signed certificate:
----
$ java -jar $JETTY_HOME/start.jar --add-modules=test-keystore

View File

@ -19,29 +19,257 @@
[[og-protocols-ssl]]
==== Configuring Secure Protocols
Secure protocols are normal protocols such as HTTP/1.1 or WebSocket that are wrapped by the link:https://en.wikipedia.org/wiki/Transport_Layer_Security[TLS protocol].
Secure protocols are normal protocols such as HTTP/1.1, HTTP/2 or WebSocket that are wrapped by the link:https://en.wikipedia.org/wiki/Transport_Layer_Security[TLS protocol].
Any network protocol can be wrapped with TLS.
The `https` scheme used in URIs really means `tls+http/1.1` and similarly the `wss` scheme used in URIs really means `tls+websocket`, etc.
Senders wrap the underlying protocol bytes (e.g. HTTP/1.1 bytes or WebSocket bytes) with the TLS protocol, while receivers first interpret the TLS protocol to obtain the underlying protocol bytes, and then interpret the wrapped bytes.
The `https` scheme used in URIs really means `tls+http/1.1` (or `tls+http/2`) and similarly the `wss` scheme used in URIs really means `tls+websocket`, etc.
Senders wrap the underlying protocol bytes (e.g. HTTP bytes or WebSocket bytes) with the TLS protocol, while receivers first interpret the TLS protocol to obtain the underlying protocol bytes, and then interpret the wrapped bytes.
Secure protocols have a slightly more complicated configuration since they require to configure a _keystore_.
A keystore is a file on the file system that contains a private key and a public certificate, along with the certificate chain of the certificate authorities that issued the certificate.
The private key, the public certificate and the certificate chain, but more generally the items present in a keystore, are typically referred to as "cryptographic material".
Keystores may encode the cryptographic material with different encodings, the most common being link:https://en.wikipedia.org/wiki/PKCS_12[PKCS12], and are typically protected by a password.
After configuring the keystore path and keystore password, you may want to further customize the parameters of the TLS protocol, such as the minimum TLS protocol version, or the TLS algorithms, etc.
The `ssl` Jetty module allows you to configure a secure network connector -- i.e. a connector configured with the TLS protocol, the keystore and the TLS parameters; if other modules require encryption, they declare a dependency on the `ssl` module.
The xref:og-module-ssl[`ssl` Jetty module] allows you to configure a secure network connector; if other modules require encryption, they declare a dependency on the `ssl` module.
It is the job of other Jetty modules to configure the wrapped protocol.
For example, it is the xref:og-protocols-https[`https` module] that configures the wrapped protocol to be HTTP/1.1.
Similarly, it is the xref:og-protocols-http2[`http2` module] that configures the wrapped protocol to be HTTP/2.
If you enable _both_ the `https` and the `http2` module, you will have a single secure connector that will be able to interpret both HTTP/1.1 and HTTP/2.
Recall from the xref:og-modules[section about modules], that only modules that are explicitly enabled get their module configuration file (`+*.ini+`) saved in `$JETTY_BASE/start.d/`, and you want `$JETTY_BASE/start.d/ssl.ini` to be present so that you can configure the connector properties, the keystore properties and the TLS properties.
TIP: Recall from the xref:og-modules[section about modules], that only modules that are explicitly enabled get their module configuration file (`+*.ini+`) saved in `$JETTY_BASE/start.d/`, and you want `$JETTY_BASE/start.d/ssl.ini` to be present so that you can configure the connector properties, the KeyStore properties and the TLS properties.
// TODO: section about client authentication with certificates?
// See readme_keystores.txt about the fact that the server keystore needs the CA=true extension.
[[og-protocols-ssl-customize]]
===== Customizing KeyStore and TLS Configuration
Secure protocols have a slightly more complicated configuration since they require to configure a _KeyStore_.
Refer to the xref:og-keystore[KeyStore section] for more information about how to create and manage a KeyStore.
For simple cases, you only need to configure the KeyStore path and KeyStore password as explained in xref:og-module-ssl-keystore-tls[this section].
For more advanced configuration you may want to configure the TLS protocol versions, or the ciphers to include/exclude, etc.
The correct way of doing this is to create a custom xref:og-xml[Jetty XML file] and reference it in `$JETTY_BASE/start.d/ssl.ini`:
.ssl.ini
[source,subs=verbatim]
----
jetty.sslContext.keyStorePassword=my_passwd! <1>
etc/tls-config.xml <2>
----
<1> Configures the `jetty.sslContext.keyStorePassword` property with the KeyStore password.
<2> References your newly created `$JETTY_BASE/etc/tls-config.xml`.
The `ssl.ini` file above only shows the lines that are not commented out (you can leave the lines that are commented unmodified for future reference).
You want to create the `$JETTY_BASE/etc/tls-config.xml` with the following template content:
.tls-config.xml
[source,xml,subs=verbatim]
----
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_10_0.dtd">
<Configure>
<Ref refid="sslContextFactory">
... <1>
</Ref>
</Configure>
----
<1> Here goes your advanced configuration.
The `tls-config.xml` file references the `sslContextFactory` component (created by the `ssl` Jetty module) that configures the KeyStore and TLS parameters, so that you can now call its APIs via XML, and you will have full flexibility for any advanced configuration you want (see below for few examples).
Refer to the link:{JDURL}/org/eclipse/jetty/util/ssl/SslContextFactory.html[SslContextFactory javadocs] for the list of methods that you can call through the Jetty XML file.
CAUTION: Use module properties whenever possible, and only resort to use a Jetty XML file for advanced configuration that you cannot do using module properties.
[[og-protocols-ssl-customize-versions]]
====== Customizing TLS Protocol Versions
By default, the SSL protocols (SSL, SSLv2, SSLv3, etc.) are already excluded because they are vulnerable.
To explicitly add the exclusion of TLSv1.0 and TLSv1.1 (that are also vulnerable -- which leaves only TLSv1.2 and TLSv1.3 available), you want to use this XML:
.tls-config.xml
[source,xml]
----
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_10_0.dtd">
<Configure>
<Ref refid="sslContextFactory">
<Call name="addExcludeProtocols">
<Arg>
<Array type="String">
<Item>TLSv1.0</Item>
<Item>TLSv1.1</Item>
</Array>
</Arg>
</Call>
</Ref>
</Configure>
----
[[og-protocols-ssl-customize-ciphers]]
====== Customizing TLS Ciphers
You can precisely set the list of excluded ciphers, completely overriding Jetty's default, with this XML:
.tls-config.xml
[source,xml]
----
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_10_0.dtd">
<Configure>
<Ref refid="sslContextFactory">
<Set name="ExcludeCipherSuites">
<Array type="String">
<Item>^TLS_RSA_.*$</Item>
<Item>^.*_RSA_.*_(MD5|SHA|SHA1)$</Item>
<Item>^.*_DHE_RSA_.*$</Item>
<Item>SSL_RSA_WITH_DES_CBC_SHA</Item>
<Item>SSL_DHE_RSA_WITH_DES_CBC_SHA</Item>
<Item>SSL_DHE_DSS_WITH_DES_CBC_SHA</Item>
<Item>SSL_RSA_EXPORT_WITH_RC4_40_MD5</Item>
<Item>SSL_RSA_EXPORT_WITH_DES40_CBC_SHA</Item>
<Item>SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA</Item>
<Item>SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA</Item>
</Array>
</Set>
</Ref>
</Configure>
----
Note how each array item specifies a _regular expression_ that matches multiple ciphers, or specifies a precise cipher to exclude.
You can choose to create multiple XML files, and reference them all from `$JETTY_BASE/start.d/ssl.ini`, or put all your custom configurations in a single XML file.
[[og-protocols-ssl-renew]]
===== Renewing the Certificates
When you create a certificate, you must specify for how many days it is valid.
The typical validity is 90 days, and while this period may seem short, it has two advantages:
* Reduces the risk in case of compromised/stolen keys.
* Encourages automation, i.e. certificate renewal performed by automated tools (rather than manually) at scheduled times.
To renew a certificate, you must go through the xref:og-keystore-create[same steps] you followed to create the certificate the first time, and then you can xref:og-protocols-ssl-reload[reload the KeyStore] without the need to stop Jetty.
[[og-protocols-ssl-reload]]
===== Watching and Reloading the KeyStore
Jetty can be configured to monitor the directory of the KeyStore file, and reload the `SslContextFactory` component if the KeyStore file changed.
This feature can be enabled by activating the `ssl-reload` Jetty module:
----
$ java -jar $JETTY_HOME/start.jar --add-module=ssl-reload
----
For more information about the configuration of the `ssl-reload` Jetty module, see xref:og-module-ssl-reload[this section].
[[og-protocols-ssl-conscrypt]]
===== Using Conscrypt as TLS Provider
By default, the standard TLS provider that comes with the JDK is used.
The standard TLS provider from OpenJDK is implemented in Java (no native code), and its performance is not optimal, both in CPU usage and memory usage.
A faster alternative, implemented natively, is Google's link:https://github.com/google/conscrypt/[Conscrypt], which is built on link:https://boringssl.googlesource.com/boringssl/[BoringSSL], which is Google's fork of link:https://www.openssl.org/[OpenSSL].
CAUTION: As Conscrypt eventually binds to a native library, there is a higher risk that a bug in Conscrypt or in the native library causes a JVM crash, while the Java implementation will not cause a JVM crash.
To use Conscrypt as the TLS provider just enable the `conscrypt` Jetty module:
----
$ java -jar $JETTY_HOME/start.jar --add-module=conscrypt
----
[[og-protocols-ssl-sni]]
===== Configuring SNI
Server Name Indication (SNI) is a TLS extension that clients send to indicate what domain they want to connect to during the initial TLS handshake.
Modern TLS clients (e.g. browsers) always send the SNI extension; however, older TLS clients may not send the SNI extension.
Being able to handle the SNI is important when you have xref:og-deploy-virtual-hosts[virtual hosts] and a KeyStore with multiple certificates, one for each domain.
For example, you have deployed over a secure connector two web applications, both at context path `/`, one at virtual host `one.com` and one at virtual host `two.net`.
The KeyStore contains two certificates, one for `one.com` and one for `two.net`.
There are three `ssl` module properties that control the SNI behavior on the server: one that works at the TLS level, and two that works at the HTTP level.
`jetty.sslContext.sniRequired`::
This is the TLS level property, defaults to `false`.
Its behavior is explained by the following table:
.Behavior of the `jetty.sslContext.sniRequired` property
[cols="3*a"]
|===
|
| `sniRequired=false`
| `sniRequired=true`
| SNI = `null`
| client receives default certificate
| client receives TLS failure
| SNI = `wrong.org`
| client receives default certificate
| client receives TLS failure
| SNI = `one.com`
| client receives `one.com` certificate
| client receives `one.com` certificate
|===
WARNING: The _default certificate_ is the certificate returned by the TLS implementation in case there is no SNI match, and you should not rely on this certificate to be the same across Java vendors and versions, or Jetty versions, or TLS provider vendors and versions.
`jetty.ssl.sniRequired`::
Whether SNI is required at the HTTP level, defaults to `false`.
`jetty.ssl.sniHostCheck`::
Whether the SNI domain must be verified at the HTTP level against the `Host` header, defaults to `true`.
Their combined behavior is explained by the following table.
.Behavior of the `jetty.ssl.[sniRequired|sniHostCheck]` properties
[cols="5*a"]
|===
|
| `sniRequired=false` +
`sniHostCheck=false`
| `sniRequired=false` +
`sniHostCheck=true`
| `sniRequired=true` +
`sniHostCheck=false`
| `sniRequired=true` +
`sniHostCheck=true`
| SNI = `null`
| 200 OK
| 200 OK
| 400 Bad Request
| 400 Bad Request
| SNI = `wrong.org`
| 200 OK
| 400 Bad Request
| 400 Bad Request
| 400 Bad Request
| SNI = `one.com`
| 200 OK
| SNI matches `Host` header ? +
200 OK : 400 Bad Request
| 200 OK
| SNI matches `Host` header ? +
200 OK : 400 Bad Request
|===
In the normal case, with modern TLS clients and HTTP requests with a correct `Host` header, Jetty will pick the correct certificate from the KeyStore based on the SNI and respond with an HTTP 200 OK.
You may modify the default values of the SNI properties if you want stricter control over old/broken TLS clients or bad HTTP requests.

View File

@ -26,6 +26,7 @@ import org.eclipse.jetty.plus.webapp.PlusConfiguration;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.resource.JarResource;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.webapp.MetaInfConfiguration;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.xml.XmlConfiguration;
import org.slf4j.Logger;
@ -109,7 +110,7 @@ public class PreconfigureQuickStartWar
new AnnotationConfiguration());
webapp.setAttribute(QuickStartConfiguration.MODE, QuickStartConfiguration.Mode.GENERATE);
webapp.setAttribute(QuickStartConfiguration.ORIGIN_ATTRIBUTE, "");
webapp.setAttribute(MetaInfConfiguration.CONTAINER_JAR_PATTERN, ".*/jetty-servlet-api-[^/]*\\.jar$|.*/javax.servlet.jsp.jstl-.*\\.jar$|.*/org.apache.taglibs.taglibs-standard-impl-.*\\.jar$");
if (xml != null)
{
if (xml.isDirectory() || !xml.toString().toLowerCase(Locale.ENGLISH).endsWith(".xml"))

View File

@ -157,7 +157,7 @@ public class QuickStartGeneratorConfiguration extends AbstractConfiguration
webappAttr.put("xmlns", "http://xmlns.jcp.org/xml/ns/javaee");
webappAttr.put("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
webappAttr.put("xsi:schemaLocation", "http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_" + (major + "_" + minor) + ".xsd");
webappAttr.put("metadata-complete", "true");
webappAttr.put("metadata-complete", Boolean.toString(context.getMetaData().isMetaDataComplete()));
webappAttr.put("version", major + "." + minor);
XmlAppendable out = new XmlAppendable(stream, "UTF-8");

View File

@ -1,8 +1,6 @@
# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html
[description]
Enables an HTTP connector on the server.
By default HTTP/1 is support, but HTTP2C can be added to the connector by enabling the http2c module.
Enables a clear-text HTTP connector.
By default clear-text HTTP/1.1 is enabled, and clear-text HTTP/2 may be added by enabling the "http2c" module.
[tags]
connector
@ -15,43 +13,41 @@ server
etc/jetty-http.xml
[ini-template]
### HTTP Connector Configuration
# tag::documentation[]
### Clear-Text HTTP Connector Configuration
## Connector host/address to bind to
## The host/address to bind the connector to.
# jetty.http.host=0.0.0.0
## Connector port to listen on
## The port the connector listens on.
# jetty.http.port=8080
## Connector idle timeout in milliseconds
## The connector idle timeout, in milliseconds.
# jetty.http.idleTimeout=30000
## Number of acceptors (-1 picks default based on number of cores)
## The number of acceptors (-1 picks a default value based on number of cores).
# jetty.http.acceptors=-1
## Number of selectors (-1 picks default based on number of cores)
## The number of selectors (-1 picks a default value based on number of cores).
# jetty.http.selectors=-1
## ServerSocketChannel backlog (0 picks platform default)
## The ServerSocketChannel accept queue backlog (0 picks the platform default).
# jetty.http.acceptQueueSize=0
## Thread priority delta to give to acceptor threads
## The thread priority delta to give to acceptor threads.
# jetty.http.acceptorPriorityDelta=0
## The requested maximum length of the queue of incoming connections.
# jetty.http.acceptQueueSize=0
## Enable/disable the SO_REUSEADDR socket option.
## Whether to enable the SO_REUSEADDR socket option.
# jetty.http.reuseAddress=true
## Enable/disable TCP_NODELAY on accepted sockets.
## Whether to enable the TCP_NODELAY socket option on accepted sockets.
# jetty.http.acceptedTcpNoDelay=true
## The SO_RCVBUF option to set on accepted sockets. A value of -1 indicates that it is left to its default value.
## The SO_RCVBUF socket option to set on accepted sockets.
## A value of -1 indicates that the platform default is used.
# jetty.http.acceptedReceiveBufferSize=-1
## The SO_SNDBUF option to set on accepted sockets. A value of -1 indicates that it is left to its default value.
## The SO_SNDBUF socket option to set on accepted sockets.
## A value of -1 indicates that the platform default is used.
# jetty.http.acceptedSendBufferSize=-1
## Connect Timeout in milliseconds
# jetty.http.connectTimeout=15000
# end::documentation[]

View File

@ -1,7 +1,5 @@
# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html
[description]
Enables the SSL keystore to be reloaded after any changes are detected on the file system.
Enables the KeyStore to be reloaded when the KeyStore file changes.
[tags]
connector
@ -14,5 +12,7 @@ ssl
etc/jetty-ssl-context-reload.xml
[ini-template]
# Monitored directory scan period (seconds)
# tag::documentation[]
# Monitored directory scan period, in seconds.
# jetty.sslContext.reload.scanInterval=1
# end::documentation[]

View File

@ -1,8 +1,6 @@
# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html
[description]
Enables a TLS(SSL) Connector on the server.
This may be used for HTTPS and/or HTTP2 by enabling the associated support modules.
Enables a TLS (SSL) connector to support secure protocols.
Secure HTTP/1.1 is provided by enabling the "https" module and secure HTTP/2 is provided by enabling the "http2" module.
[tags]
connector
@ -17,114 +15,119 @@ etc/jetty-ssl.xml
etc/jetty-ssl-context.xml
[ini-template]
### TLS(SSL) Connector Configuration
# tag::documentation-connector[]
### TLS (SSL) Connector Configuration
## Connector host/address to bind to
## The host/address to bind the connector to.
# jetty.ssl.host=0.0.0.0
## Connector port to listen on
## The port the connector listens on.
# jetty.ssl.port=8443
## Connector idle timeout in milliseconds
## The connector idle timeout, in milliseconds.
# jetty.ssl.idleTimeout=30000
## Number of acceptors (-1 picks default based on number of cores)
## The number of acceptors (-1 picks a default value based on number of cores).
# jetty.ssl.acceptors=-1
## Number of selectors (-1 picks default based on number of cores)
## The number of selectors (-1 picks a default value based on number of cores).
# jetty.ssl.selectors=-1
## ServerSocketChannel backlog (0 picks platform default)
## The ServerSocketChannel accept queue backlog (0 picks the platform default).
# jetty.ssl.acceptQueueSize=0
## Thread priority delta to give to acceptor threads
## The thread priority delta to give to acceptor threads.
# jetty.ssl.acceptorPriorityDelta=0
## The requested maximum length of the queue of incoming connections.
# jetty.ssl.acceptQueueSize=0
## Enable/disable the SO_REUSEADDR socket option.
## Whether to enable the SO_REUSEADDR socket option.
# jetty.ssl.reuseAddress=true
## Enable/disable TCP_NODELAY on accepted sockets.
## Whether to enable the TCP_NODELAY socket option on accepted sockets.
# jetty.ssl.acceptedTcpNoDelay=true
## The SO_RCVBUF option to set on accepted sockets. A value of -1 indicates that it is left to its default value.
## The SO_RCVBUF socket option to set on accepted sockets.
## A value of -1 indicates that the platform default is used.
# jetty.ssl.acceptedReceiveBufferSize=-1
## The SO_SNDBUF option to set on accepted sockets. A value of -1 indicates that it is left to its default value.
## The SO_SNDBUF socket option to set on accepted sockets.
## A value of -1 indicates that the platform default is used.
# jetty.ssl.acceptedSendBufferSize=-1
## Connect Timeout in milliseconds
# jetty.ssl.connectTimeout=15000
## Whether SNI is required for all secure connections. Rejections are in HTTP 400 response.
## Whether client SNI data is required for all secure connections.
## When SNI is required, clients that do not send SNI data are rejected with an HTTP 400 response.
# jetty.ssl.sniRequired=false
## Whether request host names are checked to match any SNI names
## Whether client SNI data is checked to match CN and SAN in server certificates.
## When SNI is checked, if the match fails the connection is rejected with an HTTP 400 response.
# jetty.ssl.sniHostCheck=true
## max age in seconds for a Strict-Transport-Security response header (default -1)
## The max age, in seconds, for the Strict-Transport-Security response header.
# jetty.ssl.stsMaxAgeSeconds=31536000
## include subdomain property in any Strict-Transport-Security header (default false)
## Whether to include the subdomain property in any Strict-Transport-Security header.
# jetty.ssl.stsIncludeSubdomains=true
# end::documentation-connector[]
# tag::documentation-ssl-context[]
### SslContextFactory Configuration
## Note that OBF passwords are not secure, just protected from casual observation
## See https://eclipse.org/jetty/documentation/current/configuring-security-secure-passwords.html
## Note that OBF passwords are not secure, just protected from casual observation.
## Whether SNI is required for all secure connections. Rejections are in TLS handshakes.
## Whether client SNI data is required for all secure connections.
## When SNI is required, clients that do not send SNI data are rejected with a TLS handshake error.
# jetty.sslContext.sniRequired=false
## The Endpoint Identification Algorithm
## Same as javax.net.ssl.SSLParameters#setEndpointIdentificationAlgorithm(String)
#jetty.sslContext.endpointIdentificationAlgorithm=
## The Endpoint Identification Algorithm.
## Same as javax.net.ssl.SSLParameters#setEndpointIdentificationAlgorithm(String).
# jetty.sslContext.endpointIdentificationAlgorithm=
## SSL JSSE Provider
## The JSSE Provider.
# jetty.sslContext.provider=
## Keystore file path (relative to $jetty.base)
## The KeyStore file path (relative to $JETTY_BASE).
# jetty.sslContext.keyStorePath=etc/keystore.p12
## Truststore file path (relative to $jetty.base)
## The TrustStore file path (relative to $JETTY_BASE).
# jetty.sslContext.trustStorePath=etc/keystore.p12
## Keystore password
## The KeyStore password.
# jetty.sslContext.keyStorePassword=
## Keystore type and provider
## The Keystore type.
# jetty.sslContext.keyStoreType=PKCS12
## The KeyStore provider.
# jetty.sslContext.keyStoreProvider=
## KeyManager password
## The KeyManager password.
# jetty.sslContext.keyManagerPassword=
## Truststore password
## The TrustStore password.
# jetty.sslContext.trustStorePassword=
## Truststore type and provider
## The TrustStore type.
# jetty.sslContext.trustStoreType=PKCS12
## The TrustStore provider.
# jetty.sslContext.trustStoreProvider=
## whether client certificate authentication is required
## Whether client certificate authentication is required.
# jetty.sslContext.needClientAuth=false
## Whether client certificate authentication is desired
## Whether client certificate authentication is desired, but not required.
# jetty.sslContext.wantClientAuth=false
## Whether cipher order is significant (since java 8 only)
## Whether cipher order is significant.
# jetty.sslContext.useCipherSuitesOrder=true
## To configure Includes / Excludes for Cipher Suites or Protocols see tweak-ssl.xml example at
## https://www.eclipse.org/jetty/documentation/current/configuring-ssl.html#configuring-sslcontextfactory-cipherSuites
## Set the size of the SslSession cache
## The SSLSession cache size.
# jetty.sslContext.sslSessionCacheSize=-1
## Set the timeout (in seconds) of the SslSession cache timeout
## The SSLSession cache timeout (in seconds).
# jetty.sslContext.sslSessionTimeout=-1
## Allow SSL renegotiation
## Whether TLS renegotiation is allowed.
# jetty.sslContext.renegotiationAllowed=true
## The max number of TLS renegotiations per connection.
# jetty.sslContext.renegotiationLimit=5
# end::documentation-ssl-context[]

View File

@ -205,7 +205,7 @@ public class Request implements HttpServletRequest
private String _pathInContext;
private ServletPathMapping _servletPathMapping;
private boolean _secure;
private String _asyncNotSupportedSource = null;
private Object _asyncNotSupportedSource = null;
private boolean _newContext;
private boolean _cookiesExtracted = false;
private boolean _handled = false;
@ -1845,7 +1845,7 @@ public class Request implements HttpServletRequest
_requestAttributeListeners.remove(listener);
}
public void setAsyncSupported(boolean supported, String source)
public void setAsyncSupported(boolean supported, Object source)
{
_asyncNotSupportedSource = supported ? null : (source == null ? "unknown" : source);
}

View File

@ -35,6 +35,7 @@ import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.component.DumpableCollection;
@ -200,11 +201,24 @@ public class FilterHolder extends Holder<Filter>
return _filter;
}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain)
throws IOException, ServletException
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
{
_filter.doFilter(request, response, chain);
if (isAsyncSupported() || !request.isAsyncSupported())
getFilter().doFilter(request, response, chain);
else
{
Request baseRequest = Request.getBaseRequest(request);
Objects.requireNonNull(baseRequest);
try
{
baseRequest.setAsyncSupported(false, this);
getFilter().doFilter(request, response, chain);
}
finally
{
baseRequest.setAsyncSupported(true, null);
}
}
}
@Override

View File

@ -80,30 +80,22 @@ public class ListenerHolder extends BaseHolder<EventListener>
throw new IllegalStateException(msg);
}
ContextHandler contextHandler = ContextHandler.getCurrentContext().getContextHandler();
if (contextHandler != null)
ContextHandler contextHandler = null;
if (getServletHandler() != null)
contextHandler = getServletHandler().getServletContextHandler();
if (contextHandler == null && ContextHandler.getCurrentContext() != null)
contextHandler = ContextHandler.getCurrentContext().getContextHandler();
if (contextHandler == null)
throw new IllegalStateException("No Context");
_listener = getInstance();
if (_listener == null)
{
_listener = getInstance();
if (_listener == null)
{
//create an instance of the listener and decorate it
try
{
_listener = createInstance();
}
catch (ServletException ex)
{
Throwable cause = ex.getRootCause();
if (cause instanceof InstantiationException)
throw (InstantiationException)cause;
if (cause instanceof IllegalAccessException)
throw (IllegalAccessException)cause;
throw ex;
}
}
//create an instance of the listener and decorate it
_listener = createInstance();
_listener = wrap(_listener, WrapFunction.class, WrapFunction::wrapEventListener);
contextHandler.addEventListener(_listener);
}
contextHandler.addEventListener(_listener);
}
@Override

View File

@ -27,10 +27,9 @@ import java.util.EventListener;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Consumer;
import java.util.stream.Stream;
@ -62,7 +61,6 @@ import org.eclipse.jetty.server.UserIdentity;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ScopedHandler;
import org.eclipse.jetty.util.ArrayUtil;
import org.eclipse.jetty.util.LazyList;
import org.eclipse.jetty.util.MultiException;
import org.eclipse.jetty.util.MultiMap;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
@ -99,7 +97,7 @@ public class ServletHandler extends ScopedHandler
private int _matchBeforeIndex = -1; //index of last programmatic FilterMapping with isMatchAfter=false
private int _matchAfterIndex = -1; //index of 1st programmatic FilterMapping with isMatchAfter=true
private boolean _filterChainsCached = true;
private int _maxFilterChainsCacheSize = 512;
private int _maxFilterChainsCacheSize = 1024;
private boolean _startWithUnavailable = false;
private boolean _ensureDefaultServlet = true;
private IdentityService _identityService;
@ -110,6 +108,7 @@ public class ServletHandler extends ScopedHandler
private final Map<String, FilterHolder> _filterNameMap = new HashMap<>();
private List<FilterMapping> _filterPathMappings;
private MultiMap<FilterMapping> _filterNameMappings;
private List<FilterMapping> _wildFilterNameMappings;
private final Map<String, MappedServlet> _servletNameMap = new HashMap<>();
private PathMappings<MappedServlet> _servletPathMap;
@ -120,9 +119,6 @@ public class ServletHandler extends ScopedHandler
@SuppressWarnings("unchecked")
protected final ConcurrentMap<String, FilterChain>[] _chainCache = new ConcurrentMap[FilterMapping.ALL];
@SuppressWarnings("unchecked")
protected final Queue<String>[] _chainLRU = new Queue[FilterMapping.ALL];
/**
* Constructor.
*/
@ -138,7 +134,7 @@ public class ServletHandler extends ScopedHandler
@Override
public boolean isDumpable(Object o)
{
return !(o instanceof Holder || o instanceof BaseHolder || o instanceof FilterMapping || o instanceof ServletMapping);
return !(o instanceof BaseHolder || o instanceof FilterMapping || o instanceof ServletMapping);
}
@Override
@ -187,12 +183,6 @@ public class ServletHandler extends ScopedHandler
_chainCache[FilterMapping.INCLUDE] = new ConcurrentHashMap<>();
_chainCache[FilterMapping.ERROR] = new ConcurrentHashMap<>();
_chainCache[FilterMapping.ASYNC] = new ConcurrentHashMap<>();
_chainLRU[FilterMapping.REQUEST] = new ConcurrentLinkedQueue<>();
_chainLRU[FilterMapping.FORWARD] = new ConcurrentLinkedQueue<>();
_chainLRU[FilterMapping.INCLUDE] = new ConcurrentLinkedQueue<>();
_chainLRU[FilterMapping.ERROR] = new ConcurrentLinkedQueue<>();
_chainLRU[FilterMapping.ASYNC] = new ConcurrentLinkedQueue<>();
}
if (_contextHandler == null)
@ -274,14 +264,14 @@ public class ServletHandler extends ScopedHandler
}
//Retain only filters and mappings that were added using jetty api (ie Source.EMBEDDED)
FilterHolder[] fhs = (FilterHolder[])LazyList.toArray(filterHolders, FilterHolder.class);
FilterHolder[] fhs = filterHolders.toArray(new FilterHolder[0]);
updateBeans(_filters, fhs);
_filters = fhs;
FilterMapping[] fms = (FilterMapping[])LazyList.toArray(filterMappings, FilterMapping.class);
FilterMapping[] fms = filterMappings.toArray(new FilterMapping[0]);
updateBeans(_filterMappings, fms);
_filterMappings = fms;
_matchAfterIndex = (_filterMappings == null || _filterMappings.length == 0 ? -1 : _filterMappings.length - 1);
_matchAfterIndex = (_filterMappings.length == 0 ? -1 : _filterMappings.length - 1);
_matchBeforeIndex = -1;
// Stop servlets
@ -314,10 +304,10 @@ public class ServletHandler extends ScopedHandler
}
//Retain only Servlets and mappings added via jetty apis (ie Source.EMBEDDED)
ServletHolder[] shs = (ServletHolder[])LazyList.toArray(servletHolders, ServletHolder.class);
ServletHolder[] shs = servletHolders.toArray(new ServletHolder[0]);
updateBeans(_servlets, shs);
_servlets = shs;
ServletMapping[] sms = (ServletMapping[])LazyList.toArray(servletMappings, ServletMapping.class);
ServletMapping[] sms = servletMappings.toArray(new ServletMapping[0]);
updateBeans(_servletMappings, sms);
_servletMappings = sms;
@ -343,7 +333,7 @@ public class ServletHandler extends ScopedHandler
listenerHolders.add(listener);
}
}
ListenerHolder[] listeners = (ListenerHolder[])LazyList.toArray(listenerHolders, ListenerHolder.class);
ListenerHolder[] listeners = listenerHolders.toArray(new ListenerHolder[0]);
updateBeans(_listeners, listeners);
_listeners = listeners;
@ -581,85 +571,66 @@ public class ServletHandler extends ScopedHandler
return chain;
}
// Build list of filters (list of FilterHolder objects)
List<FilterHolder> filters = new ArrayList<>();
// Build the filter chain from the inside out.
// ie first wrap the servlet with the last filter to be applied.
// The mappings lists have been reversed to make this simple and fast.
FilterChain chain = null;
// Path filters
if (pathInContext != null && _filterPathMappings != null)
{
for (FilterMapping filterPathMapping : _filterPathMappings)
{
if (filterPathMapping.appliesTo(pathInContext, dispatch))
filters.add(filterPathMapping.getFilterHolder());
}
}
// Servlet name filters
if (servletHolder != null && _filterNameMappings != null && !_filterNameMappings.isEmpty())
{
Object o = _filterNameMappings.get(servletHolder.getName());
if (_wildFilterNameMappings != null)
for (FilterMapping mapping : _wildFilterNameMappings)
chain = newFilterChain(mapping.getFilterHolder(), chain == null ? new ChainEnd(servletHolder) : chain);
for (int i = 0; i < LazyList.size(o); i++)
for (FilterMapping mapping : _filterNameMappings.get(servletHolder.getName()))
{
FilterMapping mapping = LazyList.get(o, i);
if (mapping.appliesTo(dispatch))
filters.add(mapping.getFilterHolder());
}
o = _filterNameMappings.get("*");
for (int i = 0; i < LazyList.size(o); i++)
{
FilterMapping mapping = LazyList.get(o, i);
if (mapping.appliesTo(dispatch))
filters.add(mapping.getFilterHolder());
chain = newFilterChain(mapping.getFilterHolder(), chain == null ? new ChainEnd(servletHolder) : chain);
}
}
if (filters.isEmpty())
return null;
if (pathInContext != null && _filterPathMappings != null)
{
for (FilterMapping mapping : _filterPathMappings)
{
if (mapping.appliesTo(pathInContext, dispatch))
chain = newFilterChain(mapping.getFilterHolder(), chain == null ? new ChainEnd(servletHolder) : chain);
}
}
FilterChain chain = null;
if (_filterChainsCached)
{
chain = newCachedChain(filters, servletHolder);
final Map<String, FilterChain> cache = _chainCache[dispatch];
final Queue<String> lru = _chainLRU[dispatch];
// Do we have too many cached chains?
while (_maxFilterChainsCacheSize > 0 && cache.size() >= _maxFilterChainsCacheSize)
if (_maxFilterChainsCacheSize > 0 && cache.size() >= _maxFilterChainsCacheSize)
{
// The LRU list is not atomic with the cache map, so be prepared to invalidate if
// a key is not found to delete.
// Delete by LRU (where U==created)
String k = lru.poll();
if (k == null)
{
cache.clear();
break;
}
cache.remove(k);
// flush the cache
LOG.debug("{} flushed filter chain cache for {}", this, baseRequest.getDispatcherType());
cache.clear();
}
chain = chain == null ? new ChainEnd(servletHolder) : chain;
// flush the cache
LOG.debug("{} cached filter chain for {}: {}", this, baseRequest.getDispatcherType(), chain);
cache.put(key, chain);
lru.add(key);
}
else
chain = new Chain(baseRequest, filters, servletHolder);
return chain;
}
/**
* Create a FilterChain that calls the passed filter with the passed chain
* @param filterHolder The filter to invoke
* @param chain The chain to pass to the filter
* @return A FilterChain that invokes the filter with the chain
*/
protected FilterChain newFilterChain(FilterHolder filterHolder, FilterChain chain)
{
return new Chain(filterHolder, chain);
}
protected void invalidateChainsCache()
{
if (_chainLRU[FilterMapping.REQUEST] != null)
if (_chainCache[FilterMapping.REQUEST] != null)
{
_chainLRU[FilterMapping.REQUEST].clear();
_chainLRU[FilterMapping.FORWARD].clear();
_chainLRU[FilterMapping.INCLUDE].clear();
_chainLRU[FilterMapping.ERROR].clear();
_chainLRU[FilterMapping.ASYNC].clear();
_chainCache[FilterMapping.REQUEST].clear();
_chainCache[FilterMapping.FORWARD].clear();
_chainCache[FilterMapping.INCLUDE].clear();
@ -770,6 +741,29 @@ public class ServletHandler extends ScopedHandler
return _initialized;
}
protected void initializeHolders(BaseHolder<?>[] holders)
{
for (BaseHolder<?> holder : holders)
{
holder.setServletHandler(this);
if (isInitialized())
{
try
{
if (!holder.isStarted())
{
holder.start();
holder.initialize();
}
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
}
}
/**
* @return whether the filter chains are cached.
*/
@ -797,10 +791,7 @@ public class ServletHandler extends ScopedHandler
public void setListeners(ListenerHolder[] listeners)
{
if (listeners != null)
for (ListenerHolder holder : listeners)
{
holder.setServletHandler(this);
}
initializeHolders(listeners);
updateBeans(_listeners,listeners);
_listeners = listeners;
}
@ -810,18 +801,6 @@ public class ServletHandler extends ScopedHandler
return new ListenerHolder(source);
}
/**
* Create a new CachedChain
*
* @param filters the filter chain to be cached as a collection of {@link FilterHolder}
* @param servletHolder the servletHolder
* @return a new {@link CachedChain} instance
*/
public CachedChain newCachedChain(List<FilterHolder> filters, ServletHolder servletHolder)
{
return new CachedChain(filters, servletHolder);
}
/**
* Add a new servlet holder
*
@ -872,15 +851,13 @@ public class ServletHandler extends ScopedHandler
*/
public void addServletWithMapping(ServletHolder servlet, String pathSpec)
{
Objects.requireNonNull(servlet);
ServletHolder[] holders = getServlets();
if (holders != null)
holders = holders.clone();
try
{
try (AutoLock l = lock())
{
if (servlet != null && !containsServletHolder(servlet))
if (!containsServletHolder(servlet))
setServlets(ArrayUtil.addToArray(holders, servlet, ServletHolder.class));
}
@ -985,15 +962,14 @@ public class ServletHandler extends ScopedHandler
*/
public void addFilterWithMapping(FilterHolder holder, String pathSpec, EnumSet<DispatcherType> dispatches)
{
Objects.requireNonNull(holder);
FilterHolder[] holders = getFilters();
if (holders != null)
holders = holders.clone();
try
{
try (AutoLock l = lock())
{
if (holder != null && !containsFilterHolder(holder))
if (!containsFilterHolder(holder))
setFilters(ArrayUtil.addToArray(holders, holder, FilterHolder.class));
}
@ -1053,6 +1029,7 @@ public class ServletHandler extends ScopedHandler
*/
public void addFilterWithMapping(FilterHolder holder, String pathSpec, int dispatches)
{
Objects.requireNonNull(holder);
FilterHolder[] holders = getFilters();
if (holders != null)
holders = holders.clone();
@ -1061,7 +1038,7 @@ public class ServletHandler extends ScopedHandler
{
try (AutoLock l = lock())
{
if (holder != null && !containsFilterHolder(holder))
if (!containsFilterHolder(holder))
setFilters(ArrayUtil.addToArray(holders, holder, FilterHolder.class));
}
@ -1295,6 +1272,7 @@ public class ServletHandler extends ScopedHandler
{
_filterPathMappings = null;
_filterNameMappings = null;
_wildFilterNameMappings = Collections.emptyList();
}
else
{
@ -1319,6 +1297,13 @@ public class ServletHandler extends ScopedHandler
}
}
}
// Reverse filter mappings to apply as wrappers last filter wrapped first
for (Map.Entry<String, List<FilterMapping>> entry : _filterNameMappings.entrySet())
Collections.reverse(entry.getValue());
Collections.reverse(_filterPathMappings);
_wildFilterNameMappings = _filterNameMappings.get("*");
if (_wildFilterNameMappings != null)
Collections.reverse(_wildFilterNameMappings);
}
// Map servlet paths to holders
@ -1421,16 +1406,6 @@ public class ServletHandler extends ScopedHandler
LOG.debug("filterNameMap={} pathFilters={} servletFilterMap={} servletPathMap={} servletNameMap={}",
_filterNameMap, _filterPathMappings, _filterNameMappings, _servletPathMap, _servletNameMap);
}
try
{
if (_contextHandler != null && _contextHandler.isStarted() || _contextHandler == null && isStarted())
initialize();
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
}
@ -1489,7 +1464,7 @@ public class ServletHandler extends ScopedHandler
{
updateBeans(_filterMappings,filterMappings);
_filterMappings = filterMappings;
if (isStarted())
if (isRunning())
updateMappings();
invalidateChainsCache();
}
@ -1499,12 +1474,8 @@ public class ServletHandler extends ScopedHandler
try (AutoLock l = lock())
{
if (holders != null)
{
for (FilterHolder holder : holders)
{
holder.setServletHandler(this);
}
}
initializeHolders(holders);
updateBeans(_filters,holders);
_filters = holders;
updateNameMappings();
@ -1519,7 +1490,7 @@ public class ServletHandler extends ScopedHandler
{
updateBeans(_servletMappings,servletMappings);
_servletMappings = servletMappings;
if (isStarted())
if (isRunning())
updateMappings();
invalidateChainsCache();
}
@ -1534,12 +1505,7 @@ public class ServletHandler extends ScopedHandler
try (AutoLock l = lock())
{
if (holders != null)
{
for (ServletHolder holder : holders)
{
holder.setServletHandler(this);
}
}
initializeHolders(holders);
updateBeans(_servlets,holders);
_servlets = holders;
updateNameMappings();
@ -1547,159 +1513,6 @@ public class ServletHandler extends ScopedHandler
}
}
protected class CachedChain implements FilterChain
{
FilterHolder _filterHolder;
CachedChain _next;
ServletHolder _servletHolder;
/**
* @param filters list of {@link FilterHolder} objects
* @param servletHolder the current {@link ServletHolder}
*/
protected CachedChain(List<FilterHolder> filters, ServletHolder servletHolder)
{
if (!filters.isEmpty())
{
_filterHolder = filters.get(0);
filters.remove(0);
_next = new CachedChain(filters, servletHolder);
}
else
_servletHolder = servletHolder;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException
{
final Request baseRequest = Request.getBaseRequest(request);
// pass to next filter
if (_filterHolder != null)
{
if (LOG.isDebugEnabled())
LOG.debug("call filter {}", _filterHolder);
//if the request already does not support async, then the setting for the filter
//is irrelevant. However if the request supports async but this filter does not
//temporarily turn it off for the execution of the filter
if (baseRequest.isAsyncSupported() && !_filterHolder.isAsyncSupported())
{
try
{
baseRequest.setAsyncSupported(false, _filterHolder.toString());
_filterHolder.doFilter(request, response, _next);
}
finally
{
baseRequest.setAsyncSupported(true, null);
}
}
else
_filterHolder.doFilter(request, response, _next);
return;
}
// Call servlet
HttpServletRequest srequest = (HttpServletRequest)request;
if (_servletHolder == null)
notFound(baseRequest, srequest, (HttpServletResponse)response);
else
{
if (LOG.isDebugEnabled())
LOG.debug("call servlet {}", _servletHolder);
_servletHolder.handle(baseRequest, request, response);
}
}
@Override
public String toString()
{
if (_filterHolder != null)
return _filterHolder + "->" + _next.toString();
if (_servletHolder != null)
return _servletHolder.toString();
return "null";
}
}
private class Chain implements FilterChain
{
final Request _baseRequest;
final List<FilterHolder> _chain;
final ServletHolder _servletHolder;
int _filter = 0;
private Chain(Request baseRequest, List<FilterHolder> filters, ServletHolder servletHolder)
{
_baseRequest = baseRequest;
_chain = filters;
_servletHolder = servletHolder;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException
{
if (LOG.isDebugEnabled())
LOG.debug("doFilter {}", _filter);
// pass to next filter
if (_filter < _chain.size())
{
FilterHolder holder = _chain.get(_filter++);
if (LOG.isDebugEnabled())
LOG.debug("call filter {}", holder);
//if the request already does not support async, then the setting for the filter
//is irrelevant. However if the request supports async but this filter does not
//temporarily turn it off for the execution of the filter
if (!holder.isAsyncSupported() && _baseRequest.isAsyncSupported())
{
try
{
_baseRequest.setAsyncSupported(false, holder.toString());
holder.doFilter(request, response, this);
}
finally
{
_baseRequest.setAsyncSupported(true, null);
}
}
else
holder.doFilter(request, response, this);
return;
}
// Call servlet
HttpServletRequest srequest = (HttpServletRequest)request;
if (_servletHolder == null)
notFound(Request.getBaseRequest(request), srequest, (HttpServletResponse)response);
else
{
if (LOG.isDebugEnabled())
LOG.debug("call servlet {}", _servletHolder);
_servletHolder.handle(_baseRequest, request, response);
}
}
@Override
public String toString()
{
StringBuilder b = new StringBuilder();
for (FilterHolder f : _chain)
{
b.append(f.toString());
b.append("->");
}
b.append(_servletHolder);
return b.toString();
}
}
/**
* @return The maximum entries in a filter chain cache.
*/
@ -1810,4 +1623,52 @@ public class ServletHandler extends ScopedHandler
resp.sendError(HttpServletResponse.SC_NOT_FOUND);
}
}
static class Chain implements FilterChain
{
private final FilterHolder _filterHolder;
private final FilterChain _filterChain;
Chain(FilterHolder filter, FilterChain chain)
{
_filterHolder = filter;
_filterChain = chain;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException
{
_filterHolder.doFilter(request, response, _filterChain);
}
@Override
public String toString()
{
return String.format("Chain@%x(%s)->%s", hashCode(), _filterHolder, _filterChain);
}
}
static class ChainEnd implements FilterChain
{
private final ServletHolder _servletHolder;
ChainEnd(ServletHolder holder)
{
_servletHolder = holder;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException
{
Request baseRequest = Request.getBaseRequest(request);
Objects.requireNonNull(baseRequest);
_servletHolder.handle(baseRequest, request, response);
}
@Override
public String toString()
{
return String.format("ChainEnd@%x(%s)", hashCode(), _servletHolder);
}
}
}

View File

@ -2007,38 +2007,179 @@ public class ServletContextHandlerTest
}
}
public static class TestPListener implements ServletRequestListener
{
@Override
public void requestInitialized(ServletRequestEvent sre)
{
ServletRequest request = sre.getServletRequest();
Integer count = (Integer)request.getAttribute("testRequestListener");
request.setAttribute("testRequestListener", count == null ? 1 : count + 1);
}
@Override
public void requestDestroyed(ServletRequestEvent sre)
{
}
}
@Test
public void testProgrammaticFilterServlet() throws Exception
public void testProgrammaticListener() throws Exception
{
ServletContextHandler context = new ServletContextHandler();
ServletHandler handler = new ServletHandler();
_server.setHandler(context);
context.setHandler(handler);
handler.addServletWithMapping(new ServletHolder(new TestServlet()), "/");
// Add a servlet to report number of listeners
handler.addServletWithMapping(new ServletHolder(new HttpServlet()
{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
resp.getOutputStream().print("Listeners=" + req.getAttribute("testRequestListener"));
}
}), "/");
// Add a listener in STOPPED, STARTING and STARTED states
handler.addListener(new ListenerHolder(TestPListener.class));
handler.addServlet(new ServletHolder(new HttpServlet()
{
@Override
public void init() throws ServletException
{
handler.addListener(new ListenerHolder(TestPListener.class));
}
})
{
{
setInitOrder(1);
}
});
_server.start();
handler.addListener(new ListenerHolder(TestPListener.class));
String request =
"GET /hello HTTP/1.0\n" +
"Host: localhost\n" +
"\n";
"GET /test HTTP/1.0\n" +
"Host: localhost\n" +
"\n";
String response = _connector.getResponse(request);
assertThat(response, containsString("200 OK"));
assertThat(response, containsString("Test"));
assertThat(response, containsString("Listeners=3"));
}
handler.addFilterWithMapping(new FilterHolder(new MyFilter()), "/*", EnumSet.of(DispatcherType.REQUEST));
handler.addServletWithMapping(new ServletHolder(new HelloServlet()), "/hello/*");
public static class TestPFilter implements Filter
{
@Override
public void init(FilterConfig filterConfig) throws ServletException
{
}
_server.dumpStdErr();
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
{
Integer count = (Integer)request.getAttribute("testFilter");
request.setAttribute("testFilter", count == null ? 1 : count + 1);
chain.doFilter(request, response);
}
request =
"GET /hello HTTP/1.0\n" +
"Host: localhost\n" +
"\n";
@Override
public void destroy()
{
}
}
@Test
public void testProgrammaticFilters() throws Exception
{
ServletContextHandler context = new ServletContextHandler();
ServletHandler handler = new ServletHandler();
_server.setHandler(context);
context.setHandler(handler);
// Add a servlet to report number of filters
handler.addServletWithMapping(new ServletHolder(new HttpServlet()
{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
resp.getOutputStream().print("Filters=" + req.getAttribute("testFilter"));
}
}), "/");
// Add a filter in STOPPED, STARTING and STARTED states
handler.addFilterWithMapping(new FilterHolder(TestPFilter.class), "/*", EnumSet.of(DispatcherType.REQUEST));
handler.addServlet(new ServletHolder(new HttpServlet()
{
@Override
public void init() throws ServletException
{
handler.addFilterWithMapping(new FilterHolder(TestPFilter.class), "/*", EnumSet.of(DispatcherType.REQUEST));
}
})
{
{
setInitOrder(1);
}
});
_server.start();
handler.addFilterWithMapping(new FilterHolder(TestPFilter.class), "/*", EnumSet.of(DispatcherType.REQUEST));
String request =
"GET /test HTTP/1.0\n" +
"Host: localhost\n" +
"\n";
String response = _connector.getResponse(request);
assertThat(response, containsString("200 OK"));
assertThat(response, containsString("Filters=3"));
}
public static class TestPServlet extends HttpServlet
{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
resp.getOutputStream().println(req.getRequestURI());
}
}
@Test
public void testProgrammaticServlets() throws Exception
{
ServletContextHandler context = new ServletContextHandler();
ServletHandler handler = new ServletHandler();
_server.setHandler(context);
context.setHandler(handler);
// Add a filter in STOPPED, STARTING and STARTED states
handler.addServletWithMapping(new ServletHolder(TestPServlet.class), "/one");
handler.addServlet(new ServletHolder(new HttpServlet()
{
@Override
public void init() throws ServletException
{
handler.addServletWithMapping(new ServletHolder(TestPServlet.class), "/two");
}
})
{
{
setInitOrder(1);
}
});
_server.start();
handler.addServletWithMapping(new ServletHolder(TestPServlet.class), "/three");
String request = "GET /one HTTP/1.0\n" + "Host: localhost\n" + "\n";
String response = _connector.getResponse(request);
assertThat(response, containsString("200 OK"));
assertThat(response, containsString("/one"));
request = "GET /two HTTP/1.0\n" + "Host: localhost\n" + "\n";
response = _connector.getResponse(request);
assertThat(response, containsString("200 OK"));
assertThat(response, containsString("filter: filter"));
assertThat(response, containsString("Hello World"));
assertThat(response, containsString("/two"));
request = "GET /three HTTP/1.0\n" + "Host: localhost\n" + "\n";
response = _connector.getResponse(request);
assertThat(response, containsString("200 OK"));
assertThat(response, containsString("/three"));
}
}

View File

@ -22,18 +22,23 @@ import java.net.URI;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import jakarta.servlet.http.HttpServlet;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.eclipse.jetty.websocket.server.JettyWebSocketServerContainer;
import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer;
import org.eclipse.jetty.websocket.util.server.WebSocketUpgradeFilter;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ -45,8 +50,17 @@ public class JettyWebSocketFilterTest
private WebSocketClient client;
private ServletContextHandler contextHandler;
@BeforeEach
public void start() throws Exception
public void start(JettyWebSocketServletContainerInitializer.Configurator configurator) throws Exception
{
start(configurator, null);
}
public void start(ServletHolder servletHolder) throws Exception
{
start(null, servletHolder);
}
public void start(JettyWebSocketServletContainerInitializer.Configurator configurator, ServletHolder servletHolder) throws Exception
{
server = new Server();
connector = new ServerConnector(server);
@ -54,9 +68,11 @@ public class JettyWebSocketFilterTest
contextHandler = new ServletContextHandler(ServletContextHandler.SESSIONS);
contextHandler.setContextPath("/");
if (servletHolder != null)
contextHandler.addServlet(servletHolder, "/");
server.setHandler(contextHandler);
JettyWebSocketServletContainerInitializer.configure(contextHandler, null);
JettyWebSocketServletContainerInitializer.configure(contextHandler, configurator);
server.start();
client = new WebSocketClient();
@ -70,9 +86,37 @@ public class JettyWebSocketFilterTest
server.stop();
}
@Test
public void testWebSocketUpgradeFilter() throws Exception
{
start((context, container) -> container.addMapping("/", EchoSocket.class));
// After mapping is added we have an UpgradeFilter.
assertThat(contextHandler.getServletHandler().getFilters().length, is(1));
FilterHolder filterHolder = contextHandler.getServletHandler().getFilter("WebSocketUpgradeFilter");
assertNotNull(filterHolder);
assertThat(filterHolder.getState(), is(AbstractLifeCycle.STARTED));
assertThat(filterHolder.getFilter(), instanceOf(WebSocketUpgradeFilter.class));
// Test we can upgrade to websocket and send a message.
URI uri = URI.create("ws://localhost:" + connector.getLocalPort() + "/filterPath");
EventSocket socket = new EventSocket();
CompletableFuture<Session> connect = client.connect(socket, uri);
try (Session session = connect.get(5, TimeUnit.SECONDS))
{
session.getRemote().sendString("hello world");
}
assertTrue(socket.closeLatch.await(10, TimeUnit.SECONDS));
String msg = socket.textMessages.poll();
assertThat(msg, is("hello world"));
}
@Test
public void testLazyWebSocketUpgradeFilter() throws Exception
{
start(null, null);
// JettyWebSocketServerContainer has already been created.
JettyWebSocketServerContainer container = JettyWebSocketServerContainer.getContainer(contextHandler.getServletContext());
assertNotNull(container);
@ -83,6 +127,47 @@ public class JettyWebSocketFilterTest
// After mapping is added we have an UpgradeFilter.
container.addMapping("/", EchoSocket.class);
assertThat(contextHandler.getServletHandler().getFilters().length, is(1));
FilterHolder filterHolder = contextHandler.getServletHandler().getFilter("WebSocketUpgradeFilter");
assertNotNull(filterHolder);
assertThat(filterHolder.getState(), is(AbstractLifeCycle.STARTED));
assertThat(filterHolder.getFilter(), instanceOf(WebSocketUpgradeFilter.class));
// Test we can upgrade to websocket and send a message.
URI uri = URI.create("ws://localhost:" + connector.getLocalPort() + "/filterPath");
EventSocket socket = new EventSocket();
CompletableFuture<Session> connect = client.connect(socket, uri);
try (Session session = connect.get(5, TimeUnit.SECONDS))
{
session.getRemote().sendString("hello world");
}
assertTrue(socket.closeLatch.await(10, TimeUnit.SECONDS));
String msg = socket.textMessages.poll();
assertThat(msg, is("hello world"));
}
@Test
public void testWebSocketUpgradeFilterWhileStarting() throws Exception
{
start(new ServletHolder(new HttpServlet()
{
@Override
public void init()
{
JettyWebSocketServerContainer container = JettyWebSocketServerContainer.getContainer(getServletContext());
if (container == null)
throw new IllegalArgumentException("Missing JettyWebSocketServerContainer");
container.addMapping("/", EchoSocket.class);
}
}));
// After mapping is added we have an UpgradeFilter.
assertThat(contextHandler.getServletHandler().getFilters().length, is(1));
FilterHolder filterHolder = contextHandler.getServletHandler().getFilter("WebSocketUpgradeFilter");
assertNotNull(filterHolder);
assertThat(filterHolder.getState(), is(AbstractLifeCycle.STARTED));
assertThat(filterHolder.getFilter(), instanceOf(WebSocketUpgradeFilter.class));
// Test we can upgrade to websocket and send a message.
URI uri = URI.create("ws://localhost:" + connector.getLocalPort() + "/filterPath");

View File

@ -21,6 +21,7 @@ package org.eclipse.jetty.websocket.util.server;
import java.io.IOException;
import java.time.Duration;
import java.util.EnumSet;
import java.util.Objects;
import jakarta.servlet.DispatcherType;
import jakarta.servlet.Filter;
@ -37,6 +38,7 @@ import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.thread.AutoLock;
import org.eclipse.jetty.websocket.core.Configuration;
import org.eclipse.jetty.websocket.core.server.WebSocketServerComponents;
import org.eclipse.jetty.websocket.util.server.internal.WebSocketMapping;
@ -74,10 +76,12 @@ import org.slf4j.LoggerFactory;
public class WebSocketUpgradeFilter implements Filter, Dumpable
{
private static final Logger LOG = LoggerFactory.getLogger(WebSocketUpgradeFilter.class);
private static final AutoLock LOCK = new AutoLock();
private static FilterHolder getFilter(ServletContext servletContext)
{
ServletHandler servletHandler = ContextHandler.getContextHandler(servletContext).getChildHandlerByClass(ServletHandler.class);
ContextHandler contextHandler = Objects.requireNonNull(ContextHandler.getContextHandler(servletContext));
ServletHandler servletHandler = contextHandler.getChildHandlerByClass(ServletHandler.class);
for (FilterHolder holder : servletHandler.getFilters())
{
@ -105,22 +109,27 @@ public class WebSocketUpgradeFilter implements Filter, Dumpable
*/
public static FilterHolder ensureFilter(ServletContext servletContext)
{
FilterHolder existingFilter = WebSocketUpgradeFilter.getFilter(servletContext);
if (existingFilter != null)
return existingFilter;
// Lock in case two concurrent requests are initializing the filter lazily.
try (AutoLock l = LOCK.lock())
{
FilterHolder existingFilter = WebSocketUpgradeFilter.getFilter(servletContext);
if (existingFilter != null)
return existingFilter;
final String name = "WebSocketUpgradeFilter";
final String pathSpec = "/*";
FilterHolder holder = new FilterHolder(new WebSocketUpgradeFilter());
holder.setName(name);
holder.setInitParameter(MAPPING_ATTRIBUTE_INIT_PARAM, WebSocketMapping.DEFAULT_KEY);
final String name = "WebSocketUpgradeFilter";
final String pathSpec = "/*";
FilterHolder holder = new FilterHolder(new WebSocketUpgradeFilter());
holder.setName(name);
holder.setInitParameter(MAPPING_ATTRIBUTE_INIT_PARAM, WebSocketMapping.DEFAULT_KEY);
holder.setAsyncSupported(true);
ServletHandler servletHandler = ContextHandler.getContextHandler(servletContext).getChildHandlerByClass(ServletHandler.class);
servletHandler.addFilterWithMapping(holder, pathSpec, EnumSet.of(DispatcherType.REQUEST));
if (LOG.isDebugEnabled())
LOG.debug("Adding {} mapped to {} in {}", holder, pathSpec, servletContext);
return holder;
holder.setAsyncSupported(true);
ContextHandler contextHandler = Objects.requireNonNull(ContextHandler.getContextHandler(servletContext));
ServletHandler servletHandler = contextHandler.getChildHandlerByClass(ServletHandler.class);
servletHandler.addFilterWithMapping(holder, pathSpec, EnumSet.of(DispatcherType.REQUEST));
if (LOG.isDebugEnabled())
LOG.debug("Adding {} mapped to {} in {}", holder, pathSpec, servletContext);
return holder;
}
}
public static final String MAPPING_ATTRIBUTE_INIT_PARAM = "org.eclipse.jetty.websocket.util.server.internal.WebSocketMapping.key";

View File

@ -465,7 +465,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.1.1</version>
<version>3.3.0</version>
<dependencies>
<dependency>
<groupId>org.eclipse.jetty.toolchain</groupId>

View File

@ -61,11 +61,13 @@ public class CDITests extends AbstractJettyHomeTest
return Stream.of(
// -- Weld --
// Uses test-weld-cdi-webapp
Arguments.of("weld", "cdi-spi", null), // Weld >= 3.1.2
Arguments.of("weld", "decorate", null), // Weld >= 3.1.2
Arguments.of("weld", "cdi-decorate", null), // Weld >= 3.1.3
// -- Apache OpenWebBeans --
// Uses test-owb-cdi-webapp
Arguments.of("owb", "cdi-spi", null)
// Arguments.of("owb", "decorate", null), // Not supported
// Arguments.of("owb", "cdi-decorate", null) // Not supported

View File

@ -139,14 +139,16 @@ public class QuickStartTest
}
server.setHandler(webapp);
server.start();
URL url = new URL("http://127.0.0.1:" + server.getBean(NetworkConnector.class).getLocalPort() + "/");
URL url = new URL("http://127.0.0.1:" + server.getBean(NetworkConnector.class).getLocalPort() + "/test/");
HttpURLConnection connection = (HttpURLConnection)url.openConnection();
connection.setRequestMethod("POST");
assertEquals(200, connection.getResponseCode());
assertThat(IO.toString((InputStream)connection.getContent()), Matchers.containsString("Test Specification WebApp"));
String content = IO.toString((InputStream)connection.getContent());
assertThat(content, Matchers.containsString("Results"));
assertThat(content, Matchers.not(Matchers.containsString("FAIL")));
server.stop();
}

View File

@ -13,6 +13,7 @@
<properties>
<bundle-symbolic-name>${project.groupId}.cdi.owb</bundle-symbolic-name>
<openwebbeans.version>2.0.18</openwebbeans.version>
</properties>
<build>
@ -59,12 +60,12 @@
<dependency>
<groupId>org.apache.openwebbeans</groupId>
<artifactId>openwebbeans-web</artifactId>
<version>2.0.15</version>
<version>${openwebbeans.version}</version>
</dependency>
<dependency>
<groupId>org.apache.openwebbeans</groupId>
<artifactId>openwebbeans-jetty9</artifactId>
<version>2.0.15</version>
<version>${openwebbeans.version}</version>
</dependency>
</dependencies>
</project>

View File

@ -1,11 +1,15 @@
<?xml version="1.0"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<display-name>OWB CDI Integration Test WebApp</display-name>
<!-- OWB Listener is added by OwbServletContainerInitializer -->
<!-- Required by org.apache.webbeans.servlet.WebBeansConfigurationListener$Auto -->
<context-param>
<param-name>openwebbeans.web.sci.active</param-name>
<param-value>true</param-value>
</context-param>
<resource-env-ref>
<description>Object factory for the CDI Bean Manager</description>