diff --git a/Jenkinsfile b/Jenkinsfile
index 7d5820e08803..bc747e8b6cc2 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -113,7 +113,7 @@ def mavenBuild(jdk, cmdline, mvnName) {
"MAVEN_OPTS=-Xms2g -Xmx4g -Djava.awt.headless=true"]) {
configFileProvider(
[configFile(fileId: 'oss-settings.xml', variable: 'GLOBAL_MVN_SETTINGS')]) {
- sh "mvn --no-transfer-progress -s $GLOBAL_MVN_SETTINGS -Dmaven.repo.local=.repository -Pci -V -B -e -Djetty.testtracker.log=true $cmdline -Dunix.socket.tmp=/tmp/unixsocket"
+ sh "mvn --no-transfer-progress -s $GLOBAL_MVN_SETTINGS -Dmaven.repo.local=.repository -Pci -V -B -e -Djetty.testtracker.log=true $cmdline"
}
}
}
diff --git a/Jenkinsfile-autobahn b/Jenkinsfile-autobahn
index 0944e587368d..0e92efd217d6 100644
--- a/Jenkinsfile-autobahn
+++ b/Jenkinsfile-autobahn
@@ -80,7 +80,7 @@ def mavenBuild(jdk, cmdline, mvnName, junitPublishDisabled) {
mavenOpts: mavenOpts,
mavenLocalRepo: localRepo) {
// Some common Maven command line + provided command line
- sh "mvn -Pci -V -B -e -fae -Dmaven.test.failure.ignore=true -Djetty.testtracker.log=true $cmdline -Dunix.socket.tmp=" + env.JENKINS_HOME
+ sh "mvn -Pci -V -B -e -fae -Dmaven.test.failure.ignore=true -Djetty.testtracker.log=true $cmdline"
}
}
diff --git a/VERSION.txt b/VERSION.txt
index 405b0ed27831..372baea126a7 100644
--- a/VERSION.txt
+++ b/VERSION.txt
@@ -9,7 +9,7 @@ jetty-10.0.6 - 29 June 2021
+ 6410 Ensure Jetty IO uses SocketAddress instead of InetSocketAddress
+ 6418 Bad and/or missing Require-Capability for osgi.serviceloader
+ 6425 Update to asm 9.1
- + 6447 Deprecate support for UTF16 encoding in URIs
+ + 6447 Deprecate support for UTF16 encoding in URIs (Resolves CVE-2021-34429)
+ 6451 Request#getServletPath() returns null for ROOT mapping
+ 6464 Wrong files/lib definitions in certain *-capture.mod files?
+ 6473 Improve alias checking in PathResource
diff --git a/apache-jsp/pom.xml b/apache-jsp/pom.xml
index 460dd96a1ccc..7bc846eb24f5 100644
--- a/apache-jsp/pom.xml
+++ b/apache-jsp/pom.xml
@@ -80,12 +80,6 @@
org.mortbay.jasperapache-jsp
-
- org.eclipse.jetty
- jetty-annotations
- ${project.version}
-
-
org.eclipse.jettyjetty-servlet
diff --git a/demos/demo-simple-webapp/src/main/webapp/WEB-INF/web.xml b/demos/demo-simple-webapp/src/main/webapp/WEB-INF/web.xml
index da1263c1b8d7..79d16dcd8500 100644
--- a/demos/demo-simple-webapp/src/main/webapp/WEB-INF/web.xml
+++ b/demos/demo-simple-webapp/src/main/webapp/WEB-INF/web.xml
@@ -6,4 +6,10 @@
Simple Web Application
+
+
+ icon
+ image/vnd.microsoft.icon
+
+
diff --git a/demos/demo-simple-webapp/src/main/webapp/jetty.icon b/demos/demo-simple-webapp/src/main/webapp/jetty.icon
new file mode 100644
index 000000000000..54e2e6104332
Binary files /dev/null and b/demos/demo-simple-webapp/src/main/webapp/jetty.icon differ
diff --git a/demos/demo-simple-webapp/src/main/webapp/jetty.png b/demos/demo-simple-webapp/src/main/webapp/jetty.png
new file mode 100644
index 000000000000..d579fffddfe1
Binary files /dev/null and b/demos/demo-simple-webapp/src/main/webapp/jetty.png differ
diff --git a/demos/demo-simple-webapp/src/main/webapp/jetty.webp b/demos/demo-simple-webapp/src/main/webapp/jetty.webp
new file mode 100644
index 000000000000..2d1bfea3ef79
Binary files /dev/null and b/demos/demo-simple-webapp/src/main/webapp/jetty.webp differ
diff --git a/documentation/jetty-asciidoctor-extensions/src/main/java/org/eclipse/jetty/docs/JettyIncludeExtension.java b/documentation/jetty-asciidoctor-extensions/src/main/java/org/eclipse/jetty/docs/JettyIncludeExtension.java
index f9ace37ef6d7..903f6920a5eb 100644
--- a/documentation/jetty-asciidoctor-extensions/src/main/java/org/eclipse/jetty/docs/JettyIncludeExtension.java
+++ b/documentation/jetty-asciidoctor-extensions/src/main/java/org/eclipse/jetty/docs/JettyIncludeExtension.java
@@ -144,6 +144,7 @@ private String captureOutput(Document document, Map attributes,
.map(line -> redact(line, run.getConfig().getMavenLocalRepository(), "/path/to/maven.repository"))
.map(line -> redact(line, run.getConfig().getJettyHome().toString(), "/path/to/jetty.home"))
.map(line -> redact(line, run.getConfig().getJettyBase().toString(), "/path/to/jetty.base"))
+ .map(line -> regexpRedact(line, "(^| )[^ ]+/etc/jetty-halt\\.xml", ""))
.map(line -> redact(line, (String)document.getAttribute("project-version"), (String)document.getAttribute("version")));
lines = replace(lines, (String)attributes.get("replace"));
lines = delete(lines, (String)attributes.get("delete"));
@@ -160,6 +161,13 @@ private String redact(String line, String target, String replacement)
return line;
}
+ private String regexpRedact(String line, String regexp, String replacement)
+ {
+ if (regexp != null && replacement != null)
+ return line.replaceAll(regexp, replacement);
+ return line;
+ }
+
private Stream replace(Stream lines, String replace)
{
if (replace == null)
@@ -178,8 +186,7 @@ private Stream delete(Stream lines, String delete)
if (delete == null)
return lines;
Pattern regExp = Pattern.compile(delete);
- return lines.filter(line -> !regExp.matcher(line).find())
- .filter(line -> !line.contains("jetty-halt.xml"));
+ return lines.filter(line -> !regExp.matcher(line).find());
}
private Stream denoteLineStart(Stream lines)
diff --git a/documentation/jetty-documentation/pom.xml b/documentation/jetty-documentation/pom.xml
index 01d3db94affd..48dd43ee6834 100644
--- a/documentation/jetty-documentation/pom.xml
+++ b/documentation/jetty-documentation/pom.xml
@@ -201,6 +201,11 @@
http2-http-client-transport${project.version}
+
+ org.eclipse.jetty
+ jetty-unixdomain-server
+ ${project.version}
+ org.eclipse.jettyjetty-slf4j-impl
diff --git a/documentation/jetty-documentation/src/main/asciidoc/operations-guide/deploy/deploy-virtual-hosts.adoc b/documentation/jetty-documentation/src/main/asciidoc/operations-guide/deploy/deploy-virtual-hosts.adoc
index 7e63f26756ad..9412f3da1f93 100644
--- a/documentation/jetty-documentation/src/main/asciidoc/operations-guide/deploy/deploy-virtual-hosts.adoc
+++ b/documentation/jetty-documentation/src/main/asciidoc/operations-guide/deploy/deploy-virtual-hosts.adoc
@@ -41,8 +41,8 @@ A wildcard domain name which will match only one level of arbitrary subdomains.
An IP address may be set as a virtual host to indicate that a web application should handle requests received on the network interface with that IP address for protocols that do not indicate a host name such as HTTP/0.9 or HTTP/1.0.
`@ConnectorName`::
-A Jetty `ServerConnector` name to indicate that a web application should handle requests received on the `ServerConnector` with that name, and therefore received on a specific IP port.
-A `ServerConnector` name can be set via link:{javadoc-url}/org/eclipse/jetty/server/AbstractConnector.html#setName(java.lang.String)[].
+A Jetty server `Connector` name to indicate that a web application should handle requests received on the server `Connector` with that name, and therefore received on a specific socket address (either an IP port for `ServerConnector`, or a Unix-Domain path for `UnixDomainServerConnector`).
+A server `Connector` name can be set via link:{javadoc-url}/org/eclipse/jetty/server/AbstractConnector.html#setName(java.lang.String)[].
`www.√integral.com`::
Non-ASCII and https://en.wikipedia.org/wiki/Internationalized_domain_name[IDN] domain names can be set as virtual hosts using https://en.wikipedia.org/wiki/Punycode[Puny Code] equivalents that may be obtained from a https://www.punycoder.com/[Punycode/IDN converters].
@@ -134,7 +134,7 @@ To achieve this, you simply use the same context path of `/` for each of your we
[[og-deploy-virtual-hosts-port]]
===== Different Port, Different Web Application
-Sometimes it is required to serve different web applications from different IP ports, and therefore from different ``ServerConnector``s.
+Sometimes it is required to serve different web applications from different socket addresses (either different IP ports, or different Unix-Domain paths), and therefore from different server ``Connector``s.
For example, you want requests to `+http://localhost:8080/+` to be served by one web application, but requests to `+http://localhost:9090/+` to be served by another web application.
diff --git a/documentation/jetty-documentation/src/main/asciidoc/operations-guide/modules/modules-custom.adoc b/documentation/jetty-documentation/src/main/asciidoc/operations-guide/modules/modules-custom.adoc
index 826570e42e16..15d23edc5d2d 100644
--- a/documentation/jetty-documentation/src/main/asciidoc/operations-guide/modules/modules-custom.adoc
+++ b/documentation/jetty-documentation/src/main/asciidoc/operations-guide/modules/modules-custom.adoc
@@ -131,7 +131,7 @@ In the cases where you need to enhance Jetty with a custom functionality, you ca
For example, let's assume that you need to add a custom auditing component that integrates with the auditing tools used by your company.
This custom auditing component should measure the HTTP request processing times and record them (how they are recorded is irrelevant here -- could be in a local log file or sent via network to an external service).
-The Jetty libraries already provide a way to measure HTTP request processing times via xref:{prog-guide}#pg-server-http-channel-events[`HttpChannel` events]: you write a custom component that implements the `HttpChannel.Listener` interface and add it as a bean to the `ServerConnector` that receives the HTTP requests.
+The Jetty libraries already provide a way to measure HTTP request processing times via xref:{prog-guide}#pg-server-http-channel-events[`HttpChannel` events]: you write a custom component that implements the `HttpChannel.Listener` interface and add it as a bean to the server `Connector` that receives the HTTP requests.
The steps to create a Jetty module are similar to those necessary to xref:og-modules-custom-modify[modify an existing module]:
diff --git a/documentation/jetty-documentation/src/main/asciidoc/operations-guide/modules/modules.adoc b/documentation/jetty-documentation/src/main/asciidoc/operations-guide/modules/modules.adoc
index 67c414717672..2b020bac4bb2 100644
--- a/documentation/jetty-documentation/src/main/asciidoc/operations-guide/modules/modules.adoc
+++ b/documentation/jetty-documentation/src/main/asciidoc/operations-guide/modules/modules.adoc
@@ -396,6 +396,8 @@ When the `[exec]` section is present, the JVM running the Jetty start mechanism
This is necessary because JVM options such as `-Xmx` (that specifies the max JVM heap size) cannot be changed in a running JVM.
For an example, see xref:og-start-configure-custom-module-exec[this section].
+You can avoid that the Jetty start mechanism forks the second JVM, as explained in xref:og-start-configure-dry-run[this section].
+
[[og-modules-directive-jpms]]
===== [jpms]
diff --git a/documentation/jetty-documentation/src/main/asciidoc/operations-guide/start/start-configure.adoc b/documentation/jetty-documentation/src/main/asciidoc/operations-guide/start/start-configure.adoc
index c6814ce45d63..e711d9018eae 100644
--- a/documentation/jetty-documentation/src/main/asciidoc/operations-guide/start/start-configure.adoc
+++ b/documentation/jetty-documentation/src/main/asciidoc/operations-guide/start/start-configure.adoc
@@ -163,10 +163,15 @@ $ java -jar $JETTY_HOME/start.jar --add-modules=jvm
Since the module defines an `[exec]` section, it will fork _another_ JVM when Jetty is started.
-This means that when you start Jetty, there will be _two_ JVMs running: one spawned by you when you run `java -jar $JETTY_HOME/start.jar`, and another spawned by the Jetty start mechanism with the JVM options you specified (that cannot be applied to an already running JVM).
+This means that when you start Jetty, there will be _two_ JVMs running: one created by you when you run `java -jar $JETTY_HOME/start.jar`, and another forked by the Jetty start mechanism with the JVM options you specified (that cannot be applied to an already running JVM).
Again, you can xref:og-start-configure-dry-run[display the JVM command line] to verify that it is correct.
+[TIP]
+====
+The second JVM forked by the Jetty start mechanism when one of the modules requires forking, for example a module that contains an `[exec]` section, may not be desirable, and may be avoided as explained in xref:og-start-configure-dry-run[this section].
+====
+
[[og-start-configure-display]]
===== Displaying the Configuration
@@ -205,7 +210,28 @@ Some option, such as `--jpms`, imply `--exec`, as it won't be possible to modify
To start Jetty without forking a second JVM, the `--dry-run` option can be used to generate a command line that is then executed so that starting Jetty only spawns one JVM.
-The `--dry-run` option is quite flexible and below you can find a few examples of how to use it to generate scripts or to create an arguments file that can be passed to the `java` executable.
+IMPORTANT: You can use the `--dry-run` option as explained below to avoid forking a second JVM when using modules that have the `[exec]` section, or the `--exec` option, or when using the `--jpms` option.
+
+For example, using the `--dry-run` option with the `jvm.mod` introduced in xref:og-start-configure-custom-module-exec[this section] produces the following command line:
+
+----
+$ java -jar $JETTY_HOME/start.jar --dry-run
+----
+
+[source,options=nowrap]
+----
+include::jetty[setupModules="src/main/asciidoc/operations-guide/start/jvm.mod",setupArgs="--add-modules=http,jvm",args="--dry-run",replace="( ),$1\\\n"]
+----
+
+You can then run the generated command line.
+
+For example, in the Linux `bash` shell you can run it by wrapping it into `$(\...)`:
+
+----
+$ $(java -jar $JETTY_HOME/start.jar --dry-run)
+----
+
+The `--dry-run` option is quite flexible and below you can find a few examples of how to use it to avoid forking a second JVM, or generating scripts or creating an arguments file that can be passed to (a possibly alternative) `java` executable.
To display the `java` executable used to start Jetty:
@@ -304,7 +330,13 @@ $ java -jar $JETTY_HOME/start.jar --dry-run=##opts,path,main,args## > /tmp/jvm_c
$ /some/other/java @/tmp/jvm_cmd_line.txt
----
-Alternatively, they can be combined in a shell script:
+Using `--dry-run=opts,path,main,args` can be used to avoid that the Jetty start mechanism forks a second JVM when using modules that require forking:
+
+----
+$ java $(java -jar $JETTY_HOME/start.jar --dry-run=opts,path,main,args)
+----
+
+The output of different `--dry-run` executions can be creatively combined in a shell script:
[source,subs=quotes]
----
diff --git a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/arch-io.adoc b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/arch-io.adoc
index 5b2b8a6a995c..23f4de0d797d 100644
--- a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/arch-io.adoc
+++ b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/arch-io.adoc
@@ -26,7 +26,7 @@ Each `ManagedSelector` wraps an instance of `java.nio.channels.Selector` that in
NOTE: TODO: add image
-`SocketChannel` instances can be created by network clients when connecting to a server and by a network server when accepting connections from network clients.
+`SocketChannel` instances can be created by clients when connecting to a server and by a server when accepting connections from clients.
In both cases the `SocketChannel` instance is passed to `SelectorManager` (which passes it to `ManagedSelector` and eventually to `java.nio.channels.Selector`) to be registered for use within Jetty.
It is possible for an application to create the `SocketChannel` instances outside Jetty, even perform some initial network traffic also outside Jetty (for example for authentication purposes), and then pass the `SocketChannel` instance to `SelectorManager` for use within Jetty.
@@ -50,7 +50,7 @@ include::{doc_code}/org/eclipse/jetty/docs/programming/SelectorManagerDocs.java[
``SocketChannel``s that are passed to `SelectorManager` are wrapped into two related components: an link:{javadoc-url}/org/eclipse/jetty/io/EndPoint.html[`EndPoint`] and a link:{javadoc-url}/org/eclipse/jetty/io/Connection.html[`Connection`].
-`EndPoint` is the Jetty abstraction for a `SocketChannel`: you can read bytes from an `EndPoint` via `EndPoint.fill(ByteBuffer)`, you can write bytes to an `EndPoint` via `EndPoint.flush(ByteBuffer...)` and `EndPoint.write(Callback, ByteBuffer...)`, you can close an `EndPoint` via `EndPoint.close()`, etc.
+`EndPoint` is the Jetty abstraction for a `SocketChannel`: you can read bytes from an `EndPoint` via `EndPoint.fill(ByteBuffer)`, you can write bytes to an `EndPoint` via `EndPoint.flush(ByteBuffer\...)` and `EndPoint.write(Callback, ByteBuffer\...)`, you can close an `EndPoint` via `EndPoint.close()`, etc.
`Connection` is the Jetty abstraction that is responsible to read bytes from the `EndPoint` and to deserialize the read bytes into objects.
For example, an HTTP/1.1 server-side `Connection` implementation is responsible to deserialize HTTP/1.1 request bytes into an HTTP request object.
@@ -63,9 +63,9 @@ The writing side for a specific protocol _may_ be implemented in the `Connection
While there is primarily just one implementation of `EndPoint`,link:{javadoc-url}/org/eclipse/jetty/io/SocketChannelEndPoint.html[`SocketChannelEndPoint`] (used both on the client-side and on the server-side), there are many implementations of `Connection`, typically two for each protocol (one for the client-side and one for the server-side).
The `EndPoint` and `Connection` pairs can be chained, for example in case of encrypted communication using the TLS protocol.
-There is an `EndPoint` and `Connection` TLS pair where the `EndPoint` reads the encrypted bytes from the network and the `Connection` decrypts them; next in the chain there is an `EndPoint` and `Connection` pair where the `EndPoint` "reads" decrypted bytes (provided by the previous `Connection`) and the `Connection` deserializes them into specific protocol objects (for example HTTP/2 frame objects).
+There is an `EndPoint` and `Connection` TLS pair where the `EndPoint` reads the encrypted bytes from the socket and the `Connection` decrypts them; next in the chain there is an `EndPoint` and `Connection` pair where the `EndPoint` "reads" decrypted bytes (provided by the previous `Connection`) and the `Connection` deserializes them into specific protocol objects (for example HTTP/2 frame objects).
-Certain protocols, such as WebSocket, start the communication with the server using one protocol (e.g. HTTP/1.1), but then change the communication to use another protocol (e.g. WebSocket).
+Certain protocols, such as WebSocket, start the communication with the server using one protocol (for example, HTTP/1.1), but then change the communication to use another protocol (for example, WebSocket).
`EndPoint` supports changing the `Connection` object on-the-fly via `EndPoint.upgrade(Connection)`.
This allows to use the HTTP/1.1 `Connection` during the initial communication and later to replace it with a WebSocket `Connection`.
@@ -75,9 +75,9 @@ NOTE: TODO: add a section on `UpgradeFrom` and `UpgradeTo`?
Creating `Connection` instances is performed on the server-side by link:{javadoc-url}/org/eclipse/jetty/server/ConnectionFactory.html[`ConnectionFactory`]s and on the client-side by link:{javadoc-url}/org/eclipse/jetty/io/ClientConnectionFactory.html[`ClientConnectionFactory`]s
-On the server-side, the component that aggregates a `SelectorManager` with a set of ``ConnectionFactory``s is link:{javadoc-url}/org/eclipse/jetty/server/ServerConnector.html[`ServerConnector`]s, see xref:pg-server-io-arch[].
+On the server-side, the component that aggregates a `SelectorManager` with a set of ``ConnectionFactory``s is link:{javadoc-url}/org/eclipse/jetty/server/ServerConnector.html[`ServerConnector`]s for TCP/IP sockets, and link:{JDURL}/org/eclipse/jetty/unixdomain/server/UnixDomainServerConnector.html[`UnixDomainServerConnector`] for Unix-Domain sockets (see the xref:pg-server-io-arch[server-side architecture section] for more information).
-On the client-side, the components that aggregates a `SelectorManager` with a set of ``ClientConnectionFactory``s are link:{javadoc-url}/org/eclipse/jetty/client/HttpClientTransport.html[`HttpClientTransport`] subclasses, see xref:pg-client-io-arch[].
+On the client-side, the components that aggregates a `SelectorManager` with a set of ``ClientConnectionFactory``s are link:{javadoc-url}/org/eclipse/jetty/client/HttpClientTransport.html[`HttpClientTransport`] subclasses (see the xref:pg-client-io-arch[client-side architecture section] for more information).
[[pg-arch-io-endpoint]]
==== Jetty I/O: `EndPoint`
@@ -86,7 +86,7 @@ The Jetty I/O library use Java NIO to handle I/O, so that I/O is non-blocking.
At the Java NIO level, in order to be notified when a `SocketChannel` has data to be read, the `SelectionKey.OP_READ` flag must be set.
-In the Jetty I/O library, you can call `EndPoint.fillInterested(Callback)` to declare interest in the "read" (or "fill") event, and the `Callback` parameter is the object that is notified when such an event occurs.
+In the Jetty I/O library, you can call `EndPoint.fillInterested(Callback)` to declare interest in the "read" (also called "fill") event, and the `Callback` parameter is the object that is notified when such an event occurs.
At the Java NIO level, a `SocketChannel` is always writable, unless it becomes TCP congested.
In order to be notified when a `SocketChannel` uncongests and it is therefore writable again, the `SelectionKey.OP_WRITE` flag must be set.
diff --git a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/server/http/server-http-connector.adoc b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/server/http/server-http-connector.adoc
index a736a9ddfd7f..767c94a82d3b 100644
--- a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/server/http/server-http-connector.adoc
+++ b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/server/http/server-http-connector.adoc
@@ -16,20 +16,31 @@
A `Connector` is the component that handles incoming requests from clients, and works in conjunction with `ConnectionFactory` instances.
-The primary implementation is `org.eclipse.jetty.server.ServerConnector`.
-`ServerConnector` uses a `java.nio.channels.ServerSocketChannel` to listen to a TCP port and to accept TCP connections.
+The available implementations are:
-Since `ServerConnector` wraps a `ServerSocketChannel`, it can be configured in a similar way, for example the port to listen to, the network address to bind to, etc.:
+* `org.eclipse.jetty.server.ServerConnector`, for TCP/IP sockets.
+* `org.eclipse.jetty.unixdomain.server.UnixDomainServerConnector` for Unix-Domain sockets.
+
+Both use a `java.nio.channels.ServerSocketChannel` to listen to a socket address and to accept socket connections.
+
+Since `ServerConnector` wraps a `ServerSocketChannel`, it can be configured in a similar way, for example the IP port to listen to, the IP address to bind to, etc.:
[source,java,indent=0]
----
include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/http/HTTPServerDocs.java[tags=configureConnector]
----
-The _acceptors_ are threads (typically only one) that compete to accept TCP connections on the listening port.
+Likewise, `UnixDomainServerConnector` also wraps a `ServerSocketChannel` and can be configured with the Unix-Domain path to listen to:
+
+[source,java,indent=0]
+----
+include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/http/HTTPServerDocs.java[tags=configureConnectorUnix]
+----
+
+The _acceptors_ are threads (typically only one) that compete to accept socket connections.
When a connection is accepted, `ServerConnector` wraps the accepted `SocketChannel` and passes it to the xref:pg-arch-io-selector-manager[`SelectorManager`].
Therefore, there is a little moment where the acceptor thread is not accepting new connections because it is busy wrapping the just accepted connection to pass it to the `SelectorManager`.
-Connections that are ready to be accepted but are not accepted yet are queued in a bounded queue (at the OS level) whose capacity can be configured with the `ServerConnector.acceptQueueSize` parameter.
+Connections that are ready to be accepted but are not accepted yet are queued in a bounded queue (at the OS level) whose capacity can be configured with the `acceptQueueSize` parameter.
If your application must withstand a very high rate of connections opened, configuring more than one acceptor thread may be beneficial: when one acceptor thread accepts one connection, another acceptor thread can take over accepting connections.
@@ -42,7 +53,7 @@ In this case a single selector may be able to manage many sockets because chance
On the contrary, web messaging applications tend to send many small messages at a very high frequency so that sockets are rarely idle.
In this case a single selector may be able to manage less sockets because chances are that many of them will be active at the same time.
-It is possible to configure more than one `ServerConnector`, each listening on a different port:
+It is possible to configure more than one `ServerConnector` (each listening on a different port), or more than one `UnixDomainServerConnector` (each listening on a different path), or ``ServerConnector``s and ``UnixDomainServerConnector``s, for example:
[source,java,indent=0]
----
@@ -52,9 +63,9 @@ include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/http/HTTPSer
[[pg-server-http-connector-protocol]]
===== Configuring Protocols
-For each accepted TCP connection, `ServerConnector` asks a `ConnectionFactory` to create a `Connection` object that handles the network traffic on that TCP connection, parsing and generating bytes for a specific protocol (see xref:pg-arch-io[this section] for more details about `Connection` objects).
+For each accepted socket connection, the server `Connector` asks a `ConnectionFactory` to create a `Connection` object that handles the traffic on that socket connection, parsing and generating bytes for a specific protocol (see xref:pg-arch-io[this section] for more details about `Connection` objects).
-A `ServerConnector` can be configured with one or more ``ConnectionFactory``s.
+A server `Connector` can be configured with one or more ``ConnectionFactory``s.
If no `ConnectionFactory` is specified then `HttpConnectionFactory` is implicitly configured.
[[pg-server-http-connector-protocol-http11]]
@@ -87,7 +98,7 @@ By using those ports, a client had _prior knowledge_ that the server would speak
HTTP/2 was designed to be a smooth transition from HTTP/1.1 for users and as such the HTTP ports were not changed.
However the HTTP/2 protocol is, on the wire, a binary protocol, completely different from HTTP/1.1.
-Therefore, with HTTP/2, clients that connect to port `80` may speak either HTTP/1.1 or HTTP/2, and the server must figure out which version of the HTTP protocol the client is speaking.
+Therefore, with HTTP/2, clients that connect to port `80` (or to a specific Unix-Domain path) may speak either HTTP/1.1 or HTTP/2, and the server must figure out which version of the HTTP protocol the client is speaking.
Jetty can support both HTTP/1.1 and HTTP/2 on the same clear-text port by configuring both the HTTP/1.1 and the HTTP/2 ``ConnectionFactory``s:
@@ -128,7 +139,7 @@ Note also that the default protocol set in the ALPN ``ConnectionFactory``, which
It is often the case that Jetty receives connections from a load balancer configured to distribute the load among many Jetty backend servers.
-From the Jetty point of view, all the connections arrive from the load balancer, rather than the real clients, but is possible to configure the load balancer to forward the real client IP address and port to the backend Jetty server using the link:https://www.haproxy.org/download/2.1/doc/proxy-protocol.txt[PROXY protocol].
+From the Jetty point of view, all the connections arrive from the load balancer, rather than the real clients, but is possible to configure the load balancer to forward the real client IP address and IP port to the backend Jetty server using the link:https://www.haproxy.org/download/2.1/doc/proxy-protocol.txt[PROXY protocol].
NOTE: The PROXY protocol is widely supported by load balancers such as link:http://cbonte.github.io/haproxy-dconv/2.2/configuration.html#5.2-send-proxy[HAProxy] (via its `send-proxy` directive), link:https://docs.nginx.com/nginx/admin-guide/load-balancer/using-proxy-protocol[Nginx](via its `proxy_protocol on` directive) and others.
@@ -144,3 +155,13 @@ Note also how the PROXY `ConnectionFactory` needs to know its _next_ protocol (i
Each `ConnectionFactory` is asked to create a `Connection` object for each accepted TCP connection; the `Connection` objects will be chained together to handle the bytes, each for its own protocol.
Therefore the `ProxyConnection` will handle the PROXY protocol bytes and `HttpConnection` will handle the HTTP/1.1 bytes producing a request object and response object that will be processed by ``Handler``s.
+
+The load balancer may be configured to communicate with Jetty backend servers via Unix-Domain sockets (requires Java 16 or later).
+For example:
+
+[source,java,indent=0]
+----
+include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/http/HTTPServerDocs.java[tags=proxyHTTPUnix]
+----
+
+Note that the only difference when using Unix-Domain sockets is instantiating `UnixDomainServerConnector` instead of `ServerConnector` and configuring the Unix-Domain path instead of the IP port.
diff --git a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/server/server-io-arch.adoc b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/server/server-io-arch.adoc
index 3e98026afcc3..ab6c9b8bd010 100644
--- a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/server/server-io-arch.adoc
+++ b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/server/server-io-arch.adoc
@@ -12,23 +12,30 @@
//
[[pg-server-io-arch]]
-=== Server Libraries I/O Architecture
+=== Server I/O Architecture
The Jetty server libraries provide the basic components and APIs to implement a network server.
They build on the common xref:pg-arch-io[Jetty I/O Architecture] and provide server specific concepts.
-The central I/O server-side component is `org.eclipse.jetty.server.ServerConnector`.
+The Jetty server libraries provide I/O support for TCP/IP sockets (for both IPv4 and IPv6) and, when using Java 16 or later, for Unix-Domain sockets.
+
+Support for Unix-Domain sockets is interesting when Jetty is deployed behind a proxy or a load-balancer: it is possible to configure the proxy or load balancer to communicate with Jetty via Unix-Domain sockets, rather than via the loopback network interface.
+
+The central I/O server-side component are `org.eclipse.jetty.server.ServerConnector`, that handles the TCP/IP socket traffic, and `org.eclipse.jetty.unixdomain.server.UnixDomainServerConnector`, that handles the Unix-Domain socket traffic.
+
+`ServerConnector` and `UnixDomainServerConnector` are very similar, and while in the following sections `ServerConnector` is used, the same concepts apply to `UnixDomainServerConnector`, unless otherwise noted.
+
A `ServerConnector` manages a list of ``ConnectionFactory``s, that indicate what protocols the connector is able to speak.
[[pg-server-io-arch-connection-factory]]
==== Creating Connections with `ConnectionFactory`
-Recall from the xref:pg-arch-io-connection[`Connection` section] of the Jetty I/O architecture that `Connection` instances are responsible for parsing bytes read from a TCP connection and generating bytes to write to that TCP connection.
+Recall from the xref:pg-arch-io-connection[`Connection` section] of the Jetty I/O architecture that `Connection` instances are responsible for parsing bytes read from a socket and generating bytes to write to that socket.
On the server-side, a `ConnectionFactory` creates `Connection` instances that know how to parse and generate bytes for the specific protocol they support -- it can be either HTTP/1.1, or TLS, or FastCGI, or the link:https://www.haproxy.org/download/2.1/doc/proxy-protocol.txt[PROXY protocol].
-For example, this is how clear-text HTTP/1.1 is configured:
+For example, this is how clear-text HTTP/1.1 is configured for TCP/IP sockets:
[source,java,indent=0]
----
@@ -36,7 +43,24 @@ include::../{doc_code}/org/eclipse/jetty/docs/programming/server/ServerDocs.java
----
With this configuration, the `ServerConnector` will listen on port `8080`.
-When a new TCP connection is established, `ServerConnector` delegates to the `ConnectionFactory` the creation of the `Connection` instance for that TCP connection, that is linked to the corresponding `EndPoint`:
+
+Similarly, this is how clear-text HTTP/1.1 is configured for Unix-Domain sockets:
+
+[source,java,indent=0]
+----
+include::../{doc_code}/org/eclipse/jetty/docs/programming/server/ServerDocs.java[tags=httpUnix]
+----
+
+With this configuration, the `UnixDomainServerConnector` will listen on file `/tmp/jetty.sock`.
+
+[NOTE]
+====
+`ServerConnector` and `UnixDomainServerConnector` only differ by how they are configured -- for `ServerConnector` you specify the IP port it listens to, for `UnixDomainServerConnector` you specify the Unix-Domain path it listens to.
+
+Both configure ``ConnectionFactory``s in exactly the same way.
+====
+
+When a new socket connection is established, `ServerConnector` delegates to the `ConnectionFactory` the creation of the `Connection` instance for that socket connection, that is linked to the corresponding `EndPoint`:
[plantuml]
----
@@ -53,12 +77,12 @@ scale 1.5
circle network
circle application
-network - SocketEndPoint
-SocketEndPoint - HttpConnection
+network - SocketChannelEndPoint
+SocketChannelEndPoint - HttpConnection
HttpConnection - application
----
-For every TCP connection there will be an `EndPoint` + `Connection` pair.
+For every socket connection there will be an `EndPoint` + `Connection` pair.
[[pg-server-io-arch-connection-factory-wrapping]]
==== Wrapping a `ConnectionFactory`
@@ -72,7 +96,7 @@ include::../{doc_code}/org/eclipse/jetty/docs/programming/server/ServerDocs.java
----
With this configuration, the `ServerConnector` will listen on port `8443`.
-When a new TCP connection is established, the first `ConnectionFactory` configured in `ServerConnector` is invoked to create a `Connection`.
+When a new socket connection is established, the first `ConnectionFactory` configured in `ServerConnector` is invoked to create a `Connection`.
In the example above, `SslConnectionFactory` creates a `SslConnection` and then asks to its wrapped `ConnectionFactory` (in the example, `HttpConnectionFactory`) to create the wrapped `Connection` (an `HttpConnection`) and will then link the two ``Connection``s together, in this way:
[plantuml]
@@ -90,16 +114,16 @@ scale 1.5
circle network
circle application
-network - SocketEndPoint
-SocketEndPoint - SslConnection
+network - SocketChannelEndPoint
+SocketChannelEndPoint - SslConnection
SslConnection -- DecryptedEndPoint
DecryptedEndPoint - HttpConnection
HttpConnection - application
----
-Bytes read by the `SocketEndPoint` will be interpreted as TLS bytes by the `SslConnection`, then decrypted and made available to the `DecryptedEndPoint` (a component part of `SslConnection`), which will then provide them to `HttpConnection`.
+Bytes read by the `SocketChannelEndPoint` will be interpreted as TLS bytes by the `SslConnection`, then decrypted and made available to the `DecryptedEndPoint` (a component part of `SslConnection`), which will then provide them to `HttpConnection`.
-The application writes bytes through the `HttpConnection` to the `DecryptedEndPoint`, which will encrypt them through the `SslConnection` and write the encrypted bytes to the `SocketEndPoint`.
+The application writes bytes through the `HttpConnection` to the `DecryptedEndPoint`, which will encrypt them through the `SslConnection` and write the encrypted bytes to the `SocketChannelEndPoint`.
[[pg-server-io-arch-connection-factory-detecting]]
==== Choosing `ConnectionFactory` via Bytes Detection
@@ -124,18 +148,18 @@ With this configuration, the detector will delegate to `SslConnectionFactory` to
<2> Creates the `ServerConnector` with `DetectorConnectionFactory` as the first `ConnectionFactory`, and `HttpConnectionFactory` as the next `ConnectionFactory` to invoke if the detection fails.
In the example above `ServerConnector` will listen on port 8181.
-When a new TCP connection is established, `DetectorConnectionFactory` is invoked to create a `Connection`, because it is the first `ConnectionFactory` specified in the `ServerConnector` list.
+When a new socket connection is established, `DetectorConnectionFactory` is invoked to create a `Connection`, because it is the first `ConnectionFactory` specified in the `ServerConnector` list.
`DetectorConnectionFactory` reads the initial bytes and asks to its detecting ``ConnectionFactory``s if they recognize the bytes.
In the example above, the detecting ``ConnectionFactory`` is `SslConnectionFactory` which will therefore detect whether the initial bytes are TLS bytes.
If one of the detecting ``ConnectionFactory``s recognizes the bytes, it creates a `Connection`; otherwise `DetectorConnectionFactory` will try the next `ConnectionFactory` after itself in the `ServerConnector` list.
In the example above, the next `ConnectionFactory` after `DetectorConnectionFactory` is `HttpConnectionFactory`.
-The final result is that when new TCP connection is established, the initial bytes are examined: if they are TLS bytes, a `SslConnectionFactory` will create a `SslConnection` that wraps an `HttpConnection` as explained xref:pg-server-io-arch-connection-factory-wrapping[here], therefore supporting `https`; otherwise they are not TLS bytes and an `HttpConnection` is created, therefore supporting `http`.
+The final result is that when new socket connection is established, the initial bytes are examined: if they are TLS bytes, a `SslConnectionFactory` will create a `SslConnection` that wraps an `HttpConnection` as explained xref:pg-server-io-arch-connection-factory-wrapping[here], therefore supporting `https`; otherwise they are not TLS bytes and an `HttpConnection` is created, therefore supporting `http`.
[[pg-server-io-arch-connection-factory-custom]]
==== Writing a Custom `ConnectionFactory`
-This section explains how to use the Jetty server-side libraries to write a generic network server able to parse and generate any protocol based on TCP.
+This section explains how to use the Jetty server-side libraries to write a generic network server able to parse and generate any protocol..
Let's suppose that we want to write a custom protocol that is based on JSON but has the same semantic as HTTP; let's call this custom protocol `JSONHTTP`, so that a request would look like this:
diff --git a/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/server/ServerDocs.java b/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/server/ServerDocs.java
index 5c063daf3428..6829c6d19719 100644
--- a/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/server/ServerDocs.java
+++ b/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/server/ServerDocs.java
@@ -14,6 +14,7 @@
package org.eclipse.jetty.docs.programming.server;
import java.nio.ByteBuffer;
+import java.nio.file.Path;
import java.util.Map;
import java.util.concurrent.Executor;
@@ -31,6 +32,7 @@
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SslConnectionFactory;
+import org.eclipse.jetty.unixdomain.server.UnixDomainServerConnector;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.IteratingCallback;
@@ -57,6 +59,23 @@ public void http() throws Exception
// end::http[]
}
+ public void httpUnix() throws Exception
+ {
+ // tag::httpUnix[]
+ // Create the HTTP/1.1 ConnectionFactory.
+ HttpConnectionFactory http = new HttpConnectionFactory();
+
+ Server server = new Server();
+
+ // Create the connector with the ConnectionFactory.
+ UnixDomainServerConnector connector = new UnixDomainServerConnector(server, http);
+ connector.setUnixDomainPath(Path.of("/tmp/jetty.sock"));
+
+ server.addConnector(connector);
+ server.start();
+ // end::httpUnix[]
+ }
+
public void tlsHttp() throws Exception
{
// tag::tlsHttp[]
diff --git a/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/server/http/HTTPServerDocs.java b/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/server/http/HTTPServerDocs.java
index 103d24402cfd..6aaa1e43a519 100644
--- a/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/server/http/HTTPServerDocs.java
+++ b/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/server/http/HTTPServerDocs.java
@@ -14,6 +14,7 @@
package org.eclipse.jetty.docs.programming.server.http;
import java.io.IOException;
+import java.nio.file.Path;
import java.util.EnumSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@@ -62,6 +63,7 @@
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.servlets.CrossOriginFilter;
+import org.eclipse.jetty.unixdomain.server.UnixDomainServerConnector;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceCollection;
@@ -163,14 +165,15 @@ public void configureConnector() throws Exception
int selectors = 1;
// Create a ServerConnector instance.
- ServerConnector connector = new ServerConnector(server, 1, 1, new HttpConnectionFactory());
+ ServerConnector connector = new ServerConnector(server, acceptors, selectors, new HttpConnectionFactory());
- // Configure TCP parameters.
+ // Configure TCP/IP parameters.
- // The TCP port to listen to.
+ // The port to listen to.
connector.setPort(8080);
- // The TCP address to bind to.
+ // The address to bind to.
connector.setHost("127.0.0.1");
+
// The TCP accept queue size.
connector.setAcceptQueueSize(128);
@@ -179,6 +182,33 @@ public void configureConnector() throws Exception
// end::configureConnector[]
}
+ public void configureConnectorUnix() throws Exception
+ {
+ // tag::configureConnectorUnix[]
+ Server server = new Server();
+
+ // The number of acceptor threads.
+ int acceptors = 1;
+
+ // The number of selectors.
+ int selectors = 1;
+
+ // Create a ServerConnector instance.
+ UnixDomainServerConnector connector = new UnixDomainServerConnector(server, acceptors, selectors, new HttpConnectionFactory());
+
+ // Configure Unix-Domain parameters.
+
+ // The Unix-Domain path to listen to.
+ connector.setUnixDomainPath(Path.of("/tmp/jetty.sock"));
+
+ // The TCP accept queue size.
+ connector.setAcceptQueueSize(128);
+
+ server.addConnector(connector);
+ server.start();
+ // end::configureConnectorUnix[]
+ }
+
public void configureConnectors() throws Exception
{
// tag::configureConnectors[]
@@ -248,6 +278,31 @@ public void proxyHTTP() throws Exception
// end::proxyHTTP[]
}
+ public void proxyHTTPUnix() throws Exception
+ {
+ // tag::proxyHTTPUnix[]
+ Server server = new Server();
+
+ // The HTTP configuration object.
+ HttpConfiguration httpConfig = new HttpConfiguration();
+ // Configure the HTTP support, for example:
+ httpConfig.setSendServerVersion(false);
+
+ // The ConnectionFactory for HTTP/1.1.
+ HttpConnectionFactory http11 = new HttpConnectionFactory(httpConfig);
+
+ // The ConnectionFactory for the PROXY protocol.
+ ProxyConnectionFactory proxy = new ProxyConnectionFactory(http11.getProtocol());
+
+ // Create the ServerConnector.
+ UnixDomainServerConnector connector = new UnixDomainServerConnector(server, proxy, http11);
+ connector.setUnixDomainPath(Path.of("/tmp/jetty.sock"));
+
+ server.addConnector(connector);
+ server.start();
+ // end::proxyHTTPUnix[]
+ }
+
public void tlsHttp11() throws Exception
{
// tag::tlsHttp11[]
diff --git a/jetty-client/pom.xml b/jetty-client/pom.xml
index 7317069eb0e0..67702b3a5777 100644
--- a/jetty-client/pom.xml
+++ b/jetty-client/pom.xml
@@ -132,6 +132,11 @@
${project.version}test
+
+ org.awaitility
+ awaitility
+ test
+ org.apache.kerbykerb-simplekdc
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractHttpClientTransport.java b/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractHttpClientTransport.java
index 4fd7f4c72e27..2ed04c31a020 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractHttpClientTransport.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractHttpClientTransport.java
@@ -25,7 +25,7 @@
@ManagedObject
public abstract class AbstractHttpClientTransport extends ContainerLifeCycle implements HttpClientTransport
{
- protected static final Logger LOG = LoggerFactory.getLogger(HttpClientTransport.class);
+ private static final Logger LOG = LoggerFactory.getLogger(HttpClientTransport.class);
private HttpClient client;
private ConnectionPool.Factory factory;
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java
index 2e89a292a4d4..8eb987c34946 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java
@@ -161,7 +161,7 @@ public HttpClient(HttpClientTransport transport)
{
this.transport = Objects.requireNonNull(transport);
addBean(transport);
- this.connector = ((AbstractHttpClientTransport)transport).getBean(ClientConnector.class);
+ this.connector = ((AbstractHttpClientTransport)transport).getContainedBeans(ClientConnector.class).stream().findFirst().orElseThrow();
addBean(handlers);
addBean(decoderFactories);
}
@@ -553,24 +553,25 @@ public List getDestinations()
return new ArrayList<>(destinations.values());
}
- protected void send(final HttpRequest request, List listeners)
+ protected void send(HttpRequest request, List listeners)
{
HttpDestination destination = (HttpDestination)resolveDestination(request);
destination.send(request, listeners);
}
- protected void newConnection(final HttpDestination destination, final Promise promise)
+ protected void newConnection(HttpDestination destination, Promise promise)
{
+ // Multiple threads may access the map, especially with DEBUG logging enabled.
+ Map context = new ConcurrentHashMap<>();
+ context.put(ClientConnectionFactory.CLIENT_CONTEXT_KEY, HttpClient.this);
+ context.put(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY, destination);
+
Origin.Address address = destination.getConnectAddress();
resolver.resolve(address.getHost(), address.getPort(), new Promise<>()
{
@Override
public void succeeded(List socketAddresses)
{
- // Multiple threads may access the map, especially with DEBUG logging enabled.
- Map context = new ConcurrentHashMap<>();
- context.put(ClientConnectionFactory.CLIENT_CONTEXT_KEY, HttpClient.this);
- context.put(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY, destination);
connect(socketAddresses, 0, context);
}
@@ -922,8 +923,10 @@ public void setMaxRedirects(int maxRedirects)
/**
* @return whether TCP_NODELAY is enabled
+ * @deprecated use {@link ClientConnector#isTCPNoDelay()} instead
*/
@ManagedAttribute(value = "Whether the TCP_NODELAY option is enabled", name = "tcpNoDelay")
+ @Deprecated
public boolean isTCPNoDelay()
{
return tcpNoDelay;
@@ -932,7 +935,9 @@ public boolean isTCPNoDelay()
/**
* @param tcpNoDelay whether TCP_NODELAY is enabled
* @see java.net.Socket#setTcpNoDelay(boolean)
+ * @deprecated use {@link ClientConnector#setTCPNoDelay(boolean)} instead
*/
+ @Deprecated
public void setTCPNoDelay(boolean tcpNoDelay)
{
this.tcpNoDelay = tcpNoDelay;
@@ -1229,7 +1234,7 @@ public boolean containsAll(Collection> c)
@Override
public Iterator iterator()
{
- final Iterator iterator = set.iterator();
+ Iterator iterator = set.iterator();
return new Iterator<>()
{
@Override
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/dynamic/HttpClientTransportDynamic.java b/jetty-client/src/main/java/org/eclipse/jetty/client/dynamic/HttpClientTransportDynamic.java
index c45f3664e65a..f52eff81a7d2 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/dynamic/HttpClientTransportDynamic.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/dynamic/HttpClientTransportDynamic.java
@@ -41,6 +41,8 @@
import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
*
A {@link HttpClientTransport} that can dynamically switch among different application protocols.
@@ -79,6 +81,8 @@
*/
public class HttpClientTransportDynamic extends AbstractConnectorHttpClientTransport
{
+ private static final Logger LOG = LoggerFactory.getLogger(HttpClientTransportDynamic.class);
+
private final List factoryInfos;
private final List protocols;
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpClientTransportOverHTTP.java b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpClientTransportOverHTTP.java
index 4064a3a0b1c5..0eb54ef3502e 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpClientTransportOverHTTP.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpClientTransportOverHTTP.java
@@ -29,11 +29,14 @@
import org.eclipse.jetty.util.ProcessorUtils;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
@ManagedObject("The HTTP/1.1 client transport")
public class HttpClientTransportOverHTTP extends AbstractConnectorHttpClientTransport
{
public static final Origin.Protocol HTTP11 = new Origin.Protocol(List.of("http/1.1"), false);
+ private static final Logger LOG = LoggerFactory.getLogger(HttpClientTransportOverHTTP.class);
private final ClientConnectionFactory factory = new HttpClientConnectionFactory();
private int headerCacheSize = 1024;
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTLSTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTLSTest.java
index ca01c19287fb..d7de9d72e18f 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTLSTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTLSTest.java
@@ -67,8 +67,9 @@
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.condition.EnabledOnJre;
+import org.junit.jupiter.api.condition.EnabledForJreRange;
import org.junit.jupiter.api.condition.JRE;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -366,7 +367,7 @@ public void handshakeSucceeded(Event event)
// Excluded in JDK 11+ because resumed sessions cannot be compared
// using their session IDs even though they are resumed correctly.
- @EnabledOnJre({JRE.JAVA_8, JRE.JAVA_9, JRE.JAVA_10})
+ @EnabledForJreRange(max = JRE.JAVA_10)
@Test
public void testHandshakeSucceededWithSessionResumption() throws Exception
{
@@ -446,7 +447,7 @@ public void handshakeSucceeded(Event event)
// Excluded in JDK 11+ because resumed sessions cannot be compared
// using their session IDs even though they are resumed correctly.
- @EnabledOnJre({JRE.JAVA_8, JRE.JAVA_9, JRE.JAVA_10})
+ @EnabledForJreRange(max = JRE.JAVA_10)
@Test
public void testClientRawCloseDoesNotInvalidateSession() throws Exception
{
@@ -1014,7 +1015,6 @@ public void testForcedNonDomainSNI() throws Exception
// Force TLS-level hostName verification, as we want to receive the correspondent certificate.
clientTLS.setEndpointIdentificationAlgorithm("HTTPS");
startClient(clientTLS);
-
clientTLS.setSNIProvider(SslContextFactory.Client.SniProvider.NON_DOMAIN_SNI_PROVIDER);
// Send a request with SNI "localhost", we should get the certificate at alias=localhost.
@@ -1028,16 +1028,40 @@ public void testForcedNonDomainSNI() throws Exception
.scheme(HttpScheme.HTTPS.asString())
.send();
assertEquals(HttpStatus.OK_200, response2.getStatus());
+ }
- if (Net.isIpv6InterfaceAvailable())
+ @Test
+ @EnabledForJreRange(max = JRE.JAVA_16, disabledReason = "Since Java 17, SNI host names can only have letter|digit|hyphen characters.")
+ public void testForcedNonDomainSNIWithIPv6() throws Exception
+ {
+ Assumptions.assumeTrue(Net.isIpv6InterfaceAvailable());
+
+ SslContextFactory.Server serverTLS = new SslContextFactory.Server();
+ serverTLS.setKeyStorePath("src/test/resources/keystore_sni_non_domain.p12");
+ serverTLS.setKeyStorePassword("storepwd");
+ serverTLS.setSNISelector((keyType, issuers, session, sniHost, certificates) ->
{
- // Send a request with SNI "[::1]", we should get the certificate at alias=ip.
- ContentResponse response3 = client.newRequest("[::1]", connector.getLocalPort())
- .scheme(HttpScheme.HTTPS.asString())
- .send();
+ // We have forced the client to send the non-domain SNI.
+ assertNotNull(sniHost);
+ return serverTLS.sniSelect(keyType, issuers, session, sniHost, certificates);
+ });
+ startServer(serverTLS, new EmptyServerHandler());
- assertEquals(HttpStatus.OK_200, response3.getStatus());
- }
+ SslContextFactory.Client clientTLS = new SslContextFactory.Client();
+ // Trust any certificate received by the server.
+ clientTLS.setTrustStorePath("src/test/resources/keystore_sni_non_domain.p12");
+ clientTLS.setTrustStorePassword("storepwd");
+ // Force TLS-level hostName verification, as we want to receive the correspondent certificate.
+ clientTLS.setEndpointIdentificationAlgorithm("HTTPS");
+ startClient(clientTLS);
+ clientTLS.setSNIProvider(SslContextFactory.Client.SniProvider.NON_DOMAIN_SNI_PROVIDER);
+
+ // Send a request with SNI "[::1]", we should get the certificate at alias=ip.
+ ContentResponse response3 = client.newRequest("[::1]", connector.getLocalPort())
+ .scheme(HttpScheme.HTTPS.asString())
+ .send();
+
+ assertEquals(HttpStatus.OK_200, response3.getStatus());
}
@Test
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java
index 2a33f7148839..ab46fcdb2505 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java
@@ -40,7 +40,6 @@
import java.util.concurrent.Exchanger;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
@@ -49,7 +48,6 @@
import javax.servlet.AsyncContext;
import javax.servlet.DispatcherType;
import javax.servlet.ReadListener;
-import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
@@ -75,6 +73,7 @@
import org.eclipse.jetty.http.HttpHeaderValue;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpScheme;
+import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.io.EndPoint;
@@ -91,7 +90,6 @@
import org.eclipse.jetty.util.SocketAddressResolver;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Assumptions;
-import org.junit.jupiter.api.condition.DisabledIfSystemProperty;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ArgumentsSource;
@@ -688,48 +686,6 @@ public void onComplete(Result result)
assertTrue(latch.await(5, TimeUnit.SECONDS));
}
- @ParameterizedTest
- @ArgumentsSource(ScenarioProvider.class)
- @DisabledIfSystemProperty(named = "env", matches = "ci") // TODO: SLOW, needs review
- public void testRequestIdleTimeout(Scenario scenario) throws Exception
- {
- long idleTimeout = 1000;
- start(scenario, new AbstractHandler()
- {
- @Override
- public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws ServletException
- {
- try
- {
- baseRequest.setHandled(true);
- TimeUnit.MILLISECONDS.sleep(2 * idleTimeout);
- }
- catch (InterruptedException x)
- {
- throw new ServletException(x);
- }
- }
- });
-
- String host = "localhost";
- int port = connector.getLocalPort();
- assertThrows(TimeoutException.class, () ->
- client.newRequest(host, port)
- .scheme(scenario.getScheme())
- .idleTimeout(idleTimeout, TimeUnit.MILLISECONDS)
- .timeout(3 * idleTimeout, TimeUnit.MILLISECONDS)
- .send());
-
- // Make another request without specifying the idle timeout, should not fail
- ContentResponse response = client.newRequest(host, port)
- .scheme(scenario.getScheme())
- .timeout(3 * idleTimeout, TimeUnit.MILLISECONDS)
- .send();
-
- assertNotNull(response);
- assertEquals(200, response.getStatus());
- }
-
@ParameterizedTest
@ArgumentsSource(ScenarioProvider.class)
public void testSendToIPv6Address(Scenario scenario) throws Exception
@@ -1954,6 +1910,45 @@ public long getLength()
assertTrue(serverOnErrorLatch.await(5, TimeUnit.SECONDS), "serverOnErrorLatch didn't finish");
}
+ @ParameterizedTest
+ @ArgumentsSource(ScenarioProvider.class)
+ public void testBindAddress(Scenario scenario) throws Exception
+ {
+ String bindAddress = "127.0.0.2";
+ start(scenario, new EmptyServerHandler()
+ {
+ @Override
+ protected void service(String target, org.eclipse.jetty.server.Request jettyRequest, HttpServletRequest request, HttpServletResponse response)
+ {
+ assertEquals(bindAddress, request.getRemoteAddr());
+ }
+ });
+
+ client.setBindAddress(new InetSocketAddress(bindAddress, 0));
+
+ CountDownLatch latch = new CountDownLatch(1);
+ ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
+ .scheme(scenario.getScheme())
+ .path("/1")
+ .onRequestBegin(r ->
+ {
+ client.newRequest("localhost", connector.getLocalPort())
+ .scheme(scenario.getScheme())
+ .path("/2")
+ .send(result ->
+ {
+ assertTrue(result.isSucceeded());
+ assertEquals(HttpStatus.OK_200, result.getResponse().getStatus());
+ latch.countDown();
+ });
+ })
+ .timeout(5, TimeUnit.SECONDS)
+ .send();
+
+ assertTrue(latch.await(5, TimeUnit.SECONDS));
+ assertEquals(HttpStatus.OK_200, response.getStatus());
+ }
+
private void assertCopyRequest(Request original)
{
Request copy = client.copyRequest((HttpRequest)original, original.getURI());
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java
index a28631cfe3fc..b1ec697f01ef 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java
@@ -32,13 +32,12 @@
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.logging.StacklessLogging;
import org.eclipse.jetty.server.handler.AbstractHandler;
-import org.junit.jupiter.api.Tag;
-import org.junit.jupiter.api.condition.DisabledIfSystemProperty;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ArgumentsSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import static org.awaitility.Awaitility.await;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -213,8 +212,6 @@ public void onComplete(Result result)
@ParameterizedTest
@ArgumentsSource(ScenarioProvider.class)
- @Tag("Slow")
- @DisabledIfSystemProperty(named = "env", matches = "ci") // TODO: SLOW, needs review
public void testBadRequestWithSlowRequestRemovesConnection(Scenario scenario) throws Exception
{
start(scenario, new EmptyServerHandler());
@@ -423,8 +420,6 @@ public void onComplete(Result result)
@ParameterizedTest
@ArgumentsSource(ScenarioProvider.class)
- @Tag("Slow")
- @DisabledIfSystemProperty(named = "env", matches = "ci") // TODO: SLOW, needs review
public void testIdleConnectionIsClosedOnRemoteClose(Scenario scenario) throws Exception
{
start(scenario, new EmptyServerHandler());
@@ -448,10 +443,7 @@ public void testIdleConnectionIsClosedOnRemoteClose(Scenario scenario) throws Ex
connector.stop();
// Give the connection some time to process the remote close
- TimeUnit.SECONDS.sleep(1);
-
- assertEquals(0, idleConnections.size());
- assertEquals(0, activeConnections.size());
+ await().atMost(5, TimeUnit.SECONDS).until(() -> idleConnections.size() == 0 && activeConnections.size() == 0);
}
@ParameterizedTest
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpSenderOverHTTPTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpSenderOverHTTPTest.java
index 1712e73a5065..8ec4f83dd194 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpSenderOverHTTPTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpSenderOverHTTPTest.java
@@ -35,8 +35,8 @@
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.condition.DisabledIfSystemProperty;
+import static org.awaitility.Awaitility.await;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -91,7 +91,6 @@ public void onSuccess(Request request)
}
@Test
- @DisabledIfSystemProperty(named = "env", matches = "ci") // TODO: SLOW, needs review
public void testSendNoRequestContentIncompleteFlush() throws Exception
{
ByteArrayEndPoint endPoint = new ByteArrayEndPoint("", 16);
@@ -105,7 +104,7 @@ public void testSendNoRequestContentIncompleteFlush() throws Exception
StringBuilder builder = new StringBuilder(endPoint.takeOutputString());
// Wait for the write to complete
- TimeUnit.SECONDS.sleep(1);
+ await().atMost(5, TimeUnit.SECONDS).until(() -> endPoint.toEndPointString().contains(",flush=P,"));
String chunk = endPoint.takeOutputString();
while (chunk.length() > 0)
diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/WebAppProvider.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/WebAppProvider.java
index 24c6958c6674..b531ce1d1ea1 100644
--- a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/WebAppProvider.java
+++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/WebAppProvider.java
@@ -22,6 +22,7 @@
import org.eclipse.jetty.deploy.util.FileID;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandler;
+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.ManagedObject;
@@ -265,8 +266,18 @@ public ContextHandler createContextHandler(final App app) throws Exception
if (!resource.exists())
throw new IllegalStateException("App resource does not exist " + resource);
- String context = file.getName();
+ final String contextName = file.getName();
+ // Resource aliases (after getting name) to ensure baseResource is not an alias
+ if (resource.isAlias())
+ {
+ file = new File(resource.getAlias()).toPath().toRealPath().toFile();
+ resource = Resource.newResource(file);
+ if (!resource.exists())
+ throw new IllegalStateException("App resource does not exist " + resource);
+ }
+
+ // Handle a context XML file
if (resource.exists() && FileID.isXmlFile(file))
{
XmlConfiguration xmlc = new XmlConfiguration(resource)
@@ -276,11 +287,15 @@ public void initializeDefaults(Object context)
{
super.initializeDefaults(context);
+ // If the XML created object is a ContextHandler
+ if (context instanceof ContextHandler)
+ // Initialize the context path prior to running context XML
+ initializeContextPath((ContextHandler)context, contextName, true);
+
+ // If it is a webapp
if (context instanceof WebAppContext)
- {
- WebAppContext webapp = (WebAppContext)context;
- initializeWebAppContextDefaults(webapp);
- }
+ // initialize other defaults prior to running context XML
+ initializeWebAppContextDefaults((WebAppContext)context);
}
};
@@ -290,54 +305,62 @@ public void initializeDefaults(Object context)
xmlc.getProperties().putAll(getConfigurationManager().getProperties());
return (ContextHandler)xmlc.configure();
}
- else if (file.isDirectory())
- {
- // must be a directory
- }
- else if (FileID.isWebArchiveFile(file))
- {
- // Context Path is the same as the archive.
- context = context.substring(0, context.length() - 4);
- }
- else
+ // Otherwise it must be a directory or an archive
+ else if (!file.isDirectory() && !FileID.isWebArchiveFile(file))
{
throw new IllegalStateException("unable to create ContextHandler for " + app);
}
- // Ensure "/" is Not Trailing in context paths.
- if (context.endsWith("/") && context.length() > 0)
- {
- context = context.substring(0, context.length() - 1);
- }
-
- // Start building the webapplication
+ // Build the web application
WebAppContext webAppContext = new WebAppContext();
- webAppContext.setDisplayName(context);
+ webAppContext.setWar(file.getAbsolutePath());
+ initializeContextPath(webAppContext, contextName, !file.isDirectory());
+ initializeWebAppContextDefaults(webAppContext);
+
+ return webAppContext;
+ }
+
+ protected void initializeContextPath(ContextHandler context, String contextName, boolean stripExtension)
+ {
+ String contextPath = contextName;
+
+ // Strip any 3 char extension from non directories
+ if (stripExtension && contextPath.length() > 4 && contextPath.charAt(contextPath.length() - 4) == '.')
+ contextPath = contextPath.substring(0, contextPath.length() - 4);
+
+ // Ensure "/" is Not Trailing in context paths.
+ if (contextPath.endsWith("/") && contextPath.length() > 1)
+ contextPath = contextPath.substring(0, contextPath.length() - 1);
// special case of archive (or dir) named "root" is / context
- if (context.equalsIgnoreCase("root"))
+ if (contextPath.equalsIgnoreCase("root"))
{
- context = URIUtil.SLASH;
+ contextPath = URIUtil.SLASH;
}
- else if (context.toLowerCase(Locale.ENGLISH).startsWith("root-"))
+ // handle root with virtual host form
+ else if (StringUtil.startsWithIgnoreCase(contextPath, "root-"))
{
- int dash = context.toLowerCase(Locale.ENGLISH).indexOf('-');
- String virtual = context.substring(dash + 1);
- webAppContext.setVirtualHosts(new String[]{virtual});
- context = URIUtil.SLASH;
+ int dash = contextPath.indexOf('-');
+ String virtual = contextPath.substring(dash + 1);
+ context.setVirtualHosts(virtual.split(","));
+ contextPath = URIUtil.SLASH;
}
// Ensure "/" is Prepended to all context paths.
- if (context.charAt(0) != '/')
+ if (contextPath.charAt(0) != '/')
+ contextPath = "/" + contextPath;
+
+ // Set the display name and context Path
+ context.setDisplayName(contextName);
+ if (context instanceof WebAppContext)
{
- context = "/" + context;
+ WebAppContext webAppContext = (WebAppContext)context;
+ webAppContext.setDefaultContextPath(contextPath);
+ }
+ else
+ {
+ context.setContextPath(contextPath);
}
-
- webAppContext.setDefaultContextPath(context);
- webAppContext.setWar(file.getAbsolutePath());
- initializeWebAppContextDefaults(webAppContext);
-
- return webAppContext;
}
@Override
diff --git a/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/WebAppProviderTest.java b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/WebAppProviderTest.java
index 90a687b9663f..e0ec2a8e8c4b 100644
--- a/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/WebAppProviderTest.java
+++ b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/WebAppProviderTest.java
@@ -25,6 +25,7 @@
import org.eclipse.jetty.deploy.test.XmlConfiguredJetty;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
@@ -124,9 +125,12 @@ public void testStartupSymlinkContext()
// Check Server for expected Handlers
jetty.assertWebAppContextsExists("/bar", "/foo", "/bob");
+ // Check that baseResources are not aliases
+ jetty.getServer().getContainedBeans(ContextHandler.class).forEach(h -> assertFalse(h.getBaseResource().isAlias()));
+
// Test for expected work/temp directory behaviour
File workDir = jetty.getJettyDir("workish");
- assertTrue(hasJettyGeneratedPath(workDir, "bar_war"), "Should have generated directory in work directory: " + workDir);
+ assertTrue(hasJettyGeneratedPath(workDir, "_war-_bar"), "Should have generated directory in work directory: " + workDir);
}
@Test
diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java
index a33fa967e10d..039f813ed319 100644
--- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java
+++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java
@@ -34,10 +34,14 @@
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
@ManagedObject("The FastCGI/1.0 client transport")
public class HttpClientTransportOverFCGI extends AbstractConnectorHttpClientTransport
{
+ private static final Logger LOG = LoggerFactory.getLogger(HttpClientTransportOverFCGI.class);
+
private final String scriptRoot;
public HttpClientTransportOverFCGI(String scriptRoot)
diff --git a/jetty-fcgi/fcgi-server/pom.xml b/jetty-fcgi/fcgi-server/pom.xml
index bed9361cede4..0a2226e4b81d 100644
--- a/jetty-fcgi/fcgi-server/pom.xml
+++ b/jetty-fcgi/fcgi-server/pom.xml
@@ -69,5 +69,11 @@
${project.version}test
+
+ org.eclipse.jetty
+ jetty-unixdomain-server
+ ${project.version}
+ test
+
diff --git a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServlet.java b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServlet.java
index a543c22df221..e22b81c70d62 100644
--- a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServlet.java
+++ b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServlet.java
@@ -14,6 +14,7 @@
package org.eclipse.jetty.fcgi.server.proxy;
import java.net.URI;
+import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.Set;
@@ -36,6 +37,7 @@
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpScheme;
+import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.proxy.AsyncProxyServlet;
import org.eclipse.jetty.util.ProcessorUtils;
@@ -62,6 +64,8 @@
* to force the FastCGI {@code HTTPS} parameter to the value {@code on}
*
{@code fastCGI.envNames}, optional, a comma separated list of environment variable
* names read via {@link System#getenv(String)} that are forwarded as FastCGI parameters.
+ *
{@code unixDomainPath}, optional, that specifies the Unix-Domain path the FastCGI
+ * server listens to.
*
*
* @see TryFilesFilter
@@ -122,11 +126,23 @@ protected HttpClient newHttpClient()
String scriptRoot = config.getInitParameter(SCRIPT_ROOT_INIT_PARAM);
if (scriptRoot == null)
throw new IllegalArgumentException("Mandatory parameter '" + SCRIPT_ROOT_INIT_PARAM + "' not configured");
- int selectors = Math.max(1, ProcessorUtils.availableProcessors() / 2);
- String value = config.getInitParameter("selectors");
- if (value != null)
- selectors = Integer.parseInt(value);
- return new HttpClient(new ProxyHttpClientTransportOverFCGI(selectors, scriptRoot));
+
+ ClientConnector connector;
+ String unixDomainPath = config.getInitParameter("unixDomainPath");
+ if (unixDomainPath != null)
+ {
+ connector = ClientConnector.forUnixDomain(Path.of(unixDomainPath));
+ }
+ else
+ {
+ int selectors = Math.max(1, ProcessorUtils.availableProcessors() / 2);
+ String value = config.getInitParameter("selectors");
+ if (value != null)
+ selectors = Integer.parseInt(value);
+ connector = new ClientConnector();
+ connector.setSelectors(selectors);
+ }
+ return new HttpClient(new ProxyHttpClientTransportOverFCGI(connector, scriptRoot));
}
@Override
@@ -261,9 +277,9 @@ protected void customizeFastCGIHeaders(Request proxyRequest, HttpFields.Mutable
private class ProxyHttpClientTransportOverFCGI extends HttpClientTransportOverFCGI
{
- private ProxyHttpClientTransportOverFCGI(int selectors, String scriptRoot)
+ private ProxyHttpClientTransportOverFCGI(ClientConnector connector, String scriptRoot)
{
- super(selectors, scriptRoot);
+ super(connector, scriptRoot);
}
@Override
diff --git a/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/HttpClientTest.java b/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/HttpClientTest.java
index b45ac747af6b..fee6a3d2617c 100644
--- a/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/HttpClientTest.java
+++ b/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/HttpClientTest.java
@@ -24,7 +24,6 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.zip.GZIPOutputStream;
@@ -48,7 +47,6 @@
import org.eclipse.jetty.util.Callback;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.condition.DisabledIfSystemProperty;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.instanceOf;
@@ -410,47 +408,6 @@ public void handle(String target, org.eclipse.jetty.server.Request baseRequest,
assertArrayEquals(data, response.getContent());
}
- @Test
- @DisabledIfSystemProperty(named = "env", matches = "ci") // TODO: SLOW, needs review
- public void testRequestIdleTimeout() throws Exception
- {
- final long idleTimeout = 1000;
- start(new AbstractHandler()
- {
- @Override
- public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws ServletException
- {
- try
- {
- baseRequest.setHandled(true);
- TimeUnit.MILLISECONDS.sleep(2 * idleTimeout);
- }
- catch (InterruptedException x)
- {
- throw new ServletException(x);
- }
- }
- });
-
- final String host = "localhost";
- final int port = connector.getLocalPort();
- assertThrows(TimeoutException.class, () ->
- client.newRequest(host, port)
- .scheme(scheme)
- .idleTimeout(idleTimeout, TimeUnit.MILLISECONDS)
- .timeout(3 * idleTimeout, TimeUnit.MILLISECONDS)
- .send());
-
- // Make another request without specifying the idle timeout, should not fail
- ContentResponse response = client.newRequest(host, port)
- .scheme(scheme)
- .timeout(3 * idleTimeout, TimeUnit.MILLISECONDS)
- .send();
-
- assertNotNull(response);
- assertEquals(200, response.getStatus());
- }
-
@Test
public void testConnectionIdleTimeout() throws Exception
{
diff --git a/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServletTest.java b/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServletTest.java
index 6df325b47312..1498be1b98fc 100644
--- a/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServletTest.java
+++ b/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServletTest.java
@@ -14,9 +14,12 @@
package org.eclipse.jetty.fcgi.server.proxy;
import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Map;
import java.util.Random;
import java.util.concurrent.TimeUnit;
-import java.util.stream.Stream;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
@@ -29,18 +32,22 @@
import org.eclipse.jetty.fcgi.FCGI;
import org.eclipse.jetty.fcgi.server.ServerFCGIConnectionFactory;
import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.HandlerWrapper;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.unixdomain.server.UnixDomainServerConnector;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.EnabledForJreRange;
+import org.junit.jupiter.api.condition.JRE;
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 static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
@@ -49,19 +56,13 @@
public class FastCGIProxyServletTest
{
- public static Stream factories()
- {
- return Stream.of(
- true, // send status 200
- false // don't send status 200
- ).map(Arguments::of);
- }
-
+ private final Map fcgiParams = new HashMap<>();
private Server server;
private ServerConnector httpConnector;
- private ServerConnector fcgiConnector;
+ private Connector fcgiConnector;
private ServletContextHandler context;
private HttpClient client;
+ private Path unixDomainPath;
public void prepare(boolean sendStatus200, HttpServlet servlet) throws Exception
{
@@ -71,19 +72,32 @@ public void prepare(boolean sendStatus200, HttpServlet servlet) throws Exception
httpConnector = new ServerConnector(server);
server.addConnector(httpConnector);
- fcgiConnector = new ServerConnector(server, new ServerFCGIConnectionFactory(new HttpConfiguration(), sendStatus200));
+ ServerFCGIConnectionFactory fcgi = new ServerFCGIConnectionFactory(new HttpConfiguration(), sendStatus200);
+ if (unixDomainPath == null)
+ {
+ fcgiConnector = new ServerConnector(server, fcgi);
+ }
+ else
+ {
+ UnixDomainServerConnector connector = new UnixDomainServerConnector(server, fcgi);
+ connector.setUnixDomainPath(unixDomainPath);
+ fcgiConnector = connector;
+ }
server.addConnector(fcgiConnector);
- final String contextPath = "/";
+ String contextPath = "/";
context = new ServletContextHandler(server, contextPath);
- final String servletPath = "/script";
+ String servletPath = "/script";
FastCGIProxyServlet fcgiServlet = new FastCGIProxyServlet()
{
@Override
protected String rewriteTarget(HttpServletRequest request)
{
- return "http://localhost:" + fcgiConnector.getLocalPort() + servletPath + request.getServletPath();
+ String uri = "http://localhost";
+ if (unixDomainPath == null)
+ uri += ":" + ((ServerConnector)fcgiConnector).getLocalPort();
+ return uri + servletPath + request.getServletPath();
}
};
ServletHolder fcgiServletHolder = new ServletHolder(fcgiServlet);
@@ -91,6 +105,7 @@ protected String rewriteTarget(HttpServletRequest request)
fcgiServletHolder.setInitParameter(FastCGIProxyServlet.SCRIPT_ROOT_INIT_PARAM, "/scriptRoot");
fcgiServletHolder.setInitParameter("proxyTo", "http://localhost");
fcgiServletHolder.setInitParameter(FastCGIProxyServlet.SCRIPT_PATTERN_INIT_PARAM, "(.+?\\.php)");
+ fcgiParams.forEach(fcgiServletHolder::setInitParameter);
context.addServlet(fcgiServletHolder, "*.php");
context.addServlet(new ServletHolder(servlet), servletPath + "/*");
@@ -111,36 +126,36 @@ public void dispose() throws Exception
}
@ParameterizedTest(name = "[{index}] sendStatus200={0}")
- @MethodSource("factories")
+ @ValueSource(booleans = {true, false})
public void testGETWithSmallResponseContent(boolean sendStatus200) throws Exception
{
testGETWithResponseContent(sendStatus200, 1024, 0);
}
@ParameterizedTest(name = "[{index}] sendStatus200={0}")
- @MethodSource("factories")
+ @ValueSource(booleans = {true, false})
public void testGETWithLargeResponseContent(boolean sendStatus200) throws Exception
{
testGETWithResponseContent(sendStatus200, 16 * 1024 * 1024, 0);
}
@ParameterizedTest(name = "[{index}] sendStatus200={0}")
- @MethodSource("factories")
+ @ValueSource(booleans = {true, false})
public void testGETWithLargeResponseContentWithSlowClient(boolean sendStatus200) throws Exception
{
testGETWithResponseContent(sendStatus200, 16 * 1024 * 1024, 1);
}
- private void testGETWithResponseContent(boolean sendStatus200, int length, final long delay) throws Exception
+ private void testGETWithResponseContent(boolean sendStatus200, int length, long delay) throws Exception
{
- final byte[] data = new byte[length];
+ byte[] data = new byte[length];
new Random().nextBytes(data);
- final String path = "/foo/index.php";
+ String path = "/foo/index.php";
prepare(sendStatus200, new HttpServlet()
{
@Override
- protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException
{
assertTrue(request.getRequestURI().endsWith(path));
response.setContentLength(data.length);
@@ -173,16 +188,20 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) t
}
@ParameterizedTest(name = "[{index}] sendStatus200={0}")
- @MethodSource("factories")
+ @ValueSource(booleans = {true, false})
public void testURIRewrite(boolean sendStatus200) throws Exception
{
String originalPath = "/original/index.php";
String originalQuery = "foo=bar";
String remotePath = "/remote/index.php";
+ String pathAttribute = "_path_attribute";
+ String queryAttribute = "_query_attribute";
+ fcgiParams.put(FastCGIProxyServlet.ORIGINAL_URI_ATTRIBUTE_INIT_PARAM, pathAttribute);
+ fcgiParams.put(FastCGIProxyServlet.ORIGINAL_QUERY_ATTRIBUTE_INIT_PARAM, queryAttribute);
prepare(sendStatus200, new HttpServlet()
{
@Override
- protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+ protected void service(HttpServletRequest request, HttpServletResponse response)
{
assertThat((String)request.getAttribute(FCGI.Headers.REQUEST_URI), Matchers.startsWith(originalPath));
assertEquals(originalQuery, request.getAttribute(FCGI.Headers.QUERY_STRING));
@@ -190,11 +209,6 @@ protected void service(HttpServletRequest request, HttpServletResponse response)
}
});
context.stop();
- String pathAttribute = "_path_attribute";
- String queryAttribute = "_query_attribute";
- ServletHolder fcgi = context.getServletHandler().getServlet("fcgi");
- fcgi.setInitParameter(FastCGIProxyServlet.ORIGINAL_URI_ATTRIBUTE_INIT_PARAM, pathAttribute);
- fcgi.setInitParameter(FastCGIProxyServlet.ORIGINAL_QUERY_ATTRIBUTE_INIT_PARAM, queryAttribute);
context.insertHandler(new HandlerWrapper()
{
@Override
@@ -216,4 +230,34 @@ public void handle(String target, org.eclipse.jetty.server.Request baseRequest,
assertEquals(HttpStatus.OK_200, response.getStatus());
}
+
+ @Test
+ @EnabledForJreRange(min = JRE.JAVA_16)
+ public void testUnixDomain() throws Exception
+ {
+ int maxUnixDomainPathLength = 108;
+ Path path = Files.createTempFile("unix", ".sock");
+ if (path.normalize().toAbsolutePath().toString().length() > maxUnixDomainPathLength)
+ path = Files.createTempFile(Path.of("/tmp"), "unix", ".sock");
+ assertTrue(Files.deleteIfExists(path));
+ unixDomainPath = path;
+ fcgiParams.put("unixDomainPath", path.toString());
+ byte[] content = new byte[512];
+ new Random().nextBytes(content);
+ prepare(true, new HttpServlet()
+ {
+ @Override
+ protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException
+ {
+ response.getOutputStream().write(content);
+ }
+ });
+
+ ContentResponse response = client.newRequest("localhost", httpConnector.getLocalPort())
+ .path("/index.php")
+ .send();
+
+ assertEquals(HttpStatus.OK_200, response.getStatus());
+ assertArrayEquals(content, response.getContent());
+ }
}
diff --git a/jetty-fcgi/fcgi-server/src/test/resources/jetty-logging.properties b/jetty-fcgi/fcgi-server/src/test/resources/jetty-logging.properties
index 4e7406f1b548..9d915cb3b003 100644
--- a/jetty-fcgi/fcgi-server/src/test/resources/jetty-logging.properties
+++ b/jetty-fcgi/fcgi-server/src/test/resources/jetty-logging.properties
@@ -1,3 +1,3 @@
-# Jetty Logging using jetty-slf4j-impl
+#org.eclipse.jetty.LEVEL=DEBUG
#org.eclipse.jetty.client.LEVEL=DEBUG
#org.eclipse.jetty.fcgi.LEVEL=DEBUG
diff --git a/jetty-gcloud/jetty-gcloud-session-manager/src/main/config-template/etc/sessions/gcloud/session-store.xml b/jetty-gcloud/jetty-gcloud-session-manager/src/main/config-template/etc/sessions/gcloud/session-store.xml
index 43d7fd0fe03f..070c76aa25ab 100644
--- a/jetty-gcloud/jetty-gcloud-session-manager/src/main/config-template/etc/sessions/gcloud/session-store.xml
+++ b/jetty-gcloud/jetty-gcloud-session-manager/src/main/config-template/etc/sessions/gcloud/session-store.xml
@@ -14,7 +14,9 @@
-
+
+
+
diff --git a/jetty-gcloud/jetty-gcloud-session-manager/src/main/config-template/modules/session-store-gcloud.mod b/jetty-gcloud/jetty-gcloud-session-manager/src/main/config-template/modules/session-store-gcloud.mod
index 9ee2a31f749d..90856809b16f 100644
--- a/jetty-gcloud/jetty-gcloud-session-manager/src/main/config-template/modules/session-store-gcloud.mod
+++ b/jetty-gcloud/jetty-gcloud-session-manager/src/main/config-template/modules/session-store-gcloud.mod
@@ -33,6 +33,8 @@ etc/sessions/gcloud/session-store.xml
#jetty.session.gcloud.maxRetries=5
#jetty.session.gcloud.backoffMs=1000
#jetty.session.gcloud.namespace=
+#jetty.session.gcloud.host=
+#jetty.session.gcloud.projectId=
#jetty.session.gcloud.model.kind=GCloudSession
#jetty.session.gcloud.model.id=id
#jetty.session.gcloud.model.contextPath=contextPath
diff --git a/jetty-gcloud/jetty-gcloud-session-manager/src/main/java/org/eclipse/jetty/gcloud/session/GCloudSessionDataStore.java b/jetty-gcloud/jetty-gcloud-session-manager/src/main/java/org/eclipse/jetty/gcloud/session/GCloudSessionDataStore.java
index d120ff593ac1..fd5fb1a87160 100644
--- a/jetty-gcloud/jetty-gcloud-session-manager/src/main/java/org/eclipse/jetty/gcloud/session/GCloudSessionDataStore.java
+++ b/jetty-gcloud/jetty-gcloud-session-manager/src/main/java/org/eclipse/jetty/gcloud/session/GCloudSessionDataStore.java
@@ -67,6 +67,8 @@ public class GCloudSessionDataStore extends AbstractSessionDataStore
protected EntityDataModel _model;
protected boolean _modelProvided;
private String _namespace = DEFAULT_NAMESPACE;
+ private String _host;
+ private String _projectId;
/**
* EntityDataModel
@@ -455,15 +457,49 @@ public int getMaxRetries()
return _maxRetries;
}
+ public void setHost(String host)
+ {
+ _host = host;
+ }
+
+ @ManagedAttribute(value = "gcloud host", readonly = true)
+ public String getHost()
+ {
+ return _host;
+ }
+
+ public void setProjectId(String projectId)
+ {
+ _projectId = projectId;
+ }
+
+ @ManagedAttribute(value = "gcloud project Id", readonly = true)
+ public String getProjectId()
+ {
+ return _projectId;
+ }
+
@Override
protected void doStart() throws Exception
{
if (!_dsProvided)
{
- if (!StringUtil.isBlank(getNamespace()))
- _datastore = DatastoreOptions.newBuilder().setNamespace(getNamespace()).build().getService();
+ boolean blankCustomnamespace = StringUtil.isBlank(getNamespace());
+ boolean blankCustomHost = StringUtil.isBlank(getHost());
+ boolean blankCustomProjectId = StringUtil.isBlank(getProjectId());
+ if (blankCustomnamespace && blankCustomHost && blankCustomProjectId)
+ _datastore = DatastoreOptions.getDefaultInstance().getService();
else
- _datastore = DatastoreOptions.getDefaultInstance().getService();
+ {
+ DatastoreOptions.Builder builder = DatastoreOptions.newBuilder();
+ if (!blankCustomnamespace)
+ builder.setNamespace(getNamespace());
+ if (!blankCustomHost)
+ builder.setHost(getHost());
+ if (!blankCustomProjectId)
+ builder.setProjectId(getProjectId());
+ _datastore = builder.build().getService();
+ }
}
if (_model == null)
diff --git a/jetty-gcloud/jetty-gcloud-session-manager/src/main/java/org/eclipse/jetty/gcloud/session/GCloudSessionDataStoreFactory.java b/jetty-gcloud/jetty-gcloud-session-manager/src/main/java/org/eclipse/jetty/gcloud/session/GCloudSessionDataStoreFactory.java
index 716e18f91028..6875a334a4ec 100644
--- a/jetty-gcloud/jetty-gcloud-session-manager/src/main/java/org/eclipse/jetty/gcloud/session/GCloudSessionDataStoreFactory.java
+++ b/jetty-gcloud/jetty-gcloud-session-manager/src/main/java/org/eclipse/jetty/gcloud/session/GCloudSessionDataStoreFactory.java
@@ -26,6 +26,8 @@ public class GCloudSessionDataStoreFactory extends AbstractSessionDataStoreFacto
private int _maxRetries = GCloudSessionDataStore.DEFAULT_MAX_RETRIES;
private int _backoffMs = GCloudSessionDataStore.DEFAULT_BACKOFF_MS;
private GCloudSessionDataStore.EntityDataModel _model;
+ private String _host;
+ private String _projectId;
public GCloudSessionDataStore.EntityDataModel getEntityDataModel()
{
@@ -73,6 +75,26 @@ public void setNamespace(String namespace)
_namespace = namespace;
}
+ public void setHost(String host)
+ {
+ _host = host;
+ }
+
+ public String getHost()
+ {
+ return _host;
+ }
+
+ public void setProjectId(String projectId)
+ {
+ _projectId = projectId;
+ }
+
+ public String getProjectId()
+ {
+ return _projectId;
+ }
+
@Override
public SessionDataStore getSessionDataStore(SessionHandler handler) throws Exception
{
@@ -83,6 +105,8 @@ public SessionDataStore getSessionDataStore(SessionHandler handler) throws Excep
ds.setNamespace(getNamespace());
ds.setSavePeriodSec(getSavePeriodSec());
ds.setEntityDataModel(getEntityDataModel());
+ ds.setHost(getHost());
+ ds.setProjectId(getProjectId());
return ds;
}
}
diff --git a/jetty-home/pom.xml b/jetty-home/pom.xml
index d0daeeb66574..a7ee899e87b0 100644
--- a/jetty-home/pom.xml
+++ b/jetty-home/pom.xml
@@ -534,6 +534,7 @@
+
@@ -648,6 +649,12 @@
jetty-proxy${project.version}
+
+ org.eclipse.jetty
+ jetty-unixdomain-server
+ ${project.version}
+ true
+ org.eclipse.jettyjetty-unixsocket-server
diff --git a/jetty-home/src/main/resources/bin/jetty.sh b/jetty-home/src/main/resources/bin/jetty.sh
index c128bbad2ce7..3a429107d13a 100755
--- a/jetty-home/src/main/resources/bin/jetty.sh
+++ b/jetty-home/src/main/resources/bin/jetty.sh
@@ -66,7 +66,8 @@ NAME=$(echo $(basename $0) | sed -e 's/^[SK][0-9]*//' -e 's/\.sh$//')
# /webapps/jetty.war
#
# JETTY_BASE
-# Where your Jetty base directory is. If not set, the value from
+# Where your Jetty base directory is. If not set, then the currently
+# directory is checked, otherwise the value from
# $JETTY_HOME will be used.
#
# JETTY_RUN
@@ -238,7 +239,6 @@ then
fi
fi
-
##################################################
# No JETTY_HOME yet? We're out of luck!
##################################################
@@ -247,20 +247,23 @@ if [ -z "$JETTY_HOME" ]; then
exit 1
fi
+RUN_DIR=$(pwd)
cd "$JETTY_HOME"
-JETTY_HOME=$PWD
-
+JETTY_HOME=$(pwd)
##################################################
# Set JETTY_BASE
##################################################
+export JETTY_BASE
if [ -z "$JETTY_BASE" ]; then
- JETTY_BASE=$JETTY_HOME
+ if [ -d "$RUN_DIR/start.d" -o -f "$RUN_DIR/start.ini" ]; then
+ JETTY_BASE=$RUN_DIR
+ else
+ JETTY_BASE=$JETTY_HOME
+ fi
fi
-
cd "$JETTY_BASE"
-JETTY_BASE=$PWD
-
+JETTY_BASE=$(pwd)
#####################################################
# Check that jetty is where we think it is
@@ -430,7 +433,7 @@ case "`uname`" in
CYGWIN*) JETTY_START="`cygpath -w $JETTY_START`";;
esac
-RUN_ARGS=(${JAVA_OPTIONS[@]} -jar "$JETTY_START" ${JETTY_ARGS[*]})
+RUN_ARGS=$(echo $JAVA_OPTIONS ; "$JAVA" -jar "$JETTY_START" --dry-run=opts,path,main,args ${JETTY_ARGS[*]})
RUN_CMD=("$JAVA" ${RUN_ARGS[@]})
#####################################################
diff --git a/jetty-home/src/main/resources/modules/hawtio.mod b/jetty-home/src/main/resources/modules/hawtio.mod
index 2181a65259fe..050c6265449a 100644
--- a/jetty-home/src/main/resources/modules/hawtio.mod
+++ b/jetty-home/src/main/resources/modules/hawtio.mod
@@ -17,7 +17,7 @@ etc/hawtio.xml
[files]
etc/hawtio/
lib/hawtio/
-maven://io.hawt/hawtio-default/1.4.16|lib/hawtio/hawtio.war
+maven://io.hawt/hawtio-default/${hawtio.version}/war|lib/hawtio/hawtio.war
basehome:modules/hawtio/hawtio.xml|etc/hawtio.xml
[license]
@@ -26,6 +26,9 @@ http://hawt.io/
http://github.com/hawtio/hawtio
http://www.apache.org/licenses/LICENSE-2.0.html
+[ini]
+hawtio.version?=2.13.5
+
[ini-template]
## Hawt.io configuration
-Dhawtio.authenticationEnabled?=false
diff --git a/jetty-home/src/main/resources/modules/jminix.mod b/jetty-home/src/main/resources/modules/jminix.mod
index a52f9d5e50d8..c3d6f7f71bbb 100644
--- a/jetty-home/src/main/resources/modules/jminix.mod
+++ b/jetty-home/src/main/resources/modules/jminix.mod
@@ -9,8 +9,7 @@ Deploys the Jminix JMX Console within the server.
[depend]
stats
jmx
-jcl-api
-jcl-impl
+commons-logging
[xml]
etc/jminix.xml
diff --git a/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpClientTransportOverHTTP2.java b/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpClientTransportOverHTTP2.java
index f316cbedb0eb..001584b3a1f1 100644
--- a/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpClientTransportOverHTTP2.java
+++ b/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpClientTransportOverHTTP2.java
@@ -93,6 +93,8 @@ protected void doStart() throws Exception
client.setInputBufferSize(httpClient.getResponseBufferSize());
client.setUseInputDirectByteBuffers(httpClient.isUseInputDirectByteBuffers());
client.setUseOutputDirectByteBuffers(httpClient.isUseOutputDirectByteBuffers());
+ client.setConnectBlocking(httpClient.isConnectBlocking());
+ client.setBindAddress(httpClient.getBindAddress());
}
addBean(client);
super.doStart();
diff --git a/jetty-http2/http2-server/src/main/config/etc/jetty-http2.xml b/jetty-http2/http2-server/src/main/config/etc/jetty-http2.xml
index 3110c911d9d9..78904fd4bba3 100644
--- a/jetty-http2/http2-server/src/main/config/etc/jetty-http2.xml
+++ b/jetty-http2/http2-server/src/main/config/etc/jetty-http2.xml
@@ -12,7 +12,7 @@
-
+
diff --git a/jetty-http2/http2-server/src/main/config/etc/jetty-http2c.xml b/jetty-http2/http2-server/src/main/config/etc/jetty-http2c.xml
index 86b10ada984f..a13cf3970f68 100644
--- a/jetty-http2/http2-server/src/main/config/etc/jetty-http2c.xml
+++ b/jetty-http2/http2-server/src/main/config/etc/jetty-http2c.xml
@@ -8,11 +8,11 @@
-
-
+
+
-
+
diff --git a/jetty-http2/http2-server/src/main/config/modules/http2.mod b/jetty-http2/http2-server/src/main/config/modules/http2.mod
index f1742041b519..2447ef364ec9 100644
--- a/jetty-http2/http2-server/src/main/config/modules/http2.mod
+++ b/jetty-http2/http2-server/src/main/config/modules/http2.mod
@@ -33,5 +33,5 @@ etc/jetty-http2.xml
## Specifies the maximum number of bad frames and pings per second,
## after which a session is closed to avoid denial of service attacks.
-# jetty.http2.rateControl.maxEventsPerSecond=20
+# jetty.http2.rateControl.maxEventsPerSecond=50
# end::documentation[]
diff --git a/jetty-http2/http2-server/src/main/config/modules/http2c.mod b/jetty-http2/http2-server/src/main/config/modules/http2c.mod
index f1a6fc4f55af..eb40a13ed66c 100644
--- a/jetty-http2/http2-server/src/main/config/modules/http2c.mod
+++ b/jetty-http2/http2-server/src/main/config/modules/http2c.mod
@@ -20,16 +20,16 @@ etc/jetty-http2c.xml
## Specifies the maximum number of concurrent requests per session.
# jetty.http2c.maxConcurrentStreams=128
- ## Specifies the initial stream receive window (client to server) in bytes.
+## Specifies the initial stream receive window (client to server) in bytes.
# jetty.http2c.initialStreamRecvWindow=524288
## Specifies the initial session receive window (client to server) in bytes.
-# jetty.http2.initialSessionRecvWindow=1232896
+# jetty.http2c.initialSessionRecvWindow=1232896
## Specifies the maximum number of keys in all SETTINGS frames received by a session.
-# jetty.http2.maxSettingsKeys=64
+# jetty.http2c.maxSettingsKeys=64
## Specifies the maximum number of bad frames and pings per second,
## after which a session is closed to avoid denial of service attacks.
-# jetty.http2.rateControl.maxEventsPerSecond=20
+# jetty.http2c.rateControl.maxEventsPerSecond=50
# end::documentation[]
diff --git a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/AbstractHTTP2ServerConnectionFactory.java b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/AbstractHTTP2ServerConnectionFactory.java
index ff386f198fa5..1436e1188317 100644
--- a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/AbstractHTTP2ServerConnectionFactory.java
+++ b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/AbstractHTTP2ServerConnectionFactory.java
@@ -61,7 +61,7 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne
private int maxFrameLength = Frame.DEFAULT_MAX_LENGTH;
private int maxSettingsKeys = SettingsFrame.DEFAULT_MAX_KEYS;
private boolean connectProtocolEnabled = true;
- private RateControl.Factory rateControlFactory = new WindowRateControl.Factory(20);
+ private RateControl.Factory rateControlFactory = new WindowRateControl.Factory(50);
private FlowControlStrategy.Factory flowControlStrategyFactory = () -> new BufferingFlowControlStrategy(0.5F);
private long streamIdleTimeout;
private boolean useInputDirectByteBuffers;
diff --git a/jetty-infinispan/infinispan-remote/src/main/config-template/etc/sessions/infinispan/infinispan-remote.xml b/jetty-infinispan/infinispan-remote/src/main/config-template/etc/sessions/infinispan/infinispan-remote.xml
index 7fa442548a04..ebce2c692761 100644
--- a/jetty-infinispan/infinispan-remote/src/main/config-template/etc/sessions/infinispan/infinispan-remote.xml
+++ b/jetty-infinispan/infinispan-remote/src/main/config-template/etc/sessions/infinispan/infinispan-remote.xml
@@ -6,7 +6,7 @@
-
+
diff --git a/jetty-infinispan/infinispan-remote/src/main/config-template/modules/session-store-infinispan-remote.mod b/jetty-infinispan/infinispan-remote/src/main/config-template/modules/session-store-infinispan-remote.mod
index e627553096a5..cfa1a0354951 100644
--- a/jetty-infinispan/infinispan-remote/src/main/config-template/modules/session-store-infinispan-remote.mod
+++ b/jetty-infinispan/infinispan-remote/src/main/config-template/modules/session-store-infinispan-remote.mod
@@ -29,4 +29,3 @@ http://www.apache.org/licenses/LICENSE-2.0.html
#jetty.session.infinispan.idleTimeout.seconds=0
#jetty.session.gracePeriod.seconds=3600
#jetty.session.savePeriod.seconds=0
-
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java
index f2d5ebb4ce8b..414db0623366 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java
@@ -14,19 +14,28 @@
package org.eclipse.jetty.io;
import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.ProtocolFamily;
import java.net.SocketAddress;
import java.net.SocketException;
+import java.net.SocketOption;
+import java.net.StandardProtocolFamily;
import java.net.StandardSocketOptions;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
+import java.nio.file.Path;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
+import java.util.Objects;
import java.util.concurrent.Executor;
import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.JavaVersion;
import org.eclipse.jetty.util.Promise;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
@@ -35,6 +44,32 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+/**
+ *
The client-side component that connects to server sockets.
+ *
ClientConnector delegates the handling of {@link SocketChannel}s
+ * to a {@link SelectorManager}, and centralizes the configuration of
+ * necessary components such as the executor, the scheduler, etc.
+ *
ClientConnector offers a low-level API that can be used to
+ * connect {@link SocketChannel}s to listening servers via the
+ * {@link #connect(SocketAddress, Map)} method.
+ *
However, a ClientConnector instance is typically just configured
+ * and then passed to an HttpClient transport, so that applications
+ * can use high-level APIs to make HTTP requests to servers:
+ *
+ * // Create a ClientConnector instance.
+ * ClientConnector connector = new ClientConnector();
+ *
+ * // Configure the ClientConnector.
+ * connector.setSelectors(1);
+ * connector.setSslContextFactory(new SslContextFactory.Client());
+ *
+ * // Pass it to the HttpClient transport.
+ * HttpClientTransport transport = new HttpClientTransportDynamic(connector);
+ * HttpClient httpClient = new HttpClient(transport);
+ * httpClient.start();
+ *
+ */
+@ManagedObject
public class ClientConnector extends ContainerLifeCycle
{
public static final String CLIENT_CONNECTOR_CONTEXT_KEY = "org.eclipse.jetty.client.connector";
@@ -43,6 +78,18 @@ public class ClientConnector extends ContainerLifeCycle
public static final String CONNECTION_PROMISE_CONTEXT_KEY = CLIENT_CONNECTOR_CONTEXT_KEY + ".connectionPromise";
private static final Logger LOG = LoggerFactory.getLogger(ClientConnector.class);
+ /**
+ *
Creates a ClientConnector configured to connect via Unix-Domain sockets to the given Unix-Domain path
+ *
+ * @param path the Unix-Domain path to connect to
+ * @return a ClientConnector that connects to the given Unix-Domain path
+ */
+ public static ClientConnector forUnixDomain(Path path)
+ {
+ return new ClientConnector(SocketChannelWithAddress.Factory.forUnixDomain(path));
+ }
+
+ private final SocketChannelWithAddress.Factory factory;
private Executor executor;
private Scheduler scheduler;
private ByteBufferPool byteBufferPool;
@@ -53,7 +100,21 @@ public class ClientConnector extends ContainerLifeCycle
private Duration connectTimeout = Duration.ofSeconds(5);
private Duration idleTimeout = Duration.ofSeconds(30);
private SocketAddress bindAddress;
+ private boolean tcpNoDelay = true;
private boolean reuseAddress = true;
+ private boolean reusePort;
+ private int receiveBufferSize = -1;
+ private int sendBufferSize = -1;
+
+ public ClientConnector()
+ {
+ this((address, context) -> new SocketChannelWithAddress(SocketChannel.open(), address));
+ }
+
+ private ClientConnector(SocketChannelWithAddress.Factory factory)
+ {
+ this.factory = Objects.requireNonNull(factory);
+ }
public Executor getExecutor()
{
@@ -107,6 +168,10 @@ public void setSslContextFactory(SslContextFactory.Client sslContextFactory)
this.sslContextFactory = sslContextFactory;
}
+ /**
+ * @return the number of NIO selectors
+ */
+ @ManagedAttribute("The number of NIO selectors")
public int getSelectors()
{
return selectors;
@@ -119,6 +184,10 @@ public void setSelectors(int selectors)
this.selectors = selectors;
}
+ /**
+ * @return whether {@link #connect(SocketAddress, Map)} operations are performed in blocking mode
+ */
+ @ManagedAttribute("Whether connect operations are performed in blocking mode")
public boolean isConnectBlocking()
{
return connectBlocking;
@@ -129,6 +198,10 @@ public void setConnectBlocking(boolean connectBlocking)
this.connectBlocking = connectBlocking;
}
+ /**
+ * @return the timeout of {@link #connect(SocketAddress, Map)} operations
+ */
+ @ManagedAttribute("The timeout of connect operations")
public Duration getConnectTimeout()
{
return connectTimeout;
@@ -141,6 +214,10 @@ public void setConnectTimeout(Duration connectTimeout)
selectorManager.setConnectTimeout(connectTimeout.toMillis());
}
+ /**
+ * @return the max duration for which a connection can be idle (that is, without traffic of bytes in either direction)
+ */
+ @ManagedAttribute("The duration for which a connection can be idle")
public Duration getIdleTimeout()
{
return idleTimeout;
@@ -151,26 +228,120 @@ public void setIdleTimeout(Duration idleTimeout)
this.idleTimeout = idleTimeout;
}
+ /**
+ * @return the address to bind a socket to before the connect operation
+ */
+ @ManagedAttribute("The socket address to bind sockets to before the connect operation")
public SocketAddress getBindAddress()
{
return bindAddress;
}
+ /**
+ *
Sets the bind address of sockets before the connect operation.
+ *
In multi-homed hosts, you may want to connect from a specific address:
Note the use of the port {@code 0} to indicate that a different ephemeral port
+ * should be used for each different connection.
+ *
In the rare cases where you want to use the same port for all connections,
+ * you must also call {@link #setReusePort(boolean) setReusePort(true)}.
+ *
+ * @param bindAddress the socket address to bind to before the connect operation
+ */
public void setBindAddress(SocketAddress bindAddress)
{
this.bindAddress = bindAddress;
}
+ /**
+ * @return whether small TCP packets are sent without delay
+ */
+ @ManagedAttribute("Whether small TCP packets are sent without delay")
+ public boolean isTCPNoDelay()
+ {
+ return tcpNoDelay;
+ }
+
+ public void setTCPNoDelay(boolean tcpNoDelay)
+ {
+ this.tcpNoDelay = tcpNoDelay;
+ }
+
+ /**
+ * @return whether rebinding is allowed with sockets in tear-down states
+ */
+ @ManagedAttribute("Whether rebinding is allowed with sockets in tear-down states")
public boolean getReuseAddress()
{
return reuseAddress;
}
+ /**
+ *
Sets whether it is allowed to bind a socket to a socket address
+ * that may be in use by another socket in tear-down state, for example
+ * in TIME_WAIT state.
+ *
This is useful when ClientConnector is restarted: an existing connection
+ * may still be using a network address (same host and same port) that is also
+ * chosen for a new connection.
+ *
+ * @param reuseAddress whether rebinding is allowed with sockets in tear-down states
+ * @see #setReusePort(boolean)
+ */
public void setReuseAddress(boolean reuseAddress)
{
this.reuseAddress = reuseAddress;
}
+ /**
+ * @return whether binding to same host and port is allowed
+ */
+ @ManagedAttribute("Whether binding to same host and port is allowed")
+ public boolean isReusePort()
+ {
+ return reusePort;
+ }
+
+ /**
+ *
Sets whether it is allowed to bind multiple sockets to the same
+ * socket address (same host and same port).
+ *
+ * @param reusePort whether binding to same host and port is allowed
+ */
+ public void setReusePort(boolean reusePort)
+ {
+ this.reusePort = reusePort;
+ }
+
+ /**
+ * @return the receive buffer size in bytes, or -1 for the default value
+ */
+ @ManagedAttribute("The receive buffer size in bytes")
+ public int getReceiveBufferSize()
+ {
+ return receiveBufferSize;
+ }
+
+ public void setReceiveBufferSize(int receiveBufferSize)
+ {
+ this.receiveBufferSize = receiveBufferSize;
+ }
+
+ /**
+ * @return the send buffer size in bytes, or -1 for the default value
+ */
+ @ManagedAttribute("The send buffer size in bytes")
+ public int getSendBufferSize()
+ {
+ return sendBufferSize;
+ }
+
+ public void setSendBufferSize(int sendBufferSize)
+ {
+ this.sendBufferSize = sendBufferSize;
+ }
+
@Override
protected void doStart() throws Exception
{
@@ -221,20 +392,18 @@ public void connect(SocketAddress address, Map context)
context.put(ClientConnector.CLIENT_CONNECTOR_CONTEXT_KEY, this);
context.putIfAbsent(REMOTE_SOCKET_ADDRESS_CONTEXT_KEY, address);
- channel = SocketChannel.open();
+ SocketChannelWithAddress channelWithAddress = factory.newSocketChannelWithAddress(address, context);
+ channel = channelWithAddress.getSocketChannel();
+ address = channelWithAddress.getSocketAddress();
+
+ configure(channel);
+
SocketAddress bindAddress = getBindAddress();
if (bindAddress != null)
- {
- boolean reuseAddress = getReuseAddress();
- if (LOG.isDebugEnabled())
- LOG.debug("Binding to {} to connect to {}{}", bindAddress, address, (reuseAddress ? " reusing address" : ""));
- channel.setOption(StandardSocketOptions.SO_REUSEADDR, reuseAddress);
- channel.bind(bindAddress);
- }
- configure(channel);
+ bind(channel, bindAddress);
boolean connected = true;
- boolean blocking = isConnectBlocking();
+ boolean blocking = isConnectBlocking() && address instanceof InetSocketAddress;
if (LOG.isDebugEnabled())
LOG.debug("Connecting {} to {}", blocking ? "blocking" : "non-blocking", address);
if (blocking)
@@ -288,9 +457,37 @@ public void accept(SocketChannel channel, Map context)
}
}
+ private void bind(SocketChannel channel, SocketAddress bindAddress) throws IOException
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Binding {} to {}", channel, bindAddress);
+ channel.bind(bindAddress);
+ }
+
protected void configure(SocketChannel channel) throws IOException
{
- channel.socket().setTcpNoDelay(true);
+ setSocketOption(channel, StandardSocketOptions.TCP_NODELAY, isTCPNoDelay());
+ setSocketOption(channel, StandardSocketOptions.SO_REUSEADDR, getReuseAddress());
+ setSocketOption(channel, StandardSocketOptions.SO_REUSEPORT, isReusePort());
+ int receiveBufferSize = getReceiveBufferSize();
+ if (receiveBufferSize >= 0)
+ setSocketOption(channel, StandardSocketOptions.SO_RCVBUF, receiveBufferSize);
+ int sendBufferSize = getSendBufferSize();
+ if (sendBufferSize >= 0)
+ setSocketOption(channel, StandardSocketOptions.SO_SNDBUF, sendBufferSize);
+ }
+
+ private void setSocketOption(SocketChannel channel, SocketOption option, T value)
+ {
+ try
+ {
+ channel.setOption(option, value);
+ }
+ catch (Throwable x)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Could not configure {} to {} on {}", option, value, channel);
+ }
}
protected EndPoint newEndPoint(SocketChannel channel, ManagedSelector selector, SelectionKey selectionKey)
@@ -351,4 +548,77 @@ protected void connectionFailed(SelectableChannel channel, Throwable failure, Ob
connectFailed(failure, context);
}
}
+
+ /**
+ *
A pair/record holding a {@link SocketChannel} and a {@link SocketAddress} to connect to.
Creates a new {@link SocketChannel} to connect to a {@link SocketAddress}
+ * derived from the input socket address.
+ *
The input socket address represents the destination socket address to
+ * connect to, as it is typically specified by a URI authority, for example
+ * {@code localhost:8080} if the URI is {@code http://localhost:8080/path}.
+ *
However, the returned socket address may be different as the implementation
+ * may use a Unix-Domain socket address to physically connect to the virtual
+ * destination socket address given as input.
+ *
The return type is a pair/record holding the socket channel and the
+ * socket address, with the socket channel not yet connected.
+ * The implementation of this methods must not call
+ * {@link SocketChannel#connect(SocketAddress)}, as this is done later,
+ * after configuring the socket, by the {@link ClientConnector} implementation.
+ *
+ * @param address the destination socket address, typically specified in a URI
+ * @param context the context to create the new socket channel
+ * @return a new {@link SocketChannel} with an associated {@link SocketAddress} to connect to
+ * @throws IOException if the socket channel or the socket address cannot be created
+ */
+ public SocketChannelWithAddress newSocketChannelWithAddress(SocketAddress address, Map context) throws IOException;
+ }
+ }
}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/SocketChannelEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/SocketChannelEndPoint.java
index 1258424ddbb6..2ff61f4ae84c 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/SocketChannelEndPoint.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/SocketChannelEndPoint.java
@@ -165,8 +165,10 @@ public SocketAddress getLocalSocketAddress()
{
return _channel.getLocalAddress();
}
- catch (IOException x)
+ catch (Throwable x)
{
+ if (LOG.isDebugEnabled())
+ LOG.debug("Could not retrieve local socket address", x);
return null;
}
}
@@ -178,8 +180,10 @@ public SocketAddress getRemoteSocketAddress()
{
return _channel.getRemoteAddress();
}
- catch (IOException e)
+ catch (Throwable x)
{
+ if (LOG.isDebugEnabled())
+ LOG.debug("Could not retrieve remote socket address", x);
return null;
}
}
diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/SelectorManagerTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/SelectorManagerTest.java
index 68eb13bb47eb..d93d9dfb6531 100644
--- a/jetty-io/src/test/java/org/eclipse/jetty/io/SelectorManagerTest.java
+++ b/jetty-io/src/test/java/org/eclipse/jetty/io/SelectorManagerTest.java
@@ -22,7 +22,6 @@
import java.nio.channels.SocketChannel;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicLong;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
@@ -30,7 +29,6 @@
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.condition.DisabledIfSystemProperty;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -55,23 +53,21 @@ public void dispose() throws Exception
}
@Test
- @DisabledIfSystemProperty(named = "env", matches = "ci") // TODO: SLOW, needs review
public void testConnectTimeoutBeforeSuccessfulConnect() throws Exception
{
ServerSocketChannel server = ServerSocketChannel.open();
server.bind(new InetSocketAddress("localhost", 0));
SocketAddress address = server.getLocalAddress();
- final AtomicLong timeoutConnection = new AtomicLong();
- final long connectTimeout = 1000;
+ CountDownLatch connectionFinishedLatch = new CountDownLatch(1);
+ CountDownLatch failedConnectionLatch = new CountDownLatch(1);
+ long connectTimeout = 1000;
SelectorManager selectorManager = new SelectorManager(executor, scheduler)
{
@Override
protected EndPoint newEndPoint(SelectableChannel channel, ManagedSelector selector, SelectionKey key)
{
- SocketChannelEndPoint endPoint = new SocketChannelEndPoint((SocketChannel)channel, selector, key, getScheduler());
- endPoint.setIdleTimeout(connectTimeout / 2);
- return endPoint;
+ return new SocketChannelEndPoint((SocketChannel)channel, selector, key, getScheduler());
}
@Override
@@ -79,15 +75,17 @@ protected boolean doFinishConnect(SelectableChannel channel) throws IOException
{
try
{
- long timeout = timeoutConnection.get();
- if (timeout > 0)
- TimeUnit.MILLISECONDS.sleep(timeout);
+ assertTrue(failedConnectionLatch.await(connectTimeout * 2, TimeUnit.MILLISECONDS));
return super.doFinishConnect(channel);
}
catch (InterruptedException e)
{
return false;
}
+ finally
+ {
+ connectionFinishedLatch.countDown();
+ }
}
@Override
@@ -116,40 +114,36 @@ protected void connectionFailed(SelectableChannel channel, Throwable ex, Object
{
SocketChannel client1 = SocketChannel.open();
client1.configureBlocking(false);
- client1.connect(address);
- long timeout = connectTimeout * 2;
- timeoutConnection.set(timeout);
- final CountDownLatch latch1 = new CountDownLatch(1);
+ assertFalse(client1.connect(address));
selectorManager.connect(client1, new Callback()
{
@Override
public void failed(Throwable x)
{
- latch1.countDown();
+ failedConnectionLatch.countDown();
}
});
- assertTrue(latch1.await(connectTimeout * 3, TimeUnit.MILLISECONDS));
+ assertTrue(failedConnectionLatch.await(connectTimeout * 2, TimeUnit.MILLISECONDS));
assertFalse(client1.isOpen());
- // Wait for the first connect to finish, as the selector thread is waiting in finishConnect().
- Thread.sleep(timeout);
+ // Wait for the first connect to finish, as the selector thread is waiting in doFinishConnect().
+ assertTrue(connectionFinishedLatch.await(5, TimeUnit.SECONDS));
// Verify that after the failure we can connect successfully.
try (SocketChannel client2 = SocketChannel.open())
{
client2.configureBlocking(false);
- client2.connect(address);
- timeoutConnection.set(0);
- final CountDownLatch latch2 = new CountDownLatch(1);
+ assertFalse(client2.connect(address));
+ CountDownLatch successfulConnectionLatch = new CountDownLatch(1);
selectorManager.connect(client2, new Callback()
{
@Override
public void succeeded()
{
- latch2.countDown();
+ successfulConnectionLatch.countDown();
}
});
- assertTrue(latch2.await(connectTimeout * 5, TimeUnit.MILLISECONDS));
+ assertTrue(successfulConnectionLatch.await(connectTimeout * 2, TimeUnit.MILLISECONDS));
assertTrue(client2.isOpen());
}
}
diff --git a/jetty-jndi/pom.xml b/jetty-jndi/pom.xml
index 7caf00bf284e..7bbd856a06b3 100644
--- a/jetty-jndi/pom.xml
+++ b/jetty-jndi/pom.xml
@@ -60,14 +60,19 @@
org.eclipse.jetty
- jetty-webapp
+ jetty-server
+ ${project.version}
+ provided
+
+
+ org.eclipse.jetty
+ jetty-security${project.version}providedorg.eclipse.jetty.orbitjavax.mail.glassfish
- 1.4.1.v201005082020provided
diff --git a/jetty-memcached/jetty-memcached-sessions/src/main/java/org/eclipse/jetty/memcached/session/MemcachedSessionDataMap.java b/jetty-memcached/jetty-memcached-sessions/src/main/java/org/eclipse/jetty/memcached/session/MemcachedSessionDataMap.java
index 531da8eaf1b3..0d503bc5dba6 100644
--- a/jetty-memcached/jetty-memcached-sessions/src/main/java/org/eclipse/jetty/memcached/session/MemcachedSessionDataMap.java
+++ b/jetty-memcached/jetty-memcached-sessions/src/main/java/org/eclipse/jetty/memcached/session/MemcachedSessionDataMap.java
@@ -25,6 +25,7 @@
import org.eclipse.jetty.server.session.SessionData;
import org.eclipse.jetty.server.session.SessionDataMap;
import org.eclipse.jetty.util.ClassLoadingObjectInputStream;
+import org.eclipse.jetty.util.FuturePromise;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
@@ -43,6 +44,7 @@ public class MemcachedSessionDataMap extends AbstractLifeCycle implements Sessio
protected int _expirySec = 0;
protected boolean _heartbeats = true;
protected XMemcachedClientBuilder _builder;
+ protected SessionContext _context;
/**
* SessionDataTranscoder
@@ -140,8 +142,12 @@ public void setHeartbeats(boolean heartbeats)
@Override
public void initialize(SessionContext context)
{
+ if (isStarted())
+ throw new IllegalStateException("Context set after MemcachedSessionDataMap started");
+
try
{
+ _context = context;
_builder.setTranscoder(new SessionDataTranscoder());
_client = _builder.build();
_client.setEnableHeartBeat(isHeartbeats());
@@ -155,14 +161,48 @@ public void initialize(SessionContext context)
@Override
public SessionData load(String id) throws Exception
{
- SessionData data = _client.get(id);
- return data;
+ if (!isStarted())
+ throw new IllegalStateException("Not started");
+
+ final FuturePromise result = new FuturePromise<>();
+
+ Runnable r = () ->
+ {
+ try
+ {
+ result.succeeded(_client.get(id));
+ }
+ catch (Exception e)
+ {
+ result.failed(e);
+ }
+ };
+
+ _context.run(r);
+ return result.getOrThrow();
}
@Override
public void store(String id, SessionData data) throws Exception
{
- _client.set(id, _expirySec, data);
+ if (!isStarted())
+ throw new IllegalStateException("Not started");
+
+ final FuturePromise result = new FuturePromise<>();
+ Runnable r = () ->
+ {
+ try
+ {
+ _client.set(id, _expirySec, data);
+ result.succeeded(null);
+ }
+ catch (Exception e)
+ {
+ result.failed(e);
+ }
+ };
+ _context.run(r);
+ result.getOrThrow();
}
@Override
diff --git a/jetty-openid/src/main/config/etc/jetty-openid.xml b/jetty-openid/src/main/config/etc/jetty-openid.xml
index 5072c1604951..8e252c22f809 100644
--- a/jetty-openid/src/main/config/etc/jetty-openid.xml
+++ b/jetty-openid/src/main/config/etc/jetty-openid.xml
@@ -26,6 +26,7 @@
+
diff --git a/jetty-openid/src/main/config/modules/openid.mod b/jetty-openid/src/main/config/modules/openid.mod
index 85f0de4c8899..7f04767a2d92 100644
--- a/jetty-openid/src/main/config/modules/openid.mod
+++ b/jetty-openid/src/main/config/modules/openid.mod
@@ -42,3 +42,6 @@ etc/jetty-openid.xml
## True if all certificates should be trusted by the default SslContextFactory
# jetty.openid.sslContextFactory.trustAll=false
+
+## What authentication method to use with the Token Endpoint (client_secret_post, client_secret_basic).
+# jetty.openid.authMethod=client_secret_post
diff --git a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/JwtDecoder.java b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/JwtDecoder.java
index dfdbd511f8f2..69d03a478bc5 100644
--- a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/JwtDecoder.java
+++ b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/JwtDecoder.java
@@ -35,6 +35,7 @@ public class JwtDecoder
* @param jwt the JWT to decode.
* @return the map of claims encoded in the JWT.
*/
+ @SuppressWarnings("unchecked")
public static Map decode(String jwt)
{
if (LOG.isDebugEnabled())
@@ -54,7 +55,6 @@ public static Map decode(String jwt)
Object parsedJwtHeader = json.fromJSON(jwtHeaderString);
if (!(parsedJwtHeader instanceof Map))
throw new IllegalStateException("Invalid JWT header");
- @SuppressWarnings("unchecked")
Map jwtHeader = (Map)parsedJwtHeader;
if (LOG.isDebugEnabled())
LOG.debug("JWT Header: {}", jwtHeader);
diff --git a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdConfiguration.java b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdConfiguration.java
index a1a82fe2b0e5..8a60ec3418c6 100644
--- a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdConfiguration.java
+++ b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdConfiguration.java
@@ -45,6 +45,7 @@ public class OpenIdConfiguration extends ContainerLifeCycle
private final String clientId;
private final String clientSecret;
private final List scopes = new ArrayList<>();
+ private final String authMethod;
private String authEndpoint;
private String tokenEndpoint;
@@ -70,6 +71,22 @@ public OpenIdConfiguration(String provider, String clientId, String clientSecret
*/
public OpenIdConfiguration(String issuer, String authorizationEndpoint, String tokenEndpoint,
String clientId, String clientSecret, HttpClient httpClient)
+ {
+ this(issuer, authorizationEndpoint, tokenEndpoint, clientId, clientSecret, "client_secret_post", httpClient);
+ }
+
+ /**
+ * Create an OpenID configuration for a specific OIDC provider.
+ * @param issuer The URL of the OpenID provider.
+ * @param authorizationEndpoint the URL of the OpenID provider's authorization endpoint if configured.
+ * @param tokenEndpoint the URL of the OpenID provider's token endpoint if configured.
+ * @param clientId OAuth 2.0 Client Identifier valid at the Authorization Server.
+ * @param clientSecret The client secret known only by the Client and the Authorization Server.
+ * @param authMethod Authentication method to use with the Token Endpoint.
+ * @param httpClient The {@link HttpClient} instance to use.
+ */
+ public OpenIdConfiguration(String issuer, String authorizationEndpoint, String tokenEndpoint,
+ String clientId, String clientSecret, String authMethod, HttpClient httpClient)
{
this.issuer = issuer;
this.clientId = clientId;
@@ -77,6 +94,7 @@ public OpenIdConfiguration(String issuer, String authorizationEndpoint, String t
this.authEndpoint = authorizationEndpoint;
this.tokenEndpoint = tokenEndpoint;
this.httpClient = httpClient != null ? httpClient : newHttpClient();
+ this.authMethod = authMethod;
if (this.issuer == null)
throw new IllegalArgumentException("Issuer was not configured");
@@ -177,6 +195,11 @@ public String getTokenEndpoint()
return tokenEndpoint;
}
+ public String getAuthMethod()
+ {
+ return authMethod;
+ }
+
public void addScopes(String... scopes)
{
if (scopes != null)
diff --git a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdCredentials.java b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdCredentials.java
index c87ef1604f26..dfc748325f77 100644
--- a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdCredentials.java
+++ b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdCredentials.java
@@ -14,12 +14,16 @@
package org.eclipse.jetty.security.openid;
import java.io.Serializable;
+import java.net.URI;
import java.util.Arrays;
+import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
+import org.eclipse.jetty.client.api.Authentication;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.util.BasicAuthentication;
import org.eclipse.jetty.client.util.FormRequestContent;
import org.eclipse.jetty.util.Fields;
import org.eclipse.jetty.util.ajax.JSON;
@@ -45,6 +49,14 @@ public class OpenIdCredentials implements Serializable
private String authCode;
private Map response;
private Map claims;
+ private boolean verified = false;
+
+ public OpenIdCredentials(Map claims)
+ {
+ this.redirectUri = null;
+ this.authCode = null;
+ this.claims = claims;
+ }
public OpenIdCredentials(String authCode, String redirectUri)
{
@@ -95,7 +107,6 @@ public void redeemAuthCode(OpenIdConfiguration configuration) throws Exception
claims = JwtDecoder.decode(idToken);
if (LOG.isDebugEnabled())
LOG.debug("claims {}", claims);
- validateClaims(configuration);
}
finally
{
@@ -103,6 +114,12 @@ public void redeemAuthCode(OpenIdConfiguration configuration) throws Exception
authCode = null;
}
}
+
+ if (!verified)
+ {
+ validateClaims(configuration);
+ verified = true;
+ }
}
private void validateClaims(OpenIdConfiguration configuration) throws Exception
@@ -138,10 +155,11 @@ private void validateAudience(OpenIdConfiguration configuration) throws Authenti
throw new AuthenticationException("Audience Claim MUST contain the client_id value");
else if (isList)
{
- if (!Arrays.asList((Object[])aud).contains(clientId))
+ List
+
+ org.awaitility
+ awaitility
+ test
+ org.eclipse.jetty.toolchainjetty-perf-helper
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/FuturePromise.java b/jetty-util/src/main/java/org/eclipse/jetty/util/FuturePromise.java
index ad8ab65aa207..deae5c669c0a 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/FuturePromise.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/FuturePromise.java
@@ -118,6 +118,30 @@ public C get() throws InterruptedException, ExecutionException
throw (CancellationException)new CancellationException().initCause(_cause);
throw new ExecutionException(_cause);
}
+
+ /**
+ * Return the result if completed successfully
+ * or in the case of failure, throw the
+ * Exception/Error, or an ExecutionException wrapping
+ * the cause if it is neither an Exception or Error.
+ *
+ * @return the computed result
+ * @throws Exception if the cause is an Exception or Error,
+ * otherwise an ExecutionException wrapping the cause
+ */
+ public C getOrThrow() throws Exception
+ {
+ _latch.await();
+
+ if (_cause == COMPLETED)
+ return _result;
+ if (_cause instanceof Exception)
+ throw (Exception)_cause;
+ if (_cause instanceof Error)
+ throw (Error)_cause;
+
+ throw new ExecutionException(_cause);
+ }
@Override
public C get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ssl/SslContextFactory.java b/jetty-util/src/main/java/org/eclipse/jetty/util/ssl/SslContextFactory.java
index 41f36af02833..c3470f4124ac 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/ssl/SslContextFactory.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/ssl/SslContextFactory.java
@@ -2180,6 +2180,7 @@ private static List getSniServerNames(SSLEngine sslEngine, ListCalls to {@link #execute(Runnable)} on a {@link ReservedThreadExecutor} will either succeed
* with a Thread immediately being assigned the Runnable task, or fail if no Thread is
* available.
- *
Threads are reserved lazily, with a new reserved thread being allocated from a
- * wrapped {@link Executor} when an execution fails. If the {@link #setIdleTimeout(long, TimeUnit)}
- * is set to non zero (default 1 minute), then the reserved thread pool will shrink by 1 thread
- * whenever it has been idle for that period.
+ *
Threads are reserved lazily, with a new reserved threads being allocated from the
+ * {@link Executor} passed to the constructor. Whenever 1 or more reserved threads have been
+ * idle for more than {@link #getIdleTimeoutMs()} then one reserved thread will return to
+ * the executor.
*/
@ManagedObject("A pool for reserved threads")
-public class ReservedThreadExecutor extends AbstractLifeCycle implements TryExecutor
+public class ReservedThreadExecutor extends AbstractLifeCycle implements TryExecutor, Dumpable
{
private static final Logger LOG = LoggerFactory.getLogger(ReservedThreadExecutor.class);
+ private static final long DEFAULT_IDLE_TIMEOUT = TimeUnit.MINUTES.toNanos(1);
private static final Runnable STOP = new Runnable()
{
@Override
@@ -57,13 +68,13 @@ public String toString()
private final Executor _executor;
private final int _capacity;
- private final ConcurrentLinkedDeque _stack;
- private final AtomicInteger _size = new AtomicInteger();
- private final AtomicInteger _pending = new AtomicInteger();
+ private final Set _threads = ConcurrentHashMap.newKeySet();
+ private final SynchronousQueue _queue = new SynchronousQueue<>(false);
+ private final AtomicBiInteger _count = new AtomicBiInteger(); // hi=pending; lo=size;
+ private final AtomicLong _lastEmptyTime = new AtomicLong(System.nanoTime());
private ThreadPoolBudget.Lease _lease;
- private long _idleTime = 1L;
- private TimeUnit _idleTimeUnit = TimeUnit.MINUTES;
+ private long _idleTimeNanos = DEFAULT_IDLE_TIMEOUT;
/**
* @param executor The executor to use to obtain threads
@@ -75,7 +86,6 @@ public ReservedThreadExecutor(Executor executor, int capacity)
{
_executor = executor;
_capacity = reservedThreads(executor, capacity);
- _stack = new ConcurrentLinkedDeque<>();
if (LOG.isDebugEnabled())
LOG.debug("{}", this);
}
@@ -121,42 +131,39 @@ public int getCapacity()
@ManagedAttribute(value = "available reserved threads", readonly = true)
public int getAvailable()
{
- return _stack.size();
+ return _count.getLo();
}
@ManagedAttribute(value = "pending reserved threads", readonly = true)
public int getPending()
{
- return _pending.get();
+ return _count.getHi();
}
- @ManagedAttribute(value = "idletimeout in MS", readonly = true)
+ @ManagedAttribute(value = "idle timeout in ms", readonly = true)
public long getIdleTimeoutMs()
{
- if (_idleTimeUnit == null)
- return 0;
- return _idleTimeUnit.toMillis(_idleTime);
+ return NANOSECONDS.toMillis(_idleTimeNanos);
}
/**
* Set the idle timeout for shrinking the reserved thread pool
*
- * @param idleTime Time to wait before shrinking, or 0 for no timeout.
+ * @param idleTime Time to wait before shrinking, or 0 for default timeout.
* @param idleTimeUnit Time units for idle timeout
*/
public void setIdleTimeout(long idleTime, TimeUnit idleTimeUnit)
{
if (isRunning())
throw new IllegalStateException();
- _idleTime = idleTime;
- _idleTimeUnit = idleTimeUnit;
+ _idleTimeNanos = (idleTime <= 0 || idleTimeUnit == null) ? DEFAULT_IDLE_TIMEOUT : idleTimeUnit.toNanos(idleTime);
}
@Override
public void doStart() throws Exception
{
_lease = ThreadPoolBudget.leaseFrom(getExecutor(), this, _capacity);
- _size.set(0);
+ _count.set(0, 0);
super.doStart();
}
@@ -168,26 +175,22 @@ public void doStop() throws Exception
super.doStop();
- while (true)
+ // Offer STOP task to all waiting reserved threads.
+ for (int i = _count.getAndSetLo(-1); i-- > 0;)
{
- int size = _size.get();
- // If no reserved threads left try setting size to -1 to
- // atomically prevent other threads adding themselves to stack.
- if (size == 0 && _size.compareAndSet(size, -1))
- break;
-
- ReservedThread thread = _stack.pollFirst();
- if (thread == null)
- {
- // Reserved thread must have incremented size but not yet added itself to queue.
- // We will spin until it is added.
- Thread.onSpinWait();
- continue;
- }
-
- _size.decrementAndGet();
- thread.stop();
+ // yield to wait for any reserved threads that have incremented the size but not yet polled
+ Thread.yield();
+ _queue.offer(STOP);
}
+ // Interrupt any reserved thread missed the offer so it doesn't wait too long.
+ for (ReservedThread reserved : _threads)
+ {
+ Thread thread = reserved._thread;
+ if (thread != null)
+ thread.interrupt();
+ }
+ _threads.clear();
+ _count.getAndSetHi(0);
}
@Override
@@ -207,52 +210,61 @@ public boolean tryExecute(Runnable task)
{
if (LOG.isDebugEnabled())
LOG.debug("{} tryExecute {}", this, task);
-
if (task == null)
return false;
- ReservedThread thread = _stack.pollFirst();
- if (thread == null)
- {
- if (task != STOP)
- startReservedThread();
- return false;
- }
+ // Offer will only succeed if there is a reserved thread waiting
+ boolean offered = _queue.offer(task);
- int size = _size.decrementAndGet();
- if (!thread.offer(task))
- return false;
+ // If the offer succeeded we need to reduce the size, unless it is set to -1 in the meantime
+ int size = _count.getLo();
+ while (offered && size > 0 && !_count.compareAndSetLo(size, --size))
+ size = _count.getLo();
+ // If size is 0 and we are not stopping, start a new reserved thread
if (size == 0 && task != STOP)
startReservedThread();
- return true;
+ return offered;
}
private void startReservedThread()
{
- try
+ while (true)
{
- while (true)
+ long count = _count.get();
+ int pending = getHi(count);
+ int size = getLo(count);
+ if (size < 0 || pending + size >= _capacity)
+ return;
+ if (size == 0)
+ _lastEmptyTime.set(System.nanoTime());
+ if (!_count.compareAndSet(count, pending + 1, size))
+ continue;
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("{} startReservedThread p={}", this, pending + 1);
+ try
{
- // Not atomic, but there is a re-check in ReservedThread.run().
- int pending = _pending.get();
- int size = _size.get();
- if (pending + size >= _capacity)
- return;
- if (_pending.compareAndSet(pending, pending + 1))
- {
- if (LOG.isDebugEnabled())
- LOG.debug("{} startReservedThread p={}", this, pending + 1);
- _executor.execute(new ReservedThread());
- return;
- }
+ ReservedThread thread = new ReservedThread();
+ _threads.add(thread);
+ _executor.execute(thread);
}
+ catch (Throwable e)
+ {
+ _count.add(-1, 0);
+ if (LOG.isDebugEnabled())
+ LOG.debug("ignored", e);
+ }
+ return;
}
- catch (RejectedExecutionException e)
- {
- LOG.trace("IGNORED", e);
- }
+ }
+
+ @Override
+ public void dump(Appendable out, String indent) throws IOException
+ {
+ Dumpable.dumpObjects(out, indent, this,
+ new DumpableCollection("reserved", _threads));
}
@Override
@@ -261,136 +273,149 @@ public String toString()
return String.format("%s@%x{s=%d/%d,p=%d}",
getClass().getSimpleName(),
hashCode(),
- _size.get(),
+ _count.getLo(),
_capacity,
- _pending.get());
+ _count.getHi());
}
- private class ReservedThread implements Runnable
+ private enum State
{
- private final SynchronousQueue _task = new SynchronousQueue<>();
- private boolean _starting = true;
-
- public boolean offer(Runnable task)
- {
- if (LOG.isDebugEnabled())
- LOG.debug("{} offer {}", this, task);
-
- try
- {
- _task.put(task);
- return true;
- }
- catch (Throwable e)
- {
- LOG.trace("IGNORED", e);
- _size.getAndIncrement();
- _stack.offerFirst(this);
- return false;
- }
- }
+ PENDING,
+ RESERVED,
+ RUNNING,
+ IDLE,
+ STOPPED
+ }
- public void stop()
- {
- offer(STOP);
- }
+ private class ReservedThread implements Runnable
+ {
+ // The state and thread are kept only for dumping
+ private volatile State _state = State.PENDING;
+ private volatile Thread _thread;
private Runnable reservedWait()
{
if (LOG.isDebugEnabled())
- LOG.debug("{} waiting", this);
+ LOG.debug("{} waiting {}", this, ReservedThreadExecutor.this);
- while (true)
+ // Keep waiting until stopped, tasked or idle
+ while (_count.getLo() >= 0)
{
try
{
- Runnable task = _idleTime <= 0 ? _task.take() : _task.poll(_idleTime, _idleTimeUnit);
+ // Always poll at some period as safety to ensure we don't poll forever.
+ Runnable task = _queue.poll(_idleTimeNanos, NANOSECONDS);
if (LOG.isDebugEnabled())
- LOG.debug("{} task={}", this, task);
+ LOG.debug("{} task={} {}", this, task, ReservedThreadExecutor.this);
if (task != null)
return task;
- if (_stack.remove(this))
+ // we have idled out
+ int size = _count.getLo();
+ // decrement size if we have not also been stopped.
+ while (size > 0)
{
- if (LOG.isDebugEnabled())
- LOG.debug("{} IDLE", this);
- _size.decrementAndGet();
- return STOP;
+ if (_count.compareAndSetLo(size, --size))
+ break;
+ size = _count.getLo();
}
+ _state = size >= 0 ? State.IDLE : State.STOPPED;
+ return STOP;
+
}
catch (InterruptedException e)
{
- LOG.trace("IGNORED", e);
+ if (LOG.isDebugEnabled())
+ LOG.debug("ignored", e);
}
}
+ _state = State.STOPPED;
+ return STOP;
}
@Override
public void run()
{
- while (isRunning())
+ _thread = Thread.currentThread();
+ try
{
- // test and increment size BEFORE decrementing pending,
- // so that we don't have a race starting new pending.
- int size = _size.get();
+ while (true)
+ {
+ long count = _count.get();
- // Are we stopped?
- if (size < 0)
- return;
+ // reduce pending if this thread was pending
+ int pending = getHi(count) - (_state == State.PENDING ? 1 : 0);
+ int size = getLo(count);
- // Are we surplus to capacity?
- if (size >= _capacity)
- {
- if (LOG.isDebugEnabled())
- LOG.debug("{} size {} > capacity {}", this, size, _capacity);
- if (_starting)
- _pending.decrementAndGet();
- return;
- }
+ State next;
+ if (size < 0 || size >= _capacity)
+ {
+ // The executor has stopped or this thread is excess to capacity
+ next = State.STOPPED;
+ }
+ else
+ {
+ long now = System.nanoTime();
+ long lastEmpty = _lastEmptyTime.get();
+ if (size > 0 && _idleTimeNanos < (now - lastEmpty) && _lastEmptyTime.compareAndSet(lastEmpty, now))
+ {
+ // it has been too long since we hit zero reserved threads, so are "busy" idle
+ next = State.IDLE;
+ }
+ else
+ {
+ // We will become a reserved thread if we can update the count below.
+ next = State.RESERVED;
+ size++;
+ }
+ }
- // If we cannot update size then recalculate
- if (!_size.compareAndSet(size, size + 1))
- continue;
+ // Update count for pending and size
+ if (!_count.compareAndSet(count, pending, size))
+ continue;
- if (_starting)
- {
if (LOG.isDebugEnabled())
- LOG.debug("{} started", this);
- _pending.decrementAndGet();
- _starting = false;
- }
+ LOG.debug("{} was={} next={} size={}+{} capacity={}", this, _state, next, pending, size, _capacity);
+ _state = next;
+ if (next != State.RESERVED)
+ break;
- // Insert ourselves in the stack. Size is already incremented, but
- // that only effects the decision to keep other threads reserved.
- _stack.offerFirst(this);
+ // We are reserved whilst we are waiting for an offered _task.
+ Runnable task = reservedWait();
- // Once added to the stack, we must always wait for a job on the _task Queue
- // and never return early, else we may leave a thread blocked offering a _task.
- Runnable task = reservedWait();
+ // Is the task the STOP poison pill?
+ if (task == STOP)
+ break;
- if (task == STOP)
- // return on STOP poison pill
- break;
-
- // Run the task
- try
- {
- task.run();
- }
- catch (Throwable e)
- {
- LOG.warn("Unable to run task", e);
+ // Run the task
+ try
+ {
+ _state = State.RUNNING;
+ task.run();
+ }
+ catch (Throwable e)
+ {
+ LOG.warn("Unable to run task", e);
+ }
}
}
-
- if (LOG.isDebugEnabled())
- LOG.debug("{} Exited", this);
+ finally
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("{} exited {}", this, ReservedThreadExecutor.this);
+ _threads.remove(this);
+ _thread = null;
+ }
}
@Override
public String toString()
{
- return String.format("%s@%x", ReservedThreadExecutor.this, hashCode());
+ return String.format("%s@%x{%s,thread=%s}",
+ getClass().getSimpleName(),
+ hashCode(),
+ _state,
+ _thread);
}
}
-}
+}
\ No newline at end of file
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/BlockingArrayQueueTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/BlockingArrayQueueTest.java
index c3bc28a204b4..ba6da7943ad4 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/BlockingArrayQueueTest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/BlockingArrayQueueTest.java
@@ -14,22 +14,24 @@
package org.eclipse.jetty.util;
import java.util.ArrayList;
-import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
-import java.util.Random;
-import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.condition.DisabledIfSystemProperty;
+import static org.awaitility.Awaitility.await;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
@@ -161,12 +163,12 @@ public void testGrow() throws Exception
}
@Test
- @DisabledIfSystemProperty(named = "env", matches = "ci") // TODO: SLOW, needs review
public void testTake() throws Exception
{
final String[] data = new String[4];
final BlockingArrayQueue queue = new BlockingArrayQueue<>();
+ CyclicBarrier barrier = new CyclicBarrier(2);
Thread thread = new Thread()
{
@@ -177,7 +179,7 @@ public void run()
{
data[0] = queue.take();
data[1] = queue.take();
- Thread.sleep(1000);
+ barrier.await(5, TimeUnit.SECONDS); // Wait until the main thread already called offer().
data[2] = queue.take();
data[3] = queue.poll(100, TimeUnit.MILLISECONDS);
}
@@ -191,35 +193,36 @@ public void run()
thread.start();
- Thread.sleep(1000);
+ // Wait until the spawned thread is blocked in queue.take().
+ await().atMost(5, TimeUnit.SECONDS).until(() -> thread.getState() == Thread.State.WAITING);
queue.offer("zero");
queue.offer("one");
queue.offer("two");
+ barrier.await(5, TimeUnit.SECONDS); // Notify the spawned thread that offer() was called.
thread.join();
assertEquals("zero", data[0]);
assertEquals("one", data[1]);
assertEquals("two", data[2]);
- assertEquals(null, data[3]);
+ assertNull(data[3]);
}
@Test
- @DisabledIfSystemProperty(named = "env", matches = "ci") // TODO: SLOW, needs review
public void testConcurrentAccess() throws Exception
{
- final int THREADS = 50;
+ final int THREADS = 32;
final int LOOPS = 1000;
- final BlockingArrayQueue queue = new BlockingArrayQueue<>(1 + THREADS * LOOPS);
+ BlockingArrayQueue queue = new BlockingArrayQueue<>(1 + THREADS * LOOPS);
- final ConcurrentLinkedQueue produced = new ConcurrentLinkedQueue<>();
- final ConcurrentLinkedQueue consumed = new ConcurrentLinkedQueue<>();
+ Set produced = ConcurrentHashMap.newKeySet();
+ Set consumed = ConcurrentHashMap.newKeySet();
- final AtomicBoolean running = new AtomicBoolean(true);
+ AtomicBoolean consumersRunning = new AtomicBoolean(true);
// start consumers
- final CyclicBarrier barrier0 = new CyclicBarrier(THREADS + 1);
+ CyclicBarrier consumersBarrier = new CyclicBarrier(THREADS + 1);
for (int i = 0; i < THREADS; i++)
{
new Thread()
@@ -227,20 +230,18 @@ public void testConcurrentAccess() throws Exception
@Override
public void run()
{
- final Random random = new Random();
-
setPriority(getPriority() - 1);
try
{
- while (running.get())
+ while (consumersRunning.get())
{
- int r = 1 + random.nextInt(10);
+ int r = 1 + ThreadLocalRandom.current().nextInt(10);
if (r % 2 == 0)
{
Integer msg = queue.poll();
if (msg == null)
{
- Thread.sleep(1 + random.nextInt(10));
+ Thread.sleep(ThreadLocalRandom.current().nextInt(2));
continue;
}
consumed.add(msg);
@@ -261,7 +262,7 @@ public void run()
{
try
{
- barrier0.await();
+ consumersBarrier.await();
}
catch (Exception e)
{
@@ -273,7 +274,7 @@ public void run()
}
// start producers
- final CyclicBarrier barrier1 = new CyclicBarrier(THREADS + 1);
+ CyclicBarrier producersBarrier = new CyclicBarrier(THREADS + 1);
for (int i = 0; i < THREADS; i++)
{
final int id = i;
@@ -282,16 +283,15 @@ public void run()
@Override
public void run()
{
- final Random random = new Random();
try
{
for (int j = 0; j < LOOPS; j++)
{
- Integer msg = random.nextInt();
+ Integer msg = ThreadLocalRandom.current().nextInt();
produced.add(msg);
if (!queue.offer(msg))
throw new Exception(id + " FULL! " + queue.size());
- Thread.sleep(1 + random.nextInt(10));
+ Thread.sleep(ThreadLocalRandom.current().nextInt(2));
}
}
catch (Exception e)
@@ -302,7 +302,7 @@ public void run()
{
try
{
- barrier1.await();
+ producersBarrier.await();
}
catch (Exception e)
{
@@ -313,22 +313,22 @@ public void run()
}.start();
}
- barrier1.await();
- int size = queue.size();
- int last = size - 1;
- while (size > 0 && size != last)
+ producersBarrier.await();
+
+ AtomicInteger last = new AtomicInteger(queue.size() - 1);
+ await().atMost(5, TimeUnit.SECONDS).until(() ->
{
- last = size;
- Thread.sleep(500);
- size = queue.size();
- }
- running.set(false);
- barrier0.await();
+ int size = queue.size();
+ if (size == 0 && last.get() == size)
+ return true;
+ last.set(size);
+ return false;
+ });
- HashSet prodSet = new HashSet<>(produced);
- HashSet consSet = new HashSet<>(consumed);
+ consumersRunning.set(false);
+ consumersBarrier.await();
- assertEquals(prodSet, consSet);
+ assertEquals(produced, consumed);
}
@Test
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/thread/ReservedThreadExecutorTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/thread/ReservedThreadExecutorTest.java
index 5f86fa2b1be1..e3a08b876bf3 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/thread/ReservedThreadExecutorTest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/thread/ReservedThreadExecutorTest.java
@@ -22,10 +22,10 @@
import org.junit.jupiter.api.AfterEach;
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.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -180,6 +180,35 @@ public void testShrink() throws Exception
assertThat(_reservedExecutor.getAvailable(), is(0));
}
+ @Test
+ public void testBusyShrink() throws Exception
+ {
+ final long IDLE = 1000;
+
+ _reservedExecutor.stop();
+ _reservedExecutor.setIdleTimeout(IDLE, TimeUnit.MILLISECONDS);
+ _reservedExecutor.start();
+ assertThat(_reservedExecutor.getAvailable(), is(0));
+
+ assertThat(_reservedExecutor.tryExecute(NOOP), is(false));
+ assertThat(_reservedExecutor.tryExecute(NOOP), is(false));
+
+ _executor.startThread();
+ _executor.startThread();
+
+ waitForAvailable(2);
+
+ int available = _reservedExecutor.getAvailable();
+ assertThat(available, is(2));
+
+ for (int i = 10; i-- > 0;)
+ {
+ assertThat(_reservedExecutor.tryExecute(NOOP), is(true));
+ Thread.sleep(200);
+ }
+ assertThat(_reservedExecutor.getAvailable(), is(1));
+ }
+
@Test
public void testReservedIdleTimeoutWithOneReservedThread() throws Exception
{
@@ -261,7 +290,6 @@ public void run()
}
}
- @Disabled
@Test
public void stressTest() throws Exception
{
@@ -271,9 +299,9 @@ public void stressTest() throws Exception
reserved.setIdleTimeout(0, null);
reserved.start();
- final int LOOPS = 1000000;
+ final int LOOPS = 200000;
final AtomicInteger executions = new AtomicInteger(LOOPS);
- final CountDownLatch executed = new CountDownLatch(executions.get());
+ final CountDownLatch executed = new CountDownLatch(LOOPS);
final AtomicInteger usedReserved = new AtomicInteger(0);
final AtomicInteger usedPool = new AtomicInteger(0);
@@ -322,10 +350,15 @@ public void run()
assertTrue(executed.await(60, TimeUnit.SECONDS));
+ // ensure tryExecute is still working
+ while (!reserved.tryExecute(() -> {}))
+ Thread.yield();
+
reserved.stop();
pool.stop();
+ assertThat(usedReserved.get(), greaterThan(0));
assertThat(usedReserved.get() + usedPool.get(), is(LOOPS));
- System.err.printf("reserved=%d pool=%d total=%d%n", usedReserved.get(), usedPool.get(), LOOPS);
+ // System.err.printf("reserved=%d pool=%d total=%d%n", usedReserved.get(), usedPool.get(), LOOPS);
}
}
diff --git a/jetty-webapp/pom.xml b/jetty-webapp/pom.xml
index 94a4a4b28437..17d4dc26586e 100644
--- a/jetty-webapp/pom.xml
+++ b/jetty-webapp/pom.xml
@@ -80,13 +80,6 @@
org.slf4jslf4j-api
This configuration configures the WebAppContext server/system classes to
* be able to see the org.eclipse.jetty.jaas package.
* This class is defined in the webapp package, as it implements the {@link Configuration} interface,
- * which is unknown to the jaas package. However, the corresponding {@link ServiceLoader}
- * resource is defined in the jaas package, so that this configuration only be
- * loaded if the jetty-jaas jars are on the classpath.
+ * which is unknown to the jaas package.
*
*/
public class JaasConfiguration extends AbstractConfiguration
diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JettyWebXmlConfiguration.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JettyWebXmlConfiguration.java
index aba74957f58b..7dccaaea820a 100644
--- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JettyWebXmlConfiguration.java
+++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JettyWebXmlConfiguration.java
@@ -24,7 +24,7 @@
/**
* JettyWebConfiguration.
*
- * Looks for XmlConfiguration files in WEB-INF. Searches in order for the first of jetty6-web.xml, jetty-web.xml or web-jetty.xml
+ * Looks for XmlConfiguration files in WEB-INF. Searches in order for the first of jetty8-web.xml, jetty-web.xml or web-jetty.xml
*/
public class JettyWebXmlConfiguration extends AbstractConfiguration
{
diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JmxConfiguration.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JmxConfiguration.java
index 2eebbbebad68..ed9cef8fd595 100644
--- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JmxConfiguration.java
+++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JmxConfiguration.java
@@ -13,8 +13,6 @@
package org.eclipse.jetty.webapp;
-import java.util.ServiceLoader;
-
import org.eclipse.jetty.util.Loader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -24,9 +22,7 @@
*
This configuration configures the WebAppContext server/system classes to
* be able to see the org.eclipse.jetty.jmx package. This class is defined
* in the webapp package, as it implements the {@link Configuration} interface,
- * which is unknown to the jmx package. However, the corresponding {@link ServiceLoader}
- * resource is defined in the jmx package, so that this configuration only be
- * loaded if the jetty-jmx jars are on the classpath.
+ * which is unknown to the jmx package.
*
This configuration configures the WebAppContext system/server classes to
* be able to see the org.eclipse.jetty.jaas package.
* This class is defined in the webapp package, as it implements the {@link Configuration} interface,
- * which is unknown to the jndi package. However, the corresponding {@link ServiceLoader}
- * resource is defined in the jndi package, so that this configuration only be
- * loaded if the jetty-jndi jars are on the classpath.
+ * which is unknown to the jndi package.
*
This configuration configures the WebAppContext server/system classes to
* be able to see the org.eclipse.jetty.jsp and org.eclipse.jetty.apache packages.
* This class is defined in the webapp package, as it implements the {@link Configuration} interface,
- * which is unknown to the jsp package. However, the corresponding {@link ServiceLoader}
- * resource is defined in the jsp package, so that this configuration only be
- * loaded if the jetty-jsp jars are on the classpath.
+ * which is unknown to the jsp package.
*
*/
public class JspConfiguration extends AbstractConfiguration
diff --git a/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/TempDirTest.java b/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/TempDirTest.java
index 97803758d4a3..4f95f09fd1d9 100644
--- a/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/TempDirTest.java
+++ b/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/TempDirTest.java
@@ -27,6 +27,7 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.DisabledIfSystemProperty;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
@@ -289,9 +290,11 @@ public void jettyBaseWorkExists() throws Exception
/**
* ServletContext.TEMPDIR has invalid String directory value (wrong permission to write into it)
- * IllegalStateException
+ *
+ * Note that if run in the CI environment, the test will fail, because it runs as root,
+ * so we _will_ have permission to write to this directory.
*/
- @Disabled("Jenkins will run as root so we do have permission to write to this directory.")
+ @DisabledIfSystemProperty(named = "env", matches = "ci")
@Test
public void attributeWithInvalidPermissions()
{
diff --git a/jetty-websocket/websocket-core-client/src/main/java/org/eclipse/jetty/websocket/core/client/WebSocketCoreClient.java b/jetty-websocket/websocket-core-client/src/main/java/org/eclipse/jetty/websocket/core/client/WebSocketCoreClient.java
index c8db7f1d82f3..950231f0fa17 100644
--- a/jetty-websocket/websocket-core-client/src/main/java/org/eclipse/jetty/websocket/core/client/WebSocketCoreClient.java
+++ b/jetty-websocket/websocket-core-client/src/main/java/org/eclipse/jetty/websocket/core/client/WebSocketCoreClient.java
@@ -40,13 +40,6 @@ public class WebSocketCoreClient extends ContainerLifeCycle
private final WebSocketComponents components;
private ClassLoader classLoader;
- // TODO: Things to consider for inclusion in this class (or removal if they can be set elsewhere, like HttpClient)
- // - AsyncWrite Idle Timeout
- // - Bind Address
- // - SslContextFactory setup
- // - Connect Timeout
- // - Cookie Store
-
public WebSocketCoreClient()
{
this(null, new WebSocketComponents());
@@ -61,6 +54,8 @@ public WebSocketCoreClient(HttpClient httpClient, WebSocketComponents webSocketC
{
if (httpClient == null)
httpClient = Objects.requireNonNull(HttpClientProvider.get());
+ if (httpClient.getExecutor() == null)
+ httpClient.setExecutor(webSocketComponents.getExecutor());
this.classLoader = Thread.currentThread().getContextClassLoader();
this.httpClient = httpClient;
diff --git a/jetty-websocket/websocket-core-client/src/main/java/org/eclipse/jetty/websocket/core/client/internal/HttpClientProvider.java b/jetty-websocket/websocket-core-client/src/main/java/org/eclipse/jetty/websocket/core/client/internal/HttpClientProvider.java
index f6b855e8525f..d1ea884064ed 100644
--- a/jetty-websocket/websocket-core-client/src/main/java/org/eclipse/jetty/websocket/core/client/internal/HttpClientProvider.java
+++ b/jetty-websocket/websocket-core-client/src/main/java/org/eclipse/jetty/websocket/core/client/internal/HttpClientProvider.java
@@ -14,7 +14,6 @@
package org.eclipse.jetty.websocket.core.client.internal;
import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.util.thread.QueuedThreadPool;
public interface HttpClientProvider
{
@@ -30,11 +29,7 @@ static HttpClient get()
private static HttpClient newDefaultHttpClient()
{
- HttpClient client = new HttpClient();
- QueuedThreadPool threadPool = new QueuedThreadPool();
- threadPool.setName("WebSocketClient@" + client.hashCode());
- client.setExecutor(threadPool);
- return client;
+ return new HttpClient();
}
default HttpClient newHttpClient()
diff --git a/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/CoreSession.java b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/CoreSession.java
index 7c6bd9bb4ec0..bbaf05c4e5a7 100644
--- a/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/CoreSession.java
+++ b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/CoreSession.java
@@ -256,7 +256,7 @@ public SocketAddress getRemoteAddress()
@Override
public boolean isOutputOpen()
{
- return false;
+ return true;
}
@Override
diff --git a/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/WebSocketComponents.java b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/WebSocketComponents.java
index f9c6e86e742e..22353c83b825 100644
--- a/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/WebSocketComponents.java
+++ b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/WebSocketComponents.java
@@ -13,6 +13,7 @@
package org.eclipse.jetty.websocket.core;
+import java.util.concurrent.Executor;
import java.util.zip.Deflater;
import org.eclipse.jetty.io.ByteBufferPool;
@@ -22,6 +23,7 @@
import org.eclipse.jetty.util.compression.CompressionPool;
import org.eclipse.jetty.util.compression.DeflaterPool;
import org.eclipse.jetty.util.compression.InflaterPool;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
/**
* A collection of components which are the resources needed for websockets such as
@@ -29,11 +31,12 @@
*/
public class WebSocketComponents extends ContainerLifeCycle
{
- private final DecoratedObjectFactory objectFactory;
- private final WebSocketExtensionRegistry extensionRegistry;
- private final ByteBufferPool bufferPool;
- private final InflaterPool inflaterPool;
- private final DeflaterPool deflaterPool;
+ private final DecoratedObjectFactory _objectFactory;
+ private final WebSocketExtensionRegistry _extensionRegistry;
+ private final Executor _executor;
+ private final ByteBufferPool _bufferPool;
+ private final InflaterPool _inflaterPool;
+ private final DeflaterPool _deflaterPool;
public WebSocketComponents()
{
@@ -43,41 +46,64 @@ public WebSocketComponents()
public WebSocketComponents(WebSocketExtensionRegistry extensionRegistry, DecoratedObjectFactory objectFactory,
ByteBufferPool bufferPool, InflaterPool inflaterPool, DeflaterPool deflaterPool)
{
- this.extensionRegistry = (extensionRegistry == null) ? new WebSocketExtensionRegistry() : extensionRegistry;
- this.objectFactory = (objectFactory == null) ? new DecoratedObjectFactory() : objectFactory;
- this.bufferPool = (bufferPool == null) ? new MappedByteBufferPool() : bufferPool;
- this.inflaterPool = (inflaterPool == null) ? new InflaterPool(CompressionPool.DEFAULT_CAPACITY, true) : inflaterPool;
- this.deflaterPool = (deflaterPool == null) ? new DeflaterPool(CompressionPool.DEFAULT_CAPACITY, Deflater.DEFAULT_COMPRESSION, true) : deflaterPool;
+ this (extensionRegistry, objectFactory, bufferPool, inflaterPool, deflaterPool, null);
+ }
+
+ public WebSocketComponents(WebSocketExtensionRegistry extensionRegistry, DecoratedObjectFactory objectFactory,
+ ByteBufferPool bufferPool, InflaterPool inflaterPool, DeflaterPool deflaterPool, Executor executor)
+ {
+ _extensionRegistry = (extensionRegistry == null) ? new WebSocketExtensionRegistry() : extensionRegistry;
+ _objectFactory = (objectFactory == null) ? new DecoratedObjectFactory() : objectFactory;
+ _bufferPool = (bufferPool == null) ? new MappedByteBufferPool() : bufferPool;
+ _inflaterPool = (inflaterPool == null) ? new InflaterPool(CompressionPool.DEFAULT_CAPACITY, true) : inflaterPool;
+ _deflaterPool = (deflaterPool == null) ? new DeflaterPool(CompressionPool.DEFAULT_CAPACITY, Deflater.DEFAULT_COMPRESSION, true) : deflaterPool;
- addBean(inflaterPool);
- addBean(deflaterPool);
- addBean(bufferPool);
- addBean(extensionRegistry);
- addBean(objectFactory);
+ if (executor == null)
+ {
+ QueuedThreadPool threadPool = new QueuedThreadPool();
+ threadPool.setName("WebSocket@" + hashCode());
+ _executor = threadPool;
+ }
+ else
+ {
+ _executor = executor;
+ }
+
+ addBean(_inflaterPool);
+ addBean(_deflaterPool);
+ addBean(_bufferPool);
+ addBean(_extensionRegistry);
+ addBean(_objectFactory);
+ addBean(_executor);
}
public ByteBufferPool getBufferPool()
{
- return bufferPool;
+ return _bufferPool;
+ }
+
+ public Executor getExecutor()
+ {
+ return _executor;
}
public WebSocketExtensionRegistry getExtensionRegistry()
{
- return extensionRegistry;
+ return _extensionRegistry;
}
public DecoratedObjectFactory getObjectFactory()
{
- return objectFactory;
+ return _objectFactory;
}
public InflaterPool getInflaterPool()
{
- return inflaterPool;
+ return _inflaterPool;
}
public DeflaterPool getDeflaterPool()
{
- return deflaterPool;
+ return _deflaterPool;
}
}
diff --git a/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/messages/DispatchedMessageSink.java b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/messages/DispatchedMessageSink.java
index 875fab0096bd..57d31e0d3f67 100644
--- a/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/messages/DispatchedMessageSink.java
+++ b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/messages/DispatchedMessageSink.java
@@ -16,6 +16,7 @@
import java.io.Closeable;
import java.lang.invoke.MethodHandle;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.IO;
@@ -95,10 +96,12 @@ public abstract class DispatchedMessageSink extends AbstractMessageSink
{
private CompletableFuture dispatchComplete;
private MessageSink typeSink;
+ private final Executor executor;
public DispatchedMessageSink(CoreSession session, MethodHandle methodHandle)
{
super(session, methodHandle);
+ executor = session.getWebSocketComponents().getExecutor();
}
public abstract MessageSink newSink(Frame frame);
@@ -112,7 +115,7 @@ public void accept(Frame frame, final Callback callback)
// Dispatch to end user function (will likely start with blocking for data/accept).
// If the MessageSink can be closed do this after invoking and before completing the CompletableFuture.
- new Thread(() ->
+ executor.execute(() ->
{
try
{
@@ -129,7 +132,7 @@ public void accept(Frame frame, final Callback callback)
dispatchComplete.completeExceptionally(throwable);
}
- }).start();
+ });
}
Callback frameCallback = callback;
diff --git a/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/WebSocketServerComponents.java b/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/WebSocketServerComponents.java
index 946882acda1b..d5cd19562f2e 100644
--- a/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/WebSocketServerComponents.java
+++ b/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/WebSocketServerComponents.java
@@ -14,6 +14,7 @@
package org.eclipse.jetty.websocket.core.server;
import java.util.Objects;
+import java.util.concurrent.Executor;
import javax.servlet.ServletContext;
import org.eclipse.jetty.io.ByteBufferPool;
@@ -40,9 +41,9 @@ public class WebSocketServerComponents extends WebSocketComponents
public static final String WEBSOCKET_DEFLATER_POOL_ATTRIBUTE = "jetty.websocket.deflater";
public static final String WEBSOCKET_BUFFER_POOL_ATTRIBUTE = "jetty.websocket.bufferPool";
- WebSocketServerComponents(InflaterPool inflaterPool, DeflaterPool deflaterPool, ByteBufferPool bufferPool, DecoratedObjectFactory objectFactory)
+ WebSocketServerComponents(InflaterPool inflaterPool, DeflaterPool deflaterPool, ByteBufferPool bufferPool, DecoratedObjectFactory objectFactory, Executor executor)
{
- super(null, objectFactory, bufferPool, inflaterPool, deflaterPool);
+ super(null, objectFactory, bufferPool, inflaterPool, deflaterPool, executor);
}
/**
@@ -79,8 +80,12 @@ public static WebSocketComponents ensureWebSocketComponents(Server server, Servl
if (bufferPool == null)
bufferPool = server.getBean(ByteBufferPool.class);
+ Executor executor = (Executor)servletContext.getAttribute("org.eclipse.jetty.server.Executor");
+ if (executor == null)
+ executor = server.getThreadPool();
+
DecoratedObjectFactory objectFactory = (DecoratedObjectFactory)servletContext.getAttribute(DecoratedObjectFactory.ATTR);
- WebSocketComponents serverComponents = new WebSocketServerComponents(inflaterPool, deflaterPool, bufferPool, objectFactory);
+ WebSocketComponents serverComponents = new WebSocketServerComponents(inflaterPool, deflaterPool, bufferPool, objectFactory, executor);
if (objectFactory != null)
serverComponents.unmanage(objectFactory);
@@ -92,6 +97,8 @@ public static WebSocketComponents ensureWebSocketComponents(Server server, Servl
serverComponents.unmanage(deflaterPool);
if (server.contains(bufferPool))
serverComponents.unmanage(bufferPool);
+ if (executor != null)
+ serverComponents.unmanage(executor);
// Stop the WebSocketComponents when the ContextHandler stops.
ContextHandler contextHandler = Objects.requireNonNull(ContextHandler.getContextHandler(servletContext));
diff --git a/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketFrameHandler.java b/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketFrameHandler.java
index 99cfd8bb2e6f..030312178243 100644
--- a/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketFrameHandler.java
+++ b/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketFrameHandler.java
@@ -133,6 +133,9 @@ public void onOpen(CoreSession coreSession, Callback callback)
// Rewire EndpointConfig to call CoreSession setters if Jetty specific properties are set.
endpointConfig = getWrappedEndpointConfig();
session = new JavaxWebSocketSession(container, coreSession, this, endpointConfig);
+ if (!session.isOpen())
+ throw new IllegalStateException("Session is not open");
+
openHandle = InvokerUtils.bindTo(openHandle, session, endpointConfig);
closeHandle = InvokerUtils.bindTo(closeHandle, session);
errorHandle = InvokerUtils.bindTo(errorHandle, session);
@@ -171,7 +174,9 @@ public void onOpen(CoreSession coreSession, Callback callback)
if (openHandle != null)
openHandle.invoke();
- container.notifySessionListeners((listener) -> listener.onJavaxWebSocketSessionOpened(session));
+ if (session.isOpen())
+ container.notifySessionListeners((listener) -> listener.onJavaxWebSocketSessionOpened(session));
+
callback.succeeded();
}
catch (Throwable cause)
diff --git a/jetty-websocket/websocket-javax-common/src/test/java/org/eclipse/jetty/websocket/javax/common/AbstractJavaxWebSocketFrameHandlerTest.java b/jetty-websocket/websocket-javax-common/src/test/java/org/eclipse/jetty/websocket/javax/common/AbstractJavaxWebSocketFrameHandlerTest.java
index b9b2de4c6083..f4a6321b0b46 100644
--- a/jetty-websocket/websocket-javax-common/src/test/java/org/eclipse/jetty/websocket/javax/common/AbstractJavaxWebSocketFrameHandlerTest.java
+++ b/jetty-websocket/websocket-javax-common/src/test/java/org/eclipse/jetty/websocket/javax/common/AbstractJavaxWebSocketFrameHandlerTest.java
@@ -28,17 +28,21 @@
public abstract class AbstractJavaxWebSocketFrameHandlerTest
{
protected static DummyContainer container;
+ private static WebSocketComponents components;
@BeforeAll
public static void initContainer() throws Exception
{
container = new DummyContainer();
container.start();
+ components = new WebSocketComponents();
+ components.start();
}
@AfterAll
public static void stopContainer() throws Exception
{
+ components.stop();
container.stop();
}
@@ -48,7 +52,6 @@ public static void stopContainer() throws Exception
protected EndpointConfig endpointConfig;
protected CoreSession coreSession = new CoreSession.Empty()
{
- private final WebSocketComponents components = new WebSocketComponents();
@Override
public WebSocketComponents getWebSocketComponents()
diff --git a/jetty-websocket/websocket-javax-common/src/test/java/org/eclipse/jetty/websocket/javax/common/AbstractSessionTest.java b/jetty-websocket/websocket-javax-common/src/test/java/org/eclipse/jetty/websocket/javax/common/AbstractSessionTest.java
index e9ae179af5c7..30fa05edad77 100644
--- a/jetty-websocket/websocket-javax-common/src/test/java/org/eclipse/jetty/websocket/javax/common/AbstractSessionTest.java
+++ b/jetty-websocket/websocket-javax-common/src/test/java/org/eclipse/jetty/websocket/javax/common/AbstractSessionTest.java
@@ -18,6 +18,7 @@
import javax.websocket.Session;
import org.eclipse.jetty.websocket.core.CoreSession;
+import org.eclipse.jetty.websocket.core.WebSocketComponents;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
@@ -25,16 +26,26 @@ public abstract class AbstractSessionTest
{
protected static JavaxWebSocketSession session;
protected static JavaxWebSocketContainer container;
+ protected static WebSocketComponents components;
@BeforeAll
public static void initSession() throws Exception
{
container = new DummyContainer();
container.start();
+ components = new WebSocketComponents();
+ components.start();
Object websocketPojo = new DummyEndpoint();
UpgradeRequest upgradeRequest = new UpgradeRequestAdapter();
JavaxWebSocketFrameHandler frameHandler = container.newFrameHandler(websocketPojo, upgradeRequest);
- CoreSession coreSession = new CoreSession.Empty();
+ CoreSession coreSession = new CoreSession.Empty()
+ {
+ @Override
+ public WebSocketComponents getWebSocketComponents()
+ {
+ return components;
+ }
+ };
session = new JavaxWebSocketSession(container, coreSession, frameHandler, container.getFrameHandlerFactory()
.newDefaultEndpointConfig(websocketPojo.getClass()));
}
@@ -42,6 +53,7 @@ public static void initSession() throws Exception
@AfterAll
public static void stopContainer() throws Exception
{
+ components.stop();
container.stop();
}
diff --git a/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JavaxWebSocketServerContainer.java b/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JavaxWebSocketServerContainer.java
index ac488c842da8..6683b931244d 100644
--- a/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JavaxWebSocketServerContainer.java
+++ b/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JavaxWebSocketServerContainer.java
@@ -73,12 +73,7 @@ public static JavaxWebSocketServerContainer ensureContainer(ServletContext servl
if (httpClient == null)
httpClient = (HttpClient)contextHandler.getServer().getAttribute(JavaxWebSocketServletContainerInitializer.HTTPCLIENT_ATTRIBUTE);
- Executor executor = httpClient == null ? null : httpClient.getExecutor();
- if (executor == null)
- executor = (Executor)servletContext.getAttribute("org.eclipse.jetty.server.Executor");
- if (executor == null)
- executor = contextHandler.getServer().getThreadPool();
-
+ Executor executor = wsComponents.getExecutor();
if (httpClient != null && httpClient.getExecutor() == null)
httpClient.setExecutor(executor);
@@ -123,23 +118,6 @@ public void lifeCycleStopping(LifeCycle event)
private List> deferredEndpointClasses;
private List deferredEndpointConfigs;
- /**
- * Main entry point for {@link JavaxWebSocketServletContainerInitializer}.
- *
- * @param webSocketMappings the {@link WebSocketMappings} that this container belongs to
- */
- public JavaxWebSocketServerContainer(WebSocketMappings webSocketMappings)
- {
- this(webSocketMappings, new WebSocketComponents());
- }
-
- public JavaxWebSocketServerContainer(WebSocketMappings webSocketMappings, WebSocketComponents components)
- {
- super(components);
- this.webSocketMappings = webSocketMappings;
- this.frameHandlerFactory = new JavaxWebSocketServerFrameHandlerFactory(this);
- }
-
/**
* Main entry point for {@link JavaxWebSocketServletContainerInitializer}.
*
diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/CloseInOnOpenTest.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/CloseInOnOpenTest.java
new file mode 100644
index 000000000000..859a8568e5e6
--- /dev/null
+++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/CloseInOnOpenTest.java
@@ -0,0 +1,97 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2021 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.websocket.javax.tests;
+
+import java.net.URI;
+import java.util.concurrent.TimeUnit;
+import javax.websocket.CloseReason;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.websocket.javax.client.internal.JavaxWebSocketClientContainer;
+import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer;
+import org.eclipse.jetty.websocket.javax.server.internal.JavaxWebSocketServerContainer;
+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.is;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class CloseInOnOpenTest
+{
+ private Server server;
+ private ServerConnector connector;
+ private JavaxWebSocketServerContainer serverContainer;
+ private JavaxWebSocketClientContainer client;
+
+ @BeforeEach
+ public void beforeEach() throws Exception
+ {
+ server = new Server();
+
+ connector = new ServerConnector(server);
+ server.addConnector(connector);
+
+ ServletContextHandler context = new ServletContextHandler();
+ context.setContextPath("/");
+ server.setHandler(context);
+
+ JavaxWebSocketServletContainerInitializer.configure(context, (servletContext, wsContainer) ->
+ wsContainer.addEndpoint(ClosingListener.class));
+ server.start();
+
+ serverContainer = JavaxWebSocketServerContainer.getContainer(context.getServletContext());
+ assertNotNull(serverContainer);
+
+ client = new JavaxWebSocketClientContainer();
+ client.start();
+ }
+
+ @AfterEach
+ public void afterEach() throws Exception
+ {
+ client.stop();
+ server.stop();
+ }
+
+ @Test
+ public void testCloseInOnWebSocketConnect() throws Exception
+ {
+ URI uri = URI.create("ws://localhost:" + connector.getLocalPort() + "/ws");
+ EventSocket clientEndpoint = new EventSocket();
+
+ client.connectToServer(clientEndpoint, uri);
+ assertTrue(clientEndpoint.closeLatch.await(5, TimeUnit.SECONDS));
+ assertThat(clientEndpoint.closeReason.getCloseCode(), is(CloseReason.CloseCodes.VIOLATED_POLICY));
+
+ assertThat(serverContainer.getOpenSessions().size(), is(0));
+ }
+
+ @ServerEndpoint("/ws")
+ public static class ClosingListener
+ {
+ @OnOpen
+ public void onWebSocketConnect(Session session) throws Exception
+ {
+ session.close(new CloseReason(CloseReason.CloseCodes.VIOLATED_POLICY, "I am a WS that closes immediately"));
+ }
+ }
+}
diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/client/AbstractClientSessionTest.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/client/AbstractClientSessionTest.java
index a5bbde19c45d..135c4b08a0ab 100644
--- a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/client/AbstractClientSessionTest.java
+++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/client/AbstractClientSessionTest.java
@@ -14,6 +14,7 @@
package org.eclipse.jetty.websocket.javax.tests.client;
import org.eclipse.jetty.websocket.core.CoreSession;
+import org.eclipse.jetty.websocket.core.WebSocketComponents;
import org.eclipse.jetty.websocket.javax.client.internal.BasicClientEndpointConfig;
import org.eclipse.jetty.websocket.javax.client.internal.JavaxWebSocketClientContainer;
import org.eclipse.jetty.websocket.javax.common.JavaxWebSocketContainer;
@@ -29,16 +30,26 @@ public abstract class AbstractClientSessionTest
{
protected static JavaxWebSocketSession session;
protected static JavaxWebSocketContainer container;
+ protected static WebSocketComponents components;
@BeforeAll
public static void initSession() throws Exception
{
container = new JavaxWebSocketClientContainer();
container.start();
+ components = new WebSocketComponents();
+ components.start();
Object websocketPojo = new DummyEndpoint();
UpgradeRequest upgradeRequest = new UpgradeRequestAdapter();
JavaxWebSocketFrameHandler frameHandler = container.newFrameHandler(websocketPojo, upgradeRequest);
- CoreSession coreSession = new CoreSession.Empty();
+ CoreSession coreSession = new CoreSession.Empty()
+ {
+ @Override
+ public WebSocketComponents getWebSocketComponents()
+ {
+ return components;
+ }
+ };
session = new JavaxWebSocketSession(container, coreSession, frameHandler, new BasicClientEndpointConfig());
}
diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/JavaxWebSocketFrameHandlerOnMessageTextStreamTest.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/JavaxWebSocketFrameHandlerOnMessageTextStreamTest.java
index d60015606606..6eda07ca6e30 100644
--- a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/JavaxWebSocketFrameHandlerOnMessageTextStreamTest.java
+++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/JavaxWebSocketFrameHandlerOnMessageTextStreamTest.java
@@ -27,10 +27,13 @@
import org.eclipse.jetty.websocket.core.CoreSession;
import org.eclipse.jetty.websocket.core.Frame;
import org.eclipse.jetty.websocket.core.OpCode;
+import org.eclipse.jetty.websocket.core.WebSocketComponents;
import org.eclipse.jetty.websocket.javax.common.JavaxWebSocketFrameHandler;
import org.eclipse.jetty.websocket.javax.common.UpgradeRequest;
import org.eclipse.jetty.websocket.javax.common.UpgradeRequestAdapter;
import org.eclipse.jetty.websocket.javax.tests.WSEventTracker;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -38,6 +41,20 @@
public class JavaxWebSocketFrameHandlerOnMessageTextStreamTest extends AbstractJavaxWebSocketServerFrameHandlerTest
{
+ private static final WebSocketComponents components = new WebSocketComponents();
+
+ @BeforeAll
+ public static void beforeAll() throws Exception
+ {
+ components.start();
+ }
+
+ @AfterAll
+ public static void afterAll() throws Exception
+ {
+ components.stop();
+ }
+
@SuppressWarnings("Duplicates")
private T performOnMessageInvocation(T socket, Consumer func) throws Exception
{
@@ -46,7 +63,14 @@ private T performOnMessageInvocation(T socket, Consum
// Establish endpoint function
JavaxWebSocketFrameHandler frameHandler = container.newFrameHandler(socket, request);
- frameHandler.onOpen(new CoreSession.Empty(), Callback.NOOP);
+ frameHandler.onOpen(new CoreSession.Empty()
+ {
+ @Override
+ public WebSocketComponents getWebSocketComponents()
+ {
+ return components;
+ }
+ }, Callback.NOOP);
func.accept(frameHandler);
return socket;
}
diff --git a/jetty-websocket/websocket-jetty-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketClient.java b/jetty-websocket/websocket-jetty-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketClient.java
index 5e184a3f4887..b4ff5563d228 100644
--- a/jetty-websocket/websocket-jetty-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketClient.java
+++ b/jetty-websocket/websocket-jetty-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketClient.java
@@ -85,10 +85,6 @@ public WebSocketClient(HttpClient httpClient)
{
coreClient = new WebSocketCoreClient(httpClient, components);
addManaged(coreClient);
-
- if (httpClient == null)
- coreClient.getHttpClient().setName("Jetty-WebSocketClient@" + hashCode());
-
frameHandlerFactory = new JettyWebSocketFrameHandlerFactory(this, components);
sessionListeners.add(sessionTracker);
addBean(sessionTracker);
diff --git a/jetty-websocket/websocket-jetty-client/src/test/java/org/eclipse/jetty/websocket/client/HttpClientInitTest.java b/jetty-websocket/websocket-jetty-client/src/test/java/org/eclipse/jetty/websocket/client/HttpClientInitTest.java
index 4a652c1450e5..0159b3d44f82 100644
--- a/jetty-websocket/websocket-jetty-client/src/test/java/org/eclipse/jetty/websocket/client/HttpClientInitTest.java
+++ b/jetty-websocket/websocket-jetty-client/src/test/java/org/eclipse/jetty/websocket/client/HttpClientInitTest.java
@@ -41,7 +41,7 @@ public void testDefaultInit() throws Exception
assertThat("Executor exists", executor, notNullValue());
assertThat("Executor instanceof", executor, instanceOf(QueuedThreadPool.class));
QueuedThreadPool threadPool = (QueuedThreadPool)executor;
- assertThat("QueuedThreadPool.name", threadPool.getName(), startsWith("WebSocketClient@"));
+ assertThat("QueuedThreadPool.name", threadPool.getName(), startsWith("WebSocket@"));
}
finally
{
diff --git a/jetty-websocket/websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandler.java b/jetty-websocket/websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandler.java
index d3235ad0ed73..992c9042dd2a 100644
--- a/jetty-websocket/websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandler.java
+++ b/jetty-websocket/websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandler.java
@@ -151,6 +151,8 @@ public void onOpen(CoreSession coreSession, Callback callback)
{
customizer.customize(coreSession);
session = new WebSocketSession(container, coreSession, this);
+ if (!session.isOpen())
+ throw new IllegalStateException("Session is not open");
frameHandle = InvokerUtils.bindTo(frameHandle, session);
openHandle = InvokerUtils.bindTo(openHandle, session);
@@ -172,7 +174,8 @@ public void onOpen(CoreSession coreSession, Callback callback)
if (openHandle != null)
openHandle.invoke();
- container.notifySessionListeners((listener) -> listener.onWebSocketSessionOpened(session));
+ if (session.isOpen())
+ container.notifySessionListeners((listener) -> listener.onWebSocketSessionOpened(session));
callback.succeeded();
demand();
diff --git a/jetty-websocket/websocket-jetty-common/src/test/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandlerTest.java b/jetty-websocket/websocket-jetty-common/src/test/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandlerTest.java
index 1fd9853c81e6..668c63ab63f7 100644
--- a/jetty-websocket/websocket-jetty-common/src/test/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandlerTest.java
+++ b/jetty-websocket/websocket-jetty-common/src/test/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandlerTest.java
@@ -22,6 +22,7 @@
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.api.WebSocketConnectionListener;
@@ -46,6 +47,10 @@ public class JettyWebSocketFrameHandlerTest
{
private static DummyContainer container;
+ private final WebSocketComponents components;
+ private final JettyWebSocketFrameHandlerFactory endpointFactory;
+ private final CoreSession coreSession;
+
@BeforeAll
public static void startContainer() throws Exception
{
@@ -59,22 +64,27 @@ public static void stopContainer() throws Exception
container.stop();
}
- private final WebSocketComponents components = new WebSocketComponents();
- private final JettyWebSocketFrameHandlerFactory endpointFactory = new JettyWebSocketFrameHandlerFactory(container, components);
- private final CoreSession coreSession = new CoreSession.Empty()
+ public JettyWebSocketFrameHandlerTest()
{
- @Override
- public Behavior getBehavior()
- {
- return Behavior.CLIENT;
- }
-
- @Override
- public WebSocketComponents getWebSocketComponents()
+ components = new WebSocketComponents();
+ endpointFactory = new JettyWebSocketFrameHandlerFactory(container, components);
+ coreSession = new CoreSession.Empty()
{
- return components;
- }
- };
+ @Override
+ public Behavior getBehavior()
+ {
+ return Behavior.CLIENT;
+ }
+
+ @Override
+ public WebSocketComponents getWebSocketComponents()
+ {
+ return components;
+ }
+ };
+
+ LifeCycle.start(components);
+ }
private JettyWebSocketFrameHandler newLocalFrameHandler(Object wsEndpoint)
{
diff --git a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/CloseInOnOpenTest.java b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/CloseInOnOpenTest.java
new file mode 100644
index 000000000000..903c51c5ed6d
--- /dev/null
+++ b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/CloseInOnOpenTest.java
@@ -0,0 +1,95 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2021 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.websocket.tests;
+
+import java.net.URI;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.StatusCode;
+import org.eclipse.jetty.websocket.api.WebSocketConnectionListener;
+import org.eclipse.jetty.websocket.client.WebSocketClient;
+import org.eclipse.jetty.websocket.server.JettyWebSocketServerContainer;
+import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer;
+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.is;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class CloseInOnOpenTest
+{
+ private Server server;
+ private ServerConnector connector;
+ private JettyWebSocketServerContainer serverContainer;
+ private WebSocketClient client;
+
+ @BeforeEach
+ public void beforeEach() throws Exception
+ {
+ server = new Server();
+
+ connector = new ServerConnector(server);
+ server.addConnector(connector);
+
+ ServletContextHandler context = new ServletContextHandler();
+ context.setContextPath("/");
+ server.setHandler(context);
+
+ JettyWebSocketServletContainerInitializer.configure(context, (servletContext, wsContainer) ->
+ wsContainer.addMapping("/ws", (req, resp) -> new ClosingListener()));
+ server.start();
+
+ serverContainer = JettyWebSocketServerContainer.getContainer(context.getServletContext());
+ assertNotNull(serverContainer);
+
+ client = new WebSocketClient();
+ client.start();
+ }
+
+ @AfterEach
+ public void afterEach() throws Exception
+ {
+ client.stop();
+ server.stop();
+ }
+
+ @Test
+ public void testCloseInOnWebSocketConnect() throws Exception
+ {
+ URI uri = URI.create("ws://localhost:" + connector.getLocalPort() + "/ws");
+ EventSocket clientEndpoint = new EventSocket();
+
+ client.connect(clientEndpoint, uri).get(5, TimeUnit.SECONDS);
+ assertTrue(clientEndpoint.closeLatch.await(5, TimeUnit.SECONDS));
+ assertThat(clientEndpoint.closeCode, is(StatusCode.POLICY_VIOLATION));
+
+ assertThat(serverContainer.getOpenSessions().size(), is(0));
+ }
+
+ public static class ClosingListener implements WebSocketConnectionListener
+ {
+ @Override
+ public void onWebSocketConnect(Session session)
+ {
+ session.close(StatusCode.POLICY_VIOLATION, "I am a WS that closes immediately");
+ }
+ }
+}
diff --git a/pom.xml b/pom.xml
index 7eb35f3bee1a..13cbedc949b4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -12,6 +12,7 @@
1995
+ /tmp111111
@@ -43,8 +44,7 @@
3.1.8.Final3.4.2.Final1.0.6
- 1.10.9
-
+ 1.10.11org.slf4j;version="[1.7,3.0)", org.slf4j.event;version="[1.7,3.0)", org.slf4j.helpers;version="[1.7,3.0)", org.slf4j.spi;version="[1.7,3.0)"
@@ -68,6 +68,7 @@
false5.52.2
+ 10.3.6
@@ -79,7 +80,7 @@
false01.15.1
- 2.7.0
+ 2.7.0
@@ -150,6 +151,7 @@
jetty-bomdocumentationjetty-keystore
+ jetty-unixdomain-server
@@ -684,7 +686,7 @@
alphabetical${project.build.directory}
- ${unix.socket.tmp}
+ ${jetty.unixdomain.dir}true${jetty.testtracker.log}
@@ -979,7 +981,7 @@
org.mortbay.jettyh2spec-maven-plugin
- 1.0.5
+ 1.0.6
@@ -1129,6 +1131,11 @@
hamcrest${hamcrest.version}
+
+ org.awaitility
+ awaitility
+ 4.1.0
+ org.testcontainerstestcontainers-bom
@@ -1136,6 +1143,11 @@
pomimport
+
+ org.mariadb.jdbc
+ mariadb-java-client
+ ${mariadb.version}
+ net.java.dev.jnajna
@@ -1243,6 +1255,17 @@
+
+ unix-domain-windows
+
+
+ Windows
+
+
+
+ ${user.home}
+
+ errorprone
@@ -1435,6 +1458,9 @@
${maven.surefire.version}external, large-disk-resource
+
+ ci
+
diff --git a/tests/test-distribution/pom.xml b/tests/test-distribution/pom.xml
index 107ba0f26e52..304f6da8356f 100644
--- a/tests/test-distribution/pom.xml
+++ b/tests/test-distribution/pom.xml
@@ -12,6 +12,7 @@
${project.groupId}.tests.distribution-1
+ 10
@@ -180,6 +181,21 @@
testcontainerstest
+
+ org.testcontainers
+ mariadb
+ test
+
+
+ org.testcontainers
+ gcloud
+ test
+
+
+ org.mariadb.jdbc
+ mariadb-java-client
+ test
+
@@ -192,7 +208,10 @@
${settings.localRepository}${project.version}${hazelcast.version}
+ ${mariadb.docker.version}$(distribution.debug.port}
+ ${home.start.timeout}
+ ${mariadb.version}
diff --git a/tests/test-distribution/src/main/java/org/eclipse/jetty/tests/distribution/JettyHomeTester.java b/tests/test-distribution/src/main/java/org/eclipse/jetty/tests/distribution/JettyHomeTester.java
index ebbb5b43d0e9..16ce3a2fbabd 100644
--- a/tests/test-distribution/src/main/java/org/eclipse/jetty/tests/distribution/JettyHomeTester.java
+++ b/tests/test-distribution/src/main/java/org/eclipse/jetty/tests/distribution/JettyHomeTester.java
@@ -180,6 +180,7 @@ public JettyHomeTester.Run start(List args) throws Exception
ProcessBuilder pbCmd = new ProcessBuilder(commands);
pbCmd.directory(jettyBaseDir);
+ pbCmd.environment().putAll(config.env);
Process process = pbCmd.start();
return new Run(config, process);
@@ -393,6 +394,7 @@ public static class Config
private String jettyVersion;
private String mavenLocalRepository = System.getProperty("mavenRepoPath", System.getProperty("user.home") + "/.m2/repository");
private List jvmArgs = new ArrayList<>();
+ private Map env = new HashMap<>();
public Path getJettyBase()
{
@@ -419,6 +421,11 @@ public List getJVMArgs()
return Collections.unmodifiableList(jvmArgs);
}
+ public Map getEnv()
+ {
+ return Collections.unmodifiableMap(env);
+ }
+
@Override
public String toString()
{
@@ -765,6 +772,16 @@ public Builder jvmArgs(List jvmArgs)
return this;
}
+ /**
+ * @param env the env to add
+ * @return this Builder
+ */
+ public Builder env(Map env)
+ {
+ config.env = env;
+ return this;
+ }
+
/**
* @return an empty instance of Builder
*/
diff --git a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/AbstractJettyHomeTest.java b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/AbstractJettyHomeTest.java
index 37468835f650..7f33ce80b3b6 100644
--- a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/AbstractJettyHomeTest.java
+++ b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/AbstractJettyHomeTest.java
@@ -19,6 +19,7 @@
import java.util.function.Supplier;
import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.toolchain.test.FS;
@@ -68,4 +69,24 @@ public void dispose() throws Exception
if (client != null)
client.stop();
}
+
+ protected class ResponseDetails implements Supplier
+ {
+ private final ContentResponse response;
+
+ public ResponseDetails(ContentResponse response)
+ {
+ this.response = response;
+ }
+
+ @Override
+ public String get()
+ {
+ StringBuilder ret = new StringBuilder();
+ ret.append(response.toString()).append(System.lineSeparator());
+ ret.append(response.getHeaders().toString()).append(System.lineSeparator());
+ ret.append(response.getContentAsString()).append(System.lineSeparator());
+ return ret.toString();
+ }
+ }
}
diff --git a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DemoModulesTests.java b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DemoModulesTests.java
index ec5a06826bb2..d1e0cde25015 100644
--- a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DemoModulesTests.java
+++ b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DemoModulesTests.java
@@ -296,24 +296,4 @@ public void testSessionDump() throws Exception
}
}
}
-
- private class ResponseDetails implements Supplier
- {
- private final ContentResponse response;
-
- public ResponseDetails(ContentResponse response)
- {
- this.response = response;
- }
-
- @Override
- public String get()
- {
- StringBuilder ret = new StringBuilder();
- ret.append(response.toString()).append(System.lineSeparator());
- ret.append(response.getHeaders().toString()).append(System.lineSeparator());
- ret.append(response.getContentAsString()).append(System.lineSeparator());
- return ret.toString();
- }
- }
}
diff --git a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java
index 7de00f6112c2..1201ac5c170d 100644
--- a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java
+++ b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java
@@ -29,6 +29,7 @@
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.dynamic.HttpClientTransportDynamic;
import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http2.client.HTTP2Client;
@@ -38,7 +39,6 @@
import org.eclipse.jetty.unixsocket.server.UnixSocketConnector;
import org.eclipse.jetty.util.BlockingArrayQueue;
import org.eclipse.jetty.util.IO;
-import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.StatusCode;
@@ -48,6 +48,7 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnJre;
import org.junit.jupiter.api.condition.DisabledOnOs;
+import org.junit.jupiter.api.condition.EnabledForJreRange;
import org.junit.jupiter.api.condition.JRE;
import org.junit.jupiter.api.condition.OS;
import org.junit.jupiter.params.ParameterizedTest;
@@ -61,8 +62,8 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.junit.jupiter.api.Assumptions.assumeTrue;
public class DistributionTests extends AbstractJettyHomeTest
{
@@ -277,20 +278,11 @@ private void testSimpleWebAppWithJSPOverHTTP2(boolean ssl) throws Exception
@DisabledOnOs(OS.WINDOWS) // jnr not supported on windows
public void testUnixSocket() throws Exception
{
- Path tmpSockFile;
- String unixSocketTmp = System.getProperty("unix.socket.tmp");
- if (StringUtil.isNotBlank(unixSocketTmp))
- tmpSockFile = Files.createTempFile(Paths.get(unixSocketTmp), "unix", ".sock");
- else
- tmpSockFile = Files.createTempFile("unix", ".sock");
- if (tmpSockFile.toAbsolutePath().toString().length() > UnixSocketConnector.MAX_UNIX_SOCKET_PATH_LENGTH)
- {
- Path tmp = Paths.get("/tmp");
- assumeTrue(Files.exists(tmp) && Files.isDirectory(tmp));
- tmpSockFile = Files.createTempFile(tmp, "unix", ".sock");
- }
- Path sockFile = tmpSockFile;
- assertTrue(Files.deleteIfExists(sockFile), "temp sock file cannot be deleted");
+ String dir = System.getProperty("jetty.unixdomain.dir");
+ assertNotNull(dir);
+ Path sockFile = Files.createTempFile(Path.of(dir), "unix_", ".sock");
+ assertTrue(sockFile.toAbsolutePath().toString().length() < UnixSocketConnector.MAX_UNIX_SOCKET_PATH_LENGTH, "Unix-Domain path too long");
+ Files.delete(sockFile);
String jettyVersion = System.getProperty("jettyVersion");
JettyHomeTester distribution = JettyHomeTester.Builder.newInstance()
@@ -311,7 +303,7 @@ public void testUnixSocket() throws Exception
File war = distribution.resolveArtifact("org.eclipse.jetty.demos:demo-jsp-webapp:war:" + jettyVersion);
distribution.installWarFile(war, "test");
- try (JettyHomeTester.Run run2 = distribution.start("jetty.unixsocket.path=" + sockFile.toString()))
+ try (JettyHomeTester.Run run2 = distribution.start("jetty.unixsocket.path=" + sockFile))
{
assertTrue(run2.awaitConsoleLogsFor("Started Server@", 10, TimeUnit.SECONDS));
@@ -851,7 +843,7 @@ public void testBeforeDirectiveInModule() throws Exception
// Protocol "h2" must not be enabled because the
// http2 Jetty module was not explicitly enabled.
assertFalse(run3.getLogs().stream()
- .anyMatch(log -> log.contains("h2")));
+ .anyMatch(log -> log.contains("h2")), "Full logs: " + String.join("", run3.getLogs()));
}
}
}
@@ -906,4 +898,70 @@ public void testDefaultLoggingProviderNotActiveWhenExplicitProviderIsPresent() t
assertFalse(Files.exists(jettyBase.resolve("resources/jetty-logging.properties")));
}
}
+
+ @Test
+ @EnabledForJreRange(min = JRE.JAVA_16)
+ public void testUnixDomain() throws Exception
+ {
+ String jettyVersion = System.getProperty("jettyVersion");
+ JettyHomeTester distribution = JettyHomeTester.Builder.newInstance()
+ .jettyVersion(jettyVersion)
+ .mavenLocalRepository(System.getProperty("mavenRepoPath"))
+ .build();
+
+ try (JettyHomeTester.Run run1 = distribution.start("--add-modules=unixdomain-http"))
+ {
+ assertTrue(run1.awaitFor(10, TimeUnit.SECONDS));
+ assertEquals(0, run1.getExitValue());
+
+ int maxUnixDomainPathLength = 108;
+ Path path = Files.createTempFile("unix", ".sock");
+ if (path.normalize().toAbsolutePath().toString().length() > maxUnixDomainPathLength)
+ path = Files.createTempFile(Path.of("/tmp"), "unix", ".sock");
+ assertTrue(Files.deleteIfExists(path));
+ try (JettyHomeTester.Run run2 = distribution.start("jetty.unixdomain.path=" + path))
+ {
+ assertTrue(run2.awaitConsoleLogsFor("Started Server@", 10, TimeUnit.SECONDS));
+
+ ClientConnector connector = ClientConnector.forUnixDomain(path);
+ client = new HttpClient(new HttpClientTransportDynamic(connector));
+ client.start();
+ ContentResponse response = client.GET("http://localhost/path");
+ assertEquals(HttpStatus.NOT_FOUND_404, response.getStatus());
+ }
+ }
+ }
+
+ @Test
+ public void testModuleWithExecEmitsWarning() throws Exception
+ {
+ String jettyVersion = System.getProperty("jettyVersion");
+ JettyHomeTester distribution = JettyHomeTester.Builder.newInstance()
+ .jettyVersion(jettyVersion)
+ .mavenLocalRepository(System.getProperty("mavenRepoPath"))
+ .build();
+
+ Path jettyBase = distribution.getJettyBase();
+ Path jettyBaseModules = jettyBase.resolve("modules");
+ Files.createDirectories(jettyBaseModules);
+ Path execModule = jettyBaseModules.resolve("exec.mod");
+ String module = "" +
+ "[exec]\n" +
+ "--show-version";
+ Files.write(execModule, List.of(module), StandardOpenOption.CREATE);
+
+ try (JettyHomeTester.Run run1 = distribution.start(List.of("--add-modules=http,exec")))
+ {
+ assertTrue(run1.awaitFor(10, TimeUnit.SECONDS));
+ assertEquals(0, run1.getExitValue());
+
+ int port = distribution.freePort();
+ try (JettyHomeTester.Run run2 = distribution.start("jetty.http.port=" + port))
+ {
+ assertTrue(run2.awaitConsoleLogsFor("Started Server@", 10, TimeUnit.SECONDS));
+ assertTrue(run2.getLogs().stream()
+ .anyMatch(log -> log.contains("WARN") && log.contains("Forking")));
+ }
+ }
+ }
}
diff --git a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/GzipModuleTests.java b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/GzipModuleTests.java
new file mode 100644
index 000000000000..040e79e9d6ae
--- /dev/null
+++ b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/GzipModuleTests.java
@@ -0,0 +1,176 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2021 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.tests.distribution;
+
+import java.io.File;
+import java.nio.file.Path;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpStatus;
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.not;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class GzipModuleTests extends AbstractJettyHomeTest
+{
+ @Test
+ public void testGzipDefault() throws Exception
+ {
+ Path jettyBase = newTestJettyBaseDirectory();
+ String jettyVersion = System.getProperty("jettyVersion");
+ JettyHomeTester distribution = JettyHomeTester.Builder.newInstance()
+ .jettyVersion(jettyVersion)
+ .jettyBase(jettyBase)
+ .mavenLocalRepository(System.getProperty("mavenRepoPath"))
+ .build();
+
+ int httpPort = distribution.freePort();
+
+ String[] argsConfig = {
+ "--add-modules=gzip",
+ "--add-modules=deploy,webapp,http"
+ };
+
+ try (JettyHomeTester.Run runConfig = distribution.start(argsConfig))
+ {
+ assertTrue(runConfig.awaitFor(5, TimeUnit.SECONDS));
+ assertEquals(0, runConfig.getExitValue());
+
+ String[] argsStart = {
+ "jetty.http.port=" + httpPort,
+ "jetty.httpConfig.port=" + httpPort
+ };
+
+ File war = distribution.resolveArtifact("org.eclipse.jetty.demos:demo-simple-webapp:war:" + jettyVersion);
+ distribution.installWarFile(war, "demo");
+
+ try (JettyHomeTester.Run runStart = distribution.start(argsStart))
+ {
+ assertTrue(runStart.awaitConsoleLogsFor("Started Server@", 20, TimeUnit.SECONDS));
+
+ startHttpClient();
+ ContentResponse response = client.GET("http://localhost:" + httpPort + "/demo/index.html");
+ String responseDetails = toResponseDetails(response);
+ assertEquals(HttpStatus.OK_200, response.getStatus(), responseDetails);
+ assertThat(responseDetails, response.getHeaders().get(HttpHeader.CONTENT_ENCODING), containsString("gzip"));
+ }
+ }
+ }
+
+ @Test
+ public void testGzipDefaultExcludedMimeType() throws Exception
+ {
+ Path jettyBase = newTestJettyBaseDirectory();
+ String jettyVersion = System.getProperty("jettyVersion");
+ JettyHomeTester distribution = JettyHomeTester.Builder.newInstance()
+ .jettyVersion(jettyVersion)
+ .jettyBase(jettyBase)
+ .mavenLocalRepository(System.getProperty("mavenRepoPath"))
+ .build();
+
+ int httpPort = distribution.freePort();
+
+ String[] argsConfig = {
+ "--add-modules=gzip",
+ "--add-modules=deploy,webapp,http"
+ };
+
+ try (JettyHomeTester.Run runConfig = distribution.start(argsConfig))
+ {
+ assertTrue(runConfig.awaitFor(5, TimeUnit.SECONDS));
+ assertEquals(0, runConfig.getExitValue());
+
+ String[] argsStart = {
+ "jetty.http.port=" + httpPort,
+ "jetty.httpConfig.port=" + httpPort
+ };
+
+ File war = distribution.resolveArtifact("org.eclipse.jetty.demos:demo-simple-webapp:war:" + jettyVersion);
+ distribution.installWarFile(war, "demo");
+
+ try (JettyHomeTester.Run runStart = distribution.start(argsStart))
+ {
+ assertTrue(runStart.awaitConsoleLogsFor("Started Server@", 20, TimeUnit.SECONDS));
+
+ startHttpClient();
+ ContentResponse response = client.GET("http://localhost:" + httpPort + "/demo/jetty.webp");
+ String responseDetails = toResponseDetails(response);
+ assertEquals(HttpStatus.OK_200, response.getStatus(), responseDetails);
+ assertThat(responseDetails, response.getHeaders().get(HttpHeader.CONTENT_TYPE), containsString("image/webp"));
+ assertThat(responseDetails, response.getHeaders().get(HttpHeader.CONTENT_ENCODING), not(containsString("gzip")));
+ }
+ }
+ }
+
+ @Test
+ public void testGzipAddWebappSpecificExcludeMimeType() throws Exception
+ {
+ Path jettyBase = newTestJettyBaseDirectory();
+ String jettyVersion = System.getProperty("jettyVersion");
+ JettyHomeTester distribution = JettyHomeTester.Builder.newInstance()
+ .jettyVersion(jettyVersion)
+ .jettyBase(jettyBase)
+ .mavenLocalRepository(System.getProperty("mavenRepoPath"))
+ .build();
+
+ int httpPort = distribution.freePort();
+
+ String[] argsConfig = {
+ "--add-modules=gzip",
+ "--add-modules=deploy,webapp,http"
+ };
+
+ try (JettyHomeTester.Run runConfig = distribution.start(argsConfig))
+ {
+ assertTrue(runConfig.awaitFor(5, TimeUnit.SECONDS));
+ assertEquals(0, runConfig.getExitValue());
+
+ String[] argsStart = {
+ "jetty.http.port=" + httpPort,
+ "jetty.httpConfig.port=" + httpPort,
+ "jetty.gzip.excludedMimeTypeList=image/vnd.microsoft.icon"
+ };
+
+ File war = distribution.resolveArtifact("org.eclipse.jetty.demos:demo-simple-webapp:war:" + jettyVersion);
+ distribution.installWarFile(war, "demo");
+
+ try (JettyHomeTester.Run runStart = distribution.start(argsStart))
+ {
+ assertTrue(runStart.awaitConsoleLogsFor("Started Server@", 20, TimeUnit.SECONDS));
+
+ startHttpClient();
+ ContentResponse response = client.GET("http://localhost:" + httpPort + "/demo/jetty.icon");
+ String responseDetails = toResponseDetails(response);
+ assertEquals(HttpStatus.OK_200, response.getStatus(), responseDetails);
+ assertThat(responseDetails, response.getHeaders().get(HttpHeader.CONTENT_ENCODING), not(containsString("gzip")));
+ assertThat(responseDetails, response.getHeaders().get(HttpHeader.CONTENT_TYPE), containsString("image/vnd.microsoft.icon"));
+ }
+ }
+ }
+
+ private static String toResponseDetails(ContentResponse response)
+ {
+ StringBuilder ret = new StringBuilder();
+ ret.append(response.toString()).append(System.lineSeparator());
+ ret.append(response.getHeaders().toString()).append(System.lineSeparator());
+ ret.append(response.getContentAsString()).append(System.lineSeparator());
+ return ret.toString();
+ }
+}
diff --git a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/ThirdPartyModulesTests.java b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/ThirdPartyModulesTests.java
new file mode 100644
index 000000000000..5c92ac5017e1
--- /dev/null
+++ b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/ThirdPartyModulesTests.java
@@ -0,0 +1,181 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2021 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.tests.distribution;
+
+import java.nio.file.Path;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.http.HttpStatus;
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class ThirdPartyModulesTests extends AbstractJettyHomeTest
+{
+ @Test
+ public void testHawtio() throws Exception
+ {
+ Path jettyBase = newTestJettyBaseDirectory();
+ String jettyVersion = System.getProperty("jettyVersion");
+ JettyHomeTester distribution = JettyHomeTester.Builder.newInstance()
+ .jettyVersion(jettyVersion)
+ .jettyBase(jettyBase)
+ .mavenLocalRepository(System.getProperty("mavenRepoPath"))
+ .build();
+
+ int httpPort = distribution.freePort();
+
+ String[] argsConfig = {
+ "--approve-all-licenses",
+ "--add-modules=hawtio,http"
+ };
+
+ try (JettyHomeTester.Run runConfig = distribution.start(argsConfig))
+ {
+ assertTrue(runConfig.awaitFor(2, TimeUnit.MINUTES));
+ assertEquals(0, runConfig.getExitValue());
+
+ String[] argsStart = {
+ "jetty.http.port=" + httpPort
+ };
+
+ try (JettyHomeTester.Run runStart = distribution.start(argsStart))
+ {
+ assertTrue(runStart.awaitConsoleLogsFor("Started Server@", 20, TimeUnit.SECONDS));
+
+ startHttpClient();
+ ContentResponse response = client.GET("http://localhost:" + httpPort + "/hawtio");
+ assertEquals(HttpStatus.OK_200, response.getStatus(), new ResponseDetails(response));
+ assertThat(response.getContentAsString(), containsString("Hawtio"));
+ }
+ }
+ }
+
+ @Test
+ public void testJAMon() throws Exception
+ {
+ Path jettyBase = newTestJettyBaseDirectory();
+ String jettyVersion = System.getProperty("jettyVersion");
+ JettyHomeTester distribution = JettyHomeTester.Builder.newInstance()
+ .jettyVersion(jettyVersion)
+ .jettyBase(jettyBase)
+ .mavenLocalRepository(System.getProperty("mavenRepoPath"))
+ .build();
+
+ int httpPort = distribution.freePort();
+
+ String[] argsConfig = {
+ "--approve-all-licenses",
+ "--add-modules=jamon,http"
+ };
+
+ try (JettyHomeTester.Run runConfig = distribution.start(argsConfig))
+ {
+ assertTrue(runConfig.awaitFor(2, TimeUnit.MINUTES));
+ assertEquals(0, runConfig.getExitValue());
+
+ String[] argsStart = {
+ "jetty.http.port=" + httpPort
+ };
+
+ try (JettyHomeTester.Run runStart = distribution.start(argsStart))
+ {
+ assertTrue(runStart.awaitConsoleLogsFor("Started Server@", 20, TimeUnit.SECONDS));
+
+ startHttpClient();
+ ContentResponse response = client.GET("http://localhost:" + httpPort + "/jamon");
+ assertEquals(HttpStatus.OK_200, response.getStatus(), new ResponseDetails(response));
+ assertThat(response.getContentAsString(), containsString("JAMon"));
+ }
+ }
+ }
+
+ @Test
+ public void testjminix() throws Exception
+ {
+ Path jettyBase = newTestJettyBaseDirectory();
+ String jettyVersion = System.getProperty("jettyVersion");
+ JettyHomeTester distribution = JettyHomeTester.Builder.newInstance()
+ .jettyVersion(jettyVersion)
+ .jettyBase(jettyBase)
+ .mavenLocalRepository(System.getProperty("mavenRepoPath"))
+ .build();
+
+ int httpPort = distribution.freePort();
+
+ String[] argsConfig = {
+ "--approve-all-licenses",
+ "--add-modules=jminix,http,logging-jcl-capture"
+ };
+
+ try (JettyHomeTester.Run runConfig = distribution.start(argsConfig))
+ {
+ assertTrue(runConfig.awaitFor(2, TimeUnit.MINUTES));
+ assertEquals(0, runConfig.getExitValue());
+
+ String[] argsStart = {
+ "jetty.http.port=" + httpPort
+ };
+
+ try (JettyHomeTester.Run runStart = distribution.start(argsStart))
+ {
+ assertTrue(runStart.awaitConsoleLogsFor("Started Server@", 20, TimeUnit.SECONDS));
+ }
+ }
+ }
+
+ @Test
+ public void testjolokia() throws Exception
+ {
+ Path jettyBase = newTestJettyBaseDirectory();
+ String jettyVersion = System.getProperty("jettyVersion");
+ JettyHomeTester distribution = JettyHomeTester.Builder.newInstance()
+ .jettyVersion(jettyVersion)
+ .jettyBase(jettyBase)
+ .mavenLocalRepository(System.getProperty("mavenRepoPath"))
+ .build();
+
+ int httpPort = distribution.freePort();
+
+ String[] argsConfig = {
+ "--approve-all-licenses",
+ "--add-modules=jolokia,http"
+ };
+
+ try (JettyHomeTester.Run runConfig = distribution.start(argsConfig))
+ {
+ assertTrue(runConfig.awaitFor(2, TimeUnit.MINUTES));
+ assertEquals(0, runConfig.getExitValue());
+
+ String[] argsStart = {
+ "jetty.http.port=" + httpPort
+ };
+
+ try (JettyHomeTester.Run runStart = distribution.start(argsStart))
+ {
+ assertTrue(runStart.awaitConsoleLogsFor("Started Server@", 20, TimeUnit.SECONDS));
+
+ startHttpClient();
+ ContentResponse response = client.GET("http://localhost:" + httpPort + "/jolokia");
+ assertEquals(HttpStatus.OK_200, response.getStatus(), new ResponseDetails(response));
+ assertThat(response.getContentAsString(), containsString("\"agentType\":\"servlet\""));
+ }
+ }
+ }
+
+}
diff --git a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/session/AbstractSessionDistributionTests.java b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/session/AbstractSessionDistributionTests.java
new file mode 100644
index 000000000000..c35171cd5fab
--- /dev/null
+++ b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/session/AbstractSessionDistributionTests.java
@@ -0,0 +1,136 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2021 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.tests.distribution.session;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.tests.distribution.AbstractJettyHomeTest;
+import org.eclipse.jetty.tests.distribution.JettyHomeTester;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public abstract class AbstractSessionDistributionTests extends AbstractJettyHomeTest
+{
+
+ private String jettyVersion = System.getProperty("jettyVersion");
+
+ protected JettyHomeTester jettyHomeTester;
+
+ private static final int START_TIMEOUT = Integer.getInteger("home.start.timeout", 10);
+
+ @BeforeEach
+ public void prepareJettyHomeTester() throws Exception
+ {
+
+ jettyHomeTester = JettyHomeTester.Builder.newInstance()
+ .jettyVersion(jettyVersion)
+ .env(env())
+ .mavenLocalRepository(System.getProperty("mavenRepoPath"))
+ .build();
+ }
+
+ @Test
+ public void stopRestartWebappTestSessionContentSaved() throws Exception
+ {
+ startExternalSessionStorage();
+
+ List args = new ArrayList<>(Arrays.asList(
+ "--create-startd",
+ "--approve-all-licenses",
+ "--add-module=resources,server,http,webapp,deploy,jmx,servlet,servlets," + getFirstStartExtraModules()
+ ));
+ args.addAll(getFirstStartExtraArgs());
+ String[] argsStart = args.toArray(new String[0]);
+
+ try (JettyHomeTester.Run run1 = jettyHomeTester.start(argsStart))
+ {
+ assertTrue(run1.awaitFor(5, TimeUnit.SECONDS));
+ assertEquals(0, run1.getExitValue());
+
+ File war = jettyHomeTester.resolveArtifact("org.eclipse.jetty.tests:test-simple-session-webapp:war:" + jettyVersion);
+ jettyHomeTester.installWarFile(war, "test");
+
+ int port = jettyHomeTester.freePort();
+ args = new ArrayList<>(Collections.singletonList("jetty.http.port=" + port));
+ args.addAll(getSecondStartExtraArgs());
+ argsStart = args.toArray(new String[0]);
+
+ try (JettyHomeTester.Run run2 = jettyHomeTester.start(argsStart))
+ {
+ assertTrue(run2.awaitConsoleLogsFor("Started Server@", START_TIMEOUT, TimeUnit.SECONDS));
+
+ startHttpClient();
+ ContentResponse response = client.GET("http://localhost:" + port + "/test/session?action=CREATE");
+ assertEquals(HttpStatus.OK_200, response.getStatus());
+ assertThat(response.getContentAsString(), containsString("SESSION CREATED"));
+
+ response = client.GET("http://localhost:" + port + "/test/session?action=READ");
+ assertEquals(HttpStatus.OK_200, response.getStatus());
+ assertThat(response.getContentAsString(), containsString("SESSION READ CHOCOLATE THE BEST:FRENCH"));
+ }
+
+ Path logFile = jettyHomeTester.getJettyBase().resolve("resources").resolve("jetty-logging.properties");
+ Files.deleteIfExists(logFile);
+ try (BufferedWriter writer = Files.newBufferedWriter(logFile, StandardCharsets.UTF_8, StandardOpenOption.CREATE))
+ {
+ writer.write("org.eclipse.jetty.server.session.LEVEL=DEBUG");
+ }
+
+ try (JettyHomeTester.Run run2 = jettyHomeTester.start(argsStart))
+ {
+ assertTrue(run2.awaitConsoleLogsFor("Started Server@", START_TIMEOUT, TimeUnit.SECONDS));
+
+ ContentResponse response = client.GET("http://localhost:" + port + "/test/session?action=READ");
+ assertEquals(HttpStatus.OK_200, response.getStatus());
+ assertThat(response.getContentAsString(), containsString("SESSION READ CHOCOLATE THE BEST:FRENCH"));
+ }
+ }
+
+ stopExternalSessionStorage();
+ }
+
+ public Map env()
+ {
+ return Collections.emptyMap();
+ }
+
+ public abstract List getFirstStartExtraArgs();
+
+ public abstract String getFirstStartExtraModules();
+
+ public abstract List getSecondStartExtraArgs();
+
+ public abstract void startExternalSessionStorage() throws Exception;
+
+ public abstract void stopExternalSessionStorage() throws Exception;
+
+}
diff --git a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/session/FileSessionDistributionTests.java b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/session/FileSessionDistributionTests.java
new file mode 100644
index 000000000000..025bee15b696
--- /dev/null
+++ b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/session/FileSessionDistributionTests.java
@@ -0,0 +1,55 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2021 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.tests.distribution.session;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ *
+ */
+public class FileSessionDistributionTests extends AbstractSessionDistributionTests
+{
+
+ @Override
+ public void startExternalSessionStorage() throws Exception
+ {
+ // no op
+ }
+
+ @Override
+ public void stopExternalSessionStorage() throws Exception
+ {
+ // no op
+ }
+
+ @Override
+ public List getFirstStartExtraArgs()
+ {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public String getFirstStartExtraModules()
+ {
+ return "session-store-file";
+ }
+
+ @Override
+ public List getSecondStartExtraArgs()
+ {
+ return Collections.emptyList();
+ }
+
+}
diff --git a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/session/FileSessionWithMemcacheDistributionTests.java b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/session/FileSessionWithMemcacheDistributionTests.java
new file mode 100644
index 000000000000..70973d964158
--- /dev/null
+++ b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/session/FileSessionWithMemcacheDistributionTests.java
@@ -0,0 +1,89 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2021 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.tests.distribution.session;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.output.Slf4jLogConsumer;
+
+/**
+ *
+ */
+public class FileSessionWithMemcacheDistributionTests extends AbstractSessionDistributionTests
+{
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(FileSessionWithMemcacheDistributionTests.class);
+ private static final Logger MEMCACHED_LOG = LoggerFactory.getLogger("org.eclipse.jetty.tests.distribution.session.memcached");
+
+ private GenericContainer memcached;
+
+ private String host;
+ private int port;
+
+ @Override
+ @BeforeEach
+ public void prepareJettyHomeTester() throws Exception
+ {
+ memcached =
+ new GenericContainer("memcached:" + System.getProperty("memcached.docker.version", "1.6.6"))
+ .withLogConsumer(new Slf4jLogConsumer(MEMCACHED_LOG));
+ memcached.start();
+ this.host = memcached.getContainerIpAddress();
+ this.port = memcached.getMappedPort(11211);
+ super.prepareJettyHomeTester();
+ }
+
+ @Override
+ public void startExternalSessionStorage() throws Exception
+ {
+ // no op
+ }
+
+ @Override
+ public Map env()
+ {
+ return Map.of("MEMCACHE_PORT_11211_TCP_ADDR", host, "MEMCACHE_PORT_11211_TCP_PORT", Integer.toString(port));
+ }
+
+ @Override
+ public void stopExternalSessionStorage() throws Exception
+ {
+ memcached.stop();
+ }
+
+ @Override
+ public List getFirstStartExtraArgs()
+ {
+ return Collections.singletonList("session-data-cache=xmemcached");
+ }
+
+ @Override
+ public String getFirstStartExtraModules()
+ {
+ return "session-store-file,session-store-cache";
+ }
+
+ @Override
+ public List getSecondStartExtraArgs()
+ {
+ return Collections.singletonList("session-data-cache=xmemcached");
+ }
+
+}
diff --git a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/session/GCloudSessionDistributionTests.java b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/session/GCloudSessionDistributionTests.java
new file mode 100644
index 000000000000..a7c4b1117a7f
--- /dev/null
+++ b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/session/GCloudSessionDistributionTests.java
@@ -0,0 +1,111 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2021 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.tests.distribution.session;
+
+import java.net.InetAddress;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testcontainers.containers.DatastoreEmulatorContainer;
+import org.testcontainers.containers.output.Slf4jLogConsumer;
+import org.testcontainers.containers.wait.strategy.Wait;
+import org.testcontainers.utility.DockerImageName;
+
+/**
+ *
+ */
+public class GCloudSessionDistributionTests extends AbstractSessionDistributionTests
+{
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(GCloudSessionDistributionTests.class);
+ private static final Logger GCLOUD_LOG = LoggerFactory.getLogger("org.eclipse.jetty.tests.distribution.session.gcloudLogs");
+
+ public DatastoreEmulatorContainer emulator =
+ new CustomDatastoreEmulatorContainer(DockerImageName.parse("gcr.io/google.com/cloudsdktool/cloud-sdk:316.0.0-emulators"))
+ .withLogConsumer(new Slf4jLogConsumer(GCLOUD_LOG));
+
+ private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("gcr.io/google.com/cloudsdktool/cloud-sdk");
+
+ private static final String CMD = "gcloud beta emulators datastore start --project test-project --host-port 0.0.0.0:8081 --consistency=1.0";
+ private static final int HTTP_PORT = 8081;
+
+ String host;
+
+ public static class CustomDatastoreEmulatorContainer extends DatastoreEmulatorContainer
+ {
+ public CustomDatastoreEmulatorContainer(DockerImageName dockerImageName)
+ {
+ super(dockerImageName);
+
+ dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);
+
+ withExposedPorts(HTTP_PORT);
+ setWaitStrategy(Wait.forHttp("/").forStatusCode(200));
+ withCommand("/bin/sh", "-c", CMD);
+ }
+ }
+
+ @Override
+ public void startExternalSessionStorage() throws Exception
+ {
+ emulator.start();
+
+ //work out if we're running locally or not: if not local, then the host passed to
+ //DatastoreOptions must be prefixed with a scheme
+ String endPoint = emulator.getEmulatorEndpoint();
+ InetAddress hostAddr = InetAddress.getByName(new URL("http://" + endPoint).getHost());
+ LOGGER.info("endPoint: {} ,hostAddr.isAnyLocalAddress(): {},hostAddr.isLoopbackAddress(): {}",
+ endPoint,
+ hostAddr.isAnyLocalAddress(),
+ hostAddr.isLoopbackAddress());
+ if (hostAddr.isAnyLocalAddress() || hostAddr.isLoopbackAddress())
+ host = endPoint;
+ else
+ host = "http://" + endPoint;
+ }
+
+ @Override
+ public void stopExternalSessionStorage() throws Exception
+ {
+ emulator.stop();
+ }
+
+ @Override
+ public List getFirstStartExtraArgs()
+ {
+ return Arrays.asList(
+ "jetty.session.gcloud.host=" + host,
+ "jetty.session.gcloud.projectId=foobar"
+ );
+ }
+
+ @Override
+ public String getFirstStartExtraModules()
+ {
+ return "session-store-gcloud";
+ }
+
+ @Override
+ public List getSecondStartExtraArgs()
+ {
+ return Arrays.asList(
+ "jetty.session.gcloud.host=" + host,
+ "jetty.session.gcloud.projectId=foobar"
+ );
+ }
+
+}
diff --git a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/HazelcastSessionDistributionTests.java b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/session/HazelcastSessionDistributionTests.java
similarity index 65%
rename from tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/HazelcastSessionDistributionTests.java
rename to tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/session/HazelcastSessionDistributionTests.java
index 6fe937dd4743..338391e766f5 100644
--- a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/HazelcastSessionDistributionTests.java
+++ b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/session/HazelcastSessionDistributionTests.java
@@ -11,7 +11,7 @@
// ========================================================================
//
-package org.eclipse.jetty.tests.distribution;
+package org.eclipse.jetty.tests.distribution.session;
import java.io.File;
import java.io.OutputStream;
@@ -21,6 +21,7 @@
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -28,8 +29,8 @@
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.tests.distribution.JettyHomeTester;
import org.junit.jupiter.api.Disabled;
-import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testcontainers.containers.BindMode;
@@ -42,90 +43,67 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
-public class HazelcastSessionDistributionTests extends AbstractJettyHomeTest
+/**
+ * This simulate the onlyClient option which means the JVM running Jetty is only an Hazelcast client and not part
+ * of the cluster
+ */
+public class HazelcastSessionDistributionTests extends AbstractSessionDistributionTests
{
- private static final Logger HAZELCAST_LOG = LoggerFactory.getLogger("org.eclipse.jetty.tests.distribution.HazelcastLogs");
+ private static final Logger HAZELCAST_LOG = LoggerFactory.getLogger("org.eclipse.jetty.tests.distribution.session.HazelcastLogs");
private static final Logger LOGGER = LoggerFactory.getLogger(HazelcastSessionDistributionTests.class);
+ private GenericContainer hazelcast = new GenericContainer("hazelcast/hazelcast:" + System.getProperty("hazelcast.version", "4.1"))
+ .withExposedPorts(5701)
+ .waitingFor(Wait.forListeningPort())
+ .withLogConsumer(new Slf4jLogConsumer(HAZELCAST_LOG));
- /**
- * This simulate the onlyClient option which means the JVM running Jetty is only an Hazelcast client and not part
- * of the cluster
- */
- @Test
- public void testHazelcastRemoteOnlyClient() throws Exception
- {
- try (GenericContainer hazelcast =
- new GenericContainer("hazelcast/hazelcast:" + System.getProperty("hazelcast.version", "4.1"))
- .withExposedPorts(5701)
- .waitingFor(Wait.forListeningPort())
- .withLogConsumer(new Slf4jLogConsumer(HAZELCAST_LOG)))
- {
- hazelcast.start();
- String hazelcastHost = hazelcast.getContainerIpAddress();
- int hazelcastPort = hazelcast.getMappedPort(5701);
+ private Path hazelcastJettyPath;
- LOGGER.info("hazelcast started on {}:{}", hazelcastHost, hazelcastPort);
-
- Map tokenValues = new HashMap<>();
- tokenValues.put("hazelcast_ip", hazelcastHost);
- tokenValues.put("hazelcast_port", Integer.toString(hazelcastPort));
- Path hazelcastJettyPath = Paths.get("target/hazelcast-client.xml");
- transformFileWithHostAndPort(Paths.get("src/test/resources/hazelcast-client.xml"),
- hazelcastJettyPath,
- tokenValues);
-
- String jettyVersion = System.getProperty("jettyVersion");
- JettyHomeTester distribution = JettyHomeTester.Builder.newInstance()
- .jettyVersion(jettyVersion)
- .mavenLocalRepository(System.getProperty("mavenRepoPath"))
- .build();
-
- String[] args1 = {
- "--create-startd",
- "--approve-all-licenses",
- "--add-to-start=resources,server,http,webapp,deploy,jmx,servlet,servlets,session-store-hazelcast-remote"
- };
- try (JettyHomeTester.Run run1 = distribution.start(args1))
- {
- assertTrue(run1.awaitFor(5, TimeUnit.SECONDS));
- assertEquals(0, run1.getExitValue());
+ @Override
+ public void startExternalSessionStorage() throws Exception
+ {
+ hazelcast.start();
- File war = distribution.resolveArtifact("org.eclipse.jetty.tests:test-simple-session-webapp:war:" + jettyVersion);
- distribution.installWarFile(war, "test");
+ String hazelcastHost = hazelcast.getContainerIpAddress();
+ int hazelcastPort = hazelcast.getMappedPort(5701);
- int port = distribution.freePort();
- String[] argsStart = {
- "jetty.http.port=" + port,
- "jetty.session.hazelcast.configurationLocation=" + hazelcastJettyPath.toAbsolutePath(),
- "jetty.session.hazelcast.onlyClient=true"
- };
- try (JettyHomeTester.Run run2 = distribution.start(argsStart))
- {
- assertTrue(run2.awaitConsoleLogsFor("Started Server@", 10, TimeUnit.SECONDS));
+ LOGGER.info("hazelcast started on {}:{}", hazelcastHost, hazelcastPort);
- startHttpClient();
- ContentResponse response = client.GET("http://localhost:" + port + "/test/session?action=CREATE");
- assertEquals(HttpStatus.OK_200, response.getStatus());
- assertThat(response.getContentAsString(), containsString("SESSION CREATED"));
+ Map tokenValues = new HashMap<>();
+ tokenValues.put("hazelcast_ip", hazelcastHost);
+ tokenValues.put("hazelcast_port", Integer.toString(hazelcastPort));
+ this.hazelcastJettyPath = Paths.get("target/hazelcast-client.xml");
+ transformFileWithHostAndPort(Paths.get("src/test/resources/hazelcast-client.xml"),
+ hazelcastJettyPath,
+ tokenValues);
+ }
- response = client.GET("http://localhost:" + port + "/test/session?action=READ");
- assertEquals(HttpStatus.OK_200, response.getStatus());
- assertThat(response.getContentAsString(), containsString("SESSION READ CHOCOLATE THE BEST:FRENCH"));
- }
+ @Override
+ public void stopExternalSessionStorage() throws Exception
+ {
+ hazelcast.stop();
+ }
- try (JettyHomeTester.Run run2 = distribution.start(argsStart))
- {
- assertTrue(run2.awaitConsoleLogsFor("Started Server@", 10, TimeUnit.SECONDS));
+ @Override
+ public List getFirstStartExtraArgs()
+ {
+ return Collections.emptyList();
+ }
- ContentResponse response = client.GET("http://localhost:" + port + "/test/session?action=READ");
- assertEquals(HttpStatus.OK_200, response.getStatus());
- assertThat(response.getContentAsString(), containsString("SESSION READ CHOCOLATE THE BEST:FRENCH"));
- }
- }
+ @Override
+ public String getFirstStartExtraModules()
+ {
+ return "session-store-hazelcast-remote";
+ }
- }
+ @Override
+ public List getSecondStartExtraArgs()
+ {
+ return Arrays.asList(
+ "jetty.session.hazelcast.configurationLocation=" + hazelcastJettyPath.toAbsolutePath(),
+ "jetty.session.hazelcast.onlyClient=true"
+ );
}
@Disabled("not working see https://github.com/hazelcast/hazelcast/issues/18508")
diff --git a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/session/InfinispanSessionDistributionTests.java b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/session/InfinispanSessionDistributionTests.java
new file mode 100644
index 000000000000..917c181b972a
--- /dev/null
+++ b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/session/InfinispanSessionDistributionTests.java
@@ -0,0 +1,116 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2021 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.tests.distribution.session;
+
+import java.io.Writer;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Properties;
+
+import org.eclipse.jetty.util.IO;
+import org.infinispan.client.hotrod.RemoteCacheManager;
+import org.infinispan.client.hotrod.configuration.Configuration;
+import org.infinispan.client.hotrod.configuration.ConfigurationBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.output.Slf4jLogConsumer;
+import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy;
+
+/**
+ *
+ */
+public class InfinispanSessionDistributionTests extends AbstractSessionDistributionTests
+{
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(InfinispanSessionDistributionTests.class);
+ private static final Logger INFINISPAN_LOG = LoggerFactory.getLogger("org.eclipse.jetty.tests.distribution.session.infinispan");
+
+ private GenericContainer infinispan;
+
+ private String host;
+
+ @Override
+ public void startExternalSessionStorage() throws Exception
+ {
+ String infinispanVersion = System.getProperty("infinispan.docker.image.version", "9.4.8.Final");
+ infinispan =
+ new GenericContainer(System.getProperty("infinispan.docker.image.name", "jboss/infinispan-server") +
+ ":" + infinispanVersion)
+ //.withEnv("APP_USER", "theuser")
+ //.withEnv("APP_PASS", "foobar")
+ .withEnv("MGMT_USER", "admin")
+ .withEnv("MGMT_PASS", "admin")
+ .withCommand("standalone")
+ .waitingFor(new LogMessageWaitStrategy()
+ .withRegEx(".*Infinispan Server.*started in.*\\s"))
+ .withExposedPorts(4712, 4713, 8088, 8089, 8443, 9990, 9993, 11211, 11222, 11223, 11224)
+ .withLogConsumer(new Slf4jLogConsumer(INFINISPAN_LOG));
+ infinispan.start();
+ String host = infinispan.getContainerIpAddress();
+ int port = infinispan.getMappedPort(11222);
+
+ Path resourcesDirectory = Path.of(jettyHomeTester.getJettyBase().toString(), "resources/");
+ if (Files.exists(resourcesDirectory))
+ {
+ IO.delete(resourcesDirectory.toFile());
+ }
+ Files.createDirectories(resourcesDirectory);
+ Properties properties = new Properties();
+ properties.put("infinispan.client.hotrod.server_list", host + ":" + port);
+ //properties.put("jetty.session.infinispan.clientIntelligence", "BASIC");
+
+ Path hotrod = Path.of(resourcesDirectory.toString(), "hotrod-client.properties");
+ Files.deleteIfExists(hotrod);
+ Files.createFile(hotrod);
+ try (Writer writer = Files.newBufferedWriter(hotrod))
+ {
+ properties.store(writer, null);
+ }
+
+ Configuration configuration = new ConfigurationBuilder().withProperties(properties)
+ .addServer().host(host).port(port).build();
+
+ RemoteCacheManager remoteCacheManager = new RemoteCacheManager(configuration);
+ remoteCacheManager.administration().getOrCreateCache("sessions", (String)null);
+
+ }
+
+ @Override
+ public void stopExternalSessionStorage() throws Exception
+ {
+ infinispan.stop();
+ }
+
+ @Override
+ public List getFirstStartExtraArgs()
+ {
+ return Arrays.asList();
+ }
+
+ @Override
+ public String getFirstStartExtraModules()
+ {
+ return "session-store-infinispan-remote";
+ }
+
+ @Override
+ public List getSecondStartExtraArgs()
+ {
+ return Arrays.asList();
+ }
+
+}
diff --git a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/session/JDBCSessionDistributionTests.java b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/session/JDBCSessionDistributionTests.java
new file mode 100644
index 000000000000..580a34317c39
--- /dev/null
+++ b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/session/JDBCSessionDistributionTests.java
@@ -0,0 +1,107 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2021 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.tests.distribution.session;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testcontainers.containers.MariaDBContainer;
+import org.testcontainers.containers.output.Slf4jLogConsumer;
+
+/**
+ *
+ */
+public class JDBCSessionDistributionTests extends AbstractSessionDistributionTests
+{
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(JDBCSessionDistributionTests.class);
+
+ private static final String MARIA_DB_USER = "beer";
+ private static final String MARIA_DB_PASSWORD = "pacific_ale";
+ private String jdbcUrl;
+ private String driverClassName;
+
+ private MariaDBContainer mariaDBContainer = new MariaDBContainer("mariadb:" + System.getProperty("mariadb.docker.version", "10.3.6"))
+ .withUsername(MARIA_DB_USER)
+ .withPassword(MARIA_DB_PASSWORD)
+ .withDatabaseName("sessions");
+
+ @Override
+ public void startExternalSessionStorage() throws Exception
+ {
+ mariaDBContainer.start();
+ jdbcUrl = mariaDBContainer.getJdbcUrl() + "?user=" + MARIA_DB_USER +
+ "&password=" + MARIA_DB_PASSWORD;
+ driverClassName = mariaDBContainer.getDriverClassName();
+
+ // prepare mariadb driver mod file
+ String mariaDBVersion = System.getProperty("mariadb.version");
+ StringBuilder modFileContent = new StringBuilder();
+ modFileContent.append("[lib]").append(System.lineSeparator());
+ modFileContent.append("lib/mariadb-java-client-" + mariaDBVersion + ".jar").append(System.lineSeparator());
+ modFileContent.append("[files]").append(System.lineSeparator());
+ modFileContent.append("maven://org.mariadb.jdbc/mariadb-java-client/" + mariaDBVersion +
+ "|lib/mariadb-java-client-" + mariaDBVersion + ".jar")
+ .append(System.lineSeparator());
+
+ Path modulesDirectory = Path.of(jettyHomeTester.getJettyBase().toString(), "modules");
+ if (Files.notExists(modulesDirectory))
+ {
+ Files.createDirectories(modulesDirectory);
+ }
+ Path mariaDbModPath = Path.of(modulesDirectory.toString(), "mariadb-driver.mod");
+ Files.deleteIfExists(mariaDbModPath);
+ Files.createFile(mariaDbModPath);
+ LOGGER.info("create file modfile: {} with content {} ", mariaDbModPath, modFileContent);
+ Files.writeString(mariaDbModPath, modFileContent);
+ }
+
+ @Override
+ public void stopExternalSessionStorage() throws Exception
+ {
+ mariaDBContainer.stop();
+ }
+
+ @Override
+ public List getFirstStartExtraArgs()
+ {
+ return Arrays.asList(
+ "jetty.session.jdbc.driverUrl=" + jdbcUrl,
+ "db-connection-type=driver",
+ "jetty.session.jdbc.driverClass=" + driverClassName
+ );
+ }
+
+ @Override
+ public String getFirstStartExtraModules()
+ {
+ return "session-store-jdbc,mariadb-driver";
+ }
+
+ @Override
+ public List getSecondStartExtraArgs()
+ {
+ return Arrays.asList(
+ "jetty.session.jdbc.driverUrl=" + jdbcUrl,
+ "db-connection-type=driver",
+ "jetty.session.jdbc.driverClass=" + driverClassName
+ );
+ }
+
+}
diff --git a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/session/MongodbSessionDistributionTests.java b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/session/MongodbSessionDistributionTests.java
new file mode 100644
index 000000000000..80558f61d3cb
--- /dev/null
+++ b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/session/MongodbSessionDistributionTests.java
@@ -0,0 +1,79 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2021 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.tests.distribution.session;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.output.Slf4jLogConsumer;
+import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy;
+
+/**
+ *
+ */
+public class MongodbSessionDistributionTests extends AbstractSessionDistributionTests
+{
+ private static final Logger LOGGER = LoggerFactory.getLogger(MongodbSessionDistributionTests.class);
+
+ private static final Logger MONGO_LOG = LoggerFactory.getLogger("org.eclipse.jetty.tests.distribution.session.mongo");
+
+ final String imageName = "mongo:" + System.getProperty("mongo.docker.version", "2.2.7");
+ final GenericContainer mongoDBContainer =
+ new GenericContainer(imageName)
+ .withLogConsumer(new Slf4jLogConsumer(MONGO_LOG))
+ .waitingFor(new LogMessageWaitStrategy()
+ .withRegEx(".*waiting for connections.*"));
+ private String host;
+ private int port;
+
+ @Override
+ public void startExternalSessionStorage() throws Exception
+ {
+ mongoDBContainer.start();
+ host = mongoDBContainer.getHost();
+ port = mongoDBContainer.getMappedPort(27017);
+ }
+
+ @Override
+ public void stopExternalSessionStorage() throws Exception
+ {
+ mongoDBContainer.stop();
+ }
+
+ @Override
+ public List getFirstStartExtraArgs()
+ {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public String getFirstStartExtraModules()
+ {
+ return "session-store-mongo";
+ }
+
+ @Override
+ public List getSecondStartExtraArgs()
+ {
+ return Arrays.asList(
+ "jetty.session.mongo.host=" + host,
+ "jetty.session.mongo.port=" + port
+ );
+ }
+
+}
diff --git a/tests/test-http-client-transport/pom.xml b/tests/test-http-client-transport/pom.xml
index 9c2aab3097d6..8c508d35092b 100644
--- a/tests/test-http-client-transport/pom.xml
+++ b/tests/test-http-client-transport/pom.xml
@@ -46,6 +46,11 @@
slf4j-apitest
+
+ org.awaitility
+ awaitility
+ test
+ org.eclipse.jettyjetty-alpn-java-client
diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/AsyncIOServletTest.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/AsyncIOServletTest.java
index d1c573059be6..571839413d85 100644
--- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/AsyncIOServletTest.java
+++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/AsyncIOServletTest.java
@@ -60,11 +60,13 @@
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.client.http.HttpConnectionOverHTTP2;
import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EofException;
import org.eclipse.jetty.logging.StacklessLogging;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpChannel;
import org.eclipse.jetty.server.HttpInput;
import org.eclipse.jetty.server.HttpInput.Content;
+import org.eclipse.jetty.server.HttpOutput;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ContextHandler.Context;
@@ -74,12 +76,11 @@
import org.eclipse.jetty.util.compression.InflaterPool;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Assumptions;
-import org.junit.jupiter.api.Disabled;
-import org.junit.jupiter.api.Tag;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ArgumentsSource;
import static java.nio.ByteBuffer.wrap;
+import static org.awaitility.Awaitility.await;
import static org.eclipse.jetty.http.client.Transport.FCGI;
import static org.eclipse.jetty.http.client.Transport.H2C;
import static org.eclipse.jetty.http.client.Transport.HTTP;
@@ -398,18 +399,11 @@ public void onError(Throwable t)
@ParameterizedTest
@ArgumentsSource(TransportProvider.class)
- @Tag("Unstable")
- @Disabled
public void testAsyncWriteClosed(Transport transport) throws Exception
{
init(transport);
- String text = "Now is the winter of our discontent. How Now Brown Cow. The quick brown fox jumped over the lazy dog.\n";
- for (int i = 0; i < 10; i++)
- {
- text = text + text;
- }
- byte[] data = text.getBytes(StandardCharsets.UTF_8);
+ byte[] data = new byte[1024];
CountDownLatch errorLatch = new CountDownLatch(1);
scenario.start(new HttpServlet()
@@ -431,9 +425,26 @@ public void onWritePossible() throws IOException
// Wait for the failure to arrive to
// the server while we are about to write.
- sleep(2000);
-
- out.write(data);
+ try
+ {
+ await().atMost(5, TimeUnit.SECONDS).until(() ->
+ {
+ try
+ {
+ if (out.isReady())
+ ((HttpOutput)out).write(ByteBuffer.wrap(data));
+ return false;
+ }
+ catch (EofException e)
+ {
+ return true;
+ }
+ });
+ }
+ catch (Exception e)
+ {
+ throw new AssertionError(e);
+ }
}
@Override
diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientContinueTest.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientContinueTest.java
index 1eb3d3e2be78..55cd8ae642a8 100644
--- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientContinueTest.java
+++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientContinueTest.java
@@ -14,6 +14,7 @@
package org.eclipse.jetty.http.client;
import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -25,8 +26,10 @@
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
+import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -45,11 +48,10 @@
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.util.IO;
-import org.junit.jupiter.api.Tag;
-import org.junit.jupiter.api.condition.DisabledIfSystemProperty;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ArgumentsSource;
+import static org.awaitility.Awaitility.await;
import static org.eclipse.jetty.http.client.Transport.FCGI;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -319,36 +321,40 @@ public void onComplete(Result result)
@ParameterizedTest
@ArgumentsSource(TransportProvider.class)
- @Tag("Slow")
- @DisabledIfSystemProperty(named = "env", matches = "ci") // TODO: SLOW, needs review
public void testExpect100ContinueWithContentWithResponseFailureBefore100Continue(Transport transport) throws Exception
{
init(transport);
- long idleTimeout = 1000;
+ AtomicReference clientRequestRef = new AtomicReference<>();
+ CountDownLatch clientLatch = new CountDownLatch(1);
+ CountDownLatch serverLatch = new CountDownLatch(1);
+
scenario.startServer(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws ServletException
{
baseRequest.setHandled(true);
+ clientRequestRef.get().abort(new Exception("abort!"));
try
{
- TimeUnit.MILLISECONDS.sleep(2 * idleTimeout);
+ if (!clientLatch.await(5, TimeUnit.SECONDS))
+ throw new ServletException("Server timed out on client latch");
+ serverLatch.countDown();
}
- catch (InterruptedException x)
+ catch (InterruptedException e)
{
- throw new ServletException(x);
+ throw new ServletException(e);
}
}
});
- scenario.startClient(httpClient -> httpClient.setIdleTimeout(2 * idleTimeout));
+ scenario.startClient();
byte[] content = new byte[1024];
- CountDownLatch latch = new CountDownLatch(1);
- scenario.client.newRequest(scenario.newURI())
+ org.eclipse.jetty.client.api.Request clientRequest = scenario.client.newRequest(scenario.newURI());
+ clientRequestRef.set(clientRequest);
+ clientRequest
.headers(headers -> headers.put(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE))
.body(new BytesRequestContent(content))
- .idleTimeout(idleTimeout, TimeUnit.MILLISECONDS)
.send(new BufferingResponseListener()
{
@Override
@@ -357,21 +363,22 @@ public void onComplete(Result result)
assertTrue(result.isFailed());
assertNotNull(result.getRequestFailure());
assertNotNull(result.getResponseFailure());
- latch.countDown();
+ clientLatch.countDown();
}
});
- assertTrue(latch.await(3 * idleTimeout, TimeUnit.MILLISECONDS));
+ assertTrue(clientLatch.await(5, TimeUnit.SECONDS));
+ assertTrue(serverLatch.await(5, TimeUnit.SECONDS));
}
@ParameterizedTest
@ArgumentsSource(TransportProvider.class)
- @Tag("Slow")
- @DisabledIfSystemProperty(named = "env", matches = "ci") // TODO: SLOW, needs review
public void testExpect100ContinueWithContentWithResponseFailureAfter100Continue(Transport transport) throws Exception
{
init(transport);
- long idleTimeout = 1000;
+ AtomicReference clientRequestRef = new AtomicReference<>();
+ CountDownLatch clientLatch = new CountDownLatch(1);
+ CountDownLatch serverLatch = new CountDownLatch(1);
scenario.startServer(new AbstractHandler()
{
@Override
@@ -380,9 +387,12 @@ public void handle(String target, Request baseRequest, HttpServletRequest reques
baseRequest.setHandled(true);
// Send 100-Continue and consume the content
IO.copy(request.getInputStream(), new ByteArrayOutputStream());
+ clientRequestRef.get().abort(new Exception("abort!"));
try
{
- TimeUnit.MILLISECONDS.sleep(2 * idleTimeout);
+ if (!clientLatch.await(5, TimeUnit.SECONDS))
+ throw new ServletException("Server timed out on client latch");
+ serverLatch.countDown();
}
catch (InterruptedException x)
{
@@ -390,11 +400,12 @@ public void handle(String target, Request baseRequest, HttpServletRequest reques
}
}
});
- scenario.startClient(httpClient -> httpClient.setIdleTimeout(idleTimeout));
+ scenario.startClient();
byte[] content = new byte[1024];
- CountDownLatch latch = new CountDownLatch(1);
- scenario.client.newRequest(scenario.newURI())
+ org.eclipse.jetty.client.api.Request clientRequest = scenario.client.newRequest(scenario.newURI());
+ clientRequestRef.set(clientRequest);
+ clientRequest
.headers(headers -> headers.put(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE))
.body(new BytesRequestContent(content))
.send(new BufferingResponseListener()
@@ -405,11 +416,12 @@ public void onComplete(Result result)
assertTrue(result.isFailed());
assertNull(result.getRequestFailure());
assertNotNull(result.getResponseFailure());
- latch.countDown();
+ clientLatch.countDown();
}
});
- assertTrue(latch.await(3 * idleTimeout, TimeUnit.MILLISECONDS));
+ assertTrue(clientLatch.await(5, TimeUnit.SECONDS));
+ assertTrue(serverLatch.await(5, TimeUnit.SECONDS));
}
@ParameterizedTest
@@ -474,10 +486,16 @@ public void onComplete(Result result)
@ParameterizedTest
@ArgumentsSource(TransportProvider.class)
- @Tag("Slow")
- @DisabledIfSystemProperty(named = "env", matches = "ci") // TODO: SLOW, needs review
public void testExpect100ContinueWithDeferredContentRespond100Continue(Transport transport) throws Exception
{
+ byte[] chunk1 = new byte[]{0, 1, 2, 3};
+ byte[] chunk2 = new byte[]{4, 5, 6, 7};
+ byte[] data = new byte[chunk1.length + chunk2.length];
+ System.arraycopy(chunk1, 0, data, 0, chunk1.length);
+ System.arraycopy(chunk2, 0, data, chunk1.length, chunk2.length);
+
+ CountDownLatch serverLatch = new CountDownLatch(1);
+ AtomicReference handlerThread = new AtomicReference<>();
init(transport);
scenario.start(new AbstractHandler()
{
@@ -485,18 +503,22 @@ public void testExpect100ContinueWithDeferredContentRespond100Continue(Transport
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
{
baseRequest.setHandled(true);
+ handlerThread.set(Thread.currentThread());
// Send 100-Continue and echo the content
- IO.copy(request.getInputStream(), response.getOutputStream());
+
+ ServletOutputStream outputStream = response.getOutputStream();
+ DataInputStream inputStream = new DataInputStream(request.getInputStream());
+ // Block until the 1st chunk is fully received.
+ byte[] buf1 = new byte[chunk1.length];
+ inputStream.readFully(buf1);
+ outputStream.write(buf1);
+
+ serverLatch.countDown();
+ IO.copy(inputStream, outputStream);
}
});
- byte[] chunk1 = new byte[]{0, 1, 2, 3};
- byte[] chunk2 = new byte[]{4, 5, 6, 7};
- byte[] data = new byte[chunk1.length + chunk2.length];
- System.arraycopy(chunk1, 0, data, 0, chunk1.length);
- System.arraycopy(chunk2, 0, data, chunk1.length, chunk2.length);
-
- CountDownLatch latch = new CountDownLatch(1);
+ CountDownLatch requestLatch = new CountDownLatch(1);
AsyncRequestContent content = new AsyncRequestContent();
scenario.client.newRequest(scenario.newURI())
.headers(headers -> headers.put(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE))
@@ -507,28 +529,38 @@ public void handle(String target, Request baseRequest, HttpServletRequest reques
public void onComplete(Result result)
{
assertArrayEquals(data, getContent());
- latch.countDown();
+ requestLatch.countDown();
}
});
- Thread.sleep(1000);
+ // Wait for the handler thread to be blocked in the 1st IO.
+ await().atMost(5, TimeUnit.SECONDS).until(() ->
+ {
+ Thread thread = handlerThread.get();
+ return thread != null && thread.getState() == Thread.State.WAITING;
+ });
content.offer(ByteBuffer.wrap(chunk1));
- Thread.sleep(1000);
+ // Wait for the handler thread to be blocked in the 2nd IO.
+ assertTrue(serverLatch.await(5, TimeUnit.SECONDS));
+ await().atMost(5, TimeUnit.SECONDS).until(() ->
+ {
+ Thread thread = handlerThread.get();
+ return thread != null && thread.getState() == Thread.State.WAITING;
+ });
content.offer(ByteBuffer.wrap(chunk2));
content.close();
- assertTrue(latch.await(5, TimeUnit.SECONDS));
+ assertTrue(requestLatch.await(5, TimeUnit.SECONDS));
}
@ParameterizedTest
@ArgumentsSource(TransportProvider.class)
- @Tag("Slow")
- @DisabledIfSystemProperty(named = "env", matches = "ci") // TODO: SLOW, needs review
public void testExpect100ContinueWithInitialAndDeferredContentRespond100Continue(Transport transport) throws Exception
{
+ AtomicReference handlerThread = new AtomicReference<>();
init(transport);
scenario.start(new AbstractHandler()
{
@@ -536,6 +568,7 @@ public void testExpect100ContinueWithInitialAndDeferredContentRespond100Continue
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
{
baseRequest.setHandled(true);
+ handlerThread.set(Thread.currentThread());
// Send 100-Continue and echo the content
IO.copy(request.getInputStream(), response.getOutputStream());
}
@@ -562,7 +595,12 @@ public void onComplete(Result result)
}
});
- Thread.sleep(1000);
+ // Wait for the handler thread to be blocked in IO.
+ await().atMost(5, TimeUnit.SECONDS).until(() ->
+ {
+ Thread thread = handlerThread.get();
+ return thread != null && thread.getState() == Thread.State.WAITING;
+ });
content.offer(ByteBuffer.wrap(chunk2));
content.close();
diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientStreamTest.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientStreamTest.java
index 70639327cebc..6e49d148f4b5 100644
--- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientStreamTest.java
+++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientStreamTest.java
@@ -58,7 +58,6 @@
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.IO;
-import org.junit.jupiter.api.condition.DisabledIfSystemProperty;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ArgumentsSource;
@@ -666,7 +665,6 @@ public void handle(String target, org.eclipse.jetty.server.Request baseRequest,
@ParameterizedTest
@ArgumentsSource(TransportProvider.class)
- @DisabledIfSystemProperty(named = "env", matches = "ci") // TODO: SLOW, needs review
public void testUploadWithDeferredContentProviderFromInputStream(Transport transport) throws Exception
{
init(transport);
@@ -680,20 +678,22 @@ public void handle(String target, org.eclipse.jetty.server.Request baseRequest,
}
});
- CountDownLatch latch = new CountDownLatch(1);
+ CountDownLatch requestSentLatch = new CountDownLatch(1);
+ CountDownLatch responseLatch = new CountDownLatch(1);
try (AsyncRequestContent content = new AsyncRequestContent())
{
scenario.client.newRequest(scenario.newURI())
.scheme(scenario.getScheme())
.body(content)
+ .onRequestCommit((request) -> requestSentLatch.countDown())
.send(result ->
{
if (result.isSucceeded() && result.getResponse().getStatus() == 200)
- latch.countDown();
+ responseLatch.countDown();
});
// Make sure we provide the content *after* the request has been "sent".
- Thread.sleep(1000);
+ assertTrue(requestSentLatch.await(5, TimeUnit.SECONDS));
try (ByteArrayInputStream input = new ByteArrayInputStream(new byte[1024]))
{
@@ -705,7 +705,7 @@ public void handle(String target, org.eclipse.jetty.server.Request baseRequest,
}
}
}
- assertTrue(latch.await(5, TimeUnit.SECONDS));
+ assertTrue(responseLatch.await(5, TimeUnit.SECONDS));
}
@ParameterizedTest
diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientTest.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientTest.java
index 4bb62d62dafe..92522a15da10 100644
--- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientTest.java
+++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientTest.java
@@ -22,9 +22,11 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.IntStream;
+import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
@@ -781,6 +783,58 @@ protected void service(String target, Request jettyRequest, HttpServletRequest r
assertTrue(resultLatch.await(5, TimeUnit.SECONDS));
}
+ @ParameterizedTest
+ @ArgumentsSource(TransportProvider.class)
+ public void testRequestIdleTimeout(Transport transport) throws Exception
+ {
+ init(transport);
+
+ CountDownLatch latch = new CountDownLatch(1);
+ long idleTimeout = 500;
+ scenario.start(new AbstractHandler()
+ {
+ @Override
+ public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws ServletException
+ {
+ try
+ {
+ baseRequest.setHandled(true);
+ if (target.equals("/1"))
+ assertTrue(latch.await(5, TimeUnit.SECONDS));
+ else if (target.equals("/2"))
+ Thread.sleep(2 * idleTimeout);
+ else
+ fail("Unknown path: " + target);
+ }
+ catch (InterruptedException x)
+ {
+ throw new ServletException(x);
+ }
+ }
+ });
+
+ String host = "localhost";
+ int port = scenario.getNetworkConnectorLocalPortInt().get();
+ assertThrows(TimeoutException.class, () ->
+ scenario.client.newRequest(host, port)
+ .scheme(scenario.getScheme())
+ .path("/1")
+ .idleTimeout(idleTimeout, TimeUnit.MILLISECONDS)
+ .timeout(2 * idleTimeout, TimeUnit.MILLISECONDS)
+ .send());
+ latch.countDown();
+
+ // Make another request without specifying the idle timeout, should not fail
+ ContentResponse response = scenario.client.newRequest(host, port)
+ .scheme(scenario.getScheme())
+ .path("/2")
+ .timeout(3 * idleTimeout, TimeUnit.MILLISECONDS)
+ .send();
+
+ assertNotNull(response);
+ assertEquals(200, response.getStatus());
+ }
+
private void sleep(long time) throws IOException
{
try
diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/TransportScenario.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/TransportScenario.java
index a0d49548f065..4f5b0f3990a9 100644
--- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/TransportScenario.java
+++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/TransportScenario.java
@@ -17,7 +17,6 @@
import java.lang.management.ManagementFactory;
import java.nio.file.Files;
import java.nio.file.Path;
-import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@@ -52,12 +51,10 @@
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
-import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.unixsocket.client.HttpClientTransportOverUnixSockets;
import org.eclipse.jetty.unixsocket.server.UnixSocketConnector;
import org.eclipse.jetty.util.BlockingArrayQueue;
import org.eclipse.jetty.util.SocketAddressResolver;
-import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.junit.jupiter.api.Assumptions;
@@ -65,7 +62,8 @@
import org.slf4j.LoggerFactory;
import static org.eclipse.jetty.http.client.Transport.UNIX_SOCKET;
-import static org.junit.jupiter.api.Assumptions.assumeTrue;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
public class TransportScenario
{
@@ -86,20 +84,10 @@ public TransportScenario(final Transport transport) throws IOException
{
this.transport = transport;
- Path unixSocketTmp;
- String tmpProp = System.getProperty("unix.socket.tmp");
- if (StringUtil.isBlank(tmpProp))
- unixSocketTmp = MavenTestingUtils.getTargetPath();
- else
- unixSocketTmp = Paths.get(tmpProp);
- sockFile = Files.createTempFile(unixSocketTmp, "unix", ".sock");
- if (sockFile.toAbsolutePath().toString().length() > UnixSocketConnector.MAX_UNIX_SOCKET_PATH_LENGTH)
- {
- Files.delete(sockFile);
- Path tmp = Paths.get("/tmp");
- assumeTrue(Files.exists(tmp) && Files.isDirectory(tmp));
- sockFile = Files.createTempFile(tmp, "unix", ".sock");
- }
+ String dir = System.getProperty("jetty.unixdomain.dir");
+ assertNotNull(dir);
+ sockFile = Files.createTempFile(Path.of(dir), "unix_", ".sock");
+ assertTrue(sockFile.toAbsolutePath().toString().length() < UnixSocketConnector.MAX_UNIX_SOCKET_PATH_LENGTH, "Unix-Domain path too long");
Files.delete(sockFile);
// Disable UNIX_SOCKET due to jnr/jnr-unixsocket#69.
diff --git a/tests/test-loginservice/pom.xml b/tests/test-loginservice/pom.xml
index 959214680b54..a53fd881972f 100644
--- a/tests/test-loginservice/pom.xml
+++ b/tests/test-loginservice/pom.xml
@@ -65,7 +65,7 @@
org.mariadb.jdbcmariadb-java-client
- ${maria.version}
+ ${mariadb.version}test
diff --git a/tests/test-sessions/test-jdbc-sessions/pom.xml b/tests/test-sessions/test-jdbc-sessions/pom.xml
index a38fab2cf2df..cb114b0ef0be 100644
--- a/tests/test-sessions/test-jdbc-sessions/pom.xml
+++ b/tests/test-sessions/test-jdbc-sessions/pom.xml
@@ -10,7 +10,6 @@
Jetty Tests :: Sessions :: JDBC${project.groupId}.sessions.jdbc
- 10.3.6
@@ -91,7 +90,6 @@
org.mariadb.jdbcmariadb-java-client
- ${maria.version}test