Merge remote-tracking branch 'origin/jetty-10.0.x' into jetty-10.0.x-6106-WebSocketCDI
This commit is contained in:
commit
a0cca858a7
44
VERSION.txt
44
VERSION.txt
|
@ -27,13 +27,13 @@ jetty-10.0.2 - 26 March 2021
|
||||||
+ 6037 Review logging modules for j.u.l.
|
+ 6037 Review logging modules for j.u.l.
|
||||||
+ 6050 Websocket: NotUtf8Exception after upgrade 9.4.35 -> 9.4.36 or newer
|
+ 6050 Websocket: NotUtf8Exception after upgrade 9.4.35 -> 9.4.36 or newer
|
||||||
+ 6063 Allow override of hazelcast version when using module
|
+ 6063 Allow override of hazelcast version when using module
|
||||||
+ 6072 jetty server high CPU when client send data length > 17408
|
+ 6072 jetty server high CPU when client send data length > 17408 - Resolves CVE-2021-28165
|
||||||
+ 6076 Embedded Jetty throws null pointer exception
|
+ 6076 Embedded Jetty throws null pointer exception
|
||||||
+ 6082 SslConnection compacting
|
+ 6082 SslConnection compacting
|
||||||
+ 6085 Jetty keeps Sessions in use after "Duplicate valid session cookies"
|
+ 6085 Jetty keeps Sessions in use after "Duplicate valid session cookies"
|
||||||
Message
|
Message
|
||||||
+ 6101 Normalise ambiguous URIs
|
+ 6101 Normalize ambiguous URIs - Resolves CVE-2021-28164
|
||||||
+ 6102 Exclude webapps directory from deployment scan
|
+ 6102 Exclude webapps directory from deployment scan - Resolves CVE-2021-28163
|
||||||
|
|
||||||
jetty-10.0.1 - 19 February 2021
|
jetty-10.0.1 - 19 February 2021
|
||||||
+ 1673 jetty-demo/etc/keystore should not be distributed
|
+ 1673 jetty-demo/etc/keystore should not be distributed
|
||||||
|
@ -133,8 +133,22 @@ jetty-10.0.0.beta3 - 21 October 2020
|
||||||
+ 5475 Update to spifly 1.3.2 and asm 9
|
+ 5475 Update to spifly 1.3.2 and asm 9
|
||||||
+ 5480 NPE from WebInfConfiguration.deconfigure during WebAppContext shutdown
|
+ 5480 NPE from WebInfConfiguration.deconfigure during WebAppContext shutdown
|
||||||
|
|
||||||
|
jetty-9.4.39.v20210325 - 25 March 2021
|
||||||
|
+ 6034 SslContextFactory may select a wildcard certificate during SNI
|
||||||
|
selection when a more specific SSL certificate is present
|
||||||
|
+ 6050 Websocket: NotUtf8Exception after upgrade to 9.4.36 or newer
|
||||||
|
+ 6052 Cleanup TypeUtil and ModuleLocation to allow jetty-client/hybrid to
|
||||||
|
work on Android
|
||||||
|
+ 6063 Allow override of hazelcast version when using module
|
||||||
|
+ 6072 jetty server high CPU when client send data length > 17408 - Resolves CVE-2021-28165
|
||||||
|
+ 6085 Jetty keeps Sessions in use after "Duplicate valid session cookies"
|
||||||
|
Message
|
||||||
|
+ 6101 Normalize ambiguous URIs - Resolves CVE-2021-28164
|
||||||
|
+ 6102 Exclude webapps directory from deployment scan - Resolves CVE-2021-28163
|
||||||
|
|
||||||
jetty-9.4.38.v20210224 - 24 February 2021
|
jetty-9.4.38.v20210224 - 24 February 2021
|
||||||
+ 4275 Path Normalization/Traversal - Context Matching
|
+ 4275 Path Normalization/Traversal - Context Matching
|
||||||
|
+ 5963 Improve QuotedQualityCSV for CVE-2020-27223
|
||||||
+ 5977 Cache-Control header set by a filter is override by the value from
|
+ 5977 Cache-Control header set by a filter is override by the value from
|
||||||
DefaultServlet configuration
|
DefaultServlet configuration
|
||||||
+ 5994 QueuedThreadPool "free" threads
|
+ 5994 QueuedThreadPool "free" threads
|
||||||
|
@ -158,7 +172,7 @@ jetty-9.4.37.v20210219 - 19 February 2021
|
||||||
+ 5979 Configurable gzip Etag extension
|
+ 5979 Configurable gzip Etag extension
|
||||||
|
|
||||||
jetty-9.4.36.v20210114 - 14 January 2021
|
jetty-9.4.36.v20210114 - 14 January 2021
|
||||||
+ 5310 Jetty Http2 client discards the response fames when there is GOAWAY and
|
+ 5310 Jetty Http2 client discards the response frames when there is GOAWAY and
|
||||||
sends RST_STREAM
|
sends RST_STREAM
|
||||||
+ 5499 Improve temporary buffer usage for WebSocket PerMessageDeflate
|
+ 5499 Improve temporary buffer usage for WebSocket PerMessageDeflate
|
||||||
+ 5633 Allow to configure HttpClient request authority
|
+ 5633 Allow to configure HttpClient request authority
|
||||||
|
@ -420,7 +434,6 @@ jetty-9.4.31.v20200723 - 23 July 2020
|
||||||
+ 5057 `javax.servlet.include.context_path` attribute on root context. should
|
+ 5057 `javax.servlet.include.context_path` attribute on root context. should
|
||||||
be empty string, but is `"/"`
|
be empty string, but is `"/"`
|
||||||
+ 5064 NotSerializableException for OpenIdConfiguration
|
+ 5064 NotSerializableException for OpenIdConfiguration
|
||||||
+ 5069 HttpClientTimeoutTests can occasionally fail due to unreachable network
|
|
||||||
|
|
||||||
jetty-9.4.30.v20200611 - 11 June 2020
|
jetty-9.4.30.v20200611 - 11 June 2020
|
||||||
+ 4776 Incorrect path matching for WebSocket using PathMappings
|
+ 4776 Incorrect path matching for WebSocket using PathMappings
|
||||||
|
@ -723,10 +736,8 @@ jetty-9.4.20.v20190813 - 13 August 2019
|
||||||
+ 3648 javax.websocket client container incorrectly creates Server
|
+ 3648 javax.websocket client container incorrectly creates Server
|
||||||
SslContextFactory
|
SslContextFactory
|
||||||
+ 3698 Missing WebSocket ServerContainer after server restart
|
+ 3698 Missing WebSocket ServerContainer after server restart
|
||||||
+ 3700 stackoverflow in WebAppClassLoaderUrlStreamTest
|
|
||||||
+ 3708 Swap various java.lang.String replace() methods for better performant
|
+ 3708 Swap various java.lang.String replace() methods for better performant
|
||||||
ones
|
ones
|
||||||
+ 3731 Add testing of CDI behaviors
|
|
||||||
+ 3736 NPE from WebAppClassLoader during CDI
|
+ 3736 NPE from WebAppClassLoader during CDI
|
||||||
+ 3746 ClassCastException in WriteFlusher.java - IdleState cannot be cast to
|
+ 3746 ClassCastException in WriteFlusher.java - IdleState cannot be cast to
|
||||||
FailedState
|
FailedState
|
||||||
|
@ -928,7 +939,6 @@ jetty-9.2.27.v20190403 - 03 April 2019
|
||||||
|
|
||||||
jetty-9.4.14.v20181114 - 14 November 2018
|
jetty-9.4.14.v20181114 - 14 November 2018
|
||||||
+ 3097 Duplicated programmatic Servlet Listeners causing duplicate calls
|
+ 3097 Duplicated programmatic Servlet Listeners causing duplicate calls
|
||||||
+ 3103 HttpClientLoadTest reports a leak in byte buffer
|
|
||||||
+ 3104 Align jetty-schemas version within apache-jsp module as well
|
+ 3104 Align jetty-schemas version within apache-jsp module as well
|
||||||
|
|
||||||
jetty-9.4.13.v20181111 - 11 November 2018
|
jetty-9.4.13.v20181111 - 11 November 2018
|
||||||
|
@ -992,8 +1002,6 @@ jetty-9.4.12.v20180830 - 30 August 2018
|
||||||
Runtimes
|
Runtimes
|
||||||
+ 2075 Deprecating MultiException
|
+ 2075 Deprecating MultiException
|
||||||
+ 2135 Android 8.1 needs direct buffers for SSL/TLS to work
|
+ 2135 Android 8.1 needs direct buffers for SSL/TLS to work
|
||||||
+ 2233 JDK9 Test failure:
|
|
||||||
org.eclipse.jetty.server.ThreadStarvationTest.testWriteStarvation[https/ssl/tls]
|
|
||||||
+ 2342 File Descriptor Leak: Conscrypt: "Too many open files"
|
+ 2342 File Descriptor Leak: Conscrypt: "Too many open files"
|
||||||
+ 2349 HTTP/2 max streams enforcement
|
+ 2349 HTTP/2 max streams enforcement
|
||||||
+ 2398 MultiPartFormInputStream parsing should default to UTF-8, but allowed
|
+ 2398 MultiPartFormInputStream parsing should default to UTF-8, but allowed
|
||||||
|
@ -1003,9 +1011,6 @@ jetty-9.4.12.v20180830 - 30 August 2018
|
||||||
+ 2530 Client waits forever for cancelled large uploads
|
+ 2530 Client waits forever for cancelled large uploads
|
||||||
+ 2560 Review PathResource exception handling
|
+ 2560 Review PathResource exception handling
|
||||||
+ 2565 HashLoginService silently ignores file:/ config paths from 9.3.x
|
+ 2565 HashLoginService silently ignores file:/ config paths from 9.3.x
|
||||||
+ 2592 Failing test on Windows:
|
|
||||||
ServerTimeoutsTest.testAsyncWriteIdleTimeoutFires[transport: HTTP]
|
|
||||||
+ 2597 Failing tests on windows UnixSocketTest
|
|
||||||
+ 2631 IllegalArgumentException: Buffering capacity exceeded, from HttpClient
|
+ 2631 IllegalArgumentException: Buffering capacity exceeded, from HttpClient
|
||||||
HEAD Requests to resources referencing large body contents
|
HEAD Requests to resources referencing large body contents
|
||||||
+ 2648 LdapLoginModule fails with forceBinding=true under Java 9
|
+ 2648 LdapLoginModule fails with forceBinding=true under Java 9
|
||||||
|
@ -1067,7 +1072,6 @@ jetty-9.4.12.v20180830 - 30 August 2018
|
||||||
hot redeploy on Windows
|
hot redeploy on Windows
|
||||||
+ 2836 Sequential HTTPS requests may not reuse the same connection
|
+ 2836 Sequential HTTPS requests may not reuse the same connection
|
||||||
+ 2844 Clean up webdefault.xml and DefaultServlet doc
|
+ 2844 Clean up webdefault.xml and DefaultServlet doc
|
||||||
+ 2846 add unit test for ldap module
|
|
||||||
+ 2847 Wrap Connection.Listener invocations in try/catch
|
+ 2847 Wrap Connection.Listener invocations in try/catch
|
||||||
+ 2860 Leakage of HttpDestinations in HttpClient
|
+ 2860 Leakage of HttpDestinations in HttpClient
|
||||||
+ 2871 Server reads -1 after client resets HTTP/2 stream
|
+ 2871 Server reads -1 after client resets HTTP/2 stream
|
||||||
|
@ -1426,7 +1430,6 @@ jetty-9.4.7.v20170914 - 14 September 2017
|
||||||
+ 1759 HTTP/2: producer can block in onReset
|
+ 1759 HTTP/2: producer can block in onReset
|
||||||
+ 1766 JettyClientContainerProvider does not actually use common objects
|
+ 1766 JettyClientContainerProvider does not actually use common objects
|
||||||
correctly
|
correctly
|
||||||
+ 1789 PropertyUserStoreTest failures in Windows
|
|
||||||
+ 1790 HTTP/2: 100% CPU usage seen during close/shutdown of endpoint
|
+ 1790 HTTP/2: 100% CPU usage seen during close/shutdown of endpoint
|
||||||
+ 1792 Accept ISO-8859-1 characters in response reason
|
+ 1792 Accept ISO-8859-1 characters in response reason
|
||||||
+ 1794 Config properties typos in session-store-cache.mod
|
+ 1794 Config properties typos in session-store-cache.mod
|
||||||
|
@ -1439,8 +1442,6 @@ jetty-9.4.7.v20170914 - 14 September 2017
|
||||||
+ 1809 NPE: StandardDescriptorProcessor.visitSecurityConstraint() with null/no
|
+ 1809 NPE: StandardDescriptorProcessor.visitSecurityConstraint() with null/no
|
||||||
security manager
|
security manager
|
||||||
+ 1814 Move JavaVersion to jetty-util for future Java 9 support requirements
|
+ 1814 Move JavaVersion to jetty-util for future Java 9 support requirements
|
||||||
+ 1816 HttpClientTest.testClientCannotValidateServerCertificate() hangs with
|
|
||||||
JDK 9
|
|
||||||
+ 475546 ClosedChannelException when connection to HTTPS over HTTP proxy with
|
+ 475546 ClosedChannelException when connection to HTTPS over HTTP proxy with
|
||||||
CONNECT
|
CONNECT
|
||||||
|
|
||||||
|
@ -1662,11 +1663,8 @@ jetty-9.4.3.v20170317 - 17 March 2017
|
||||||
jetty-9.3.17.v20170317 - 17 March 2017
|
jetty-9.3.17.v20170317 - 17 March 2017
|
||||||
+ 329 Javadoc for HttpTester and ServletTester needs to reference limited HTTP
|
+ 329 Javadoc for HttpTester and ServletTester needs to reference limited HTTP
|
||||||
version scope
|
version scope
|
||||||
+ 609 websocket ClientCloseTest testServerNoCloseHandshake is failing
|
|
||||||
+ 1015 Ensure jetty-distribution excludes git / temp files
|
+ 1015 Ensure jetty-distribution excludes git / temp files
|
||||||
+ 1047 ReadPendingException and then thread death
|
+ 1047 ReadPendingException and then thread death
|
||||||
+ 1049 test-jetty-osgi test exits/crashes the surefire forked JVM
|
|
||||||
+ 1282 ByteArrayEndPointTest.testIdle() failure
|
|
||||||
+ 1296 Introduce HTTP parser "content complete" event
|
+ 1296 Introduce HTTP parser "content complete" event
|
||||||
+ 1326 Jetty shutdown command got NullPointerException (http2 module added to
|
+ 1326 Jetty shutdown command got NullPointerException (http2 module added to
|
||||||
start)
|
start)
|
||||||
|
@ -1686,7 +1684,6 @@ jetty-9.3.17.v20170317 - 17 March 2017
|
||||||
+ 1390 HashLoginService and "this.web-inf.url" property are incompatible
|
+ 1390 HashLoginService and "this.web-inf.url" property are incompatible
|
||||||
+ 1394 Default OS Locale/Encoding/Charset can cause test failures
|
+ 1394 Default OS Locale/Encoding/Charset can cause test failures
|
||||||
+ 1396 Set-Cookie produced by Jetty is invalid for RFC6265 and Chrome
|
+ 1396 Set-Cookie produced by Jetty is invalid for RFC6265 and Chrome
|
||||||
+ 1399 SlowClientTest is failing on CI
|
|
||||||
+ 1401 HttpOutput.recycle() does not clear the write listener
|
+ 1401 HttpOutput.recycle() does not clear the write listener
|
||||||
|
|
||||||
jetty-9.4.2.v20170220 - 20 February 2017
|
jetty-9.4.2.v20170220 - 20 February 2017
|
||||||
|
@ -1790,9 +1787,6 @@ jetty-9.3.16.v20170120 - 20 January 2017
|
||||||
+ 1229 ClassLoader constraint issue when using NativeWebSocketConfiguration
|
+ 1229 ClassLoader constraint issue when using NativeWebSocketConfiguration
|
||||||
with WEB-INF/lib/jetty-http.jar present
|
with WEB-INF/lib/jetty-http.jar present
|
||||||
+ 1234 onBadMessage called from with handled message
|
+ 1234 onBadMessage called from with handled message
|
||||||
+ 1259 HostnameVerificationTest.simpleGetWithHostnameVerificationEnabledTest
|
|
||||||
is broken
|
|
||||||
+ 1261 Intermittent H2C test failure AsyncIOServletTest.testAsyncReadEarlyEOF
|
|
||||||
+ 1262 BufferUtil.isMappedBuffer() uses reflection on private JDK fields
|
+ 1262 BufferUtil.isMappedBuffer() uses reflection on private JDK fields
|
||||||
+ 1265 JAXB not available in JDK 9
|
+ 1265 JAXB not available in JDK 9
|
||||||
+ 1267 Request.getRemoteUser can throw undeclared IllegalStateException via
|
+ 1267 Request.getRemoteUser can throw undeclared IllegalStateException via
|
||||||
|
@ -1806,7 +1800,6 @@ jetty-9.3.16.v20170120 - 20 January 2017
|
||||||
+ 1275 Get rid of Mockito
|
+ 1275 Get rid of Mockito
|
||||||
+ 1276 Remove org.eclipse.jetty.websocket.server.WebSocketServerFactory from
|
+ 1276 Remove org.eclipse.jetty.websocket.server.WebSocketServerFactory from
|
||||||
SPI
|
SPI
|
||||||
+ 1277 http2 alpn test error
|
|
||||||
|
|
||||||
jetty-9.2.21.v20170120 - 20 January 2017
|
jetty-9.2.21.v20170120 - 20 January 2017
|
||||||
+ 592 Support no-value Host header in HttpParser
|
+ 592 Support no-value Host header in HttpParser
|
||||||
|
@ -1842,7 +1835,6 @@ jetty-9.3.15.v20161220 - 20 December 2016
|
||||||
+ 1099 PushCacheFilter pushes POST requests
|
+ 1099 PushCacheFilter pushes POST requests
|
||||||
+ 1108 Please improve logging in SslContextFactory when there are no approved
|
+ 1108 Please improve logging in SslContextFactory when there are no approved
|
||||||
cipher suites
|
cipher suites
|
||||||
+ 1114 Add testcase for WSUF for stop/start of the Server
|
|
||||||
+ 1118 Filter.destroy() conflicts with ContainerLifeCycle.destroy() in
|
+ 1118 Filter.destroy() conflicts with ContainerLifeCycle.destroy() in
|
||||||
WebSocketUpgradeFilter
|
WebSocketUpgradeFilter
|
||||||
+ 1123 Broken lifecycle for WebSocket's mappings
|
+ 1123 Broken lifecycle for WebSocket's mappings
|
||||||
|
|
|
@ -47,8 +47,7 @@
|
||||||
<project.basedir>${project.basedir}</project.basedir>
|
<project.basedir>${project.basedir}</project.basedir>
|
||||||
<maven.local.repo>${settings.localRepository}</maven.local.repo>
|
<maven.local.repo>${settings.localRepository}</maven.local.repo>
|
||||||
<prog_guide>../programming-guide/index.html</prog_guide>
|
<prog_guide>../programming-guide/index.html</prog_guide>
|
||||||
<JDURL>http://www.eclipse.org/jetty/javadoc/jetty-10</JDURL>
|
<JDURL>https://www.eclipse.org/jetty/javadoc/jetty-10</JDURL>
|
||||||
<JXURL>http://download.eclipse.org/jetty/stable-9/xref</JXURL>
|
|
||||||
<SRCDIR>${basedir}/..</SRCDIR>
|
<SRCDIR>${basedir}/..</SRCDIR>
|
||||||
<GITBROWSEURL>https://github.com/eclipse/jetty.project/tree/master</GITBROWSEURL>
|
<GITBROWSEURL>https://github.com/eclipse/jetty.project/tree/master</GITBROWSEURL>
|
||||||
<GITDOCURL>https://github.com/eclipse/jetty.project/tree/jetty-10.0.x-doc-refactor/jetty-documentation/src/main/asciidoc</GITDOCURL>
|
<GITDOCURL>https://github.com/eclipse/jetty.project/tree/jetty-10.0.x-doc-refactor/jetty-documentation/src/main/asciidoc</GITDOCURL>
|
||||||
|
|
|
@ -11,9 +11,8 @@
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
//
|
//
|
||||||
|
|
||||||
:doctitle: Eclipse Jetty: Operations Guide
|
:doctitle: link:https://eclipse.org/jetty[Eclipse Jetty]: Operations Guide
|
||||||
:toc-title: Operations Guide
|
:toc-title: Operations Guide
|
||||||
:breadcrumb: Home:../index.html | Operations Guide:./index.html
|
|
||||||
:idprefix: og-
|
:idprefix: og-
|
||||||
:docinfo: private-head
|
:docinfo: private-head
|
||||||
|
|
||||||
|
|
|
@ -22,24 +22,26 @@ The following command creates a KeyStore file containing a private key and a sel
|
||||||
----
|
----
|
||||||
keytool
|
keytool
|
||||||
-genkeypair <1>
|
-genkeypair <1>
|
||||||
-validity 90 <2>
|
-alias mykey <2>
|
||||||
-keyalg RSA <3>
|
-validity 90 <3>
|
||||||
-keysize 2048 <4>
|
-keyalg RSA <4>
|
||||||
-keystore /path/to/keystore.p12 <5>
|
-keysize 2048 <5>
|
||||||
-storetype pkcs12 <6>
|
-keystore /path/to/keystore.p12 <6>
|
||||||
-dname "CN=domain.com, OU=Unit, O=Company, L=City, S=State, C=Country" <7>
|
-storetype pkcs12 <7>
|
||||||
-ext san=dns:www.domain.com,dns:domain.org <8>
|
-dname "CN=domain.com, OU=Unit, O=Company, L=City, S=State, C=Country" <8>
|
||||||
-v <9>
|
-ext san=dns:www.domain.com,dns:domain.org <9>
|
||||||
|
-v <10>
|
||||||
----
|
----
|
||||||
<1> the command to generate a key and certificate pair
|
<1> the command to generate a key and certificate pair
|
||||||
<2> specifies the number of days after which the certificate expires
|
<2> the alias name of the key and certificate pair
|
||||||
<3> the algorithm _must_ be RSA (the DSA algorithm does not work for web sites)
|
<3> specifies the number of days after which the certificate expires
|
||||||
<4> indicates the strength of the key
|
<4> the algorithm _must_ be RSA (the DSA algorithm does not work for web sites)
|
||||||
<5> the keyStore file
|
<5> indicates the strength of the key
|
||||||
<6> the keyStore type, stick with the standard PKCS12
|
<6> the KeyStore file
|
||||||
<7> the distinguished name (more below) -- customize it with your values for CN, OU, O, L, S and C
|
<7> the KeyStore type, stick with the standard PKCS12
|
||||||
<8> the extension with the subject alternative names (more below)
|
<8> the distinguished name (more below) -- customize it with your values for CN, OU, O, L, S and C
|
||||||
<9> verbose output
|
<9> the extension with the subject alternative names (more below)
|
||||||
|
<10> verbose output
|
||||||
|
|
||||||
The command prompts for the KeyStore password that you must choose to protect the access to the KeyStore.
|
The command prompts for the KeyStore password that you must choose to protect the access to the KeyStore.
|
||||||
|
|
||||||
|
@ -56,3 +58,13 @@ In the example above, `san=dns:www.domain.com,dns:domain.org` specifies `www.dom
|
||||||
In rare cases, you may want to specify IP addresses, rather than domains, in the SAN extension.
|
In rare cases, you may want to specify IP addresses, rather than domains, in the SAN extension.
|
||||||
The syntax in such case is `san=ip:127.0.0.1,ip:[::1]`, which specifies as subject alternative names IPv4 `127.0.0.1` and IPv6 `[::1]`.
|
The syntax in such case is `san=ip:127.0.0.1,ip:[::1]`, which specifies as subject alternative names IPv4 `127.0.0.1` and IPv6 `[::1]`.
|
||||||
====
|
====
|
||||||
|
|
||||||
|
[[og-keystore-create-many]]
|
||||||
|
===== KeyStores with Multiple Entries
|
||||||
|
|
||||||
|
A single KeyStore may contain multiple key/certificate pairs.
|
||||||
|
This is useful when you need to support multiple domains on the same Jetty server (typically accomplished using xref:og-deploy-virtual-hosts[virtual hosts]).
|
||||||
|
|
||||||
|
You can create multiple key/certificate pairs as detailed in the xref:og-keystore-create[previous section], provided that you assign each one to a different alias.
|
||||||
|
|
||||||
|
Compliant TLS clients will send the xref:og-protocols-ssl-sni[TLS SNI extension] when creating new connections, and Jetty will automatically choose the right certificate by matching the SNI name sent by the client with the CN or SAN of certificates present in the KeyStore.
|
||||||
|
|
|
@ -34,8 +34,11 @@ $ java -jar $JETTY_HOME/start.jar --list-modules=connector
|
||||||
include::protocols-http.adoc[]
|
include::protocols-http.adoc[]
|
||||||
include::protocols-https.adoc[]
|
include::protocols-https.adoc[]
|
||||||
include::protocols-http2.adoc[]
|
include::protocols-http2.adoc[]
|
||||||
|
include::protocols-http2s.adoc[]
|
||||||
|
include::protocols-http2c.adoc[]
|
||||||
include::protocols-ssl.adoc[]
|
include::protocols-ssl.adoc[]
|
||||||
include::protocols-proxy.adoc[]
|
include::protocols-proxy.adoc[]
|
||||||
|
include::protocols-websocket.adoc[]
|
||||||
|
|
||||||
// TODO: old_docs/connectors/*.adoc
|
// TODO: old_docs/connectors/*.adoc
|
||||||
|
|
||||||
|
|
|
@ -143,6 +143,3 @@ microservice3 <--> jetty : HTTP/2
|
||||||
microservice2 <--> microservice3 : HTTP/2
|
microservice2 <--> microservice3 : HTTP/2
|
||||||
microservice1 <--> microservice3 : HTTP/2
|
microservice1 <--> microservice3 : HTTP/2
|
||||||
----
|
----
|
||||||
|
|
||||||
include::protocols-http2s.adoc[]
|
|
||||||
include::protocols-http2c.adoc[]
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ Allows a single stream of an HTTP/2 connection to be upgraded to WebSocket.
|
||||||
This allows one TCP connection to be shared by both protocols and extends HTTP/2's more efficient use of the network to WebSockets.
|
This allows one TCP connection to be shared by both protocols and extends HTTP/2's more efficient use of the network to WebSockets.
|
||||||
|
|
||||||
[[og-protocols-websocket-configure]]
|
[[og-protocols-websocket-configure]]
|
||||||
==== Configuring WebSocket
|
===== Configuring WebSocket
|
||||||
|
|
||||||
Jetty provides two WebSocket implementations: one based on the Java WebSocket APIs defined by JSR 356, provided by module `websocket-javax`, and one based on Jetty specific WebSocket APIs, provided by module `websocket-jetty`.
|
Jetty provides two WebSocket implementations: one based on the Java WebSocket APIs defined by JSR 356, provided by module `websocket-javax`, and one based on Jetty specific WebSocket APIs, provided by module `websocket-jetty`.
|
||||||
The Jetty `websocket` module enables both implementations, but each implementation can be enabled independently.
|
The Jetty `websocket` module enables both implementations, but each implementation can be enabled independently.
|
||||||
|
@ -69,7 +69,7 @@ $ java -jar $JETTY_HOME/start.jar --add-modules=http,https,http2c,http2,websocke
|
||||||
----
|
----
|
||||||
|
|
||||||
[[og-protocols-websocket-disable]]
|
[[og-protocols-websocket-disable]]
|
||||||
==== Selectively Disabling WebSocket
|
===== Selectively Disabling WebSocket
|
||||||
|
|
||||||
Enabling the WebSocket Jetty modules comes with a startup cost because Jetty must perform two steps:
|
Enabling the WebSocket Jetty modules comes with a startup cost because Jetty must perform two steps:
|
||||||
|
|
||||||
|
@ -104,7 +104,7 @@ For a specific web application, you can disable step 2 for Java WebSocket suppor
|
||||||
Furthermore, for a specific web application, you can disable step 1 (and therefore also step 2) as described in the xref:og-annotations[annotations processing section].
|
Furthermore, for a specific web application, you can disable step 1 (and therefore also step 2) as described in the xref:og-annotations[annotations processing section].
|
||||||
|
|
||||||
[[og-protocols-websocket-webapp-client]]
|
[[og-protocols-websocket-webapp-client]]
|
||||||
==== Using WebSocket Client in WebApps
|
===== Using WebSocket Client in WebApps
|
||||||
|
|
||||||
Web applications may need to use a WebSocket client to communicate with third party WebSocket services.
|
Web applications may need to use a WebSocket client to communicate with third party WebSocket services.
|
||||||
|
|
||||||
|
|
|
@ -84,6 +84,17 @@ include::../../{doc_code}/org/eclipse/jetty/docs/programming/client/http/HTTPCli
|
||||||
|
|
||||||
Stopping `HttpClient` makes sure that the memory it holds (for example, authentication credentials, cookies, etc.) is released, and that the thread pool and scheduler are properly stopped allowing all threads used by `HttpClient` to exit.
|
Stopping `HttpClient` makes sure that the memory it holds (for example, authentication credentials, cookies, etc.) is released, and that the thread pool and scheduler are properly stopped allowing all threads used by `HttpClient` to exit.
|
||||||
|
|
||||||
|
[NOTE]
|
||||||
|
====
|
||||||
|
You cannot call `HttpClient.stop()` from one of its own threads, as it would cause a deadlock.
|
||||||
|
It is recommended that you stop `HttpClient` from an unrelated thread, or from a newly allocated thread, for example:
|
||||||
|
|
||||||
|
[source,java,indent=0]
|
||||||
|
----
|
||||||
|
include::../../{doc_code}/org/eclipse/jetty/docs/programming/client/http/HTTPClientDocs.java[tags=stopFromOtherThread]
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
[[pg-client-http-arch]]
|
[[pg-client-http-arch]]
|
||||||
==== HttpClient Architecture
|
==== HttpClient Architecture
|
||||||
|
|
||||||
|
|
|
@ -11,9 +11,8 @@
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
//
|
//
|
||||||
|
|
||||||
:doctitle: Eclipse Jetty: Programming Guide
|
:doctitle: link:https://eclipse.org/jetty[Eclipse Jetty]: Programming Guide
|
||||||
:toc-title: Jetty Programming Guide
|
:toc-title: Jetty Programming Guide
|
||||||
:breadcrumb: Home:../index.html | Programming Guide:./index.html
|
|
||||||
:idprefix: pg-
|
:idprefix: pg-
|
||||||
:docinfo: private-head
|
:docinfo: private-head
|
||||||
|
|
||||||
|
@ -25,3 +24,4 @@ include::server/server.adoc[]
|
||||||
include::maven/maven.adoc[]
|
include::maven/maven.adoc[]
|
||||||
include::arch.adoc[]
|
include::arch.adoc[]
|
||||||
include::troubleshooting.adoc[]
|
include::troubleshooting.adoc[]
|
||||||
|
include::migration/migration.adoc[]
|
||||||
|
|
|
@ -90,6 +90,16 @@ keepSources::
|
||||||
Default value: false
|
Default value: false
|
||||||
+
|
+
|
||||||
If true, the generated .java files are not deleted at the end of processing.
|
If true, the generated .java files are not deleted at the end of processing.
|
||||||
|
scanAllDirectories::
|
||||||
|
Default value: true
|
||||||
|
+
|
||||||
|
Determines if dirs on the classpath should be scanned as well as jars.
|
||||||
|
If true, this allows scanning for tlds of dependent projects that
|
||||||
|
are in the reactor as unassembled jars.
|
||||||
|
scanManifest::
|
||||||
|
Default value: true
|
||||||
|
+
|
||||||
|
Determines if the manifest of JAR files found on the classpath should be scanned.
|
||||||
sourceVersion::
|
sourceVersion::
|
||||||
Introduced in Jetty 9.3.6.
|
Introduced in Jetty 9.3.6.
|
||||||
Java version of jsp source files.
|
Java version of jsp source files.
|
||||||
|
|
|
@ -0,0 +1,135 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// 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
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
[appendix]
|
||||||
|
[[pg-migration]]
|
||||||
|
== Migration Guides
|
||||||
|
|
||||||
|
[[pg-migration-94-to-10]]
|
||||||
|
=== Migrating from Jetty 9.4.x to Jetty 10.0.x
|
||||||
|
|
||||||
|
[[pg-migration-94-to-10-java-version]]
|
||||||
|
==== Required Java Version Changes
|
||||||
|
|
||||||
|
[cols="1,1", options="header"]
|
||||||
|
|===
|
||||||
|
| Jetty 9.4.x | Jetty 10.0.x
|
||||||
|
| Java 8 | Java 11
|
||||||
|
|===
|
||||||
|
|
||||||
|
[[pg-migration-94-to-10-websocket]]
|
||||||
|
==== WebSocket Migration Guide
|
||||||
|
|
||||||
|
Migrating from Jetty 9.4.x to Jetty 10.0.x requires changes in the coordinates of the Maven artifact dependencies for WebSocket. Some of these classes have also changed name and package. This is not a comprehensive list of changes but should cover the most common changes encountered during migration.
|
||||||
|
|
||||||
|
[[pg-migration-94-to-10-websocket-maven-artifact-changes]]
|
||||||
|
===== Maven Artifacts Changes
|
||||||
|
|
||||||
|
[cols="1a,1a", options="header"]
|
||||||
|
|===
|
||||||
|
| Jetty 9.4.x | Jetty 10.0.x
|
||||||
|
|
||||||
|
| `org.eclipse.jetty.websocket:**websocket-api**`
|
||||||
|
| `org.eclipse.jetty.websocket:**websocket-jetty-api**`
|
||||||
|
|
||||||
|
| `org.eclipse.jetty.websocket:**websocket-server**`
|
||||||
|
| `org.eclipse.jetty.websocket:**websocket-jetty-server**`
|
||||||
|
|
||||||
|
| `org.eclipse.jetty.websocket:**websocket-client**`
|
||||||
|
| `org.eclipse.jetty.websocket:**websocket-jetty-client**`
|
||||||
|
|
||||||
|
| `org.eclipse.jetty.websocket:**javax-websocket-server-impl**`
|
||||||
|
| `org.eclipse.jetty.websocket:**websocket-javax-server**`
|
||||||
|
|
||||||
|
| `org.eclipse.jetty.websocket:**javax-websocket-client-impl**`
|
||||||
|
| `org.eclipse.jetty.websocket:**websocket-javax-client**`
|
||||||
|
|
||||||
|
|===
|
||||||
|
|
||||||
|
[[pg-migration-94-to-10-websocket-class-name-changes]]
|
||||||
|
===== Class Names Changes
|
||||||
|
|
||||||
|
[cols="1a,1a", options="header"]
|
||||||
|
|===
|
||||||
|
| Jetty 9.4.x | Jetty 10.0.x
|
||||||
|
|
||||||
|
| `org.eclipse.jetty.websocket.**server.NativeWebSocketServletContainerInitializer**`
|
||||||
|
| `org.eclipse.jetty.websocket.**server.config.JettyWebSocketServletContainerInitializer**`
|
||||||
|
|
||||||
|
| `org.eclipse.jetty.websocket.**jsr356.server.deploy.WebSocketServerContainerInitializer**`
|
||||||
|
| `org.eclipse.jetty.websocket.**javax.server.config.JavaxWebSocketServletContainerInitializer**`
|
||||||
|
|
||||||
|
| `org.eclipse.jetty.websocket.**servlet.WebSocketCreator**`
|
||||||
|
| `org.eclipse.jetty.websocket.**server.JettyWebSocketCreator**`
|
||||||
|
|
||||||
|
| `org.eclipse.jetty.websocket.**servlet.ServletUpgradeRequest**`
|
||||||
|
| `org.eclipse.jetty.websocket.**server.JettyServerUpgradeRequest**`
|
||||||
|
|
||||||
|
| `org.eclipse.jetty.websocket.**servlet.ServletUpgradeResponse**`
|
||||||
|
| `org.eclipse.jetty.websocket.**server.JettyServerUpgradeResponse**`
|
||||||
|
|
||||||
|
| `org.eclipse.jetty.websocket.**servlet.WebSocketServlet**`
|
||||||
|
| `org.eclipse.jetty.websocket.**server.JettyWebSocketServlet**`
|
||||||
|
|
||||||
|
| `org.eclipse.jetty.websocket.**servlet.WebSocketServletFactory**`
|
||||||
|
| `org.eclipse.jetty.websocket.**server.JettyWebSocketServletFactory**`
|
||||||
|
|===
|
||||||
|
|
||||||
|
[[pg-migration-94-to-10-websocket-example-code]]
|
||||||
|
===== Example Code
|
||||||
|
|
||||||
|
[cols="1a,1a", options="header"]
|
||||||
|
|===
|
||||||
|
| Jetty 9.4.x
|
||||||
|
| Jetty 10.0.x
|
||||||
|
|
||||||
|
|
|
||||||
|
[source,java]
|
||||||
|
----
|
||||||
|
public class ExampleWebSocketServlet extends WebSocketServlet
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void configure(WebSocketServletFactory factory)
|
||||||
|
{
|
||||||
|
factory.setCreator(new WebSocketCreator()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp)
|
||||||
|
{
|
||||||
|
return new ExampleEndpoint();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
|
|
||||||
|
[source,java]
|
||||||
|
----
|
||||||
|
public class ExampleWebSocketServlet extends JettyWebSocketServlet
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void configure(JettyWebSocketServletFactory factory)
|
||||||
|
{
|
||||||
|
factory.setCreator(new JettyWebSocketCreator()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public Object createWebSocket(JettyServerUpgradeRequest req, JettyServerUpgradeResponse resp)
|
||||||
|
{
|
||||||
|
return new ExampleEndpoint();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|===
|
|
@ -66,6 +66,7 @@ import org.eclipse.jetty.io.ClientConnectionFactory;
|
||||||
import org.eclipse.jetty.io.ClientConnector;
|
import org.eclipse.jetty.io.ClientConnector;
|
||||||
import org.eclipse.jetty.util.Callback;
|
import org.eclipse.jetty.util.Callback;
|
||||||
import org.eclipse.jetty.util.HttpCookieStore;
|
import org.eclipse.jetty.util.HttpCookieStore;
|
||||||
|
import org.eclipse.jetty.util.component.LifeCycle;
|
||||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||||
|
|
||||||
import static java.lang.System.Logger.Level.INFO;
|
import static java.lang.System.Logger.Level.INFO;
|
||||||
|
@ -97,6 +98,17 @@ public class HTTPClientDocs
|
||||||
// end::stop[]
|
// end::stop[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void stopFromOtherThread() throws Exception
|
||||||
|
{
|
||||||
|
HttpClient httpClient = new HttpClient();
|
||||||
|
httpClient.start();
|
||||||
|
// tag::stopFromOtherThread[]
|
||||||
|
// Stop HttpClient from a new thread.
|
||||||
|
// Use LifeCycle.stop(...) to rethrow checked exceptions as unchecked.
|
||||||
|
new Thread(() -> LifeCycle.stop(httpClient)).start();
|
||||||
|
// end::stopFromOtherThread[]
|
||||||
|
}
|
||||||
|
|
||||||
public void tlsExplicit() throws Exception
|
public void tlsExplicit() throws Exception
|
||||||
{
|
{
|
||||||
// tag::tlsExplicit[]
|
// tag::tlsExplicit[]
|
||||||
|
|
|
@ -105,6 +105,11 @@
|
||||||
<artifactId>jetty-client</artifactId>
|
<artifactId>jetty-client</artifactId>
|
||||||
<version>10.0.3-SNAPSHOT</version>
|
<version>10.0.3-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-cdi</artifactId>
|
||||||
|
<version>10.0.3-SNAPSHOT</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>jetty-deploy</artifactId>
|
<artifactId>jetty-deploy</artifactId>
|
||||||
|
|
|
@ -16,6 +16,7 @@ package org.eclipse.jetty.client;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import org.eclipse.jetty.client.api.Connection;
|
import org.eclipse.jetty.client.api.Connection;
|
||||||
import org.eclipse.jetty.io.ClientConnector;
|
import org.eclipse.jetty.io.ClientConnector;
|
||||||
|
@ -30,7 +31,7 @@ public abstract class AbstractConnectorHttpClientTransport extends AbstractHttpC
|
||||||
|
|
||||||
protected AbstractConnectorHttpClientTransport(ClientConnector connector)
|
protected AbstractConnectorHttpClientTransport(ClientConnector connector)
|
||||||
{
|
{
|
||||||
this.connector = connector;
|
this.connector = Objects.requireNonNull(connector);
|
||||||
addBean(connector);
|
addBean(connector);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
@ -86,7 +87,12 @@ public class HttpClientTransportDynamic extends AbstractConnectorHttpClientTrans
|
||||||
*/
|
*/
|
||||||
public HttpClientTransportDynamic()
|
public HttpClientTransportDynamic()
|
||||||
{
|
{
|
||||||
this(new ClientConnector(), HttpClientConnectionFactory.HTTP11);
|
this(HttpClientConnectionFactory.HTTP11);
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpClientTransportDynamic(ClientConnectionFactory.Info... factoryInfos)
|
||||||
|
{
|
||||||
|
this(findClientConnector(factoryInfos), factoryInfos);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -98,7 +104,6 @@ public class HttpClientTransportDynamic extends AbstractConnectorHttpClientTrans
|
||||||
public HttpClientTransportDynamic(ClientConnector connector, ClientConnectionFactory.Info... factoryInfos)
|
public HttpClientTransportDynamic(ClientConnector connector, ClientConnectionFactory.Info... factoryInfos)
|
||||||
{
|
{
|
||||||
super(connector);
|
super(connector);
|
||||||
addBean(connector);
|
|
||||||
if (factoryInfos.length == 0)
|
if (factoryInfos.length == 0)
|
||||||
factoryInfos = new Info[]{HttpClientConnectionFactory.HTTP11};
|
factoryInfos = new Info[]{HttpClientConnectionFactory.HTTP11};
|
||||||
this.factoryInfos = Arrays.asList(factoryInfos);
|
this.factoryInfos = Arrays.asList(factoryInfos);
|
||||||
|
@ -112,6 +117,15 @@ public class HttpClientTransportDynamic extends AbstractConnectorHttpClientTrans
|
||||||
new MultiplexConnectionPool(destination, destination.getHttpClient().getMaxConnectionsPerDestination(), destination, 1));
|
new MultiplexConnectionPool(destination, destination.getHttpClient().getMaxConnectionsPerDestination(), destination, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static ClientConnector findClientConnector(ClientConnectionFactory.Info[] infos)
|
||||||
|
{
|
||||||
|
return Arrays.stream(infos)
|
||||||
|
.map(info -> info.getBean(ClientConnector.class))
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.findFirst()
|
||||||
|
.orElse(new ClientConnector());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Origin newOrigin(HttpRequest request)
|
public Origin newOrigin(HttpRequest request)
|
||||||
{
|
{
|
||||||
|
|
|
@ -314,8 +314,9 @@ public class HttpStatus
|
||||||
switch (status)
|
switch (status)
|
||||||
{
|
{
|
||||||
case NO_CONTENT_204:
|
case NO_CONTENT_204:
|
||||||
case NOT_MODIFIED_304:
|
case RESET_CONTENT_205:
|
||||||
case PARTIAL_CONTENT_206:
|
case PARTIAL_CONTENT_206:
|
||||||
|
case NOT_MODIFIED_304:
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -25,7 +25,9 @@ import org.slf4j.LoggerFactory;
|
||||||
import static java.util.Arrays.asList;
|
import static java.util.Arrays.asList;
|
||||||
import static java.util.Collections.unmodifiableSet;
|
import static java.util.Collections.unmodifiableSet;
|
||||||
import static java.util.EnumSet.allOf;
|
import static java.util.EnumSet.allOf;
|
||||||
|
import static java.util.EnumSet.complementOf;
|
||||||
import static java.util.EnumSet.noneOf;
|
import static java.util.EnumSet.noneOf;
|
||||||
|
import static java.util.EnumSet.of;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* URI compliance modes for Jetty request handling.
|
* URI compliance modes for Jetty request handling.
|
||||||
|
@ -37,23 +39,29 @@ public final class UriCompliance implements ComplianceViolation.Mode
|
||||||
protected static final Logger LOG = LoggerFactory.getLogger(UriCompliance.class);
|
protected static final Logger LOG = LoggerFactory.getLogger(UriCompliance.class);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* These are URI compliance violations, which may be allowed by the compliance mode. Currently all these
|
* These are URI compliance "violations", which may be allowed by the compliance mode. These are actual
|
||||||
* violations are for additional criteria in excess of the strict requirements of rfc3986.
|
* violations of the RFC, as they represent additional requirements in excess of the strict compliance of rfc3986.
|
||||||
|
* A compliance mode that contains one or more of these Violations, allows request to violate the corresponding
|
||||||
|
* additional requirement.
|
||||||
*/
|
*/
|
||||||
public enum Violation implements ComplianceViolation
|
public enum Violation implements ComplianceViolation
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Ambiguous path segments e.g. <code>/foo/%2e%2e/bar</code>
|
* Allow ambiguous path segments e.g. <code>/foo/%2e%2e/bar</code>
|
||||||
*/
|
*/
|
||||||
AMBIGUOUS_PATH_SEGMENT("https://tools.ietf.org/html/rfc3986#section-3.3", "Ambiguous URI path segment"),
|
AMBIGUOUS_PATH_SEGMENT("https://tools.ietf.org/html/rfc3986#section-3.3", "Ambiguous URI path segment"),
|
||||||
/**
|
/**
|
||||||
* Ambiguous path separator within a URI segment e.g. <code>/foo/b%2fr</code>
|
* Allow ambiguous path separator within a URI segment e.g. <code>/foo/b%2fr</code>
|
||||||
*/
|
*/
|
||||||
AMBIGUOUS_PATH_SEPARATOR("https://tools.ietf.org/html/rfc3986#section-3.3", "Ambiguous URI path separator"),
|
AMBIGUOUS_PATH_SEPARATOR("https://tools.ietf.org/html/rfc3986#section-3.3", "Ambiguous URI path separator"),
|
||||||
/**
|
/**
|
||||||
* Ambiguous path parameters within a URI segment e.g. <code>/foo/..;/bar</code>
|
* Allow ambiguous path parameters within a URI segment e.g. <code>/foo/..;/bar</code>
|
||||||
*/
|
*/
|
||||||
AMBIGUOUS_PATH_PARAMETER("https://tools.ietf.org/html/rfc3986#section-3.3", "Ambiguous URI path parameter");
|
AMBIGUOUS_PATH_PARAMETER("https://tools.ietf.org/html/rfc3986#section-3.3", "Ambiguous URI path parameter"),
|
||||||
|
/**
|
||||||
|
* Allow Non canonical ambiguous paths. eg <code>/foo/x%2f%2e%2e%/bar</code> provided to applications as <code>/foo/x/../bar</code>
|
||||||
|
*/
|
||||||
|
NON_CANONICAL_AMBIGUOUS_PATHS("https://tools.ietf.org/html/rfc3986#section-3.3", "Non canonical ambiguous paths");
|
||||||
|
|
||||||
private final String _url;
|
private final String _url;
|
||||||
private final String _description;
|
private final String _description;
|
||||||
|
@ -84,19 +92,28 @@ public final class UriCompliance implements ComplianceViolation.Mode
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default compliance mode that extends RFC3986 compliance with additional violations to avoid ambiguous URIs
|
* The default compliance mode that extends RFC3986 compliance with additional violations to avoid most ambiguous URIs.
|
||||||
|
* This mode does allow {@link Violation#AMBIGUOUS_PATH_SEPARATOR}, but disallows
|
||||||
|
* {@link Violation#AMBIGUOUS_PATH_PARAMETER} and {@link Violation#AMBIGUOUS_PATH_SEGMENT}.
|
||||||
|
* Ambiguous paths are not allowed by {@link Violation#NON_CANONICAL_AMBIGUOUS_PATHS}.
|
||||||
*/
|
*/
|
||||||
public static final UriCompliance DEFAULT = new UriCompliance("DEFAULT", noneOf(Violation.class));
|
public static final UriCompliance DEFAULT = new UriCompliance("DEFAULT", of(Violation.AMBIGUOUS_PATH_SEPARATOR));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* LEGACY compliance mode that disallows only ambiguous path parameters as per Jetty-9.4
|
* LEGACY compliance mode that models Jetty-9.4 behavior by allowing {@link Violation#AMBIGUOUS_PATH_SEGMENT} and {@link Violation#AMBIGUOUS_PATH_SEPARATOR}
|
||||||
*/
|
*/
|
||||||
public static final UriCompliance LEGACY = new UriCompliance("LEGACY", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT, Violation.AMBIGUOUS_PATH_SEPARATOR));
|
public static final UriCompliance LEGACY = new UriCompliance("LEGACY", of(Violation.AMBIGUOUS_PATH_SEGMENT, Violation.AMBIGUOUS_PATH_SEPARATOR));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compliance mode that exactly follows RFC3986, including allowing all additional ambiguous URI Violations
|
* Compliance mode that exactly follows RFC3986, including allowing all additional ambiguous URI Violations,
|
||||||
|
* except {@link Violation#NON_CANONICAL_AMBIGUOUS_PATHS}, thus ambiguous paths are canonicalized for safety.
|
||||||
*/
|
*/
|
||||||
public static final UriCompliance RFC3986 = new UriCompliance("RFC3986", allOf(Violation.class));
|
public static final UriCompliance RFC3986 = new UriCompliance("RFC3986", complementOf(of(Violation.NON_CANONICAL_AMBIGUOUS_PATHS)));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compliance mode that allows all URI Violations, including allowing ambiguous paths in non canonicalized form.
|
||||||
|
*/
|
||||||
|
public static final UriCompliance UNSAFE = new UriCompliance("UNSAFE", allOf(Violation.class));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated equivalent to DEFAULT
|
* @deprecated equivalent to DEFAULT
|
||||||
|
@ -125,6 +142,17 @@ public final class UriCompliance implements ComplianceViolation.Mode
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create compliance set from a set of allowed Violations.
|
||||||
|
*
|
||||||
|
* @param violations A string of violations to allow:
|
||||||
|
* @return the compliance from the string spec
|
||||||
|
*/
|
||||||
|
public static UriCompliance from(Set<Violation> violations)
|
||||||
|
{
|
||||||
|
return new UriCompliance("CUSTOM" + __custom.getAndIncrement(), violations);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create compliance set from string.
|
* Create compliance set from string.
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -151,22 +179,23 @@ public final class UriCompliance implements ComplianceViolation.Mode
|
||||||
*/
|
*/
|
||||||
public static UriCompliance from(String spec)
|
public static UriCompliance from(String spec)
|
||||||
{
|
{
|
||||||
Set<Violation> sections;
|
Set<Violation> violations;
|
||||||
String[] elements = spec.split("\\s*,\\s*");
|
String[] elements = spec.split("\\s*,\\s*");
|
||||||
switch (elements[0])
|
switch (elements[0])
|
||||||
{
|
{
|
||||||
case "0":
|
case "0":
|
||||||
sections = noneOf(Violation.class);
|
violations = noneOf(Violation.class);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "*":
|
case "*":
|
||||||
sections = allOf(Violation.class);
|
violations = allOf(Violation.class);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
UriCompliance mode = UriCompliance.valueOf(elements[0]);
|
UriCompliance mode = UriCompliance.valueOf(elements[0]);
|
||||||
sections = (mode == null) ? noneOf(Violation.class) : copyOf(mode.getAllowed());
|
violations = (mode == null) ? noneOf(Violation.class) : copyOf(mode.getAllowed());
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,12 +207,12 @@ public final class UriCompliance implements ComplianceViolation.Mode
|
||||||
element = element.substring(1);
|
element = element.substring(1);
|
||||||
Violation section = Violation.valueOf(element);
|
Violation section = Violation.valueOf(element);
|
||||||
if (exclude)
|
if (exclude)
|
||||||
sections.remove(section);
|
violations.remove(section);
|
||||||
else
|
else
|
||||||
sections.add(section);
|
violations.add(section);
|
||||||
}
|
}
|
||||||
|
|
||||||
UriCompliance compliance = new UriCompliance("CUSTOM" + __custom.getAndIncrement(), sections);
|
UriCompliance compliance = new UriCompliance("CUSTOM" + __custom.getAndIncrement(), violations);
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("UriCompliance from {}->{}", spec, compliance);
|
LOG.debug("UriCompliance from {}->{}", spec, compliance);
|
||||||
return compliance;
|
return compliance;
|
||||||
|
@ -192,7 +221,7 @@ public final class UriCompliance implements ComplianceViolation.Mode
|
||||||
private final String _name;
|
private final String _name;
|
||||||
private final Set<Violation> _allowed;
|
private final Set<Violation> _allowed;
|
||||||
|
|
||||||
private UriCompliance(String name, Set<Violation> violations)
|
public UriCompliance(String name, Set<Violation> violations)
|
||||||
{
|
{
|
||||||
Objects.requireNonNull(violations);
|
Objects.requireNonNull(violations);
|
||||||
_name = name;
|
_name = name;
|
||||||
|
|
|
@ -18,6 +18,7 @@ import org.junit.jupiter.api.Test;
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.hamcrest.Matchers.allOf;
|
import static org.hamcrest.Matchers.allOf;
|
||||||
import static org.hamcrest.Matchers.containsString;
|
import static org.hamcrest.Matchers.containsString;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
import static org.junit.jupiter.api.Assertions.fail;
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
|
||||||
public class SyntaxTest
|
public class SyntaxTest
|
||||||
|
@ -61,19 +62,13 @@ public class SyntaxTest
|
||||||
|
|
||||||
for (String token : tokens)
|
for (String token : tokens)
|
||||||
{
|
{
|
||||||
try
|
Throwable e = assertThrows(IllegalArgumentException.class,
|
||||||
{
|
() -> Syntax.requireValidRFC2616Token(token, "Test Based"));
|
||||||
Syntax.requireValidRFC2616Token(token, "Test Based");
|
|
||||||
fail("RFC2616 Token [" + token + "] Should have thrown " + IllegalArgumentException.class.getName());
|
|
||||||
}
|
|
||||||
catch (IllegalArgumentException e)
|
|
||||||
{
|
|
||||||
assertThat("Testing Bad RFC2616 Token [" + token + "]", e.getMessage(),
|
assertThat("Testing Bad RFC2616 Token [" + token + "]", e.getMessage(),
|
||||||
allOf(containsString("Test Based"),
|
allOf(containsString("Test Based"),
|
||||||
containsString("RFC2616")));
|
containsString("RFC2616")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRequireValidRFC6265CookieValueGood()
|
public void testRequireValidRFC6265CookieValueGood()
|
||||||
|
|
|
@ -44,7 +44,6 @@ import org.eclipse.jetty.util.IO;
|
||||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||||
import org.eclipse.jetty.util.thread.Scheduler;
|
import org.eclipse.jetty.util.thread.Scheduler;
|
||||||
import org.hamcrest.Matchers;
|
import org.hamcrest.Matchers;
|
||||||
import org.junit.jupiter.api.Disabled;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -52,7 +51,6 @@ import org.slf4j.LoggerFactory;
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
@Disabled
|
|
||||||
public class SmallThreadPoolLoadTest extends AbstractTest
|
public class SmallThreadPoolLoadTest extends AbstractTest
|
||||||
{
|
{
|
||||||
private final Logger logger = LoggerFactory.getLogger(SmallThreadPoolLoadTest.class);
|
private final Logger logger = LoggerFactory.getLogger(SmallThreadPoolLoadTest.class);
|
||||||
|
@ -83,8 +81,8 @@ public class SmallThreadPoolLoadTest extends AbstractTest
|
||||||
boolean result = IntStream.range(0, 16).parallel()
|
boolean result = IntStream.range(0, 16).parallel()
|
||||||
.mapToObj(i -> IntStream.range(0, runs)
|
.mapToObj(i -> IntStream.range(0, runs)
|
||||||
.mapToObj(j -> run(session, iterations))
|
.mapToObj(j -> run(session, iterations))
|
||||||
.reduce(true, (acc, res) -> acc && res))
|
.reduce(true, Boolean::logicalAnd))
|
||||||
.reduce(true, (acc, res) -> acc && res);
|
.reduce(true, Boolean::logicalAnd);
|
||||||
|
|
||||||
assertTrue(result);
|
assertTrue(result);
|
||||||
}
|
}
|
||||||
|
@ -94,10 +92,10 @@ public class SmallThreadPoolLoadTest extends AbstractTest
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
CountDownLatch latch = new CountDownLatch(iterations);
|
CountDownLatch latch = new CountDownLatch(iterations);
|
||||||
int factor = (logger.isDebugEnabled() ? 25 : 1) * 100;
|
long factor = (logger.isDebugEnabled() ? 25 : 1) * 100;
|
||||||
|
|
||||||
// Dumps the state of the client if the test takes too long.
|
// Dumps the state of the client if the test takes too long.
|
||||||
final Thread testThread = Thread.currentThread();
|
Thread testThread = Thread.currentThread();
|
||||||
Scheduler.Task task = client.getScheduler().schedule(() ->
|
Scheduler.Task task = client.getScheduler().schedule(() ->
|
||||||
{
|
{
|
||||||
logger.warn("Interrupting test, it is taking too long{}Server:{}{}{}Client:{}{}",
|
logger.warn("Interrupting test, it is taking too long{}Server:{}{}{}Client:{}{}",
|
||||||
|
@ -123,7 +121,7 @@ public class SmallThreadPoolLoadTest extends AbstractTest
|
||||||
logger.info("{} requests in {} ms, {}/{} success/failure, {} req/s",
|
logger.info("{} requests in {} ms, {}/{} success/failure, {} req/s",
|
||||||
iterations, elapsed,
|
iterations, elapsed,
|
||||||
successes, iterations - successes,
|
successes, iterations - successes,
|
||||||
elapsed > 0 ? iterations * 1000 / elapsed : -1);
|
elapsed > 0 ? iterations * 1000L / elapsed : -1);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (Exception x)
|
catch (Exception x)
|
||||||
|
|
|
@ -42,6 +42,7 @@ public class ByteArrayEndPoint extends AbstractEndPoint
|
||||||
static final Logger LOG = LoggerFactory.getLogger(ByteArrayEndPoint.class);
|
static final Logger LOG = LoggerFactory.getLogger(ByteArrayEndPoint.class);
|
||||||
static final InetAddress NOIP;
|
static final InetAddress NOIP;
|
||||||
static final InetSocketAddress NOIPPORT;
|
static final InetSocketAddress NOIPPORT;
|
||||||
|
private static final int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 1024;
|
||||||
|
|
||||||
static
|
static
|
||||||
{
|
{
|
||||||
|
@ -67,6 +68,7 @@ public class ByteArrayEndPoint extends AbstractEndPoint
|
||||||
private final AutoLock _lock = new AutoLock();
|
private final AutoLock _lock = new AutoLock();
|
||||||
private final Condition _hasOutput = _lock.newCondition();
|
private final Condition _hasOutput = _lock.newCondition();
|
||||||
private final Queue<ByteBuffer> _inQ = new ArrayDeque<>();
|
private final Queue<ByteBuffer> _inQ = new ArrayDeque<>();
|
||||||
|
private final int _outputSize;
|
||||||
private ByteBuffer _out;
|
private ByteBuffer _out;
|
||||||
private boolean _growOutput;
|
private boolean _growOutput;
|
||||||
|
|
||||||
|
@ -113,7 +115,8 @@ public class ByteArrayEndPoint extends AbstractEndPoint
|
||||||
super(timer);
|
super(timer);
|
||||||
if (BufferUtil.hasContent(input))
|
if (BufferUtil.hasContent(input))
|
||||||
addInput(input);
|
addInput(input);
|
||||||
_out = output == null ? BufferUtil.allocate(1024) : output;
|
_outputSize = (output == null) ? 1024 : output.capacity();
|
||||||
|
_out = output == null ? BufferUtil.allocate(_outputSize) : output;
|
||||||
setIdleTimeout(idleTimeoutMs);
|
setIdleTimeout(idleTimeoutMs);
|
||||||
onOpen();
|
onOpen();
|
||||||
}
|
}
|
||||||
|
@ -290,7 +293,7 @@ public class ByteArrayEndPoint extends AbstractEndPoint
|
||||||
try (AutoLock lock = _lock.lock())
|
try (AutoLock lock = _lock.lock())
|
||||||
{
|
{
|
||||||
b = _out;
|
b = _out;
|
||||||
_out = BufferUtil.allocate(b.capacity());
|
_out = BufferUtil.allocate(_outputSize);
|
||||||
}
|
}
|
||||||
getWriteFlusher().completeWrite();
|
getWriteFlusher().completeWrite();
|
||||||
return b;
|
return b;
|
||||||
|
@ -316,7 +319,7 @@ public class ByteArrayEndPoint extends AbstractEndPoint
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
b = _out;
|
b = _out;
|
||||||
_out = BufferUtil.allocate(b.capacity());
|
_out = BufferUtil.allocate(_outputSize);
|
||||||
}
|
}
|
||||||
getWriteFlusher().completeWrite();
|
getWriteFlusher().completeWrite();
|
||||||
return b;
|
return b;
|
||||||
|
@ -424,11 +427,16 @@ public class ByteArrayEndPoint extends AbstractEndPoint
|
||||||
BufferUtil.compact(_out);
|
BufferUtil.compact(_out);
|
||||||
if (b.remaining() > BufferUtil.space(_out))
|
if (b.remaining() > BufferUtil.space(_out))
|
||||||
{
|
{
|
||||||
ByteBuffer n = BufferUtil.allocate(_out.capacity() + b.remaining() * 2);
|
// Don't grow larger than MAX_BUFFER_SIZE to avoid memory issues.
|
||||||
|
if (_out.capacity() < MAX_BUFFER_SIZE)
|
||||||
|
{
|
||||||
|
long newBufferCapacity = Math.min((long)(_out.capacity() + b.remaining() * 1.5), MAX_BUFFER_SIZE);
|
||||||
|
ByteBuffer n = BufferUtil.allocate(Math.toIntExact(newBufferCapacity));
|
||||||
BufferUtil.append(n, _out);
|
BufferUtil.append(n, _out);
|
||||||
_out = n;
|
_out = n;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (BufferUtil.append(_out, b) > 0)
|
if (BufferUtil.append(_out, b) > 0)
|
||||||
idle = false;
|
idle = false;
|
||||||
|
|
|
@ -83,6 +83,7 @@ public class JspcMojo extends AbstractMojo
|
||||||
{
|
{
|
||||||
|
|
||||||
private boolean scanAll;
|
private boolean scanAll;
|
||||||
|
private boolean scanManifest;
|
||||||
|
|
||||||
public void setClassLoader(ClassLoader loader)
|
public void setClassLoader(ClassLoader loader)
|
||||||
{
|
{
|
||||||
|
@ -99,6 +100,16 @@ public class JspcMojo extends AbstractMojo
|
||||||
return this.scanAll;
|
return this.scanAll;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setScanManifest(boolean scanManifest)
|
||||||
|
{
|
||||||
|
this.scanManifest = scanManifest;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getScanManifest()
|
||||||
|
{
|
||||||
|
return this.scanManifest;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected TldScanner newTldScanner(JspCServletContext context, boolean namespaceAware, boolean validate, boolean blockExternal)
|
protected TldScanner newTldScanner(JspCServletContext context, boolean namespaceAware, boolean validate, boolean blockExternal)
|
||||||
{
|
{
|
||||||
|
@ -106,6 +117,7 @@ public class JspcMojo extends AbstractMojo
|
||||||
{
|
{
|
||||||
StandardJarScanner jarScanner = new StandardJarScanner();
|
StandardJarScanner jarScanner = new StandardJarScanner();
|
||||||
jarScanner.setScanAllDirectories(getScanAllDirectories());
|
jarScanner.setScanAllDirectories(getScanAllDirectories());
|
||||||
|
jarScanner.setScanManifest(getScanManifest());
|
||||||
context.setAttribute(JarScanner.class.getName(), jarScanner);
|
context.setAttribute(JarScanner.class.getName(), jarScanner);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -243,6 +255,13 @@ public class JspcMojo extends AbstractMojo
|
||||||
@Parameter(defaultValue = "true")
|
@Parameter(defaultValue = "true")
|
||||||
private boolean scanAllDirectories;
|
private boolean scanAllDirectories;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if the manifest of JAR files found on the classpath should be scanned.
|
||||||
|
* True by default.
|
||||||
|
*/
|
||||||
|
@Parameter(defaultValue = "true")
|
||||||
|
private boolean scanManifest;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute() throws MojoExecutionException, MojoFailureException
|
public void execute() throws MojoExecutionException, MojoFailureException
|
||||||
{
|
{
|
||||||
|
@ -319,6 +338,7 @@ public class JspcMojo extends AbstractMojo
|
||||||
jspc.setOutputDir(generatedClasses);
|
jspc.setOutputDir(generatedClasses);
|
||||||
jspc.setClassLoader(fakeWebAppClassLoader);
|
jspc.setClassLoader(fakeWebAppClassLoader);
|
||||||
jspc.setScanAllDirectories(scanAllDirectories);
|
jspc.setScanAllDirectories(scanAllDirectories);
|
||||||
|
jspc.setScanManifest(scanManifest);
|
||||||
jspc.setCompile(true);
|
jspc.setCompile(true);
|
||||||
if (sourceVersion != null)
|
if (sourceVersion != null)
|
||||||
jspc.setCompilerSourceVM(sourceVersion);
|
jspc.setCompilerSourceVM(sourceVersion);
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>commons-io</groupId>
|
<groupId>commons-io</groupId>
|
||||||
<artifactId>commons-io</artifactId>
|
<artifactId>commons-io</artifactId>
|
||||||
<version>2.6</version>
|
<version>2.7</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.jetty.toolchain</groupId>
|
<groupId>org.eclipse.jetty.toolchain</groupId>
|
||||||
|
|
|
@ -50,6 +50,17 @@
|
||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<goals>
|
||||||
|
<goal>test-jar</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-invoker-plugin</artifactId>
|
<artifactId>maven-invoker-plugin</artifactId>
|
||||||
|
@ -65,8 +76,12 @@
|
||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
<configuration>
|
<configuration>
|
||||||
|
<debug>${it.debug}</debug>
|
||||||
|
<addTestClassPath>true</addTestClassPath>
|
||||||
<scriptVariables>
|
<scriptVariables>
|
||||||
<maven.dependency.plugin.version>${maven.dependency.plugin.version}</maven.dependency.plugin.version>
|
<maven.dependency.plugin.version>${maven.dependency.plugin.version}</maven.dependency.plugin.version>
|
||||||
|
<maven.surefire.version>${maven.surefire.version}</maven.surefire.version>
|
||||||
|
<hamcrest.version>${hamcrest.version}</hamcrest.version>
|
||||||
</scriptVariables>
|
</scriptVariables>
|
||||||
<goals>
|
<goals>
|
||||||
<goal>clean</goal>
|
<goal>clean</goal>
|
||||||
|
@ -149,5 +164,18 @@
|
||||||
<artifactId>jetty-slf4j-impl</artifactId>
|
<artifactId>jetty-slf4j-impl</artifactId>
|
||||||
<scope>runtime</scope>
|
<scope>runtime</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty.demos</groupId>
|
||||||
|
<artifactId>demo-simple-webapp</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<type>war</type>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-client</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
invoker.goals = test
|
|
@ -0,0 +1,139 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>org.eclipse.jetty.its</groupId>
|
||||||
|
<artifactId>jetty-runner-it-test-demo-simple-webapp</artifactId>
|
||||||
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
|
<packaging>war</packaging>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-runner</artifactId>
|
||||||
|
<version>@project.version@</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-runner</artifactId>
|
||||||
|
<version>@project.version@</version>
|
||||||
|
<classifier>tests</classifier>
|
||||||
|
<type>test-jar</type>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty.demos</groupId>
|
||||||
|
<artifactId>demo-simple-webapp</artifactId>
|
||||||
|
<version>@project.version@</version>
|
||||||
|
<type>war</type>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-client</artifactId>
|
||||||
|
<version>@project.version@</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.hamcrest</groupId>
|
||||||
|
<artifactId>hamcrest-core</artifactId>
|
||||||
|
<version>@hamcrest.version@</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-dependency-plugin</artifactId>
|
||||||
|
<version>@maven.dependency.plugin.version@</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>copy-jetty-runner</id>
|
||||||
|
<phase>generate-resources</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>copy</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<artifactItems>
|
||||||
|
<artifactItem>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-runner</artifactId>
|
||||||
|
<version>@project.version@</version>
|
||||||
|
<type>jar</type>
|
||||||
|
<overWrite>false</overWrite>
|
||||||
|
<outputDirectory>${project.build.directory}/</outputDirectory>
|
||||||
|
<destFileName>jetty-runner.jar</destFileName>
|
||||||
|
</artifactItem>
|
||||||
|
<artifactItem>
|
||||||
|
<groupId>org.eclipse.jetty.demos</groupId>
|
||||||
|
<artifactId>demo-simple-webapp</artifactId>
|
||||||
|
<version>@project.version@</version>
|
||||||
|
<type>war</type>
|
||||||
|
<overWrite>false</overWrite>
|
||||||
|
<outputDirectory>${project.build.directory}</outputDirectory>
|
||||||
|
<destFileName>demo-simple-webapp.war</destFileName>
|
||||||
|
</artifactItem>
|
||||||
|
</artifactItems>
|
||||||
|
<overWriteReleases>false</overWriteReleases>
|
||||||
|
<overWriteSnapshots>true</overWriteSnapshots>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
|
<artifactId>exec-maven-plugin</artifactId>
|
||||||
|
<version>@maven.exec.plugin.version@</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<goals>
|
||||||
|
<goal>exec</goal>
|
||||||
|
</goals>
|
||||||
|
<phase>generate-test-resources</phase>
|
||||||
|
<configuration>
|
||||||
|
<outputFile>${project.build.directory}/jetty-runner.log</outputFile>
|
||||||
|
<async>true</async>
|
||||||
|
<executable>${java.home}/bin/java</executable>
|
||||||
|
<arguments>
|
||||||
|
<argument>-jar</argument>
|
||||||
|
<argument>${project.build.directory}/jetty-runner.jar</argument>
|
||||||
|
<argument>--out</argument>
|
||||||
|
<argument>${project.build.directory}/jetty-runner.out</argument>
|
||||||
|
<argument>--port</argument>
|
||||||
|
<argument>0</argument>
|
||||||
|
<argument>--path</argument>
|
||||||
|
<argument>french-chocolate-rocks</argument>
|
||||||
|
<argument>--server-uri-file</argument>
|
||||||
|
<argument>${project.build.directory}/server-uri.txt</argument>
|
||||||
|
<argument>${project.build.directory}/demo-simple-webapp.war</argument>
|
||||||
|
</arguments>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<version>@maven.surefire.version@</version>
|
||||||
|
<configuration>
|
||||||
|
<includes>
|
||||||
|
<include>IntegrationTest*.java</include>
|
||||||
|
</includes>
|
||||||
|
<systemPropertyVariables>
|
||||||
|
</systemPropertyVariables>
|
||||||
|
<dependenciesToScan>
|
||||||
|
<dependency>org.eclipse.jetty:jetty-runner</dependency>
|
||||||
|
</dependenciesToScan>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
|
@ -0,0 +1 @@
|
||||||
|
invoker.goals = test
|
|
@ -0,0 +1,139 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>org.eclipse.jetty.its</groupId>
|
||||||
|
<artifactId>jetty-runner-it-test-demo-simple-webapp</artifactId>
|
||||||
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
|
<packaging>war</packaging>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-runner</artifactId>
|
||||||
|
<version>@project.version@</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-runner</artifactId>
|
||||||
|
<version>@project.version@</version>
|
||||||
|
<classifier>tests</classifier>
|
||||||
|
<type>test-jar</type>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty.demos</groupId>
|
||||||
|
<artifactId>demo-simple-webapp</artifactId>
|
||||||
|
<version>@project.version@</version>
|
||||||
|
<type>war</type>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-client</artifactId>
|
||||||
|
<version>@project.version@</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.hamcrest</groupId>
|
||||||
|
<artifactId>hamcrest-core</artifactId>
|
||||||
|
<version>@hamcrest.version@</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-dependency-plugin</artifactId>
|
||||||
|
<version>@maven.dependency.plugin.version@</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>copy-jetty-runner</id>
|
||||||
|
<phase>generate-resources</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>copy</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<artifactItems>
|
||||||
|
<artifactItem>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-runner</artifactId>
|
||||||
|
<version>@project.version@</version>
|
||||||
|
<type>jar</type>
|
||||||
|
<overWrite>false</overWrite>
|
||||||
|
<outputDirectory>${project.build.directory}/</outputDirectory>
|
||||||
|
<destFileName>jetty-runner.jar</destFileName>
|
||||||
|
</artifactItem>
|
||||||
|
<artifactItem>
|
||||||
|
<groupId>org.eclipse.jetty.demos</groupId>
|
||||||
|
<artifactId>demo-simple-webapp</artifactId>
|
||||||
|
<version>@project.version@</version>
|
||||||
|
<type>war</type>
|
||||||
|
<overWrite>false</overWrite>
|
||||||
|
<outputDirectory>${project.build.directory}</outputDirectory>
|
||||||
|
<destFileName>demo-simple-webapp.war</destFileName>
|
||||||
|
</artifactItem>
|
||||||
|
</artifactItems>
|
||||||
|
<overWriteReleases>false</overWriteReleases>
|
||||||
|
<overWriteSnapshots>true</overWriteSnapshots>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
|
<artifactId>exec-maven-plugin</artifactId>
|
||||||
|
<version>@maven.exec.plugin.version@</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<goals>
|
||||||
|
<goal>exec</goal>
|
||||||
|
</goals>
|
||||||
|
<phase>generate-test-resources</phase>
|
||||||
|
<configuration>
|
||||||
|
<outputFile>${project.build.directory}/jetty-runner.log</outputFile>
|
||||||
|
<async>true</async>
|
||||||
|
<executable>${java.home}/bin/java</executable>
|
||||||
|
<arguments>
|
||||||
|
<!-- <argument>-Xdebug</argument>-->
|
||||||
|
<!-- <argument>-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000</argument>-->
|
||||||
|
<argument>-jar</argument>
|
||||||
|
<argument>${project.build.directory}/jetty-runner.jar</argument>
|
||||||
|
<argument>--out</argument>
|
||||||
|
<argument>${project.build.directory}/jetty-runner.out</argument>
|
||||||
|
<argument>--port</argument>
|
||||||
|
<argument>0</argument>
|
||||||
|
<argument>--server-uri-file</argument>
|
||||||
|
<argument>${project.build.directory}/server-uri.txt</argument>
|
||||||
|
<argument>${project.build.directory}/demo-simple-webapp.war</argument>
|
||||||
|
</arguments>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<version>@maven.surefire.version@</version>
|
||||||
|
<configuration>
|
||||||
|
<includes>
|
||||||
|
<include>IntegrationTest*.java</include>
|
||||||
|
</includes>
|
||||||
|
<systemPropertyVariables>
|
||||||
|
</systemPropertyVariables>
|
||||||
|
<dependenciesToScan>
|
||||||
|
<dependency>org.eclipse.jetty:jetty-runner</dependency>
|
||||||
|
</dependenciesToScan>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
|
@ -19,6 +19,9 @@ import java.net.MalformedURLException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.URLClassLoader;
|
import java.net.URLClassLoader;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
@ -78,7 +81,9 @@ public class Runner
|
||||||
org.eclipse.jetty.plus.webapp.EnvConfiguration.class.getCanonicalName(),
|
org.eclipse.jetty.plus.webapp.EnvConfiguration.class.getCanonicalName(),
|
||||||
org.eclipse.jetty.plus.webapp.PlusConfiguration.class.getCanonicalName(),
|
org.eclipse.jetty.plus.webapp.PlusConfiguration.class.getCanonicalName(),
|
||||||
org.eclipse.jetty.annotations.AnnotationConfiguration.class.getCanonicalName(),
|
org.eclipse.jetty.annotations.AnnotationConfiguration.class.getCanonicalName(),
|
||||||
org.eclipse.jetty.webapp.JettyWebXmlConfiguration.class.getCanonicalName()
|
org.eclipse.jetty.webapp.JettyWebXmlConfiguration.class.getCanonicalName(),
|
||||||
|
org.eclipse.jetty.webapp.WebAppConfiguration.class.getCanonicalName(),
|
||||||
|
org.eclipse.jetty.webapp.JspConfiguration.class.getCanonicalName()
|
||||||
};
|
};
|
||||||
public static final String CONTAINER_INCLUDE_JAR_PATTERN = ".*/jetty-runner-[^/]*\\.jar$";
|
public static final String CONTAINER_INCLUDE_JAR_PATTERN = ".*/jetty-runner-[^/]*\\.jar$";
|
||||||
public static final String DEFAULT_CONTEXT_PATH = "/";
|
public static final String DEFAULT_CONTEXT_PATH = "/";
|
||||||
|
@ -92,6 +97,7 @@ public class Runner
|
||||||
protected ArrayList<String> _configFiles;
|
protected ArrayList<String> _configFiles;
|
||||||
protected boolean _enableStats = false;
|
protected boolean _enableStats = false;
|
||||||
protected String _statsPropFile;
|
protected String _statsPropFile;
|
||||||
|
protected String _serverUriFile;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Classpath
|
* Classpath
|
||||||
|
@ -164,6 +170,7 @@ public class Runner
|
||||||
System.err.println(" --out file - info/warn/debug log filename (with optional 'yyyy_mm_dd' wildcard");
|
System.err.println(" --out file - info/warn/debug log filename (with optional 'yyyy_mm_dd' wildcard");
|
||||||
System.err.println(" --host name|ip - interface to listen on (default is all interfaces)");
|
System.err.println(" --host name|ip - interface to listen on (default is all interfaces)");
|
||||||
System.err.println(" --port n - port to listen on (default 8080)");
|
System.err.println(" --port n - port to listen on (default 8080)");
|
||||||
|
System.err.println(" --server-uri-file path - file to write a single line with server base URI");
|
||||||
System.err.println(" --stop-port n - port to listen for stop command (or -DSTOP.PORT=n)");
|
System.err.println(" --stop-port n - port to listen for stop command (or -DSTOP.PORT=n)");
|
||||||
System.err.println(" --stop-key n - security string for stop command (required if --stop-port is present) (or -DSTOP.KEY=n)");
|
System.err.println(" --stop-key n - security string for stop command (required if --stop-port is present) (or -DSTOP.KEY=n)");
|
||||||
System.err.println(" [--jar file]*n - each tuple specifies an extra jar to be added to the classloader");
|
System.err.println(" [--jar file]*n - each tuple specifies an extra jar to be added to the classloader");
|
||||||
|
@ -293,6 +300,9 @@ public class Runner
|
||||||
_statsPropFile = args[++i];
|
_statsPropFile = args[++i];
|
||||||
_statsPropFile = ("unsecure".equalsIgnoreCase(_statsPropFile) ? null : _statsPropFile);
|
_statsPropFile = ("unsecure".equalsIgnoreCase(_statsPropFile) ? null : _statsPropFile);
|
||||||
break;
|
break;
|
||||||
|
case "--server-uri-file":
|
||||||
|
_serverUriFile = args[++i];
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
// process system property type argument so users can use in second args part
|
// process system property type argument so users can use in second args part
|
||||||
if (args[i].startsWith("-D"))
|
if (args[i].startsWith("-D"))
|
||||||
|
@ -310,7 +320,7 @@ public class Runner
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// process contexts
|
// process contexts
|
||||||
|
|
||||||
if (!runnerServerInitialized) // log handlers not registered, server maybe not created, etc
|
if (!runnerServerInitialized) // log handlers not registered, server maybe not created, etc
|
||||||
{
|
{
|
||||||
|
@ -334,7 +344,7 @@ public class Runner
|
||||||
}
|
}
|
||||||
|
|
||||||
//check that everything got configured, and if not, make the handlers
|
//check that everything got configured, and if not, make the handlers
|
||||||
HandlerCollection handlers = (HandlerCollection)_server.getChildHandlerByClass(HandlerCollection.class);
|
HandlerCollection handlers = _server.getChildHandlerByClass(HandlerCollection.class);
|
||||||
if (handlers == null)
|
if (handlers == null)
|
||||||
{
|
{
|
||||||
handlers = new HandlerList();
|
handlers = new HandlerList();
|
||||||
|
@ -342,7 +352,7 @@ public class Runner
|
||||||
}
|
}
|
||||||
|
|
||||||
//check if contexts already configured
|
//check if contexts already configured
|
||||||
_contexts = (ContextHandlerCollection)handlers.getChildHandlerByClass(ContextHandlerCollection.class);
|
_contexts = handlers.getChildHandlerByClass(ContextHandlerCollection.class);
|
||||||
if (_contexts == null)
|
if (_contexts == null)
|
||||||
{
|
{
|
||||||
_contexts = new ContextHandlerCollection();
|
_contexts = new ContextHandlerCollection();
|
||||||
|
@ -519,6 +529,13 @@ public class Runner
|
||||||
public void run() throws Exception
|
public void run() throws Exception
|
||||||
{
|
{
|
||||||
_server.start();
|
_server.start();
|
||||||
|
if (_serverUriFile != null)
|
||||||
|
{
|
||||||
|
Path fileWithPort = Paths.get(_serverUriFile);
|
||||||
|
Files.deleteIfExists(fileWithPort);
|
||||||
|
String serverUri = _server.getURI().toString();
|
||||||
|
Files.writeString(fileWithPort, serverUri);
|
||||||
|
}
|
||||||
_server.join();
|
_server.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// 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.maven.jettyrunner.it;
|
||||||
|
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
|
import org.eclipse.jetty.client.api.ContentResponse;
|
||||||
|
import org.hamcrest.Matchers;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static java.util.concurrent.TimeUnit.MINUTES;
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
|
||||||
|
public class IntegrationTestJettyRunner
|
||||||
|
{
|
||||||
|
@Test
|
||||||
|
public void testGet() throws Exception
|
||||||
|
{
|
||||||
|
String serverUri = findServerUri();
|
||||||
|
HttpClient httpClient = new HttpClient();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
httpClient.start();
|
||||||
|
ContentResponse response = httpClient.newRequest(serverUri).send();
|
||||||
|
String res = response.getContentAsString();
|
||||||
|
assertThat(res, Matchers.containsString("Hello World!"));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
httpClient.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String findServerUri() throws Exception
|
||||||
|
{
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
|
||||||
|
while (System.currentTimeMillis() - now < MINUTES.toMillis(2))
|
||||||
|
{
|
||||||
|
Path portTxt = Paths.get("target", "server-uri.txt");
|
||||||
|
if (Files.exists(portTxt))
|
||||||
|
{
|
||||||
|
List<String> lines = Files.readAllLines(portTxt);
|
||||||
|
return lines.get(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Exception("cannot find started Jetty");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -13,6 +13,7 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.server;
|
package org.eclipse.jetty.server;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.locks.Condition;
|
import java.util.concurrent.locks.Condition;
|
||||||
|
|
||||||
|
@ -302,12 +303,16 @@ class AsyncContentProducer implements ContentProducer
|
||||||
|
|
||||||
// In case the _rawContent was set by consumeAll(), check the httpChannel
|
// In case the _rawContent was set by consumeAll(), check the httpChannel
|
||||||
// to see if it has a more precise error. Otherwise, the exact same
|
// to see if it has a more precise error. Otherwise, the exact same
|
||||||
// special content will be returned by the httpChannel.
|
// special content will be returned by the httpChannel; do not do that
|
||||||
|
// if the _error flag was set, meaning the current error is definitive.
|
||||||
|
if (!_error)
|
||||||
|
{
|
||||||
HttpInput.Content refreshedRawContent = produceRawContent();
|
HttpInput.Content refreshedRawContent = produceRawContent();
|
||||||
if (refreshedRawContent != null)
|
if (refreshedRawContent != null)
|
||||||
_rawContent = refreshedRawContent;
|
_rawContent = refreshedRawContent;
|
||||||
|
|
||||||
_error = _rawContent.getError() != null;
|
_error = _rawContent.getError() != null;
|
||||||
|
}
|
||||||
|
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("raw content is special (with error = {}), returning it {}", _error, this);
|
LOG.debug("raw content is special (with error = {}), returning it {}", _error, this);
|
||||||
return _rawContent;
|
return _rawContent;
|
||||||
|
@ -317,7 +322,9 @@ class AsyncContentProducer implements ContentProducer
|
||||||
{
|
{
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("using interceptor to transform raw content {}", this);
|
LOG.debug("using interceptor to transform raw content {}", this);
|
||||||
_transformedContent = _interceptor.readFrom(_rawContent);
|
_transformedContent = intercept();
|
||||||
|
if (_error)
|
||||||
|
return _rawContent;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -369,6 +376,26 @@ class AsyncContentProducer implements ContentProducer
|
||||||
return _transformedContent;
|
return _transformedContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private HttpInput.Content intercept()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return _interceptor.readFrom(_rawContent);
|
||||||
|
}
|
||||||
|
catch (Throwable x)
|
||||||
|
{
|
||||||
|
IOException failure = new IOException("Bad content", x);
|
||||||
|
failCurrentContent(failure);
|
||||||
|
// Set the _error flag to mark the error as definitive, i.e.:
|
||||||
|
// do not try to produce new raw content to get a fresher error.
|
||||||
|
_error = true;
|
||||||
|
Response response = _httpChannel.getResponse();
|
||||||
|
if (response.isCommitted())
|
||||||
|
_httpChannel.abort(failure);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private HttpInput.Content produceRawContent()
|
private HttpInput.Content produceRawContent()
|
||||||
{
|
{
|
||||||
HttpInput.Content content = _httpChannel.produceContent();
|
HttpInput.Content content = _httpChannel.produceContent();
|
||||||
|
|
|
@ -22,6 +22,7 @@ import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.function.BiPredicate;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
@ -279,6 +280,7 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog
|
||||||
private final String _formatString;
|
private final String _formatString;
|
||||||
private transient PathMappings<String> _ignorePathMap;
|
private transient PathMappings<String> _ignorePathMap;
|
||||||
private String[] _ignorePaths;
|
private String[] _ignorePaths;
|
||||||
|
private BiPredicate<Request, Response> _filter;
|
||||||
|
|
||||||
public CustomRequestLog()
|
public CustomRequestLog()
|
||||||
{
|
{
|
||||||
|
@ -311,6 +313,16 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This allows you to set a custom filter to decide whether to log a request or omit it from the request log.
|
||||||
|
* This filter is evaluated after path filtering is applied from {@link #setIgnorePaths(String[])}.
|
||||||
|
* @param filter - a BiPredicate which returns true if this request should be logged.
|
||||||
|
*/
|
||||||
|
public void setFilter(BiPredicate<Request, Response> filter)
|
||||||
|
{
|
||||||
|
_filter = filter;
|
||||||
|
}
|
||||||
|
|
||||||
@ManagedAttribute("The RequestLogWriter")
|
@ManagedAttribute("The RequestLogWriter")
|
||||||
public RequestLog.Writer getWriter()
|
public RequestLog.Writer getWriter()
|
||||||
{
|
{
|
||||||
|
@ -324,12 +336,15 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void log(Request request, Response response)
|
public void log(Request request, Response response)
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
if (_ignorePathMap != null && _ignorePathMap.getMatch(request.getRequestURI()) != null)
|
if (_ignorePathMap != null && _ignorePathMap.getMatch(request.getRequestURI()) != null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (_filter != null && !_filter.test(request, response))
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
StringBuilder sb = _buffers.get();
|
StringBuilder sb = _buffers.get();
|
||||||
sb.setLength(0);
|
sb.setLength(0);
|
||||||
|
|
||||||
|
|
|
@ -263,9 +263,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
|
||||||
{
|
{
|
||||||
// Fill the request buffer (if needed).
|
// Fill the request buffer (if needed).
|
||||||
int filled = fillRequestBuffer();
|
int filled = fillRequestBuffer();
|
||||||
if (filled > 0)
|
if (filled < 0 && getEndPoint().isOutputShutdown())
|
||||||
bytesIn.add(filled);
|
|
||||||
else if (filled == -1 && getEndPoint().isOutputShutdown())
|
|
||||||
close();
|
close();
|
||||||
|
|
||||||
// Parse the request buffer.
|
// Parse the request buffer.
|
||||||
|
@ -300,6 +298,14 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (Throwable x)
|
||||||
|
{
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug("{} caught exception {}", this, _channel.getState(), x);
|
||||||
|
BufferUtil.clear(_requestBuffer);
|
||||||
|
releaseRequestBuffer();
|
||||||
|
getEndPoint().close(x);
|
||||||
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
setCurrentConnection(last);
|
setCurrentConnection(last);
|
||||||
|
@ -333,10 +339,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
|
||||||
private int fillRequestBuffer()
|
private int fillRequestBuffer()
|
||||||
{
|
{
|
||||||
if (_contentBufferReferences.get() > 0)
|
if (_contentBufferReferences.get() > 0)
|
||||||
{
|
throw new IllegalStateException("fill with unconsumed content on " + this);
|
||||||
LOG.warn("{} fill with unconsumed content!", this);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (BufferUtil.isEmpty(_requestBuffer))
|
if (BufferUtil.isEmpty(_requestBuffer))
|
||||||
{
|
{
|
||||||
|
@ -352,8 +355,9 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
|
||||||
if (filled == 0) // Do a retry on fill 0 (optimization for SSL connections)
|
if (filled == 0) // Do a retry on fill 0 (optimization for SSL connections)
|
||||||
filled = getEndPoint().fill(_requestBuffer);
|
filled = getEndPoint().fill(_requestBuffer);
|
||||||
|
|
||||||
// tell parser
|
if (filled > 0)
|
||||||
if (filled < 0)
|
bytesIn.add(filled);
|
||||||
|
else if (filled < 0)
|
||||||
_parser.atEOF();
|
_parser.atEOF();
|
||||||
|
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
|
@ -363,6 +367,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
|
||||||
}
|
}
|
||||||
catch (IOException e)
|
catch (IOException e)
|
||||||
{
|
{
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("Unable to fill from endpoint {}", getEndPoint(), e);
|
LOG.debug("Unable to fill from endpoint {}", getEndPoint(), e);
|
||||||
_parser.atEOF();
|
_parser.atEOF();
|
||||||
return -1;
|
return -1;
|
||||||
|
|
|
@ -23,7 +23,6 @@ import java.nio.charset.Charset;
|
||||||
import java.nio.charset.CharsetEncoder;
|
import java.nio.charset.CharsetEncoder;
|
||||||
import java.nio.charset.CoderResult;
|
import java.nio.charset.CoderResult;
|
||||||
import java.nio.charset.CodingErrorAction;
|
import java.nio.charset.CodingErrorAction;
|
||||||
import java.util.ResourceBundle;
|
|
||||||
import java.util.concurrent.CancellationException;
|
import java.util.concurrent.CancellationException;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import javax.servlet.RequestDispatcher;
|
import javax.servlet.RequestDispatcher;
|
||||||
|
@ -627,6 +626,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
||||||
catch (Throwable t)
|
catch (Throwable t)
|
||||||
{
|
{
|
||||||
onWriteComplete(true, t);
|
onWriteComplete(true, t);
|
||||||
|
throw t;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,7 +67,6 @@ import javax.servlet.http.WebConnection;
|
||||||
import org.eclipse.jetty.http.BadMessageException;
|
import org.eclipse.jetty.http.BadMessageException;
|
||||||
import org.eclipse.jetty.http.ComplianceViolation;
|
import org.eclipse.jetty.http.ComplianceViolation;
|
||||||
import org.eclipse.jetty.http.HostPortHttpField;
|
import org.eclipse.jetty.http.HostPortHttpField;
|
||||||
import org.eclipse.jetty.http.HttpCompliance;
|
|
||||||
import org.eclipse.jetty.http.HttpCookie;
|
import org.eclipse.jetty.http.HttpCookie;
|
||||||
import org.eclipse.jetty.http.HttpCookie.SetCookieHttpField;
|
import org.eclipse.jetty.http.HttpCookie.SetCookieHttpField;
|
||||||
import org.eclipse.jetty.http.HttpField;
|
import org.eclipse.jetty.http.HttpField;
|
||||||
|
@ -1692,10 +1691,11 @@ public class Request implements HttpServletRequest
|
||||||
_httpFields = request.getFields();
|
_httpFields = request.getFields();
|
||||||
final HttpURI uri = request.getURI();
|
final HttpURI uri = request.getURI();
|
||||||
|
|
||||||
|
UriCompliance compliance = null;
|
||||||
boolean ambiguous = uri.isAmbiguous();
|
boolean ambiguous = uri.isAmbiguous();
|
||||||
if (ambiguous)
|
if (ambiguous)
|
||||||
{
|
{
|
||||||
UriCompliance compliance = _channel == null || _channel.getHttpConfiguration() == null ? null : _channel.getHttpConfiguration().getUriCompliance();
|
compliance = _channel == null || _channel.getHttpConfiguration() == null ? null : _channel.getHttpConfiguration().getUriCompliance();
|
||||||
if (uri.hasAmbiguousSegment() && (compliance == null || !compliance.allows(UriCompliance.Violation.AMBIGUOUS_PATH_SEGMENT)))
|
if (uri.hasAmbiguousSegment() && (compliance == null || !compliance.allows(UriCompliance.Violation.AMBIGUOUS_PATH_SEGMENT)))
|
||||||
throw new BadMessageException("Ambiguous segment in URI");
|
throw new BadMessageException("Ambiguous segment in URI");
|
||||||
if (uri.hasAmbiguousSeparator() && (compliance == null || !compliance.allows(UriCompliance.Violation.AMBIGUOUS_PATH_SEPARATOR)))
|
if (uri.hasAmbiguousSeparator() && (compliance == null || !compliance.allows(UriCompliance.Violation.AMBIGUOUS_PATH_SEPARATOR)))
|
||||||
|
@ -1746,9 +1746,9 @@ public class Request implements HttpServletRequest
|
||||||
path = (encoded.length() == 1) ? "/" : _uri.getDecodedPath();
|
path = (encoded.length() == 1) ? "/" : _uri.getDecodedPath();
|
||||||
// Strictly speaking if a URI is legal and encodes ambiguous segments, then they should be
|
// Strictly speaking if a URI is legal and encodes ambiguous segments, then they should be
|
||||||
// reflected in the decoded string version. However, it can be ambiguous to provide a decoded path as
|
// reflected in the decoded string version. However, it can be ambiguous to provide a decoded path as
|
||||||
// a string, so we normalize again. If an application wishes to see ambiguous URIs, then they can look
|
// a string, so we normalize again. If an application wishes to see ambiguous URIs, then they must
|
||||||
// at the encoded form of the URI
|
// set the {@link UriCompliance.Violation#NON_CANONICAL_AMBIGUOUS_PATHS} compliance.
|
||||||
if (ambiguous)
|
if (ambiguous && (compliance == null || !compliance.allows(UriCompliance.Violation.NON_CANONICAL_AMBIGUOUS_PATHS)))
|
||||||
path = URIUtil.canonicalPath(path);
|
path = URIUtil.canonicalPath(path);
|
||||||
}
|
}
|
||||||
else if ("*".equals(encoded) || HttpMethod.CONNECT.is(getMethod()))
|
else if ("*".equals(encoded) || HttpMethod.CONNECT.is(getMethod()))
|
||||||
|
|
|
@ -15,14 +15,15 @@ package org.eclipse.jetty.server.handler;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.ArrayDeque;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
|
||||||
import javax.servlet.ServletContext;
|
import javax.servlet.ServletContext;
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import org.eclipse.jetty.http.HttpMethod;
|
import org.eclipse.jetty.http.HttpMethod;
|
||||||
|
import org.eclipse.jetty.http.HttpStatus;
|
||||||
import org.eclipse.jetty.http.MimeTypes;
|
import org.eclipse.jetty.http.MimeTypes;
|
||||||
import org.eclipse.jetty.http.pathmap.PathSpecSet;
|
import org.eclipse.jetty.http.pathmap.PathSpecSet;
|
||||||
import org.eclipse.jetty.server.HttpChannel;
|
import org.eclipse.jetty.server.HttpChannel;
|
||||||
|
@ -39,16 +40,17 @@ import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Buffered Response Handler
|
|
||||||
* <p>
|
* <p>
|
||||||
* A Handler that can apply a {@link org.eclipse.jetty.server.HttpOutput.Interceptor}
|
* A Handler that can apply a {@link org.eclipse.jetty.server.HttpOutput.Interceptor}
|
||||||
* mechanism to buffer the entire response content until the output is closed.
|
* mechanism to buffer the entire response content until the output is closed.
|
||||||
* This allows the commit to be delayed until the response is complete and thus
|
* This allows the commit to be delayed until the response is complete and thus
|
||||||
* headers and response status can be changed while writing the body.
|
* headers and response status can be changed while writing the body.
|
||||||
|
* </p>
|
||||||
* <p>
|
* <p>
|
||||||
* Note that the decision to buffer is influenced by the headers and status at the
|
* Note that the decision to buffer is influenced by the headers and status at the
|
||||||
* first write, and thus subsequent changes to those headers will not influence the
|
* first write, and thus subsequent changes to those headers will not influence the
|
||||||
* decision to buffer or not.
|
* decision to buffer or not.
|
||||||
|
* </p>
|
||||||
* <p>
|
* <p>
|
||||||
* Note also that there are no memory limits to the size of the buffer, thus
|
* Note also that there are no memory limits to the size of the buffer, thus
|
||||||
* this handler can represent an unbounded memory commitment if the content
|
* this handler can represent an unbounded memory commitment if the content
|
||||||
|
@ -57,7 +59,7 @@ import org.slf4j.LoggerFactory;
|
||||||
*/
|
*/
|
||||||
public class BufferedResponseHandler extends HandlerWrapper
|
public class BufferedResponseHandler extends HandlerWrapper
|
||||||
{
|
{
|
||||||
static final Logger LOG = LoggerFactory.getLogger(BufferedResponseHandler.class);
|
private static final Logger LOG = LoggerFactory.getLogger(BufferedResponseHandler.class);
|
||||||
|
|
||||||
private final IncludeExclude<String> _methods = new IncludeExclude<>();
|
private final IncludeExclude<String> _methods = new IncludeExclude<>();
|
||||||
private final IncludeExclude<String> _paths = new IncludeExclude<>(PathSpecSet.class);
|
private final IncludeExclude<String> _paths = new IncludeExclude<>(PathSpecSet.class);
|
||||||
|
@ -65,10 +67,7 @@ public class BufferedResponseHandler extends HandlerWrapper
|
||||||
|
|
||||||
public BufferedResponseHandler()
|
public BufferedResponseHandler()
|
||||||
{
|
{
|
||||||
// include only GET requests
|
|
||||||
|
|
||||||
_methods.include(HttpMethod.GET.asString());
|
_methods.include(HttpMethod.GET.asString());
|
||||||
// Exclude images, aduio and video from buffering
|
|
||||||
for (String type : MimeTypes.getKnownMimeTypes())
|
for (String type : MimeTypes.getKnownMimeTypes())
|
||||||
{
|
{
|
||||||
if (type.startsWith("image/") ||
|
if (type.startsWith("image/") ||
|
||||||
|
@ -76,6 +75,8 @@ public class BufferedResponseHandler extends HandlerWrapper
|
||||||
type.startsWith("video/"))
|
type.startsWith("video/"))
|
||||||
_mimeTypes.exclude(type);
|
_mimeTypes.exclude(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("{} mime types {}", this, _mimeTypes);
|
LOG.debug("{} mime types {}", this, _mimeTypes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,66 +95,6 @@ public class BufferedResponseHandler extends HandlerWrapper
|
||||||
return _mimeTypes;
|
return _mimeTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
|
||||||
{
|
|
||||||
final ServletContext context = baseRequest.getServletContext();
|
|
||||||
final String path = baseRequest.getPathInContext();
|
|
||||||
LOG.debug("{} handle {} in {}", this, baseRequest, context);
|
|
||||||
|
|
||||||
HttpOutput out = baseRequest.getResponse().getHttpOutput();
|
|
||||||
|
|
||||||
// Are we already being gzipped?
|
|
||||||
HttpOutput.Interceptor interceptor = out.getInterceptor();
|
|
||||||
while (interceptor != null)
|
|
||||||
{
|
|
||||||
if (interceptor instanceof BufferedInterceptor)
|
|
||||||
{
|
|
||||||
LOG.debug("{} already intercepting {}", this, request);
|
|
||||||
_handler.handle(target, baseRequest, request, response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
interceptor = interceptor.getNextInterceptor();
|
|
||||||
}
|
|
||||||
|
|
||||||
// If not a supported method - no Vary because no matter what client, this URI is always excluded
|
|
||||||
if (!_methods.test(baseRequest.getMethod()))
|
|
||||||
{
|
|
||||||
LOG.debug("{} excluded by method {}", this, request);
|
|
||||||
_handler.handle(target, baseRequest, request, response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If not a supported URI- no Vary because no matter what client, this URI is always excluded
|
|
||||||
// Use pathInfo because this is be
|
|
||||||
if (!isPathBufferable(path))
|
|
||||||
{
|
|
||||||
LOG.debug("{} excluded by path {}", this, request);
|
|
||||||
_handler.handle(target, baseRequest, request, response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the mime type is known from the path, then apply mime type filtering
|
|
||||||
String mimeType = context == null ? MimeTypes.getDefaultMimeByExtension(path) : context.getMimeType(path);
|
|
||||||
if (mimeType != null)
|
|
||||||
{
|
|
||||||
mimeType = MimeTypes.getContentTypeWithoutCharset(mimeType);
|
|
||||||
if (!isMimeTypeBufferable(mimeType))
|
|
||||||
{
|
|
||||||
LOG.debug("{} excluded by path suffix mime type {}", this, request);
|
|
||||||
// handle normally without setting vary header
|
|
||||||
_handler.handle(target, baseRequest, request, response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// install interceptor and handle
|
|
||||||
out.setInterceptor(new BufferedInterceptor(baseRequest.getHttpChannel(), out.getInterceptor()));
|
|
||||||
|
|
||||||
if (_handler != null)
|
|
||||||
_handler.handle(target, baseRequest, request, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean isMimeTypeBufferable(String mimetype)
|
protected boolean isMimeTypeBufferable(String mimetype)
|
||||||
{
|
{
|
||||||
return _mimeTypes.test(mimetype);
|
return _mimeTypes.test(mimetype);
|
||||||
|
@ -167,93 +108,113 @@ public class BufferedResponseHandler extends HandlerWrapper
|
||||||
return _paths.test(requestURI);
|
return _paths.test(requestURI);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class BufferedInterceptor implements HttpOutput.Interceptor
|
protected boolean shouldBuffer(HttpChannel channel, boolean last)
|
||||||
{
|
{
|
||||||
final Interceptor _next;
|
if (last)
|
||||||
final HttpChannel _channel;
|
return false;
|
||||||
final Queue<ByteBuffer> _buffers = new ConcurrentLinkedQueue<>();
|
|
||||||
Boolean _aggregating;
|
|
||||||
ByteBuffer _aggregate;
|
|
||||||
|
|
||||||
public BufferedInterceptor(HttpChannel httpChannel, Interceptor interceptor)
|
Response response = channel.getResponse();
|
||||||
{
|
int status = response.getStatus();
|
||||||
_next = interceptor;
|
if (HttpStatus.hasNoBody(status) || HttpStatus.isRedirection(status))
|
||||||
_channel = httpChannel;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void resetBuffer()
|
|
||||||
{
|
|
||||||
_buffers.clear();
|
|
||||||
_aggregating = null;
|
|
||||||
_aggregate = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(ByteBuffer content, boolean last, Callback callback)
|
|
||||||
{
|
|
||||||
if (LOG.isDebugEnabled())
|
|
||||||
LOG.debug("{} write last={} {}", this, last, BufferUtil.toDetailString(content));
|
|
||||||
// if we are not committed, have to decide if we should aggregate or not
|
|
||||||
if (_aggregating == null)
|
|
||||||
{
|
|
||||||
Response response = _channel.getResponse();
|
|
||||||
int sc = response.getStatus();
|
|
||||||
if (sc > 0 && (sc < 200 || sc == 204 || sc == 205 || sc >= 300))
|
|
||||||
_aggregating = Boolean.FALSE; // No body
|
|
||||||
else
|
|
||||||
{
|
|
||||||
String ct = response.getContentType();
|
String ct = response.getContentType();
|
||||||
if (ct == null)
|
if (ct == null)
|
||||||
_aggregating = Boolean.TRUE;
|
return true;
|
||||||
else
|
|
||||||
{
|
|
||||||
ct = MimeTypes.getContentTypeWithoutCharset(ct);
|
ct = MimeTypes.getContentTypeWithoutCharset(ct);
|
||||||
_aggregating = isMimeTypeBufferable(StringUtil.asciiToLowerCase(ct));
|
return isMimeTypeBufferable(StringUtil.asciiToLowerCase(ct));
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we are not aggregating, then handle normally
|
@Override
|
||||||
if (!_aggregating.booleanValue())
|
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||||
{
|
{
|
||||||
getNextInterceptor().write(content, last, callback);
|
final ServletContext context = baseRequest.getServletContext();
|
||||||
|
final String path = baseRequest.getPathInContext();
|
||||||
|
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug("{} handle {} in {}", this, baseRequest, context);
|
||||||
|
|
||||||
|
// Are we already buffering?
|
||||||
|
HttpOutput out = baseRequest.getResponse().getHttpOutput();
|
||||||
|
HttpOutput.Interceptor interceptor = out.getInterceptor();
|
||||||
|
while (interceptor != null)
|
||||||
|
{
|
||||||
|
if (interceptor instanceof BufferedInterceptor)
|
||||||
|
{
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug("{} already intercepting {}", this, request);
|
||||||
|
_handler.handle(target, baseRequest, request, response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
interceptor = interceptor.getNextInterceptor();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not a supported method this URI is always excluded.
|
||||||
|
if (!_methods.test(baseRequest.getMethod()))
|
||||||
|
{
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug("{} excluded by method {}", this, request);
|
||||||
|
_handler.handle(target, baseRequest, request, response);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If last
|
// If not a supported path this URI is always excluded.
|
||||||
if (last)
|
if (!isPathBufferable(path))
|
||||||
{
|
|
||||||
// Add the current content to the buffer list without a copy
|
|
||||||
if (BufferUtil.length(content) > 0)
|
|
||||||
_buffers.add(content);
|
|
||||||
|
|
||||||
if (LOG.isDebugEnabled())
|
|
||||||
LOG.debug("{} committing {}", this, _buffers.size());
|
|
||||||
commit(_buffers, callback);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("{} aggregating", this);
|
LOG.debug("{} excluded by path {}", this, request);
|
||||||
|
_handler.handle(target, baseRequest, request, response);
|
||||||
// Aggregate the content into buffer chain
|
return;
|
||||||
while (BufferUtil.hasContent(content))
|
|
||||||
{
|
|
||||||
// Do we need a new aggregate buffer
|
|
||||||
if (BufferUtil.space(_aggregate) == 0)
|
|
||||||
{
|
|
||||||
int size = Math.max(_channel.getHttpConfiguration().getOutputBufferSize(), BufferUtil.length(content));
|
|
||||||
_aggregate = BufferUtil.allocate(size); // TODO use a buffer pool
|
|
||||||
_buffers.add(_aggregate);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BufferUtil.append(_aggregate, content);
|
// If the mime type is known from the path then apply mime type filtering.
|
||||||
|
String mimeType = context == null ? MimeTypes.getDefaultMimeByExtension(path) : context.getMimeType(path);
|
||||||
|
if (mimeType != null)
|
||||||
|
{
|
||||||
|
mimeType = MimeTypes.getContentTypeWithoutCharset(mimeType);
|
||||||
|
if (!isMimeTypeBufferable(mimeType))
|
||||||
|
{
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug("{} excluded by path suffix mime type {}", this, request);
|
||||||
|
|
||||||
|
// handle normally without setting vary header
|
||||||
|
_handler.handle(target, baseRequest, request, response);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
callback.succeeded();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Install buffered interceptor and handle.
|
||||||
|
out.setInterceptor(newBufferedInterceptor(baseRequest.getHttpChannel(), out.getInterceptor()));
|
||||||
|
if (_handler != null)
|
||||||
|
_handler.handle(target, baseRequest, request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected BufferedInterceptor newBufferedInterceptor(HttpChannel httpChannel, Interceptor interceptor)
|
||||||
|
{
|
||||||
|
return new ArrayBufferedInterceptor(httpChannel, interceptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An {@link HttpOutput.Interceptor} which is created by {@link #newBufferedInterceptor(HttpChannel, Interceptor)}
|
||||||
|
* and is used by the implementation to buffer outgoing content.
|
||||||
|
*/
|
||||||
|
protected interface BufferedInterceptor extends HttpOutput.Interceptor
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ArrayBufferedInterceptor implements BufferedInterceptor
|
||||||
|
{
|
||||||
|
private final Interceptor _next;
|
||||||
|
private final HttpChannel _channel;
|
||||||
|
private final Queue<ByteBuffer> _buffers = new ArrayDeque<>();
|
||||||
|
private Boolean _aggregating;
|
||||||
|
private ByteBuffer _aggregate;
|
||||||
|
|
||||||
|
public ArrayBufferedInterceptor(HttpChannel httpChannel, Interceptor interceptor)
|
||||||
|
{
|
||||||
|
_next = interceptor;
|
||||||
|
_channel = httpChannel;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -262,21 +223,82 @@ public class BufferedResponseHandler extends HandlerWrapper
|
||||||
return _next;
|
return _next;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void commit(Queue<ByteBuffer> buffers, Callback callback)
|
@Override
|
||||||
|
public void resetBuffer()
|
||||||
{
|
{
|
||||||
// If only 1 buffer
|
_buffers.clear();
|
||||||
if (_buffers.size() == 0)
|
_aggregating = null;
|
||||||
getNextInterceptor().write(BufferUtil.EMPTY_BUFFER, true, callback);
|
_aggregate = null;
|
||||||
else if (_buffers.size() == 1)
|
BufferedInterceptor.super.resetBuffer();
|
||||||
// just flush it with the last callback
|
}
|
||||||
getNextInterceptor().write(_buffers.remove(), true, callback);
|
|
||||||
|
@Override
|
||||||
|
public void write(ByteBuffer content, boolean last, Callback callback)
|
||||||
|
{
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug("{} write last={} {}", this, last, BufferUtil.toDetailString(content));
|
||||||
|
|
||||||
|
// If we are not committed, have to decide if we should aggregate or not.
|
||||||
|
if (_aggregating == null)
|
||||||
|
_aggregating = shouldBuffer(_channel, last);
|
||||||
|
|
||||||
|
// If we are not aggregating, then handle normally.
|
||||||
|
if (!_aggregating)
|
||||||
|
{
|
||||||
|
getNextInterceptor().write(content, last, callback);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (last)
|
||||||
|
{
|
||||||
|
// Add the current content to the buffer list without a copy.
|
||||||
|
if (BufferUtil.length(content) > 0)
|
||||||
|
_buffers.offer(content);
|
||||||
|
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug("{} committing {}", this, _buffers.size());
|
||||||
|
commit(callback);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Create an iterating callback to do the writing
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug("{} aggregating", this);
|
||||||
|
|
||||||
|
// Aggregate the content into buffer chain.
|
||||||
|
while (BufferUtil.hasContent(content))
|
||||||
|
{
|
||||||
|
// Do we need a new aggregate buffer.
|
||||||
|
if (BufferUtil.space(_aggregate) == 0)
|
||||||
|
{
|
||||||
|
// TODO: use a buffer pool always allocating with outputBufferSize to avoid polluting the ByteBufferPool.
|
||||||
|
int size = Math.max(_channel.getHttpConfiguration().getOutputBufferSize(), BufferUtil.length(content));
|
||||||
|
_aggregate = BufferUtil.allocate(size);
|
||||||
|
_buffers.offer(_aggregate);
|
||||||
|
}
|
||||||
|
|
||||||
|
BufferUtil.append(_aggregate, content);
|
||||||
|
}
|
||||||
|
callback.succeeded();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void commit(Callback callback)
|
||||||
|
{
|
||||||
|
if (_buffers.size() == 0)
|
||||||
|
{
|
||||||
|
getNextInterceptor().write(BufferUtil.EMPTY_BUFFER, true, callback);
|
||||||
|
}
|
||||||
|
else if (_buffers.size() == 1)
|
||||||
|
{
|
||||||
|
getNextInterceptor().write(_buffers.poll(), true, callback);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Create an iterating callback to do the writing.
|
||||||
IteratingCallback icb = new IteratingCallback()
|
IteratingCallback icb = new IteratingCallback()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
protected Action process() throws Exception
|
protected Action process()
|
||||||
{
|
{
|
||||||
ByteBuffer buffer = _buffers.poll();
|
ByteBuffer buffer = _buffers.poll();
|
||||||
if (buffer == null)
|
if (buffer == null)
|
||||||
|
@ -289,14 +311,14 @@ public class BufferedResponseHandler extends HandlerWrapper
|
||||||
@Override
|
@Override
|
||||||
protected void onCompleteSuccess()
|
protected void onCompleteSuccess()
|
||||||
{
|
{
|
||||||
// Signal last callback
|
// Signal last callback.
|
||||||
callback.succeeded();
|
callback.succeeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCompleteFailure(Throwable cause)
|
protected void onCompleteFailure(Throwable cause)
|
||||||
{
|
{
|
||||||
// Signal last callback
|
// Signal last callback.
|
||||||
callback.failed(cause);
|
callback.failed(cause);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,232 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// 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.server.handler;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.StandardOpenOption;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.server.HttpChannel;
|
||||||
|
import org.eclipse.jetty.server.HttpOutput.Interceptor;
|
||||||
|
import org.eclipse.jetty.util.BufferUtil;
|
||||||
|
import org.eclipse.jetty.util.Callback;
|
||||||
|
import org.eclipse.jetty.util.IO;
|
||||||
|
import org.eclipse.jetty.util.IteratingCallback;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* A Handler that can apply a {@link org.eclipse.jetty.server.HttpOutput.Interceptor}
|
||||||
|
* mechanism to buffer the entire response content until the output is closed.
|
||||||
|
* This allows the commit to be delayed until the response is complete and thus
|
||||||
|
* headers and response status can be changed while writing the body.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Note that the decision to buffer is influenced by the headers and status at the
|
||||||
|
* first write, and thus subsequent changes to those headers will not influence the
|
||||||
|
* decision to buffer or not.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Note also that there are no memory limits to the size of the buffer, thus
|
||||||
|
* this handler can represent an unbounded memory commitment if the content
|
||||||
|
* generated can also be unbounded.
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
public class FileBufferedResponseHandler extends BufferedResponseHandler
|
||||||
|
{
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(FileBufferedResponseHandler.class);
|
||||||
|
|
||||||
|
private Path _tempDir = new File(System.getProperty("java.io.tmpdir")).toPath();
|
||||||
|
|
||||||
|
public Path getTempDir()
|
||||||
|
{
|
||||||
|
return _tempDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTempDir(Path tempDir)
|
||||||
|
{
|
||||||
|
_tempDir = Objects.requireNonNull(tempDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected BufferedInterceptor newBufferedInterceptor(HttpChannel httpChannel, Interceptor interceptor)
|
||||||
|
{
|
||||||
|
return new FileBufferedInterceptor(httpChannel, interceptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FileBufferedInterceptor implements BufferedResponseHandler.BufferedInterceptor
|
||||||
|
{
|
||||||
|
private static final int MAX_MAPPED_BUFFER_SIZE = Integer.MAX_VALUE / 2;
|
||||||
|
|
||||||
|
private final Interceptor _next;
|
||||||
|
private final HttpChannel _channel;
|
||||||
|
private Boolean _aggregating;
|
||||||
|
private Path _filePath;
|
||||||
|
private OutputStream _fileOutputStream;
|
||||||
|
|
||||||
|
public FileBufferedInterceptor(HttpChannel httpChannel, Interceptor interceptor)
|
||||||
|
{
|
||||||
|
_next = interceptor;
|
||||||
|
_channel = httpChannel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Interceptor getNextInterceptor()
|
||||||
|
{
|
||||||
|
return _next;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void resetBuffer()
|
||||||
|
{
|
||||||
|
dispose();
|
||||||
|
BufferedInterceptor.super.resetBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void dispose()
|
||||||
|
{
|
||||||
|
IO.close(_fileOutputStream);
|
||||||
|
_fileOutputStream = null;
|
||||||
|
_aggregating = null;
|
||||||
|
|
||||||
|
if (_filePath != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Files.delete(_filePath);
|
||||||
|
}
|
||||||
|
catch (Throwable t)
|
||||||
|
{
|
||||||
|
LOG.warn("Could not delete file {}", _filePath, t);
|
||||||
|
}
|
||||||
|
_filePath = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(ByteBuffer content, boolean last, Callback callback)
|
||||||
|
{
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug("{} write last={} {}", this, last, BufferUtil.toDetailString(content));
|
||||||
|
|
||||||
|
// If we are not committed, must decide if we should aggregate or not.
|
||||||
|
if (_aggregating == null)
|
||||||
|
_aggregating = shouldBuffer(_channel, last);
|
||||||
|
|
||||||
|
// If we are not aggregating, then handle normally.
|
||||||
|
if (!_aggregating)
|
||||||
|
{
|
||||||
|
getNextInterceptor().write(content, last, callback);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug("{} aggregating", this);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (BufferUtil.hasContent(content))
|
||||||
|
aggregate(content);
|
||||||
|
}
|
||||||
|
catch (Throwable t)
|
||||||
|
{
|
||||||
|
dispose();
|
||||||
|
callback.failed(t);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (last)
|
||||||
|
commit(callback);
|
||||||
|
else
|
||||||
|
callback.succeeded();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void aggregate(ByteBuffer content) throws IOException
|
||||||
|
{
|
||||||
|
if (_fileOutputStream == null)
|
||||||
|
{
|
||||||
|
// Create a new OutputStream to a file.
|
||||||
|
_filePath = Files.createTempFile(_tempDir, "BufferedResponse", "");
|
||||||
|
_fileOutputStream = Files.newOutputStream(_filePath, StandardOpenOption.WRITE);
|
||||||
|
}
|
||||||
|
|
||||||
|
BufferUtil.writeTo(content, _fileOutputStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void commit(Callback callback)
|
||||||
|
{
|
||||||
|
if (_fileOutputStream == null)
|
||||||
|
{
|
||||||
|
// We have no content to write, signal next interceptor that we are finished.
|
||||||
|
getNextInterceptor().write(BufferUtil.EMPTY_BUFFER, true, callback);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_fileOutputStream.close();
|
||||||
|
_fileOutputStream = null;
|
||||||
|
}
|
||||||
|
catch (Throwable t)
|
||||||
|
{
|
||||||
|
dispose();
|
||||||
|
callback.failed(t);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create an iterating callback to do the writing
|
||||||
|
IteratingCallback icb = new IteratingCallback()
|
||||||
|
{
|
||||||
|
private final long fileLength = _filePath.toFile().length();
|
||||||
|
private long _pos = 0;
|
||||||
|
private boolean _last = false;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Action process() throws Exception
|
||||||
|
{
|
||||||
|
if (_last)
|
||||||
|
return Action.SUCCEEDED;
|
||||||
|
|
||||||
|
long len = Math.min(MAX_MAPPED_BUFFER_SIZE, fileLength - _pos);
|
||||||
|
_last = (_pos + len == fileLength);
|
||||||
|
ByteBuffer buffer = BufferUtil.toMappedBuffer(_filePath, _pos, len);
|
||||||
|
getNextInterceptor().write(buffer, _last, this);
|
||||||
|
_pos += len;
|
||||||
|
return Action.SCHEDULED;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCompleteSuccess()
|
||||||
|
{
|
||||||
|
dispose();
|
||||||
|
callback.succeeded();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCompleteFailure(Throwable cause)
|
||||||
|
{
|
||||||
|
dispose();
|
||||||
|
callback.failed(cause);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
icb.iterate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,609 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// 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.server;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.NoSuchFileException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.ServletOutputStream;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.http.HttpStatus;
|
||||||
|
import org.eclipse.jetty.http.HttpTester;
|
||||||
|
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||||
|
import org.eclipse.jetty.server.handler.FileBufferedResponseHandler;
|
||||||
|
import org.eclipse.jetty.server.handler.HandlerCollection;
|
||||||
|
import org.eclipse.jetty.toolchain.test.FS;
|
||||||
|
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
||||||
|
import org.eclipse.jetty.util.Callback;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.hamcrest.Matchers.containsString;
|
||||||
|
import static org.hamcrest.Matchers.instanceOf;
|
||||||
|
import static org.hamcrest.Matchers.is;
|
||||||
|
import static org.hamcrest.Matchers.not;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
public class FileBufferedResponseHandlerTest
|
||||||
|
{
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(FileBufferedResponseHandlerTest.class);
|
||||||
|
|
||||||
|
private Server _server;
|
||||||
|
private LocalConnector _localConnector;
|
||||||
|
private ServerConnector _serverConnector;
|
||||||
|
private Path _testDir;
|
||||||
|
private FileBufferedResponseHandler _bufferedHandler;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void before() throws Exception
|
||||||
|
{
|
||||||
|
_testDir = MavenTestingUtils.getTargetTestingPath(FileBufferedResponseHandlerTest.class.getName());
|
||||||
|
FS.ensureDirExists(_testDir);
|
||||||
|
|
||||||
|
_server = new Server();
|
||||||
|
HttpConfiguration config = new HttpConfiguration();
|
||||||
|
config.setOutputBufferSize(1024);
|
||||||
|
config.setOutputAggregationSize(256);
|
||||||
|
|
||||||
|
_localConnector = new LocalConnector(_server, new HttpConnectionFactory(config));
|
||||||
|
_localConnector.setIdleTimeout(Duration.ofMinutes(1).toMillis());
|
||||||
|
_server.addConnector(_localConnector);
|
||||||
|
_serverConnector = new ServerConnector(_server, new HttpConnectionFactory(config));
|
||||||
|
_server.addConnector(_serverConnector);
|
||||||
|
|
||||||
|
_bufferedHandler = new FileBufferedResponseHandler();
|
||||||
|
_bufferedHandler.setTempDir(_testDir);
|
||||||
|
_bufferedHandler.getPathIncludeExclude().include("/include/*");
|
||||||
|
_bufferedHandler.getPathIncludeExclude().exclude("*.exclude");
|
||||||
|
_bufferedHandler.getMimeIncludeExclude().exclude("text/excluded");
|
||||||
|
_server.setHandler(_bufferedHandler);
|
||||||
|
|
||||||
|
FS.ensureEmpty(_testDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
public void after() throws Exception
|
||||||
|
{
|
||||||
|
_server.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPathNotIncluded() throws Exception
|
||||||
|
{
|
||||||
|
_bufferedHandler.setHandler(new AbstractHandler()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||||
|
{
|
||||||
|
baseRequest.setHandled(true);
|
||||||
|
response.setBufferSize(10);
|
||||||
|
PrintWriter writer = response.getWriter();
|
||||||
|
writer.println("a string larger than the buffer size");
|
||||||
|
writer.println("Committed: " + response.isCommitted());
|
||||||
|
writer.println("NumFiles: " + getNumFiles());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
_server.start();
|
||||||
|
String rawResponse = _localConnector.getResponse("GET /path HTTP/1.1\r\nHost: localhost\r\n\r\n");
|
||||||
|
HttpTester.Response response = HttpTester.parseResponse(rawResponse);
|
||||||
|
String responseContent = response.getContent();
|
||||||
|
|
||||||
|
// The response was committed after the first write and we never created a file to buffer the response into.
|
||||||
|
assertThat(response.getStatus(), is(HttpStatus.OK_200));
|
||||||
|
assertThat(responseContent, containsString("Committed: true"));
|
||||||
|
assertThat(responseContent, containsString("NumFiles: 0"));
|
||||||
|
assertThat(getNumFiles(), is(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIncludedByPath() throws Exception
|
||||||
|
{
|
||||||
|
_bufferedHandler.setHandler(new AbstractHandler()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||||
|
{
|
||||||
|
baseRequest.setHandled(true);
|
||||||
|
response.setBufferSize(10);
|
||||||
|
PrintWriter writer = response.getWriter();
|
||||||
|
writer.println("a string larger than the buffer size");
|
||||||
|
writer.println("Committed: " + response.isCommitted());
|
||||||
|
writer.println("NumFiles: " + getNumFiles());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
_server.start();
|
||||||
|
String rawResponse = _localConnector.getResponse("GET /include/path HTTP/1.1\r\nHost: localhost\r\n\r\n");
|
||||||
|
HttpTester.Response response = HttpTester.parseResponse(rawResponse);
|
||||||
|
String responseContent = response.getContent();
|
||||||
|
|
||||||
|
// The response was not committed after the first write and a file was created to buffer the response.
|
||||||
|
assertThat(response.getStatus(), is(HttpStatus.OK_200));
|
||||||
|
assertThat(responseContent, containsString("Committed: false"));
|
||||||
|
assertThat(responseContent, containsString("NumFiles: 1"));
|
||||||
|
assertThat(getNumFiles(), is(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExcludedByPath() throws Exception
|
||||||
|
{
|
||||||
|
_bufferedHandler.setHandler(new AbstractHandler()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||||
|
{
|
||||||
|
baseRequest.setHandled(true);
|
||||||
|
response.setBufferSize(10);
|
||||||
|
PrintWriter writer = response.getWriter();
|
||||||
|
writer.println("a string larger than the buffer size");
|
||||||
|
writer.println("Committed: " + response.isCommitted());
|
||||||
|
writer.println("NumFiles: " + getNumFiles());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
_server.start();
|
||||||
|
String rawResponse = _localConnector.getResponse("GET /include/path.exclude HTTP/1.1\r\nHost: localhost\r\n\r\n");
|
||||||
|
HttpTester.Response response = HttpTester.parseResponse(rawResponse);
|
||||||
|
String responseContent = response.getContent();
|
||||||
|
|
||||||
|
// The response was committed after the first write and we never created a file to buffer the response into.
|
||||||
|
assertThat(response.getStatus(), is(HttpStatus.OK_200));
|
||||||
|
assertThat(responseContent, containsString("Committed: true"));
|
||||||
|
assertThat(responseContent, containsString("NumFiles: 0"));
|
||||||
|
assertThat(getNumFiles(), is(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExcludedByMime() throws Exception
|
||||||
|
{
|
||||||
|
String excludedMimeType = "text/excluded";
|
||||||
|
_bufferedHandler.setHandler(new AbstractHandler()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||||
|
{
|
||||||
|
baseRequest.setHandled(true);
|
||||||
|
response.setContentType(excludedMimeType);
|
||||||
|
response.setBufferSize(10);
|
||||||
|
PrintWriter writer = response.getWriter();
|
||||||
|
writer.println("a string larger than the buffer size");
|
||||||
|
writer.println("Committed: " + response.isCommitted());
|
||||||
|
writer.println("NumFiles: " + getNumFiles());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
_server.start();
|
||||||
|
String rawResponse = _localConnector.getResponse("GET /include/path HTTP/1.1\r\nHost: localhost\r\n\r\n");
|
||||||
|
HttpTester.Response response = HttpTester.parseResponse(rawResponse);
|
||||||
|
String responseContent = response.getContent();
|
||||||
|
|
||||||
|
// The response was committed after the first write and we never created a file to buffer the response into.
|
||||||
|
assertThat(response.getStatus(), is(HttpStatus.OK_200));
|
||||||
|
assertThat(responseContent, containsString("Committed: true"));
|
||||||
|
assertThat(responseContent, containsString("NumFiles: 0"));
|
||||||
|
assertThat(getNumFiles(), is(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFlushed() throws Exception
|
||||||
|
{
|
||||||
|
_bufferedHandler.setHandler(new AbstractHandler()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||||
|
{
|
||||||
|
baseRequest.setHandled(true);
|
||||||
|
response.setBufferSize(1024);
|
||||||
|
PrintWriter writer = response.getWriter();
|
||||||
|
writer.println("a string smaller than the buffer size");
|
||||||
|
writer.println("NumFilesBeforeFlush: " + getNumFiles());
|
||||||
|
writer.flush();
|
||||||
|
writer.println("Committed: " + response.isCommitted());
|
||||||
|
writer.println("NumFiles: " + getNumFiles());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
_server.start();
|
||||||
|
String rawResponse = _localConnector.getResponse("GET /include/path HTTP/1.1\r\nHost: localhost\r\n\r\n");
|
||||||
|
HttpTester.Response response = HttpTester.parseResponse(rawResponse);
|
||||||
|
String responseContent = response.getContent();
|
||||||
|
|
||||||
|
// The response was not committed after the buffer was flushed and a file was created to buffer the response.
|
||||||
|
assertThat(response.getStatus(), is(HttpStatus.OK_200));
|
||||||
|
assertThat(responseContent, containsString("NumFilesBeforeFlush: 0"));
|
||||||
|
assertThat(responseContent, containsString("Committed: false"));
|
||||||
|
assertThat(responseContent, containsString("NumFiles: 1"));
|
||||||
|
assertThat(getNumFiles(), is(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testClosed() throws Exception
|
||||||
|
{
|
||||||
|
_bufferedHandler.setHandler(new AbstractHandler()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||||
|
{
|
||||||
|
baseRequest.setHandled(true);
|
||||||
|
response.setBufferSize(10);
|
||||||
|
PrintWriter writer = response.getWriter();
|
||||||
|
writer.println("a string larger than the buffer size");
|
||||||
|
writer.println("NumFiles: " + getNumFiles());
|
||||||
|
writer.close();
|
||||||
|
writer.println("writtenAfterClose");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
_server.start();
|
||||||
|
String rawResponse = _localConnector.getResponse("GET /include/path HTTP/1.1\r\nHost: localhost\r\n\r\n");
|
||||||
|
HttpTester.Response response = HttpTester.parseResponse(rawResponse);
|
||||||
|
String responseContent = response.getContent();
|
||||||
|
|
||||||
|
// The content written after close was not sent.
|
||||||
|
assertThat(response.getStatus(), is(HttpStatus.OK_200));
|
||||||
|
assertThat(responseContent, not(containsString("writtenAfterClose")));
|
||||||
|
assertThat(responseContent, containsString("NumFiles: 1"));
|
||||||
|
assertThat(getNumFiles(), is(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBufferSizeBig() throws Exception
|
||||||
|
{
|
||||||
|
int bufferSize = 4096;
|
||||||
|
String largeContent = generateContent(bufferSize - 64);
|
||||||
|
_bufferedHandler.setHandler(new AbstractHandler()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||||
|
{
|
||||||
|
baseRequest.setHandled(true);
|
||||||
|
response.setBufferSize(bufferSize);
|
||||||
|
PrintWriter writer = response.getWriter();
|
||||||
|
writer.println(largeContent);
|
||||||
|
writer.println("Committed: " + response.isCommitted());
|
||||||
|
writer.println("NumFiles: " + getNumFiles());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
_server.start();
|
||||||
|
String rawResponse = _localConnector.getResponse("GET /include/path HTTP/1.1\r\nHost: localhost\r\n\r\n");
|
||||||
|
HttpTester.Response response = HttpTester.parseResponse(rawResponse);
|
||||||
|
String responseContent = response.getContent();
|
||||||
|
|
||||||
|
// The content written was not buffered as a file as it was less than the buffer size.
|
||||||
|
assertThat(response.getStatus(), is(HttpStatus.OK_200));
|
||||||
|
assertThat(responseContent, not(containsString("writtenAfterClose")));
|
||||||
|
assertThat(responseContent, containsString("Committed: false"));
|
||||||
|
assertThat(responseContent, containsString("NumFiles: 0"));
|
||||||
|
assertThat(getNumFiles(), is(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFlushEmpty() throws Exception
|
||||||
|
{
|
||||||
|
_bufferedHandler.setHandler(new AbstractHandler()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||||
|
{
|
||||||
|
baseRequest.setHandled(true);
|
||||||
|
response.setBufferSize(1024);
|
||||||
|
PrintWriter writer = response.getWriter();
|
||||||
|
writer.flush();
|
||||||
|
int numFiles = getNumFiles();
|
||||||
|
writer.println("NumFiles: " + numFiles);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
_server.start();
|
||||||
|
String rawResponse = _localConnector.getResponse("GET /include/path HTTP/1.1\r\nHost: localhost\r\n\r\n");
|
||||||
|
HttpTester.Response response = HttpTester.parseResponse(rawResponse);
|
||||||
|
String responseContent = response.getContent();
|
||||||
|
|
||||||
|
// The flush should not create the file unless there is content to write.
|
||||||
|
assertThat(response.getStatus(), is(HttpStatus.OK_200));
|
||||||
|
assertThat(responseContent, containsString("NumFiles: 0"));
|
||||||
|
assertThat(getNumFiles(), is(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReset() throws Exception
|
||||||
|
{
|
||||||
|
_bufferedHandler.setHandler(new AbstractHandler()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||||
|
{
|
||||||
|
baseRequest.setHandled(true);
|
||||||
|
response.setBufferSize(8);
|
||||||
|
PrintWriter writer = response.getWriter();
|
||||||
|
writer.println("THIS WILL BE RESET");
|
||||||
|
writer.flush();
|
||||||
|
writer.println("THIS WILL BE RESET");
|
||||||
|
int numFilesBeforeReset = getNumFiles();
|
||||||
|
response.resetBuffer();
|
||||||
|
int numFilesAfterReset = getNumFiles();
|
||||||
|
|
||||||
|
writer.println("NumFilesBeforeReset: " + numFilesBeforeReset);
|
||||||
|
writer.println("NumFilesAfterReset: " + numFilesAfterReset);
|
||||||
|
writer.println("a string larger than the buffer size");
|
||||||
|
writer.println("NumFilesAfterWrite: " + getNumFiles());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
_server.start();
|
||||||
|
String rawResponse = _localConnector.getResponse("GET /include/path HTTP/1.1\r\nHost: localhost\r\n\r\n");
|
||||||
|
HttpTester.Response response = HttpTester.parseResponse(rawResponse);
|
||||||
|
String responseContent = response.getContent();
|
||||||
|
|
||||||
|
// Resetting the response buffer will delete the file.
|
||||||
|
assertThat(response.getStatus(), is(HttpStatus.OK_200));
|
||||||
|
assertThat(responseContent, not(containsString("THIS WILL BE RESET")));
|
||||||
|
assertThat(responseContent, containsString("NumFilesBeforeReset: 1"));
|
||||||
|
assertThat(responseContent, containsString("NumFilesAfterReset: 0"));
|
||||||
|
assertThat(responseContent, containsString("NumFilesAfterWrite: 1"));
|
||||||
|
assertThat(getNumFiles(), is(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFileLargerThanMaxInteger() throws Exception
|
||||||
|
{
|
||||||
|
long fileSize = Integer.MAX_VALUE + 1234L;
|
||||||
|
byte[] bytes = randomBytes(1024 * 1024);
|
||||||
|
|
||||||
|
_bufferedHandler.setHandler(new AbstractHandler()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||||
|
{
|
||||||
|
baseRequest.setHandled(true);
|
||||||
|
ServletOutputStream outputStream = response.getOutputStream();
|
||||||
|
|
||||||
|
long written = 0;
|
||||||
|
while (written < fileSize)
|
||||||
|
{
|
||||||
|
int length = Math.toIntExact(Math.min(bytes.length, fileSize - written));
|
||||||
|
outputStream.write(bytes, 0, length);
|
||||||
|
written += length;
|
||||||
|
}
|
||||||
|
outputStream.flush();
|
||||||
|
|
||||||
|
response.setHeader("NumFiles", Integer.toString(getNumFiles()));
|
||||||
|
response.setHeader("FileSize", Long.toString(getFileSize()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
_server.start();
|
||||||
|
|
||||||
|
AtomicLong received = new AtomicLong();
|
||||||
|
HttpTester.Response response = new HttpTester.Response()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public boolean content(ByteBuffer ref)
|
||||||
|
{
|
||||||
|
// Verify the content is what was sent.
|
||||||
|
while (ref.hasRemaining())
|
||||||
|
{
|
||||||
|
byte byteFromBuffer = ref.get();
|
||||||
|
long totalReceived = received.getAndIncrement();
|
||||||
|
int bytesIndex = (int)(totalReceived % bytes.length);
|
||||||
|
byte byteFromArray = bytes[bytesIndex];
|
||||||
|
|
||||||
|
if (byteFromBuffer != byteFromArray)
|
||||||
|
{
|
||||||
|
LOG.warn("Mismatch at index {} received bytes {}, {}!={}", bytesIndex, totalReceived, byteFromBuffer, byteFromArray, new IllegalStateException());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
try (Socket socket = new Socket("localhost", _serverConnector.getLocalPort()))
|
||||||
|
{
|
||||||
|
OutputStream output = socket.getOutputStream();
|
||||||
|
String request = "GET /include/path HTTP/1.1\r\nHost: localhost\r\n\r\n";
|
||||||
|
output.write(request.getBytes(StandardCharsets.UTF_8));
|
||||||
|
output.flush();
|
||||||
|
|
||||||
|
HttpTester.Input input = HttpTester.from(socket.getInputStream());
|
||||||
|
HttpTester.parseResponse(input, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertTrue(response.isComplete());
|
||||||
|
assertThat(response.get("NumFiles"), is("1"));
|
||||||
|
assertThat(response.get("FileSize"), is(Long.toString(fileSize)));
|
||||||
|
assertThat(received.get(), is(fileSize));
|
||||||
|
assertThat(getNumFiles(), is(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNextInterceptorFailed() throws Exception
|
||||||
|
{
|
||||||
|
AbstractHandler failingInterceptorHandler = new AbstractHandler()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||||
|
{
|
||||||
|
HttpOutput httpOutput = baseRequest.getResponse().getHttpOutput();
|
||||||
|
HttpOutput.Interceptor nextInterceptor = httpOutput.getInterceptor();
|
||||||
|
httpOutput.setInterceptor(new HttpOutput.Interceptor()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void write(ByteBuffer content, boolean last, Callback callback)
|
||||||
|
{
|
||||||
|
callback.failed(new Throwable("intentionally throwing from interceptor"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HttpOutput.Interceptor getNextInterceptor()
|
||||||
|
{
|
||||||
|
return nextInterceptor;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_server.setHandler(new HandlerCollection(failingInterceptorHandler, _server.getHandler()));
|
||||||
|
CompletableFuture<Throwable> errorFuture = new CompletableFuture<>();
|
||||||
|
_bufferedHandler.setHandler(new AbstractHandler()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||||
|
{
|
||||||
|
baseRequest.setHandled(true);
|
||||||
|
byte[] chunk1 = "this content will ".getBytes();
|
||||||
|
byte[] chunk2 = "be buffered in a file".getBytes();
|
||||||
|
response.setContentLength(chunk1.length + chunk2.length);
|
||||||
|
ServletOutputStream outputStream = response.getOutputStream();
|
||||||
|
|
||||||
|
// Write chunk1 and then flush so it is written to the file.
|
||||||
|
outputStream.write(chunk1);
|
||||||
|
outputStream.flush();
|
||||||
|
assertThat(getNumFiles(), is(1));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// ContentLength is set so it knows this is the last write.
|
||||||
|
// This will cause the file to be written to the next interceptor which will fail.
|
||||||
|
outputStream.write(chunk2);
|
||||||
|
}
|
||||||
|
catch (Throwable t)
|
||||||
|
{
|
||||||
|
errorFuture.complete(t);
|
||||||
|
throw t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
_server.start();
|
||||||
|
String rawResponse = _localConnector.getResponse("GET /include/path HTTP/1.1\r\nHost: localhost\r\n\r\n");
|
||||||
|
HttpTester.Response response = HttpTester.parseResponse(rawResponse);
|
||||||
|
|
||||||
|
// Response was aborted.
|
||||||
|
assertThat(response.getStatus(), is(0));
|
||||||
|
|
||||||
|
// We failed because of the next interceptor.
|
||||||
|
Throwable error = errorFuture.get(5, TimeUnit.SECONDS);
|
||||||
|
assertThat(error.getMessage(), containsString("intentionally throwing from interceptor"));
|
||||||
|
|
||||||
|
// All files were deleted.
|
||||||
|
assertThat(getNumFiles(), is(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFileWriteFailed() throws Exception
|
||||||
|
{
|
||||||
|
// Set the temp directory to an empty directory so that the file cannot be created.
|
||||||
|
File tempDir = MavenTestingUtils.getTargetTestingDir(getClass().getSimpleName());
|
||||||
|
FS.ensureDeleted(tempDir);
|
||||||
|
_bufferedHandler.setTempDir(tempDir.toPath());
|
||||||
|
|
||||||
|
CompletableFuture<Throwable> errorFuture = new CompletableFuture<>();
|
||||||
|
_bufferedHandler.setHandler(new AbstractHandler()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||||
|
{
|
||||||
|
baseRequest.setHandled(true);
|
||||||
|
ServletOutputStream outputStream = response.getOutputStream();
|
||||||
|
byte[] content = "this content will be buffered in a file".getBytes();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Write the content and flush it to the file.
|
||||||
|
// This should throw as it cannot create the file to aggregate into.
|
||||||
|
outputStream.write(content);
|
||||||
|
outputStream.flush();
|
||||||
|
}
|
||||||
|
catch (Throwable t)
|
||||||
|
{
|
||||||
|
errorFuture.complete(t);
|
||||||
|
throw t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
_server.start();
|
||||||
|
String rawResponse = _localConnector.getResponse("GET /include/path HTTP/1.1\r\nHost: localhost\r\n\r\n");
|
||||||
|
HttpTester.Response response = HttpTester.parseResponse(rawResponse);
|
||||||
|
|
||||||
|
// Response was aborted.
|
||||||
|
assertThat(response.getStatus(), is(0));
|
||||||
|
|
||||||
|
// We failed because cannot create the file.
|
||||||
|
Throwable error = errorFuture.get(5, TimeUnit.SECONDS);
|
||||||
|
assertThat(error, instanceOf(NoSuchFileException.class));
|
||||||
|
|
||||||
|
// No files were created.
|
||||||
|
assertThat(getNumFiles(), is(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getNumFiles()
|
||||||
|
{
|
||||||
|
File[] files = _testDir.toFile().listFiles();
|
||||||
|
if (files == null)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return files.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
private long getFileSize()
|
||||||
|
{
|
||||||
|
File[] files = _testDir.toFile().listFiles();
|
||||||
|
assertNotNull(files);
|
||||||
|
assertThat(files.length, is(1));
|
||||||
|
return files[0].length();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String generateContent(int size)
|
||||||
|
{
|
||||||
|
Random random = new Random();
|
||||||
|
StringBuilder stringBuilder = new StringBuilder(size);
|
||||||
|
for (int i = 0; i < size; i++)
|
||||||
|
{
|
||||||
|
stringBuilder.append((char)Math.abs(random.nextInt(0x7F)));
|
||||||
|
}
|
||||||
|
return stringBuilder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("SameParameterValue")
|
||||||
|
private byte[] randomBytes(int size)
|
||||||
|
{
|
||||||
|
byte[] data = new byte[size];
|
||||||
|
new Random().nextBytes(data);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
|
@ -32,6 +32,8 @@ import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
@ -40,6 +42,7 @@ import javax.servlet.http.HttpServletResponse;
|
||||||
import org.eclipse.jetty.http.HttpCompliance;
|
import org.eclipse.jetty.http.HttpCompliance;
|
||||||
import org.eclipse.jetty.http.HttpHeader;
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
import org.eclipse.jetty.http.HttpParser;
|
import org.eclipse.jetty.http.HttpParser;
|
||||||
|
import org.eclipse.jetty.http.HttpStatus;
|
||||||
import org.eclipse.jetty.http.HttpTester;
|
import org.eclipse.jetty.http.HttpTester;
|
||||||
import org.eclipse.jetty.http.MimeTypes;
|
import org.eclipse.jetty.http.MimeTypes;
|
||||||
import org.eclipse.jetty.logging.StacklessLogging;
|
import org.eclipse.jetty.logging.StacklessLogging;
|
||||||
|
@ -47,6 +50,7 @@ import org.eclipse.jetty.server.LocalConnector.LocalEndPoint;
|
||||||
import org.eclipse.jetty.server.handler.AbstractHandler;
|
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||||
import org.eclipse.jetty.server.handler.ErrorHandler;
|
import org.eclipse.jetty.server.handler.ErrorHandler;
|
||||||
import org.eclipse.jetty.util.BufferUtil;
|
import org.eclipse.jetty.util.BufferUtil;
|
||||||
|
import org.eclipse.jetty.util.IO;
|
||||||
import org.hamcrest.Matchers;
|
import org.hamcrest.Matchers;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
@ -59,9 +63,11 @@ import org.slf4j.LoggerFactory;
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.hamcrest.Matchers.containsString;
|
import static org.hamcrest.Matchers.containsString;
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
|
import static org.hamcrest.Matchers.greaterThan;
|
||||||
import static org.hamcrest.Matchers.is;
|
import static org.hamcrest.Matchers.is;
|
||||||
import static org.hamcrest.Matchers.lessThanOrEqualTo;
|
import static org.hamcrest.Matchers.lessThanOrEqualTo;
|
||||||
import static org.hamcrest.Matchers.not;
|
import static org.hamcrest.Matchers.not;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
public class HttpConnectionTest
|
public class HttpConnectionTest
|
||||||
|
@ -1353,6 +1359,68 @@ public class HttpConnectionTest
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBytesIn() throws Exception
|
||||||
|
{
|
||||||
|
String chunk1 = "0123456789ABCDEF";
|
||||||
|
String chunk2 = IntStream.range(0, 64).mapToObj(i -> chunk1).collect(Collectors.joining());
|
||||||
|
long dataLength = chunk1.length() + chunk2.length();
|
||||||
|
server.stop();
|
||||||
|
server.setHandler(new AbstractHandler()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||||
|
{
|
||||||
|
jettyRequest.setHandled(true);
|
||||||
|
IO.copy(request.getInputStream(), IO.getNullStream());
|
||||||
|
|
||||||
|
HttpConnection connection = HttpConnection.getCurrentConnection();
|
||||||
|
long bytesIn = connection.getBytesIn();
|
||||||
|
assertThat(bytesIn, greaterThan(dataLength));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
server.start();
|
||||||
|
|
||||||
|
LocalEndPoint localEndPoint = connector.executeRequest("" +
|
||||||
|
"POST / HTTP/1.1\r\n" +
|
||||||
|
"Host: localhost\r\n" +
|
||||||
|
"Content-Length: " + dataLength + "\r\n" +
|
||||||
|
"\r\n" +
|
||||||
|
chunk1);
|
||||||
|
|
||||||
|
// Wait for the server to block on the read().
|
||||||
|
Thread.sleep(500);
|
||||||
|
|
||||||
|
// Send more content.
|
||||||
|
localEndPoint.addInput(chunk2);
|
||||||
|
|
||||||
|
HttpTester.Response response = HttpTester.parseResponse(localEndPoint.getResponse());
|
||||||
|
assertEquals(response.getStatus(), HttpStatus.OK_200);
|
||||||
|
localEndPoint.close();
|
||||||
|
|
||||||
|
localEndPoint = connector.executeRequest("" +
|
||||||
|
"POST / HTTP/1.1\r\n" +
|
||||||
|
"Host: localhost\r\n" +
|
||||||
|
"Transfer-Encoding: chunked\r\n" +
|
||||||
|
"\r\n" +
|
||||||
|
Integer.toHexString(chunk1.length()) + "\r\n" +
|
||||||
|
chunk1 + "\r\n");
|
||||||
|
|
||||||
|
// Wait for the server to block on the read().
|
||||||
|
Thread.sleep(500);
|
||||||
|
|
||||||
|
// Send more content.
|
||||||
|
localEndPoint.addInput("" +
|
||||||
|
Integer.toHexString(chunk2.length()) + "\r\n" +
|
||||||
|
chunk2 + "\r\n" +
|
||||||
|
"0\r\n" +
|
||||||
|
"\r\n");
|
||||||
|
|
||||||
|
response = HttpTester.parseResponse(localEndPoint.getResponse());
|
||||||
|
assertEquals(response.getStatus(), HttpStatus.OK_200);
|
||||||
|
localEndPoint.close();
|
||||||
|
}
|
||||||
|
|
||||||
private int checkContains(String s, int offset, String c)
|
private int checkContains(String s, int offset, String c)
|
||||||
{
|
{
|
||||||
assertThat(s.substring(offset), Matchers.containsString(c));
|
assertThat(s.substring(offset), Matchers.containsString(c));
|
||||||
|
|
|
@ -28,6 +28,7 @@ import java.security.Principal;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.EnumSet;
|
||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
@ -1733,6 +1734,8 @@ public class RequestTest
|
||||||
"Host: whatever\r\n" +
|
"Host: whatever\r\n" +
|
||||||
"\r\n";
|
"\r\n";
|
||||||
_connector.getBean(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.DEFAULT);
|
_connector.getBean(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.DEFAULT);
|
||||||
|
assertThat(_connector.getResponse(request), startsWith("HTTP/1.1 200"));
|
||||||
|
_connector.getBean(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(new UriCompliance("Test", EnumSet.noneOf(UriCompliance.Violation.class)));
|
||||||
assertThat(_connector.getResponse(request), startsWith("HTTP/1.1 400"));
|
assertThat(_connector.getResponse(request), startsWith("HTTP/1.1 400"));
|
||||||
_connector.getBean(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.LEGACY);
|
_connector.getBean(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.LEGACY);
|
||||||
assertThat(_connector.getResponse(request), startsWith("HTTP/1.1 200"));
|
assertThat(_connector.getResponse(request), startsWith("HTTP/1.1 200"));
|
||||||
|
@ -1740,6 +1743,37 @@ public class RequestTest
|
||||||
assertThat(_connector.getResponse(request), startsWith("HTTP/1.1 200"));
|
assertThat(_connector.getResponse(request), startsWith("HTTP/1.1 200"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAmbiguousPaths() throws Exception
|
||||||
|
{
|
||||||
|
_handler._checker = (request, response) ->
|
||||||
|
{
|
||||||
|
response.getOutputStream().println("servletPath=" + request.getServletPath());
|
||||||
|
response.getOutputStream().println("pathInfo=" + request.getPathInfo());
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
String request = "GET /unnormal/.././path/ambiguous%2f%2e%2e/%2e;/info HTTP/1.0\r\n" +
|
||||||
|
"Host: whatever\r\n" +
|
||||||
|
"\r\n";
|
||||||
|
|
||||||
|
_connector.getBean(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.from(EnumSet.of(
|
||||||
|
UriCompliance.Violation.AMBIGUOUS_PATH_SEPARATOR,
|
||||||
|
UriCompliance.Violation.AMBIGUOUS_PATH_SEGMENT,
|
||||||
|
UriCompliance.Violation.AMBIGUOUS_PATH_PARAMETER)));
|
||||||
|
assertThat(_connector.getResponse(request), Matchers.allOf(
|
||||||
|
startsWith("HTTP/1.1 200"),
|
||||||
|
containsString("pathInfo=/path/info")));
|
||||||
|
|
||||||
|
_connector.getBean(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.from(EnumSet.of(
|
||||||
|
UriCompliance.Violation.AMBIGUOUS_PATH_SEPARATOR,
|
||||||
|
UriCompliance.Violation.AMBIGUOUS_PATH_SEGMENT,
|
||||||
|
UriCompliance.Violation.AMBIGUOUS_PATH_PARAMETER,
|
||||||
|
UriCompliance.Violation.NON_CANONICAL_AMBIGUOUS_PATHS)));
|
||||||
|
assertThat(_connector.getResponse(request), Matchers.allOf(
|
||||||
|
startsWith("HTTP/1.1 200"),
|
||||||
|
containsString("pathInfo=/path/ambiguous/.././info")));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testPushBuilder()
|
public void testPushBuilder()
|
||||||
{
|
{
|
||||||
|
|
|
@ -39,7 +39,7 @@ import org.eclipse.jetty.start.fileinits.TestFileInitializer;
|
||||||
import org.eclipse.jetty.start.fileinits.UriFileInitializer;
|
import org.eclipse.jetty.start.fileinits.UriFileInitializer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build a start configuration in <code>${jetty.base}</code>, including
|
* Build a start configuration in {@code ${jetty.base}}, including
|
||||||
* ini files, directories, and libs. Also handles License management.
|
* ini files, directories, and libs. Also handles License management.
|
||||||
*/
|
*/
|
||||||
public class BaseBuilder
|
public class BaseBuilder
|
||||||
|
@ -47,7 +47,7 @@ public class BaseBuilder
|
||||||
public static interface Config
|
public static interface Config
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Add a module to the start environment in <code>${jetty.base}</code>
|
* Add a module to the start environment in {@code ${jetty.base}}
|
||||||
*
|
*
|
||||||
* @param module the module to add
|
* @param module the module to add
|
||||||
* @param props The properties to substitute into a template
|
* @param props The properties to substitute into a template
|
||||||
|
@ -163,7 +163,7 @@ public class BaseBuilder
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate the files
|
// generate the files
|
||||||
List<FileArg> files = new ArrayList<FileArg>();
|
List<FileArg> files = new ArrayList<>();
|
||||||
AtomicReference<BaseBuilder.Config> builder = new AtomicReference<>();
|
AtomicReference<BaseBuilder.Config> builder = new AtomicReference<>();
|
||||||
AtomicBoolean modified = new AtomicBoolean();
|
AtomicBoolean modified = new AtomicBoolean();
|
||||||
|
|
||||||
|
@ -184,18 +184,21 @@ public class BaseBuilder
|
||||||
if (Files.exists(startd))
|
if (Files.exists(startd))
|
||||||
{
|
{
|
||||||
// Copy start.d files into start.ini
|
// Copy start.d files into start.ini
|
||||||
DirectoryStream.Filter<Path> filter = new DirectoryStream.Filter<Path>()
|
DirectoryStream.Filter<Path> filter = new DirectoryStream.Filter<>()
|
||||||
{
|
{
|
||||||
PathMatcher iniMatcher = PathMatchers.getMatcher("glob:**/start.d/*.ini");
|
private final PathMatcher iniMatcher = PathMatchers.getMatcher("glob:**/start.d/*.ini");
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean accept(Path entry) throws IOException
|
public boolean accept(Path entry)
|
||||||
{
|
{
|
||||||
return iniMatcher.matches(entry);
|
return iniMatcher.matches(entry);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
List<Path> paths = new ArrayList<>();
|
List<Path> paths = new ArrayList<>();
|
||||||
for (Path path : Files.newDirectoryStream(startd, filter))
|
for (Path path : Files.newDirectoryStream(startd, filter))
|
||||||
|
{
|
||||||
paths.add(path);
|
paths.add(path);
|
||||||
|
}
|
||||||
paths.sort(new NaturalSort.Paths());
|
paths.sort(new NaturalSort.Paths());
|
||||||
|
|
||||||
// Read config from start.d
|
// Read config from start.d
|
||||||
|
@ -212,12 +215,16 @@ public class BaseBuilder
|
||||||
try (FileWriter out = new FileWriter(startini.toFile(), true))
|
try (FileWriter out = new FileWriter(startini.toFile(), true))
|
||||||
{
|
{
|
||||||
for (String line : startLines)
|
for (String line : startLines)
|
||||||
|
{
|
||||||
out.append(line).append(System.lineSeparator());
|
out.append(line).append(System.lineSeparator());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// delete start.d files
|
// delete start.d files
|
||||||
for (Path path : paths)
|
for (Path path : paths)
|
||||||
|
{
|
||||||
Files.delete(path);
|
Files.delete(path);
|
||||||
|
}
|
||||||
Files.delete(startd);
|
Files.delete(startd);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -264,7 +271,13 @@ public class BaseBuilder
|
||||||
StartLog.warn("Use of both %s and %s is deprecated", getBaseHome().toShortForm(startd), getBaseHome().toShortForm(startini));
|
StartLog.warn("Use of both %s and %s is deprecated", getBaseHome().toShortForm(startd), getBaseHome().toShortForm(startini));
|
||||||
|
|
||||||
builder.set(useStartD ? new StartDirBuilder(this) : new StartIniBuilder(this));
|
builder.set(useStartD ? new StartDirBuilder(this) : new StartIniBuilder(this));
|
||||||
newlyAdded.stream().map(modules::get).forEach(module ->
|
|
||||||
|
// Collect the filesystem operations to perform,
|
||||||
|
// only for those modules that are enabled.
|
||||||
|
newlyAdded.stream()
|
||||||
|
.map(modules::get)
|
||||||
|
.filter(Module::isEnabled)
|
||||||
|
.forEach(module ->
|
||||||
{
|
{
|
||||||
String ini = null;
|
String ini = null;
|
||||||
try
|
try
|
||||||
|
@ -303,12 +316,16 @@ public class BaseBuilder
|
||||||
else if (module.isTransitive())
|
else if (module.isTransitive())
|
||||||
{
|
{
|
||||||
if (module.hasIniTemplate())
|
if (module.hasIniTemplate())
|
||||||
|
{
|
||||||
StartLog.info("%-15s transitively enabled, ini template available with --add-module=%s",
|
StartLog.info("%-15s transitively enabled, ini template available with --add-module=%s",
|
||||||
module.getName(),
|
module.getName(),
|
||||||
module.getName());
|
module.getName());
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
StartLog.info("%-15s transitively enabled", module.getName());
|
StartLog.info("%-15s transitively enabled", module.getName());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
StartLog.info("%-15s initialized in %s", module.getName(), ini);
|
StartLog.info("%-15s initialized in %s", module.getName(), ini);
|
||||||
|
@ -370,7 +387,7 @@ public class BaseBuilder
|
||||||
* @param files the list of {@link FileArg}s to process
|
* @param files the list of {@link FileArg}s to process
|
||||||
* @return true if base directory modified, false if left untouched
|
* @return true if base directory modified, false if left untouched
|
||||||
*/
|
*/
|
||||||
private boolean processFileResources(List<FileArg> files) throws IOException
|
private boolean processFileResources(List<FileArg> files)
|
||||||
{
|
{
|
||||||
if ((files == null) || (files.isEmpty()))
|
if ((files == null) || (files.isEmpty()))
|
||||||
{
|
{
|
||||||
|
|
|
@ -18,7 +18,6 @@ import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
@ -552,7 +551,7 @@ public class Modules implements Iterable<Module>
|
||||||
Set<Module> providers = _provided.get(name);
|
Set<Module> providers = _provided.get(name);
|
||||||
StartLog.debug("Providers of [%s] are %s", name, providers);
|
StartLog.debug("Providers of [%s] are %s", name, providers);
|
||||||
if (providers == null || providers.isEmpty())
|
if (providers == null || providers.isEmpty())
|
||||||
return Collections.emptySet();
|
return Set.of();
|
||||||
|
|
||||||
providers = new HashSet<>(providers);
|
providers = new HashSet<>(providers);
|
||||||
|
|
||||||
|
|
|
@ -52,14 +52,8 @@ import org.eclipse.jetty.util.ManifestUtils;
|
||||||
public class StartArgs
|
public class StartArgs
|
||||||
{
|
{
|
||||||
public static final String VERSION;
|
public static final String VERSION;
|
||||||
public static final Set<String> ALL_PARTS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
|
public static final Set<String> ALL_PARTS = Set.of("java", "opts", "path", "main", "args");
|
||||||
"java",
|
public static final Set<String> ARG_PARTS = Set.of("args");
|
||||||
"opts",
|
|
||||||
"path",
|
|
||||||
"main",
|
|
||||||
"args")));
|
|
||||||
public static final Set<String> ARG_PARTS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
|
|
||||||
"args")));
|
|
||||||
|
|
||||||
static
|
static
|
||||||
{
|
{
|
||||||
|
@ -126,12 +120,12 @@ public class StartArgs
|
||||||
/**
|
/**
|
||||||
* List of enabled modules
|
* List of enabled modules
|
||||||
*/
|
*/
|
||||||
private List<String> modules = new ArrayList<>();
|
private final List<String> modules = new ArrayList<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of modules to skip [files] section validation
|
* List of modules to skip [files] section validation
|
||||||
*/
|
*/
|
||||||
private Set<String> skipFileValidationModules = new HashSet<>();
|
private final Set<String> skipFileValidationModules = new HashSet<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map of enabled modules to the source of where that activation occurred
|
* Map of enabled modules to the source of where that activation occurred
|
||||||
|
@ -141,56 +135,56 @@ public class StartArgs
|
||||||
/**
|
/**
|
||||||
* List of all active [files] sections from enabled modules
|
* List of all active [files] sections from enabled modules
|
||||||
*/
|
*/
|
||||||
private List<FileArg> files = new ArrayList<>();
|
private final List<FileArg> files = new ArrayList<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of all active [lib] sections from enabled modules
|
* List of all active [lib] sections from enabled modules
|
||||||
*/
|
*/
|
||||||
private Classpath classpath;
|
private final Classpath classpath;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of all active [xml] sections from enabled modules
|
* List of all active [xml] sections from enabled modules
|
||||||
*/
|
*/
|
||||||
private List<Path> xmls = new ArrayList<>();
|
private final List<Path> xmls = new ArrayList<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of all active [jpms] sections for enabled modules
|
* List of all active [jpms] sections for enabled modules
|
||||||
*/
|
*/
|
||||||
private Set<String> jmodAdds = new LinkedHashSet<>();
|
private final Set<String> jmodAdds = new LinkedHashSet<>();
|
||||||
private Map<String, Set<String>> jmodPatch = new LinkedHashMap<>();
|
private final Map<String, Set<String>> jmodPatch = new LinkedHashMap<>();
|
||||||
private Map<String, Set<String>> jmodOpens = new LinkedHashMap<>();
|
private final Map<String, Set<String>> jmodOpens = new LinkedHashMap<>();
|
||||||
private Map<String, Set<String>> jmodExports = new LinkedHashMap<>();
|
private final Map<String, Set<String>> jmodExports = new LinkedHashMap<>();
|
||||||
private Map<String, Set<String>> jmodReads = new LinkedHashMap<>();
|
private final Map<String, Set<String>> jmodReads = new LinkedHashMap<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JVM arguments, found via command line and in all active [exec] sections from enabled modules
|
* JVM arguments, found via command line and in all active [exec] sections from enabled modules
|
||||||
*/
|
*/
|
||||||
private List<String> jvmArgs = new ArrayList<>();
|
private final List<String> jvmArgs = new ArrayList<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of all xml references found directly on command line or start.ini
|
* List of all xml references found directly on command line or start.ini
|
||||||
*/
|
*/
|
||||||
private List<String> xmlRefs = new ArrayList<>();
|
private final List<String> xmlRefs = new ArrayList<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of all property references found directly on command line or start.ini
|
* List of all property references found directly on command line or start.ini
|
||||||
*/
|
*/
|
||||||
private List<String> propertyFileRefs = new ArrayList<>();
|
private final List<String> propertyFileRefs = new ArrayList<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of all property files
|
* List of all property files
|
||||||
*/
|
*/
|
||||||
private List<Path> propertyFiles = new ArrayList<>();
|
private final List<Path> propertyFiles = new ArrayList<>();
|
||||||
|
|
||||||
private Props properties = new Props();
|
private final Props properties = new Props();
|
||||||
private Map<String, String> systemPropertySource = new HashMap<>();
|
private final Map<String, String> systemPropertySource = new HashMap<>();
|
||||||
private List<String> rawLibs = new ArrayList<>();
|
private final List<String> rawLibs = new ArrayList<>();
|
||||||
|
|
||||||
// jetty.base - build out commands
|
// jetty.base - build out commands
|
||||||
/**
|
/**
|
||||||
* --add-module=[module,[module]]
|
* --add-module=[module,[module]]
|
||||||
*/
|
*/
|
||||||
private List<String> startModules = new ArrayList<>();
|
private final List<String> startModules = new ArrayList<>();
|
||||||
|
|
||||||
// module inspection commands
|
// module inspection commands
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -25,6 +25,7 @@ import java.nio.channels.FileChannel;
|
||||||
import java.nio.channels.FileChannel.MapMode;
|
import java.nio.channels.FileChannel.MapMode;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.nio.file.StandardOpenOption;
|
import java.nio.file.StandardOpenOption;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
@ -1032,9 +1033,14 @@ public class BufferUtil
|
||||||
|
|
||||||
public static ByteBuffer toMappedBuffer(File file) throws IOException
|
public static ByteBuffer toMappedBuffer(File file) throws IOException
|
||||||
{
|
{
|
||||||
try (FileChannel channel = FileChannel.open(file.toPath(), StandardOpenOption.READ))
|
return toMappedBuffer(file.toPath(), 0, file.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ByteBuffer toMappedBuffer(Path filePath, long pos, long len) throws IOException
|
||||||
{
|
{
|
||||||
return channel.map(MapMode.READ_ONLY, 0, file.length());
|
try (FileChannel channel = FileChannel.open(filePath, StandardOpenOption.READ))
|
||||||
|
{
|
||||||
|
return channel.map(MapMode.READ_ONLY, pos, len);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -95,7 +95,9 @@ public class MessageInputStream extends InputStream implements MessageSink
|
||||||
@Override
|
@Override
|
||||||
public int read(final byte[] b, final int off, final int len) throws IOException
|
public int read(final byte[] b, final int off, final int len) throws IOException
|
||||||
{
|
{
|
||||||
return read(ByteBuffer.wrap(b, off, len).flip());
|
ByteBuffer buffer = ByteBuffer.wrap(b, off, len).slice();
|
||||||
|
BufferUtil.clear(buffer);
|
||||||
|
return read(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int read(ByteBuffer buffer) throws IOException
|
public int read(ByteBuffer buffer) throws IOException
|
||||||
|
|
|
@ -51,7 +51,7 @@ import org.testcontainers.utility.MountableFile;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
@Disabled
|
@Disabled("Disable this test so it doesn't run locally as it takes 1h+ to run.")
|
||||||
@Testcontainers
|
@Testcontainers
|
||||||
public class AutobahnTests
|
public class AutobahnTests
|
||||||
{
|
{
|
||||||
|
|
|
@ -266,6 +266,10 @@ public class JavaxWebSocketFrameHandler implements FrameHandler
|
||||||
{
|
{
|
||||||
notifyOnClose(closeStatus, callback);
|
notifyOnClose(closeStatus, callback);
|
||||||
container.notifySessionListeners((listener) -> listener.onJavaxWebSocketSessionClosed(session));
|
container.notifySessionListeners((listener) -> listener.onJavaxWebSocketSessionClosed(session));
|
||||||
|
|
||||||
|
// Close AvailableEncoders and AvailableDecoders to call destroy() on any instances of Encoder/Encoder created.
|
||||||
|
session.getDecoders().close();
|
||||||
|
session.getEncoders().close();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void notifyOnClose(CloseStatus closeStatus, Callback callback)
|
private void notifyOnClose(CloseStatus closeStatus, Callback callback)
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.websocket.javax.common.decoders;
|
package org.eclipse.jetty.websocket.javax.common.decoders;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
@ -30,7 +31,7 @@ import org.eclipse.jetty.websocket.core.exception.InvalidSignatureException;
|
||||||
import org.eclipse.jetty.websocket.core.exception.InvalidWebSocketException;
|
import org.eclipse.jetty.websocket.core.exception.InvalidWebSocketException;
|
||||||
import org.eclipse.jetty.websocket.core.internal.util.ReflectUtils;
|
import org.eclipse.jetty.websocket.core.internal.util.ReflectUtils;
|
||||||
|
|
||||||
public class AvailableDecoders implements Iterable<RegisteredDecoder>
|
public class AvailableDecoders implements Iterable<RegisteredDecoder>, Closeable
|
||||||
{
|
{
|
||||||
private final List<RegisteredDecoder> registeredDecoders = new ArrayList<>();
|
private final List<RegisteredDecoder> registeredDecoders = new ArrayList<>();
|
||||||
private final EndpointConfig config;
|
private final EndpointConfig config;
|
||||||
|
@ -211,4 +212,10 @@ public class AvailableDecoders implements Iterable<RegisteredDecoder>
|
||||||
{
|
{
|
||||||
return registeredDecoders.stream();
|
return registeredDecoders.stream();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close()
|
||||||
|
{
|
||||||
|
registeredDecoders.forEach(RegisteredDecoder::destroyInstance);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,10 +19,12 @@ import javax.websocket.EndpointConfig;
|
||||||
|
|
||||||
import org.eclipse.jetty.websocket.core.WebSocketComponents;
|
import org.eclipse.jetty.websocket.core.WebSocketComponents;
|
||||||
import org.eclipse.jetty.websocket.javax.common.InitException;
|
import org.eclipse.jetty.websocket.javax.common.InitException;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
public class RegisteredDecoder
|
public class RegisteredDecoder
|
||||||
{
|
{
|
||||||
private final WebSocketComponents components;
|
private static final Logger LOG = LoggerFactory.getLogger(RegisteredDecoder.class);
|
||||||
|
|
||||||
// The user supplied Decoder class
|
// The user supplied Decoder class
|
||||||
public final Class<? extends Decoder> decoder;
|
public final Class<? extends Decoder> decoder;
|
||||||
|
@ -31,6 +33,7 @@ public class RegisteredDecoder
|
||||||
public final Class<?> objectType;
|
public final Class<?> objectType;
|
||||||
public final boolean primitive;
|
public final boolean primitive;
|
||||||
public final EndpointConfig config;
|
public final EndpointConfig config;
|
||||||
|
private final WebSocketComponents components;
|
||||||
|
|
||||||
private Decoder instance;
|
private Decoder instance;
|
||||||
|
|
||||||
|
@ -78,6 +81,23 @@ public class RegisteredDecoder
|
||||||
return (T)instance;
|
return (T)instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void destroyInstance()
|
||||||
|
{
|
||||||
|
if (instance != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
instance.destroy();
|
||||||
|
}
|
||||||
|
catch (Throwable t)
|
||||||
|
{
|
||||||
|
LOG.warn("Error destroying Decoder", t);
|
||||||
|
}
|
||||||
|
|
||||||
|
instance = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.websocket.javax.common.encoders;
|
package org.eclipse.jetty.websocket.javax.common.encoders;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
@ -29,9 +30,12 @@ import org.eclipse.jetty.websocket.core.exception.InvalidSignatureException;
|
||||||
import org.eclipse.jetty.websocket.core.exception.InvalidWebSocketException;
|
import org.eclipse.jetty.websocket.core.exception.InvalidWebSocketException;
|
||||||
import org.eclipse.jetty.websocket.core.internal.util.ReflectUtils;
|
import org.eclipse.jetty.websocket.core.internal.util.ReflectUtils;
|
||||||
import org.eclipse.jetty.websocket.javax.common.InitException;
|
import org.eclipse.jetty.websocket.javax.common.InitException;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
public class AvailableEncoders implements Predicate<Class<?>>
|
public class AvailableEncoders implements Predicate<Class<?>>, Closeable
|
||||||
{
|
{
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(AvailableEncoders.class);
|
||||||
|
|
||||||
private final EndpointConfig config;
|
private final EndpointConfig config;
|
||||||
private final WebSocketComponents components;
|
private final WebSocketComponents components;
|
||||||
|
@ -241,4 +245,10 @@ public class AvailableEncoders implements Predicate<Class<?>>
|
||||||
{
|
{
|
||||||
return registeredEncoders.stream().anyMatch(registered -> registered.isType(type));
|
return registeredEncoders.stream().anyMatch(registered -> registered.isType(type));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close()
|
||||||
|
{
|
||||||
|
registeredEncoders.forEach(RegisteredEncoder::destroyInstance);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,8 +15,13 @@ package org.eclipse.jetty.websocket.javax.common.encoders;
|
||||||
|
|
||||||
import javax.websocket.Encoder;
|
import javax.websocket.Encoder;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
public class RegisteredEncoder
|
public class RegisteredEncoder
|
||||||
{
|
{
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(RegisteredEncoder.class);
|
||||||
|
|
||||||
public final Class<? extends Encoder> encoder;
|
public final Class<? extends Encoder> encoder;
|
||||||
public final Class<? extends Encoder> interfaceType;
|
public final Class<? extends Encoder> interfaceType;
|
||||||
public final Class<?> objectType;
|
public final Class<?> objectType;
|
||||||
|
@ -46,6 +51,23 @@ public class RegisteredEncoder
|
||||||
return objectType.isAssignableFrom(type);
|
return objectType.isAssignableFrom(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void destroyInstance()
|
||||||
|
{
|
||||||
|
if (instance != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
instance.destroy();
|
||||||
|
}
|
||||||
|
catch (Throwable t)
|
||||||
|
{
|
||||||
|
LOG.warn("Error destroying Decoder", t);
|
||||||
|
}
|
||||||
|
|
||||||
|
instance = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
|
|
|
@ -37,7 +37,6 @@ import org.eclipse.jetty.websocket.javax.common.encoders.AvailableEncoders;
|
||||||
import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer;
|
import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer;
|
||||||
import org.eclipse.jetty.websocket.javax.tests.EchoSocket;
|
import org.eclipse.jetty.websocket.javax.tests.EchoSocket;
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
import org.junit.jupiter.api.Disabled;
|
|
||||||
import org.junit.jupiter.params.ParameterizedTest;
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
import org.junit.jupiter.params.provider.ValueSource;
|
import org.junit.jupiter.params.provider.ValueSource;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -147,8 +146,6 @@ public class EncoderLifeCycleTest
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Encoder.destroy() is never called in Jetty 10.
|
|
||||||
@Disabled()
|
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@ValueSource(classes = {StringHolder.class, StringHolderSubtype.class})
|
@ValueSource(classes = {StringHolder.class, StringHolderSubtype.class})
|
||||||
public void testEncoderLifeCycle(Class<? extends StringHolder> clazz) throws Exception
|
public void testEncoderLifeCycle(Class<? extends StringHolder> clazz) throws Exception
|
||||||
|
|
|
@ -37,7 +37,6 @@ import org.eclipse.jetty.websocket.core.OpCode;
|
||||||
import org.eclipse.jetty.websocket.javax.tests.Fuzzer;
|
import org.eclipse.jetty.websocket.javax.tests.Fuzzer;
|
||||||
import org.eclipse.jetty.websocket.javax.tests.LocalServer;
|
import org.eclipse.jetty.websocket.javax.tests.LocalServer;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.Disabled;
|
|
||||||
import org.junit.jupiter.params.ParameterizedTest;
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
import org.junit.jupiter.params.provider.Arguments;
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
import org.junit.jupiter.params.provider.MethodSource;
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
@ -62,11 +61,7 @@ public class SessionTest
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ret.append('[').append(pathParams.size()).append(']');
|
ret.append('[').append(pathParams.size()).append(']');
|
||||||
List<String> keys = new ArrayList<>();
|
List<String> keys = new ArrayList<>(pathParams.keySet());
|
||||||
for (String key : pathParams.keySet())
|
|
||||||
{
|
|
||||||
keys.add(key);
|
|
||||||
}
|
|
||||||
Collections.sort(keys);
|
Collections.sort(keys);
|
||||||
for (String key : keys)
|
for (String key : keys)
|
||||||
{
|
{
|
||||||
|
@ -126,11 +121,7 @@ public class SessionTest
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ret.append('[').append(pathParams.size()).append(']');
|
ret.append('[').append(pathParams.size()).append(']');
|
||||||
List<String> keys = new ArrayList<>();
|
List<String> keys = new ArrayList<>(pathParams.keySet());
|
||||||
for (String key : pathParams.keySet())
|
|
||||||
{
|
|
||||||
keys.add(key);
|
|
||||||
}
|
|
||||||
Collections.sort(keys);
|
Collections.sort(keys);
|
||||||
for (String key : keys)
|
for (String key : keys)
|
||||||
{
|
{
|
||||||
|
@ -227,14 +218,13 @@ public class SessionTest
|
||||||
container.addEndpoint(ServerEndpointConfig.Builder.create(endpointClass, "/info/{a}/{b}/").build());
|
container.addEndpoint(ServerEndpointConfig.Builder.create(endpointClass, "/info/{a}/{b}/").build());
|
||||||
container.addEndpoint(ServerEndpointConfig.Builder.create(endpointClass, "/info/{a}/{b}/{c}/").build());
|
container.addEndpoint(ServerEndpointConfig.Builder.create(endpointClass, "/info/{a}/{b}/{c}/").build());
|
||||||
container.addEndpoint(ServerEndpointConfig.Builder.create(endpointClass, "/info/{a}/{b}/{c}/{d}/").build());
|
container.addEndpoint(ServerEndpointConfig.Builder.create(endpointClass, "/info/{a}/{b}/{c}/{d}/").build());
|
||||||
/*
|
|
||||||
endpointClass = SessionInfoEndpoint.class;
|
endpointClass = SessionInfoEndpoint.class;
|
||||||
container.addEndpoint(ServerEndpointConfig.Builder.create(endpointClass,"/einfo/").build());
|
container.addEndpoint(ServerEndpointConfig.Builder.create(endpointClass, "/einfo/").build());
|
||||||
container.addEndpoint(ServerEndpointConfig.Builder.create(endpointClass,"/einfo/{a}/").build());
|
container.addEndpoint(ServerEndpointConfig.Builder.create(endpointClass, "/einfo/{a}/").build());
|
||||||
container.addEndpoint(ServerEndpointConfig.Builder.create(endpointClass,"/einfo/{a}/{b}/").build());
|
container.addEndpoint(ServerEndpointConfig.Builder.create(endpointClass, "/einfo/{a}/{b}/").build());
|
||||||
container.addEndpoint(ServerEndpointConfig.Builder.create(endpointClass,"/einfo/{a}/{b}/{c}/").build());
|
container.addEndpoint(ServerEndpointConfig.Builder.create(endpointClass, "/einfo/{a}/{b}/{c}/").build());
|
||||||
container.addEndpoint(ServerEndpointConfig.Builder.create(endpointClass,"/einfo/{a}/{b}/{c}/{d}/").build());
|
container.addEndpoint(ServerEndpointConfig.Builder.create(endpointClass, "/einfo/{a}/{b}/{c}/{d}/").build());
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertResponse(String requestPath, String requestMessage,
|
private void assertResponse(String requestPath, String requestMessage,
|
||||||
|
@ -293,7 +283,6 @@ public class SessionTest
|
||||||
|
|
||||||
@ParameterizedTest(name = "{0}")
|
@ParameterizedTest(name = "{0}")
|
||||||
@MethodSource("data")
|
@MethodSource("data")
|
||||||
@Disabled
|
|
||||||
public void testPathParamsEndpointEmpty(Case testCase) throws Exception
|
public void testPathParamsEndpointEmpty(Case testCase) throws Exception
|
||||||
{
|
{
|
||||||
setup(testCase);
|
setup(testCase);
|
||||||
|
@ -303,7 +292,6 @@ public class SessionTest
|
||||||
|
|
||||||
@ParameterizedTest(name = "{0}")
|
@ParameterizedTest(name = "{0}")
|
||||||
@MethodSource("data")
|
@MethodSource("data")
|
||||||
@Disabled
|
|
||||||
public void testPathParamsEndpointSingle(Case testCase) throws Exception
|
public void testPathParamsEndpointSingle(Case testCase) throws Exception
|
||||||
{
|
{
|
||||||
setup(testCase);
|
setup(testCase);
|
||||||
|
@ -313,7 +301,6 @@ public class SessionTest
|
||||||
|
|
||||||
@ParameterizedTest(name = "{0}")
|
@ParameterizedTest(name = "{0}")
|
||||||
@MethodSource("data")
|
@MethodSource("data")
|
||||||
@Disabled
|
|
||||||
public void testPathParamsEndpointDouble(Case testCase) throws Exception
|
public void testPathParamsEndpointDouble(Case testCase) throws Exception
|
||||||
{
|
{
|
||||||
setup(testCase);
|
setup(testCase);
|
||||||
|
@ -323,7 +310,6 @@ public class SessionTest
|
||||||
|
|
||||||
@ParameterizedTest(name = "{0}")
|
@ParameterizedTest(name = "{0}")
|
||||||
@MethodSource("data")
|
@MethodSource("data")
|
||||||
@Disabled
|
|
||||||
public void testPathParamsEndpointTriple(Case testCase) throws Exception
|
public void testPathParamsEndpointTriple(Case testCase) throws Exception
|
||||||
{
|
{
|
||||||
setup(testCase);
|
setup(testCase);
|
||||||
|
@ -363,7 +349,6 @@ public class SessionTest
|
||||||
|
|
||||||
@ParameterizedTest(name = "{0}")
|
@ParameterizedTest(name = "{0}")
|
||||||
@MethodSource("data")
|
@MethodSource("data")
|
||||||
@Disabled
|
|
||||||
public void testRequestUriEndpointBasic(Case testCase) throws Exception
|
public void testRequestUriEndpointBasic(Case testCase) throws Exception
|
||||||
{
|
{
|
||||||
setup(testCase);
|
setup(testCase);
|
||||||
|
@ -373,7 +358,6 @@ public class SessionTest
|
||||||
|
|
||||||
@ParameterizedTest(name = "{0}")
|
@ParameterizedTest(name = "{0}")
|
||||||
@MethodSource("data")
|
@MethodSource("data")
|
||||||
@Disabled
|
|
||||||
public void testRequestUriEndpointWithPathParam(Case testCase) throws Exception
|
public void testRequestUriEndpointWithPathParam(Case testCase) throws Exception
|
||||||
{
|
{
|
||||||
setup(testCase);
|
setup(testCase);
|
||||||
|
@ -383,7 +367,6 @@ public class SessionTest
|
||||||
|
|
||||||
@ParameterizedTest(name = "{0}")
|
@ParameterizedTest(name = "{0}")
|
||||||
@MethodSource("data")
|
@MethodSource("data")
|
||||||
@Disabled
|
|
||||||
public void testRequestUriEndpointWithPathParamWithQuery(Case testCase) throws Exception
|
public void testRequestUriEndpointWithPathParamWithQuery(Case testCase) throws Exception
|
||||||
{
|
{
|
||||||
setup(testCase);
|
setup(testCase);
|
||||||
|
|
|
@ -23,6 +23,7 @@ import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
import java.util.concurrent.BlockingQueue;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import javax.websocket.ClientEndpointConfig;
|
import javax.websocket.ClientEndpointConfig;
|
||||||
|
@ -50,7 +51,6 @@ import org.eclipse.jetty.websocket.javax.tests.WSEndpointTracker;
|
||||||
import org.hamcrest.Matchers;
|
import org.hamcrest.Matchers;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Disabled;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -116,33 +116,26 @@ public class TextStreamTest
|
||||||
send.add(CloseStatus.toFrame(CloseStatus.NORMAL));
|
send.add(CloseStatus.toFrame(CloseStatus.NORMAL));
|
||||||
|
|
||||||
ByteBuffer expectedMessage = DataUtils.copyOf(data);
|
ByteBuffer expectedMessage = DataUtils.copyOf(data);
|
||||||
List<Frame> expect = new ArrayList<>();
|
|
||||||
expect.add(new Frame(OpCode.TEXT).setPayload(expectedMessage));
|
|
||||||
expect.add(CloseStatus.toFrame(CloseStatus.NORMAL));
|
|
||||||
|
|
||||||
try (Fuzzer fuzzer = server.newNetworkFuzzer("/echo"))
|
try (Fuzzer fuzzer = server.newNetworkFuzzer("/echo"))
|
||||||
{
|
{
|
||||||
fuzzer.sendBulk(send);
|
fuzzer.sendBulk(send);
|
||||||
fuzzer.expect(expect);
|
BlockingQueue<Frame> receivedFrames = fuzzer.getOutputFrames();
|
||||||
|
fuzzer.expectMessage(receivedFrames, OpCode.TEXT, expectedMessage);
|
||||||
|
fuzzer.expect(List.of(CloseStatus.toFrame(CloseStatus.NORMAL)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO These tests incorrectly assumes no frame fragmentation.
|
|
||||||
// When message fragmentation is implemented in PartialStringMessageSink then update
|
|
||||||
// this test to check on the server side for no buffers larger than the maxTextMessageBufferSize.
|
|
||||||
|
|
||||||
@Disabled
|
|
||||||
@Test
|
@Test
|
||||||
public void testAtMaxDefaultMessageBufferSize() throws Exception
|
public void testAtMaxDefaultMessageBufferSize() throws Exception
|
||||||
{
|
{
|
||||||
testEcho(container.getDefaultMaxTextMessageBufferSize());
|
testEcho(container.getDefaultMaxTextMessageBufferSize());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Disabled
|
|
||||||
@Test
|
@Test
|
||||||
public void testLargerThenMaxDefaultMessageBufferSize() throws Exception
|
public void testLargerThenMaxDefaultMessageBufferSize() throws Exception
|
||||||
{
|
{
|
||||||
int size = container.getDefaultMaxTextMessageBufferSize() + 16;
|
int maxTextMessageBufferSize = container.getDefaultMaxTextMessageBufferSize();
|
||||||
|
int size = maxTextMessageBufferSize + 16;
|
||||||
byte[] data = newData(size);
|
byte[] data = newData(size);
|
||||||
|
|
||||||
List<Frame> send = new ArrayList<>();
|
List<Frame> send = new ArrayList<>();
|
||||||
|
@ -153,19 +146,13 @@ public class TextStreamTest
|
||||||
byte[] expectedData = new byte[data.length];
|
byte[] expectedData = new byte[data.length];
|
||||||
System.arraycopy(data, 0, expectedData, 0, data.length);
|
System.arraycopy(data, 0, expectedData, 0, data.length);
|
||||||
|
|
||||||
// Frames expected are influenced by container.getDefaultMaxTextMessageBufferSize setting
|
|
||||||
ByteBuffer frame1 = ByteBuffer.wrap(expectedData, 0, container.getDefaultMaxTextMessageBufferSize());
|
|
||||||
ByteBuffer frame2 = ByteBuffer
|
|
||||||
.wrap(expectedData, container.getDefaultMaxTextMessageBufferSize(), size - container.getDefaultMaxTextMessageBufferSize());
|
|
||||||
List<Frame> expect = new ArrayList<>();
|
|
||||||
expect.add(new Frame(OpCode.TEXT).setPayload(frame1).setFin(false));
|
|
||||||
expect.add(new Frame(OpCode.CONTINUATION).setPayload(frame2).setFin(true));
|
|
||||||
expect.add(CloseStatus.toFrame(CloseStatus.NORMAL));
|
|
||||||
|
|
||||||
try (Fuzzer fuzzer = server.newNetworkFuzzer("/echo"))
|
try (Fuzzer fuzzer = server.newNetworkFuzzer("/echo"))
|
||||||
{
|
{
|
||||||
fuzzer.sendBulk(send);
|
fuzzer.sendBulk(send);
|
||||||
fuzzer.expect(expect);
|
|
||||||
|
BlockingQueue<Frame> receivedFrames = fuzzer.getOutputFrames();
|
||||||
|
fuzzer.expectMessage(receivedFrames, OpCode.TEXT, ByteBuffer.wrap(expectedData));
|
||||||
|
fuzzer.expect(List.of(CloseStatus.toFrame(CloseStatus.NORMAL)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -69,6 +69,36 @@ public class MessageInputStreamTest
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMultipleReadsIntoSingleByteArray() throws IOException
|
||||||
|
{
|
||||||
|
try (MessageInputStream stream = new MessageInputStream())
|
||||||
|
{
|
||||||
|
// Append a single message (simple, short)
|
||||||
|
Frame frame = new Frame(OpCode.TEXT);
|
||||||
|
frame.setPayload("Hello World");
|
||||||
|
frame.setFin(true);
|
||||||
|
stream.accept(frame, Callback.NOOP);
|
||||||
|
|
||||||
|
// Read entire message it from the stream.
|
||||||
|
byte[] bytes = new byte[100];
|
||||||
|
|
||||||
|
int read = stream.read(bytes, 0, 6);
|
||||||
|
assertThat(read, is(6));
|
||||||
|
|
||||||
|
read = stream.read(bytes, 6, 10);
|
||||||
|
assertThat(read, is(5));
|
||||||
|
|
||||||
|
read = stream.read(bytes, 11, 10);
|
||||||
|
assertThat(read, is(-1));
|
||||||
|
|
||||||
|
String message = new String(bytes, 0, 11, StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
// Test it
|
||||||
|
assertThat("Message", message, is("Hello World"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBlockOnRead() throws Exception
|
public void testBlockOnRead() throws Exception
|
||||||
{
|
{
|
||||||
|
|
6
pom.xml
6
pom.xml
|
@ -59,6 +59,7 @@
|
||||||
<maven.plugin-tools.version>3.6.0</maven.plugin-tools.version>
|
<maven.plugin-tools.version>3.6.0</maven.plugin-tools.version>
|
||||||
<maven.install.plugin.version>3.0.0-M1</maven.install.plugin.version>
|
<maven.install.plugin.version>3.0.0-M1</maven.install.plugin.version>
|
||||||
<maven.deploy.plugin.version>3.0.0-M1</maven.deploy.plugin.version>
|
<maven.deploy.plugin.version>3.0.0-M1</maven.deploy.plugin.version>
|
||||||
|
<maven.exec.plugin.version>3.0.0</maven.exec.plugin.version>
|
||||||
|
|
||||||
<!-- testing -->
|
<!-- testing -->
|
||||||
<it.debug>false</it.debug>
|
<it.debug>false</it.debug>
|
||||||
|
@ -768,8 +769,7 @@
|
||||||
<require>asciidoctor-diagram</require>
|
<require>asciidoctor-diagram</require>
|
||||||
</requires>
|
</requires>
|
||||||
<attributes>
|
<attributes>
|
||||||
<JDURL>http://www.eclipse.org/jetty/javadoc/${project.version}</JDURL>
|
<JDURL>https://www.eclipse.org/jetty/javadoc/jetty-10</JDURL>
|
||||||
<JXURL>http://download.eclipse.org/jetty/stable-9/xref</JXURL>
|
|
||||||
<SRCDIR>${basedir}/..</SRCDIR>
|
<SRCDIR>${basedir}/..</SRCDIR>
|
||||||
<GITBROWSEURL>https://github.com/eclipse/jetty.project/tree/jetty-9.4.x</GITBROWSEURL>
|
<GITBROWSEURL>https://github.com/eclipse/jetty.project/tree/jetty-9.4.x</GITBROWSEURL>
|
||||||
<GITDOCURL>https://github.com/eclipse/jetty.project/tree/jetty-10.0.x-doc-refactor/jetty-documentation/src/main/asciidoc</GITDOCURL>
|
<GITDOCURL>https://github.com/eclipse/jetty.project/tree/jetty-10.0.x-doc-refactor/jetty-documentation/src/main/asciidoc</GITDOCURL>
|
||||||
|
@ -813,7 +813,7 @@
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.codehaus.mojo</groupId>
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
<artifactId>exec-maven-plugin</artifactId>
|
<artifactId>exec-maven-plugin</artifactId>
|
||||||
<version>3.0.0</version>
|
<version>${maven.exec.plugin.version}</version>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.eclipse.m2e</groupId>
|
<groupId>org.eclipse.m2e</groupId>
|
||||||
|
|
|
@ -859,4 +859,54 @@ public class DistributionTests extends AbstractJettyHomeTest
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDefaultLoggingProviderNotActiveWhenExplicitProviderIsPresent() throws Exception
|
||||||
|
{
|
||||||
|
String jettyVersion = System.getProperty("jettyVersion");
|
||||||
|
JettyHomeTester distribution1 = JettyHomeTester.Builder.newInstance()
|
||||||
|
.jettyVersion(jettyVersion)
|
||||||
|
.mavenLocalRepository(System.getProperty("mavenRepoPath"))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
String[] args1 = {
|
||||||
|
"--approve-all-licenses",
|
||||||
|
"--add-modules=logging-logback,http"
|
||||||
|
};
|
||||||
|
|
||||||
|
try (JettyHomeTester.Run run1 = distribution1.start(args1))
|
||||||
|
{
|
||||||
|
assertTrue(run1.awaitFor(10, TimeUnit.SECONDS));
|
||||||
|
assertEquals(0, run1.getExitValue());
|
||||||
|
|
||||||
|
Path jettyBase = run1.getConfig().getJettyBase();
|
||||||
|
|
||||||
|
assertTrue(Files.exists(jettyBase.resolve("resources/logback.xml")));
|
||||||
|
// The jetty-logging.properties should be absent.
|
||||||
|
assertFalse(Files.exists(jettyBase.resolve("resources/jetty-logging.properties")));
|
||||||
|
}
|
||||||
|
|
||||||
|
JettyHomeTester distribution2 = JettyHomeTester.Builder.newInstance()
|
||||||
|
.jettyVersion(jettyVersion)
|
||||||
|
.mavenLocalRepository(System.getProperty("mavenRepoPath"))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// Try the modules in reverse order, since it may execute a different code path.
|
||||||
|
String[] args2 = {
|
||||||
|
"--approve-all-licenses",
|
||||||
|
"--add-modules=http,logging-logback"
|
||||||
|
};
|
||||||
|
|
||||||
|
try (JettyHomeTester.Run run2 = distribution2.start(args2))
|
||||||
|
{
|
||||||
|
assertTrue(run2.awaitFor(1000, TimeUnit.SECONDS));
|
||||||
|
assertEquals(0, run2.getExitValue());
|
||||||
|
|
||||||
|
Path jettyBase = run2.getConfig().getJettyBase();
|
||||||
|
|
||||||
|
assertTrue(Files.exists(jettyBase.resolve("resources/logback.xml")));
|
||||||
|
// The jetty-logging.properties should be absent.
|
||||||
|
assertFalse(Files.exists(jettyBase.resolve("resources/jetty-logging.properties")));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,6 @@ import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||||
import org.eclipse.jetty.util.IO;
|
import org.eclipse.jetty.util.IO;
|
||||||
import org.hamcrest.Matchers;
|
import org.hamcrest.Matchers;
|
||||||
import org.junit.jupiter.api.Assumptions;
|
import org.junit.jupiter.api.Assumptions;
|
||||||
import org.junit.jupiter.api.Disabled;
|
|
||||||
import org.junit.jupiter.params.ParameterizedTest;
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
import org.junit.jupiter.params.provider.ArgumentsSource;
|
import org.junit.jupiter.params.provider.ArgumentsSource;
|
||||||
|
|
||||||
|
@ -50,7 +49,6 @@ public class ConnectionStatisticsTest extends AbstractTest<TransportScenario>
|
||||||
Assumptions.assumeTrue(scenario.transport == HTTP || scenario.transport == H2C);
|
Assumptions.assumeTrue(scenario.transport == HTTP || scenario.transport == H2C);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Disabled
|
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@ArgumentsSource(TransportProvider.class)
|
@ArgumentsSource(TransportProvider.class)
|
||||||
public void testConnectionStatistics(Transport transport) throws Exception
|
public void testConnectionStatistics(Transport transport) throws Exception
|
||||||
|
|
|
@ -14,7 +14,6 @@
|
||||||
package org.eclipse.jetty.test;
|
package org.eclipse.jetty.test;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.NetworkInterface;
|
import java.net.NetworkInterface;
|
||||||
|
@ -27,7 +26,7 @@ import java.util.Locale;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.BlockingQueue;
|
import java.util.concurrent.BlockingQueue;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import javax.servlet.ServletException;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import javax.servlet.ServletOutputStream;
|
import javax.servlet.ServletOutputStream;
|
||||||
import javax.servlet.http.HttpServlet;
|
import javax.servlet.http.HttpServlet;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
@ -53,6 +52,7 @@ import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||||
import org.eclipse.jetty.servlet.ServletHolder;
|
import org.eclipse.jetty.servlet.ServletHolder;
|
||||||
import org.eclipse.jetty.util.BlockingArrayQueue;
|
import org.eclipse.jetty.util.BlockingArrayQueue;
|
||||||
import org.eclipse.jetty.util.DateCache;
|
import org.eclipse.jetty.util.DateCache;
|
||||||
|
import org.eclipse.jetty.util.IO;
|
||||||
import org.eclipse.jetty.util.security.Constraint;
|
import org.eclipse.jetty.util.security.Constraint;
|
||||||
import org.eclipse.jetty.util.security.Credential;
|
import org.eclipse.jetty.util.security.Credential;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
@ -66,22 +66,24 @@ import static org.hamcrest.Matchers.greaterThanOrEqualTo;
|
||||||
import static org.hamcrest.Matchers.is;
|
import static org.hamcrest.Matchers.is;
|
||||||
import static org.hamcrest.Matchers.lessThanOrEqualTo;
|
import static org.hamcrest.Matchers.lessThanOrEqualTo;
|
||||||
import static org.hamcrest.Matchers.not;
|
import static org.hamcrest.Matchers.not;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
import static org.junit.jupiter.api.Assertions.fail;
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
|
||||||
public class CustomRequestLogTest
|
public class CustomRequestLogTest
|
||||||
{
|
{
|
||||||
CustomRequestLog _log;
|
private final BlockingQueue<String> _entries = new BlockingArrayQueue<>();
|
||||||
Server _server;
|
private final BlockingQueue<Long> requestTimes = new BlockingArrayQueue<>();
|
||||||
LocalConnector _connector;
|
private CustomRequestLog _log;
|
||||||
BlockingQueue<String> _entries = new BlockingArrayQueue<>();
|
private Server _server;
|
||||||
BlockingQueue<Long> requestTimes = new BlockingArrayQueue<>();
|
private LocalConnector _connector;
|
||||||
ServerConnector _serverConnector;
|
private ServerConnector _serverConnector;
|
||||||
URI _serverURI;
|
private URI _serverURI;
|
||||||
|
|
||||||
private static final long DELAY = 2000;
|
private static final long DELAY = 2000;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
public void before() throws Exception
|
public void before()
|
||||||
{
|
{
|
||||||
_server = new Server();
|
_server = new Server();
|
||||||
_connector = new LocalConnector(_server);
|
_connector = new LocalConnector(_server);
|
||||||
|
@ -111,6 +113,7 @@ public class CustomRequestLogTest
|
||||||
_serverURI = new URI(String.format("http://%s:%d/", host, localPort));
|
_serverURI = new URI(String.format("http://%s:%d/", host, localPort));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("SameParameterValue")
|
||||||
private static SecurityHandler getSecurityHandler(String username, String password, String realm)
|
private static SecurityHandler getSecurityHandler(String username, String password, String realm)
|
||||||
{
|
{
|
||||||
HashLoginService loginService = new HashLoginService();
|
HashLoginService loginService = new HashLoginService();
|
||||||
|
@ -142,6 +145,22 @@ public class CustomRequestLogTest
|
||||||
_server.stop();
|
_server.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRequestFilter() throws Exception
|
||||||
|
{
|
||||||
|
AtomicReference<Boolean> logRequest = new AtomicReference<>();
|
||||||
|
testHandlerServerStart("RequestPath: %U");
|
||||||
|
_log.setFilter((request, response) -> logRequest.get());
|
||||||
|
|
||||||
|
logRequest.set(true);
|
||||||
|
_connector.getResponse("GET /path HTTP/1.0\n\n");
|
||||||
|
assertThat(_entries.poll(5, TimeUnit.SECONDS), is("RequestPath: /path"));
|
||||||
|
|
||||||
|
logRequest.set(false);
|
||||||
|
_connector.getResponse("GET /path HTTP/1.0\n\n");
|
||||||
|
assertNull(_entries.poll(1, TimeUnit.SECONDS));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLogRemoteUser() throws Exception
|
public void testLogRemoteUser() throws Exception
|
||||||
{
|
{
|
||||||
|
@ -197,16 +216,16 @@ public class CustomRequestLogTest
|
||||||
"%{server}a|%{server}p|" +
|
"%{server}a|%{server}p|" +
|
||||||
"%{client}a|%{client}p");
|
"%{client}a|%{client}p");
|
||||||
|
|
||||||
Enumeration e = NetworkInterface.getNetworkInterfaces();
|
Enumeration<NetworkInterface> e = NetworkInterface.getNetworkInterfaces();
|
||||||
while (e.hasMoreElements())
|
while (e.hasMoreElements())
|
||||||
{
|
{
|
||||||
NetworkInterface n = (NetworkInterface)e.nextElement();
|
NetworkInterface n = e.nextElement();
|
||||||
if (n.isLoopback())
|
if (n.isLoopback())
|
||||||
{
|
{
|
||||||
Enumeration ee = n.getInetAddresses();
|
Enumeration<InetAddress> ee = n.getInetAddresses();
|
||||||
while (ee.hasMoreElements())
|
while (ee.hasMoreElements())
|
||||||
{
|
{
|
||||||
InetAddress i = (InetAddress)ee.nextElement();
|
InetAddress i = ee.nextElement();
|
||||||
try (Socket client = newSocket(i.getHostAddress(), _serverURI.getPort()))
|
try (Socket client = newSocket(i.getHostAddress(), _serverURI.getPort()))
|
||||||
{
|
{
|
||||||
OutputStream os = client.getOutputStream();
|
OutputStream os = client.getOutputStream();
|
||||||
|
@ -217,7 +236,7 @@ public class CustomRequestLogTest
|
||||||
os.write(request.getBytes(StandardCharsets.ISO_8859_1));
|
os.write(request.getBytes(StandardCharsets.ISO_8859_1));
|
||||||
os.flush();
|
os.flush();
|
||||||
|
|
||||||
String[] log = _entries.poll(5, TimeUnit.SECONDS).split("\\|");
|
String[] log = Objects.requireNonNull(_entries.poll(5, TimeUnit.SECONDS)).split("\\|");
|
||||||
assertThat(log.length, is(8));
|
assertThat(log.length, is(8));
|
||||||
|
|
||||||
String localAddr = log[0];
|
String localAddr = log[0];
|
||||||
|
@ -428,7 +447,7 @@ public class CustomRequestLogTest
|
||||||
|
|
||||||
_connector.getResponse("GET / HTTP/1.0\n\n");
|
_connector.getResponse("GET / HTTP/1.0\n\n");
|
||||||
String log = _entries.poll(5, TimeUnit.SECONDS);
|
String log = _entries.poll(5, TimeUnit.SECONDS);
|
||||||
long requestTime = requestTimes.poll(5, TimeUnit.SECONDS);
|
long requestTime = getTimeRequestReceived();
|
||||||
DateCache dateCache = new DateCache(CustomRequestLog.DEFAULT_DATE_FORMAT, Locale.getDefault(), "GMT");
|
DateCache dateCache = new DateCache(CustomRequestLog.DEFAULT_DATE_FORMAT, Locale.getDefault(), "GMT");
|
||||||
assertThat(log, is("RequestTime: [" + dateCache.format(requestTime) + "]"));
|
assertThat(log, is("RequestTime: [" + dateCache.format(requestTime) + "]"));
|
||||||
}
|
}
|
||||||
|
@ -442,7 +461,8 @@ public class CustomRequestLogTest
|
||||||
|
|
||||||
_connector.getResponse("GET / HTTP/1.0\n\n");
|
_connector.getResponse("GET / HTTP/1.0\n\n");
|
||||||
String log = _entries.poll(5, TimeUnit.SECONDS);
|
String log = _entries.poll(5, TimeUnit.SECONDS);
|
||||||
long requestTime = requestTimes.poll(5, TimeUnit.SECONDS);
|
assertNotNull(log);
|
||||||
|
long requestTime = getTimeRequestReceived();
|
||||||
|
|
||||||
DateCache dateCache1 = new DateCache("EEE MMM dd HH:mm:ss zzz yyyy", Locale.getDefault(), "GMT");
|
DateCache dateCache1 = new DateCache("EEE MMM dd HH:mm:ss zzz yyyy", Locale.getDefault(), "GMT");
|
||||||
DateCache dateCache2 = new DateCache("EEE MMM dd HH:mm:ss zzz yyyy", Locale.getDefault(), "EST");
|
DateCache dateCache2 = new DateCache("EEE MMM dd HH:mm:ss zzz yyyy", Locale.getDefault(), "EST");
|
||||||
|
@ -461,7 +481,8 @@ public class CustomRequestLogTest
|
||||||
|
|
||||||
_connector.getResponse("GET /delay HTTP/1.0\n\n");
|
_connector.getResponse("GET /delay HTTP/1.0\n\n");
|
||||||
String log = _entries.poll(5, TimeUnit.SECONDS);
|
String log = _entries.poll(5, TimeUnit.SECONDS);
|
||||||
long lowerBound = requestTimes.poll(5, TimeUnit.SECONDS);
|
assertNotNull(log);
|
||||||
|
long lowerBound = getTimeRequestReceived();
|
||||||
long upperBound = System.currentTimeMillis();
|
long upperBound = System.currentTimeMillis();
|
||||||
|
|
||||||
long measuredDuration = Long.parseLong(log);
|
long measuredDuration = Long.parseLong(log);
|
||||||
|
@ -479,7 +500,8 @@ public class CustomRequestLogTest
|
||||||
|
|
||||||
_connector.getResponse("GET /delay HTTP/1.0\n\n");
|
_connector.getResponse("GET /delay HTTP/1.0\n\n");
|
||||||
String log = _entries.poll(5, TimeUnit.SECONDS);
|
String log = _entries.poll(5, TimeUnit.SECONDS);
|
||||||
long lowerBound = requestTimes.poll(5, TimeUnit.SECONDS);
|
assertNotNull(log);
|
||||||
|
long lowerBound = getTimeRequestReceived();
|
||||||
long upperBound = System.currentTimeMillis();
|
long upperBound = System.currentTimeMillis();
|
||||||
|
|
||||||
long measuredDuration = Long.parseLong(log);
|
long measuredDuration = Long.parseLong(log);
|
||||||
|
@ -497,7 +519,8 @@ public class CustomRequestLogTest
|
||||||
|
|
||||||
_connector.getResponse("GET /delay HTTP/1.0\n\n");
|
_connector.getResponse("GET /delay HTTP/1.0\n\n");
|
||||||
String log = _entries.poll(5, TimeUnit.SECONDS);
|
String log = _entries.poll(5, TimeUnit.SECONDS);
|
||||||
long lowerBound = requestTimes.poll(5, TimeUnit.SECONDS);
|
assertNotNull(log);
|
||||||
|
long lowerBound = getTimeRequestReceived();
|
||||||
long upperBound = System.currentTimeMillis();
|
long upperBound = System.currentTimeMillis();
|
||||||
|
|
||||||
long measuredDuration = Long.parseLong(log);
|
long measuredDuration = Long.parseLong(log);
|
||||||
|
@ -575,11 +598,6 @@ public class CustomRequestLogTest
|
||||||
fail(log);
|
fail(log);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Socket newSocket() throws Exception
|
|
||||||
{
|
|
||||||
return newSocket(_serverURI.getHost(), _serverURI.getPort());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Socket newSocket(String host, int port) throws Exception
|
protected Socket newSocket(String host, int port) throws Exception
|
||||||
{
|
{
|
||||||
Socket socket = new Socket(host, port);
|
Socket socket = new Socket(host, port);
|
||||||
|
@ -604,10 +622,17 @@ public class CustomRequestLogTest
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private long getTimeRequestReceived() throws InterruptedException
|
||||||
|
{
|
||||||
|
Long requestTime = requestTimes.poll(5, TimeUnit.SECONDS);
|
||||||
|
assertNotNull(requestTime);
|
||||||
|
return requestTime;
|
||||||
|
}
|
||||||
|
|
||||||
private class TestServlet extends HttpServlet
|
private class TestServlet extends HttpServlet
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||||
{
|
{
|
||||||
Request baseRequest = Objects.requireNonNull(Request.getBaseRequest(request));
|
Request baseRequest = Objects.requireNonNull(Request.getBaseRequest(request));
|
||||||
|
|
||||||
|
@ -652,8 +677,7 @@ public class CustomRequestLogTest
|
||||||
|
|
||||||
if (request.getContentLength() > 0)
|
if (request.getContentLength() > 0)
|
||||||
{
|
{
|
||||||
InputStream in = request.getInputStream();
|
IO.readBytes(request.getInputStream());
|
||||||
while (in.read() > 0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -251,7 +251,8 @@ public class GzipWithSendErrorTest
|
||||||
assertThat("Request Input Content Received", inputContentReceived.get(), is(0L));
|
assertThat("Request Input Content Received", inputContentReceived.get(), is(0L));
|
||||||
assertThat("Request Input Content Received less then initial buffer", inputContentReceived.get(), lessThanOrEqualTo((long)sizeActuallySent));
|
assertThat("Request Input Content Received less then initial buffer", inputContentReceived.get(), lessThanOrEqualTo((long)sizeActuallySent));
|
||||||
assertThat("Request Connection BytesIn should have some minimal data", inputBytesIn.get(), greaterThanOrEqualTo(1024L));
|
assertThat("Request Connection BytesIn should have some minimal data", inputBytesIn.get(), greaterThanOrEqualTo(1024L));
|
||||||
assertThat("Request Connection BytesIn read should not have read all of the data", inputBytesIn.get(), lessThanOrEqualTo((long)sizeActuallySent));
|
long requestBytesSent = sizeActuallySent + 512; // Take into account headers and chunked metadata.
|
||||||
|
assertThat("Request Connection BytesIn read should not have read all of the data", inputBytesIn.get(), lessThanOrEqualTo(requestBytesSent));
|
||||||
|
|
||||||
// Now provide rest
|
// Now provide rest
|
||||||
content.offer(ByteBuffer.wrap(compressedRequest, sizeActuallySent, compressedRequest.length - sizeActuallySent));
|
content.offer(ByteBuffer.wrap(compressedRequest, sizeActuallySent, compressedRequest.length - sizeActuallySent));
|
||||||
|
@ -351,7 +352,8 @@ public class GzipWithSendErrorTest
|
||||||
assertThat("Request Input Content Received", inputContentReceived.get(), read ? greaterThan(0L) : is(0L));
|
assertThat("Request Input Content Received", inputContentReceived.get(), read ? greaterThan(0L) : is(0L));
|
||||||
assertThat("Request Input Content Received less then initial buffer", inputContentReceived.get(), lessThanOrEqualTo((long)sizeActuallySent));
|
assertThat("Request Input Content Received less then initial buffer", inputContentReceived.get(), lessThanOrEqualTo((long)sizeActuallySent));
|
||||||
assertThat("Request Connection BytesIn should have some minimal data", inputBytesIn.get(), greaterThanOrEqualTo(1024L));
|
assertThat("Request Connection BytesIn should have some minimal data", inputBytesIn.get(), greaterThanOrEqualTo(1024L));
|
||||||
assertThat("Request Connection BytesIn read should not have read all of the data", inputBytesIn.get(), lessThanOrEqualTo((long)sizeActuallySent));
|
long requestBytesSent = sizeActuallySent + 512; // Take into account headers and chunked metadata.
|
||||||
|
assertThat("Request Connection BytesIn read should not have read all of the data", inputBytesIn.get(), lessThanOrEqualTo(requestBytesSent));
|
||||||
|
|
||||||
// Now provide rest
|
// Now provide rest
|
||||||
content.offer(ByteBuffer.wrap(compressedRequest, sizeActuallySent, compressedRequest.length - sizeActuallySent));
|
content.offer(ByteBuffer.wrap(compressedRequest, sizeActuallySent, compressedRequest.length - sizeActuallySent));
|
||||||
|
|
|
@ -0,0 +1,337 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// 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.test;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import javax.servlet.AsyncContext;
|
||||||
|
import javax.servlet.ReadListener;
|
||||||
|
import javax.servlet.ServletInputStream;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
|
import org.eclipse.jetty.client.api.ContentResponse;
|
||||||
|
import org.eclipse.jetty.client.util.AsyncRequestContent;
|
||||||
|
import org.eclipse.jetty.client.util.BytesRequestContent;
|
||||||
|
import org.eclipse.jetty.http.HttpMethod;
|
||||||
|
import org.eclipse.jetty.http.HttpStatus;
|
||||||
|
import org.eclipse.jetty.server.Handler;
|
||||||
|
import org.eclipse.jetty.server.HttpConnectionFactory;
|
||||||
|
import org.eclipse.jetty.server.HttpInput;
|
||||||
|
import org.eclipse.jetty.server.Request;
|
||||||
|
import org.eclipse.jetty.server.Server;
|
||||||
|
import org.eclipse.jetty.server.ServerConnector;
|
||||||
|
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||||
|
import org.eclipse.jetty.util.IO;
|
||||||
|
import org.eclipse.jetty.util.component.LifeCycle;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.hamcrest.core.Is.is;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
public class HttpInputInterceptorTest
|
||||||
|
{
|
||||||
|
private Server server;
|
||||||
|
private HttpConnectionFactory httpConnectionFactory = new HttpConnectionFactory();
|
||||||
|
private ServerConnector connector;
|
||||||
|
private HttpClient client;
|
||||||
|
|
||||||
|
private void start(Handler handler) throws Exception
|
||||||
|
{
|
||||||
|
server = new Server();
|
||||||
|
connector = new ServerConnector(server, 1, 1, httpConnectionFactory);
|
||||||
|
server.addConnector(connector);
|
||||||
|
|
||||||
|
server.setHandler(handler);
|
||||||
|
|
||||||
|
client = new HttpClient();
|
||||||
|
server.addBean(client);
|
||||||
|
|
||||||
|
server.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
public void dispose()
|
||||||
|
{
|
||||||
|
LifeCycle.stop(server);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBlockingReadInterceptorThrows() throws Exception
|
||||||
|
{
|
||||||
|
CountDownLatch serverLatch = new CountDownLatch(1);
|
||||||
|
start(new AbstractHandler()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response)
|
||||||
|
{
|
||||||
|
jettyRequest.setHandled(true);
|
||||||
|
|
||||||
|
// Throw immediately from the interceptor.
|
||||||
|
jettyRequest.getHttpInput().addInterceptor(content ->
|
||||||
|
{
|
||||||
|
throw new RuntimeException();
|
||||||
|
});
|
||||||
|
|
||||||
|
assertThrows(IOException.class, () -> IO.readBytes(request.getInputStream()));
|
||||||
|
serverLatch.countDown();
|
||||||
|
response.setStatus(HttpStatus.NO_CONTENT_204);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
|
||||||
|
.method(HttpMethod.POST)
|
||||||
|
.body(new BytesRequestContent(new byte[1]))
|
||||||
|
.timeout(5, TimeUnit.SECONDS)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
assertTrue(serverLatch.await(5, TimeUnit.SECONDS));
|
||||||
|
assertEquals(HttpStatus.NO_CONTENT_204, response.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBlockingReadInterceptorConsumesHalfThenThrows() throws Exception
|
||||||
|
{
|
||||||
|
CountDownLatch serverLatch = new CountDownLatch(1);
|
||||||
|
start(new AbstractHandler()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response)
|
||||||
|
{
|
||||||
|
jettyRequest.setHandled(true);
|
||||||
|
|
||||||
|
// Consume some and then throw.
|
||||||
|
AtomicInteger readCount = new AtomicInteger();
|
||||||
|
jettyRequest.getHttpInput().addInterceptor(content ->
|
||||||
|
{
|
||||||
|
int reads = readCount.incrementAndGet();
|
||||||
|
if (reads == 1)
|
||||||
|
{
|
||||||
|
ByteBuffer buffer = content.getByteBuffer();
|
||||||
|
int half = buffer.remaining() / 2;
|
||||||
|
int limit = buffer.limit();
|
||||||
|
buffer.limit(buffer.position() + half);
|
||||||
|
ByteBuffer chunk = buffer.slice();
|
||||||
|
buffer.position(buffer.limit());
|
||||||
|
buffer.limit(limit);
|
||||||
|
return new HttpInput.Content(chunk);
|
||||||
|
}
|
||||||
|
throw new RuntimeException();
|
||||||
|
});
|
||||||
|
|
||||||
|
assertThrows(IOException.class, () -> IO.readBytes(request.getInputStream()));
|
||||||
|
serverLatch.countDown();
|
||||||
|
response.setStatus(HttpStatus.NO_CONTENT_204);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
|
||||||
|
.method(HttpMethod.POST)
|
||||||
|
.body(new BytesRequestContent(new byte[1024]))
|
||||||
|
.timeout(5, TimeUnit.SECONDS)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
assertTrue(serverLatch.await(5, TimeUnit.SECONDS));
|
||||||
|
assertEquals(HttpStatus.NO_CONTENT_204, response.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAvailableReadInterceptorThrows() throws Exception
|
||||||
|
{
|
||||||
|
CountDownLatch interceptorLatch = new CountDownLatch(1);
|
||||||
|
start(new AbstractHandler()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||||
|
{
|
||||||
|
jettyRequest.setHandled(true);
|
||||||
|
|
||||||
|
// Throw immediately from the interceptor.
|
||||||
|
jettyRequest.getHttpInput().addInterceptor(content ->
|
||||||
|
{
|
||||||
|
interceptorLatch.countDown();
|
||||||
|
throw new RuntimeException();
|
||||||
|
});
|
||||||
|
|
||||||
|
int available = request.getInputStream().available();
|
||||||
|
assertEquals(0, available);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
|
||||||
|
.method(HttpMethod.POST)
|
||||||
|
.body(new BytesRequestContent(new byte[1]))
|
||||||
|
.timeout(5, TimeUnit.SECONDS)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
assertTrue(interceptorLatch.await(5, TimeUnit.SECONDS));
|
||||||
|
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsReadyReadInterceptorThrows() throws Exception
|
||||||
|
{
|
||||||
|
AsyncRequestContent asyncRequestContent = new AsyncRequestContent(ByteBuffer.wrap(new byte[1]));
|
||||||
|
CountDownLatch interceptorLatch = new CountDownLatch(1);
|
||||||
|
CountDownLatch readFailureLatch = new CountDownLatch(1);
|
||||||
|
start(new AbstractHandler()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||||
|
{
|
||||||
|
jettyRequest.setHandled(true);
|
||||||
|
|
||||||
|
AtomicBoolean onDataAvailable = new AtomicBoolean();
|
||||||
|
jettyRequest.getHttpInput().addInterceptor(content ->
|
||||||
|
{
|
||||||
|
if (onDataAvailable.get())
|
||||||
|
{
|
||||||
|
interceptorLatch.countDown();
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
AsyncContext asyncContext = request.startAsync();
|
||||||
|
ServletInputStream input = request.getInputStream();
|
||||||
|
input.setReadListener(new ReadListener()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onDataAvailable()
|
||||||
|
{
|
||||||
|
onDataAvailable.set(true);
|
||||||
|
|
||||||
|
// The input.setReadListener() call called the interceptor so there is content for read().
|
||||||
|
assertThat(input.isReady(), is(true));
|
||||||
|
assertDoesNotThrow(() -> assertEquals(0, input.read()));
|
||||||
|
|
||||||
|
// Make the client send more content so that the interceptor will be called again.
|
||||||
|
asyncRequestContent.offer(ByteBuffer.wrap(new byte[1]));
|
||||||
|
asyncRequestContent.close();
|
||||||
|
sleep(500); // Wait a little to make sure the content arrived by next isReady() call.
|
||||||
|
|
||||||
|
// The interceptor should throw, but isReady() should not.
|
||||||
|
assertThat(input.isReady(), is(true));
|
||||||
|
assertThrows(IOException.class, () -> assertEquals(0, input.read()));
|
||||||
|
readFailureLatch.countDown();
|
||||||
|
response.setStatus(HttpStatus.NO_CONTENT_204);
|
||||||
|
asyncContext.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAllDataRead()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(Throwable error)
|
||||||
|
{
|
||||||
|
error.printStackTrace();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
|
||||||
|
.method(HttpMethod.POST)
|
||||||
|
.body(asyncRequestContent)
|
||||||
|
.timeout(5, TimeUnit.SECONDS)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
assertTrue(interceptorLatch.await(5, TimeUnit.SECONDS));
|
||||||
|
assertTrue(readFailureLatch.await(5, TimeUnit.SECONDS));
|
||||||
|
assertEquals(HttpStatus.NO_CONTENT_204, response.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSetReadListenerReadInterceptorThrows() throws Exception
|
||||||
|
{
|
||||||
|
RuntimeException failure = new RuntimeException();
|
||||||
|
CountDownLatch interceptorLatch = new CountDownLatch(1);
|
||||||
|
start(new AbstractHandler()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||||
|
{
|
||||||
|
jettyRequest.setHandled(true);
|
||||||
|
|
||||||
|
// Throw immediately from the interceptor.
|
||||||
|
jettyRequest.getHttpInput().addInterceptor(content ->
|
||||||
|
{
|
||||||
|
interceptorLatch.countDown();
|
||||||
|
failure.addSuppressed(new Throwable());
|
||||||
|
throw failure;
|
||||||
|
});
|
||||||
|
|
||||||
|
AsyncContext asyncContext = request.startAsync();
|
||||||
|
ServletInputStream input = request.getInputStream();
|
||||||
|
input.setReadListener(new ReadListener()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onDataAvailable()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAllDataRead()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(Throwable error)
|
||||||
|
{
|
||||||
|
assertSame(failure, error.getCause());
|
||||||
|
response.setStatus(HttpStatus.NO_CONTENT_204);
|
||||||
|
asyncContext.complete();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
|
||||||
|
.method(HttpMethod.POST)
|
||||||
|
.body(new BytesRequestContent(new byte[1]))
|
||||||
|
.timeout(5, TimeUnit.SECONDS)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
assertTrue(interceptorLatch.await(5, TimeUnit.SECONDS));
|
||||||
|
assertEquals(HttpStatus.NO_CONTENT_204, response.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void sleep(long time)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Thread.sleep(time);
|
||||||
|
}
|
||||||
|
catch (InterruptedException x)
|
||||||
|
{
|
||||||
|
throw new RuntimeException(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -37,14 +37,14 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SameNodeLoadTest
|
* ConcurrencyTest
|
||||||
*
|
*
|
||||||
* This test performs multiple concurrent requests for the same session on the same node.
|
* This test performs multiple concurrent requests from different clients
|
||||||
|
* for the same session on the same node.
|
||||||
*/
|
*/
|
||||||
public class SameNodeLoadTest
|
public class ConcurrencyTest
|
||||||
{
|
{
|
||||||
@Test
|
@Test
|
||||||
@DisabledIfSystemProperty(named = "env", matches = "ci") // TODO: SLOW, needs review
|
|
||||||
public void testLoad() throws Exception
|
public void testLoad() throws Exception
|
||||||
{
|
{
|
||||||
DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory();
|
DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory();
|
||||||
|
@ -68,17 +68,18 @@ public class SameNodeLoadTest
|
||||||
{
|
{
|
||||||
String url = "http://localhost:" + port1 + contextPath + servletMapping;
|
String url = "http://localhost:" + port1 + contextPath + servletMapping;
|
||||||
|
|
||||||
//create session via first server
|
//create session upfront so the session id is established and
|
||||||
|
//can be shared to all clients
|
||||||
ContentResponse response1 = client.GET(url + "?action=init");
|
ContentResponse response1 = client.GET(url + "?action=init");
|
||||||
assertEquals(HttpServletResponse.SC_OK, response1.getStatus());
|
assertEquals(HttpServletResponse.SC_OK, response1.getStatus());
|
||||||
String sessionCookie = response1.getHeaders().get("Set-Cookie");
|
String sessionCookie = response1.getHeaders().get("Set-Cookie");
|
||||||
assertTrue(sessionCookie != null);
|
assertTrue(sessionCookie != null);
|
||||||
|
|
||||||
//simulate 10 clients making 100 requests each
|
//simulate 10 clients making 10 requests each for the same session
|
||||||
ExecutorService executor = Executors.newCachedThreadPool();
|
ExecutorService executor = Executors.newCachedThreadPool();
|
||||||
int clientsCount = 10;
|
int clientsCount = 10;
|
||||||
CyclicBarrier barrier = new CyclicBarrier(clientsCount + 1);
|
CyclicBarrier barrier = new CyclicBarrier(clientsCount + 1);
|
||||||
int requestsCount = 100;
|
int requestsCount = 10;
|
||||||
Worker[] workers = new Worker[clientsCount];
|
Worker[] workers = new Worker[clientsCount];
|
||||||
for (int i = 0; i < clientsCount; ++i)
|
for (int i = 0; i < clientsCount; ++i)
|
||||||
{
|
{
|
||||||
|
@ -96,7 +97,9 @@ public class SameNodeLoadTest
|
||||||
System.err.println("Elapsed ms:" + elapsed);
|
System.err.println("Elapsed ms:" + elapsed);
|
||||||
executor.shutdownNow();
|
executor.shutdownNow();
|
||||||
|
|
||||||
// Perform one request to get the result
|
// Perform one request to get the result - the session
|
||||||
|
// should have counted all the requests by incrementing
|
||||||
|
// a counter in an attribute.
|
||||||
Request request = client.newRequest(url + "?action=result");
|
Request request = client.newRequest(url + "?action=result");
|
||||||
ContentResponse response2 = request.send();
|
ContentResponse response2 = request.send();
|
||||||
assertEquals(HttpServletResponse.SC_OK, response2.getStatus());
|
assertEquals(HttpServletResponse.SC_OK, response2.getStatus());
|
Loading…
Reference in New Issue