From 07a97da01069f9cc952ca47aee6570da2dc19eb0 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Wed, 7 Aug 2019 10:59:50 +0200 Subject: [PATCH 001/101] Draft 1 for week 2. Signed-off-by: Simone Bordet --- jetty-documentation/pom.xml | 1 + .../main/asciidoc/embedded-guide/io-arch.adoc | 85 +++++- .../embedded/SelectorManagerDocSnippets.java | 264 ++++++++++++++++++ .../client/SelectorManagerDocSnippets.java | 61 ---- 4 files changed, 344 insertions(+), 67 deletions(-) create mode 100644 jetty-documentation/src/main/java/embedded/SelectorManagerDocSnippets.java delete mode 100644 jetty-documentation/src/main/java/embedded/client/SelectorManagerDocSnippets.java diff --git a/jetty-documentation/pom.xml b/jetty-documentation/pom.xml index 16ece04c3ea..7af6995bf49 100644 --- a/jetty-documentation/pom.xml +++ b/jetty-documentation/pom.xml @@ -149,6 +149,7 @@ ${basedir}/src/main/asciidoc/contribution-guide index.adoc ${project.build.directory}/html/contribution-guide + coderay diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/io-arch.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/io-arch.adoc index 5bcc7c6019a..e677a5958fe 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/io-arch.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/io-arch.adoc @@ -145,7 +145,7 @@ The Jetty I/O library use Java NIO to handle I/O, so that I/O is non-blocking. At the Java NIO level, in order to be notified when a `SocketChannel` has data to be read, the `SelectionKey.OP_READ` flag must be set. -In the Jetty I/O library, you can call `AbstractEndPoint.fillInterested(Callback)` +In the Jetty I/O library, you can call `EndPoint.fillInterested(Callback)` to declare interest in the "read" (or "fill") event, and the `Callback` parameter is the object that is notified when such event occurs. @@ -158,9 +158,82 @@ to write the ``ByteBuffer``s and the `Callback` parameter is the object that is notified when the whole write is finished(i.e. _all_ ``ByteBuffer``s have been fully written). -[[io-arch-connection]] -=== Jetty I/O: Implementing `Connection` +The `EndPoint` APIs abstract out the Java NIO details by providing non-blocking +APIs based on `Callback` objects for I/O operations. +The `EndPoint` APIs are typically called by `Connection` implementations, see +link:#io-arch-connection[this section]. -Implementing a `Connection` is how you deserialize incoming bytes into objects -that can be used by more abstract layers, for example a HTTP request object or -a WebSocket frame object. +[[io-arch-connection]] +=== Jetty I/O: `Connection` + +`Connection` is the abstraction that deserializes incoming bytes into objects, +for example a HTTP request object or a WebSocket frame object, that can be used +by more abstract layers. + +`Connection` instances have two lifecycle methods: + +* `Connection.onOpen()`, invoked when the `Connection` is associated with the +`EndPoint` +* `Connection.onClose(Throwable)`, invoked when the `Connection` is disassociated +from the `EndPoint`, where the `Throwable` parameter indicates whether the +disassociation was due to an error + +When a `Connection` is first created, it is not registered for any Java NIO +event. +It is therefore typical to implement `onOpen()` to call +`EndPoint.fillInterested(Callback)` so that the `Connection` declares interest +for read events and it is invoked (via the `Callback`) when the read event +happens. + +Abstract class `AbstractConnection` partially implements `Connection` and +provides simpler APIs. The example below shows a typical implementation that +extends `AbstractConnection`: + +[source,java,indent=0] +---- +include::{docbits}/embedded/SelectorManagerDocSnippets.java[tags=connection] +---- + +[[io-arch-echo]] +=== Jetty I/O: Network Echo + +With the concepts above it is now possible to write a simple, fully non-blocking, +`Connection` implementation that simply echoes the bytes that it reads back +to the other peer. + +A naive, but wrong, implementation may be the following: + +[source,java,indent=0] +---- +include::{docbits}/embedded/SelectorManagerDocSnippets.java[tags=echo-wrong] +---- + +NOTE: The implementation above is wrong and leads to `StackOverflowError`. + +The problem with this implementation is that if the writes always complete +synchronously (i.e. without being delayed by TCP congestion), you end up with +this sequence of calls: + +---- +Connection.onFillable() + EndPoint.write() + Callback.succeeded() + Connection.onFillable() + EndPoint.write() + Callback.succeeded() + ... +---- + +which leads to `StackOverflowException`. + +This is a typical side effect of asynchronous programming using non-blocking +APIs, and happens in the Jetty I/O library as well. + +A correct implementation is the following: + +[source,java,indent=0] +---- +include::{docbits}/embedded/SelectorManagerDocSnippets.java[tags=echo-correct] +---- + +The correct implementation performs the reads diff --git a/jetty-documentation/src/main/java/embedded/SelectorManagerDocSnippets.java b/jetty-documentation/src/main/java/embedded/SelectorManagerDocSnippets.java new file mode 100644 index 00000000000..5e4f4b36c8f --- /dev/null +++ b/jetty-documentation/src/main/java/embedded/SelectorManagerDocSnippets.java @@ -0,0 +1,264 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package embedded; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; +import java.nio.channels.WritePendingException; +import java.util.Map; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicInteger; + +import org.eclipse.jetty.io.AbstractConnection; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.SelectorManager; +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.Callback; + +public class SelectorManagerDocSnippets +{ + // tag::connect[] + public void connect(SelectorManager selectorManager, Map context) throws IOException + { + String host = "host"; + int port = 8080; + + // Create an unconnected SocketChannel. + SocketChannel socketChannel = SocketChannel.open(); + socketChannel.configureBlocking(false); + + // Connect and register to Jetty. + if (socketChannel.connect(new InetSocketAddress(host, port))) + selectorManager.accept(socketChannel, context); + else + selectorManager.connect(socketChannel, context); + } + // end::connect[] + + // tag::accept[] + public void accept(ServerSocketChannel acceptor, SelectorManager selectorManager) throws IOException + { + // Wait until a client connects. + SocketChannel socketChannel = acceptor.accept(); + socketChannel.configureBlocking(false); + + // Accept and register to Jetty. + Object attachment = null; + selectorManager.accept(socketChannel, attachment); + } + // end::accept[] + + public void connection() + { + // tag::connection[] + // Extend AbstractConnection to inherit basic implementation. + class MyConnection extends AbstractConnection + { + public MyConnection(EndPoint endPoint, Executor executor) + { + super(endPoint, executor); + } + + @Override + public void onOpen() + { + super.onOpen(); + + // Declare interest for fill events. + fillInterested(); + } + + @Override + public void onFillable() + { + // Called when a fill event happens. + } + } + // end::connection[] + } + + public void echoWrong() + { + // tag::echo-wrong[] + class EchoConnection extends AbstractConnection implements Callback + { + public EchoConnection(EndPoint endp, Executor executor) + { + super(endp, executor); + } + + @Override + public void onOpen() + { + super.onOpen(); + + // Declare interest for fill events. + fillInterested(); + } + + @Override + public void onFillable() + { + try + { + ByteBuffer buffer = BufferUtil.allocate(1024); + int filled = getEndPoint().fill(buffer); + if (filled > 0) + { + // Filled some bytes, echo them back. + getEndPoint().write(this, buffer); + } + else if (filled == 0) + { + // No more bytes to fill, declare + // again interest for fill events. + fillInterested(); + } + else + { + // The other peer closed the + // connection, close it back. + getEndPoint().close(); + } + } + catch (Exception x) + { + getEndPoint().close(x); + } + } + + @Override + public void succeeded() + { + // The write is complete, fill again. + onFillable(); + } + + @Override + public void failed(Throwable x) + { + getEndPoint().close(x); + } + } + // end::echo-wrong[] + } + + public void echoCorrect() + { + // tag::echo-correct[] + class EchoConnection extends AbstractConnection implements Callback + { + public static final int IDLE = 0; + public static final int WRITING = 1; + public static final int PENDING = 2; + + private final AtomicInteger state = new AtomicInteger(); + + public EchoConnection(EndPoint endp, Executor executor) + { + super(endp, executor); + } + + @Override + public void onOpen() + { + super.onOpen(); + + // Declare interest for fill events. + fillInterested(); + } + + @Override + public void onFillable() + { + try + { + ByteBuffer buffer = BufferUtil.allocate(1024); + while (true) + { + int filled = getEndPoint().fill(buffer); + if (filled > 0) + { + // We have filled some bytes, echo them back. + if (write(buffer)) + { + // If the write completed, continue to fill. + continue; + } + } + else if (filled == 0) + { + // No more bytes to read, declare + // again interest for fill events. + fillInterested(); + } + else + { + // The other peer closed the connection. + close(); + } + break; + } + } + catch (Throwable x) + { + getEndPoint().close(x); + } + } + + private boolean write(ByteBuffer buffer) + { + // Check if we are writing concurrently. + if (!state.compareAndSet(IDLE, WRITING)) + throw new WritePendingException(); + + // Write the buffer using "this" as a callback. + getEndPoint().write(this, buffer); + + // Check if the write is already completed. + boolean writeIsPending = state.compareAndSet(WRITING, PENDING); + + // Return true if the write was completed. + return !writeIsPending; + } + + @Override + public void succeeded() + { + // The write is complete, reset the state. + int prevState = state.getAndSet(IDLE); + + // If the write was pending we need + // to resume reading from the network. + if (prevState == PENDING) + onFillable(); + } + + @Override + public void failed(Throwable x) + { + getEndPoint().close(x); + } + } + // end::echo-correct[] + } +} diff --git a/jetty-documentation/src/main/java/embedded/client/SelectorManagerDocSnippets.java b/jetty-documentation/src/main/java/embedded/client/SelectorManagerDocSnippets.java deleted file mode 100644 index a5b2239fb40..00000000000 --- a/jetty-documentation/src/main/java/embedded/client/SelectorManagerDocSnippets.java +++ /dev/null @@ -1,61 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd. -// ------------------------------------------------------------------------ -// All rights reserved. This program and the accompanying materials -// are made available under the terms of the Eclipse Public License v1.0 -// and Apache License v2.0 which accompanies this distribution. -// -// The Eclipse Public License is available at -// http://www.eclipse.org/legal/epl-v10.html -// -// The Apache License v2.0 is available at -// http://www.opensource.org/licenses/apache2.0.php -// -// You may elect to redistribute this code under either of these licenses. -// ======================================================================== -// - -package embedded.client; - -import java.io.IOException; -import java.net.InetSocketAddress; -import java.nio.channels.ServerSocketChannel; -import java.nio.channels.SocketChannel; -import java.util.Map; - -import org.eclipse.jetty.io.SelectorManager; - -public class SelectorManagerDocSnippets -{ - // tag::connect[] - public void connect(SelectorManager selectorManager, Map context) throws IOException - { - String host = "host"; - int port = 8080; - - // Create an unconnected SocketChannel. - SocketChannel socketChannel = SocketChannel.open(); - socketChannel.configureBlocking(false); - - // Connect and register to Jetty. - if (socketChannel.connect(new InetSocketAddress(host, port))) - selectorManager.accept(socketChannel, context); - else - selectorManager.connect(socketChannel, context); - } - // end::connect[] - - // tag::accept[] - public void accept(ServerSocketChannel acceptor, SelectorManager selectorManager) throws IOException - { - // Wait until a client connects. - SocketChannel socketChannel = acceptor.accept(); - socketChannel.configureBlocking(false); - - // Accept and register to Jetty. - Object attachment = null; - selectorManager.accept(socketChannel, attachment); - } - // end::accept[] -} From 31ba922ce05bb3d25d65dd13b40814dc92bd46c6 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Sun, 11 Aug 2019 16:03:45 +0200 Subject: [PATCH 002/101] Draft 2 for week 2. Signed-off-by: Simone Bordet --- .../main/asciidoc/embedded-guide/io-arch.adoc | 37 ++++++++++++++----- .../embedded/SelectorManagerDocSnippets.java | 12 ++++-- 2 files changed, 37 insertions(+), 12 deletions(-) diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/io-arch.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/io-arch.adoc index e677a5958fe..8fd79a91d3c 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/io-arch.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/io-arch.adoc @@ -101,9 +101,10 @@ The `EndPoint` and `Connection` pairs can be chained, for example in case of encrypted communication using the TLS protocol. There is an `EndPoint` and `Connection` TLS pair where the `EndPoint` reads the encrypted bytes from the network and the `Connection` decrypts them; next in the -chain there is an `EndPoint` and `Connection` pair where the `EndPoint` provides -decrypted bytes and the `Connection` deserializes them into specific protocol -objects (for example a HTTP/1.1 request object). +chain there is an `EndPoint` and `Connection` pair where the `EndPoint` "reads" +decrypted bytes (provided by the previous `Connection`) and the `Connection` +deserializes them into specific protocol objects (for example HTTP/2 frame +objects). Certain protocols, such as WebSocket, start the communication with the server using one protocol (e.g. HTTP/1.1), but then change the communication to use @@ -155,8 +156,8 @@ is therefore writable again, the `SelectionKey.OP_WRITE` flag must be set. In the Jetty I/O library, you can call `EndPoint.write(Callback, ByteBuffer...)` to write the ``ByteBuffer``s and the `Callback` parameter is the object that is -notified when the whole write is finished(i.e. _all_ ``ByteBuffer``s have been -fully written). +notified when the whole write is finished (i.e. _all_ ``ByteBuffer``s have been +fully written, even if they are delayed by TCP congestion/uncongestion). The `EndPoint` APIs abstract out the Java NIO details by providing non-blocking APIs based on `Callback` objects for I/O operations. @@ -176,7 +177,8 @@ by more abstract layers. `EndPoint` * `Connection.onClose(Throwable)`, invoked when the `Connection` is disassociated from the `EndPoint`, where the `Throwable` parameter indicates whether the -disassociation was due to an error +disassociation was normal (when the parameter is `null`) or was due to an error +(when the parameter is not `null`) When a `Connection` is first created, it is not registered for any Java NIO event. @@ -208,7 +210,7 @@ A naive, but wrong, implementation may be the following: include::{docbits}/embedded/SelectorManagerDocSnippets.java[tags=echo-wrong] ---- -NOTE: The implementation above is wrong and leads to `StackOverflowError`. +WARNING: The implementation above is wrong and leads to `StackOverflowError`. The problem with this implementation is that if the writes always complete synchronously (i.e. without being delayed by TCP congestion), you end up with @@ -224,7 +226,7 @@ Connection.onFillable() ... ---- -which leads to `StackOverflowException`. +which leads to `StackOverflowError`. This is a typical side effect of asynchronous programming using non-blocking APIs, and happens in the Jetty I/O library as well. @@ -236,4 +238,21 @@ A correct implementation is the following: include::{docbits}/embedded/SelectorManagerDocSnippets.java[tags=echo-correct] ---- -The correct implementation performs the reads +The correct implementation performs consecutive reads in a loop (rather than +recursively), but _only_ if the correspondent write is completed successfully. + +In order to detect whether the write is completed, a concurrent state machine +is used. This is necessary because the notification of the completion of the +write may happen in a different thread, while the original writing thread +may still be changing the state. + +The original writing thread starts moves the state from `IDLE` to `WRITING`, +then issues the actual `write()` call. +The original writing thread then assumes that the `write()` did not complete +and tries to move to the `PENDING` state just after the `write()`. +If it fails to move from the `WRITING` state to the `PENDING` state, it means +that the write was completed. +Otherwise, the write is now `PENDING` and waiting for the callback to be +notified of the completion at a later time. +When the callback is notified of the `write()` completion, it checks whether +the `write()` was `PENDING`, and if it was it resumes reading. diff --git a/jetty-documentation/src/main/java/embedded/SelectorManagerDocSnippets.java b/jetty-documentation/src/main/java/embedded/SelectorManagerDocSnippets.java index 5e4f4b36c8f..3844c4f9dab 100644 --- a/jetty-documentation/src/main/java/embedded/SelectorManagerDocSnippets.java +++ b/jetty-documentation/src/main/java/embedded/SelectorManagerDocSnippets.java @@ -99,9 +99,9 @@ public class SelectorManagerDocSnippets public void echoWrong() { // tag::echo-wrong[] - class EchoConnection extends AbstractConnection implements Callback + class WrongEchoConnection extends AbstractConnection implements Callback { - public EchoConnection(EndPoint endp, Executor executor) + public WrongEchoConnection(EndPoint endp, Executor executor) { super(endp, executor); } @@ -204,19 +204,25 @@ public class SelectorManagerDocSnippets // If the write completed, continue to fill. continue; } + else + { + // The write is pending, return to wait for completion. + return; + } } else if (filled == 0) { // No more bytes to read, declare // again interest for fill events. fillInterested(); + return; } else { // The other peer closed the connection. close(); + return; } - break; } } catch (Throwable x) From 1393c0e92bc16efb852624bd3de3a575cafddfa7 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Wed, 19 Feb 2020 15:46:21 +0100 Subject: [PATCH 003/101] Issue #4400 - Review HttpClient's ContentProvider. Introduced Request.Content with a reactive model to provide request content. Introduced RequestContentAdapter to wrap ContentProviders into Request.Content. Updated implementation to use the reactive model rather than the old pull model. Reimplemented all ContentProviders in terms of Request.Content. Converted most of the tests from ContentProvider to Request.Content. Updated proxy servlets and documentation. Signed-off-by: Simone Bordet --- .../jetty/embedded/ExampleServerTest.java | 4 +- .../jetty/client/AsyncContentProvider.java | 4 + .../client/AuthenticationProtocolHandler.java | 5 +- .../org/eclipse/jetty/client/HttpClient.java | 6 +- .../eclipse/jetty/client/HttpConnection.java | 21 +- .../org/eclipse/jetty/client/HttpContent.java | 237 ------- .../eclipse/jetty/client/HttpReceiver.java | 2 +- .../org/eclipse/jetty/client/HttpRequest.java | 27 +- .../org/eclipse/jetty/client/HttpSender.java | 642 ++++-------------- .../eclipse/jetty/client/RequestNotifier.java | 4 +- .../jetty/client/api/ContentProvider.java | 19 + .../org/eclipse/jetty/client/api/Request.java | 151 ++++ .../jetty/client/http/HttpSenderOverHTTP.java | 222 +++--- .../internal/RequestContentAdapter.java | 329 +++++++++ .../client/util/AbstractRequestContent.java | 250 +++++++ .../util/AbstractTypedContentProvider.java | 4 + .../client/util/AsyncRequestContent.java | 385 +++++++++++ .../util/ByteBufferContentProvider.java | 3 + .../client/util/ByteBufferRequestContent.java | 94 +++ .../client/util/BytesContentProvider.java | 3 + .../client/util/BytesRequestContent.java | 91 +++ .../client/util/DeferredContentProvider.java | 4 + .../client/util/FormContentProvider.java | 3 + .../jetty/client/util/FormRequestContent.java | 78 +++ .../util/InputStreamContentProvider.java | 3 + .../util/InputStreamRequestContent.java | 146 ++++ .../util/InputStreamResponseListener.java | 23 +- .../client/util/MultiPartContentProvider.java | 3 + .../client/util/MultiPartRequestContent.java | 380 +++++++++++ .../util/OutputStreamContentProvider.java | 3 + .../util/OutputStreamRequestContent.java | 125 ++++ .../client/util/PathContentProvider.java | 3 + .../jetty/client/util/PathRequestContent.java | 170 +++++ .../client/util/StringContentProvider.java | 3 + .../client/util/StringRequestContent.java | 52 ++ .../client/ClientConnectionCloseTest.java | 10 +- .../jetty/client/ConnectionPoolTest.java | 4 +- .../client/HttpClientAuthenticationTest.java | 78 +-- .../jetty/client/HttpClientFailureTest.java | 10 +- .../jetty/client/HttpClientRedirectTest.java | 4 +- .../client/HttpClientSynchronizationTest.java | 20 +- .../eclipse/jetty/client/HttpClientTest.java | 79 +-- ...pClientUploadDuringServerShutdownTest.java | 4 +- .../client/HttpConnectionLifecycleTest.java | 4 +- .../jetty/client/HttpRequestAbortTest.java | 6 +- .../jetty/client/HttpResponseAbortTest.java | 17 +- .../org/eclipse/jetty/client/api/Usage.java | 32 +- .../client/http/HttpSenderOverHTTPTest.java | 8 +- .../client/util/AsyncRequestContentTest.java | 151 ++++ .../util/DeferredContentProviderTest.java | 151 ---- .../util/InputStreamContentProviderTest.java | 161 ----- .../client/util/InputStreamContentTest.java | 266 ++++++++ ...derTest.java => MultiPartContentTest.java} | 142 +--- .../util/RequestContentBehaviorTest.java | 342 ++++++++++ .../client/util/SPNEGOAuthenticationTest.java | 2 +- .../client/util/TypedContentProviderTest.java | 4 +- .../server/clients/http/http-client-api.adoc | 43 +- .../fcgi/client/http/HttpSenderOverFCGI.java | 30 +- .../jetty/fcgi/server/HttpClientTest.java | 43 +- .../client/http/HttpSenderOverHTTP2.java | 107 +-- .../client/http/RequestTrailersTest.java | 24 +- .../security/openid/OpenIdCredentials.java | 6 +- .../TestJettyOSGiBootWithAnnotations.java | 10 +- .../jetty/proxy/AsyncMiddleManServlet.java | 59 +- .../jetty/proxy/AsyncProxyServlet.java | 34 +- .../org/eclipse/jetty/proxy/ProxyServlet.java | 124 ++-- .../proxy/AsyncMiddleManServletTest.java | 67 +- .../proxy/ForwardProxyTLSServerTest.java | 6 +- .../jetty/proxy/ProxyServletFailureTest.java | 28 +- .../jetty/proxy/ProxyServletLoadTest.java | 12 +- .../eclipse/jetty/proxy/ProxyServletTest.java | 33 +- .../server/MultiPartFormInputStreamTest.java | 2 +- .../org/eclipse/jetty/servlet/FormTest.java | 10 +- .../jetty/servlet/MultiPartServletTest.java | 24 +- .../jetty/webapp/HugeResourceTest.java | 20 +- .../tests/distribution/DemoBaseTests.java | 6 +- .../jetty/http/client/AsyncIOServletTest.java | 78 +-- .../http/client/AsyncRequestContentTest.java | 94 ++- .../http/client/ConnectionStatisticsTest.java | 4 +- .../http/client/HttpClientContinueTest.java | 94 +-- .../jetty/http/client/HttpClientLoadTest.java | 6 +- .../http/client/HttpClientStreamTest.java | 221 ++---- .../jetty/http/client/HttpClientTest.java | 9 +- .../http/client/HttpClientTimeoutTest.java | 8 +- .../jetty/http/client/HttpTrailersTest.java | 4 +- .../jetty/http/client/ServerTimeoutsTest.java | 72 +- .../eclipse/jetty/test/DigestPostTest.java | 52 +- .../eclipse/jetty/JdbcLoginServiceTest.java | 9 +- .../session/RequestDispatchedSessionTest.java | 5 +- 89 files changed, 4146 insertions(+), 2192 deletions(-) delete mode 100644 jetty-client/src/main/java/org/eclipse/jetty/client/HttpContent.java create mode 100644 jetty-client/src/main/java/org/eclipse/jetty/client/internal/RequestContentAdapter.java create mode 100644 jetty-client/src/main/java/org/eclipse/jetty/client/util/AbstractRequestContent.java create mode 100644 jetty-client/src/main/java/org/eclipse/jetty/client/util/AsyncRequestContent.java create mode 100644 jetty-client/src/main/java/org/eclipse/jetty/client/util/ByteBufferRequestContent.java create mode 100644 jetty-client/src/main/java/org/eclipse/jetty/client/util/BytesRequestContent.java create mode 100644 jetty-client/src/main/java/org/eclipse/jetty/client/util/FormRequestContent.java create mode 100644 jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamRequestContent.java create mode 100644 jetty-client/src/main/java/org/eclipse/jetty/client/util/MultiPartRequestContent.java create mode 100644 jetty-client/src/main/java/org/eclipse/jetty/client/util/OutputStreamRequestContent.java create mode 100644 jetty-client/src/main/java/org/eclipse/jetty/client/util/PathRequestContent.java create mode 100644 jetty-client/src/main/java/org/eclipse/jetty/client/util/StringRequestContent.java create mode 100644 jetty-client/src/test/java/org/eclipse/jetty/client/util/AsyncRequestContentTest.java delete mode 100644 jetty-client/src/test/java/org/eclipse/jetty/client/util/DeferredContentProviderTest.java delete mode 100644 jetty-client/src/test/java/org/eclipse/jetty/client/util/InputStreamContentProviderTest.java create mode 100644 jetty-client/src/test/java/org/eclipse/jetty/client/util/InputStreamContentTest.java rename jetty-client/src/test/java/org/eclipse/jetty/client/util/{MultiPartContentProviderTest.java => MultiPartContentTest.java} (78%) create mode 100644 jetty-client/src/test/java/org/eclipse/jetty/client/util/RequestContentBehaviorTest.java diff --git a/examples/embedded/src/test/java/org/eclipse/jetty/embedded/ExampleServerTest.java b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/ExampleServerTest.java index 56d9e32007b..c1ef6421593 100644 --- a/examples/embedded/src/test/java/org/eclipse/jetty/embedded/ExampleServerTest.java +++ b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/ExampleServerTest.java @@ -21,7 +21,7 @@ package org.eclipse.jetty.embedded; import java.net.URI; import org.eclipse.jetty.client.api.ContentResponse; -import org.eclipse.jetty.client.util.StringContentProvider; +import org.eclipse.jetty.client.util.StringRequestContent; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.server.Server; @@ -75,7 +75,7 @@ public class ExampleServerTest extends AbstractEmbeddedTest String postBody = "Greetings from " + ExampleServerTest.class; ContentResponse response = client.newRequest(uri) .method(HttpMethod.POST) - .content(new StringContentProvider(postBody)) + .body(new StringRequestContent(postBody)) .send(); // Check the response status code diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/AsyncContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/AsyncContentProvider.java index 417d27d2e5c..ace7d76a9cc 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/AsyncContentProvider.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/AsyncContentProvider.java @@ -21,10 +21,14 @@ package org.eclipse.jetty.client; import java.util.EventListener; import org.eclipse.jetty.client.api.ContentProvider; +import org.eclipse.jetty.client.api.Request; /** * A {@link ContentProvider} that notifies listeners that content is available. + * + * @deprecated no replacement, use {@link Request.Content} instead. */ +@Deprecated public interface AsyncContentProvider extends ContentProvider { /** diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java b/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java index 878a639c2a1..c0cd8a0882c 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java @@ -30,7 +30,6 @@ import java.util.regex.Pattern; import org.eclipse.jetty.client.api.Authentication; import org.eclipse.jetty.client.api.Authentication.HeaderInfo; import org.eclipse.jetty.client.api.Connection; -import org.eclipse.jetty.client.api.ContentProvider; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; @@ -187,8 +186,8 @@ public abstract class AuthenticationProtocolHandler implements ProtocolHandler return; } - ContentProvider requestContent = request.getContent(); - if (requestContent != null && !requestContent.isReproducible()) + Request.Content requestContent = request.getBody(); + if (!requestContent.isReproducible()) { if (LOG.isDebugEnabled()) LOG.debug("Request content not reproducible for {}", request); diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java index bbd56bb12b8..5a45927d063 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java @@ -53,7 +53,7 @@ import org.eclipse.jetty.client.api.Destination; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP; -import org.eclipse.jetty.client.util.FormContentProvider; +import org.eclipse.jetty.client.util.FormRequestContent; import org.eclipse.jetty.http.HttpCompliance; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpHeader; @@ -379,7 +379,7 @@ public class HttpClient extends ContainerLifeCycle */ public ContentResponse FORM(URI uri, Fields fields) throws InterruptedException, ExecutionException, TimeoutException { - return POST(uri).content(new FormContentProvider(fields)).send(); + return POST(uri).body(new FormRequestContent(fields)).send(); } /** @@ -446,7 +446,7 @@ public class HttpClient extends ContainerLifeCycle Request newRequest = newHttpRequest(oldRequest.getConversation(), newURI); newRequest.method(oldRequest.getMethod()) .version(oldRequest.getVersion()) - .content(oldRequest.getContent()) + .body(oldRequest.getBody()) .idleTimeout(oldRequest.getIdleTimeout(), TimeUnit.MILLISECONDS) .timeout(oldRequest.getTimeout(), TimeUnit.MILLISECONDS) .followRedirects(oldRequest.isFollowRedirects()); diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java index 1cf2ef0b6a9..d719a7bf4fe 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java @@ -27,9 +27,9 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.eclipse.jetty.client.api.Authentication; -import org.eclipse.jetty.client.api.ContentProvider; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; +import org.eclipse.jetty.client.util.BytesRequestContent; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpVersion; @@ -129,11 +129,6 @@ public abstract class HttpConnection implements IConnection if (normalized) return; - HttpVersion version = request.getVersion(); - HttpFields headers = request.getHeaders(); - ContentProvider content = request.getContent(); - ProxyConfiguration.Proxy proxy = destination.getProxy(); - // Make sure the path is there String path = request.getPath(); if (path.trim().length() == 0) @@ -144,6 +139,7 @@ public abstract class HttpConnection implements IConnection URI uri = request.getURI(); + ProxyConfiguration.Proxy proxy = destination.getProxy(); if (proxy instanceof HttpProxy && !HttpClient.isSchemeSecure(request.getScheme()) && uri != null) { path = uri.toString(); @@ -151,6 +147,8 @@ public abstract class HttpConnection implements IConnection } // If we are HTTP 1.1, add the Host header + HttpVersion version = request.getVersion(); + HttpFields headers = request.getHeaders(); if (version.getVersion() <= 11) { if (!headers.containsKey(HttpHeader.HOST.asString())) @@ -158,13 +156,16 @@ public abstract class HttpConnection implements IConnection } // Add content headers - if (content != null) + Request.Content content = request.getBody(); + if (content == null) + { + request.body(new BytesRequestContent()); + } + else { if (!headers.containsKey(HttpHeader.CONTENT_TYPE.asString())) { - String contentType = null; - if (content instanceof ContentProvider.Typed) - contentType = ((ContentProvider.Typed)content).getContentType(); + String contentType = content.getContentType(); if (contentType != null) { headers.put(HttpHeader.CONTENT_TYPE, contentType); diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpContent.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpContent.java deleted file mode 100644 index 9559b43eece..00000000000 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpContent.java +++ /dev/null @@ -1,237 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under -// the terms of the Eclipse Public License 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0 -// -// This Source Code may also be made available under the following -// Secondary Licenses when the conditions for such availability set -// forth in the Eclipse Public License, v. 2.0 are satisfied: -// the Apache License v2.0 which is available at -// https://www.apache.org/licenses/LICENSE-2.0 -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package org.eclipse.jetty.client; - -import java.io.Closeable; -import java.nio.ByteBuffer; -import java.util.Collections; -import java.util.Iterator; - -import org.eclipse.jetty.client.api.ContentProvider; -import org.eclipse.jetty.util.BufferUtil; -import org.eclipse.jetty.util.Callback; -import org.eclipse.jetty.util.IO; -import org.eclipse.jetty.util.log.Log; -import org.eclipse.jetty.util.log.Logger; - -/** - * {@link HttpContent} is a stateful, linear representation of the request content provided - * by a {@link ContentProvider} that can be traversed one-way to obtain content buffers to - * send to an HTTP server. - *

- * {@link HttpContent} offers the notion of a one-way cursor to traverse the content. - * The cursor starts in a virtual "before" position and can be advanced using {@link #advance()} - * until it reaches a virtual "after" position where the content is fully consumed. - *

- *      +---+  +---+  +---+  +---+  +---+
- *      |   |  |   |  |   |  |   |  |   |
- *      +---+  +---+  +---+  +---+  +---+
- *   ^           ^                    ^    ^
- *   |           | --> advance()      |    |
- *   |           |                  last   |
- *   |           |                         |
- * before        |                        after
- *               |
- *            current
- * 
- * At each valid (non-before and non-after) cursor position, {@link HttpContent} provides the following state: - *
    - *
  • the buffer containing the content to send, via {@link #getByteBuffer()}
  • - *
  • a copy of the content buffer that can be used for notifications, via {@link #getContent()}
  • - *
  • whether the buffer to write is the last one, via {@link #isLast()}
  • - *
- * {@link HttpContent} may not have content, if the related {@link ContentProvider} is {@code null}, and this - * is reflected by {@link #hasContent()}. - *

- * {@link HttpContent} may have {@link AsyncContentProvider deferred content}, in which case {@link #advance()} - * moves the cursor to a position that provides {@code null} {@link #getByteBuffer() buffer} and - * {@link #getContent() content}. When the deferred content is available, a further call to {@link #advance()} - * will move the cursor to a position that provides non {@code null} buffer and content. - */ -public class HttpContent implements Callback, Closeable -{ - private static final Logger LOG = Log.getLogger(HttpContent.class); - private static final ByteBuffer AFTER = ByteBuffer.allocate(0); - private static final ByteBuffer CLOSE = ByteBuffer.allocate(0); - - private final ContentProvider provider; - private final Iterator iterator; - private ByteBuffer buffer; - private ByteBuffer content; - private boolean last; - - public HttpContent(ContentProvider provider) - { - this.provider = provider; - this.iterator = provider == null ? Collections.emptyIterator() : provider.iterator(); - } - - /** - * @return true if the buffer is the sentinel instance {@link CLOSE} - */ - private static boolean isTheCloseBuffer(ByteBuffer buffer) - { - @SuppressWarnings("ReferenceEquality") - boolean isTheCloseBuffer = (buffer == CLOSE); - return isTheCloseBuffer; - } - - /** - * @return whether there is any content at all - */ - public boolean hasContent() - { - return provider != null; - } - - /** - * @return whether the cursor points to the last content - */ - public boolean isLast() - { - return last; - } - - /** - * @return the {@link ByteBuffer} containing the content at the cursor's position - */ - public ByteBuffer getByteBuffer() - { - return buffer; - } - - /** - * @return a {@link ByteBuffer#slice()} of {@link #getByteBuffer()} at the cursor's position - */ - public ByteBuffer getContent() - { - return content; - } - - /** - * Advances the cursor to the next block of content. - *

- * The next block of content may be valid (which yields a non-null buffer - * returned by {@link #getByteBuffer()}), but may also be deferred - * (which yields a null buffer returned by {@link #getByteBuffer()}). - *

- * If the block of content pointed by the new cursor position is valid, this method returns true. - * - * @return true if there is content at the new cursor's position, false otherwise. - */ - public boolean advance() - { - if (iterator instanceof Synchronizable) - { - synchronized (((Synchronizable)iterator).getLock()) - { - return advance(iterator); - } - } - else - { - return advance(iterator); - } - } - - private boolean advance(Iterator iterator) - { - boolean hasNext = iterator.hasNext(); - ByteBuffer bytes = hasNext ? iterator.next() : null; - boolean hasMore = hasNext && iterator.hasNext(); - boolean wasLast = last; - last = !hasMore; - - if (hasNext) - { - buffer = bytes; - content = bytes == null ? null : bytes.slice(); - if (LOG.isDebugEnabled()) - LOG.debug("Advanced content to {} chunk {}", hasMore ? "next" : "last", String.valueOf(bytes)); - return bytes != null; - } - else - { - // No more content, but distinguish between last and consumed. - if (wasLast) - { - buffer = content = AFTER; - if (LOG.isDebugEnabled()) - LOG.debug("Advanced content past last chunk"); - } - else - { - buffer = content = CLOSE; - if (LOG.isDebugEnabled()) - LOG.debug("Advanced content to last chunk"); - } - return false; - } - } - - /** - * @return whether the cursor has been advanced past the {@link #isLast() last} position. - */ - @SuppressWarnings("ReferenceEquality") - public boolean isConsumed() - { - return buffer == AFTER; - } - - @Override - public void succeeded() - { - if (isConsumed()) - return; - if (isTheCloseBuffer(buffer)) - return; - if (iterator instanceof Callback) - ((Callback)iterator).succeeded(); - } - - @Override - public void failed(Throwable x) - { - if (isConsumed()) - return; - if (isTheCloseBuffer(buffer)) - return; - if (iterator instanceof Callback) - ((Callback)iterator).failed(x); - } - - @Override - public void close() - { - if (iterator instanceof Closeable) - IO.close((Closeable)iterator); - } - - @Override - public String toString() - { - return String.format("%s@%x - has=%b,last=%b,consumed=%b,buffer=%s", - getClass().getSimpleName(), - hashCode(), - hasContent(), - isLast(), - isConsumed(), - BufferUtil.toDetailString(getContent())); - } -} diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java index c2fd19178f6..bb3c5d4e663 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java @@ -528,7 +528,7 @@ public abstract class HttpReceiver HttpResponse response = exchange.getResponse(); if (LOG.isDebugEnabled()) - LOG.debug("Response complete {}", response); + LOG.debug("Response complete {}, result: {}", response, result); if (result != null) { diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java index 0cc192f6cdd..e0919bc3f0d 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java @@ -49,8 +49,9 @@ import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.api.Result; +import org.eclipse.jetty.client.internal.RequestContentAdapter; import org.eclipse.jetty.client.util.FutureResponseListener; -import org.eclipse.jetty.client.util.PathContentProvider; +import org.eclipse.jetty.client.util.PathRequestContent; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; @@ -81,7 +82,7 @@ public class HttpRequest implements Request private long idleTimeout = -1; private long timeout; private long timeoutAt; - private ContentProvider content; + private Content content; private boolean followRedirects; private List cookies; private Map attributes; @@ -647,7 +648,9 @@ public class HttpRequest implements Request @Override public ContentProvider getContent() { - return content; + if (content instanceof RequestContentAdapter) + return ((RequestContentAdapter)content).getContentProvider(); + return null; } @Override @@ -661,6 +664,18 @@ public class HttpRequest implements Request { if (contentType != null) header(HttpHeader.CONTENT_TYPE, contentType); + return body(ContentProvider.toRequestContent(content)); + } + + @Override + public Content getBody() + { + return content; + } + + @Override + public Request body(Content content) + { this.content = content; return this; } @@ -674,7 +689,7 @@ public class HttpRequest implements Request @Override public Request file(Path file, String contentType) throws IOException { - return content(new PathContentProvider(contentType, file)); + return body(new PathRequestContent(contentType, file)); } @Override @@ -810,8 +825,8 @@ public class HttpRequest implements Request { if (aborted.compareAndSet(null, Objects.requireNonNull(cause))) { - if (content instanceof Callback) - ((Callback)content).failed(cause); + if (content != null) + content.fail(cause); return conversation.abort(cause); } return false; diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java index 0e2cc1280d6..d08a8321518 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java @@ -23,51 +23,37 @@ import java.util.concurrent.Executor; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.atomic.AtomicReference; -import org.eclipse.jetty.client.api.ContentProvider; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Result; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeaderValue; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; -import org.eclipse.jetty.util.IteratingCallback; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; /** - * {@link HttpSender} abstracts the algorithm to send HTTP requests, so that subclasses only implement - * the transport-specific code to send requests over the wire, implementing - * {@link #sendHeaders(HttpExchange, HttpContent, Callback)} and - * {@link #sendContent(HttpExchange, HttpContent, Callback)}. - *

- * {@link HttpSender} governs two state machines. - *

- * The request state machine is updated by {@link HttpSender} as the various steps of sending a request - * are executed, see {@code RequestState}. - * At any point in time, a user thread may abort the request, which may (if the request has not been - * completely sent yet) move the request state machine to {@code RequestState#FAILURE}. - * The request state machine guarantees that the request steps are executed (by I/O threads) only if - * the request has not been failed already. - *

- * The sender state machine is updated by {@link HttpSender} from three sources: deferred content notifications - * (via {@link #onContent()}), 100-continue notifications (via {@link #proceed(HttpExchange, Throwable)}) - * and normal request send (via {@link #sendContent(HttpExchange, HttpContent, Callback)}). - * This state machine must guarantee that the request sending is never executed concurrently: only one of - * those sources may trigger the call to {@link #sendContent(HttpExchange, HttpContent, Callback)}. + *

HttpSender abstracts the algorithm to send HTTP requests, so that subclasses only + * implement the transport-specific code to send requests over the wire, implementing + * {@link #sendHeaders(HttpExchange, ByteBuffer, boolean, Callback)} and + * {@link #sendContent(HttpExchange, ByteBuffer, boolean, Callback)}.

+ *

HttpSender governs the request state machines, which is updated as the various + * steps of sending a request are executed, see {@code RequestState}. + * At any point in time, a user thread may abort the request, which may (if the request + * has not been completely sent yet) move the request state machine to {@code RequestState#FAILURE}. + * The request state machine guarantees that the request steps are executed (by I/O threads) + * only if the request has not been failed already.

* * @see HttpReceiver */ -public abstract class HttpSender implements AsyncContentProvider.Listener +public abstract class HttpSender { - protected static final Logger LOG = Log.getLogger(HttpSender.class); + private static final Logger LOG = Log.getLogger(HttpSender.class); + private final ContentConsumer consumer = new ContentConsumer(); private final AtomicReference requestState = new AtomicReference<>(RequestState.QUEUED); - private final AtomicReference senderState = new AtomicReference<>(SenderState.IDLE); - private final Callback commitCallback = new CommitCallback(); - private final IteratingCallback contentCallback = new ContentCallback(); - private final Callback lastCallback = new LastCallback(); private final HttpChannel channel; - private HttpContent content; + private Request.Content.Subscription subscription; private Throwable failure; protected HttpSender(HttpChannel channel) @@ -90,126 +76,22 @@ public abstract class HttpSender implements AsyncContentProvider.Listener return requestState.get() == RequestState.FAILURE; } - @Override - public void onContent() - { - HttpExchange exchange = getHttpExchange(); - if (exchange == null) - return; - - while (true) - { - SenderState current = senderState.get(); - switch (current) - { - case IDLE: - { - SenderState newSenderState = SenderState.SENDING; - if (updateSenderState(current, newSenderState)) - { - if (LOG.isDebugEnabled()) - LOG.debug("Deferred content available, {} -> {}", current, newSenderState); - contentCallback.iterate(); - return; - } - break; - } - case SENDING: - { - SenderState newSenderState = SenderState.SENDING_WITH_CONTENT; - if (updateSenderState(current, newSenderState)) - { - if (LOG.isDebugEnabled()) - LOG.debug("Deferred content available, {} -> {}", current, newSenderState); - return; - } - break; - } - case EXPECTING: - { - SenderState newSenderState = SenderState.EXPECTING_WITH_CONTENT; - if (updateSenderState(current, newSenderState)) - { - if (LOG.isDebugEnabled()) - LOG.debug("Deferred content available, {} -> {}", current, newSenderState); - return; - } - break; - } - case PROCEEDING: - { - SenderState newSenderState = SenderState.PROCEEDING_WITH_CONTENT; - if (updateSenderState(current, newSenderState)) - { - if (LOG.isDebugEnabled()) - LOG.debug("Deferred content available, {} -> {}", current, newSenderState); - return; - } - break; - } - case SENDING_WITH_CONTENT: - case EXPECTING_WITH_CONTENT: - case PROCEEDING_WITH_CONTENT: - case WAITING: - case COMPLETED: - case FAILED: - { - if (LOG.isDebugEnabled()) - LOG.debug("Deferred content available, {}", current); - return; - } - default: - { - illegalSenderState(current); - return; - } - } - } - } - public void send(HttpExchange exchange) { + Request request = exchange.getRequest(); + Request.Content body = request.getBody(); + + consumer.exchange = exchange; + consumer.expect100 = expects100Continue(request); + subscription = body.subscribe(consumer, !consumer.expect100); + if (!queuedToBegin(exchange)) return; - Request request = exchange.getRequest(); - ContentProvider contentProvider = request.getContent(); - HttpContent content = this.content = new HttpContent(contentProvider); - - SenderState newSenderState = SenderState.SENDING; - if (expects100Continue(request)) - newSenderState = content.hasContent() ? SenderState.EXPECTING_WITH_CONTENT : SenderState.EXPECTING; - - out: - while (true) - { - SenderState current = senderState.get(); - switch (current) - { - case IDLE: - case COMPLETED: - { - if (updateSenderState(current, newSenderState)) - break out; - break; - } - default: - { - illegalSenderState(current); - return; - } - } - } - - // Setting the listener may trigger calls to onContent() by other - // threads so we must set it only after the sender state has been updated - if (contentProvider instanceof AsyncContentProvider) - ((AsyncContentProvider)contentProvider).setListener(this); - if (!beginToHeaders(exchange)) return; - sendHeaders(exchange, content, commitCallback); + demand(); } protected boolean expects100Continue(Request request) @@ -353,6 +235,20 @@ public abstract class HttpSender implements AsyncContentProvider.Listener executeAbort(exchange, failure); } + private void demand() + { + try + { + subscription.demand(); + } + catch (Throwable x) + { + if (LOG.isDebugEnabled()) + LOG.debug(x); + anyToFailure(x); + } + } + private void executeAbort(HttpExchange exchange, Throwable failure) { try @@ -415,142 +311,62 @@ public abstract class HttpSender implements AsyncContentProvider.Listener } /** - * Implementations should send the HTTP headers over the wire, possibly with some content, - * in a single write, and notify the given {@code callback} of the result of this operation. - *

- * If there is more content to send, then {@link #sendContent(HttpExchange, HttpContent, Callback)} - * will be invoked. + *

Implementations should send the HTTP headers over the wire, possibly with some content, + * in a single write, and notify the given {@code callback} of the result of this operation.

+ *

If there is more content to send, then {@link #sendContent(HttpExchange, ByteBuffer, boolean, Callback)} + * will be invoked.

* - * @param exchange the exchange to send - * @param content the content to send + * @param exchange the exchange + * @param contentBuffer the content to send + * @param lastContent whether the content is the last content to send * @param callback the callback to notify */ - protected abstract void sendHeaders(HttpExchange exchange, HttpContent content, Callback callback); + protected abstract void sendHeaders(HttpExchange exchange, ByteBuffer contentBuffer, boolean lastContent, Callback callback); /** - * Implementations should send the content at the {@link HttpContent} cursor position over the wire. - *

- * The {@link HttpContent} cursor is advanced by HttpSender at the right time, and if more - * content needs to be sent, this method is invoked again; subclasses need only to send the content - * at the {@link HttpContent} cursor position. - *

- * This method is invoked one last time when {@link HttpContent#isConsumed()} is true and therefore - * there is no actual content to send. - * This is done to allow subclasses to write "terminal" bytes (such as the terminal chunk when the - * transfer encoding is chunked) if their protocol needs to. + *

Implementations should send the given HTTP content over the wire.

* - * @param exchange the exchange to send - * @param content the content to send + * @param exchange the exchange + * @param contentBuffer the content to send + * @param lastContent whether the content is the last content to send * @param callback the callback to notify */ - protected abstract void sendContent(HttpExchange exchange, HttpContent content, Callback callback); + protected abstract void sendContent(HttpExchange exchange, ByteBuffer contentBuffer, boolean lastContent, Callback callback); protected void reset() { - HttpContent content = this.content; - this.content = null; - content.close(); - senderState.set(SenderState.COMPLETED); + consumer.reset(); } protected void dispose() { - HttpContent content = this.content; - this.content = null; - if (content != null) - content.close(); - senderState.set(SenderState.FAILED); } public void proceed(HttpExchange exchange, Throwable failure) { - if (!expects100Continue(exchange.getRequest())) - return; - - if (failure != null) - { + consumer.expect100 = false; + if (failure == null) + demand(); + else anyToFailure(failure); - return; - } - - while (true) - { - SenderState current = senderState.get(); - switch (current) - { - case EXPECTING: - { - // We are still sending the headers, but we already got the 100 Continue. - if (updateSenderState(current, SenderState.PROCEEDING)) - { - if (LOG.isDebugEnabled()) - LOG.debug("Proceeding while expecting"); - return; - } - break; - } - case EXPECTING_WITH_CONTENT: - { - // More deferred content was submitted to onContent(), we already - // got the 100 Continue, but we may be still sending the headers - // (for example, with SSL we may have sent the encrypted data, - // received the 100 Continue but not yet updated the decrypted - // WriteFlusher so sending more content now may result in a - // WritePendingException). - if (updateSenderState(current, SenderState.PROCEEDING_WITH_CONTENT)) - { - if (LOG.isDebugEnabled()) - LOG.debug("Proceeding while scheduled"); - return; - } - break; - } - case WAITING: - { - // We received the 100 Continue, now send the content if any. - if (updateSenderState(current, SenderState.SENDING)) - { - if (LOG.isDebugEnabled()) - LOG.debug("Proceeding while waiting"); - contentCallback.iterate(); - return; - } - break; - } - case FAILED: - { - return; - } - default: - { - illegalSenderState(current); - return; - } - } - } } public boolean abort(HttpExchange exchange, Throwable failure) { // Update the state to avoid more request processing. boolean terminate; - out: while (true) { RequestState current = requestState.get(); - switch (current) + if (current == RequestState.FAILURE) { - case FAILURE: + return false; + } + else + { + if (updateRequestState(current, RequestState.FAILURE)) { - return false; - } - default: - { - if (updateRequestState(current, RequestState.FAILURE)) - { - terminate = current != RequestState.TRANSIENT; - break out; - } + terminate = current != RequestState.TRANSIENT; break; } } @@ -590,27 +406,13 @@ public abstract class HttpSender implements AsyncContentProvider.Listener return updated; } - private boolean updateSenderState(SenderState from, SenderState to) - { - boolean updated = senderState.compareAndSet(from, to); - if (!updated && LOG.isDebugEnabled()) - LOG.debug("SenderState update failed: {} -> {}: {}", from, to, senderState.get()); - return updated; - } - - private void illegalSenderState(SenderState current) - { - anyToFailure(new IllegalStateException("Expected " + current + " found " + senderState.get() + " instead")); - } - @Override public String toString() { - return String.format("%s@%x(req=%s,snd=%s,failure=%s)", + return String.format("%s@%x(req=%s,failure=%s)", getClass().getSimpleName(), hashCode(), requestState, - senderState, failure); } @@ -649,286 +451,98 @@ public abstract class HttpSender implements AsyncContentProvider.Listener FAILURE } - /** - * The sender states {@link HttpSender} goes through when sending a request. - */ - private enum SenderState + private class ContentConsumer implements Request.Content.Consumer, Callback { - /** - * {@link HttpSender} is not sending request headers nor request content - */ - IDLE, - /** - * {@link HttpSender} is sending the request header or request content - */ - SENDING, - /** - * {@link HttpSender} is currently sending the request, and deferred content is available to be sent - */ - SENDING_WITH_CONTENT, - /** - * {@link HttpSender} is sending the headers but will wait for 100 Continue before sending the content - */ - EXPECTING, - /** - * {@link HttpSender} is currently sending the headers, will wait for 100 Continue, and deferred content is available to be sent - */ - EXPECTING_WITH_CONTENT, - /** - * {@link HttpSender} has sent the headers and is waiting for 100 Continue - */ - WAITING, - /** - * {@link HttpSender} is sending the headers, while 100 Continue has arrived - */ - PROCEEDING, - /** - * {@link HttpSender} is sending the headers, while 100 Continue has arrived, and deferred content is available to be sent - */ - PROCEEDING_WITH_CONTENT, - /** - * {@link HttpSender} has finished to send the request - */ - COMPLETED, - /** - * {@link HttpSender} has failed to send the request - */ - FAILED - } + private HttpExchange exchange; + private boolean expect100; + private ByteBuffer contentBuffer; + private boolean lastContent; + private Callback callback; + private boolean committed; + + private void reset() + { + exchange = null; + contentBuffer = null; + lastContent = false; + callback = null; + committed = false; + } + + @Override + public void onContent(ByteBuffer buffer, boolean last, Callback callback) + { + if (LOG.isDebugEnabled()) + LOG.debug("Content {} last={} for {}", BufferUtil.toDetailString(buffer), last, exchange.getRequest()); + this.contentBuffer = buffer.slice(); + this.lastContent = last; + this.callback = callback; + if (committed) + sendContent(exchange, buffer, last, this); + else + sendHeaders(exchange, buffer, last, this); + } + + @Override + public void onFailure(Throwable failure) + { + failed(failure); + } - private class CommitCallback implements Callback - { @Override public void succeeded() { - try + boolean proceed = false; + if (committed) { - HttpContent content = HttpSender.this.content; - if (content == null) - return; - content.succeeded(); - process(); - } - catch (Throwable x) - { - anyToFailure(x); - } - } - - @Override - public void failed(Throwable failure) - { - HttpContent content = HttpSender.this.content; - if (content == null) - return; - content.failed(failure); - anyToFailure(failure); - } - - private void process() throws Exception - { - HttpExchange exchange = getHttpExchange(); - if (exchange == null) - return; - - if (!headersToCommit(exchange)) - return; - - HttpContent content = HttpSender.this.content; - if (content == null) - return; - - if (!content.hasContent()) - { - // No content to send, we are done. - someToSuccess(exchange); + proceed = someToContent(exchange, contentBuffer); } else { - // Was any content sent while committing? - ByteBuffer contentBuffer = content.getContent(); - if (contentBuffer != null) + committed = true; + if (headersToCommit(exchange)) { - if (!someToContent(exchange, contentBuffer)) - return; - } - - while (true) - { - SenderState current = senderState.get(); - switch (current) - { - case SENDING: - { - contentCallback.iterate(); - return; - } - case SENDING_WITH_CONTENT: - { - // We have deferred content to send. - updateSenderState(current, SenderState.SENDING); - break; - } - case EXPECTING: - { - // We sent the headers, wait for the 100 Continue response. - if (updateSenderState(current, SenderState.WAITING)) - return; - break; - } - case EXPECTING_WITH_CONTENT: - { - // We sent the headers, we have deferred content to send, - // wait for the 100 Continue response. - if (updateSenderState(current, SenderState.WAITING)) - return; - break; - } - case PROCEEDING: - { - // We sent the headers, we have the 100 Continue response, - // we have no content to send. - if (updateSenderState(current, SenderState.IDLE)) - return; - break; - } - case PROCEEDING_WITH_CONTENT: - { - // We sent the headers, we have the 100 Continue response, - // we have deferred content to send. - updateSenderState(current, SenderState.SENDING); - break; - } - case FAILED: - { - return; - } - default: - { - illegalSenderState(current); - return; - } - } + proceed = true; + // Was any content sent while committing? + if (contentBuffer.hasRemaining()) + proceed = someToContent(exchange, contentBuffer); } } - } - } - private class ContentCallback extends IteratingCallback - { - @Override - protected Action process() throws Exception - { - HttpExchange exchange = getHttpExchange(); - if (exchange == null) - return Action.IDLE; + // Succeed the content callback only after emitting the request content event. + callback.succeeded(); - HttpContent content = HttpSender.this.content; - if (content == null) - return Action.IDLE; + // There was some concurrent error? + if (!proceed) + return; - while (true) + if (lastContent) + { + someToSuccess(exchange); + } + else if (expect100) { - boolean advanced = content.advance(); - boolean lastContent = content.isLast(); if (LOG.isDebugEnabled()) - LOG.debug("Content present {}, last {}, consumed {} for {}", advanced, lastContent, content.isConsumed(), exchange.getRequest()); - - if (advanced) - { - sendContent(exchange, content, this); - return Action.SCHEDULED; - } - - if (lastContent) - { - sendContent(exchange, content, lastCallback); - return Action.IDLE; - } - - SenderState current = senderState.get(); - switch (current) - { - case SENDING: - { - if (updateSenderState(current, SenderState.IDLE)) - { - if (LOG.isDebugEnabled()) - LOG.debug("Content is deferred for {}", exchange.getRequest()); - return Action.IDLE; - } - break; - } - case SENDING_WITH_CONTENT: - { - updateSenderState(current, SenderState.SENDING); - break; - } - default: - { - illegalSenderState(current); - return Action.IDLE; - } - } + LOG.debug("Expecting 100 Continue for {}", exchange.getRequest()); + } + else + { + demand(); } } @Override - public void succeeded() + public void failed(Throwable x) { - HttpExchange exchange = getHttpExchange(); - if (exchange == null) - return; - HttpContent content = HttpSender.this.content; - if (content == null) - return; - content.succeeded(); - ByteBuffer buffer = content.getContent(); - someToContent(exchange, buffer); - super.succeeded(); + if (callback != null) + callback.failed(x); + anyToFailure(x); } @Override - public void onCompleteFailure(Throwable failure) + public InvocationType getInvocationType() { - HttpContent content = HttpSender.this.content; - if (content == null) - return; - content.failed(failure); - anyToFailure(failure); - } - - @Override - protected void onCompleteSuccess() - { - // Nothing to do, since we always return IDLE from process(). - // Termination is obtained via LastCallback. - } - } - - private class LastCallback implements Callback - { - @Override - public void succeeded() - { - HttpExchange exchange = getHttpExchange(); - if (exchange == null) - return; - HttpContent content = HttpSender.this.content; - if (content == null) - return; - content.succeeded(); - someToSuccess(exchange); - } - - @Override - public void failed(Throwable failure) - { - HttpContent content = HttpSender.this.content; - if (content == null) - return; - content.failed(failure); - anyToFailure(failure); + return InvocationType.NON_BLOCKING; } } } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/RequestNotifier.java b/jetty-client/src/main/java/org/eclipse/jetty/client/RequestNotifier.java index 1a2819e0ccd..c12974c1f6b 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/RequestNotifier.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/RequestNotifier.java @@ -158,10 +158,10 @@ public class RequestNotifier public void notifyContent(Request request, ByteBuffer content) { - // Slice the buffer to avoid that listeners peek into data they should not look at. - content = content.slice(); if (!content.hasRemaining()) return; + // Slice the buffer to avoid that listeners peek into data they should not look at. + content = content.slice(); // Optimized to avoid allocations of iterator instances. List requestListeners = request.getRequestListeners(null); for (int i = 0; i < requestListeners.size(); ++i) diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/ContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/ContentProvider.java index f9a705af082..3d43110fdc3 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/api/ContentProvider.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/api/ContentProvider.java @@ -23,6 +23,7 @@ import java.nio.ByteBuffer; import java.util.Iterator; import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.internal.RequestContentAdapter; import org.eclipse.jetty.client.util.ByteBufferContentProvider; import org.eclipse.jetty.client.util.PathContentProvider; @@ -41,9 +42,24 @@ import org.eclipse.jetty.client.util.PathContentProvider; * header set by applications; if the length is negative, it typically removes * any {@code Content-Length} header set by applications, resulting in chunked * content (i.e. {@code Transfer-Encoding: chunked}) being sent to the server.

+ * + * @deprecated use {@link Request.Content} instead, or {@link #toRequestContent(ContentProvider)} + * to convert ContentProvider to {@link Request.Content}. */ +@Deprecated public interface ContentProvider extends Iterable { + /** + *

Converts a ContentProvider to a {@link Request.Content}.

+ * + * @param provider the ContentProvider to convert + * @return a {@link Request.Content} that wraps the ContentProvider + */ + public static Request.Content toRequestContent(ContentProvider provider) + { + return new RequestContentAdapter(provider); + } + /** * @return the content length, if known, or -1 if the content length is unknown */ @@ -68,7 +84,10 @@ public interface ContentProvider extends Iterable /** * An extension of {@link ContentProvider} that provides a content type string * to be used as a {@code Content-Type} HTTP header in requests. + * + * @deprecated use {@link Request.Content} instead */ + @Deprecated public interface Typed extends ContentProvider { /** diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java index 8b7d97d1248..51c2e6ff22b 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java @@ -19,6 +19,7 @@ package org.eclipse.jetty.client.api; import java.io.IOException; +import java.io.InputStream; import java.net.HttpCookie; import java.net.URI; import java.net.URLEncoder; @@ -37,6 +38,7 @@ import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpVersion; +import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.Fields; /** @@ -216,22 +218,39 @@ public interface Request /** * @return the content provider of this request + * @deprecated use {@link #getBody()} instead */ + @Deprecated ContentProvider getContent(); /** * @param content the content provider of this request * @return this request object + * @deprecated use {@link #body(Content)} instead */ + @Deprecated Request content(ContentProvider content); /** * @param content the content provider of this request * @param contentType the content type * @return this request object + * @deprecated use {@link #body(Content)} instead */ + @Deprecated Request content(ContentProvider content, String contentType); + /** + * @return the request content of this request + */ + Content getBody(); + + /** + * @param content the request content of this request + * @return this request object + */ + Request body(Content content); + /** * Shortcut method to specify a file as a content for this request, with the default content type of * "application/octect-stream". @@ -615,4 +634,136 @@ public interface Request { } } + + /** + *

A reactive model to produce request content, similar to {@link java.util.concurrent.Flow.Publisher}.

+ *

Implementations receive the content consumer via {@link #subscribe(Consumer, boolean)}, + * and return a {@link Subscription} as the link between producer and consumer.

+ *

Content producers must notify content to the consumer only if there is demand.

+ *

Content consumers can generate demand for content by invoking {@link Subscription#demand()}.

+ */ + public interface Content + { + /** + * @return the content type string such as "application/octet-stream" or + * "application/json;charset=UTF8", or null if no content type must be set + */ + public default String getContentType() + { + return "application/octet-stream"; + } + + /** + * @return the content length, if known, or -1 if the content length is unknown + */ + public default long getLength() + { + return -1; + } + + /** + *

Whether this content producer can produce exactly the same content more + * than once.

+ *

Implementations should return {@code true} only if the content can be + * produced more than once, which means that {@link #subscribe(Consumer, boolean)} + * may be called again.

+ *

The {@link HttpClient} implementation may use this method in particular + * cases where it detects that it is safe to retry a request that failed.

+ * + * @return whether the content can be produced more than once + */ + public default boolean isReproducible() + { + return false; + } + + /** + *

Initializes this content producer with the content consumer, and with + * the indication of whether initial content, if present, must be emitted + * upon the initial demand of content (to support delaying the send of the + * request content in case of {@code Expect: 100-Continue} when + * {@code emitInitialContent} is {@code false}).

+ * + * @param consumer the content consumer to invoke when there is demand for content + * @param emitInitialContent whether to emit initial content, if present + * @return the Subscription that links this producer to the consumer + */ + public Subscription subscribe(Consumer consumer, boolean emitInitialContent); + + /** + *

Fails this request content, possibly failing and discarding accumulated + * content that was not demanded.

+ *

The failure may be notified to the consumer at a later time, when the + * consumer demands for content.

+ *

Typical failure: the request being aborted by user code, or idle timeouts.

+ * + * @param failure the reason of the failure + */ + public default void fail(Throwable failure) + { + } + + /** + *

A reactive model to consume request content, similar to {@link java.util.concurrent.Flow.Subscriber}.

+ *

Callback methods {@link #onContent(ByteBuffer, boolean, Callback)} and {@link #onFailure(Throwable)} + * are invoked in strict sequential order and never concurrently, although possibly by different threads.

+ */ + public interface Consumer + { + /** + *

Callback method invoked by the producer when there is content available + * and there is demand for content.

+ *

The {@code callback} is associated with the {@code buffer} to + * signal when the content buffer has been consumed.

+ *

Failing the {@code callback} does not have any effect on content + * production. To stop the content production, the consumer must call + * {@link Subscription#fail(Throwable)}.

+ *

In case an exception is thrown by this method, it is equivalent to + * a call to {@link Subscription#fail(Throwable)}.

+ * + * @param buffer the content buffer to consume + * @param last whether it's the last content + * @param callback a callback to invoke when the content buffer is consumed + */ + public void onContent(ByteBuffer buffer, boolean last, Callback callback); + + /** + *

Callback method invoked by the producer when it failed to produce content.

+ *

Typical failure: a producer getting an exception while reading from an + * {@link InputStream} to produce content.

+ * + * @param failure the reason of the failure + */ + public default void onFailure(Throwable failure) + { + } + } + + /** + *

The link between a content producer and a content consumer.

+ *

Content consumers can demand more content via {@link #demand()}, + * or ask the content producer to stop producing content via + * {@link #fail(Throwable)}.

+ */ + public interface Subscription + { + /** + *

Demands more content, which eventually results in + * {@link Consumer#onContent(ByteBuffer, boolean, Callback)} to be invoked.

+ */ + public void demand(); + + /** + *

Fails the subscription, notifying the content producer to stop producing + * content.

+ *

Typical failure: a proxy consumer waiting for more content (or waiting + * to demand content) that is failed by an error response from the server.

+ * + * @param failure the reason of the failure + */ + public default void fail(Throwable failure) + { + } + } + } } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpSenderOverHTTP.java b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpSenderOverHTTP.java index 411cc48713b..8654f2ed87d 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpSenderOverHTTP.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpSenderOverHTTP.java @@ -21,12 +21,11 @@ package org.eclipse.jetty.client.http; import java.nio.ByteBuffer; import org.eclipse.jetty.client.HttpClient; -import org.eclipse.jetty.client.HttpContent; import org.eclipse.jetty.client.HttpExchange; import org.eclipse.jetty.client.HttpRequest; import org.eclipse.jetty.client.HttpRequestException; import org.eclipse.jetty.client.HttpSender; -import org.eclipse.jetty.client.api.ContentProvider; +import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.http.HttpGenerator; import org.eclipse.jetty.http.HttpURI; import org.eclipse.jetty.http.MetaData; @@ -35,10 +34,21 @@ import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.IteratingCallback; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; public class HttpSenderOverHTTP extends HttpSender { + private static final Logger LOG = Log.getLogger(HttpSenderOverHTTP.class); + + private final IteratingCallback headersCallback = new HeadersCallback(); + private final IteratingCallback contentCallback = new ContentCallback(); private final HttpGenerator generator = new HttpGenerator(); + private HttpExchange exchange; + private MetaData.Request metaData; + private ByteBuffer contentBuffer; + private boolean lastContent; + private Callback callback; private boolean shutdown; public HttpSenderOverHTTP(HttpChannelOverHTTP channel) @@ -53,11 +63,26 @@ public class HttpSenderOverHTTP extends HttpSender } @Override - protected void sendHeaders(HttpExchange exchange, HttpContent content, Callback callback) + protected void sendHeaders(HttpExchange exchange, ByteBuffer contentBuffer, boolean lastContent, Callback callback) { try { - new HeadersCallback(exchange, content, callback).iterate(); + this.exchange = exchange; + this.contentBuffer = contentBuffer; + this.lastContent = lastContent; + this.callback = callback; + HttpRequest request = exchange.getRequest(); + Request.Content requestContent = request.getBody(); + long contentLength = requestContent == null ? -1 : requestContent.getLength(); + String path = request.getPath(); + String query = request.getQuery(); + if (query != null) + path += "?" + query; + metaData = new MetaData.Request(request.getMethod(), new HttpURI(path), request.getVersion(), request.getHeaders(), contentLength); + metaData.setTrailerSupplier(request.getTrailers()); + if (LOG.isDebugEnabled()) + LOG.debug("Sending headers with content {} last={} for {}", BufferUtil.toDetailString(contentBuffer), lastContent, exchange.getRequest()); + headersCallback.iterate(); } catch (Throwable x) { @@ -68,67 +93,17 @@ public class HttpSenderOverHTTP extends HttpSender } @Override - protected void sendContent(HttpExchange exchange, HttpContent content, Callback callback) + protected void sendContent(HttpExchange exchange, ByteBuffer contentBuffer, boolean lastContent, Callback callback) { try { - HttpClient httpClient = getHttpChannel().getHttpDestination().getHttpClient(); - ByteBufferPool bufferPool = httpClient.getByteBufferPool(); - boolean useDirectByteBuffers = httpClient.isUseOutputDirectByteBuffers(); - ByteBuffer chunk = null; - while (true) - { - ByteBuffer contentBuffer = content.getByteBuffer(); - boolean lastContent = content.isLast(); - HttpGenerator.Result result = generator.generateRequest(null, null, chunk, contentBuffer, lastContent); - if (LOG.isDebugEnabled()) - LOG.debug("Generated content ({} bytes) - {}/{}", - contentBuffer == null ? -1 : contentBuffer.remaining(), - result, generator); - switch (result) - { - case NEED_CHUNK: - { - chunk = bufferPool.acquire(HttpGenerator.CHUNK_SIZE, useDirectByteBuffers); - break; - } - case NEED_CHUNK_TRAILER: - { - chunk = bufferPool.acquire(httpClient.getRequestBufferSize(), useDirectByteBuffers); - break; - } - case FLUSH: - { - EndPoint endPoint = getHttpChannel().getHttpConnection().getEndPoint(); - if (chunk != null) - endPoint.write(new ByteBufferRecyclerCallback(callback, bufferPool, chunk), chunk, contentBuffer); - else - endPoint.write(callback, contentBuffer); - return; - } - case SHUTDOWN_OUT: - { - shutdownOutput(); - break; - } - case CONTINUE: - { - if (lastContent) - break; - callback.succeeded(); - return; - } - case DONE: - { - callback.succeeded(); - return; - } - default: - { - throw new IllegalStateException(result.toString()); - } - } - } + this.exchange = exchange; + this.contentBuffer = contentBuffer; + this.lastContent = lastContent; + this.callback = callback; + if (LOG.isDebugEnabled()) + LOG.debug("Sending content {} last={} for {}", BufferUtil.toDetailString(contentBuffer), lastContent, exchange.getRequest()); + contentCallback.iterate(); } catch (Throwable x) { @@ -141,6 +116,8 @@ public class HttpSenderOverHTTP extends HttpSender @Override protected void reset() { + headersCallback.reset(); + contentCallback.reset(); generator.reset(); super.reset(); } @@ -173,54 +150,30 @@ public class HttpSenderOverHTTP extends HttpSender private class HeadersCallback extends IteratingCallback { - private final HttpExchange exchange; - private final Callback callback; - private final MetaData.Request metaData; private ByteBuffer headerBuffer; private ByteBuffer chunkBuffer; - private ByteBuffer contentBuffer; - private boolean lastContent; private boolean generated; - public HeadersCallback(HttpExchange exchange, HttpContent content, Callback callback) + private HeadersCallback() { super(false); - this.exchange = exchange; - this.callback = callback; - - HttpRequest request = exchange.getRequest(); - ContentProvider requestContent = request.getContent(); - long contentLength = requestContent == null ? -1 : requestContent.getLength(); - String path = request.getPath(); - String query = request.getQuery(); - if (query != null) - path += "?" + query; - metaData = new MetaData.Request(request.getMethod(), new HttpURI(path), request.getVersion(), request.getHeaders(), contentLength); - metaData.setTrailerSupplier(request.getTrailers()); - - if (!expects100Continue(request)) - { - content.advance(); - contentBuffer = content.getByteBuffer(); - lastContent = content.isLast(); - } } @Override protected Action process() throws Exception { + HttpClient httpClient = getHttpChannel().getHttpDestination().getHttpClient(); + ByteBufferPool byteBufferPool = httpClient.getByteBufferPool(); + boolean useDirectByteBuffers = httpClient.isUseOutputDirectByteBuffers(); while (true) { HttpGenerator.Result result = generator.generateRequest(metaData, headerBuffer, chunkBuffer, contentBuffer, lastContent); if (LOG.isDebugEnabled()) - LOG.debug("Generated headers ({} bytes), chunk ({} bytes), content ({} bytes) - {}/{}", + LOG.debug("Generated headers ({} bytes), chunk ({} bytes), content ({} bytes) - {}/{} for {}", headerBuffer == null ? -1 : headerBuffer.remaining(), chunkBuffer == null ? -1 : chunkBuffer.remaining(), contentBuffer == null ? -1 : contentBuffer.remaining(), - result, generator); - HttpClient httpClient = getHttpChannel().getHttpDestination().getHttpClient(); - ByteBufferPool byteBufferPool = httpClient.getByteBufferPool(); - boolean useDirectByteBuffers = httpClient.isUseOutputDirectByteBuffers(); + result, generator, exchange.getRequest()); switch (result) { case NEED_HEADER: @@ -328,37 +281,86 @@ public class HttpSenderOverHTTP extends HttpSender } } - private class ByteBufferRecyclerCallback extends Callback.Nested + private class ContentCallback extends IteratingCallback { - private final ByteBufferPool pool; - private final ByteBuffer[] buffers; + private ByteBuffer chunkBuffer; - private ByteBufferRecyclerCallback(Callback callback, ByteBufferPool pool, ByteBuffer... buffers) + public ContentCallback() { - super(callback); - this.pool = pool; - this.buffers = buffers; + super(false); } @Override - public void succeeded() + protected Action process() throws Exception { - for (ByteBuffer buffer : buffers) + HttpClient httpClient = getHttpChannel().getHttpDestination().getHttpClient(); + ByteBufferPool bufferPool = httpClient.getByteBufferPool(); + boolean useDirectByteBuffers = httpClient.isUseOutputDirectByteBuffers(); + while (true) { - assert !buffer.hasRemaining(); - pool.release(buffer); + HttpGenerator.Result result = generator.generateRequest(null, null, chunkBuffer, contentBuffer, lastContent); + if (LOG.isDebugEnabled()) + LOG.debug("Generated content ({} bytes, last={}) - {}/{}", + contentBuffer == null ? -1 : contentBuffer.remaining(), + lastContent, result, generator); + switch (result) + { + case NEED_CHUNK: + { + chunkBuffer = bufferPool.acquire(HttpGenerator.CHUNK_SIZE, useDirectByteBuffers); + break; + } + case NEED_CHUNK_TRAILER: + { + chunkBuffer = bufferPool.acquire(httpClient.getRequestBufferSize(), useDirectByteBuffers); + break; + } + case FLUSH: + { + EndPoint endPoint = getHttpChannel().getHttpConnection().getEndPoint(); + if (chunkBuffer != null) + endPoint.write(this, chunkBuffer, contentBuffer); + else + endPoint.write(this, contentBuffer); + return Action.SCHEDULED; + } + case SHUTDOWN_OUT: + { + shutdownOutput(); + break; + } + case CONTINUE: + { + break; + } + case DONE: + { + release(); + callback.succeeded(); + return Action.IDLE; + } + default: + { + throw new IllegalStateException(result.toString()); + } + } } - super.succeeded(); } @Override - public void failed(Throwable x) + protected void onCompleteFailure(Throwable cause) { - for (ByteBuffer buffer : buffers) - { - pool.release(buffer); - } - super.failed(x); + release(); + callback.failed(cause); + } + + private void release() + { + HttpClient httpClient = getHttpChannel().getHttpDestination().getHttpClient(); + ByteBufferPool bufferPool = httpClient.getByteBufferPool(); + bufferPool.release(chunkBuffer); + chunkBuffer = null; + contentBuffer = null; } } } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/internal/RequestContentAdapter.java b/jetty-client/src/main/java/org/eclipse/jetty/client/internal/RequestContentAdapter.java new file mode 100644 index 00000000000..5d23fafa8f2 --- /dev/null +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/internal/RequestContentAdapter.java @@ -0,0 +1,329 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.client.internal; + +import java.io.Closeable; +import java.nio.ByteBuffer; +import java.util.Iterator; + +import org.eclipse.jetty.client.AsyncContentProvider; +import org.eclipse.jetty.client.Synchronizable; +import org.eclipse.jetty.client.api.ContentProvider; +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.thread.AutoLock; + +/** + *

Implements the conversion from {@link ContentProvider} to {@link Request.Content}.

+ */ +public class RequestContentAdapter implements Request.Content, Request.Content.Subscription, AsyncContentProvider.Listener, Callback +{ + private static final Logger LOG = Log.getLogger(RequestContentAdapter.class); + + private final AutoLock lock = new AutoLock(); + private final ContentProvider provider; + private Iterator iterator; + private Consumer consumer; + private boolean emitInitialContent; + private boolean lastContent; + private boolean committed; + private int demand; + private boolean stalled; + private boolean hasContent; + private Throwable failure; + + public RequestContentAdapter(ContentProvider provider) + { + this.provider = provider; + if (provider instanceof AsyncContentProvider) + ((AsyncContentProvider)provider).setListener(this); + } + + public ContentProvider getContentProvider() + { + return provider; + } + + @Override + public String getContentType() + { + return provider instanceof ContentProvider.Typed ? ((ContentProvider.Typed)provider).getContentType() : null; + } + + @Override + public long getLength() + { + return provider.getLength(); + } + + @Override + public boolean isReproducible() + { + return provider.isReproducible(); + } + + @Override + public Subscription subscribe(Consumer consumer, boolean emitInitialContent) + { + try (AutoLock ignored = lock.lock()) + { + if (this.consumer != null && !isReproducible()) + throw new IllegalStateException("Multiple subscriptions not supported on " + this); + this.iterator = provider.iterator(); + this.consumer = consumer; + this.emitInitialContent = emitInitialContent; + this.lastContent = false; + this.committed = false; + this.demand = 0; + this.stalled = true; + this.hasContent = false; + } + return this; + } + + @Override + public void demand() + { + boolean produce; + try (AutoLock ignored = lock.lock()) + { + ++demand; + produce = stalled; + if (stalled) + stalled = false; + } + if (LOG.isDebugEnabled()) + LOG.debug("Content demand, producing {} for {}", produce, this); + if (produce) + produce(); + } + + @Override + public void fail(Throwable failure) + { + try (AutoLock ignored = lock.lock()) + { + if (this.failure == null) + this.failure = failure; + } + failed(failure); + } + + @Override + public void onContent() + { + boolean produce = false; + try (AutoLock ignored = lock.lock()) + { + hasContent = true; + if (demand > 0) + { + produce = stalled; + if (stalled) + stalled = false; + } + } + if (LOG.isDebugEnabled()) + LOG.debug("Content event, processing {} for {}", produce, this); + if (produce) + produce(); + } + + @Override + public void succeeded() + { + if (iterator instanceof Callback) + ((Callback)iterator).succeeded(); + if (lastContent && iterator instanceof Closeable) + IO.close((Closeable)iterator); + } + + @Override + public void failed(Throwable x) + { + if (iterator == null) + failed(provider, x); + else + failed(iterator, x); + } + + private void failed(Object object, Throwable failure) + { + if (object instanceof Callback) + ((Callback)object).failed(failure); + if (object instanceof Closeable) + IO.close((Closeable)object); + } + + @Override + public InvocationType getInvocationType() + { + return InvocationType.NON_BLOCKING; + } + + private void produce() + { + while (true) + { + Throwable failure; + try (AutoLock ignored = lock.lock()) + { + failure = this.failure; + } + if (failure != null) + { + notifyFailure(failure); + return; + } + + if (committed) + { + ByteBuffer content = advance(); + if (content != null) + { + notifyContent(content, lastContent); + } + else + { + try (AutoLock ignored = lock.lock()) + { + // Call to advance() said there was no content, + // but some content may have arrived meanwhile. + if (hasContent) + { + hasContent = false; + continue; + } + else + { + stalled = true; + } + } + if (LOG.isDebugEnabled()) + LOG.debug("No content, processing stalled for {}", this); + return; + } + } + else + { + committed = true; + if (emitInitialContent) + { + ByteBuffer content = advance(); + if (content != null) + notifyContent(content, lastContent); + else + notifyContent(BufferUtil.EMPTY_BUFFER, false); + } + else + { + notifyContent(BufferUtil.EMPTY_BUFFER, false); + } + } + boolean noDemand; + try (AutoLock ignored = lock.lock()) + { + noDemand = demand == 0; + if (noDemand) + stalled = true; + } + if (noDemand) + { + if (LOG.isDebugEnabled()) + LOG.debug("No demand, processing stalled for {}", this); + return; + } + } + } + + private ByteBuffer advance() + { + if (iterator instanceof Synchronizable) + { + synchronized (((Synchronizable)iterator).getLock()) + { + return next(); + } + } + else + { + return next(); + } + } + + private ByteBuffer next() + { + boolean hasNext = iterator.hasNext(); + ByteBuffer bytes = hasNext ? iterator.next() : null; + boolean hasMore = hasNext && iterator.hasNext(); + lastContent = !hasMore; + return hasNext ? bytes : BufferUtil.EMPTY_BUFFER; + } + + private void notifyContent(ByteBuffer buffer, boolean last) + { + try (AutoLock ignored = lock.lock()) + { + --demand; + hasContent = false; + } + + try + { + if (LOG.isDebugEnabled()) + LOG.debug("Notifying content last={} {} for {}", last, BufferUtil.toDetailString(buffer), this); + consumer.onContent(buffer, last, this); + } + catch (Throwable x) + { + fail(x); + } + } + + private void notifyFailure(Throwable failure) + { + try + { + if (LOG.isDebugEnabled()) + LOG.debug("Notifying failure for {}", this, failure); + consumer.onFailure(failure); + } + catch (Exception x) + { + LOG.ignore(x); + } + } + + @Override + public String toString() + { + int demand; + boolean stalled; + try (AutoLock ignored = lock.lock()) + { + demand = this.demand; + stalled = this.stalled; + } + return String.format("%s@%x[demand=%d,stalled=%b]", getClass().getSimpleName(), hashCode(), demand, stalled); + } +} diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/AbstractRequestContent.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/AbstractRequestContent.java new file mode 100644 index 00000000000..9f18c301801 --- /dev/null +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/AbstractRequestContent.java @@ -0,0 +1,250 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.client.util; + +import java.io.EOFException; +import java.nio.ByteBuffer; + +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.thread.AutoLock; + +public abstract class AbstractRequestContent implements Request.Content +{ + private static final Logger LOG = Log.getLogger(AbstractRequestContent.class); + + private final AutoLock lock = new AutoLock(); + private final String contentType; + private Subscription subscription; + private Throwable failure; + + protected AbstractRequestContent(String contentType) + { + this.contentType = contentType; + } + + @Override + public String getContentType() + { + return contentType; + } + + @Override + public Subscription subscribe(Consumer consumer, boolean emitInitialContent) + { + Subscription oldSubscription; + Subscription newSubscription; + try (AutoLock ignored = lock.lock()) + { + if (subscription != null && !isReproducible()) + throw new IllegalStateException("Multiple subscriptions not supported on " + this); + oldSubscription = subscription; + newSubscription = subscription = newSubscription(consumer, emitInitialContent, failure); + } + if (oldSubscription != null) + oldSubscription.fail(new EOFException("Content replay")); + if (LOG.isDebugEnabled()) + LOG.debug("Content subscription for {}: {}", this, consumer); + return newSubscription; + } + + protected abstract Subscription newSubscription(Consumer consumer, boolean emitInitialContent, Throwable failure); + + @Override + public void fail(Throwable failure) + { + Subscription subscription = null; + try (AutoLock ignored = lock.lock()) + { + if (this.failure == null) + { + this.failure = failure; + subscription = this.subscription; + } + } + if (subscription != null) + subscription.fail(failure); + } + + public abstract class AbstractSubscription implements Subscription + { + private final Consumer consumer; + private final boolean emitInitialContent; + private Throwable failure; + private int demand; + private boolean stalled; + private boolean committed; + + public AbstractSubscription(Consumer consumer, boolean emitInitialContent, Throwable failure) + { + this.consumer = consumer; + this.emitInitialContent = emitInitialContent; + this.failure = failure; + this.stalled = true; + } + + @Override + public void demand() + { + boolean produce; + try (AutoLock ignored = lock.lock()) + { + ++demand; + produce = stalled; + if (stalled) + stalled = false; + } + if (LOG.isDebugEnabled()) + LOG.debug("Content demand, producing {} for {}", produce, this); + if (produce) + produce(); + } + + private void produce() + { + while (true) + { + Throwable failure; + boolean committed; + try (AutoLock ignored = lock.lock()) + { + failure = this.failure; + committed = this.committed; + } + if (failure != null) + { + notifyFailure(failure); + return; + } + + if (committed || emitInitialContent) + { + try + { + if (!produceContent(this::processContent)) + return; + } + catch (Throwable x) + { + // Fail and loop around to notify the failure. + fail(x); + } + } + else + { + if (!processContent(BufferUtil.EMPTY_BUFFER, false, Callback.NOOP)) + return; + } + } + } + + protected abstract boolean produceContent(Producer producer) throws Exception; + + @Override + public void fail(Throwable failure) + { + try (AutoLock ignored = lock.lock()) + { + if (this.failure == null) + this.failure = failure; + } + } + + private boolean processContent(ByteBuffer content, boolean last, Callback callback) + { + try (AutoLock ignored = lock.lock()) + { + committed = true; + --demand; + } + + if (content != null) + notifyContent(content, last, callback); + else + callback.succeeded(); + + boolean noDemand; + try (AutoLock ignored = lock.lock()) + { + noDemand = demand == 0; + if (noDemand) + stalled = true; + } + if (noDemand) + { + if (LOG.isDebugEnabled()) + LOG.debug("No demand, processing stalled for {}", this); + return false; + } + return true; + } + + protected void notifyContent(ByteBuffer buffer, boolean last, Callback callback) + { + try + { + if (LOG.isDebugEnabled()) + LOG.debug("Notifying content last={} {} for {}", last, BufferUtil.toDetailString(buffer), this); + consumer.onContent(buffer, last, callback); + } + catch (Throwable x) + { + callback.failed(x); + fail(x); + } + } + + private void notifyFailure(Throwable failure) + { + try + { + if (LOG.isDebugEnabled()) + LOG.debug("Notifying failure for {}", this, failure); + consumer.onFailure(failure); + } + catch (Exception x) + { + LOG.ignore(x); + } + } + + @Override + public String toString() + { + int demand; + boolean stalled; + try (AutoLock ignored = lock.lock()) + { + demand = this.demand; + stalled = this.stalled; + } + return String.format("%s.%s@%x[demand=%d,stalled=%b]", + getClass().getEnclosingClass().getSimpleName(), + getClass().getSimpleName(), hashCode(), demand, stalled); + } + } + + public interface Producer + { + boolean produce(ByteBuffer content, boolean lastContent, Callback callback); + } +} diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/AbstractTypedContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/AbstractTypedContentProvider.java index 679de25be1b..6779bd5028a 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/AbstractTypedContentProvider.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/AbstractTypedContentProvider.java @@ -20,6 +20,10 @@ package org.eclipse.jetty.client.util; import org.eclipse.jetty.client.api.ContentProvider; +/** + * @deprecated use {@link AbstractRequestContent} instead. + */ +@Deprecated public abstract class AbstractTypedContentProvider implements ContentProvider.Typed { private final String contentType; diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/AsyncRequestContent.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/AsyncRequestContent.java new file mode 100644 index 00000000000..d60a309a2e9 --- /dev/null +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/AsyncRequestContent.java @@ -0,0 +1,385 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.client.util; + +import java.io.Closeable; +import java.io.EOFException; +import java.io.IOException; +import java.io.InterruptedIOException; +import java.nio.ByteBuffer; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.locks.Condition; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.thread.AutoLock; + +public class AsyncRequestContent implements Request.Content, Request.Content.Subscription, Closeable +{ + private static final Logger LOG = Log.getLogger(AsyncRequestContent.class); + + private final AutoLock lock = new AutoLock(); + private final Condition flush = lock.newCondition(); + private final Deque chunks = new ArrayDeque<>(); + private final String contentType; + private long length = -1; + private Consumer consumer; + private boolean emitInitialContent; + private int demand; + private boolean stalled; + private boolean committed; + private boolean closed; + private boolean terminated; + private Throwable failure; + + public AsyncRequestContent(ByteBuffer... buffers) + { + this("application/octet-stream", buffers); + } + + public AsyncRequestContent(String contentType, ByteBuffer... buffers) + { + this.contentType = contentType; + Stream.of(buffers).forEach(this::offer); + } + + @Override + public String getContentType() + { + return contentType; + } + + @Override + public long getLength() + { + return length; + } + + @Override + public Subscription subscribe(Consumer consumer, boolean emitInitialContent) + { + try (AutoLock ignored = lock.lock()) + { + if (this.consumer != null) + throw new IllegalStateException("Multiple subscriptions not supported on " + this); + this.consumer = consumer; + this.emitInitialContent = emitInitialContent; + this.stalled = true; + if (closed) + length = chunks.stream().mapToLong(chunk -> chunk.buffer.remaining()).sum(); + } + if (LOG.isDebugEnabled()) + LOG.debug("Content subscription for {}: {}", this, consumer); + return this; + } + + @Override + public void demand() + { + boolean produce; + try (AutoLock ignored = lock.lock()) + { + ++demand; + produce = stalled; + if (stalled) + stalled = false; + } + if (LOG.isDebugEnabled()) + LOG.debug("Content demand, producing {} for {}", produce, this); + if (produce) + produce(); + } + + @Override + public void fail(Throwable failure) + { + List toFail = List.of(); + try (AutoLock ignored = lock.lock()) + { + if (this.failure == null) + { + this.failure = failure; + // Transfer all chunks to fail them all. + toFail = chunks.stream() + .map(chunk -> chunk.callback) + .collect(Collectors.toList()); + chunks.clear(); + flush.signal(); + } + } + toFail.forEach(c -> c.failed(failure)); + } + + public boolean offer(ByteBuffer buffer) + { + return offer(buffer, Callback.NOOP); + } + + public boolean offer(ByteBuffer buffer, Callback callback) + { + return offer(new Chunk(buffer, callback)); + } + + private boolean offer(Chunk chunk) + { + boolean produce = false; + Throwable failure; + try (AutoLock ignored = lock.lock()) + { + failure = this.failure; + if (failure == null) + { + if (closed) + { + failure = new IOException("closed"); + } + else + { + chunks.offer(chunk); + if (demand > 0) + { + if (stalled) + { + stalled = false; + produce = true; + } + } + } + } + } + if (LOG.isDebugEnabled()) + LOG.debug("Content offer {}, producing {} for {}", failure == null ? "succeeded" : "failed", produce, this, failure); + if (failure != null) + { + chunk.callback.failed(failure); + return false; + } + else if (produce) + { + produce(); + } + return true; + } + + private void produce() + { + while (true) + { + Throwable failure; + try (AutoLock ignored = lock.lock()) + { + failure = this.failure; + } + if (failure != null) + { + notifyFailure(consumer, failure); + return; + } + + try + { + Consumer consumer; + Chunk chunk = Chunk.EMPTY; + boolean lastContent = false; + try (AutoLock ignored = lock.lock()) + { + if (terminated) + throw new EOFException("Demand after last content"); + consumer = this.consumer; + if (committed || emitInitialContent) + { + chunk = chunks.poll(); + lastContent = closed && chunks.isEmpty(); + if (lastContent) + terminated = true; + } + if (chunk == null && (lastContent || !committed)) + chunk = Chunk.EMPTY; + if (chunk == null) + { + stalled = true; + } + else + { + --demand; + committed = true; + } + } + if (chunk == null) + { + if (LOG.isDebugEnabled()) + LOG.debug("No content, processing stalled for {}", this); + return; + } + + notifyContent(consumer, chunk.buffer, lastContent, Callback.from(this::notifyFlush, chunk.callback)); + + boolean noDemand; + try (AutoLock ignored = lock.lock()) + { + noDemand = demand == 0; + if (noDemand) + stalled = true; + } + if (noDemand) + { + if (LOG.isDebugEnabled()) + LOG.debug("No demand, processing stalled for {}", this); + return; + } + } + catch (Throwable x) + { + // Fail and loop around to notify the failure. + fail(x); + } + } + } + + private void notifyContent(Consumer consumer, ByteBuffer buffer, boolean last, Callback callback) + { + try + { + if (LOG.isDebugEnabled()) + LOG.debug("Notifying content last={} {} for {}", last, BufferUtil.toDetailString(buffer), this); + consumer.onContent(buffer, last, callback); + } + catch (Throwable x) + { + if (LOG.isDebugEnabled()) + LOG.debug("Failure while notifying content", x); + callback.failed(x); + fail(x); + } + } + + private void notifyFailure(Consumer consumer, Throwable failure) + { + try + { + if (LOG.isDebugEnabled()) + LOG.debug("Notifying failure for {}", this, failure); + consumer.onFailure(failure); + } + catch (Throwable x) + { + LOG.ignore(x); + } + } + + private void notifyFlush() + { + try (AutoLock ignored = lock.lock()) + { + flush.signal(); + } + } + + public void flush() throws IOException + { + try (AutoLock ignored = lock.lock()) + { + try + { + while (true) + { + // Always wrap the exception to make sure + // the stack trace comes from flush(). + if (failure != null) + throw new IOException(failure); + if (chunks.isEmpty()) + return; + flush.await(); + } + } + catch (InterruptedException x) + { + throw new InterruptedIOException(); + } + } + } + + @Override + public void close() + { + boolean produce = false; + try (AutoLock ignored = lock.lock()) + { + if (closed) + return; + closed = true; + if (demand > 0) + { + if (stalled) + { + stalled = false; + produce = true; + } + } + flush.signal(); + } + if (produce) + produce(); + } + + public boolean isClosed() + { + try (AutoLock ignored = lock.lock()) + { + return closed; + } + } + + @Override + public String toString() + { + int demand; + boolean stalled; + int chunks; + try (AutoLock ignored = lock.lock()) + { + demand = this.demand; + stalled = this.stalled; + chunks = this.chunks.size(); + } + return String.format("%s@%x[demand=%d,stalled=%b,chunks=%d]", getClass().getSimpleName(), hashCode(), demand, stalled, chunks); + } + + private static class Chunk + { + private static final Chunk EMPTY = new Chunk(BufferUtil.EMPTY_BUFFER, Callback.NOOP); + + private final ByteBuffer buffer; + private final Callback callback; + + private Chunk(ByteBuffer buffer, Callback callback) + { + this.buffer = Objects.requireNonNull(buffer); + this.callback = Objects.requireNonNull(callback); + } + } +} diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/ByteBufferContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/ByteBufferContentProvider.java index 1342dc6cc0c..e1c94d3595c 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/ByteBufferContentProvider.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/ByteBufferContentProvider.java @@ -30,7 +30,10 @@ import org.eclipse.jetty.client.api.ContentProvider; * The position and limit of the {@link ByteBuffer}s passed to the constructor are not modified, * and each invocation of the {@link #iterator()} method returns a {@link ByteBuffer#slice() slice} * of the original {@link ByteBuffer}. + * + * @deprecated use {@link ByteBufferRequestContent} instead. */ +@Deprecated public class ByteBufferContentProvider extends AbstractTypedContentProvider { private final ByteBuffer[] buffers; diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/ByteBufferRequestContent.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/ByteBufferRequestContent.java new file mode 100644 index 00000000000..c5c9f35f36b --- /dev/null +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/ByteBufferRequestContent.java @@ -0,0 +1,94 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.client.util; + +import java.io.EOFException; +import java.io.IOException; +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.util.Arrays; + +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.Callback; + +/** + *

A {@link Request.Content} for {@link ByteBuffer}s.

+ *

The position and limit of the {@link ByteBuffer}s passed to the constructor are not modified; + * content production returns a {@link ByteBuffer#slice() slice} of the original {@link ByteBuffer}. + */ +public class ByteBufferRequestContent extends AbstractRequestContent +{ + private final ByteBuffer[] buffers; + private final long length; + + public ByteBufferRequestContent(ByteBuffer... buffers) + { + this("application/octet-stream", buffers); + } + + public ByteBufferRequestContent(String contentType, ByteBuffer... buffers) + { + super(contentType); + this.buffers = buffers; + this.length = Arrays.stream(buffers).mapToLong(Buffer::remaining).sum(); + } + + @Override + public long getLength() + { + return length; + } + + @Override + public boolean isReproducible() + { + return true; + } + + @Override + protected Subscription newSubscription(Consumer consumer, boolean emitInitialContent, Throwable failure) + { + return new SubscriptionImpl(consumer, emitInitialContent, failure); + } + + private class SubscriptionImpl extends AbstractSubscription + { + private int index; + + private SubscriptionImpl(Consumer consumer, boolean emitInitialContent, Throwable failure) + { + super(consumer, emitInitialContent, failure); + } + + @Override + protected boolean produceContent(Producer producer) throws IOException + { + if (index < 0) + throw new EOFException("Demand after last content"); + ByteBuffer buffer = BufferUtil.EMPTY_BUFFER; + if (index < buffers.length) + buffer = buffers[index++]; + boolean lastContent = index == buffers.length; + if (lastContent) + index = -1; + return producer.produce(buffer.slice(), lastContent, Callback.NOOP); + } + } +} diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/BytesContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/BytesContentProvider.java index 631311fdb37..3f4d3f1f2f8 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/BytesContentProvider.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/BytesContentProvider.java @@ -26,7 +26,10 @@ import org.eclipse.jetty.client.api.ContentProvider; /** * A {@link ContentProvider} for byte arrays. + * + * @deprecated use {@link BytesRequestContent} instead. */ +@Deprecated public class BytesContentProvider extends AbstractTypedContentProvider { private final byte[][] bytes; diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/BytesRequestContent.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/BytesRequestContent.java new file mode 100644 index 00000000000..c1e2e09ba42 --- /dev/null +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/BytesRequestContent.java @@ -0,0 +1,91 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.client.util; + +import java.io.EOFException; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Arrays; + +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.Callback; + +/** + * A {@link Request.Content} for byte arrays. + */ +public class BytesRequestContent extends AbstractRequestContent +{ + private final byte[][] bytes; + private final long length; + + public BytesRequestContent(byte[]... bytes) + { + this("application/octet-stream", bytes); + } + + public BytesRequestContent(String contentType, byte[]... bytes) + { + super(contentType); + this.bytes = bytes; + this.length = Arrays.stream(bytes).mapToLong(a -> a.length).sum(); + } + + @Override + public long getLength() + { + return length; + } + + @Override + public boolean isReproducible() + { + return true; + } + + @Override + protected Subscription newSubscription(Consumer consumer, boolean emitInitialContent, Throwable failure) + { + return new SubscriptionImpl(consumer, emitInitialContent, failure); + } + + private class SubscriptionImpl extends AbstractSubscription + { + private int index; + + private SubscriptionImpl(Consumer consumer, boolean emitInitialContent, Throwable failure) + { + super(consumer, emitInitialContent, failure); + } + + @Override + protected boolean produceContent(Producer producer) throws IOException + { + if (index < 0) + throw new EOFException("Demand after last content"); + ByteBuffer buffer = BufferUtil.EMPTY_BUFFER; + if (index < bytes.length) + buffer = ByteBuffer.wrap(bytes[index++]); + boolean lastContent = index == bytes.length; + if (lastContent) + index = -1; + return producer.produce(buffer, lastContent, Callback.NOOP); + } + } +} diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/DeferredContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/DeferredContentProvider.java index 174b2e25356..2d369b08fa4 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/DeferredContentProvider.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/DeferredContentProvider.java @@ -85,7 +85,10 @@ import org.eclipse.jetty.util.Callback; * content.offer(ByteBuffer.wrap("some content".getBytes())); * } * + * + * @deprecated use {@link AsyncRequestContent} instead. */ +@Deprecated public class DeferredContentProvider implements AsyncContentProvider, Callback, Closeable { private static final Chunk CLOSE = new Chunk(BufferUtil.EMPTY_BUFFER, Callback.NOOP); @@ -285,6 +288,7 @@ public class DeferredContentProvider implements AsyncContentProvider, Callback, synchronized (lock) { chunk = current; + current = null; if (chunk != null) { --size; diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/FormContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/FormContentProvider.java index 280494282d8..2bff3857e3a 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/FormContentProvider.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/FormContentProvider.java @@ -30,7 +30,10 @@ import org.eclipse.jetty.util.Fields; /** * A {@link ContentProvider} for form uploads with the * "application/x-www-form-urlencoded" content type. + * + * @deprecated use {@link FormRequestContent} instead. */ +@Deprecated public class FormContentProvider extends StringContentProvider { public FormContentProvider(Fields fields) diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/FormRequestContent.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/FormRequestContent.java new file mode 100644 index 00000000000..03f29139c16 --- /dev/null +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/FormRequestContent.java @@ -0,0 +1,78 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.client.util; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.charset.UnsupportedCharsetException; + +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.util.Fields; + +/** + *

A {@link Request.Content} for form uploads with the + * "application/x-www-form-urlencoded" content type.

+ */ +public class FormRequestContent extends StringRequestContent +{ + public FormRequestContent(Fields fields) + { + this(fields, StandardCharsets.UTF_8); + } + + public FormRequestContent(Fields fields, Charset charset) + { + super("application/x-www-form-urlencoded", convert(fields, charset), charset); + } + + public static String convert(Fields fields) + { + return convert(fields, StandardCharsets.UTF_8); + } + + public static String convert(Fields fields, Charset charset) + { + // Assume 32 chars between name and value. + StringBuilder builder = new StringBuilder(fields.getSize() * 32); + for (Fields.Field field : fields) + { + for (String value : field.getValues()) + { + if (builder.length() > 0) + builder.append("&"); + builder.append(encode(field.getName(), charset)).append("=").append(encode(value, charset)); + } + } + return builder.toString(); + } + + private static String encode(String value, Charset charset) + { + try + { + return URLEncoder.encode(value, charset.name()); + } + catch (UnsupportedEncodingException x) + { + throw new UnsupportedCharsetException(charset.name()); + } + } +} diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamContentProvider.java index 4fd7c1a9fc9..e04a6962835 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamContentProvider.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamContentProvider.java @@ -50,7 +50,10 @@ import org.eclipse.jetty.util.log.Logger; * The {@link InputStream} passed to the constructor is by default closed when is it fully * consumed (or when an exception is thrown while reading it), unless otherwise specified * to the {@link #InputStreamContentProvider(java.io.InputStream, int, boolean) constructor}. + * + * @deprecated use {@link InputStreamRequestContent} instead */ +@Deprecated public class InputStreamContentProvider implements ContentProvider, Callback, Closeable { private static final Logger LOG = Log.getLogger(InputStreamContentProvider.class); diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamRequestContent.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamRequestContent.java new file mode 100644 index 00000000000..8a5e72994ce --- /dev/null +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamRequestContent.java @@ -0,0 +1,146 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.client.util; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; + +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.IO; + +/** + *

A {@link Request.Content} that produces content from an {@link InputStream}.

+ *

The input stream is read once and therefore fully consumed.

+ *

It is possible to specify, at the constructor, a buffer size used to read + * content from the stream, by default 1024 bytes.

+ *

The {@link InputStream} passed to the constructor is by default closed + * when is it fully consumed.

+ */ +public class InputStreamRequestContent extends AbstractRequestContent +{ + private static final int DEFAULT_BUFFER_SIZE = 4096; + + private final InputStream stream; + private final int bufferSize; + + public InputStreamRequestContent(InputStream stream) + { + this(stream, DEFAULT_BUFFER_SIZE); + } + + public InputStreamRequestContent(String contentType, InputStream stream) + { + this(contentType, stream, DEFAULT_BUFFER_SIZE); + } + + public InputStreamRequestContent(InputStream stream, int bufferSize) + { + this("application/octet-stream", stream, bufferSize); + } + + public InputStreamRequestContent(String contentType, InputStream stream, int bufferSize) + { + super(contentType); + this.stream = stream; + this.bufferSize = bufferSize; + } + + @Override + protected Subscription newSubscription(Consumer consumer, boolean emitInitialContent, Throwable failure) + { + return new SubscriptionImpl(consumer, emitInitialContent, failure); + } + + @Override + public void fail(Throwable failure) + { + super.fail(failure); + close(); + } + + protected ByteBuffer onRead(byte[] buffer, int offset, int length) + { + return ByteBuffer.wrap(buffer, offset, length); + } + + protected void onReadFailure(Throwable failure) + { + } + + private void close() + { + IO.close(stream); + } + + private class SubscriptionImpl extends AbstractSubscription + { + private boolean terminated; + + private SubscriptionImpl(Consumer consumer, boolean emitInitialContent, Throwable failure) + { + super(consumer, emitInitialContent, failure); + } + + @Override + protected boolean produceContent(Producer producer) throws IOException + { + if (terminated) + throw new EOFException("Demand after last content"); + byte[] bytes = new byte[bufferSize]; + int read = read(bytes); + ByteBuffer buffer = BufferUtil.EMPTY_BUFFER; + boolean last = true; + if (read < 0) + { + close(); + terminated = true; + } + else + { + buffer = onRead(bytes, 0, read); + last = false; + } + return producer.produce(buffer, last, Callback.NOOP); + } + + private int read(byte[] bytes) throws IOException + { + try + { + return stream.read(bytes); + } + catch (Throwable x) + { + onReadFailure(x); + throw x; + } + } + + @Override + public void fail(Throwable failure) + { + super.fail(failure); + close(); + } + } +} diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamResponseListener.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamResponseListener.java index a48115460e5..f03b511c569 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamResponseListener.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamResponseListener.java @@ -27,6 +27,7 @@ import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.Queue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; @@ -76,12 +77,12 @@ import org.eclipse.jetty.util.log.Logger; public class InputStreamResponseListener extends Listener.Adapter { private static final Logger LOG = Log.getLogger(InputStreamResponseListener.class); - private static final DeferredContentProvider.Chunk EOF = new DeferredContentProvider.Chunk(BufferUtil.EMPTY_BUFFER, Callback.NOOP); + private static final Chunk EOF = new Chunk(BufferUtil.EMPTY_BUFFER, Callback.NOOP); private final Object lock = this; private final CountDownLatch responseLatch = new CountDownLatch(1); private final CountDownLatch resultLatch = new CountDownLatch(1); private final AtomicReference stream = new AtomicReference<>(); - private final Queue chunks = new ArrayDeque<>(); + private final Queue chunks = new ArrayDeque<>(); private Response response; private Result result; private Throwable failure; @@ -120,7 +121,7 @@ public class InputStreamResponseListener extends Listener.Adapter { if (LOG.isDebugEnabled()) LOG.debug("Queueing content {}", content); - chunks.add(new DeferredContentProvider.Chunk(content, callback)); + chunks.add(new Chunk(content, callback)); lock.notifyAll(); } } @@ -268,7 +269,7 @@ public class InputStreamResponseListener extends Listener.Adapter { while (true) { - DeferredContentProvider.Chunk chunk = chunks.peek(); + Chunk chunk = chunks.peek(); if (chunk == null || chunk == EOF) break; callbacks.add(chunk.callback); @@ -299,7 +300,7 @@ public class InputStreamResponseListener extends Listener.Adapter Callback callback = null; synchronized (lock) { - DeferredContentProvider.Chunk chunk; + Chunk chunk; while (true) { chunk = chunks.peek(); @@ -367,4 +368,16 @@ public class InputStreamResponseListener extends Listener.Adapter super.close(); } } + + private static class Chunk + { + private final ByteBuffer buffer; + private final Callback callback; + + private Chunk(ByteBuffer buffer, Callback callback) + { + this.buffer = Objects.requireNonNull(buffer); + this.callback = Objects.requireNonNull(callback); + } + } } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/MultiPartContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/MultiPartContentProvider.java index 27ce2ca5136..901a1a6255c 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/MultiPartContentProvider.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/MultiPartContentProvider.java @@ -63,7 +63,10 @@ import org.eclipse.jetty.util.log.Logger; * <input type="file" name="icon" /> * </form> * + * + * @deprecated use {@link MultiPartRequestContent} instead. */ +@Deprecated public class MultiPartContentProvider extends AbstractTypedContentProvider implements AsyncContentProvider, Closeable { private static final Logger LOG = Log.getLogger(MultiPartContentProvider.class); diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/MultiPartRequestContent.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/MultiPartRequestContent.java new file mode 100644 index 00000000000..ddca420a34d --- /dev/null +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/MultiPartRequestContent.java @@ -0,0 +1,380 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.client.util; + +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.EOFException; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.http.HttpField; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.io.RuntimeIOException; +import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/** + *

A {@link Request.Content} for form uploads with the {@code "multipart/form-data"} + * content type.

+ *

Example usage:

+ *
+ * MultiPartRequestContent multiPart = new MultiPartRequestContent();
+ * multiPart.addFieldPart("field", new StringRequestContent("foo"), null);
+ * multiPart.addFilePart("icon", "img.png", new PathRequestContent(Paths.get("/tmp/img.png")), null);
+ * multiPart.close();
+ * ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
+ *         .method(HttpMethod.POST)
+ *         .content(multiPart)
+ *         .send();
+ * 
+ *

The above example would be the equivalent of submitting this form:

+ *
+ * <form method="POST" enctype="multipart/form-data"  accept-charset="UTF-8">
+ *     <input type="text" name="field" value="foo" />
+ *     <input type="file" name="icon" />
+ * </form>
+ * 
+ */ +public class MultiPartRequestContent extends AbstractRequestContent implements Closeable +{ + private static final Logger LOG = Log.getLogger(MultiPartRequestContent.class); + private static final byte[] COLON_SPACE_BYTES = new byte[]{':', ' '}; + private static final byte[] CR_LF_BYTES = new byte[]{'\r', '\n'}; + + private static String makeBoundary() + { + Random random = new Random(); + StringBuilder builder = new StringBuilder("JettyHttpClientBoundary"); + int length = builder.length(); + while (builder.length() < length + 16) + { + long rnd = random.nextLong(); + builder.append(Long.toString(rnd < 0 ? -rnd : rnd, 36)); + } + builder.setLength(length + 16); + return builder.toString(); + } + + private final List parts = new ArrayList<>(); + private final ByteBuffer firstBoundary; + private final ByteBuffer middleBoundary; + private final ByteBuffer onlyBoundary; + private final ByteBuffer lastBoundary; + private long length; + private volatile boolean closed; + + public MultiPartRequestContent() + { + this(makeBoundary()); + } + + public MultiPartRequestContent(String boundary) + { + super("multipart/form-data; boundary=" + boundary); + String firstBoundaryLine = "--" + boundary + "\r\n"; + this.firstBoundary = ByteBuffer.wrap(firstBoundaryLine.getBytes(StandardCharsets.US_ASCII)); + String middleBoundaryLine = "\r\n" + firstBoundaryLine; + this.middleBoundary = ByteBuffer.wrap(middleBoundaryLine.getBytes(StandardCharsets.US_ASCII)); + String onlyBoundaryLine = "--" + boundary + "--\r\n"; + this.onlyBoundary = ByteBuffer.wrap(onlyBoundaryLine.getBytes(StandardCharsets.US_ASCII)); + String lastBoundaryLine = "\r\n" + onlyBoundaryLine; + this.lastBoundary = ByteBuffer.wrap(lastBoundaryLine.getBytes(StandardCharsets.US_ASCII)); + this.length = -1; + } + + @Override + public long getLength() + { + return length; + } + + @Override + protected Subscription newSubscription(Consumer consumer, boolean emitInitialContent, Throwable failure) + { + length = calculateLength(); + return new SubscriptionImpl(consumer, emitInitialContent, failure); + } + + /** + *

Adds a field part with the given {@code name} as field name, and the given + * {@code content} as part content.

+ *

The {@code Content-Type} of this part will be obtained from:

+ *
    + *
  • the {@code Content-Type} header in the {@code fields} parameter; otherwise
  • + *
  • the {@link Request.Content#getContentType()}
  • + *
+ * + * @param name the part name + * @param content the part content + * @param fields the headers associated with this part + */ + public void addFieldPart(String name, Request.Content content, HttpFields fields) + { + addPart(new Part(name, null, content, fields)); + } + + /** + *

Adds a file part with the given {@code name} as field name, the given + * {@code fileName} as file name, and the given {@code content} as part content.

+ *

The {@code Content-Type} of this part will be obtained from:

+ *
    + *
  • the {@code Content-Type} header in the {@code fields} parameter; otherwise
  • + *
  • the {@link Request.Content#getContentType()}
  • + *
+ * + * @param name the part name + * @param fileName the file name associated to this part + * @param content the part content + * @param fields the headers associated with this part + */ + public void addFilePart(String name, String fileName, Request.Content content, HttpFields fields) + { + addPart(new Part(name, fileName, content, fields)); + } + + private void addPart(Part part) + { + parts.add(part); + if (LOG.isDebugEnabled()) + LOG.debug("Added {}", part); + } + + @Override + public void close() + { + closed = true; + } + + private long calculateLength() + { + // Compute the length, if possible. + if (parts.isEmpty()) + { + return onlyBoundary.remaining(); + } + else + { + long result = 0; + for (int i = 0; i < parts.size(); ++i) + { + result += (i == 0) ? firstBoundary.remaining() : middleBoundary.remaining(); + Part part = parts.get(i); + long partLength = part.length; + result += partLength; + if (partLength < 0) + { + result = -1; + break; + } + } + if (result > 0) + result += lastBoundary.remaining(); + return result; + } + } + + private static class Part + { + private final String name; + private final String fileName; + private final Request.Content content; + private final HttpFields fields; + private final ByteBuffer headers; + private final long length; + + private Part(String name, String fileName, Request.Content content, HttpFields fields) + { + this.name = name; + this.fileName = fileName; + this.content = content; + this.fields = fields; + this.headers = headers(); + this.length = content.getLength() < 0 ? -1 : headers.remaining() + content.getLength(); + } + + private ByteBuffer headers() + { + try + { + // Compute the Content-Disposition. + String contentDisposition = "Content-Disposition: form-data; name=\"" + name + "\""; + if (fileName != null) + contentDisposition += "; filename=\"" + fileName + "\""; + contentDisposition += "\r\n"; + + // Compute the Content-Type. + String contentType = fields == null ? null : fields.get(HttpHeader.CONTENT_TYPE); + if (contentType == null) + contentType = content.getContentType(); + contentType = "Content-Type: " + contentType + "\r\n"; + + if (fields == null || fields.size() == 0) + { + String headers = contentDisposition; + headers += contentType; + headers += "\r\n"; + return ByteBuffer.wrap(headers.getBytes(StandardCharsets.UTF_8)); + } + + ByteArrayOutputStream buffer = new ByteArrayOutputStream((fields.size() + 1) * contentDisposition.length()); + buffer.write(contentDisposition.getBytes(StandardCharsets.UTF_8)); + buffer.write(contentType.getBytes(StandardCharsets.UTF_8)); + for (HttpField field : fields) + { + if (HttpHeader.CONTENT_TYPE.equals(field.getHeader())) + continue; + buffer.write(field.getName().getBytes(StandardCharsets.US_ASCII)); + buffer.write(COLON_SPACE_BYTES); + String value = field.getValue(); + if (value != null) + buffer.write(value.getBytes(StandardCharsets.UTF_8)); + buffer.write(CR_LF_BYTES); + } + buffer.write(CR_LF_BYTES); + return ByteBuffer.wrap(buffer.toByteArray()); + } + catch (IOException x) + { + throw new RuntimeIOException(x); + } + } + + @Override + public String toString() + { + return String.format("%s@%x[name=%s,fileName=%s,length=%d,headers=%s]", + getClass().getSimpleName(), + hashCode(), + name, + fileName, + content.getLength(), + fields); + } + } + + private class SubscriptionImpl extends AbstractSubscription implements Consumer + { + private State state = State.FIRST_BOUNDARY; + private int index; + private Subscription subscription; + + private SubscriptionImpl(Consumer consumer, boolean emitInitialContent, Throwable failure) + { + super(consumer, emitInitialContent, failure); + } + + @Override + protected boolean produceContent(Producer producer) throws IOException + { + ByteBuffer buffer; + boolean last = false; + switch (state) + { + case FIRST_BOUNDARY: + { + if (parts.isEmpty()) + { + state = State.COMPLETE; + buffer = onlyBoundary.slice(); + last = true; + break; + } + else + { + state = State.HEADERS; + buffer = firstBoundary.slice(); + break; + } + } + case HEADERS: + { + Part part = parts.get(index); + Request.Content content = part.content; + subscription = content.subscribe(this, true); + state = State.CONTENT; + buffer = part.headers.slice(); + break; + } + case CONTENT: + { + buffer = null; + subscription.demand(); + break; + } + case MIDDLE_BOUNDARY: + { + state = State.HEADERS; + buffer = middleBoundary.slice(); + break; + } + case LAST_BOUNDARY: + { + state = State.COMPLETE; + buffer = lastBoundary.slice(); + last = true; + break; + } + case COMPLETE: + { + throw new EOFException("Demand after last content"); + } + default: + { + throw new IllegalStateException("Invalid state " + state); + } + } + return producer.produce(buffer, last, Callback.NOOP); + } + + @Override + public void onContent(ByteBuffer buffer, boolean last, Callback callback) + { + if (last) + { + ++index; + if (index < parts.size()) + state = State.MIDDLE_BOUNDARY; + else + state = State.LAST_BOUNDARY; + } + notifyContent(buffer, false, callback); + } + + @Override + public void onFailure(Throwable failure) + { + parts.stream() + .map(part -> part.content) + .forEach(content -> content.fail(failure)); + } + } + + private enum State + { + FIRST_BOUNDARY, HEADERS, CONTENT, MIDDLE_BOUNDARY, LAST_BOUNDARY, COMPLETE + } +} diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/OutputStreamContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/OutputStreamContentProvider.java index edffa00bd68..fe015c6f314 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/OutputStreamContentProvider.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/OutputStreamContentProvider.java @@ -72,7 +72,10 @@ import org.eclipse.jetty.util.Callback; * output.write("some content".getBytes()); * } * + * + * @deprecated use {@link OutputStreamRequestContent} instead */ +@Deprecated public class OutputStreamContentProvider implements AsyncContentProvider, Callback, Closeable { private final DeferredContentProvider deferred = new DeferredContentProvider(); diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/OutputStreamRequestContent.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/OutputStreamRequestContent.java new file mode 100644 index 00000000000..f093456c575 --- /dev/null +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/OutputStreamRequestContent.java @@ -0,0 +1,125 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.client.util; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.util.concurrent.ExecutionException; + +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.client.api.Response; +import org.eclipse.jetty.util.FutureCallback; + +/** + *

A {@link Request.Content} that provides content asynchronously through an {@link OutputStream} + * similar to {@link AsyncRequestContent}.

+ *

{@link OutputStreamRequestContent} can only be used in conjunction with + * {@link Request#send(Response.CompleteListener)} (and not with its blocking counterpart + * {@link Request#send()}) because it provides content asynchronously.

+ *

Content must be provided by writing to the {@link #getOutputStream() output stream} + * that must be {@link OutputStream#close() closed} when all content has been provided.

+ *

Example usage:

+ *
+ * HttpClient httpClient = ...;
+ *
+ * // Use try-with-resources to autoclose the output stream.
+ * OutputStreamRequestContent content = new OutputStreamRequestContent();
+ * try (OutputStream output = content.getOutputStream())
+ * {
+ *     httpClient.newRequest("localhost", 8080)
+ *             .content(content)
+ *             .send(new Response.CompleteListener()
+ *             {
+ *                 @Override
+ *                 public void onComplete(Result result)
+ *                 {
+ *                     // Your logic here
+ *                 }
+ *             });
+ *
+ *     // At a later time...
+ *     output.write("some content".getBytes());
+ *
+ *     // Even later...
+ *     output.write("more content".getBytes());
+ * } // Implicit call to output.close().
+ * 
+ */ +public class OutputStreamRequestContent extends AsyncRequestContent +{ + private final AsyncOutputStream output; + + public OutputStreamRequestContent() + { + this("application/octet-stream"); + } + + public OutputStreamRequestContent(String contentType) + { + super(contentType); + this.output = new AsyncOutputStream(); + } + + public OutputStream getOutputStream() + { + return output; + } + + private class AsyncOutputStream extends OutputStream + { + @Override + public void write(int b) throws IOException + { + write(new byte[]{(byte)b}, 0, 1); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException + { + try + { + FutureCallback callback = new FutureCallback(); + offer(ByteBuffer.wrap(b, off, len), callback); + callback.get(); + } + catch (InterruptedException x) + { + throw new InterruptedIOException(); + } + catch (ExecutionException x) + { + throw new IOException(x.getCause()); + } + } + + @Override + public void flush() throws IOException + { + OutputStreamRequestContent.this.flush(); + } + + @Override + public void close() + { + OutputStreamRequestContent.this.close(); + } + } +} diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/PathContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/PathContentProvider.java index 67509c9b8c5..90f6fd2b973 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/PathContentProvider.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/PathContentProvider.java @@ -43,7 +43,10 @@ import org.eclipse.jetty.util.log.Logger; * If a {@link ByteBufferPool} is provided via {@link #setByteBufferPool(ByteBufferPool)}, * the buffer will be allocated from that pool, otherwise one buffer will be * allocated and used to read the file.

+ * + * @deprecated use {@link PathRequestContent} instead. */ +@Deprecated public class PathContentProvider extends AbstractTypedContentProvider { private static final Logger LOG = Log.getLogger(PathContentProvider.class); diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/PathRequestContent.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/PathRequestContent.java new file mode 100644 index 00000000000..9f0165a26a9 --- /dev/null +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/PathRequestContent.java @@ -0,0 +1,170 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.client.util; + +import java.io.EOFException; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.ReadableByteChannel; +import java.nio.file.AccessDeniedException; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; + +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.io.ByteBufferPool; +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/** + *

A {@link Request.Content} for files using JDK 7's {@code java.nio.file} APIs.

+ *

It is possible to specify, at the constructor, a buffer size used to read + * content from the stream, by default 4096 bytes. + * If a {@link ByteBufferPool} is provided via {@link #setByteBufferPool(ByteBufferPool)}, + * the buffer will be allocated from that pool, otherwise one buffer will be + * allocated and used to read the file.

+ */ +public class PathRequestContent extends AbstractRequestContent +{ + private static final Logger LOG = Log.getLogger(PathRequestContent.class); + + private final Path filePath; + private final long fileSize; + private final int bufferSize; + private ByteBufferPool bufferPool; + private boolean useDirectByteBuffers = true; + + public PathRequestContent(Path filePath) throws IOException + { + this(filePath, 4096); + } + + public PathRequestContent(Path filePath, int bufferSize) throws IOException + { + this("application/octet-stream", filePath, bufferSize); + } + + public PathRequestContent(String contentType, Path filePath) throws IOException + { + this(contentType, filePath, 4096); + } + + public PathRequestContent(String contentType, Path filePath, int bufferSize) throws IOException + { + super(contentType); + if (!Files.isRegularFile(filePath)) + throw new NoSuchFileException(filePath.toString()); + if (!Files.isReadable(filePath)) + throw new AccessDeniedException(filePath.toString()); + this.filePath = filePath; + this.fileSize = Files.size(filePath); + this.bufferSize = bufferSize; + } + + @Override + public long getLength() + { + return fileSize; + } + + @Override + public boolean isReproducible() + { + return true; + } + + public ByteBufferPool getByteBufferPool() + { + return bufferPool; + } + + public void setByteBufferPool(ByteBufferPool byteBufferPool) + { + this.bufferPool = byteBufferPool; + } + + public boolean isUseDirectByteBuffers() + { + return useDirectByteBuffers; + } + + public void setUseDirectByteBuffers(boolean useDirectByteBuffers) + { + this.useDirectByteBuffers = useDirectByteBuffers; + } + + @Override + protected Subscription newSubscription(Consumer consumer, boolean emitInitialContent, Throwable failure) + { + return new SubscriptionImpl(consumer, emitInitialContent, failure); + } + + private class SubscriptionImpl extends AbstractSubscription + { + private ReadableByteChannel channel; + private long readTotal; + + private SubscriptionImpl(Consumer consumer, boolean emitInitialContent, Throwable failure) + { + super(consumer, emitInitialContent, failure); + } + + @Override + protected boolean produceContent(Producer producer) throws IOException + { + ByteBuffer buffer; + boolean last; + if (channel == null) + { + channel = Files.newByteChannel(filePath, StandardOpenOption.READ); + if (LOG.isDebugEnabled()) + LOG.debug("Opened file {}", filePath); + } + + buffer = bufferPool == null + ? BufferUtil.allocate(bufferSize, isUseDirectByteBuffers()) + : bufferPool.acquire(bufferSize, isUseDirectByteBuffers()); + + BufferUtil.clearToFill(buffer); + int read = channel.read(buffer); + BufferUtil.flipToFlush(buffer, 0); + if (LOG.isDebugEnabled()) + LOG.debug("Read {} bytes from {}", read, filePath); + if (!channel.isOpen() && read < 0) + throw new EOFException("EOF reached for " + filePath); + + if (read > 0) + readTotal += read; + last = readTotal == fileSize; + if (last) + IO.close(channel); + return producer.produce(buffer, last, Callback.from(() -> release(buffer))); + } + + private void release(ByteBuffer buffer) + { + if (bufferPool != null) + bufferPool.release(buffer); + } + } +} diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/StringContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/StringContentProvider.java index 2b3f1f4598b..6e18517ffac 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/StringContentProvider.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/StringContentProvider.java @@ -28,7 +28,10 @@ import org.eclipse.jetty.client.api.ContentProvider; *

* It is possible to specify, at the constructor, an encoding used to convert * the string into bytes, by default UTF-8. + * + * @deprecated use {@link StringRequestContent} instead. */ +@Deprecated public class StringContentProvider extends BytesContentProvider { public StringContentProvider(String content) diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/StringRequestContent.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/StringRequestContent.java new file mode 100644 index 00000000000..41054c62857 --- /dev/null +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/StringRequestContent.java @@ -0,0 +1,52 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.client.util; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +import org.eclipse.jetty.client.api.Request; + +/** + *

A {@link Request.Content} for strings.

+ *

It is possible to specify, at the constructor, an encoding used to convert + * the string into bytes, by default UTF-8.

+ */ +public class StringRequestContent extends BytesRequestContent +{ + public StringRequestContent(String content) + { + this("text/plain;charset=UTF-8", content); + } + + public StringRequestContent(String content, Charset encoding) + { + this("text/plain;charset=" + encoding.name(), content, encoding); + } + + public StringRequestContent(String contentType, String content) + { + this(contentType, content, StandardCharsets.UTF_8); + } + + public StringRequestContent(String contentType, String content, Charset encoding) + { + super(contentType, content.getBytes(encoding)); + } +} diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/ClientConnectionCloseTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ClientConnectionCloseTest.java index 8f3d2144e92..fab10cb6b6c 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/ClientConnectionCloseTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/ClientConnectionCloseTest.java @@ -29,8 +29,8 @@ import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.http.HttpConnectionOverHTTP; -import org.eclipse.jetty.client.util.DeferredContentProvider; -import org.eclipse.jetty.client.util.StringContentProvider; +import org.eclipse.jetty.client.util.AsyncRequestContent; +import org.eclipse.jetty.client.util.StringRequestContent; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeaderValue; import org.eclipse.jetty.http.HttpStatus; @@ -87,7 +87,7 @@ public class ClientConnectionCloseTest extends AbstractHttpClientServerTest var request = client.newRequest(host, port) .scheme(scenario.getScheme()) .header(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString()) - .content(new StringContentProvider("0")) + .body(new StringRequestContent("0")) .onRequestSuccess(r -> { HttpDestination destination = (HttpDestination)client.resolveDestination(r); @@ -184,12 +184,12 @@ public class ClientConnectionCloseTest extends AbstractHttpClientServerTest String host = "localhost"; int port = connector.getLocalPort(); - DeferredContentProvider content = new DeferredContentProvider(ByteBuffer.allocate(8)); + AsyncRequestContent content = new AsyncRequestContent(ByteBuffer.allocate(8)); CountDownLatch resultLatch = new CountDownLatch(1); var request = client.newRequest(host, port) .scheme(scenario.getScheme()) .header(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString()) - .content(content) + .body(content) .idleTimeout(idleTimeout, TimeUnit.MILLISECONDS) .onRequestSuccess(r -> { diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/ConnectionPoolTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ConnectionPoolTest.java index c90329b07a2..08139dbf1d6 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/ConnectionPoolTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/ConnectionPoolTest.java @@ -33,7 +33,7 @@ import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP; -import org.eclipse.jetty.client.util.BytesContentProvider; +import org.eclipse.jetty.client.util.BytesRequestContent; import org.eclipse.jetty.client.util.FutureResponseListener; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpMethod; @@ -213,7 +213,7 @@ public class ConnectionPoolTest break; case POST: request.header(HttpHeader.CONTENT_LENGTH, String.valueOf(contentLength)); - request.content(new BytesContentProvider(new byte[contentLength])); + request.body(new BytesRequestContent(new byte[contentLength])); break; default: throw new IllegalStateException(); diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java index b10039ea904..7545b50e6cd 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java @@ -22,9 +22,7 @@ import java.io.File; import java.io.IOException; import java.net.URI; import java.nio.ByteBuffer; -import java.util.Iterator; import java.util.List; -import java.util.NoSuchElementException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -37,15 +35,15 @@ import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.api.Authentication; import org.eclipse.jetty.client.api.Authentication.HeaderInfo; import org.eclipse.jetty.client.api.AuthenticationStore; -import org.eclipse.jetty.client.api.ContentProvider; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.api.Response.Listener; import org.eclipse.jetty.client.api.Result; import org.eclipse.jetty.client.util.AbstractAuthentication; +import org.eclipse.jetty.client.util.AbstractRequestContent; +import org.eclipse.jetty.client.util.AsyncRequestContent; import org.eclipse.jetty.client.util.BasicAuthentication; -import org.eclipse.jetty.client.util.DeferredContentProvider; import org.eclipse.jetty.client.util.DigestAuthentication; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpStatus; @@ -60,6 +58,8 @@ import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.util.Attributes; +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.util.security.Constraint; @@ -460,7 +460,7 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest CountDownLatch resultLatch = new CountDownLatch(1); byte[] data = new byte[]{'h', 'e', 'l', 'l', 'o'}; - DeferredContentProvider content = new DeferredContentProvider(ByteBuffer.wrap(data)) + AsyncRequestContent content = new AsyncRequestContent(ByteBuffer.wrap(data)) { @Override public boolean isReproducible() @@ -470,7 +470,7 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest }; Request request = client.newRequest(uri) .path("/secure") - .content(content); + .body(content); request.send(result -> { if (result.isSucceeded() && result.getResponse().getStatus() == HttpStatus.UNAUTHORIZED_401) @@ -527,7 +527,7 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest authenticationStore.addAuthentication(authentication); AtomicBoolean fail = new AtomicBoolean(true); - GeneratingContentProvider content = new GeneratingContentProvider(index -> + GeneratingRequestContent content = new GeneratingRequestContent(index -> { switch (index) { @@ -546,9 +546,8 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest catch (InterruptedException ignored) { } - // Trigger request failure. - throw new RuntimeException(); + throw new RuntimeException("explicitly_thrown_by_test"); } else { @@ -563,7 +562,7 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest client.newRequest("localhost", connector.getLocalPort()) .scheme(scenario.getScheme()) .path("/secure") - .content(content) + .body(content) .onResponseSuccess(r -> authLatch.countDown()) .send(result -> { @@ -803,23 +802,16 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest assertEquals(headerInfo.getParameter("nonce"), "1523430383="); } - private static class GeneratingContentProvider implements ContentProvider + private static class GeneratingRequestContent extends AbstractRequestContent { - private static final ByteBuffer DONE = ByteBuffer.allocate(0); - private final IntFunction generator; - private GeneratingContentProvider(IntFunction generator) + private GeneratingRequestContent(IntFunction generator) { + super("application/octet-stream"); this.generator = generator; } - @Override - public long getLength() - { - return -1; - } - @Override public boolean isReproducible() { @@ -827,36 +819,32 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest } @Override - public Iterator iterator() + protected Subscription newSubscription(Consumer consumer, boolean emitInitialContent, Throwable failure) { - return new Iterator() + return new SubscriptionImpl(consumer, emitInitialContent, failure); + } + + private class SubscriptionImpl extends AbstractSubscription + { + private int index; + + public SubscriptionImpl(Consumer consumer, boolean emitInitialContent, Throwable failure) { - private int index; - public ByteBuffer current; + super(consumer, emitInitialContent, failure); + } - @Override - @SuppressWarnings("ReferenceEquality") - public boolean hasNext() + @Override + protected boolean produceContent(Producer producer) + { + ByteBuffer buffer = generator.apply(index++); + boolean last = false; + if (buffer == null) { - if (current == null) - { - current = generator.apply(index++); - if (current == null) - current = DONE; - } - return current != DONE; + buffer = BufferUtil.EMPTY_BUFFER; + last = true; } - - @Override - public ByteBuffer next() - { - ByteBuffer result = current; - current = null; - if (result == null) - throw new NoSuchElementException(); - return result; - } - }; + return producer.produce(buffer, last, Callback.NOOP); + } } } } diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientFailureTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientFailureTest.java index a7c32775530..a9a4f2d42ac 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientFailureTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientFailureTest.java @@ -28,7 +28,7 @@ import java.util.concurrent.atomic.AtomicReference; import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP; import org.eclipse.jetty.client.http.HttpConnectionOverHTTP; -import org.eclipse.jetty.client.util.DeferredContentProvider; +import org.eclipse.jetty.client.util.AsyncRequestContent; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Server; @@ -116,16 +116,16 @@ public class HttpClientFailureTest }); client.start(); - final CountDownLatch commitLatch = new CountDownLatch(1); - final CountDownLatch completeLatch = new CountDownLatch(1); - DeferredContentProvider content = new DeferredContentProvider(); + CountDownLatch commitLatch = new CountDownLatch(1); + CountDownLatch completeLatch = new CountDownLatch(1); + AsyncRequestContent content = new AsyncRequestContent(); client.newRequest("localhost", connector.getLocalPort()) .onRequestCommit(request -> { connectionRef.get().getEndPoint().close(); commitLatch.countDown(); }) - .content(content) + .body(content) .idleTimeout(2, TimeUnit.SECONDS) .send(result -> { diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientRedirectTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientRedirectTest.java index a6ef79dcaa7..442c317d4a5 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientRedirectTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientRedirectTest.java @@ -36,7 +36,7 @@ import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.api.Result; -import org.eclipse.jetty.client.util.ByteBufferContentProvider; +import org.eclipse.jetty.client.util.ByteBufferRequestContent; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpStatus; @@ -153,7 +153,7 @@ public class HttpClientRedirectTest extends AbstractHttpClientServerTest .scheme(scenario.getScheme()) .method(HttpMethod.POST) .path("/307/localhost/done") - .content(new ByteBufferContentProvider(ByteBuffer.wrap(data))) + .body(new ByteBufferRequestContent(ByteBuffer.wrap(data))) .timeout(5, TimeUnit.SECONDS) .send(); assertNotNull(response); diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientSynchronizationTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientSynchronizationTest.java index eab4e28f00d..411d2d3a3d5 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientSynchronizationTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientSynchronizationTest.java @@ -47,20 +47,22 @@ public class HttpClientSynchronizationTest extends AbstractHttpClientServerTest server.stop(); int count = 10; - final CountDownLatch latch = new CountDownLatch(count); + CountDownLatch latch = new CountDownLatch(count); for (int i = 0; i < count; ++i) { Request request = client.newRequest("localhost", port) - .scheme(scenario.getScheme()); + .scheme(scenario.getScheme()) + .path("/" + i); - synchronized (this) + Object lock = this; + synchronized (lock) { request.send(new Response.Listener.Adapter() { @Override public void onFailure(Response response, Throwable failure) { - synchronized (HttpClientSynchronizationTest.this) + synchronized (lock) { assertThat(failure, Matchers.instanceOf(ConnectException.class)); latch.countDown(); @@ -80,20 +82,22 @@ public class HttpClientSynchronizationTest extends AbstractHttpClientServerTest start(scenario, new EmptyServerHandler()); int count = 10; - final CountDownLatch latch = new CountDownLatch(count); + CountDownLatch latch = new CountDownLatch(count); for (int i = 0; i < count; ++i) { Request request = client.newRequest("localhost", connector.getLocalPort()) - .scheme(scenario.getScheme()); + .scheme(scenario.getScheme()) + .path("/" + i); - synchronized (this) + Object lock = this; + synchronized (lock) { request.send(new Response.Listener.Adapter() { @Override public void onComplete(Result result) { - synchronized (HttpClientSynchronizationTest.this) + synchronized (lock) { assertFalse(result.isFailed()); latch.countDown(); diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java index aff6cd24616..481c1fc0db0 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java @@ -38,10 +38,8 @@ import java.nio.file.StandardOpenOption; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.NoSuchElementException; import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Exchanger; @@ -59,7 +57,6 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.api.Connection; -import org.eclipse.jetty.client.api.ContentProvider; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Destination; import org.eclipse.jetty.client.api.Request; @@ -67,11 +64,12 @@ import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.api.Result; import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP; import org.eclipse.jetty.client.http.HttpConnectionOverHTTP; +import org.eclipse.jetty.client.util.AbstractRequestContent; +import org.eclipse.jetty.client.util.AsyncRequestContent; import org.eclipse.jetty.client.util.BufferingResponseListener; -import org.eclipse.jetty.client.util.BytesContentProvider; -import org.eclipse.jetty.client.util.DeferredContentProvider; +import org.eclipse.jetty.client.util.BytesRequestContent; import org.eclipse.jetty.client.util.FutureResponseListener; -import org.eclipse.jetty.client.util.StringContentProvider; +import org.eclipse.jetty.client.util.StringRequestContent; import org.eclipse.jetty.http.BadMessageException; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpHeader; @@ -231,7 +229,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest }); String value1 = "\u20AC"; - String paramValue1 = URLEncoder.encode(value1, "UTF-8"); + String paramValue1 = URLEncoder.encode(value1, StandardCharsets.UTF_8); String query = paramName1 + "=" + paramValue1 + "&" + paramName2; ContentResponse response = client.GET(scenario.getScheme() + "://localhost:" + connector.getLocalPort() + "/?" + query); @@ -268,9 +266,9 @@ public class HttpClientTest extends AbstractHttpClientServerTest String value11 = "\u20AC"; String value12 = "\u20AA"; String value2 = "&"; - String paramValue11 = URLEncoder.encode(value11, "UTF-8"); - String paramValue12 = URLEncoder.encode(value12, "UTF-8"); - String paramValue2 = URLEncoder.encode(value2, "UTF-8"); + String paramValue11 = URLEncoder.encode(value11, StandardCharsets.UTF_8); + String paramValue12 = URLEncoder.encode(value12, StandardCharsets.UTF_8); + String paramValue2 = URLEncoder.encode(value2, StandardCharsets.UTF_8); String query = paramName1 + "=" + paramValue11 + "&" + paramName1 + "=" + paramValue12 + "&" + paramName2 + "=" + paramValue2; ContentResponse response = client.GET(scenario.getScheme() + "://localhost:" + connector.getLocalPort() + "/?" + query); @@ -318,7 +316,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest { String paramName = "a"; String paramValue = "\u20AC"; - String encodedParamValue = URLEncoder.encode(paramValue, "UTF-8"); + String encodedParamValue = URLEncoder.encode(paramValue, StandardCharsets.UTF_8); start(scenario, new AbstractHandler() { @Override @@ -372,7 +370,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest ContentResponse response = client.POST(scenario.getScheme() + "://localhost:" + connector.getLocalPort() + "/?b=1") .param(paramName, paramValue) - .content(new BytesContentProvider(content)) + .body(new BytesRequestContent(content)) .timeout(5, TimeUnit.SECONDS) .send(); @@ -404,7 +402,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest if (!Arrays.equals(content, bytes)) request.abort(new Exception()); }) - .content(new BytesContentProvider(content)) + .body(new BytesRequestContent(content)) .timeout(5, TimeUnit.SECONDS) .send(); @@ -435,7 +433,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest buffer.get(bytes); assertEquals(bytes[0], progress.getAndIncrement()); }) - .content(new BytesContentProvider(new byte[]{0}, new byte[]{1}, new byte[]{2}, new byte[]{3}, new byte[]{4})) + .body(new BytesRequestContent(new byte[]{0}, new byte[]{1}, new byte[]{2}, new byte[]{3}, new byte[]{4})) .timeout(5, TimeUnit.SECONDS) .send(); @@ -511,7 +509,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest client.setMaxConnectionsPerDestination(1); - try (StacklessLogging stackless = new StacklessLogging(org.eclipse.jetty.server.HttpChannel.class)) + try (StacklessLogging ignored = new StacklessLogging(org.eclipse.jetty.server.HttpChannel.class)) { CountDownLatch latch = new CountDownLatch(2); client.newRequest("localhost", connector.getLocalPort()) @@ -630,36 +628,23 @@ public class HttpClientTest extends AbstractHttpClientServerTest CountDownLatch latch = new CountDownLatch(1); client.newRequest("localhost", connector.getLocalPort()) .scheme(scenario.getScheme()) - // The second ByteBuffer set to null will throw an exception - .content(new ContentProvider() + .body(new AbstractRequestContent("application/octet-stream") { @Override - public long getLength() + protected Subscription newSubscription(Consumer consumer, boolean emitInitialContent, Throwable failure) { - return -1; - } - - @Override - public Iterator iterator() - { - return new Iterator<>() + return new AbstractSubscription(consumer, emitInitialContent, failure) { - @Override - public boolean hasNext() - { - return true; - } + private int count; @Override - public ByteBuffer next() + protected boolean produceContent(Producer producer) throws Exception { - throw new NoSuchElementException("explicitly_thrown_by_test"); - } - - @Override - public void remove() - { - throw new UnsupportedOperationException(); + if (count == 2) + throw new IOException("explicitly_thrown_by_test"); + ByteBuffer buffer = BufferUtil.allocate(512); + ++count; + return producer.produce(buffer, false, Callback.NOOP); } }; } @@ -1244,7 +1229,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException { // Send the headers at this point, then write the content - byte[] content = "TEST".getBytes("UTF-8"); + byte[] content = "TEST".getBytes(StandardCharsets.UTF_8); response.setContentLength(content.length); response.flushBuffer(); response.getOutputStream().write(content); @@ -1413,11 +1398,11 @@ public class HttpClientTest extends AbstractHttpClientServerTest } }); - DeferredContentProvider content = new DeferredContentProvider(ByteBuffer.wrap(new byte[]{0})); + AsyncRequestContent content = new AsyncRequestContent(ByteBuffer.wrap(new byte[]{0})); Request request = client.newRequest("localhost", connector.getLocalPort()) .scheme(scenario.getScheme()) .version(version) - .content(content); + .body(content); FutureResponseListener listener = new FutureResponseListener(request); request.send(listener); // Wait some time to simulate a slow request. @@ -1530,7 +1515,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest client = new HttpClient(new HttpClientTransportOverHTTP(clientConnector) { @Override - public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map context) throws IOException + public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map context) { return new HttpConnectionOverHTTP(endPoint, context) { @@ -1658,7 +1643,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest assertCopyRequest(client.newRequest("http://example.com/some/url") .method(HttpMethod.HEAD) .version(HttpVersion.HTTP_2) - .content(new StringContentProvider("some string")) + .body(new StringRequestContent("some string")) .timeout(321, TimeUnit.SECONDS) .idleTimeout(2221, TimeUnit.SECONDS) .followRedirects(true) @@ -1668,7 +1653,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest assertCopyRequest(client.newRequest("https://example.com") .method(HttpMethod.POST) .version(HttpVersion.HTTP_1_0) - .content(new StringContentProvider("some other string")) + .body(new StringRequestContent("some other string")) .timeout(123231, TimeUnit.SECONDS) .idleTimeout(232342, TimeUnit.SECONDS) .followRedirects(false) @@ -1797,7 +1782,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest start(scenario, new AbstractHandler() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException { baseRequest.setHandled(true); ServletOutputStream output = response.getOutputStream(); @@ -1845,7 +1830,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest assertEquals(original.getURI(), copy.getURI()); assertEquals(original.getMethod(), copy.getMethod()); assertEquals(original.getVersion(), copy.getVersion()); - assertEquals(original.getContent(), copy.getContent()); + assertEquals(original.getBody(), copy.getBody()); assertEquals(original.getIdleTimeout(), copy.getIdleTimeout()); assertEquals(original.getTimeout(), copy.getTimeout()); assertEquals(original.isFollowRedirects(), copy.isFollowRedirects()); @@ -1910,7 +1895,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest .scheme(scheme) .method("POST") .param("attempt", String.valueOf(retries)) - .content(new StringContentProvider("0123456789ABCDEF")) + .body(new StringRequestContent("0123456789ABCDEF")) .send(this); } } diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientUploadDuringServerShutdownTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientUploadDuringServerShutdownTest.java index 7572259e681..a3573378a5a 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientUploadDuringServerShutdownTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientUploadDuringServerShutdownTest.java @@ -32,7 +32,7 @@ import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.http.HttpChannelOverHTTP; import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP; import org.eclipse.jetty.client.http.HttpConnectionOverHTTP; -import org.eclipse.jetty.client.util.BytesContentProvider; +import org.eclipse.jetty.client.util.BytesRequestContent; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Server; @@ -116,7 +116,7 @@ public class HttpClientUploadDuringServerShutdownTest { int length = 16 * 1024 * 1024 + random.nextInt(16 * 1024 * 1024); client.newRequest("localhost", 8888) - .content(new BytesContentProvider(new byte[length])) + .body(new BytesRequestContent(new byte[length])) .send(result -> latch.countDown()); long sleep = 1 + random.nextInt(10); TimeUnit.MILLISECONDS.sleep(sleep); diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java index c0d4aba223c..406a602f136 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java @@ -32,7 +32,7 @@ import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.api.Result; -import org.eclipse.jetty.client.util.ByteBufferContentProvider; +import org.eclipse.jetty.client.util.ByteBufferRequestContent; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.server.handler.AbstractHandler; @@ -408,7 +408,7 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest CountDownLatch latch = new CountDownLatch(1); ByteBuffer buffer = ByteBuffer.allocate(16 * 1024 * 1024); Arrays.fill(buffer.array(), (byte)'x'); - request.content(new ByteBufferContentProvider(buffer)) + request.body(new ByteBufferRequestContent(buffer)) .send(new Response.Listener.Adapter() { @Override diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpRequestAbortTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpRequestAbortTest.java index d16996c8e26..fc94ff3bfef 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpRequestAbortTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpRequestAbortTest.java @@ -32,7 +32,7 @@ import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Result; -import org.eclipse.jetty.client.util.ByteBufferContentProvider; +import org.eclipse.jetty.client.util.ByteBufferRequestContent; import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.log.StacklessLogging; @@ -268,7 +268,7 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest { aborted.set(r.abort(cause)); latch.countDown(); - }).content(new ByteBufferContentProvider(ByteBuffer.wrap(new byte[]{0}), ByteBuffer.wrap(new byte[]{1})) + }).body(new ByteBufferRequestContent(ByteBuffer.wrap(new byte[]{0}), ByteBuffer.wrap(new byte[]{1})) { @Override public long getLength() @@ -323,7 +323,7 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest { aborted.set(r.abort(cause)); latch.countDown(); - }).content(new ByteBufferContentProvider(ByteBuffer.wrap(new byte[]{0}), ByteBuffer.wrap(new byte[]{1})) + }).body(new ByteBufferRequestContent(ByteBuffer.wrap(new byte[]{0}), ByteBuffer.wrap(new byte[]{1})) { @Override public long getLength() diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpResponseAbortTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpResponseAbortTest.java index 191a60dafd1..c85c98c253a 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpResponseAbortTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpResponseAbortTest.java @@ -24,11 +24,10 @@ import java.nio.ByteBuffer; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; -import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.eclipse.jetty.client.util.DeferredContentProvider; +import org.eclipse.jetty.client.util.AsyncRequestContent; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.handler.AbstractHandler; import org.junit.jupiter.params.ParameterizedTest; @@ -104,7 +103,7 @@ public class HttpResponseAbortTest extends AbstractHttpClientServerTest start(scenario, new AbstractHandler() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) { try { @@ -141,7 +140,7 @@ public class HttpResponseAbortTest extends AbstractHttpClientServerTest start(scenario, new AbstractHandler() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) { try { @@ -159,18 +158,18 @@ public class HttpResponseAbortTest extends AbstractHttpClientServerTest } }); - final DeferredContentProvider contentProvider = new DeferredContentProvider(ByteBuffer.allocate(1)); - final AtomicInteger completes = new AtomicInteger(); - final CountDownLatch completeLatch = new CountDownLatch(1); + AsyncRequestContent requestContent = new AsyncRequestContent(ByteBuffer.allocate(1)); + AtomicInteger completes = new AtomicInteger(); + CountDownLatch completeLatch = new CountDownLatch(1); client.newRequest("localhost", connector.getLocalPort()) .scheme(scenario.getScheme()) - .content(contentProvider) + .body(requestContent) .onResponseContent((response, content) -> { try { response.abort(new Exception()); - contentProvider.close(); + requestContent.close(); // Delay to let the request side to finish its processing. Thread.sleep(1000); } diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/api/Usage.java b/jetty-client/src/test/java/org/eclipse/jetty/client/api/Usage.java index 1d6d86a792d..81a06f8ec8f 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/api/Usage.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/api/Usage.java @@ -32,12 +32,12 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.util.AsyncRequestContent; import org.eclipse.jetty.client.util.BasicAuthentication; -import org.eclipse.jetty.client.util.DeferredContentProvider; import org.eclipse.jetty.client.util.FutureResponseListener; -import org.eclipse.jetty.client.util.InputStreamContentProvider; +import org.eclipse.jetty.client.util.InputStreamRequestContent; import org.eclipse.jetty.client.util.InputStreamResponseListener; -import org.eclipse.jetty.client.util.OutputStreamContentProvider; +import org.eclipse.jetty.client.util.OutputStreamRequestContent; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.util.FuturePromise; @@ -101,16 +101,12 @@ public class Usage client.newRequest("localhost", 8080) // Send asynchronously - .send(new Response.CompleteListener() + .send(result -> { - @Override - public void onComplete(Result result) + if (result.isSucceeded()) { - if (result.isSucceeded()) - { - responseRef.set(result.getResponse()); - latch.countDown(); - } + responseRef.set(result.getResponse()); + latch.countDown(); } }); @@ -278,7 +274,7 @@ public class Usage ContentResponse response = client.newRequest("localhost", 8080) // Provide the content as InputStream - .content(new InputStreamContentProvider(input)) + .body(new InputStreamRequestContent(input)) .send(); assertEquals(200, response.getStatus()); @@ -290,11 +286,11 @@ public class Usage HttpClient client = new HttpClient(); client.start(); - OutputStreamContentProvider content = new OutputStreamContentProvider(); + OutputStreamRequestContent content = new OutputStreamRequestContent(); try (OutputStream output = content.getOutputStream()) { client.newRequest("localhost", 8080) - .content(content) + .body(content) .send(result -> assertEquals(200, result.getResponse().getStatus())); output.write(new byte[1024]); @@ -308,15 +304,15 @@ public class Usage public void testProxyUsage() throws Exception { // In proxies, we receive the headers but not the content, so we must be able to send the request with - // a lazy content provider that does not block request.send(...) + // a lazy request content that does not block request.send(...) HttpClient client = new HttpClient(); client.start(); - final AtomicBoolean sendContent = new AtomicBoolean(true); - DeferredContentProvider async = new DeferredContentProvider(ByteBuffer.wrap(new byte[]{0, 1, 2})); + AtomicBoolean sendContent = new AtomicBoolean(true); + AsyncRequestContent async = new AsyncRequestContent(ByteBuffer.wrap(new byte[]{0, 1, 2})); client.newRequest("localhost", 8080) - .content(async) + .body(async) .send(new Response.Listener.Adapter() { @Override diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpSenderOverHTTPTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpSenderOverHTTPTest.java index 374fa8eda0b..e7c1df5d2aa 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpSenderOverHTTPTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpSenderOverHTTPTest.java @@ -33,7 +33,7 @@ import org.eclipse.jetty.client.api.Connection; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.api.Result; -import org.eclipse.jetty.client.util.ByteBufferContentProvider; +import org.eclipse.jetty.client.util.ByteBufferRequestContent; import org.eclipse.jetty.io.ByteArrayEndPoint; import org.eclipse.jetty.util.Promise; import org.hamcrest.Matchers; @@ -201,7 +201,7 @@ public class HttpSenderOverHTTPTest HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, destination, new Promise.Adapter()); Request request = client.newRequest(URI.create("http://localhost/")); String content = "abcdef"; - request.content(new ByteBufferContentProvider(ByteBuffer.wrap(content.getBytes(StandardCharsets.UTF_8)))); + request.body(new ByteBufferRequestContent(ByteBuffer.wrap(content.getBytes(StandardCharsets.UTF_8)))); final CountDownLatch headersLatch = new CountDownLatch(1); final CountDownLatch successLatch = new CountDownLatch(1); request.listener(new Request.Listener.Adapter() @@ -237,7 +237,7 @@ public class HttpSenderOverHTTPTest Request request = client.newRequest(URI.create("http://localhost/")); String content1 = "0123456789"; String content2 = "abcdef"; - request.content(new ByteBufferContentProvider(ByteBuffer.wrap(content1.getBytes(StandardCharsets.UTF_8)), ByteBuffer.wrap(content2.getBytes(StandardCharsets.UTF_8)))); + request.body(new ByteBufferRequestContent(ByteBuffer.wrap(content1.getBytes(StandardCharsets.UTF_8)), ByteBuffer.wrap(content2.getBytes(StandardCharsets.UTF_8)))); final CountDownLatch headersLatch = new CountDownLatch(1); final CountDownLatch successLatch = new CountDownLatch(1); request.listener(new Request.Listener.Adapter() @@ -273,7 +273,7 @@ public class HttpSenderOverHTTPTest Request request = client.newRequest(URI.create("http://localhost/")); String content1 = "0123456789"; String content2 = "ABCDEF"; - request.content(new ByteBufferContentProvider(ByteBuffer.wrap(content1.getBytes(StandardCharsets.UTF_8)), ByteBuffer.wrap(content2.getBytes(StandardCharsets.UTF_8))) + request.body(new ByteBufferRequestContent(ByteBuffer.wrap(content1.getBytes(StandardCharsets.UTF_8)), ByteBuffer.wrap(content2.getBytes(StandardCharsets.UTF_8))) { @Override public long getLength() diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/util/AsyncRequestContentTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/util/AsyncRequestContentTest.java new file mode 100644 index 00000000000..03dee4af220 --- /dev/null +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/util/AsyncRequestContentTest.java @@ -0,0 +1,151 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.client.util; + +import java.nio.ByteBuffer; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicReference; + +import org.eclipse.jetty.client.api.Request; +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 static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class AsyncRequestContentTest +{ + private ExecutorService executor; + + @BeforeEach + public void prepare() + { + executor = Executors.newCachedThreadPool(); + } + + @AfterEach + public void dispose() + { + executor.shutdownNow(); + } + + @Test + public void testWhenEmptyFlushDoesNotBlock() throws Exception + { + AsyncRequestContent content = new AsyncRequestContent(); + + Future task = executor.submit(() -> + { + content.flush(); + return null; + }); + + assertTrue(await(task, 5000)); + } + + @Test + public void testOfferFlushDemandBlocksUntilSucceeded() throws Exception + { + AsyncRequestContent content = new AsyncRequestContent(); + content.offer(ByteBuffer.allocate(1)); + + Future task = executor.submit(() -> + { + content.flush(); + return null; + }); + + // Wait until flush() blocks. + assertFalse(await(task, 500)); + + AtomicReference callbackRef = new AtomicReference<>(); + content.subscribe((buffer, last, callback) -> callbackRef.set(callback), true).demand(); + + // Flush should block until the callback is succeeded. + assertFalse(await(task, 500)); + + callbackRef.get().succeeded(); + + // Flush should return. + assertTrue(await(task, 5000)); + } + + @Test + public void testCloseFlushDoesNotBlock() throws Exception + { + AsyncRequestContent content = new AsyncRequestContent(); + content.close(); + + Future task = executor.submit(() -> + { + content.flush(); + return null; + }); + + assertTrue(await(task, 5000)); + } + + @Test + public void testStallThenCloseProduces() throws Exception + { + AsyncRequestContent content = new AsyncRequestContent(); + + CountDownLatch latch = new CountDownLatch(1); + Request.Content.Subscription subscription = content.subscribe((buffer, last, callback) -> + { + callback.succeeded(); + if (last) + latch.countDown(); + }, true); + + // Demand the initial content. + subscription.demand(); + + // Content must not be the last one. + assertFalse(latch.await(1, TimeUnit.SECONDS)); + + // Demand more content, now we are stalled. + subscription.demand(); + + // Close, we must be notified. + content.close(); + + assertTrue(latch.await(5, TimeUnit.SECONDS)); + } + + private boolean await(Future task, long time) throws Exception + { + try + { + task.get(time, TimeUnit.MILLISECONDS); + return true; + } + catch (TimeoutException x) + { + return false; + } + } +} diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/util/DeferredContentProviderTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/util/DeferredContentProviderTest.java deleted file mode 100644 index 88304c9f249..00000000000 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/util/DeferredContentProviderTest.java +++ /dev/null @@ -1,151 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under -// the terms of the Eclipse Public License 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0 -// -// This Source Code may also be made available under the following -// Secondary Licenses when the conditions for such availability set -// forth in the Eclipse Public License, v. 2.0 are satisfied: -// the Apache License v2.0 which is available at -// https://www.apache.org/licenses/LICENSE-2.0 -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package org.eclipse.jetty.client.util; - -import java.nio.ByteBuffer; -import java.util.Iterator; -import java.util.NoSuchElementException; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -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 static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -public class DeferredContentProviderTest -{ - private ExecutorService executor; - - @BeforeEach - public void prepare() throws Exception - { - executor = Executors.newCachedThreadPool(); - } - - @AfterEach - public void dispose() throws Exception - { - executor.shutdownNow(); - } - - @Test - public void testWhenEmptyFlushDoesNotBlock() throws Exception - { - final DeferredContentProvider provider = new DeferredContentProvider(); - - Future task = executor.submit(new Callable() - { - @Override - public Object call() throws Exception - { - provider.flush(); - return null; - } - }); - - assertTrue(await(task, 5, TimeUnit.SECONDS)); - } - - @Test - public void testOfferFlushBlocksUntilSucceeded() throws Exception - { - final DeferredContentProvider provider = new DeferredContentProvider(); - Iterator iterator = provider.iterator(); - - provider.offer(ByteBuffer.allocate(0)); - - Future task = executor.submit(new Callable() - { - @Override - public Object call() throws Exception - { - provider.flush(); - return null; - } - }); - - // Wait until flush() blocks. - assertFalse(await(task, 1, TimeUnit.SECONDS)); - - // Consume the content and succeed the callback. - iterator.next(); - ((Callback)iterator).succeeded(); - - // Flush should return. - assertTrue(await(task, 5, TimeUnit.SECONDS)); - } - - @Test - public void testCloseFlushDoesNotBlock() throws Exception - { - final DeferredContentProvider provider = new DeferredContentProvider(); - - provider.close(); - - Future task = executor.submit(new Callable() - { - @Override - public Object call() throws Exception - { - provider.flush(); - return null; - } - }); - - // Wait until flush() blocks. - assertTrue(await(task, 5, TimeUnit.SECONDS)); - } - - @Test - public void testCloseNextHasNextReturnsFalse() throws Exception - { - DeferredContentProvider provider = new DeferredContentProvider(); - Iterator iterator = provider.iterator(); - - provider.close(); - - assertFalse(iterator.hasNext()); - - assertThrows(NoSuchElementException.class, () -> iterator.next()); - - assertFalse(iterator.hasNext()); - } - - private boolean await(Future task, long time, TimeUnit unit) throws Exception - { - try - { - task.get(time, unit); - return true; - } - catch (TimeoutException x) - { - return false; - } - } -} diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/util/InputStreamContentProviderTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/util/InputStreamContentProviderTest.java deleted file mode 100644 index 97f0ba0fe6c..00000000000 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/util/InputStreamContentProviderTest.java +++ /dev/null @@ -1,161 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under -// the terms of the Eclipse Public License 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0 -// -// This Source Code may also be made available under the following -// Secondary Licenses when the conditions for such availability set -// forth in the Eclipse Public License, v. 2.0 are satisfied: -// the Apache License v2.0 which is available at -// https://www.apache.org/licenses/LICENSE-2.0 -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package org.eclipse.jetty.client.util; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.ByteBuffer; -import java.util.Iterator; -import java.util.NoSuchElementException; -import java.util.concurrent.atomic.AtomicBoolean; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -public class InputStreamContentProviderTest -{ - @Test - public void testHasNextFalseThenNext() - { - final AtomicBoolean closed = new AtomicBoolean(); - InputStream stream = new InputStream() - { - @Override - public int read() throws IOException - { - return -1; - } - - @Override - public void close() throws IOException - { - super.close(); - closed.compareAndSet(false, true); - } - }; - - InputStreamContentProvider provider = new InputStreamContentProvider(stream); - Iterator iterator = provider.iterator(); - - assertNotNull(iterator); - assertFalse(iterator.hasNext()); - - assertThrows(NoSuchElementException.class, () -> iterator.next()); - - assertFalse(iterator.hasNext()); - assertTrue(closed.get()); - } - - @Test - public void testStreamWithContentThenNextThenNext() - { - final AtomicBoolean closed = new AtomicBoolean(); - ByteArrayInputStream stream = new ByteArrayInputStream(new byte[]{1}) - { - @Override - public void close() throws IOException - { - super.close(); - closed.compareAndSet(false, true); - } - }; - - InputStreamContentProvider provider = new InputStreamContentProvider(stream); - Iterator iterator = provider.iterator(); - - assertNotNull(iterator); - - ByteBuffer buffer = iterator.next(); - - assertNotNull(buffer); - - assertThrows(NoSuchElementException.class, () -> iterator.next()); - - assertFalse(iterator.hasNext()); - assertTrue(closed.get()); - } - - @Test - public void testStreamWithExceptionThenNext() - { - final AtomicBoolean closed = new AtomicBoolean(); - InputStream stream = new InputStream() - { - @Override - public int read() throws IOException - { - throw new IOException(); - } - - @Override - public void close() throws IOException - { - super.close(); - closed.compareAndSet(false, true); - } - }; - - InputStreamContentProvider provider = new InputStreamContentProvider(stream); - Iterator iterator = provider.iterator(); - - assertNotNull(iterator); - - assertThrows(NoSuchElementException.class, () -> iterator.next()); - - assertFalse(iterator.hasNext()); - assertTrue(closed.get()); - } - - @Test - public void testHasNextWithExceptionThenNext() - { - final AtomicBoolean closed = new AtomicBoolean(); - InputStream stream = new InputStream() - { - @Override - public int read() throws IOException - { - throw new IOException(); - } - - @Override - public void close() throws IOException - { - super.close(); - closed.compareAndSet(false, true); - } - }; - - InputStreamContentProvider provider = new InputStreamContentProvider(stream); - Iterator iterator = provider.iterator(); - - assertNotNull(iterator); - assertTrue(iterator.hasNext()); - - assertThrows(NoSuchElementException.class, () -> iterator.next()); - - assertFalse(iterator.hasNext()); - assertTrue(closed.get()); - } -} diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/util/InputStreamContentTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/util/InputStreamContentTest.java new file mode 100644 index 00000000000..fa70c43229d --- /dev/null +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/util/InputStreamContentTest.java @@ -0,0 +1,266 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.client.util; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.function.BiConsumer; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.client.EmptyServerHandler; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.thread.QueuedThreadPool; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class InputStreamContentTest +{ + private Server server; + private ServerConnector connector; + private HttpClient client; + + private void start(Handler handler) throws Exception + { + startServer(handler); + startClient(); + } + + private void startServer(Handler handler) throws Exception + { + QueuedThreadPool serverThreads = new QueuedThreadPool(); + serverThreads.setName("server"); + server = new Server(serverThreads); + connector = new ServerConnector(server, 1, 1); + server.addConnector(connector); + server.setHandler(handler); + server.start(); + } + + private void startClient() throws Exception + { + QueuedThreadPool clientThreads = new QueuedThreadPool(); + clientThreads.setName("client"); + client = new HttpClient(); + client.setExecutor(clientThreads); + client.start(); + } + + @AfterEach + public void dispose() throws Exception + { + if (client != null) + client.stop(); + if (server != null) + server.stop(); + } + + private static List> content() + { + return List.of( + (request, stream) -> request.body(new InputStreamRequestContent(stream)), + (request, stream) -> request.body(new InputStreamRequestContent(stream)) + ); + } + + @ParameterizedTest + @MethodSource("content") + public void testInputStreamEmpty(BiConsumer setContent) throws Exception + { + CountDownLatch serverLatch = new CountDownLatch(1); + start(new EmptyServerHandler() + { + @Override + protected void service(String target, org.eclipse.jetty.server.Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + { + serverLatch.countDown(); + if (request.getInputStream().read() >= 0) + throw new IOException(); + } + }); + + CountDownLatch closeLatch = new CountDownLatch(1); + InputStream stream = new InputStream() + { + @Override + public int read() + { + return -1; + } + + @Override + public void close() throws IOException + { + super.close(); + closeLatch.countDown(); + } + }; + + Request request = client.newRequest("localhost", connector.getLocalPort()) + .timeout(5, TimeUnit.SECONDS); + setContent.accept(request, stream); + ContentResponse response = request.send(); + + assertTrue(serverLatch.await(5, TimeUnit.SECONDS)); + assertEquals(response.getStatus(), HttpStatus.OK_200); + assertTrue(closeLatch.await(5, TimeUnit.SECONDS)); + } + + @ParameterizedTest + @MethodSource("content") + public void testInputStreamThrowing(BiConsumer setContent) throws Exception + { + start(new EmptyServerHandler()); + + CountDownLatch closeLatch = new CountDownLatch(1); + InputStream stream = new InputStream() + { + @Override + public int read() throws IOException + { + throw new IOException(); + } + + @Override + public void close() throws IOException + { + super.close(); + closeLatch.countDown(); + } + }; + + Request request = client.newRequest("localhost", connector.getLocalPort()) + .timeout(5, TimeUnit.SECONDS); + setContent.accept(request, stream); + + assertThrows(ExecutionException.class, request::send); + assertTrue(closeLatch.await(5, TimeUnit.SECONDS)); + } + + @ParameterizedTest + @MethodSource("content") + public void testInputStreamThrowingAfterFirstRead(BiConsumer setContent) throws Exception + { + byte singleByteContent = 0; + CountDownLatch serverLatch = new CountDownLatch(1); + start(new EmptyServerHandler() + { + @Override + protected void service(String target, org.eclipse.jetty.server.Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + assertEquals(singleByteContent, request.getInputStream().read()); + serverLatch.countDown(); + } + }); + + CountDownLatch closeLatch = new CountDownLatch(1); + InputStream stream = new InputStream() + { + private int reads; + + @Override + public int read() throws IOException + { + if (++reads == 1) + return singleByteContent; + throw new IOException(); + } + + @Override + public void close() throws IOException + { + super.close(); + closeLatch.countDown(); + } + }; + + Request request = client.newRequest("localhost", connector.getLocalPort()) + .timeout(5, TimeUnit.SECONDS); + setContent.accept(request, stream); + + assertThrows(ExecutionException.class, request::send); + assertTrue(closeLatch.await(5, TimeUnit.SECONDS)); + } + + @ParameterizedTest + @MethodSource("content") + public void testInputStreamWithSmallContent(BiConsumer setContent) throws Exception + { + testInputStreamWithContent(setContent, new byte[1024]); + } + + @ParameterizedTest + @MethodSource("content") + public void testInputStreamWithLargeContent(BiConsumer setContent) throws Exception + { + testInputStreamWithContent(setContent, new byte[64 * 1024 * 1024]); + } + + private void testInputStreamWithContent(BiConsumer setContent, byte[] content) throws Exception + { + CountDownLatch serverLatch = new CountDownLatch(1); + start(new EmptyServerHandler() + { + @Override + protected void service(String target, org.eclipse.jetty.server.Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + serverLatch.countDown(); + IO.copy(request.getInputStream(), IO.getNullStream()); + } + }); + + CountDownLatch closeLatch = new CountDownLatch(1); + ByteArrayInputStream stream = new ByteArrayInputStream(content) + { + @Override + public void close() throws IOException + { + super.close(); + closeLatch.countDown(); + } + }; + + Request request = client.newRequest("localhost", connector.getLocalPort()) + .timeout(5, TimeUnit.SECONDS); + setContent.accept(request, stream); + ContentResponse response = request.send(); + + assertTrue(serverLatch.await(5, TimeUnit.SECONDS)); + assertEquals(response.getStatus(), HttpStatus.OK_200); + assertTrue(closeLatch.await(5, TimeUnit.SECONDS)); + } +} diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/util/MultiPartContentProviderTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/util/MultiPartContentTest.java similarity index 78% rename from jetty-client/src/test/java/org/eclipse/jetty/client/util/MultiPartContentProviderTest.java rename to jetty-client/src/test/java/org/eclipse/jetty/client/util/MultiPartContentTest.java index f84ca04d5df..af3fd7611bb 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/util/MultiPartContentProviderTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/util/MultiPartContentTest.java @@ -20,7 +20,6 @@ package org.eclipse.jetty.client.util; import java.io.BufferedWriter; import java.io.ByteArrayInputStream; -import java.io.Closeable; import java.io.File; import java.io.IOException; import java.io.OutputStream; @@ -32,12 +31,10 @@ import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.ArrayList; import java.util.Collection; -import java.util.Iterator; import java.util.List; import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; import javax.servlet.MultipartConfigElement; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; @@ -63,7 +60,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; // @checkstyle-disable-check : AvoidEscapedUnicodeCharactersCheck -public class MultiPartContentProviderTest extends AbstractHttpClientServerTest +public class MultiPartContentTest extends AbstractHttpClientServerTest { @ParameterizedTest @ArgumentsSource(ScenarioProvider.class) @@ -79,12 +76,12 @@ public class MultiPartContentProviderTest extends AbstractHttpClientServerTest } }); - MultiPartContentProvider multiPart = new MultiPartContentProvider(); + MultiPartRequestContent multiPart = new MultiPartRequestContent(); multiPart.close(); ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) .scheme(scenario.getScheme()) .method(HttpMethod.POST) - .content(multiPart) + .body(multiPart) .send(); assertEquals(200, response.getStatus()); @@ -109,13 +106,13 @@ public class MultiPartContentProviderTest extends AbstractHttpClientServerTest } }); - MultiPartContentProvider multiPart = new MultiPartContentProvider(); - multiPart.addFieldPart(name, new StringContentProvider(value), null); + MultiPartRequestContent multiPart = new MultiPartRequestContent(); + multiPart.addFieldPart(name, new StringRequestContent(value), null); multiPart.close(); ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) .scheme(scenario.getScheme()) .method(HttpMethod.POST) - .content(multiPart) + .body(multiPart) .send(); assertEquals(200, response.getStatus()); @@ -123,7 +120,7 @@ public class MultiPartContentProviderTest extends AbstractHttpClientServerTest @ParameterizedTest @ArgumentsSource(ScenarioProvider.class) - public void testFieldWithOverridenContentType(Scenario scenario) throws Exception + public void testFieldWithOverriddenContentType(Scenario scenario) throws Exception { String name = "field"; String value = "\u00e8"; @@ -146,16 +143,16 @@ public class MultiPartContentProviderTest extends AbstractHttpClientServerTest } }); - MultiPartContentProvider multiPart = new MultiPartContentProvider(); + MultiPartRequestContent multiPart = new MultiPartRequestContent(); HttpFields fields = new HttpFields(); fields.put(HttpHeader.CONTENT_TYPE, "text/plain;charset=" + encoding.name()); - BytesContentProvider content = new BytesContentProvider(value.getBytes(encoding)); + BytesRequestContent content = new BytesRequestContent(value.getBytes(encoding)); multiPart.addFieldPart(name, content, fields); multiPart.close(); ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) .scheme(scenario.getScheme()) .method(HttpMethod.POST) - .content(multiPart) + .body(multiPart) .send(); assertEquals(200, response.getStatus()); @@ -181,15 +178,15 @@ public class MultiPartContentProviderTest extends AbstractHttpClientServerTest } }); - MultiPartContentProvider multiPart = new MultiPartContentProvider(); - DeferredContentProvider content = new DeferredContentProvider(); + MultiPartRequestContent multiPart = new MultiPartRequestContent(); + AsyncRequestContent content = new AsyncRequestContent("text/plain"); multiPart.addFieldPart(name, content, null); multiPart.close(); CountDownLatch responseLatch = new CountDownLatch(1); client.newRequest("localhost", connector.getLocalPort()) .scheme(scenario.getScheme()) .method(HttpMethod.POST) - .content(multiPart) + .body(multiPart) .send(result -> { assertTrue(result.isSucceeded(), supply(result.getFailure())); @@ -233,8 +230,8 @@ public class MultiPartContentProviderTest extends AbstractHttpClientServerTest }); CountDownLatch closeLatch = new CountDownLatch(1); - MultiPartContentProvider multiPart = new MultiPartContentProvider(); - InputStreamContentProvider content = new InputStreamContentProvider(new ByteArrayInputStream(data) + MultiPartRequestContent multiPart = new MultiPartRequestContent(); + InputStreamRequestContent content = new InputStreamRequestContent(new ByteArrayInputStream(data) { @Override public void close() throws IOException @@ -250,7 +247,7 @@ public class MultiPartContentProviderTest extends AbstractHttpClientServerTest ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) .scheme(scenario.getScheme()) .method(HttpMethod.POST) - .content(multiPart) + .body(multiPart) .send(); assertTrue(closeLatch.await(5, TimeUnit.SECONDS)); @@ -289,8 +286,8 @@ public class MultiPartContentProviderTest extends AbstractHttpClientServerTest } }); - MultiPartContentProvider multiPart = new MultiPartContentProvider(); - PathContentProvider content = new PathContentProvider(contentType, tmpPath); + MultiPartRequestContent multiPart = new MultiPartRequestContent(); + PathRequestContent content = new PathRequestContent(contentType, tmpPath); content.setByteBufferPool(client.getByteBufferPool()); content.setUseDirectByteBuffers(client.isUseOutputDirectByteBuffers()); multiPart.addFilePart(name, tmpPath.getFileName().toString(), content, null); @@ -298,7 +295,7 @@ public class MultiPartContentProviderTest extends AbstractHttpClientServerTest ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) .scheme(scenario.getScheme()) .method(HttpMethod.POST) - .content(multiPart) + .body(multiPart) .send(); assertEquals(200, response.getStatus()); @@ -356,16 +353,16 @@ public class MultiPartContentProviderTest extends AbstractHttpClientServerTest } }); - MultiPartContentProvider multiPart = new MultiPartContentProvider(); + MultiPartRequestContent multiPart = new MultiPartRequestContent(); HttpFields fields = new HttpFields(); fields.put(headerName, headerValue); - multiPart.addFieldPart(field, new StringContentProvider(value, encoding), fields); - multiPart.addFilePart(fileField, tmpPath.getFileName().toString(), new PathContentProvider(tmpPath), null); + multiPart.addFieldPart(field, new StringRequestContent(value, encoding), fields); + multiPart.addFilePart(fileField, tmpPath.getFileName().toString(), new PathRequestContent(tmpPath), null); multiPart.close(); ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) .scheme(scenario.getScheme()) .method(HttpMethod.POST) - .content(multiPart) + .body(multiPart) .send(); assertEquals(200, response.getStatus()); @@ -406,16 +403,16 @@ public class MultiPartContentProviderTest extends AbstractHttpClientServerTest } }); - MultiPartContentProvider multiPart = new MultiPartContentProvider(); - DeferredContentProvider fieldContent = new DeferredContentProvider(); + MultiPartRequestContent multiPart = new MultiPartRequestContent(); + AsyncRequestContent fieldContent = new AsyncRequestContent(); multiPart.addFieldPart("field", fieldContent, null); - DeferredContentProvider fileContent = new DeferredContentProvider(); + AsyncRequestContent fileContent = new AsyncRequestContent(); multiPart.addFilePart("file", "fileName", fileContent, null); CountDownLatch responseLatch = new CountDownLatch(1); client.newRequest("localhost", connector.getLocalPort()) .scheme(scenario.getScheme()) .method(HttpMethod.POST) - .content(multiPart) + .body(multiPart) .send(result -> { assertTrue(result.isSucceeded(), supply(result.getFailure())); @@ -440,46 +437,6 @@ public class MultiPartContentProviderTest extends AbstractHttpClientServerTest assertTrue(responseLatch.await(5, TimeUnit.SECONDS)); } - @ParameterizedTest - @ArgumentsSource(ScenarioProvider.class) - public void testEachPartIsClosed(Scenario scenario) throws Exception - { - String name1 = "field1"; - String value1 = "value1"; - String name2 = "field2"; - String value2 = "value2"; - start(scenario, new AbstractMultiPartHandler() - { - @Override - protected void handle(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException - { - Collection parts = request.getParts(); - assertEquals(2, parts.size()); - Iterator iterator = parts.iterator(); - Part part1 = iterator.next(); - assertEquals(name1, part1.getName()); - assertEquals(value1, IO.toString(part1.getInputStream())); - Part part2 = iterator.next(); - assertEquals(name2, part2.getName()); - assertEquals(value2, IO.toString(part2.getInputStream())); - } - }); - - AtomicInteger closeCount = new AtomicInteger(); - MultiPartContentProvider multiPart = new MultiPartContentProvider(); - multiPart.addFieldPart(name1, new CloseableStringContentProvider(value1, closeCount::incrementAndGet), null); - multiPart.addFieldPart(name2, new CloseableStringContentProvider(value2, closeCount::incrementAndGet), null); - multiPart.close(); - ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) - .scheme(scenario.getScheme()) - .method(HttpMethod.POST) - .content(multiPart) - .send(); - - assertEquals(200, response.getStatus()); - assertEquals(2, closeCount.get()); - } - private abstract static class AbstractMultiPartHandler extends AbstractHandler { @Override @@ -493,49 +450,4 @@ public class MultiPartContentProviderTest extends AbstractHttpClientServerTest protected abstract void handle(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException; } - - private static class CloseableStringContentProvider extends StringContentProvider - { - private final Runnable closeFn; - - private CloseableStringContentProvider(String content, Runnable closeFn) - { - super(content); - this.closeFn = closeFn; - } - - @Override - public Iterator iterator() - { - return new CloseableIterator<>(super.iterator()); - } - - private class CloseableIterator implements Iterator, Closeable - { - private final Iterator iterator; - - public CloseableIterator(Iterator iterator) - { - this.iterator = iterator; - } - - @Override - public boolean hasNext() - { - return iterator.hasNext(); - } - - @Override - public T next() - { - return iterator.next(); - } - - @Override - public void close() - { - closeFn.run(); - } - } - } } diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/util/RequestContentBehaviorTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/util/RequestContentBehaviorTest.java new file mode 100644 index 00000000000..45c3ff305af --- /dev/null +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/util/RequestContentBehaviorTest.java @@ -0,0 +1,342 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.client.util; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.toolchain.test.MavenTestingUtils; +import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.Fields; +import org.eclipse.jetty.util.IO; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class RequestContentBehaviorTest +{ + private static Path emptyFile; + private static Path smallFile; + + @BeforeAll + public static void prepare() throws IOException + { + emptyFile = MavenTestingUtils.getTargetTestingPath().resolve("empty.txt"); + Files.newOutputStream(emptyFile, StandardOpenOption.CREATE).close(); + smallFile = MavenTestingUtils.getTargetTestingPath().resolve("small.txt"); + try (var s = Files.newOutputStream(smallFile, StandardOpenOption.CREATE)) + { + byte[] bytes = new byte[64]; + Arrays.fill(bytes, (byte)'#'); + s.write(bytes); + } + } + + @AfterAll + public static void dispose() throws IOException + { + Files.delete(emptyFile); + } + + public static List emptyContents() throws IOException + { + return List.of( + new AsyncRequestContent() + { + { + close(); + } + }, + new ByteBufferRequestContent(), + new BytesRequestContent(), + new FormRequestContent(new Fields()), + new InputStreamRequestContent(IO.getClosedStream()), + new MultiPartRequestContent() + { + { + close(); + } + }, + new PathRequestContent(emptyFile), + new StringRequestContent("") + ); + } + + @ParameterizedTest + @MethodSource("emptyContents") + public void testEmptyContentEmitInitialFirstDemand(Request.Content content) throws Exception + { + CountDownLatch latch = new CountDownLatch(1); + Request.Content.Subscription subscription = content.subscribe((buffer, last, callback) -> + { + if (last) + latch.countDown(); + }, true); + + subscription.demand(); + + assertTrue(latch.await(5, TimeUnit.SECONDS)); + } + + @ParameterizedTest + @MethodSource("emptyContents") + public void testEmptyContentDontEmitInitialFirstDemand(Request.Content content) throws Exception + { + AtomicBoolean initial = new AtomicBoolean(true); + AtomicReference latch = new AtomicReference<>(new CountDownLatch(1)); + Request.Content.Subscription subscription = content.subscribe((buffer, last, callback) -> + { + if (initial.get()) + { + if (!last) + latch.get().countDown(); + } + else + { + if (last) + latch.get().countDown(); + } + }, false); + + // Initial demand should have last=false. + subscription.demand(); + + assertTrue(latch.get().await(5, TimeUnit.SECONDS)); + + // More demand should have last=true. + initial.set(false); + latch.set(new CountDownLatch(1)); + subscription.demand(); + + assertTrue(latch.get().await(5, TimeUnit.SECONDS)); + } + + public static List smallContents() throws IOException + { + return List.of( + new AsyncRequestContent(ByteBuffer.allocate(64)) + { + { + close(); + } + }, + new ByteBufferRequestContent(ByteBuffer.allocate(64)), + new BytesRequestContent(new byte[64]), + new FormRequestContent(new Fields() + { + { + add("foo", "bar"); + } + }), + new InputStreamRequestContent(new ByteArrayInputStream(new byte[64])), + new MultiPartRequestContent() + { + { + addFieldPart("field", new StringRequestContent("*".repeat(64)), null); + } + }, + new PathRequestContent(smallFile), + new StringRequestContent("x".repeat(64)) + ); + } + + @ParameterizedTest + @MethodSource("smallContents") + public void testSmallContentEmitInitialFirstDemand(Request.Content content) throws Exception + { + AtomicBoolean initial = new AtomicBoolean(true); + CountDownLatch latch = new CountDownLatch(1); + AtomicReference subscriptionRef = new AtomicReference<>(); + Request.Content.Subscription subscription = content.subscribe((buffer, last, callback) -> + { + if (initial.getAndSet(false)) + assertTrue(buffer.hasRemaining()); + if (last) + latch.countDown(); + else + subscriptionRef.get().demand(); + }, true); + subscriptionRef.set(subscription); + + // Initial demand. + subscription.demand(); + + assertTrue(latch.await(5, TimeUnit.SECONDS)); + } + + @ParameterizedTest + @MethodSource("smallContents") + public void testSmallContentDontEmitInitialFirstDemand(Request.Content content) throws Exception + { + AtomicBoolean initial = new AtomicBoolean(true); + CountDownLatch latch = new CountDownLatch(1); + AtomicReference subscriptionRef = new AtomicReference<>(); + Request.Content.Subscription subscription = content.subscribe((buffer, last, callback) -> + { + if (initial.getAndSet(false)) + { + assertFalse(buffer.hasRemaining()); + assertFalse(last); + } + if (last) + latch.countDown(); + else + subscriptionRef.get().demand(); + }, false); + subscriptionRef.set(subscription); + + // Initial demand. + subscription.demand(); + + assertTrue(latch.await(5, TimeUnit.SECONDS)); + } + + @ParameterizedTest + @MethodSource("smallContents") + public void testSmallContentFailedBeforeSubscription(Request.Content content) + { + Throwable testFailure = new Throwable("test_failure"); + content.fail(testFailure); + + AtomicInteger notified = new AtomicInteger(); + AtomicReference failureRef = new AtomicReference<>(); + Request.Content.Subscription subscription = content.subscribe(new Request.Content.Consumer() + { + @Override + public void onContent(ByteBuffer buffer, boolean last, Callback callback) + { + notified.getAndIncrement(); + } + + @Override + public void onFailure(Throwable error) + { + testFailure.addSuppressed(new Throwable("suppressed")); + failureRef.compareAndSet(null, error); + } + }, true); + + // Initial demand. + subscription.demand(); + + assertEquals(0, notified.get()); + Throwable failure = failureRef.get(); + assertNotNull(failure); + assertSame(testFailure, failure); + assertEquals(1, failure.getSuppressed().length); + } + + @ParameterizedTest + @MethodSource("smallContents") + public void testSmallContentFailedAfterFirstDemand(Request.Content content) + { + Throwable testFailure = new Throwable("test_failure"); + + AtomicInteger notified = new AtomicInteger(); + AtomicReference failureRef = new AtomicReference<>(); + Request.Content.Subscription subscription = content.subscribe(new Request.Content.Consumer() + { + @Override + public void onContent(ByteBuffer buffer, boolean last, Callback callback) + { + notified.getAndIncrement(); + } + + @Override + public void onFailure(Throwable error) + { + testFailure.addSuppressed(new Throwable("suppressed")); + failureRef.compareAndSet(null, error); + } + }, false); + + // Initial demand. + subscription.demand(); + + assertEquals(1, notified.get()); + + content.fail(testFailure); + subscription.demand(); + + assertEquals(1, notified.get()); + Throwable failure = failureRef.get(); + assertNotNull(failure); + assertSame(testFailure, failure); + assertEquals(1, failure.getSuppressed().length); + } + + @ParameterizedTest + @MethodSource("smallContents") + public void testDemandAfterLastContentFails(Request.Content content) throws Exception + { + CountDownLatch latch = new CountDownLatch(1); + AtomicReference subscriptionRef = new AtomicReference<>(); + AtomicReference failureRef = new AtomicReference<>(); + Request.Content.Subscription subscription = content.subscribe(new Request.Content.Consumer() + { + @Override + public void onContent(ByteBuffer buffer, boolean last, Callback callback) + { + if (last) + latch.countDown(); + else + subscriptionRef.get().demand(); + } + + @Override + public void onFailure(Throwable error) + { + error.addSuppressed(new Throwable("suppressed")); + failureRef.compareAndSet(null, error); + } + }, false); + subscriptionRef.set(subscription); + + // Initial demand. + subscription.demand(); + + assertTrue(latch.await(5, TimeUnit.SECONDS)); + + // Demand more, should fail. + subscription.demand(); + + Throwable failure = failureRef.get(); + assertNotNull(failure); + assertEquals(1, failure.getSuppressed().length); + } +} diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/util/SPNEGOAuthenticationTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/util/SPNEGOAuthenticationTest.java index fbb7e26f57a..19105edb530 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/util/SPNEGOAuthenticationTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/util/SPNEGOAuthenticationTest.java @@ -291,7 +291,7 @@ public class SPNEGOAuthenticationTest extends AbstractHttpClientServerTest requests.set(0); ByteArrayInputStream input = new ByteArrayInputStream("hello_world".getBytes(StandardCharsets.UTF_8)); - request = client.newRequest(uri).method("POST").path("/secure").content(new InputStreamContentProvider(input)); + request = client.newRequest(uri).method("POST").path("/secure").body(new InputStreamRequestContent(input)); response = request.timeout(15, TimeUnit.SECONDS).send(); assertEquals(200, response.getStatus()); // Authentication expired, but POSTs are allowed. diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/util/TypedContentProviderTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/util/TypedContentProviderTest.java index 0a512bd6bf4..cce0415b3a5 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/util/TypedContentProviderTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/util/TypedContentProviderTest.java @@ -108,7 +108,7 @@ public class TypedContentProviderTest extends AbstractHttpClientServerTest ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) .scheme(scenario.getScheme()) .method(HttpMethod.POST) - .content(new FormContentProvider(fields)) + .body(new FormRequestContent(fields)) .header(HttpHeader.CONTENT_TYPE, contentType) .send(); @@ -135,7 +135,7 @@ public class TypedContentProviderTest extends AbstractHttpClientServerTest ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) .scheme(scenario.getScheme()) - .content(new StringContentProvider(null, content, StandardCharsets.UTF_8)) + .body(new StringRequestContent(null, content, StandardCharsets.UTF_8)) .send(); assertEquals(200, response.getStatus()); diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/clients/http/http-client-api.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/server/clients/http/http-client-api.adoc index 76a2208e05b..6d0cd736ae4 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/server/clients/http/http-client-api.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/server/clients/http/http-client-api.adoc @@ -188,7 +188,7 @@ Have a look at the link:{JDURL}/org/eclipse/jetty/client/api/Request.Listener.ht Jetty's HTTP client provides a number of utility classes off the shelf to handle request content. -You can provide request content as `String`, `byte[]`, `ByteBuffer`, `java.nio.file.Path`, `InputStream`, and provide your own implementation of `org.eclipse.jetty.client.api.ContentProvider`. +You can provide request content as `String`, `byte[]`, `ByteBuffer`, `java.nio.file.Path`, `InputStream`, and provide your own implementation of `org.eclipse.jetty.client.api.Request.Content`. Here’s an example that provides the request content using `java.nio.file.Paths`: [source, java, subs="{sub-order}"] @@ -199,47 +199,47 @@ ContentResponse response = httpClient.newRequest("http://domain.com/upload") .send(); ---- -This is equivalent to using the `PathContentProvider` utility class: +This is equivalent to using the `PathRequestContent` utility class: [source, java, subs="{sub-order}"] ---- ContentResponse response = httpClient.newRequest("http://domain.com/upload") .method(HttpMethod.POST) - .content(new PathContentProvider(Paths.get("file_to_upload.txt")), "text/plain") + .body(new PathRequestContent("text/plain", Paths.get("file_to_upload.txt"))) .send(); ---- -Alternatively, you can use `FileInputStream` via the `InputStreamContentProvider` utility class: +Alternatively, you can use `FileInputStream` via the `InputStreamRequestContent` utility class: [source, java, subs="{sub-order}"] ---- ContentResponse response = httpClient.newRequest("http://domain.com/upload") .method(HttpMethod.POST) - .content(new InputStreamContentProvider(new FileInputStream("file_to_upload.txt")), "text/plain") + .body(new InputStreamRequestContent("text/plain", new FileInputStream("file_to_upload.txt"))) .send(); ---- Since `InputStream` is blocking, then also the send of the request will block if the input stream blocks, even in case of usage of the asynchronous `HttpClient` APIs. -If you have already read the content in memory, you can pass it as a `byte[]` using the `BytesContentProvider` utility class: +If you have already read the content in memory, you can pass it as a `byte[]` using the `BytesRequestContent` utility class: [source, java, subs="{sub-order}"] ---- byte[] bytes = ...; ContentResponse response = httpClient.newRequest("http://domain.com/upload") .method(HttpMethod.POST) - .content(new BytesContentProvider(bytes), "text/plain") + .body(new BytesRequestContent("text/plain", bytes)) .send(); ---- -If the request content is not immediately available, but your application will be notified of the content to send, you can use `DeferredContentProvider` in this way: +If the request content is not immediately available, but your application will be notified of the content to send, you can use `AsyncRequestContent` in this way: [source, java, subs="{sub-order}"] ---- -DeferredContentProvider content = new DeferredContentProvider(); +AsyncRequestContent content = new AsyncRequestContent(); httpClient.newRequest("http://domain.com/upload") .method(HttpMethod.POST) - .content(content) + .body(content) .send(new Response.CompleteListener() { @Override @@ -249,17 +249,17 @@ httpClient.newRequest("http://domain.com/upload") } }); -// Content not available yet here +// Content not available yet here. ... -// An event happens, now content is available +// An event happens, now content is available. byte[] bytes = ...; content.offer(ByteBuffer.wrap(bytes)); ... -// All content has arrived +// All content has arrived. content.close(); ---- @@ -267,19 +267,19 @@ While the request content is awaited and consequently uploaded by the client app In this case, `Response.Listener` callbacks will be invoked before the request is fully sent. This allows fine-grained control of the request/response conversation: for example the server may reject contents that are too big, send a response to the client, which in turn may stop the content upload. -Another way to provide request content is by using an `OutputStreamContentProvider`, -which allows applications to write request content when it is available to the `OutputStream` provided by `OutputStreamContentProvider`: +Another way to provide request content is by using an `OutputStreamRequestContent`, +which allows applications to write request content when it is available to the `OutputStream` provided by `OutputStreamRequestContent`: [source, java, subs="{sub-order}"] ---- -OutputStreamContentProvider content = new OutputStreamContentProvider(); +OutputStreamRequestContent content = new OutputStreamRequestContent(); -// Use try-with-resources to close the OutputStream when all content is written +// Use try-with-resources to close the OutputStream when all content is written. try (OutputStream output = content.getOutputStream()) { client.newRequest("localhost", 8080) .method(HttpMethod.POST) - .content(content) + .body(content) .send(new Response.CompleteListener() { @Override @@ -291,10 +291,11 @@ try (OutputStream output = content.getOutputStream()) ... - // Write content - writeContent(output); + // Write content. + byte[] bytes = ...; + output.write(bytes); } -// End of try-with-resource, output.close() called automatically to signal end of content +// End of try-with-resource, output.close() called automatically to signal end of content. ---- [[http-client-response-content]] diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java index 24210431962..aed9d0c96e2 100644 --- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java +++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java @@ -19,11 +19,11 @@ package org.eclipse.jetty.fcgi.client.http; import java.net.URI; +import java.nio.ByteBuffer; import java.util.Locale; import org.eclipse.jetty.client.HttpChannel; import org.eclipse.jetty.client.HttpClient; -import org.eclipse.jetty.client.HttpContent; import org.eclipse.jetty.client.HttpExchange; import org.eclipse.jetty.client.HttpSender; import org.eclipse.jetty.client.api.Request; @@ -33,7 +33,6 @@ import org.eclipse.jetty.fcgi.generator.Generator; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; -import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.Jetty; import org.eclipse.jetty.util.StringUtil; @@ -56,7 +55,7 @@ public class HttpSenderOverFCGI extends HttpSender } @Override - protected void sendHeaders(HttpExchange exchange, HttpContent content, Callback callback) + protected void sendHeaders(HttpExchange exchange, ByteBuffer contentBuffer, boolean lastContent, Callback callback) { Request request = exchange.getRequest(); // Copy the request headers to be able to convert them properly @@ -102,32 +101,31 @@ public class HttpSenderOverFCGI extends HttpSender transport.customize(request, fcgiHeaders); int id = getHttpChannel().getRequest(); - boolean hasContent = content.hasContent(); - Generator.Result headersResult = generator.generateRequestHeaders(id, fcgiHeaders, - hasContent ? callback : Callback.NOOP); - if (hasContent) + if (contentBuffer.hasRemaining() || lastContent) { - getHttpChannel().flush(headersResult); + Generator.Result headersResult = generator.generateRequestHeaders(id, fcgiHeaders, Callback.NOOP); + Generator.Result contentResult = generator.generateRequestContent(id, contentBuffer, lastContent, callback); + getHttpChannel().flush(headersResult, contentResult); } else { - Generator.Result noContentResult = generator.generateRequestContent(id, BufferUtil.EMPTY_BUFFER, true, callback); - getHttpChannel().flush(headersResult, noContentResult); + Generator.Result headersResult = generator.generateRequestHeaders(id, fcgiHeaders, callback); + getHttpChannel().flush(headersResult); } } @Override - protected void sendContent(HttpExchange exchange, HttpContent content, Callback callback) + protected void sendContent(HttpExchange exchange, ByteBuffer contentBuffer, boolean lastContent, Callback callback) { - if (content.isConsumed()) + if (contentBuffer.hasRemaining() || lastContent) { - callback.succeeded(); + int request = getHttpChannel().getRequest(); + Generator.Result result = generator.generateRequestContent(request, contentBuffer, lastContent, callback); + getHttpChannel().flush(result); } else { - int request = getHttpChannel().getRequest(); - Generator.Result result = generator.generateRequestContent(request, content.getByteBuffer(), content.isLast(), callback); - getHttpChannel().flush(result); + callback.succeeded(); } } } diff --git a/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/HttpClientTest.java b/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/HttpClientTest.java index 704d4a64f90..c6f499cf375 100644 --- a/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/HttpClientTest.java +++ b/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/HttpClientTest.java @@ -23,6 +23,7 @@ import java.io.IOException; import java.net.URI; import java.net.URLEncoder; import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Random; import java.util.concurrent.CountDownLatch; @@ -40,8 +41,8 @@ import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; -import org.eclipse.jetty.client.util.BytesContentProvider; -import org.eclipse.jetty.client.util.DeferredContentProvider; +import org.eclipse.jetty.client.util.AsyncRequestContent; +import org.eclipse.jetty.client.util.BytesRequestContent; import org.eclipse.jetty.client.util.FutureResponseListener; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.io.MappedByteBufferPool; @@ -148,22 +149,22 @@ public class HttpClientTest extends AbstractHttpClientServerTest response.setCharacterEncoding("UTF-8"); ServletOutputStream output = response.getOutputStream(); String paramValue1 = request.getParameter(paramName1); - output.write(paramValue1.getBytes("UTF-8")); + output.write(paramValue1.getBytes(StandardCharsets.UTF_8)); String paramValue2 = request.getParameter(paramName2); assertEquals("", paramValue2); - output.write("empty".getBytes("UTF-8")); + output.write("empty".getBytes(StandardCharsets.UTF_8)); baseRequest.setHandled(true); } }); String value1 = "\u20AC"; - String paramValue1 = URLEncoder.encode(value1, "UTF-8"); + String paramValue1 = URLEncoder.encode(value1, StandardCharsets.UTF_8); String query = paramName1 + "=" + paramValue1 + "&" + paramName2; ContentResponse response = client.GET(scheme + "://localhost:" + connector.getLocalPort() + "/?" + query); assertNotNull(response); assertEquals(200, response.getStatus()); - String content = new String(response.getContent(), "UTF-8"); + String content = new String(response.getContent(), StandardCharsets.UTF_8); assertEquals(value1 + "empty", content); } @@ -182,10 +183,10 @@ public class HttpClientTest extends AbstractHttpClientServerTest String[] paramValues1 = request.getParameterValues(paramName1); for (String paramValue : paramValues1) { - output.write(paramValue.getBytes("UTF-8")); + output.write(paramValue.getBytes(StandardCharsets.UTF_8)); } String paramValue2 = request.getParameter(paramName2); - output.write(paramValue2.getBytes("UTF-8")); + output.write(paramValue2.getBytes(StandardCharsets.UTF_8)); baseRequest.setHandled(true); } }); @@ -193,15 +194,15 @@ public class HttpClientTest extends AbstractHttpClientServerTest String value11 = "\u20AC"; String value12 = "\u20AA"; String value2 = "&"; - String paramValue11 = URLEncoder.encode(value11, "UTF-8"); - String paramValue12 = URLEncoder.encode(value12, "UTF-8"); - String paramValue2 = URLEncoder.encode(value2, "UTF-8"); + String paramValue11 = URLEncoder.encode(value11, StandardCharsets.UTF_8); + String paramValue12 = URLEncoder.encode(value12, StandardCharsets.UTF_8); + String paramValue2 = URLEncoder.encode(value2, StandardCharsets.UTF_8); String query = paramName1 + "=" + paramValue11 + "&" + paramName1 + "=" + paramValue12 + "&" + paramName2 + "=" + paramValue2; ContentResponse response = client.GET(scheme + "://localhost:" + connector.getLocalPort() + "/?" + query); assertNotNull(response); assertEquals(200, response.getStatus()); - String content = new String(response.getContent(), "UTF-8"); + String content = new String(response.getContent(), StandardCharsets.UTF_8); assertEquals(value11 + value12 + value2, content); } @@ -233,7 +234,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest assertNotNull(response); assertEquals(200, response.getStatus()); - assertEquals(paramValue, new String(response.getContent(), "UTF-8")); + assertEquals(paramValue, new String(response.getContent(), StandardCharsets.UTF_8)); } @Test @@ -258,14 +259,14 @@ public class HttpClientTest extends AbstractHttpClientServerTest }); String uri = scheme + "://localhost:" + connector.getLocalPort() + - "/?" + paramName + "=" + URLEncoder.encode(paramValue, "UTF-8"); + "/?" + paramName + "=" + URLEncoder.encode(paramValue, StandardCharsets.UTF_8); ContentResponse response = client.POST(uri) .timeout(5, TimeUnit.SECONDS) .send(); assertNotNull(response); assertEquals(200, response.getStatus()); - assertEquals(paramValue, new String(response.getContent(), "UTF-8")); + assertEquals(paramValue, new String(response.getContent(), StandardCharsets.UTF_8)); } @Test @@ -297,7 +298,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest assertNotNull(response); assertEquals(200, response.getStatus()); - assertEquals(paramValue, new String(response.getContent(), "UTF-8")); + assertEquals(paramValue, new String(response.getContent(), StandardCharsets.UTF_8)); } @Test @@ -326,7 +327,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest { ContentResponse response = client.POST(scheme + "://localhost:" + connector.getLocalPort() + "/?b=1") .param(paramName, paramValue) - .content(new BytesContentProvider(content)) + .body(new BytesRequestContent(content)) .timeout(5, TimeUnit.SECONDS) .send(); @@ -350,7 +351,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest if (!Arrays.equals(content, bytes)) request.abort(new Exception()); }) - .content(new BytesContentProvider(content)) + .body(new BytesRequestContent(content)) .timeout(5, TimeUnit.SECONDS) .send(); @@ -372,7 +373,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest buffer.get(bytes); assertEquals(bytes[0], progress.getAndIncrement()); }) - .content(new BytesContentProvider(new byte[]{0}, new byte[]{1}, new byte[]{ + .body(new BytesRequestContent(new byte[]{0}, new byte[]{1}, new byte[]{ 2 }, new byte[]{3}, new byte[]{4})) .timeout(5, TimeUnit.SECONDS) @@ -636,10 +637,10 @@ public class HttpClientTest extends AbstractHttpClientServerTest } }); - DeferredContentProvider content = new DeferredContentProvider(ByteBuffer.wrap(new byte[]{0})); + AsyncRequestContent content = new AsyncRequestContent(ByteBuffer.wrap(new byte[]{0})); Request request = client.newRequest("localhost", connector.getLocalPort()) .scheme(scheme) - .content(content); + .body(content); FutureResponseListener listener = new FutureResponseListener(request); request.send(listener); // Wait some time to simulate a slow request. diff --git a/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpSenderOverHTTP2.java b/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpSenderOverHTTP2.java index 1e63aaadc45..b573a07cfe8 100644 --- a/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpSenderOverHTTP2.java +++ b/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpSenderOverHTTP2.java @@ -19,10 +19,10 @@ package org.eclipse.jetty.http2.client.http; import java.net.URI; +import java.nio.ByteBuffer; import java.util.function.Consumer; import java.util.function.Supplier; -import org.eclipse.jetty.client.HttpContent; import org.eclipse.jetty.client.HttpExchange; import org.eclipse.jetty.client.HttpRequest; import org.eclipse.jetty.client.HttpSender; @@ -35,11 +35,16 @@ import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.frames.DataFrame; import org.eclipse.jetty.http2.frames.HeadersFrame; +import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.Promise; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; public class HttpSenderOverHTTP2 extends HttpSender { + private static final Logger LOG = Log.getLogger(HttpSenderOverHTTP2.class); + public HttpSenderOverHTTP2(HttpChannelOverHTTP2 channel) { super(channel); @@ -52,7 +57,7 @@ public class HttpSenderOverHTTP2 extends HttpSender } @Override - protected void sendHeaders(HttpExchange exchange, final HttpContent content, final Callback callback) + protected void sendHeaders(HttpExchange exchange, ByteBuffer contentBuffer, boolean lastContent, final Callback callback) { HttpRequest request = exchange.getRequest(); boolean isTunnel = HttpMethod.CONNECT.is(request.getMethod()); @@ -88,31 +93,10 @@ public class HttpSenderOverHTTP2 extends HttpSender } else { - if (content.hasContent()) - { - headersFrame = new HeadersFrame(metaData, null, false); - promise = new HeadersPromise(request, callback, stream -> - { - if (expects100Continue(request)) - { - // Don't send the content yet. - callback.succeeded(); - } - else - { - boolean advanced = content.advance(); - boolean lastContent = content.isLast(); - if (advanced || lastContent) - sendContent(stream, content, trailerSupplier, callback); - else - callback.succeeded(); - } - }); - } - else + if (BufferUtil.isEmpty(contentBuffer) && lastContent) { HttpFields trailers = trailerSupplier == null ? null : trailerSupplier.get(); - boolean endStream = trailers == null || trailers.size() <= 0; + boolean endStream = trailers == null || trailers.size() == 0; headersFrame = new HeadersFrame(metaData, null, endStream); promise = new HeadersPromise(request, callback, stream -> { @@ -122,6 +106,12 @@ public class HttpSenderOverHTTP2 extends HttpSender sendTrailers(stream, trailers, callback); }); } + else + { + headersFrame = new HeadersFrame(metaData, null, false); + promise = new HeadersPromise(request, callback, stream -> + sendContent(stream, contentBuffer, lastContent, trailerSupplier, callback)); + } } // TODO optimize the send of HEADERS and DATA frames. HttpChannelOverHTTP2 channel = getHttpChannel(); @@ -147,38 +137,57 @@ public class HttpSenderOverHTTP2 extends HttpSender } @Override - protected void sendContent(HttpExchange exchange, HttpContent content, Callback callback) + protected void sendContent(HttpExchange exchange, ByteBuffer contentBuffer, boolean lastContent, Callback callback) { - if (content.isConsumed()) + Stream stream = getHttpChannel().getStream(); + Supplier trailerSupplier = exchange.getRequest().getTrailers(); + sendContent(stream, contentBuffer, lastContent, trailerSupplier, callback); + } + + private void sendContent(Stream stream, ByteBuffer buffer, boolean lastContent, Supplier trailerSupplier, Callback callback) + { + boolean hasContent = buffer.hasRemaining(); + if (lastContent) { - // The superclass calls sendContent() one more time after the last content. - // This is necessary for HTTP/1.1 to generate the terminal chunk (with trailers), - // but it's not necessary for HTTP/2 so we just succeed the callback. - callback.succeeded(); + // Call the trailers supplier as late as possible. + HttpFields trailers = trailerSupplier == null ? null : trailerSupplier.get(); + boolean hasTrailers = trailers != null && trailers.size() > 0; + if (hasContent) + { + DataFrame dataFrame = new DataFrame(stream.getId(), buffer, !hasTrailers); + Callback dataCallback = callback; + if (hasTrailers) + dataCallback = Callback.from(() -> sendTrailers(stream, trailers, callback), callback::failed); + stream.data(dataFrame, dataCallback); + } + else + { + if (hasTrailers) + { + sendTrailers(stream, trailers, callback); + } + else + { + DataFrame dataFrame = new DataFrame(stream.getId(), buffer, true); + stream.data(dataFrame, callback); + } + } } else { - Stream stream = getHttpChannel().getStream(); - Supplier trailerSupplier = exchange.getRequest().getTrailers(); - sendContent(stream, content, trailerSupplier, callback); + if (hasContent) + { + DataFrame dataFrame = new DataFrame(stream.getId(), buffer, false); + stream.data(dataFrame, callback); + } + else + { + // Don't send empty non-last content. + callback.succeeded(); + } } } - private void sendContent(Stream stream, HttpContent content, Supplier trailerSupplier, Callback callback) - { - boolean lastContent = content.isLast(); - HttpFields trailers = null; - boolean endStream = false; - if (lastContent) - { - trailers = trailerSupplier == null ? null : trailerSupplier.get(); - endStream = trailers == null || trailers.size() == 0; - } - DataFrame dataFrame = new DataFrame(stream.getId(), content.getByteBuffer(), endStream); - HttpFields fTrailers = trailers; - stream.data(dataFrame, endStream || !lastContent ? callback : Callback.from(() -> sendTrailers(stream, fTrailers, callback), callback::failed)); - } - private void sendTrailers(Stream stream, HttpFields trailers, Callback callback) { MetaData metaData = new MetaData(HttpVersion.HTTP_2, trailers); diff --git a/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/RequestTrailersTest.java b/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/RequestTrailersTest.java index a94a2f88f61..89f1e3f20ab 100644 --- a/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/RequestTrailersTest.java +++ b/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/RequestTrailersTest.java @@ -25,8 +25,8 @@ import java.util.concurrent.TimeUnit; import org.eclipse.jetty.client.HttpRequest; import org.eclipse.jetty.client.api.ContentResponse; -import org.eclipse.jetty.client.util.DeferredContentProvider; -import org.eclipse.jetty.client.util.StringContentProvider; +import org.eclipse.jetty.client.util.AsyncRequestContent; +import org.eclipse.jetty.client.util.StringRequestContent; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpVersion; @@ -82,7 +82,7 @@ public class RequestTrailersTest extends AbstractTest HttpFields trailers = new HttpFields(); request.trailers(() -> trailers); if (content != null) - request.content(new StringContentProvider(content)); + request.body(new StringRequestContent(content)); ContentResponse response = request.send(); assertEquals(HttpStatus.OK_200, response.getStatus()); @@ -92,7 +92,7 @@ public class RequestTrailersTest extends AbstractTest } @Test - public void testEmptyTrailersWithDeferredContent() throws Exception + public void testEmptyTrailersWithAsyncContent() throws Exception { start(new ServerSessionListener.Adapter() { @@ -121,8 +121,8 @@ public class RequestTrailersTest extends AbstractTest HttpRequest request = (HttpRequest)client.newRequest("localhost", connector.getLocalPort()); HttpFields trailers = new HttpFields(); request.trailers(() -> trailers); - DeferredContentProvider content = new DeferredContentProvider(); - request.content(content); + AsyncRequestContent content = new AsyncRequestContent(); + request.body(content); CountDownLatch latch = new CountDownLatch(1); request.send(result -> @@ -132,16 +132,16 @@ public class RequestTrailersTest extends AbstractTest latch.countDown(); }); - // Send deferred content after a while. + // Send async content after a while. Thread.sleep(1000); - content.offer(ByteBuffer.wrap("deferred_content".getBytes(StandardCharsets.UTF_8))); + content.offer(ByteBuffer.wrap("async_content".getBytes(StandardCharsets.UTF_8))); content.close(); assertTrue(latch.await(5, TimeUnit.SECONDS)); } @Test - public void testEmptyTrailersWithEmptyDeferredContent() throws Exception + public void testEmptyTrailersWithEmptyAsyncContent() throws Exception { start(new ServerSessionListener.Adapter() { @@ -170,8 +170,8 @@ public class RequestTrailersTest extends AbstractTest HttpRequest request = (HttpRequest)client.newRequest("localhost", connector.getLocalPort()); HttpFields trailers = new HttpFields(); request.trailers(() -> trailers); - DeferredContentProvider content = new DeferredContentProvider(); - request.content(content); + AsyncRequestContent content = new AsyncRequestContent(); + request.body(content); CountDownLatch latch = new CountDownLatch(1); request.send(result -> @@ -181,7 +181,7 @@ public class RequestTrailersTest extends AbstractTest latch.countDown(); }); - // Send deferred content after a while. + // Send async content after a while. Thread.sleep(1000); content.close(); diff --git a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdCredentials.java b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdCredentials.java index 20ba2ec6119..2ab55cc4b97 100644 --- a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdCredentials.java +++ b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdCredentials.java @@ -26,7 +26,7 @@ import java.util.concurrent.TimeUnit; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Request; -import org.eclipse.jetty.client.util.FormContentProvider; +import org.eclipse.jetty.client.util.FormRequestContent; import org.eclipse.jetty.util.Fields; import org.eclipse.jetty.util.ajax.JSON; import org.eclipse.jetty.util.log.Log; @@ -176,9 +176,9 @@ public class OpenIdCredentials implements Serializable fields.add("client_secret", configuration.getClientSecret()); fields.add("redirect_uri", redirectUri); fields.add("grant_type", "authorization_code"); - FormContentProvider formContentProvider = new FormContentProvider(fields); + FormRequestContent formContent = new FormRequestContent(fields); Request request = httpClient.POST(configuration.getTokenEndpoint()) - .content(formContentProvider) + .body(formContent) .timeout(10, TimeUnit.SECONDS); ContentResponse response = request.send(); String responseBody = response.getContentAsString(); diff --git a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWithAnnotations.java b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWithAnnotations.java index 7ca4598dcac..dbd722b628c 100644 --- a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWithAnnotations.java +++ b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWithAnnotations.java @@ -25,8 +25,8 @@ import javax.inject.Inject; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Request; -import org.eclipse.jetty.client.util.MultiPartContentProvider; -import org.eclipse.jetty.client.util.StringContentProvider; +import org.eclipse.jetty.client.util.MultiPartRequestContent; +import org.eclipse.jetty.client.util.StringRequestContent; import org.eclipse.jetty.http.HttpStatus; import org.junit.Test; import org.junit.runner.RunWith; @@ -133,10 +133,10 @@ public class TestJettyOSGiBootWithAnnotations assertEquals("Response status code", HttpStatus.OK_200, response.getStatus()); content = response.getContentAsString(); TestOSGiUtil.assertContains("Response contents", content, "

FRAGMENT

"); - MultiPartContentProvider multiPart = new MultiPartContentProvider(); - multiPart.addFieldPart("field", new StringContentProvider("foo"), null); + MultiPartRequestContent multiPart = new MultiPartRequestContent(); + multiPart.addFieldPart("field", new StringRequestContent("foo"), null); response = client.newRequest("http://127.0.0.1:" + port + "/multi").method("POST") - .content(multiPart).send(); + .body(multiPart).send(); assertEquals(HttpStatus.OK_200, response.getStatus()); } finally diff --git a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AsyncMiddleManServlet.java b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AsyncMiddleManServlet.java index 220384292e7..5c38db3b03c 100644 --- a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AsyncMiddleManServlet.java +++ b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AsyncMiddleManServlet.java @@ -26,6 +26,7 @@ import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.Queue; import java.util.concurrent.TimeUnit; import java.util.zip.GZIPOutputStream; @@ -45,7 +46,7 @@ import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.api.Result; -import org.eclipse.jetty.client.util.DeferredContentProvider; +import org.eclipse.jetty.client.util.AsyncRequestContent; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.io.ByteBufferPool; @@ -110,18 +111,20 @@ public class AsyncMiddleManServlet extends AbstractProxyServlet // to allow optimization of the Content-Length header. if (hasContent(clientRequest)) { - DeferredContentProvider provider = newProxyContentProvider(clientRequest, proxyResponse, proxyRequest); - proxyRequest.content(provider); + AsyncRequestContent content = newProxyRequestContent(clientRequest, proxyResponse, proxyRequest); + proxyRequest.body(content); if (expects100Continue(clientRequest)) { + // Must delay the call to request.getInputStream() + // that sends the 100 Continue to the client. proxyRequest.attribute(CLIENT_REQUEST_ATTRIBUTE, clientRequest); proxyRequest.attribute(CONTINUE_ACTION_ATTRIBUTE, (Runnable)() -> { try { ServletInputStream input = clientRequest.getInputStream(); - input.setReadListener(newProxyReadListener(clientRequest, proxyResponse, proxyRequest, provider)); + input.setReadListener(newProxyReadListener(clientRequest, proxyResponse, proxyRequest, content)); } catch (Throwable failure) { @@ -133,7 +136,7 @@ public class AsyncMiddleManServlet extends AbstractProxyServlet else { ServletInputStream input = clientRequest.getInputStream(); - input.setReadListener(newProxyReadListener(clientRequest, proxyResponse, proxyRequest, provider)); + input.setReadListener(newProxyReadListener(clientRequest, proxyResponse, proxyRequest, content)); } } else @@ -142,14 +145,14 @@ public class AsyncMiddleManServlet extends AbstractProxyServlet } } - protected DeferredContentProvider newProxyContentProvider(final HttpServletRequest clientRequest, HttpServletResponse proxyResponse, Request proxyRequest) throws IOException + protected AsyncRequestContent newProxyRequestContent(HttpServletRequest clientRequest, HttpServletResponse proxyResponse, Request proxyRequest) { - return new ProxyDeferredContentProvider(clientRequest); + return new ProxyAsyncRequestContent(clientRequest); } - protected ReadListener newProxyReadListener(HttpServletRequest clientRequest, HttpServletResponse proxyResponse, Request proxyRequest, DeferredContentProvider provider) + protected ReadListener newProxyReadListener(HttpServletRequest clientRequest, HttpServletResponse proxyResponse, Request proxyRequest, AsyncRequestContent content) { - return new ProxyReader(clientRequest, proxyResponse, proxyRequest, provider); + return new ProxyReader(clientRequest, proxyResponse, proxyRequest, content); } protected ProxyWriter newProxyWriteListener(HttpServletRequest clientRequest, Response proxyResponse) @@ -262,17 +265,17 @@ public class AsyncMiddleManServlet extends AbstractProxyServlet private final HttpServletRequest clientRequest; private final HttpServletResponse proxyResponse; private final Request proxyRequest; - private final DeferredContentProvider provider; + private final AsyncRequestContent content; private final int contentLength; private final boolean expects100Continue; private int length; - protected ProxyReader(HttpServletRequest clientRequest, HttpServletResponse proxyResponse, Request proxyRequest, DeferredContentProvider provider) + protected ProxyReader(HttpServletRequest clientRequest, HttpServletResponse proxyResponse, Request proxyRequest, AsyncRequestContent content) { this.clientRequest = clientRequest; this.proxyResponse = proxyResponse; this.proxyRequest = proxyRequest; - this.provider = provider; + this.content = content; this.contentLength = clientRequest.getContentLength(); this.expects100Continue = expects100Continue(clientRequest); } @@ -286,7 +289,7 @@ public class AsyncMiddleManServlet extends AbstractProxyServlet @Override public void onAllDataRead() throws IOException { - if (!provider.isClosed()) + if (!content.isClosed()) { process(BufferUtil.EMPTY_BUFFER, new Callback() { @@ -376,13 +379,13 @@ public class AsyncMiddleManServlet extends AbstractProxyServlet for (ByteBuffer buffer : buffers) { newContentBytes += buffer.remaining(); - provider.offer(buffer, counter); + this.content.offer(buffer, counter); } buffers.clear(); } if (finished) - provider.close(); + this.content.close(); if (_log.isDebugEnabled()) _log.debug("{} upstream content transformation {} -> {} bytes", getRequestId(clientRequest), contentBytes, newContentBytes); @@ -594,10 +597,10 @@ public class AsyncMiddleManServlet extends AbstractProxyServlet protected class ProxyWriter implements WriteListener { - private final Queue chunks = new ArrayDeque<>(); + private final Queue chunks = new ArrayDeque<>(); private final HttpServletRequest clientRequest; private final Response serverResponse; - private DeferredContentProvider.Chunk chunk; + private Chunk chunk; private boolean writePending; protected ProxyWriter(HttpServletRequest clientRequest, Response serverResponse) @@ -610,7 +613,7 @@ public class AsyncMiddleManServlet extends AbstractProxyServlet { if (_log.isDebugEnabled()) _log.debug("{} proxying content to downstream: {} bytes {}", getRequestId(clientRequest), content.remaining(), callback); - return chunks.offer(new DeferredContentProvider.Chunk(content, callback)); + return chunks.offer(new Chunk(content, callback)); } @Override @@ -629,7 +632,7 @@ public class AsyncMiddleManServlet extends AbstractProxyServlet } int length = 0; - DeferredContentProvider.Chunk chunk = null; + Chunk chunk = null; while (output.isReady()) { if (chunk != null) @@ -673,7 +676,7 @@ public class AsyncMiddleManServlet extends AbstractProxyServlet @Override public void onError(Throwable failure) { - DeferredContentProvider.Chunk chunk = this.chunk; + Chunk chunk = this.chunk; if (chunk != null) chunk.callback.failed(failure); else @@ -840,11 +843,11 @@ public class AsyncMiddleManServlet extends AbstractProxyServlet } } - private class ProxyDeferredContentProvider extends DeferredContentProvider + private class ProxyAsyncRequestContent extends AsyncRequestContent { private final HttpServletRequest clientRequest; - public ProxyDeferredContentProvider(HttpServletRequest clientRequest) + private ProxyAsyncRequestContent(HttpServletRequest clientRequest) { this.clientRequest = clientRequest; } @@ -857,4 +860,16 @@ public class AsyncMiddleManServlet extends AbstractProxyServlet return super.offer(buffer, callback); } } + + private static class Chunk + { + private final ByteBuffer buffer; + private final Callback callback; + + private Chunk(ByteBuffer buffer, Callback callback) + { + this.buffer = Objects.requireNonNull(buffer); + this.callback = Objects.requireNonNull(callback); + } + } } diff --git a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AsyncProxyServlet.java b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AsyncProxyServlet.java index 96be479f463..db2e0668f79 100644 --- a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AsyncProxyServlet.java +++ b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AsyncProxyServlet.java @@ -30,10 +30,9 @@ import javax.servlet.WriteListener; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.eclipse.jetty.client.api.ContentProvider; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; -import org.eclipse.jetty.client.util.DeferredContentProvider; +import org.eclipse.jetty.client.util.AsyncRequestContent; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.IteratingCallback; @@ -50,17 +49,16 @@ public class AsyncProxyServlet extends ProxyServlet private static final String WRITE_LISTENER_ATTRIBUTE = AsyncProxyServlet.class.getName() + ".writeListener"; @Override - protected ContentProvider proxyRequestContent(HttpServletRequest request, HttpServletResponse response, Request proxyRequest) throws IOException + protected Request.Content proxyRequestContent(HttpServletRequest request, HttpServletResponse response, Request proxyRequest) throws IOException { - ServletInputStream input = request.getInputStream(); - DeferredContentProvider provider = new DeferredContentProvider(); - input.setReadListener(newReadListener(request, response, proxyRequest, provider)); - return provider; + AsyncRequestContent content = new AsyncRequestContent(); + request.getInputStream().setReadListener(newReadListener(request, response, proxyRequest, content)); + return content; } - protected ReadListener newReadListener(HttpServletRequest request, HttpServletResponse response, Request proxyRequest, DeferredContentProvider provider) + protected ReadListener newReadListener(HttpServletRequest request, HttpServletResponse response, Request proxyRequest, AsyncRequestContent content) { - return new StreamReader(request, response, proxyRequest, provider); + return new StreamReader(request, response, proxyRequest, content); } @Override @@ -131,28 +129,28 @@ public class AsyncProxyServlet extends ProxyServlet private final HttpServletRequest request; private final HttpServletResponse response; private final Request proxyRequest; - private final DeferredContentProvider provider; + private final AsyncRequestContent content; - protected StreamReader(HttpServletRequest request, HttpServletResponse response, Request proxyRequest, DeferredContentProvider provider) + protected StreamReader(HttpServletRequest request, HttpServletResponse response, Request proxyRequest, AsyncRequestContent content) { this.request = request; this.response = response; this.proxyRequest = proxyRequest; - this.provider = provider; + this.content = content; } @Override - public void onDataAvailable() throws IOException + public void onDataAvailable() { iterate(); } @Override - public void onAllDataRead() throws IOException + public void onAllDataRead() { if (_log.isDebugEnabled()) _log.debug("{} proxying content to upstream completed", getRequestId(request)); - provider.close(); + content.close(); } @Override @@ -176,7 +174,7 @@ public class AsyncProxyServlet extends ProxyServlet { if (_log.isDebugEnabled()) _log.debug("{} proxying content to upstream: {} bytes", requestId, read); - onRequestContent(request, proxyRequest, provider, buffer, 0, read, this); + onRequestContent(request, proxyRequest, content, buffer, 0, read, this); return Action.SCHEDULED; } else if (read < 0) @@ -192,9 +190,9 @@ public class AsyncProxyServlet extends ProxyServlet return Action.IDLE; } - protected void onRequestContent(HttpServletRequest request, Request proxyRequest, DeferredContentProvider provider, byte[] buffer, int offset, int length, Callback callback) + protected void onRequestContent(HttpServletRequest request, Request proxyRequest, AsyncRequestContent content, byte[] buffer, int offset, int length, Callback callback) { - provider.offer(ByteBuffer.wrap(buffer, offset, length), callback); + content.offer(ByteBuffer.wrap(buffer, offset, length), callback); } @Override diff --git a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ProxyServlet.java b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ProxyServlet.java index da638db8941..073e99181b8 100644 --- a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ProxyServlet.java +++ b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ProxyServlet.java @@ -18,11 +18,9 @@ package org.eclipse.jetty.proxy; -import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; -import java.util.Iterator; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import javax.servlet.AsyncContext; @@ -31,16 +29,13 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.eclipse.jetty.client.AsyncContentProvider; -import org.eclipse.jetty.client.api.ContentProvider; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.api.Result; -import org.eclipse.jetty.client.util.DeferredContentProvider; -import org.eclipse.jetty.client.util.InputStreamContentProvider; +import org.eclipse.jetty.client.util.AsyncRequestContent; +import org.eclipse.jetty.client.util.InputStreamRequestContent; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.util.Callback; -import org.eclipse.jetty.util.IteratingCallback; /** *

Servlet 3.0 asynchronous proxy servlet.

@@ -93,15 +88,17 @@ public class ProxyServlet extends AbstractProxyServlet { if (expects100Continue(request)) { - DeferredContentProvider deferred = new DeferredContentProvider(); - proxyRequest.content(deferred); + // Must delay the call to request.getInputStream() + // that sends the 100 Continue to the client. + AsyncRequestContent delegate = new AsyncRequestContent(); + proxyRequest.body(delegate); proxyRequest.attribute(CLIENT_REQUEST_ATTRIBUTE, request); proxyRequest.attribute(CONTINUE_ACTION_ATTRIBUTE, (Runnable)() -> { try { - ContentProvider provider = proxyRequestContent(request, response, proxyRequest); - new DelegatingContentProvider(request, proxyRequest, response, provider, deferred).iterate(); + Request.Content content = proxyRequestContent(request, response, proxyRequest); + new DelegatingRequestContent(request, proxyRequest, response, content, delegate); } catch (Throwable failure) { @@ -111,16 +108,25 @@ public class ProxyServlet extends AbstractProxyServlet } else { - proxyRequest.content(proxyRequestContent(request, response, proxyRequest)); + proxyRequest.body(proxyRequestContent(request, response, proxyRequest)); } } sendProxyRequest(request, response, proxyRequest); } - protected ContentProvider proxyRequestContent(HttpServletRequest request, HttpServletResponse response, Request proxyRequest) throws IOException + /** + * Wraps the client-to-proxy request content in a {@code Request.Content} for the proxy-to-server request. + * + * @param request the client-to-proxy request + * @param response the proxy-to-client response + * @param proxyRequest the proxy-to-server request + * @return a proxy-to-server request content + * @throws IOException if the proxy-to-server request content cannot be created + */ + protected Request.Content proxyRequestContent(HttpServletRequest request, HttpServletResponse response, Request proxyRequest) throws IOException { - return new ProxyInputStreamContentProvider(request, response, proxyRequest, request.getInputStream()); + return new ProxyInputStreamRequestContent(request, response, proxyRequest, request.getInputStream()); } @Override @@ -240,13 +246,13 @@ public class ProxyServlet extends AbstractProxyServlet } } - protected class ProxyInputStreamContentProvider extends InputStreamContentProvider + protected class ProxyInputStreamRequestContent extends InputStreamRequestContent { private final HttpServletResponse response; private final Request proxyRequest; private final HttpServletRequest request; - protected ProxyInputStreamContentProvider(HttpServletRequest request, HttpServletResponse response, Request proxyRequest, InputStream input) + protected ProxyInputStreamRequestContent(HttpServletRequest request, HttpServletResponse response, Request proxyRequest, InputStream input) { super(input); this.request = request; @@ -265,11 +271,6 @@ public class ProxyServlet extends AbstractProxyServlet { if (_log.isDebugEnabled()) _log.debug("{} proxying content to upstream: {} bytes", getRequestId(request), length); - return onRequestContent(request, proxyRequest, buffer, offset, length); - } - - protected ByteBuffer onRequestContent(HttpServletRequest request, Request proxyRequest, byte[] buffer, int offset, int length) - { return super.onRead(buffer, offset, length); } @@ -280,80 +281,57 @@ public class ProxyServlet extends AbstractProxyServlet } } - private class DelegatingContentProvider extends IteratingCallback implements AsyncContentProvider.Listener + private class DelegatingRequestContent implements Request.Content.Consumer { private final HttpServletRequest clientRequest; private final Request proxyRequest; private final HttpServletResponse proxyResponse; - private final Iterator iterator; - private final DeferredContentProvider deferred; + private final AsyncRequestContent delegate; + private final Request.Content.Subscription subscription; - private DelegatingContentProvider(HttpServletRequest clientRequest, Request proxyRequest, HttpServletResponse proxyResponse, ContentProvider provider, DeferredContentProvider deferred) + private DelegatingRequestContent(HttpServletRequest clientRequest, Request proxyRequest, HttpServletResponse proxyResponse, Request.Content content, AsyncRequestContent delegate) { this.clientRequest = clientRequest; this.proxyRequest = proxyRequest; this.proxyResponse = proxyResponse; - this.iterator = provider.iterator(); - this.deferred = deferred; - if (provider instanceof AsyncContentProvider) - ((AsyncContentProvider)provider).setListener(this); + this.delegate = delegate; + this.subscription = content.subscribe(this, true); + this.subscription.demand(); } @Override - protected Action process() throws Exception + public void onContent(ByteBuffer buffer, boolean last, Callback callback) { - if (!iterator.hasNext()) - return Action.SUCCEEDED; - - ByteBuffer buffer = iterator.next(); - if (buffer == null) - return Action.IDLE; - - deferred.offer(buffer, this); - return Action.SCHEDULED; - } - - @Override - public void succeeded() - { - if (iterator instanceof Callback) - ((Callback)iterator).succeeded(); - super.succeeded(); - } - - @Override - protected void onCompleteSuccess() - { - try + Callback wrapped = Callback.from(() -> succeeded(callback, last), failure -> failed(callback, failure)); + if (buffer.hasRemaining()) { - if (iterator instanceof Closeable) - ((Closeable)iterator).close(); - deferred.close(); + delegate.offer(buffer, wrapped); } - catch (Throwable x) + else { - _log.ignore(x); + wrapped.succeeded(); } + if (last) + delegate.close(); + } + + private void succeeded(Callback callback, boolean last) + { + callback.succeeded(); + if (!last) + subscription.demand(); + } + + private void failed(Callback callback, Throwable failure) + { + callback.failed(failure); + onFailure(failure); } @Override - protected void onCompleteFailure(Throwable failure) + public void onFailure(Throwable failure) { - if (iterator instanceof Callback) - ((Callback)iterator).failed(failure); onClientRequestFailure(clientRequest, proxyRequest, proxyResponse, failure); } - - @Override - public InvocationType getInvocationType() - { - return InvocationType.NON_BLOCKING; - } - - @Override - public void onContent() - { - iterate(); - } } } diff --git a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/AsyncMiddleManServletTest.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/AsyncMiddleManServletTest.java index 6937aa50b5a..88a267d7f31 100644 --- a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/AsyncMiddleManServletTest.java +++ b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/AsyncMiddleManServletTest.java @@ -50,12 +50,11 @@ import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.HttpProxy; -import org.eclipse.jetty.client.api.ContentProvider; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; -import org.eclipse.jetty.client.util.BytesContentProvider; -import org.eclipse.jetty.client.util.DeferredContentProvider; +import org.eclipse.jetty.client.util.AsyncRequestContent; +import org.eclipse.jetty.client.util.BytesRequestContent; import org.eclipse.jetty.client.util.FutureResponseListener; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeaderValue; @@ -224,11 +223,11 @@ public class AsyncMiddleManServletTest startClient(); byte[] gzipBytes = gzip(bytes); - ContentProvider gzipContent = new BytesContentProvider(gzipBytes); + Request.Content gzipContent = new BytesRequestContent(gzipBytes); ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort()) .header(HttpHeader.CONTENT_ENCODING, "gzip") - .content(gzipContent) + .body(gzipContent) .timeout(5, TimeUnit.SECONDS) .send(); @@ -303,7 +302,7 @@ public class AsyncMiddleManServletTest ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort()) .header(HttpHeader.CONTENT_ENCODING, "gzip") - .content(new BytesContentProvider(gzip(bytes))) + .body(new BytesRequestContent(gzip(bytes))) .timeout(5, TimeUnit.SECONDS) .send(); @@ -391,11 +390,11 @@ public class AsyncMiddleManServletTest }); startClient(); - DeferredContentProvider content = new DeferredContentProvider(); + AsyncRequestContent content = new AsyncRequestContent(); Request request = client.newRequest("localhost", serverConnector.getLocalPort()); FutureResponseListener listener = new FutureResponseListener(request); request.header(HttpHeader.CONTENT_ENCODING, "gzip") - .content(content) + .body(content) .send(listener); byte[] bytes = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".getBytes(StandardCharsets.UTF_8); content.offer(ByteBuffer.wrap(gzip(bytes))); @@ -440,7 +439,7 @@ public class AsyncMiddleManServletTest byte[] bytes = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".getBytes(StandardCharsets.UTF_8); ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort()) .header(HttpHeader.CONTENT_ENCODING, "gzip") - .content(new BytesContentProvider(gzip(bytes))) + .body(new BytesRequestContent(gzip(bytes))) .timeout(5, TimeUnit.SECONDS) .send(); @@ -484,7 +483,7 @@ public class AsyncMiddleManServletTest ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort()) .header(HttpHeader.CONTENT_ENCODING, "gzip") - .content(new BytesContentProvider(gzip(bytes))) + .body(new BytesRequestContent(gzip(bytes))) .timeout(5, TimeUnit.SECONDS) .send(); @@ -511,7 +510,7 @@ public class AsyncMiddleManServletTest byte[] bytes = new byte[1024]; ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort()) - .content(new BytesContentProvider(bytes)) + .body(new BytesRequestContent(bytes)) .timeout(5, TimeUnit.SECONDS) .send(); @@ -521,7 +520,7 @@ public class AsyncMiddleManServletTest @Test public void testUpstreamTransformationThrowsAfterCommittingProxyRequest() throws Exception { - try (StacklessLogging scope = new StacklessLogging(HttpChannel.class)) + try (StacklessLogging ignored = new StacklessLogging(HttpChannel.class)) { startServer(new EchoHttpServlet()); startProxy(new AsyncMiddleManServlet() @@ -546,10 +545,10 @@ public class AsyncMiddleManServletTest }); startClient(); - final CountDownLatch latch = new CountDownLatch(1); - DeferredContentProvider content = new DeferredContentProvider(); + CountDownLatch latch = new CountDownLatch(1); + AsyncRequestContent content = new AsyncRequestContent(); client.newRequest("localhost", serverConnector.getLocalPort()) - .content(content) + .body(content) .send(result -> { if (result.isSucceeded() && result.getResponse().getStatus() == 502) @@ -756,10 +755,10 @@ public class AsyncMiddleManServletTest }); startClient(); - final CountDownLatch latch = new CountDownLatch(1); - DeferredContentProvider content = new DeferredContentProvider(); + CountDownLatch latch = new CountDownLatch(1); + AsyncRequestContent content = new AsyncRequestContent(); client.newRequest("localhost", serverConnector.getLocalPort()) - .content(content) + .body(content) .send(result -> { System.err.println(result); @@ -777,7 +776,7 @@ public class AsyncMiddleManServletTest @Test public void testClientRequestReadFailsOnSecondRead() throws Exception { - try (StacklessLogging scope = new StacklessLogging(HttpChannel.class)) + try (StacklessLogging ignored = new StacklessLogging(HttpChannel.class)) { startServer(new EchoHttpServlet()); startProxy(new AsyncMiddleManServlet() @@ -795,10 +794,10 @@ public class AsyncMiddleManServletTest }); startClient(); - final CountDownLatch latch = new CountDownLatch(1); - DeferredContentProvider content = new DeferredContentProvider(); + CountDownLatch latch = new CountDownLatch(1); + AsyncRequestContent content = new AsyncRequestContent(); client.newRequest("localhost", serverConnector.getLocalPort()) - .content(content) + .body(content) .send(result -> { if (result.getResponse().getStatus() == 502) @@ -1114,7 +1113,7 @@ public class AsyncMiddleManServletTest // Send only part of the content; the proxy will idle timeout. final byte[] data = new byte[]{'c', 'a', 'f', 'e'}; ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort()) - .content(new BytesContentProvider(data) + .body(new BytesRequestContent(data) { @Override public long getLength() @@ -1231,7 +1230,7 @@ public class AsyncMiddleManServletTest } @Override - public boolean transform(Source source, Sink sink) throws IOException + public boolean transform(Source source, Sink sink) { if (readSource) { @@ -1320,10 +1319,10 @@ public class AsyncMiddleManServletTest }); startClient(); - DeferredContentProvider content = new DeferredContentProvider(); + AsyncRequestContent content = new AsyncRequestContent(); Request request = client.newRequest("localhost", serverConnector.getLocalPort()) .timeout(5, TimeUnit.SECONDS) - .content(content); + .body(content); FutureResponseListener listener = new FutureResponseListener(request); request.send(listener); @@ -1368,10 +1367,10 @@ public class AsyncMiddleManServletTest }); startClient(); - DeferredContentProvider content = new DeferredContentProvider(); + AsyncRequestContent content = new AsyncRequestContent(); Request request = client.newRequest("localhost", serverConnector.getLocalPort()) .timeout(5, TimeUnit.SECONDS) - .content(content); + .body(content); FutureResponseListener listener = new FutureResponseListener(request); request.send(listener); @@ -1439,10 +1438,10 @@ public class AsyncMiddleManServletTest }); startClient(); - DeferredContentProvider content = new DeferredContentProvider(); + AsyncRequestContent content = new AsyncRequestContent(); Request request = client.newRequest("localhost", serverConnector.getLocalPort()) .timeout(5, TimeUnit.SECONDS) - .content(content); + .body(content); FutureResponseListener listener = new FutureResponseListener(request); request.send(listener); @@ -1607,9 +1606,9 @@ public class AsyncMiddleManServletTest private static class Client extends HrefTransformer { @Override - protected String transform(String value) throws IOException + protected String transform(String value) { - String result = PREFIX + URLEncoder.encode(value, "UTF-8"); + String result = PREFIX + URLEncoder.encode(value, StandardCharsets.UTF_8); LOG.debug("{} -> {}", value, result); return result; } @@ -1618,9 +1617,9 @@ public class AsyncMiddleManServletTest private static class Server extends HrefTransformer { @Override - protected String transform(String value) throws IOException + protected String transform(String value) { - String result = URLDecoder.decode(value.substring(PREFIX.length()), "UTF-8"); + String result = URLDecoder.decode(value.substring(PREFIX.length()), StandardCharsets.UTF_8); LOG.debug("{} <- {}", value, result); return result; } diff --git a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ForwardProxyTLSServerTest.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ForwardProxyTLSServerTest.java index f5f912de160..053de8b782a 100644 --- a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ForwardProxyTLSServerTest.java +++ b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ForwardProxyTLSServerTest.java @@ -48,7 +48,7 @@ import org.eclipse.jetty.client.api.Destination; import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP; import org.eclipse.jetty.client.util.BasicAuthentication; import org.eclipse.jetty.client.util.FutureResponseListener; -import org.eclipse.jetty.client.util.StringContentProvider; +import org.eclipse.jetty.client.util.StringRequestContent; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpScheme; @@ -257,7 +257,7 @@ public class ForwardProxyTLSServerTest .path("/echo") .header(HttpHeader.CONTENT_TYPE, MimeTypes.Type.FORM_ENCODED.asString()) .header(HttpHeader.CONTENT_LENGTH, String.valueOf(content.length())) - .content(new StringContentProvider(content)) + .body(new StringRequestContent(content)) .timeout(5, TimeUnit.SECONDS) .send(); @@ -320,7 +320,7 @@ public class ForwardProxyTLSServerTest .path("/echo") .header(HttpHeader.CONTENT_TYPE, MimeTypes.Type.FORM_ENCODED.asString()) .header(HttpHeader.CONTENT_LENGTH, String.valueOf(body2.length())) - .content(new StringContentProvider(body2)); + .body(new StringRequestContent(body2)); // Make sure the second connection can send the exchange via the tunnel FutureResponseListener listener2 = new FutureResponseListener(request2); diff --git a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletFailureTest.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletFailureTest.java index ac2d93a5f37..5aa027ea0a7 100644 --- a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletFailureTest.java +++ b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletFailureTest.java @@ -23,6 +23,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -35,12 +36,11 @@ import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.HttpProxy; -import org.eclipse.jetty.client.api.ContentProvider; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; -import org.eclipse.jetty.client.util.BytesContentProvider; -import org.eclipse.jetty.client.util.DeferredContentProvider; +import org.eclipse.jetty.client.util.AsyncRequestContent; +import org.eclipse.jetty.client.util.BytesRequestContent; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.tools.HttpTester; import org.eclipse.jetty.server.HttpChannel; @@ -173,7 +173,7 @@ public class ProxyServletFailureTest "Host: " + serverHostPort + "\r\n"; // Don't sent the \r\n that would signal the end of the headers. OutputStream output = socket.getOutputStream(); - output.write(request.getBytes("UTF-8")); + output.write(request.getBytes(StandardCharsets.UTF_8)); output.flush(); // Wait for idle timeout to fire. @@ -203,7 +203,7 @@ public class ProxyServletFailureTest "Content-Length: 1\r\n" + "\r\n"; OutputStream output = socket.getOutputStream(); - output.write(request.getBytes("UTF-8")); + output.write(request.getBytes(StandardCharsets.UTF_8)); output.flush(); // Do not send the promised content, wait to idle timeout. @@ -239,7 +239,7 @@ public class ProxyServletFailureTest "\r\n" + "Z"; OutputStream output = socket.getOutputStream(); - output.write(request.getBytes("UTF-8")); + output.write(request.getBytes(StandardCharsets.UTF_8)); output.flush(); // Do not send all the promised content, wait to idle timeout. @@ -261,7 +261,7 @@ public class ProxyServletFailureTest { final byte[] content = new byte[]{'C', '0', 'F', 'F', 'E', 'E'}; int expected; - ProxyServlet proxyServlet = null; + ProxyServlet proxyServlet; if (proxyServletClass.isAssignableFrom(AsyncProxyServlet.class)) { @@ -270,9 +270,9 @@ public class ProxyServletFailureTest proxyServlet = new AsyncProxyServlet() { @Override - protected ContentProvider proxyRequestContent(HttpServletRequest request, HttpServletResponse response, Request proxyRequest) throws IOException + protected Request.Content proxyRequestContent(HttpServletRequest request, HttpServletResponse response, Request proxyRequest) throws IOException { - DeferredContentProvider provider = new DeferredContentProvider() + AsyncRequestContent requestContent = new AsyncRequestContent() { @Override public boolean offer(ByteBuffer buffer, Callback callback) @@ -282,8 +282,8 @@ public class ProxyServletFailureTest return super.offer(buffer.slice(), callback); } }; - request.getInputStream().setReadListener(newReadListener(request, response, proxyRequest, provider)); - return provider; + request.getInputStream().setReadListener(newReadListener(request, response, proxyRequest, requestContent)); + return requestContent; } }; } @@ -293,9 +293,9 @@ public class ProxyServletFailureTest proxyServlet = new ProxyServlet() { @Override - protected ContentProvider proxyRequestContent(HttpServletRequest request, HttpServletResponse response, Request proxyRequest) + protected Request.Content proxyRequestContent(HttpServletRequest request, HttpServletResponse response, Request proxyRequest) { - return new BytesContentProvider(content) + return new BytesRequestContent(content) { @Override public long getLength() @@ -316,7 +316,7 @@ public class ProxyServletFailureTest try (StacklessLogging ignore = new StacklessLogging(HttpChannel.class)) { ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort()) - .content(new BytesContentProvider(content)) + .body(new BytesRequestContent(content)) .send(); assertThat(response.toString(), response.getStatus(), is(expected)); diff --git a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletLoadTest.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletLoadTest.java index ec13fbdc1c8..3a3192718b4 100644 --- a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletLoadTest.java +++ b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletLoadTest.java @@ -19,13 +19,11 @@ package org.eclipse.jetty.proxy; import java.io.IOException; -import java.util.Arrays; import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Stream; -import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -33,7 +31,7 @@ import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.HttpProxy; import org.eclipse.jetty.client.api.ContentResponse; -import org.eclipse.jetty.client.util.BytesContentProvider; +import org.eclipse.jetty.client.util.BytesRequestContent; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConnectionFactory; @@ -57,11 +55,11 @@ public class ProxyServletLoadTest { public static Stream data() { - return Arrays.asList( + return Stream.of( ProxyServlet.class, AsyncProxyServlet.class, AsyncMiddleManServlet.class) - .stream().map(Arguments::of); + .map(Arguments::of); } private static final Logger LOG = Log.getLogger(ProxyServletLoadTest.class); @@ -136,7 +134,7 @@ public class ProxyServletLoadTest startServer(proxyServletClass, new HttpServlet() { @Override - protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { if (req.getHeader("Via") != null) resp.addHeader(PROXIED_HEADER, "true"); @@ -205,7 +203,7 @@ public class ProxyServletLoadTest byte[] content = new byte[1024]; new Random().nextBytes(content); - ContentResponse response = client.newRequest(host, port).method(HttpMethod.POST).content(new BytesContentProvider(content)) + ContentResponse response = client.newRequest(host, port).method(HttpMethod.POST).body(new BytesRequestContent(content)) .timeout(5, TimeUnit.SECONDS).send(); if (response.getStatus() != 200) diff --git a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java index 7b15315d249..af55f624436 100644 --- a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java +++ b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java @@ -27,6 +27,7 @@ import java.io.PrintWriter; import java.net.ConnectException; import java.net.HttpCookie; import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; @@ -69,9 +70,9 @@ import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.api.Result; +import org.eclipse.jetty.client.util.AsyncRequestContent; import org.eclipse.jetty.client.util.BufferingResponseListener; -import org.eclipse.jetty.client.util.BytesContentProvider; -import org.eclipse.jetty.client.util.DeferredContentProvider; +import org.eclipse.jetty.client.util.BytesRequestContent; import org.eclipse.jetty.client.util.InputStreamResponseListener; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeaderValue; @@ -305,7 +306,7 @@ public class ProxyServletTest new Random().nextBytes(content); ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort()) .method(HttpMethod.POST) - .content(new BytesContentProvider(content)) + .body(new BytesRequestContent(content)) .timeout(5, TimeUnit.SECONDS) .send(); @@ -344,7 +345,7 @@ public class ProxyServletTest byte[] content = new byte[128 * 1024]; ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort()) .method(HttpMethod.POST) - .content(new BytesContentProvider(content)) + .body(new BytesRequestContent(content)) .timeout(5, TimeUnit.SECONDS) .send(); @@ -387,7 +388,7 @@ public class ProxyServletTest ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort()) .method(HttpMethod.POST) - .content(new BytesContentProvider(content)) + .body(new BytesRequestContent(content)) .timeout(5, TimeUnit.SECONDS) .send(); @@ -933,7 +934,7 @@ public class ProxyServletTest @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { - byte[] message = "tooshort".getBytes("ascii"); + byte[] message = "tooshort".getBytes(StandardCharsets.US_ASCII); resp.setContentType("text/plain;charset=ascii"); resp.setHeader("Content-Length", Long.toString(message.length + 1)); resp.getOutputStream().write(message); @@ -1283,7 +1284,7 @@ public class ProxyServletTest CountDownLatch clientLatch = new CountDownLatch(1); client.newRequest("localhost", serverConnector.getLocalPort()) .header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString()) - .content(new BytesContentProvider(content)) + .body(new BytesRequestContent(content)) .onRequestContent((request, buffer) -> contentLatch.countDown()) .send(new BufferingResponseListener() { @@ -1335,12 +1336,12 @@ public class ProxyServletTest byte[] content = new byte[1024]; new Random().nextBytes(content); int chunk1 = content.length / 2; - DeferredContentProvider contentProvider = new DeferredContentProvider(); - contentProvider.offer(ByteBuffer.wrap(content, 0, chunk1)); + AsyncRequestContent requestContent = new AsyncRequestContent(); + requestContent.offer(ByteBuffer.wrap(content, 0, chunk1)); CountDownLatch clientLatch = new CountDownLatch(1); client.newRequest("localhost", serverConnector.getLocalPort()) .header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString()) - .content(contentProvider) + .body(requestContent) .send(new BufferingResponseListener() { @Override @@ -1359,8 +1360,8 @@ public class ProxyServletTest // Wait a while and then offer more content. Thread.sleep(1000); - contentProvider.offer(ByteBuffer.wrap(content, chunk1, content.length - chunk1)); - contentProvider.close(); + requestContent.offer(ByteBuffer.wrap(content, chunk1, content.length - chunk1)); + requestContent.close(); assertTrue(clientLatch.await(5, TimeUnit.SECONDS)); } @@ -1395,12 +1396,12 @@ public class ProxyServletTest byte[] content = new byte[1024]; new Random().nextBytes(content); int chunk1 = content.length / 2; - DeferredContentProvider contentProvider = new DeferredContentProvider(); - contentProvider.offer(ByteBuffer.wrap(content, 0, chunk1)); + AsyncRequestContent requestContent = new AsyncRequestContent(); + requestContent.offer(ByteBuffer.wrap(content, 0, chunk1)); CountDownLatch clientLatch = new CountDownLatch(1); client.newRequest("localhost", serverConnector.getLocalPort()) .header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString()) - .content(contentProvider) + .body(requestContent) .send(result -> { if (result.isFailed()) @@ -1448,7 +1449,7 @@ public class ProxyServletTest CountDownLatch clientLatch = new CountDownLatch(1); client.newRequest("localhost", serverConnector.getLocalPort()) .header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString()) - .content(new BytesContentProvider(content)) + .body(new BytesRequestContent(content)) .onRequestContent((request, buffer) -> contentLatch.countDown()) .send(result -> { diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/MultiPartFormInputStreamTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/MultiPartFormInputStreamTest.java index 524885ee5b4..cf2d11bd61d 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/MultiPartFormInputStreamTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/MultiPartFormInputStreamTest.java @@ -503,7 +503,7 @@ public class MultiPartFormInputStreamTest mpis.deleteParts(); // this should not be an NPE } - + @Test public void testAsyncCleanUp() throws Exception { diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/FormTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/FormTest.java index d1d1af530c9..f6789252dbc 100644 --- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/FormTest.java +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/FormTest.java @@ -29,8 +29,8 @@ import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.api.ContentResponse; -import org.eclipse.jetty.client.util.DeferredContentProvider; -import org.eclipse.jetty.client.util.FormContentProvider; +import org.eclipse.jetty.client.util.AsyncRequestContent; +import org.eclipse.jetty.client.util.FormRequestContent; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpStatus; @@ -131,13 +131,13 @@ public class FormTest length = length + 1; byte[] value = new byte[length]; Arrays.fill(value, (byte)'x'); - DeferredContentProvider content = new DeferredContentProvider(ByteBuffer.wrap(key), ByteBuffer.wrap(value)); + AsyncRequestContent content = new AsyncRequestContent(ByteBuffer.wrap(key), ByteBuffer.wrap(value)); ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) .method(HttpMethod.POST) .path(contextPath + servletPath) .header(HttpHeader.CONTENT_TYPE, MimeTypes.Type.FORM_ENCODED.asString()) - .content(content) + .body(content) .onRequestBegin(request -> { if (withContentLength) @@ -192,7 +192,7 @@ public class FormTest ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) .method(HttpMethod.POST) .path(contextPath + servletPath) - .content(new FormContentProvider(formParams)) + .body(new FormRequestContent(formParams)) .send(); int expected = (maxFormKeys != null && maxFormKeys < 0) diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/MultiPartServletTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/MultiPartServletTest.java index 91d459d56d6..15867d9ada6 100644 --- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/MultiPartServletTest.java +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/MultiPartServletTest.java @@ -21,6 +21,7 @@ package org.eclipse.jetty.servlet; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Arrays; import javax.servlet.MultipartConfigElement; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; @@ -30,8 +31,8 @@ import javax.servlet.http.Part; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.api.ContentResponse; -import org.eclipse.jetty.client.util.BytesContentProvider; -import org.eclipse.jetty.client.util.MultiPartContentProvider; +import org.eclipse.jetty.client.util.BytesRequestContent; +import org.eclipse.jetty.client.util.MultiPartRequestContent; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpScheme; import org.eclipse.jetty.http.MimeTypes; @@ -40,8 +41,6 @@ import org.eclipse.jetty.server.MultiPartFormInputStream; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.util.IO; -import org.eclipse.jetty.util.log.Log; -import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.StacklessLogging; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -54,8 +53,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; public class MultiPartServletTest { - private static final Logger LOG = Log.getLogger(MultiPartServletTest.class); - private Server server; private ServerConnector connector; private HttpClient client; @@ -123,22 +120,19 @@ public class MultiPartServletTest public void testTempFilesDeletedOnError() throws Exception { byte[] byteArray = new byte[LARGE_MESSAGE_SIZE]; - for (int i = 0; i < byteArray.length; i++) - { - byteArray[i] = 1; - } - BytesContentProvider contentProvider = new BytesContentProvider(byteArray); + Arrays.fill(byteArray, (byte)1); + BytesRequestContent content = new BytesRequestContent(byteArray); - MultiPartContentProvider multiPart = new MultiPartContentProvider(); - multiPart.addFieldPart("largePart", contentProvider, null); + MultiPartRequestContent multiPart = new MultiPartRequestContent(); + multiPart.addFieldPart("largePart", content, null); multiPart.close(); - try (StacklessLogging stacklessLogging = new StacklessLogging(HttpChannel.class, MultiPartFormInputStream.class)) + try (StacklessLogging ignored = new StacklessLogging(HttpChannel.class, MultiPartFormInputStream.class)) { ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) .scheme(HttpScheme.HTTP.asString()) .method(HttpMethod.POST) - .content(multiPart) + .body(multiPart) .send(); assertEquals(500, response.getStatus()); diff --git a/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/HugeResourceTest.java b/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/HugeResourceTest.java index 78c3c85f964..8d4f46a57ea 100644 --- a/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/HugeResourceTest.java +++ b/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/HugeResourceTest.java @@ -44,8 +44,8 @@ import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.util.InputStreamResponseListener; -import org.eclipse.jetty.client.util.MultiPartContentProvider; -import org.eclipse.jetty.client.util.PathContentProvider; +import org.eclipse.jetty.client.util.MultiPartRequestContent; +import org.eclipse.jetty.client.util.PathRequestContent; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.server.HttpConfiguration; @@ -101,7 +101,7 @@ public class HugeResourceTest String.format("FileStore %s of %s needs at least 30GB of free space for this test (only had %,.2fGB)", baseFileStore, staticBase, (double)(baseFileStore.getUnallocatedSpace() / GB))); - makeStaticFile(staticBase.resolve("test-1g.dat"), 1 * GB); + makeStaticFile(staticBase.resolve("test-1g.dat"), GB); makeStaticFile(staticBase.resolve("test-4g.dat"), 4 * GB); makeStaticFile(staticBase.resolve("test-10g.dat"), 10 * GB); @@ -116,7 +116,7 @@ public class HugeResourceTest { ArrayList ret = new ArrayList<>(); - ret.add(Arguments.of("test-1g.dat", 1 * GB)); + ret.add(Arguments.of("test-1g.dat", GB)); ret.add(Arguments.of("test-4g.dat", 4 * GB)); ret.add(Arguments.of("test-10g.dat", 10 * GB)); @@ -133,7 +133,7 @@ public class HugeResourceTest private static void makeStaticFile(Path staticFile, long size) throws IOException { - byte[] buf = new byte[(int)(1 * MB)]; + byte[] buf = new byte[(int)MB]; Arrays.fill(buf, (byte)'x'); ByteBuffer src = ByteBuffer.wrap(buf); @@ -248,9 +248,9 @@ public class HugeResourceTest { Path inputFile = staticBase.resolve(filename); - PathContentProvider pathContentProvider = new PathContentProvider(inputFile); + PathRequestContent content = new PathRequestContent(inputFile); URI destUri = server.getURI().resolve("/post"); - Request request = client.newRequest(destUri).method(HttpMethod.POST).content(pathContentProvider); + Request request = client.newRequest(destUri).method(HttpMethod.POST).body(content); ContentResponse response = request.send(); assertThat("HTTP Response Code", response.getStatus(), is(200)); // dumpResponse(response); @@ -263,14 +263,14 @@ public class HugeResourceTest @MethodSource("staticFiles") public void testUploadMultipart(String filename, long expectedSize) throws Exception { - MultiPartContentProvider multipart = new MultiPartContentProvider(); + MultiPartRequestContent multipart = new MultiPartRequestContent(); Path inputFile = staticBase.resolve(filename); String name = String.format("file-%d", expectedSize); - multipart.addFilePart(name, filename, new PathContentProvider(inputFile), null); + multipart.addFilePart(name, filename, new PathRequestContent(inputFile), null); URI destUri = server.getURI().resolve("/multipart"); client.setIdleTimeout(90_000); - Request request = client.newRequest(destUri).method(HttpMethod.POST).content(multipart); + Request request = client.newRequest(destUri).method(HttpMethod.POST).body(multipart); ContentResponse response = request.send(); assertThat("HTTP Response Code", response.getStatus(), is(200)); // dumpResponse(response); diff --git a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DemoBaseTests.java b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DemoBaseTests.java index 9ac95f1c9dd..cd09de2517a 100644 --- a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DemoBaseTests.java +++ b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DemoBaseTests.java @@ -23,7 +23,7 @@ import java.nio.file.Paths; import java.util.concurrent.TimeUnit; import org.eclipse.jetty.client.api.ContentResponse; -import org.eclipse.jetty.client.util.FormContentProvider; +import org.eclipse.jetty.client.util.FormRequestContent; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.util.Fields; import org.junit.jupiter.api.Test; @@ -215,7 +215,7 @@ public class DemoBaseTests extends AbstractDistributionTest Fields form = new Fields(); form.add("Action", "New Session"); response = client.POST("http://localhost:" + httpPort + "/test/session/") - .content(new FormContentProvider(form)) + .body(new FormRequestContent(form)) .send(); assertEquals(HttpStatus.OK_200, response.getStatus()); String content = response.getContentAsString(); @@ -231,7 +231,7 @@ public class DemoBaseTests extends AbstractDistributionTest form.add("Name", "Zed"); form.add("Value", "[alpha]"); response = client.POST(location) - .content(new FormContentProvider(form)) + .body(new FormRequestContent(form)) .send(); assertEquals(HttpStatus.OK_200, response.getStatus()); content = response.getContentAsString(); diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/AsyncIOServletTest.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/AsyncIOServletTest.java index 87bfdb5b1b1..07a295b06f4 100644 --- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/AsyncIOServletTest.java +++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/AsyncIOServletTest.java @@ -51,10 +51,10 @@ import org.eclipse.jetty.client.api.Destination; import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.api.Result; import org.eclipse.jetty.client.http.HttpConnectionOverHTTP; +import org.eclipse.jetty.client.util.AsyncRequestContent; import org.eclipse.jetty.client.util.BufferingResponseListener; -import org.eclipse.jetty.client.util.DeferredContentProvider; -import org.eclipse.jetty.client.util.InputStreamContentProvider; -import org.eclipse.jetty.client.util.StringContentProvider; +import org.eclipse.jetty.client.util.InputStreamRequestContent; +import org.eclipse.jetty.client.util.StringRequestContent; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpStatus; @@ -178,7 +178,7 @@ public class AsyncIOServletTest extends AbstractTest responseLatch.countDown()) .timeout(5, TimeUnit.SECONDS) .send(result -> @@ -324,7 +324,7 @@ public class AsyncIOServletTest extends AbstractTest { if (transport == HTTP) @@ -1169,7 +1169,7 @@ public class AsyncIOServletTest extends AbstractTest inputComplete = new CompletableFuture<>(); diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/AsyncRequestContentTest.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/AsyncRequestContentTest.java index 40fa9262a72..5bfc4e3b482 100644 --- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/AsyncRequestContentTest.java +++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/AsyncRequestContentTest.java @@ -22,6 +22,8 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.OutputStream; import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import javax.servlet.ServletException; @@ -29,15 +31,17 @@ import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.eclipse.jetty.client.util.DeferredContentProvider; -import org.eclipse.jetty.client.util.InputStreamContentProvider; -import org.eclipse.jetty.client.util.OutputStreamContentProvider; +import org.eclipse.jetty.client.util.AsyncRequestContent; +import org.eclipse.jetty.client.util.InputStreamRequestContent; +import org.eclipse.jetty.client.util.OutputStreamRequestContent; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.util.Callback; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ArgumentsSource; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; public class AsyncRequestContentTest extends AbstractTest @@ -50,45 +54,45 @@ public class AsyncRequestContentTest extends AbstractTest @ParameterizedTest @ArgumentsSource(TransportProvider.class) - public void testEmptyDeferredContent(Transport transport) throws Exception + public void testEmptyAsyncContent(Transport transport) throws Exception { init(transport); scenario.start(new ConsumeInputHandler()); - DeferredContentProvider contentProvider = new DeferredContentProvider(); + AsyncRequestContent content = new AsyncRequestContent(); CountDownLatch latch = new CountDownLatch(1); scenario.client.POST(scenario.newURI()) - .content(contentProvider) + .body(content) .send(result -> { if (result.isSucceeded() && result.getResponse().getStatus() == HttpStatus.OK_200) latch.countDown(); }); - contentProvider.close(); + content.close(); assertTrue(latch.await(5, TimeUnit.SECONDS)); } @ParameterizedTest @ArgumentsSource(TransportProvider.class) - public void testDeferredContent(Transport transport) throws Exception + public void testAsyncContent(Transport transport) throws Exception { init(transport); scenario.start(new ConsumeInputHandler()); - DeferredContentProvider contentProvider = new DeferredContentProvider(); + AsyncRequestContent content = new AsyncRequestContent(); CountDownLatch latch = new CountDownLatch(1); scenario.client.POST(scenario.newURI()) - .content(contentProvider) + .body(content) .send(result -> { if (result.isSucceeded() && result.getResponse().getStatus() == HttpStatus.OK_200) latch.countDown(); }); - contentProvider.offer(ByteBuffer.wrap(new byte[1])); - contentProvider.close(); + content.offer(ByteBuffer.wrap(new byte[1])); + content.close(); assertTrue(latch.await(5, TimeUnit.SECONDS)); } @@ -100,18 +104,17 @@ public class AsyncRequestContentTest extends AbstractTest init(transport); scenario.start(new ConsumeInputHandler()); - InputStreamContentProvider contentProvider = - new InputStreamContentProvider(new ByteArrayInputStream(new byte[0])); + InputStreamRequestContent content = + new InputStreamRequestContent(new ByteArrayInputStream(new byte[0])); CountDownLatch latch = new CountDownLatch(1); scenario.client.POST(scenario.newURI()) - .content(contentProvider) + .body(content) .send(result -> { if (result.isSucceeded() && result.getResponse().getStatus() == HttpStatus.OK_200) latch.countDown(); }); - contentProvider.close(); assertTrue(latch.await(5, TimeUnit.SECONDS)); } @@ -123,18 +126,17 @@ public class AsyncRequestContentTest extends AbstractTest init(transport); scenario.start(new ConsumeInputHandler()); - InputStreamContentProvider contentProvider = - new InputStreamContentProvider(new ByteArrayInputStream(new byte[1])); + InputStreamRequestContent content = + new InputStreamRequestContent(new ByteArrayInputStream(new byte[1])); CountDownLatch latch = new CountDownLatch(1); scenario.client.POST(scenario.newURI()) - .content(contentProvider) + .body(content) .send(result -> { if (result.isSucceeded() && result.getResponse().getStatus() == HttpStatus.OK_200) latch.countDown(); }); - contentProvider.close(); assertTrue(latch.await(5, TimeUnit.SECONDS)); } @@ -146,17 +148,17 @@ public class AsyncRequestContentTest extends AbstractTest init(transport); scenario.start(new ConsumeInputHandler()); - OutputStreamContentProvider contentProvider = new OutputStreamContentProvider(); + OutputStreamRequestContent content = new OutputStreamRequestContent(); CountDownLatch latch = new CountDownLatch(1); scenario.client.POST(scenario.newURI()) - .content(contentProvider) + .body(content) .send(result -> { if (result.isSucceeded() && result.getResponse().getStatus() == HttpStatus.OK_200) latch.countDown(); }); - contentProvider.close(); + content.close(); assertTrue(latch.await(5, TimeUnit.SECONDS)); } @@ -168,24 +170,62 @@ public class AsyncRequestContentTest extends AbstractTest init(transport); scenario.start(new ConsumeInputHandler()); - OutputStreamContentProvider contentProvider = new OutputStreamContentProvider(); + OutputStreamRequestContent content = new OutputStreamRequestContent(); CountDownLatch latch = new CountDownLatch(1); scenario.client.POST(scenario.newURI()) - .content(contentProvider) + .body(content) .send(result -> { if (result.isSucceeded() && result.getResponse().getStatus() == HttpStatus.OK_200) latch.countDown(); }); - OutputStream output = contentProvider.getOutputStream(); + OutputStream output = content.getOutputStream(); output.write(new byte[1]); output.flush(); - contentProvider.close(); + content.close(); assertTrue(latch.await(5, TimeUnit.SECONDS)); } + @ParameterizedTest + @ArgumentsSource(TransportProvider.class) + public void testBufferReuseAfterCallbackCompleted(Transport transport) throws Exception + { + init(transport); + scenario.start(new ConsumeInputHandler()); + + AsyncRequestContent content = new AsyncRequestContent(); + + CountDownLatch latch = new CountDownLatch(1); + List requestContent = new ArrayList<>(); + scenario.client.POST(scenario.newURI()) + .onRequestContent(((request, buffer) -> requestContent.add(buffer.get()))) + .body(content) + .send(result -> + { + if (result.isSucceeded() && + result.getResponse().getStatus() == HttpStatus.OK_200) + latch.countDown(); + }); + + byte first = '1'; + byte second = '2'; + byte[] bytes = new byte[1]; + bytes[0] = first; + ByteBuffer buffer = ByteBuffer.wrap(bytes); + content.offer(buffer, Callback.from(() -> + { + bytes[0] = second; + content.offer(ByteBuffer.wrap(bytes), Callback.from(content::close)); + })); + + assertTrue(latch.await(5, TimeUnit.SECONDS)); + assertEquals(2, requestContent.size()); + assertEquals(first, requestContent.get(0)); + assertEquals(second, requestContent.get(1)); + } + private static class ConsumeInputHandler extends AbstractHandler { @Override diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/ConnectionStatisticsTest.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/ConnectionStatisticsTest.java index b314635946d..47e3c28b362 100644 --- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/ConnectionStatisticsTest.java +++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/ConnectionStatisticsTest.java @@ -26,7 +26,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.api.ContentResponse; -import org.eclipse.jetty.client.util.BytesContentProvider; +import org.eclipse.jetty.client.util.BytesRequestContent; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.io.Connection; @@ -102,7 +102,7 @@ public class ConnectionStatisticsTest extends AbstractTest long contentLength = content.length; ContentResponse response = scenario.client.newRequest(scenario.newURI()) .header(HttpHeader.CONNECTION, "close") - .content(new BytesContentProvider(content)) + .body(new BytesRequestContent(content)) .timeout(5, TimeUnit.SECONDS) .send(); diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientContinueTest.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientContinueTest.java index 3da4befe4c0..1fd46c84b6d 100644 --- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientContinueTest.java +++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientContinueTest.java @@ -39,9 +39,9 @@ import org.eclipse.jetty.client.ContinueProtocolHandler; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.api.Result; +import org.eclipse.jetty.client.util.AsyncRequestContent; import org.eclipse.jetty.client.util.BufferingResponseListener; -import org.eclipse.jetty.client.util.BytesContentProvider; -import org.eclipse.jetty.client.util.DeferredContentProvider; +import org.eclipse.jetty.client.util.BytesRequestContent; import org.eclipse.jetty.http.HttpGenerator; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeaderValue; @@ -104,7 +104,7 @@ public class HttpClientContinueTest extends AbstractTest ContentResponse response = scenario.client.newRequest(scenario.newURI()) .header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString()) - .content(new BytesContentProvider(contents)) + .body(new BytesRequestContent(contents)) .timeout(5, TimeUnit.SECONDS) .send(); @@ -145,7 +145,7 @@ public class HttpClientContinueTest extends AbstractTest byte[] content2 = new byte[16384]; ContentResponse response = scenario.client.newRequest(scenario.newURI()) .header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString()) - .content(new BytesContentProvider(content1, content2) + .body(new BytesRequestContent(content1, content2) { @Override public long getLength() @@ -185,7 +185,7 @@ public class HttpClientContinueTest extends AbstractTest testExpect100ContinueWithContentRespondError(transport, 413); } - private void testExpect100ContinueWithContentRespondError(Transport transport, final int error) throws Exception + private void testExpect100ContinueWithContentRespondError(Transport transport, int error) throws Exception { init(transport); scenario.start(new AbstractHandler() @@ -200,10 +200,10 @@ public class HttpClientContinueTest extends AbstractTest byte[] content1 = new byte[10240]; byte[] content2 = new byte[16384]; - final CountDownLatch latch = new CountDownLatch(1); + CountDownLatch latch = new CountDownLatch(1); scenario.client.newRequest(scenario.newURI()) .header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString()) - .content(new BytesContentProvider(content1, content2)) + .body(new BytesRequestContent(content1, content2)) .send(new BufferingResponseListener() { @Override @@ -228,7 +228,7 @@ public class HttpClientContinueTest extends AbstractTest public void testExpect100ContinueWithContentWithRedirect(Transport transport) throws Exception { init(transport); - final String data = "success"; + String data = "success"; scenario.start(new AbstractHandler() { @Override @@ -250,12 +250,12 @@ public class HttpClientContinueTest extends AbstractTest }); byte[] content = new byte[10240]; - final CountDownLatch latch = new CountDownLatch(1); + CountDownLatch latch = new CountDownLatch(1); scenario.client.newRequest(scenario.newURI()) .method(HttpMethod.POST) .path("/continue") .header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString()) - .content(new BytesContentProvider(content)) + .body(new BytesRequestContent(content)) .send(new BufferingResponseListener() { @Override @@ -278,7 +278,7 @@ public class HttpClientContinueTest extends AbstractTest init(transport); // A request with Expect: 100-Continue cannot receive non-final responses like 3xx - final String data = "success"; + String data = "success"; scenario.start(new AbstractHandler() { @Override @@ -300,12 +300,12 @@ public class HttpClientContinueTest extends AbstractTest }); byte[] content = new byte[10240]; - final CountDownLatch latch = new CountDownLatch(1); + CountDownLatch latch = new CountDownLatch(1); scenario.client.newRequest(scenario.newURI()) .method(HttpMethod.POST) .path("/redirect") .header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString()) - .content(new BytesContentProvider(content)) + .body(new BytesRequestContent(content)) .send(new BufferingResponseListener() { @Override @@ -329,7 +329,7 @@ public class HttpClientContinueTest extends AbstractTest public void testExpect100ContinueWithContentWithResponseFailureBefore100Continue(Transport transport) throws Exception { init(transport); - final long idleTimeout = 1000; + long idleTimeout = 1000; scenario.startServer(new AbstractHandler() { @Override @@ -349,10 +349,10 @@ public class HttpClientContinueTest extends AbstractTest scenario.startClient(httpClient -> httpClient.setIdleTimeout(2 * idleTimeout)); byte[] content = new byte[1024]; - final CountDownLatch latch = new CountDownLatch(1); + CountDownLatch latch = new CountDownLatch(1); scenario.client.newRequest(scenario.newURI()) .header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString()) - .content(new BytesContentProvider(content)) + .body(new BytesRequestContent(content)) .idleTimeout(idleTimeout, TimeUnit.MILLISECONDS) .send(new BufferingResponseListener() { @@ -376,7 +376,7 @@ public class HttpClientContinueTest extends AbstractTest public void testExpect100ContinueWithContentWithResponseFailureAfter100Continue(Transport transport) throws Exception { init(transport); - final long idleTimeout = 1000; + long idleTimeout = 1000; scenario.startServer(new AbstractHandler() { @Override @@ -398,10 +398,10 @@ public class HttpClientContinueTest extends AbstractTest scenario.startClient(httpClient -> httpClient.setIdleTimeout(idleTimeout)); byte[] content = new byte[1024]; - final CountDownLatch latch = new CountDownLatch(1); + CountDownLatch latch = new CountDownLatch(1); scenario.client.newRequest(scenario.newURI()) .header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString()) - .content(new BytesContentProvider(content)) + .body(new BytesRequestContent(content)) .send(new BufferingResponseListener() { @Override @@ -439,7 +439,7 @@ public class HttpClientContinueTest extends AbstractTest @Override public Response.Listener getResponseListener() { - final Response.Listener listener = super.getResponseListener(); + Response.Listener listener = super.getResponseListener(); return new Response.Listener.Adapter() { @Override @@ -458,10 +458,10 @@ public class HttpClientContinueTest extends AbstractTest }); byte[] content = new byte[1024]; - final CountDownLatch latch = new CountDownLatch(1); + CountDownLatch latch = new CountDownLatch(1); scenario.client.newRequest(scenario.newURI()) .header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString()) - .content(new BytesContentProvider(content)) + .body(new BytesRequestContent(content)) .send(new BufferingResponseListener() { @Override @@ -495,17 +495,17 @@ public class HttpClientContinueTest extends AbstractTest } }); - final byte[] chunk1 = new byte[]{0, 1, 2, 3}; - final byte[] chunk2 = new byte[]{4, 5, 6, 7}; - final byte[] data = new byte[chunk1.length + chunk2.length]; + byte[] chunk1 = new byte[]{0, 1, 2, 3}; + byte[] chunk2 = new byte[]{4, 5, 6, 7}; + byte[] data = new byte[chunk1.length + chunk2.length]; System.arraycopy(chunk1, 0, data, 0, chunk1.length); System.arraycopy(chunk2, 0, data, chunk1.length, chunk2.length); - final CountDownLatch latch = new CountDownLatch(1); - DeferredContentProvider content = new DeferredContentProvider(); + CountDownLatch latch = new CountDownLatch(1); + AsyncRequestContent content = new AsyncRequestContent(); scenario.client.newRequest(scenario.newURI()) .header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString()) - .content(content) + .body(content) .send(new BufferingResponseListener() { @Override @@ -546,17 +546,17 @@ public class HttpClientContinueTest extends AbstractTest } }); - final byte[] chunk1 = new byte[]{0, 1, 2, 3}; - final byte[] chunk2 = new byte[]{4, 5, 6, 7}; - final byte[] data = new byte[chunk1.length + chunk2.length]; + byte[] chunk1 = new byte[]{0, 1, 2, 3}; + byte[] chunk2 = new byte[]{4, 5, 6, 7}; + byte[] data = new byte[chunk1.length + chunk2.length]; System.arraycopy(chunk1, 0, data, 0, chunk1.length); System.arraycopy(chunk2, 0, data, chunk1.length, chunk2.length); - final CountDownLatch latch = new CountDownLatch(1); - DeferredContentProvider content = new DeferredContentProvider(ByteBuffer.wrap(chunk1)); + CountDownLatch latch = new CountDownLatch(1); + AsyncRequestContent content = new AsyncRequestContent(ByteBuffer.wrap(chunk1)); scenario.client.newRequest(scenario.newURI()) .header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString()) - .content(content) + .body(content) .send(new BufferingResponseListener() { @Override @@ -591,10 +591,10 @@ public class HttpClientContinueTest extends AbstractTest } }); - final byte[] data = new byte[]{0, 1, 2, 3, 4, 5, 6, 7}; - final DeferredContentProvider content = new DeferredContentProvider(); + byte[] data = new byte[]{0, 1, 2, 3, 4, 5, 6, 7}; + AsyncRequestContent content = new AsyncRequestContent(); - final CountDownLatch latch = new CountDownLatch(1); + CountDownLatch latch = new CountDownLatch(1); scenario.client.newRequest(scenario.newURI()) .header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString()) .onRequestHeaders(request -> @@ -602,7 +602,7 @@ public class HttpClientContinueTest extends AbstractTest content.offer(ByteBuffer.wrap(data)); content.close(); }) - .content(content) + .body(content) .send(new BufferingResponseListener() { @Override @@ -632,13 +632,13 @@ public class HttpClientContinueTest extends AbstractTest } }); - final byte[] chunk1 = new byte[]{0, 1, 2, 3}; - final byte[] chunk2 = new byte[]{4, 5, 6}; - final byte[] data = new byte[chunk1.length + chunk2.length]; + byte[] chunk1 = new byte[]{0, 1, 2, 3}; + byte[] chunk2 = new byte[]{4, 5, 6}; + byte[] data = new byte[chunk1.length + chunk2.length]; System.arraycopy(chunk1, 0, data, 0, chunk1.length); System.arraycopy(chunk2, 0, data, chunk1.length, chunk2.length); - final DeferredContentProvider content = new DeferredContentProvider(ByteBuffer.wrap(chunk1)); + AsyncRequestContent content = new AsyncRequestContent(ByteBuffer.wrap(chunk1)); scenario.client.getProtocolHandlers().put(new ContinueProtocolHandler() { @@ -658,10 +658,10 @@ public class HttpClientContinueTest extends AbstractTest } }); - final CountDownLatch latch = new CountDownLatch(1); + CountDownLatch latch = new CountDownLatch(1); scenario.client.newRequest(scenario.newURI()) .header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString()) - .content(content) + .body(content) .send(new BufferingResponseListener() { @Override @@ -692,10 +692,10 @@ public class HttpClientContinueTest extends AbstractTest { server.bind(new InetSocketAddress("localhost", 0)); - final CountDownLatch latch = new CountDownLatch(1); + CountDownLatch latch = new CountDownLatch(1); scenario.client.newRequest("localhost", server.getLocalPort()) .header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString()) - .content(new BytesContentProvider(new byte[]{0})) + .body(new BytesRequestContent(new byte[]{0})) .send(result -> { assertTrue(result.isSucceeded(), result.toString()); @@ -756,7 +756,7 @@ public class HttpClientContinueTest extends AbstractTest byte[] bytes = new byte[1024]; new Random().nextBytes(bytes); ContentResponse response = scenario.client.newRequest(scenario.newURI()) - .content(new BytesContentProvider(bytes)) + .body(new BytesRequestContent(bytes)) .timeout(5, TimeUnit.SECONDS) .send(); diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientLoadTest.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientLoadTest.java index 9d01af09eee..5ec342ab056 100644 --- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientLoadTest.java +++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientLoadTest.java @@ -40,7 +40,7 @@ import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.api.Result; import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP; -import org.eclipse.jetty.client.util.BytesContentProvider; +import org.eclipse.jetty.client.util.BytesRequestContent; import org.eclipse.jetty.fcgi.client.http.HttpClientTransportOverFCGI; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpMethod; @@ -241,7 +241,7 @@ public class HttpClientLoadTest extends AbstractTest } }); - final AtomicLong requestTime = new AtomicLong(); + AtomicLong requestTime = new AtomicLong(); ContentResponse response = scenario.client.newRequest(scenario.newURI()) .scheme(scenario.getScheme()) .file(upload) @@ -150,7 +147,7 @@ public class HttpClientStreamTest extends AbstractTest public void testDownload(Transport transport) throws Exception { init(transport); - final byte[] data = new byte[128 * 1024]; + byte[] data = new byte[128 * 1024]; byte value = 1; Arrays.fill(data, value); scenario.start(new AbstractHandler() @@ -195,7 +192,7 @@ public class HttpClientStreamTest extends AbstractTest public void testDownloadOfUTF8Content(Transport transport) throws Exception { init(transport); - final byte[] data = new byte[]{(byte)0xC3, (byte)0xA8}; // UTF-8 representation of è + byte[] data = new byte[]{(byte)0xC3, (byte)0xA8}; // UTF-8 representation of è scenario.start(new AbstractHandler() { @Override @@ -237,7 +234,7 @@ public class HttpClientStreamTest extends AbstractTest public void testDownloadWithFailure(Transport transport) throws Exception { init(transport); - final byte[] data = new byte[64 * 1024]; + byte[] data = new byte[64 * 1024]; byte value = 1; Arrays.fill(data, value); scenario.start(new AbstractHandler() @@ -306,7 +303,7 @@ public class HttpClientStreamTest extends AbstractTest scenario.client.newRequest(scenario.newURI()) .scheme(scenario.getScheme()) - .content(new BytesContentProvider(new byte[]{0, 1, 2, 3})) + .body(new BytesRequestContent(new byte[]{0, 1, 2, 3})) .send(listener); Response response = listener.get(5, TimeUnit.SECONDS); assertEquals(200, response.getStatus()); @@ -510,11 +507,11 @@ public class HttpClientStreamTest extends AbstractTest } }); - final byte[] data = new byte[]{0, 1, 2, 3}; + byte[] data = new byte[]{0, 1, 2, 3}; ExecutionException e = assertThrows(ExecutionException.class, () -> scenario.client.newRequest(scenario.newURI()) .scheme(scenario.getScheme()) - .content(new InputStreamContentProvider(new InputStream() + .body(new InputStreamRequestContent(new InputStream() { private int index = 0; @@ -527,7 +524,7 @@ public class HttpClientStreamTest extends AbstractTest }, data.length / 2)) .timeout(5, TimeUnit.SECONDS) .send()); - assertThat(e.getCause(), instanceOf(NoSuchElementException.class)); + assertThat(e.getCause(), instanceOf(ArrayIndexOutOfBoundsException.class)); } @ParameterizedTest @@ -535,10 +532,10 @@ public class HttpClientStreamTest extends AbstractTest public void testDownloadWithCloseBeforeContent(Transport transport) throws Exception { init(transport); - final byte[] data = new byte[128 * 1024]; + byte[] data = new byte[128 * 1024]; byte value = 3; Arrays.fill(data, value); - final CountDownLatch latch = new CountDownLatch(1); + CountDownLatch latch = new CountDownLatch(1); scenario.start(new AbstractHandler() { @Override @@ -582,9 +579,9 @@ public class HttpClientStreamTest extends AbstractTest public void testDownloadWithCloseMiddleOfContent(Transport transport) throws Exception { init(transport); - final byte[] data1 = new byte[1024]; - final byte[] data2 = new byte[1024]; - final CountDownLatch latch = new CountDownLatch(1); + byte[] data1 = new byte[1024]; + byte[] data2 = new byte[1024]; + CountDownLatch latch = new CountDownLatch(1); scenario.start(new AbstractHandler() { @Override @@ -635,7 +632,7 @@ public class HttpClientStreamTest extends AbstractTest public void testDownloadWithCloseEndOfContent(Transport transport) throws Exception { init(transport); - final byte[] data = new byte[1024]; + byte[] data = new byte[1024]; scenario.start(new AbstractHandler() { @Override @@ -688,12 +685,12 @@ public class HttpClientStreamTest extends AbstractTest } }); - final CountDownLatch latch = new CountDownLatch(1); - try (DeferredContentProvider content = new DeferredContentProvider()) + CountDownLatch latch = new CountDownLatch(1); + try (AsyncRequestContent content = new AsyncRequestContent()) { scenario.client.newRequest(scenario.newURI()) .scheme(scenario.getScheme()) - .content(content) + .body(content) .send(result -> { if (result.isSucceeded() && result.getResponse().getStatus() == 200) @@ -731,9 +728,9 @@ public class HttpClientStreamTest extends AbstractTest } }); - final CountDownLatch latch = new CountDownLatch(1); - final AtomicInteger succeeds = new AtomicInteger(); - try (DeferredContentProvider content = new DeferredContentProvider()) + CountDownLatch latch = new CountDownLatch(1); + AtomicInteger succeeds = new AtomicInteger(); + try (AsyncRequestContent content = new AsyncRequestContent()) { // Make the content immediately available. content.offer(ByteBuffer.allocate(1024), new Callback() @@ -747,7 +744,7 @@ public class HttpClientStreamTest extends AbstractTest scenario.client.newRequest(scenario.newURI()) .scheme(scenario.getScheme()) - .content(content) + .body(content) .send(result -> { if (result.isSucceeded() && result.getResponse().getStatus() == 200) @@ -773,14 +770,14 @@ public class HttpClientStreamTest extends AbstractTest } }); - final CountDownLatch latch = new CountDownLatch(1); - final byte[] data = new byte[512]; - final DeferredContentProvider content = new DeferredContentProvider() + CountDownLatch latch = new CountDownLatch(1); + byte[] data = new byte[512]; + AsyncRequestContent content = new AsyncRequestContent() { @Override - public void setListener(Listener listener) + public void demand() { - super.setListener(listener); + super.demand(); // Simulate a concurrent call offer(ByteBuffer.wrap(data)); close(); @@ -789,91 +786,7 @@ public class HttpClientStreamTest extends AbstractTest scenario.client.newRequest(scenario.newURI()) .scheme(scenario.getScheme()) - .content(content) - .send(new BufferingResponseListener() - { - @Override - public void onComplete(Result result) - { - if (result.isSucceeded() && - result.getResponse().getStatus() == 200 && - Arrays.equals(data, getContent())) - latch.countDown(); - } - }); - - assertTrue(latch.await(5, TimeUnit.SECONDS)); - } - - @ParameterizedTest - @ArgumentsSource(TransportProvider.class) - public void testUploadWithDeferredContentProviderRacingWithIterator(Transport transport) throws Exception - { - init(transport); - scenario.start(new AbstractHandler() - { - @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException - { - baseRequest.setHandled(true); - IO.copy(request.getInputStream(), response.getOutputStream()); - } - }); - - final CountDownLatch latch = new CountDownLatch(1); - final byte[] data = new byte[512]; - final AtomicReference contentRef = new AtomicReference<>(); - final DeferredContentProvider content = new DeferredContentProvider() - { - @Override - public Iterator iterator() - { - return new Iterator() - { - // Data for the deferred content iterator: - // [0] => deferred - // [1] => deferred - // [2] => data - private final byte[][] iteratorData = new byte[3][]; - private final AtomicInteger index = new AtomicInteger(); - - { - iteratorData[0] = null; - iteratorData[1] = null; - iteratorData[2] = data; - } - - @Override - public boolean hasNext() - { - return index.get() < iteratorData.length; - } - - @Override - public ByteBuffer next() - { - byte[] chunk = iteratorData[index.getAndIncrement()]; - ByteBuffer result = chunk == null ? null : ByteBuffer.wrap(chunk); - if (index.get() < iteratorData.length) - { - contentRef.get().offer(result == null ? BufferUtil.EMPTY_BUFFER : result); - contentRef.get().close(); - } - return result; - } - - @Override - public void remove() - { - } - }; - } - }; - contentRef.set(content); - - scenario.client.newRequest(scenario.newURI()) - .scheme(scenario.getScheme()) - .content(content) + .body(content) .send(new BufferingResponseListener() { @Override @@ -904,12 +817,12 @@ public class HttpClientStreamTest extends AbstractTest } }); - final byte[] data = new byte[512]; - final CountDownLatch latch = new CountDownLatch(1); - OutputStreamContentProvider content = new OutputStreamContentProvider(); + byte[] data = new byte[512]; + CountDownLatch latch = new CountDownLatch(1); + OutputStreamRequestContent content = new OutputStreamRequestContent(); scenario.client.newRequest(scenario.newURI()) .scheme(scenario.getScheme()) - .content(content) + .body(content) .send(new BufferingResponseListener() { @Override @@ -948,13 +861,13 @@ public class HttpClientStreamTest extends AbstractTest } }); - final byte[] data = new byte[16 * 1024 * 1024]; + byte[] data = new byte[16 * 1024 * 1024]; new Random().nextBytes(data); - final CountDownLatch latch = new CountDownLatch(1); - OutputStreamContentProvider content = new OutputStreamContentProvider(); + CountDownLatch latch = new CountDownLatch(1); + OutputStreamRequestContent content = new OutputStreamRequestContent(); scenario.client.newRequest(scenario.newURI()) .scheme(scenario.getScheme()) - .content(content) + .body(content) .send(new BufferingResponseListener(data.length) { @Override @@ -970,17 +883,9 @@ public class HttpClientStreamTest extends AbstractTest // Make sure we provide the content *after* the request has been "sent". Thread.sleep(1000); - try (InputStream input = new ByteArrayInputStream(data); - OutputStream output = content.getOutputStream()) + try (OutputStream output = content.getOutputStream()) { - byte[] buffer = new byte[1024]; - while (true) - { - int read = input.read(buffer); - if (read < 0) - break; - output.write(buffer, 0, read); - } + IO.copy(new ByteArrayInputStream(data), output); } assertTrue(latch.await(30, TimeUnit.SECONDS)); @@ -995,15 +900,15 @@ public class HttpClientStreamTest extends AbstractTest long connectTimeout = 1000; scenario.start(new EmptyServerHandler(), httpClient -> httpClient.setConnectTimeout(connectTimeout)); - final byte[] data = new byte[512]; - final CountDownLatch latch = new CountDownLatch(1); - OutputStreamContentProvider content = new OutputStreamContentProvider(); + byte[] data = new byte[512]; + CountDownLatch latch = new CountDownLatch(1); + OutputStreamRequestContent content = new OutputStreamRequestContent(); String uri = "http://0.0.0.1"; if (scenario.getNetworkConnectorLocalPort().isPresent()) uri += ":" + scenario.getNetworkConnectorLocalPort().get(); scenario.client.newRequest(uri) .scheme(scenario.getScheme()) - .content(content) + .body(content) .send(result -> { if (result.isFailed()) @@ -1028,8 +933,8 @@ public class HttpClientStreamTest extends AbstractTest init(transport); scenario.start(new EmptyServerHandler()); - final CountDownLatch failLatch = new CountDownLatch(2); - final Callback callback = new Callback() + CountDownLatch failLatch = new CountDownLatch(2); + Callback callback = new Callback() { @Override public void failed(Throwable x) @@ -1038,11 +943,11 @@ public class HttpClientStreamTest extends AbstractTest } }; - final CountDownLatch completeLatch = new CountDownLatch(1); - final DeferredContentProvider content = new DeferredContentProvider(); + CountDownLatch completeLatch = new CountDownLatch(1); + AsyncRequestContent content = new AsyncRequestContent(); scenario.client.newRequest(scenario.newURI()) .scheme(scenario.getScheme()) - .content(content) + .body(content) .onRequestBegin(request -> { content.offer(ByteBuffer.wrap(new byte[256]), callback); @@ -1058,7 +963,7 @@ public class HttpClientStreamTest extends AbstractTest assertTrue(failLatch.await(5, TimeUnit.SECONDS)); // Make sure that adding more content results in the callback to be failed. - final CountDownLatch latch = new CountDownLatch(1); + CountDownLatch latch = new CountDownLatch(1); content.offer(ByteBuffer.wrap(new byte[128]), new Callback() { @Override @@ -1079,7 +984,7 @@ public class HttpClientStreamTest extends AbstractTest long connectTimeout = 1000; scenario.start(new EmptyServerHandler(), httpClient -> httpClient.setConnectTimeout(connectTimeout)); - final CountDownLatch closeLatch = new CountDownLatch(1); + CountDownLatch closeLatch = new CountDownLatch(1); InputStream stream = new ByteArrayInputStream("test".getBytes(StandardCharsets.UTF_8)) { @Override @@ -1089,15 +994,15 @@ public class HttpClientStreamTest extends AbstractTest closeLatch.countDown(); } }; - InputStreamContentProvider content = new InputStreamContentProvider(stream); + InputStreamRequestContent content = new InputStreamRequestContent(stream); - final CountDownLatch completeLatch = new CountDownLatch(1); + CountDownLatch completeLatch = new CountDownLatch(1); String uri = "http://0.0.0.1"; if (scenario.getNetworkConnectorLocalPort().isPresent()) uri += ":" + scenario.getNetworkConnectorLocalPort().get(); scenario.client.newRequest(uri) .scheme(scenario.getScheme()) - .content(content) + .body(content) .send(result -> { assertTrue(result.isFailed()); @@ -1113,7 +1018,7 @@ public class HttpClientStreamTest extends AbstractTest public void testUploadWithConcurrentServerCloseClosesStream(Transport transport) throws Exception { init(transport); - final CountDownLatch serverLatch = new CountDownLatch(1); + CountDownLatch serverLatch = new CountDownLatch(1); scenario.start(new AbstractHandler() { @Override @@ -1126,8 +1031,8 @@ public class HttpClientStreamTest extends AbstractTest } }); - final AtomicBoolean commit = new AtomicBoolean(); - final CountDownLatch closeLatch = new CountDownLatch(1); + AtomicBoolean commit = new AtomicBoolean(); + CountDownLatch closeLatch = new CountDownLatch(1); InputStream stream = new InputStream() { @Override @@ -1166,12 +1071,12 @@ public class HttpClientStreamTest extends AbstractTest closeLatch.countDown(); } }; - InputStreamContentProvider provider = new InputStreamContentProvider(stream, 1); + InputStreamRequestContent content = new InputStreamRequestContent(stream, 1); - final CountDownLatch completeLatch = new CountDownLatch(1); + CountDownLatch completeLatch = new CountDownLatch(1); scenario.client.newRequest(scenario.newURI()) .scheme(scenario.getScheme()) - .content(provider) + .body(content) .onRequestCommit(request -> commit.set(true)) .send(result -> { @@ -1311,7 +1216,7 @@ public class HttpClientStreamTest extends AbstractTest CountDownLatch latch = new CountDownLatch(1); byte[] bytes = "[{\"key\":\"value\"}]".getBytes(StandardCharsets.UTF_8); - OutputStreamContentProvider content = new OutputStreamContentProvider() + OutputStreamRequestContent content = new OutputStreamRequestContent("application/json;charset=UTF-8") { @Override public long getLength() @@ -1322,7 +1227,7 @@ public class HttpClientStreamTest extends AbstractTest scenario.client.newRequest(scenario.newURI()) .method(HttpMethod.POST) .path(scenario.servletPath) - .content(content, "application/json;charset=UTF-8") + .body(content) .onResponseSuccess(response -> { assertEquals(HttpStatus.REQUEST_TIMEOUT_408, response.getStatus()); diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientTest.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientTest.java index a9d7279c1c9..f1e679e9aa7 100644 --- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientTest.java +++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientTest.java @@ -22,7 +22,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; import java.util.List; -import java.util.Optional; import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; @@ -39,7 +38,7 @@ import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Destination; import org.eclipse.jetty.client.api.Response; -import org.eclipse.jetty.client.util.BytesContentProvider; +import org.eclipse.jetty.client.util.BytesRequestContent; import org.eclipse.jetty.client.util.FutureResponseListener; import org.eclipse.jetty.client.util.InputStreamResponseListener; import org.eclipse.jetty.http.HttpHeader; @@ -231,7 +230,7 @@ public class HttpClientTest extends AbstractTest ContentResponse response = scenario.client.newRequest(scenario.newURI()) .method(HttpMethod.POST) - .content(new BytesContentProvider(bytes)) + .body(new BytesRequestContent(bytes)) .timeout(15, TimeUnit.SECONDS) .send(); @@ -275,10 +274,10 @@ public class HttpClientTest extends AbstractTest int chunks = 256; int chunkSize = 16; byte[][] bytes = IntStream.range(0, chunks).mapToObj(x -> new byte[chunkSize]).toArray(byte[][]::new); - BytesContentProvider contentProvider = new BytesContentProvider("application/octet-stream", bytes); + BytesRequestContent content = new BytesRequestContent("application/octet-stream", bytes); ContentResponse response = scenario.client.newRequest(scenario.newURI()) .method(HttpMethod.POST) - .content(contentProvider) + .body(content) .timeout(15, TimeUnit.SECONDS) .send(); diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientTimeoutTest.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientTimeoutTest.java index e2044508693..392e7eb74f2 100644 --- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientTimeoutTest.java +++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientTimeoutTest.java @@ -43,7 +43,7 @@ import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.api.Result; import org.eclipse.jetty.client.util.BufferingResponseListener; -import org.eclipse.jetty.client.util.InputStreamContentProvider; +import org.eclipse.jetty.client.util.InputStreamRequestContent; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.ClientConnectionFactory; @@ -159,7 +159,7 @@ public class HttpClientTimeoutTest extends AbstractTest final CountDownLatch latch = new CountDownLatch(1); final byte[] content = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; Request request = scenario.client.newRequest(scenario.newURI()) - .content(new InputStreamContentProvider(new ByteArrayInputStream(content))) + .body(new InputStreamRequestContent(new ByteArrayInputStream(content))) .timeout(2 * timeout, TimeUnit.MILLISECONDS); request.send(new BufferingResponseListener() { @@ -400,7 +400,7 @@ public class HttpClientTimeoutTest extends AbstractTest } }); - assertTrue(latch.await(333 * connectTimeout, TimeUnit.MILLISECONDS)); + assertTrue(latch.await(3 * connectTimeout, TimeUnit.MILLISECONDS)); assertNotNull(request.getAbortCause()); } @@ -521,7 +521,7 @@ public class HttpClientTimeoutTest extends AbstractTest } } - private class TimeoutHandler extends AbstractHandler + private static class TimeoutHandler extends AbstractHandler { private final long timeout; diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpTrailersTest.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpTrailersTest.java index 12e8c9b7e9e..44d0b0f09ce 100644 --- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpTrailersTest.java +++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpTrailersTest.java @@ -33,7 +33,7 @@ import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.HttpRequest; import org.eclipse.jetty.client.HttpResponse; import org.eclipse.jetty.client.api.ContentResponse; -import org.eclipse.jetty.client.util.BytesContentProvider; +import org.eclipse.jetty.client.util.BytesRequestContent; import org.eclipse.jetty.client.util.InputStreamResponseListener; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpFields; @@ -110,7 +110,7 @@ public class HttpTrailersTest extends AbstractTest HttpRequest request = (HttpRequest)scenario.client.newRequest(scenario.newURI()); request = request.trailers(() -> trailers); if (content != null) - request.method(HttpMethod.POST).content(new BytesContentProvider(content)); + request.method(HttpMethod.POST).body(new BytesRequestContent(content)); ContentResponse response = request.timeout(5, TimeUnit.SECONDS).send(); assertEquals(HttpStatus.OK_200, response.getStatus()); } diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/ServerTimeoutsTest.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/ServerTimeoutsTest.java index 1b46c984746..d420856e385 100644 --- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/ServerTimeoutsTest.java +++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/ServerTimeoutsTest.java @@ -38,8 +38,8 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.api.Result; +import org.eclipse.jetty.client.util.AsyncRequestContent; import org.eclipse.jetty.client.util.BufferingResponseListener; -import org.eclipse.jetty.client.util.DeferredContentProvider; import org.eclipse.jetty.http.BadMessageException; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http2.FlowControlStrategy; @@ -181,9 +181,9 @@ public class ServerTimeoutsTest extends AbstractTest scenario.setServerIdleTimeout(idleTimeout); CountDownLatch resultLatch = new CountDownLatch(2); - DeferredContentProvider content = new DeferredContentProvider(); + AsyncRequestContent content = new AsyncRequestContent(); scenario.client.POST(scenario.newURI()) - .content(content) + .body(content) .onResponseSuccess(response -> { if (response.getStatus() == HttpStatus.INTERNAL_SERVER_ERROR_500) @@ -244,10 +244,10 @@ public class ServerTimeoutsTest extends AbstractTest long idleTimeout = 2500; scenario.setServerIdleTimeout(idleTimeout); - DeferredContentProvider contentProvider = new DeferredContentProvider(ByteBuffer.allocate(1)); + AsyncRequestContent content = new AsyncRequestContent(ByteBuffer.allocate(1)); CountDownLatch resultLatch = new CountDownLatch(1); scenario.client.POST(scenario.newURI()) - .content(contentProvider) + .body(content) .send(result -> { if (result.getResponse().getStatus() == HttpStatus.INTERNAL_SERVER_ERROR_500) @@ -257,7 +257,7 @@ public class ServerTimeoutsTest extends AbstractTest // Async read should timeout. assertTrue(handlerLatch.await(2 * idleTimeout, TimeUnit.MILLISECONDS)); // Complete the request. - contentProvider.close(); + content.close(); assertTrue(resultLatch.await(5, TimeUnit.SECONDS)); } @@ -362,10 +362,10 @@ public class ServerTimeoutsTest extends AbstractTest } }); - DeferredContentProvider contentProvider = new DeferredContentProvider(); + AsyncRequestContent content = new AsyncRequestContent(); BlockingQueue results = new BlockingArrayQueue<>(); scenario.client.newRequest(scenario.newURI()) - .content(contentProvider) + .body(content) .send(result -> { if (result.isFailed()) @@ -376,10 +376,10 @@ public class ServerTimeoutsTest extends AbstractTest for (int i = 0; i < 3; ++i) { - contentProvider.offer(ByteBuffer.allocate(bytesPerSecond / 2)); + content.offer(ByteBuffer.allocate(bytesPerSecond / 2)); Thread.sleep(2500); } - contentProvider.close(); + content.close(); assertThat(scenario.requestLog.poll(5, TimeUnit.SECONDS), containsString(" 408")); @@ -389,7 +389,7 @@ public class ServerTimeoutsTest extends AbstractTest Object result = results.poll(5, TimeUnit.SECONDS); assertNotNull(result); if (result instanceof Integer) - assertThat((Integer)result, is(408)); + assertThat(result, is(408)); else assertThat(result, instanceOf(Throwable.class)); } @@ -419,10 +419,10 @@ public class ServerTimeoutsTest extends AbstractTest } }); - DeferredContentProvider contentProvider = new DeferredContentProvider(); + AsyncRequestContent content = new AsyncRequestContent(); CountDownLatch resultLatch = new CountDownLatch(1); scenario.client.newRequest(scenario.newURI()) - .content(contentProvider) + .body(content) .send(result -> { if (result.getResponse().getStatus() == HttpStatus.OK_200) @@ -431,10 +431,10 @@ public class ServerTimeoutsTest extends AbstractTest for (int i = 0; i < 3; ++i) { - contentProvider.offer(ByteBuffer.allocate(bytesPerSecond * 2)); + content.offer(ByteBuffer.allocate(bytesPerSecond * 2)); Thread.sleep(2500); } - contentProvider.close(); + content.close(); assertTrue(handlerLatch.await(5, TimeUnit.SECONDS)); assertTrue(resultLatch.await(5, TimeUnit.SECONDS)); @@ -454,10 +454,10 @@ public class ServerTimeoutsTest extends AbstractTest try (StacklessLogging ignore = new StacklessLogging(HttpChannel.class)) { - DeferredContentProvider contentProvider = new DeferredContentProvider(ByteBuffer.allocate(1)); + AsyncRequestContent content = new AsyncRequestContent(ByteBuffer.allocate(1)); CountDownLatch resultLatch = new CountDownLatch(1); scenario.client.POST(scenario.newURI()) - .content(contentProvider) + .body(content) .send(result -> { if (result.getResponse().getStatus() == HttpStatus.INTERNAL_SERVER_ERROR_500) @@ -467,7 +467,7 @@ public class ServerTimeoutsTest extends AbstractTest // Blocking read should timeout. assertTrue(handlerLatch.await(2 * httpIdleTimeout, TimeUnit.MILLISECONDS)); // Complete the request. - contentProvider.close(); + content.close(); assertTrue(resultLatch.await(5, TimeUnit.SECONDS)); } } @@ -520,10 +520,10 @@ public class ServerTimeoutsTest extends AbstractTest }); scenario.setServerIdleTimeout(idleTimeout); - DeferredContentProvider contentProvider = new DeferredContentProvider(ByteBuffer.allocate(1)); + AsyncRequestContent content = new AsyncRequestContent(ByteBuffer.allocate(1)); CountDownLatch resultLatch = new CountDownLatch(1); scenario.client.POST(scenario.newURI()) - .content(contentProvider) + .body(content) .send(result -> { if (result.getResponse().getStatus() == HttpStatus.INTERNAL_SERVER_ERROR_500) @@ -533,7 +533,7 @@ public class ServerTimeoutsTest extends AbstractTest // Async read should timeout. assertTrue(handlerLatch.await(2 * idleTimeout, TimeUnit.MILLISECONDS)); // Complete the request. - contentProvider.close(); + content.close(); assertTrue(resultLatch.await(5, TimeUnit.SECONDS)); } @@ -567,11 +567,11 @@ public class ServerTimeoutsTest extends AbstractTest System.arraycopy(data, 0, data1, 0, data1.length); byte[] data2 = new byte[data.length - data1.length]; System.arraycopy(data, data1.length, data2, 0, data2.length); - DeferredContentProvider content = new DeferredContentProvider(ByteBuffer.wrap(data1)); + AsyncRequestContent content = new AsyncRequestContent(ByteBuffer.wrap(data1)); CountDownLatch latch = new CountDownLatch(1); scenario.client.newRequest(scenario.newURI()) .path(scenario.servletPath) - .content(content) + .body(content) .send(new BufferingResponseListener() { @Override @@ -691,30 +691,4 @@ public class ServerTimeoutsTest extends AbstractTest } } } - - private static class BlockingWriteHandler extends AbstractHandler - { - private final CountDownLatch handlerLatch; - - private BlockingWriteHandler(CountDownLatch handlerLatch) - { - this.handlerLatch = handlerLatch; - } - - @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException - { - baseRequest.setHandled(true); - ServletOutputStream output = response.getOutputStream(); - try - { - output.write(new byte[64 * 1024 * 1024]); - } - catch (IOException x) - { - handlerLatch.countDown(); - throw x; - } - } - } } diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/DigestPostTest.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/DigestPostTest.java index 930106598b9..059303e8c24 100644 --- a/tests/test-integration/src/test/java/org/eclipse/jetty/test/DigestPostTest.java +++ b/tests/test-integration/src/test/java/org/eclipse/jetty/test/DigestPostTest.java @@ -36,9 +36,9 @@ import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.api.AuthenticationStore; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Request; -import org.eclipse.jetty.client.util.BytesContentProvider; +import org.eclipse.jetty.client.util.BytesRequestContent; import org.eclipse.jetty.client.util.DigestAuthentication; -import org.eclipse.jetty.client.util.StringContentProvider; +import org.eclipse.jetty.client.util.StringRequestContent; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.security.AbstractLoginService; import org.eclipse.jetty.security.ConstraintMapping; @@ -53,7 +53,6 @@ import org.eclipse.jetty.server.handler.DefaultHandler; import org.eclipse.jetty.server.handler.HandlerCollection; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.util.IO; -import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.TypeUtil; import org.eclipse.jetty.util.security.Constraint; import org.eclipse.jetty.util.security.Credential; @@ -63,6 +62,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; public class DigestPostTest @@ -178,12 +178,12 @@ public class DigestPostTest String result = IO.toString(socket.getInputStream()); assertTrue(result.startsWith("HTTP/1.1 401 Unauthorized")); - assertEquals(null, _received); + assertNull(_received); int n = result.indexOf("nonce="); String nonce = result.substring(n + 7, result.indexOf('"', n + 7)); MessageDigest md = MessageDigest.getInstance("MD5"); - byte[] b = md.digest(String.valueOf(TimeUnit.NANOSECONDS.toMillis(System.nanoTime())).getBytes(org.eclipse.jetty.util.StringUtil.__ISO_8859_1)); + byte[] b = md.digest(String.valueOf(TimeUnit.NANOSECONDS.toMillis(System.nanoTime())).getBytes(StandardCharsets.ISO_8859_1)); String cnonce = encode(b); String digest = "Digest username=\"testuser\" realm=\"test\" nonce=\"" + nonce + "\" uri=\"/test/\" algorithm=MD5 response=\"" + newResponse("POST", "/test/", cnonce, "testuser", "test", "password", nonce, "auth") + @@ -218,7 +218,7 @@ public class DigestPostTest ("POST /test/ HTTP/1.1\r\n" + "Host: 127.0.0.1:" + ((NetworkConnector)_server.getConnectors()[0]).getLocalPort() + "\r\n" + "Content-Length: " + bytes.length + "\r\n" + - "\r\n").getBytes("UTF-8")); + "\r\n").getBytes(StandardCharsets.UTF_8)); socket.getOutputStream().write(bytes); socket.getOutputStream().flush(); @@ -229,12 +229,12 @@ public class DigestPostTest String result = new String(buf, 0, len, StandardCharsets.UTF_8); assertTrue(result.startsWith("HTTP/1.1 401 Unauthorized")); - assertEquals(null, _received); + assertNull(_received); int n = result.indexOf("nonce="); String nonce = result.substring(n + 7, result.indexOf('"', n + 7)); MessageDigest md = MessageDigest.getInstance("MD5"); - byte[] b = md.digest(String.valueOf(TimeUnit.NANOSECONDS.toMillis(System.nanoTime())).getBytes(StringUtil.__ISO_8859_1)); + byte[] b = md.digest(String.valueOf(TimeUnit.NANOSECONDS.toMillis(System.nanoTime())).getBytes(StandardCharsets.ISO_8859_1)); String cnonce = encode(b); String digest = "Digest username=\"testuser\" realm=\"test\" nonce=\"" + nonce + "\" uri=\"/test/\" algorithm=MD5 response=\"" + newResponse("POST", "/test/", cnonce, "testuser", "test", "password", nonce, "auth") + @@ -246,7 +246,7 @@ public class DigestPostTest "Host: 127.0.0.1:" + ((NetworkConnector)_server.getConnectors()[0]).getLocalPort() + "\r\n" + "Content-Length: " + bytes.length + "\r\n" + "Authorization: " + digest + "\r\n" + - "\r\n").getBytes("UTF-8")); + "\r\n").getBytes(StandardCharsets.UTF_8)); socket.getOutputStream().write(bytes); socket.getOutputStream().flush(); @@ -270,7 +270,7 @@ public class DigestPostTest Request request = client.newRequest(srvUrl); request.method(HttpMethod.POST); - request.content(new BytesContentProvider(__message.getBytes("UTF8"))); + request.body(new BytesRequestContent(__message.getBytes(StandardCharsets.UTF_8))); _received = null; request = request.timeout(5, TimeUnit.SECONDS); ContentResponse response = request.send(); @@ -298,7 +298,7 @@ public class DigestPostTest Request request = client.newRequest(srvUrl); request.method(HttpMethod.POST); - request.content(new StringContentProvider(sent)); + request.body(new StringRequestContent(sent)); _received = null; request = request.timeout(5, TimeUnit.SECONDS); ContentResponse response = request.send(); @@ -334,30 +334,30 @@ public class DigestPostTest MessageDigest md = MessageDigest.getInstance("MD5"); // calc A1 digest - md.update(principal.getBytes(StringUtil.__ISO_8859_1)); + md.update(principal.getBytes(StandardCharsets.ISO_8859_1)); md.update((byte)':'); - md.update(realm.getBytes(StringUtil.__ISO_8859_1)); + md.update(realm.getBytes(StandardCharsets.ISO_8859_1)); md.update((byte)':'); - md.update(credentials.getBytes(StringUtil.__ISO_8859_1)); + md.update(credentials.getBytes(StandardCharsets.ISO_8859_1)); byte[] ha1 = md.digest(); // calc A2 digest md.reset(); - md.update(method.getBytes(StringUtil.__ISO_8859_1)); + md.update(method.getBytes(StandardCharsets.ISO_8859_1)); md.update((byte)':'); - md.update(uri.getBytes(StringUtil.__ISO_8859_1)); + md.update(uri.getBytes(StandardCharsets.ISO_8859_1)); byte[] ha2 = md.digest(); - md.update(TypeUtil.toString(ha1, 16).getBytes(StringUtil.__ISO_8859_1)); + md.update(TypeUtil.toString(ha1, 16).getBytes(StandardCharsets.ISO_8859_1)); md.update((byte)':'); - md.update(nonce.getBytes(StringUtil.__ISO_8859_1)); + md.update(nonce.getBytes(StandardCharsets.ISO_8859_1)); md.update((byte)':'); - md.update(NC.getBytes(StringUtil.__ISO_8859_1)); + md.update(NC.getBytes(StandardCharsets.ISO_8859_1)); md.update((byte)':'); - md.update(cnonce.getBytes(StringUtil.__ISO_8859_1)); + md.update(cnonce.getBytes(StandardCharsets.ISO_8859_1)); md.update((byte)':'); - md.update(qop.getBytes(StringUtil.__ISO_8859_1)); + md.update(qop.getBytes(StandardCharsets.ISO_8859_1)); md.update((byte)':'); - md.update(TypeUtil.toString(ha2, 16).getBytes(StringUtil.__ISO_8859_1)); + md.update(TypeUtil.toString(ha2, 16).getBytes(StandardCharsets.ISO_8859_1)); byte[] digest = md.digest(); // check digest @@ -366,11 +366,11 @@ public class DigestPostTest private static String encode(byte[] data) { - StringBuffer buffer = new StringBuffer(); - for (int i = 0; i < data.length; i++) + StringBuilder buffer = new StringBuilder(); + for (byte datum : data) { - buffer.append(Integer.toHexString((data[i] & 0xf0) >>> 4)); - buffer.append(Integer.toHexString(data[i] & 0x0f)); + buffer.append(Integer.toHexString((datum & 0xf0) >>> 4)); + buffer.append(Integer.toHexString(datum & 0x0f)); } return buffer.toString(); } diff --git a/tests/test-loginservice/src/test/java/org/eclipse/jetty/JdbcLoginServiceTest.java b/tests/test-loginservice/src/test/java/org/eclipse/jetty/JdbcLoginServiceTest.java index f177f0d8fe1..16cb256dfb6 100644 --- a/tests/test-loginservice/src/test/java/org/eclipse/jetty/JdbcLoginServiceTest.java +++ b/tests/test-loginservice/src/test/java/org/eclipse/jetty/JdbcLoginServiceTest.java @@ -22,6 +22,7 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.net.URI; +import java.nio.charset.StandardCharsets; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.HttpClient; @@ -29,7 +30,7 @@ import org.eclipse.jetty.client.api.AuthenticationStore; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.util.BasicAuthentication; -import org.eclipse.jetty.client.util.BytesContentProvider; +import org.eclipse.jetty.client.util.BytesRequestContent; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.security.JDBCLoginService; @@ -80,7 +81,7 @@ public class JdbcLoginServiceTest try (FileOutputStream out = new FileOutputStream(content)) { - out.write(_content.getBytes("utf-8")); + out.write(_content.getBytes(StandardCharsets.UTF_8)); } //drop any tables that might have existed @@ -123,7 +124,7 @@ public class JdbcLoginServiceTest Request request = _client.newRequest(_baseUri.resolve("output.txt")); request.method(HttpMethod.PUT); - request.content(new BytesContentProvider(_content.getBytes())); + request.body(new BytesRequestContent(_content.getBytes())); ContentResponse response = request.send(); int responseStatus = response.getStatus(); boolean statusOk = (responseStatus == 200 || responseStatus == 201); @@ -198,7 +199,7 @@ public class JdbcLoginServiceTest Request request = _client.newRequest(_baseUri.resolve("test")); request.method(HttpMethod.POST); - request.content(new BytesContentProvider(_content.getBytes())); + request.body(new BytesRequestContent(_content.getBytes())); ContentResponse response = request.send(); assertEquals(HttpStatus.OK_200, response.getStatus()); assertEquals(_content, _testServer.getTestHandler().getRequestContent()); diff --git a/tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/RequestDispatchedSessionTest.java b/tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/RequestDispatchedSessionTest.java index 881380cc529..935577a4f14 100644 --- a/tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/RequestDispatchedSessionTest.java +++ b/tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/RequestDispatchedSessionTest.java @@ -27,7 +27,7 @@ import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.api.ContentResponse; -import org.eclipse.jetty.client.util.FormContentProvider; +import org.eclipse.jetty.client.util.FormRequestContent; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.server.Server; @@ -96,7 +96,7 @@ public class RequestDispatchedSessionTest ContentResponse response = client.newRequest(server.getURI().resolve("/login")) .method(HttpMethod.POST) - .content(new FormContentProvider(postForm)) + .body(new FormRequestContent(postForm)) .send(); assertThat("Response status", response.getStatus(), is(HttpStatus.OK_200)); } @@ -115,7 +115,6 @@ public class RequestDispatchedSessionTest } request.getSession(true).setAttribute(USERNAME, request.getParameter("username")); request.getRequestDispatcher("/user").forward(request, response); - return; } } } From dbaf0bf69dc70004eb1a6954f89b70e041d6cf52 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Tue, 3 Mar 2020 13:51:33 -0600 Subject: [PATCH 004/101] Issue #4628 - Ensuring checkEnabledModules is required dependency aware Signed-off-by: Joakim Erdfelt --- .../java/org/eclipse/jetty/start/Modules.java | 22 ++++++++++--------- .../org/eclipse/jetty/start/ModulesTest.java | 3 +++ 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/Modules.java b/jetty-start/src/main/java/org/eclipse/jetty/start/Modules.java index bc33e279828..4060d6cd6c3 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/Modules.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/Modules.java @@ -487,17 +487,19 @@ public class Modules implements Iterable _modules.stream().filter(Module::isEnabled).forEach(m -> { // Check dependencies - m.getDepends().forEach(d -> - { - Set providers = getAvailableProviders(d); - if (providers.stream().filter(Module::isEnabled).count() == 0) + m.getDepends().stream() + .filter(Module::isRequiredDependency) + .forEach(d -> { - if (unsatisfied.length() > 0) - unsatisfied.append(','); - unsatisfied.append(m.getName()); - StartLog.error("Module %s requires a module providing %s from one of %s%n", m.getName(), d, providers); - } - }); + Set providers = getAvailableProviders(d); + if (providers.stream().noneMatch(Module::isEnabled)) + { + if (unsatisfied.length() > 0) + unsatisfied.append(','); + unsatisfied.append(m.getName()); + StartLog.error("Module %s requires a module providing %s from one of %s%n", m.getName(), d, providers); + } + }); }); if (unsatisfied.length() > 0) diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/ModulesTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/ModulesTest.java index 461351ceea5..68dbe4a3de0 100644 --- a/jetty-start/src/test/java/org/eclipse/jetty/start/ModulesTest.java +++ b/jetty-start/src/test/java/org/eclipse/jetty/start/ModulesTest.java @@ -234,6 +234,7 @@ public class ModulesTest // Collect active module list List active = modules.getEnabled(); + modules.checkEnabledModules(); // Assert names are correct, and in the right order List expectedNames = new ArrayList<>(); @@ -282,6 +283,7 @@ public class ModulesTest // Collect active module list List active = modules.getEnabled(); + modules.checkEnabledModules(); // Assert names are correct, and in the right order List expectedNames = new ArrayList<>(); @@ -331,6 +333,7 @@ public class ModulesTest // Collect active module list List active = modules.getEnabled(); + modules.checkEnabledModules(); // Assert names are correct, and in the right order List expectedNames = new ArrayList<>(); From 3fcc2edeea526919ee3dd2cc4b442a916b2366ad Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Thu, 19 Mar 2020 10:08:32 +0100 Subject: [PATCH 005/101] Issue #4628 - Add support for conditional module dependencies in jetty-start. Updated after review. Renamed isRequiredModule() -> isConditionalModule() and inverted expressions that were using the method. Signed-off-by: Simone Bordet --- .../java/org/eclipse/jetty/start/Module.java | 6 ++-- .../java/org/eclipse/jetty/start/Modules.java | 28 ++++++++----------- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/Module.java b/jetty-start/src/main/java/org/eclipse/jetty/start/Module.java index fbd4bfc39f1..0d7b8088117 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/Module.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/Module.java @@ -195,14 +195,14 @@ public class Module implements Comparable process(basehome); } - public static boolean isRequiredDependency(String depends) + public static boolean isConditionalDependency(String depends) { - return (depends != null) && (depends.charAt(0) != '?'); + return (depends != null) && (depends.charAt(0) == '?'); } public static String normalizeModuleName(String name) { - if (!isRequiredDependency(name)) + if (isConditionalDependency(name)) return name.substring(1); return name; } diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/Modules.java b/jetty-start/src/main/java/org/eclipse/jetty/start/Modules.java index 4060d6cd6c3..7eb7fc802b5 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/Modules.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/Modules.java @@ -91,8 +91,8 @@ public class Modules implements Iterable _modules.stream() .filter(m -> { - boolean included = all || m.getTags().stream().anyMatch(t -> include.contains(t)); - boolean excluded = m.getTags().stream().anyMatch(t -> exclude.contains(t)); + boolean included = all || m.getTags().stream().anyMatch(include::contains); + boolean excluded = m.getTags().stream().anyMatch(exclude::contains); return included && !excluded; }) .sorted() @@ -135,9 +135,9 @@ public class Modules implements Iterable { parent = Module.normalizeModuleName(parent); System.out.printf(label, parent); - if (!Module.isRequiredDependency(parent)) + if (Module.isConditionalDependency(parent)) { - System.out.print(" [not-required]"); + System.out.print(" [conditional]"); } label = ", %s"; } @@ -219,11 +219,7 @@ public class Modules implements Iterable Module module = new Module(_baseHome, file); _modules.add(module); _names.put(module.getName(), module); - module.getProvides().forEach(n -> - { - _provided.computeIfAbsent(n, k -> new HashSet()).add(module); - }); - + module.getProvides().forEach(n -> _provided.computeIfAbsent(n, k -> new HashSet<>()).add(module)); return module; } catch (Error | RuntimeException t) @@ -258,7 +254,7 @@ public class Modules implements Iterable public List getEnabled() { - List enabled = _modules.stream().filter(m -> m.isEnabled()).collect(Collectors.toList()); + List enabled = _modules.stream().filter(Module::isEnabled).collect(Collectors.toList()); TopologicalSort sort = new TopologicalSort<>(); for (Module module : enabled) @@ -360,7 +356,7 @@ public class Modules implements Iterable StartLog.debug("Enabled module %s depends on %s", module.getName(), module.getDepends()); for (String dependsOnRaw : module.getDepends()) { - boolean isRequired = Module.isRequiredDependency(dependsOnRaw); + boolean isConditional = Module.isConditionalDependency(dependsOnRaw); // Final to allow lambda's below to use name final String dependentModule = Module.normalizeModuleName(dependsOnRaw); @@ -376,7 +372,7 @@ public class Modules implements Iterable if (dependentModule.contains("/")) { Path file = _baseHome.getPath("modules/" + dependentModule + ".mod"); - if (isRequired || Files.exists(file)) + if (!isConditional || Files.exists(file)) { registerModule(file).expandDependencies(_args.getProperties()); providers = _provided.get(dependentModule); @@ -387,10 +383,10 @@ public class Modules implements Iterable continue; } } - // is this a non-required module - if (!isRequired) + // is this a conditional module + if (isConditional) { - StartLog.debug("Skipping non-required module [%s]: doesn't exist", dependentModule); + StartLog.debug("Skipping conditional module [%s]: it does not exist", dependentModule); continue; } // throw an exception (not a dynamic module and a required dependency) @@ -488,7 +484,7 @@ public class Modules implements Iterable { // Check dependencies m.getDepends().stream() - .filter(Module::isRequiredDependency) + .filter(depends -> !Module.isConditionalDependency(depends)) .forEach(d -> { Set providers = getAvailableProviders(d); From 4bc32e314bd0a81f3f3be02bc71c1bd94258c619 Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Tue, 24 Mar 2020 19:51:52 +1100 Subject: [PATCH 006/101] Issue #4235 - communicate reason of OpenID auth failure to error page Signed-off-by: Lachlan Roberts --- .../security/openid/OpenIdAuthenticator.java | 102 +++++++++++------- .../java/org/eclipse/jetty/util/URIUtil.java | 22 ++++ .../org/eclipse/jetty/util/URIUtilTest.java | 23 ++++ 3 files changed, 110 insertions(+), 37 deletions(-) diff --git a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthenticator.java b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthenticator.java index b7c3f610056..a1e80d7a6d1 100644 --- a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthenticator.java +++ b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthenticator.java @@ -22,6 +22,7 @@ import java.io.IOException; import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.security.SecureRandom; +import java.util.Objects; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; @@ -74,10 +75,12 @@ public class OpenIdAuthenticator extends LoginAuthenticator public static final String J_METHOD = "org.eclipse.jetty.security.openid.METHOD"; public static final String CSRF_TOKEN = "org.eclipse.jetty.security.openid.csrf_token"; public static final String J_SECURITY_CHECK = "/j_security_check"; + public static final String ERROR_PARAMETER = "error_description_jetty"; private OpenIdConfiguration _configuration; private String _errorPage; private String _errorPath; + private String _errorQuery; private boolean _alwaysSaveUri; public OpenIdAuthenticator() @@ -148,9 +151,14 @@ public class OpenIdAuthenticator extends LoginAuthenticator } _errorPage = path; _errorPath = path; + _errorQuery = ""; - if (_errorPath.indexOf('?') > 0) - _errorPath = _errorPath.substring(0, _errorPath.indexOf('?')); + int queryIndex = _errorPath.indexOf('?'); + if (queryIndex > 0) + { + _errorPath = _errorPage.substring(0, queryIndex); + _errorQuery = _errorPage.substring(queryIndex + 1); + } } } @@ -230,7 +238,7 @@ public class OpenIdAuthenticator extends LoginAuthenticator { final HttpServletRequest request = (HttpServletRequest)req; final HttpServletResponse response = (HttpServletResponse)res; - final Request baseRequest = Request.getBaseRequest(request); + final Request baseRequest = Objects.requireNonNull(Request.getBaseRequest(request)); final Response baseResponse = baseRequest.getResponse(); String uri = request.getRequestURI(); @@ -246,12 +254,11 @@ public class OpenIdAuthenticator extends LoginAuthenticator try { + // Get the Session. + HttpSession session = request.getSession(); if (request.isRequestedSessionIdFromURL()) { - if (LOG.isDebugEnabled()) - LOG.debug("Session ID should be cookie for OpenID authentication to work"); - - baseResponse.sendRedirect(getRedirectCode(baseRequest.getHttpVersion()), URIUtil.addPaths(request.getContextPath(), _errorPage)); + sendError(request, response, "Session ID must be a cookie to support OpenID authentication"); return Authentication.SEND_FAILURE; } @@ -263,19 +270,16 @@ public class OpenIdAuthenticator extends LoginAuthenticator { // Verify anti-forgery state token String state = request.getParameter("state"); - String antiForgeryToken = (String)request.getSession().getAttribute(CSRF_TOKEN); + String antiForgeryToken = (String)session.getAttribute(CSRF_TOKEN); if (antiForgeryToken == null || !antiForgeryToken.equals(state)) { - LOG.warn("auth failed 403: invalid state parameter"); - if (response != null) - response.sendError(HttpServletResponse.SC_FORBIDDEN); + sendError(request, response, "auth failed: invalid state parameter"); return Authentication.SEND_FAILURE; } // Attempt to login with the provided authCode OpenIdCredentials credentials = new OpenIdCredentials(authCode, getRedirectUri(request), _configuration); UserIdentity user = login(null, credentials, request); - HttpSession session = request.getSession(false); if (user != null) { // Redirect to original request @@ -301,29 +305,13 @@ public class OpenIdAuthenticator extends LoginAuthenticator } } - // not authenticated - if (LOG.isDebugEnabled()) - LOG.debug("OpenId authentication FAILED"); - if (_errorPage == null) - { - if (LOG.isDebugEnabled()) - LOG.debug("auth failed 403"); - if (response != null) - response.sendError(HttpServletResponse.SC_FORBIDDEN); - } - else - { - if (LOG.isDebugEnabled()) - LOG.debug("auth failed {}", _errorPage); - baseResponse.sendRedirect(getRedirectCode(baseRequest.getHttpVersion()), URIUtil.addPaths(request.getContextPath(), _errorPage)); - } - + // Not authenticated. + sendError(request, response, null); return Authentication.SEND_FAILURE; } - // Look for cached authentication - HttpSession session = request.getSession(false); - Authentication authentication = session == null ? null : (Authentication)session.getAttribute(SessionAuthentication.__J_AUTHENTICATED); + // Look for cached authentication in the Session. + Authentication authentication = (Authentication)session.getAttribute(SessionAuthentication.__J_AUTHENTICATED); if (authentication != null) { // Has authentication been revoked? @@ -371,16 +359,15 @@ public class OpenIdAuthenticator extends LoginAuthenticator } } - // if we can't send challenge + // If we can't send challenge. if (DeferredAuthentication.isDeferred(response)) { if (LOG.isDebugEnabled()) - LOG.debug("auth deferred {}", session == null ? null : session.getId()); + LOG.debug("auth deferred {}", session.getId()); return Authentication.UNAUTHENTICATED; } - // remember the current URI - session = (session != null ? session : request.getSession(true)); + // Remember the current URI. synchronized (session) { // But only if it is not set already, or we save every uri that leads to a login redirect @@ -401,7 +388,7 @@ public class OpenIdAuthenticator extends LoginAuthenticator } } - // send the the challenge + // Send the the challenge. String challengeUri = getChallengeUri(request); if (LOG.isDebugEnabled()) LOG.debug("challenge {}->{}", session.getId(), challengeUri); @@ -415,6 +402,47 @@ public class OpenIdAuthenticator extends LoginAuthenticator } } + /** + * Report an error case either by redirecting to the error page if it is defined, otherwise sending a 403 response. + * If the message parameter is not null, a query parameter with a key of {@link #ERROR_PARAMETER} and value of the error + * message will be logged and added to the error redirect URI if the error page is defined. + * @param request the request. + * @param response the response. + * @param message the reason for the error or null. + * @throws IOException if sending the error fails for any reason. + */ + private void sendError(HttpServletRequest request, HttpServletResponse response, String message) throws IOException + { + final Request baseRequest = Request.getBaseRequest(request); + final Response baseResponse = Objects.requireNonNull(baseRequest).getResponse(); + + if (LOG.isDebugEnabled()) + LOG.debug("OpenId authentication FAILED: {}", message); + + if (_errorPage == null) + { + if (LOG.isDebugEnabled()) + LOG.debug("auth failed 403"); + if (response != null) + response.sendError(HttpServletResponse.SC_FORBIDDEN); + } + else + { + if (LOG.isDebugEnabled()) + LOG.debug("auth failed {}", _errorPage); + + String redirectUri = URIUtil.addPaths(request.getContextPath(), _errorPage); + if (message != null) + { + String query = URIUtil.combineQueryParams(ERROR_PARAMETER + "=" + UrlEncoded.encodeString(message), _errorQuery); + redirectUri = URIUtil.addPaths(request.getContextPath(), _errorPath) + "?" + query; + baseResponse.sendRedirect(getRedirectCode(baseRequest.getHttpVersion()), redirectUri); + } + + baseResponse.sendRedirect(getRedirectCode(baseRequest.getHttpVersion()), redirectUri); + } + } + public boolean isJSecurityCheck(String uri) { int jsc = uri.indexOf(J_SECURITY_CHECK); diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java b/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java index 1df98270eef..f796d343cec 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java @@ -1305,6 +1305,28 @@ public class URIUtil return URI.create(buf.toString()); } + /** + * Combine two query strings into one. Each query string should not contain the beginning '?' character, but + * may contain multiple parameters separated by the '{@literal &}' character. + * @param query1 the first query string. + * @param query2 the second query string. + * @return the combination of the two query strings. + */ + public static String combineQueryParams(String query1, String query2) + { + StringBuilder queryBuilder = new StringBuilder(); + if (!StringUtil.isEmpty(query1)) + { + queryBuilder.append(query1); + if (!StringUtil.isEmpty(query2)) + queryBuilder.append("&"); + } + + if (!StringUtil.isEmpty(query2)) + queryBuilder.append(query2); + return queryBuilder.toString(); + } + public static URI getJarSource(URI uri) { try diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/URIUtilTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/URIUtilTest.java index 8e40744fa96..7ab318e10ce 100644 --- a/jetty-util/src/test/java/org/eclipse/jetty/util/URIUtilTest.java +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/URIUtilTest.java @@ -40,6 +40,7 @@ import org.eclipse.jetty.toolchain.test.jupiter.WorkDir; import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; +import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.OS; import org.junit.jupiter.api.extension.ExtendWith; @@ -709,4 +710,26 @@ public class URIUtilTest { assertThat(URIUtil.getUriLastPathSegment(uri), is(expectedName)); } + + public static Stream addQueryParameterSource() + { + final String newQueryParam = "newParam=11"; + return Stream.of( + Arguments.of(null, newQueryParam, is(newQueryParam)), + Arguments.of(newQueryParam, null, is(newQueryParam)), + Arguments.of("", newQueryParam, is(newQueryParam)), + Arguments.of(newQueryParam, "", is(newQueryParam)), + Arguments.of("existingParam=3", newQueryParam, is("existingParam=3&" + newQueryParam)), + Arguments.of(newQueryParam, "existingParam=3", is(newQueryParam + "&existingParam=3")), + Arguments.of("existingParam1=value1&existingParam2=value2", newQueryParam, is("existingParam1=value1&existingParam2=value2&" + newQueryParam)), + Arguments.of(newQueryParam, "existingParam1=value1&existingParam2=value2", is(newQueryParam + "&existingParam1=value1&existingParam2=value2")) + ); + } + + @ParameterizedTest + @MethodSource("addQueryParameterSource") + public void testAddQueryParam(String param1, String param2, Matcher matcher) + { + assertThat(URIUtil.combineQueryParams(param1, param2), matcher); + } } From 9fec1f43e07692f9cb80ae9390ff457e0421d91d Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Tue, 24 Mar 2020 16:42:53 +0100 Subject: [PATCH 007/101] Issue #4400 - Review HttpClient's ContentProvider. Improved javadocs and comment as per initial review. Signed-off-by: Simone Bordet --- .../org/eclipse/jetty/client/api/Request.java | 22 ++++++++++ .../client/util/AbstractRequestContent.java | 42 ++++++++++++++++++- 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java index 51c2e6ff22b..53e67aab4f4 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java @@ -641,6 +641,28 @@ public interface Request * and return a {@link Subscription} as the link between producer and consumer.

*

Content producers must notify content to the consumer only if there is demand.

*

Content consumers can generate demand for content by invoking {@link Subscription#demand()}.

+ *

Content production must follow this algorithm:

+ *
    + *
  • the first time content is demanded + *
      + *
    • when the content is not available => produce an empty content
    • + *
    • when the content is available: + *
        + *
      • when {@code emitInitialContent == false} => produce an empty content
      • + *
      • when {@code emitInitialContent == true} => produce the content
      • + *
      + *
    • + *
    + *
  • + *
  • the second and subsequent times content is demanded + *
      + *
    • when the content is not available => do not produce content
    • + *
    • when the content is available => produce the content
    • + *
    + *
  • + *
+ * + * @see #subscribe(Consumer, boolean) */ public interface Content { diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/AbstractRequestContent.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/AbstractRequestContent.java index 42857585225..2b6019cf873 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/AbstractRequestContent.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/AbstractRequestContent.java @@ -28,6 +28,10 @@ import org.eclipse.jetty.util.thread.AutoLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + *

Partial implementation of {@link Request.Content}.

+ *

Manages a single subscription at a time (multiple simultaneous subscriptions are not allowed).

+ */ public abstract class AbstractRequestContent implements Request.Content { private static final Logger LOG = LoggerFactory.getLogger(AbstractRequestContent.class); @@ -85,13 +89,19 @@ public abstract class AbstractRequestContent implements Request.Content subscription.fail(failure); } + /** + *

Partial implementation of {@code Subscription}.

+ *

Implements the algorithm described in {@link Request.Content}.

+ */ public abstract class AbstractSubscription implements Subscription { private final Consumer consumer; private final boolean emitInitialContent; private Throwable failure; private int demand; + // Whether content production was stalled because there was no demand. private boolean stalled; + // Whether the first content has been produced. private boolean committed; public AbstractSubscription(Consumer consumer, boolean emitInitialContent, Throwable failure) @@ -157,6 +167,32 @@ public abstract class AbstractRequestContent implements Request.Content } } + /** + *

Subclasses implement this method to produce content, + * without worrying about demand or exception handling.

+ *

Typical implementation (pseudo code):

+ *
+         * protected boolean produceContent(Producer producer) throws Exception
+         * {
+         *     // Step 1: try to produce content, exceptions may be thrown during production
+         *     //  (for example, producing content reading from an InputStream may throw).
+         *
+         *     // Step 2A: content could be produced.
+         *     ByteBuffer buffer = ...;
+         *     boolean last = ...;
+         *     Callback callback = ...;
+         *     return producer.produce(buffer, last, callback);
+         *
+         *     // Step 2B: content could not be produced.
+         *     //  (for example it is not available yet)
+         *     return false;
+         * }
+         * 
+ * + * @param producer the producer to notify when content can be produced + * @return whether content production should continue + * @throws Exception when content production fails + */ protected abstract boolean produceContent(Producer producer) throws Exception; @Override @@ -232,14 +268,16 @@ public abstract class AbstractRequestContent implements Request.Content { int demand; boolean stalled; + boolean committed; try (AutoLock ignored = lock.lock()) { demand = this.demand; stalled = this.stalled; + committed = this.committed; } - return String.format("%s.%s@%x[demand=%d,stalled=%b]", + return String.format("%s.%s@%x[demand=%d,stalled=%b,committed=%b,emitInitial=%b]", getClass().getEnclosingClass().getSimpleName(), - getClass().getSimpleName(), hashCode(), demand, stalled); + getClass().getSimpleName(), hashCode(), demand, stalled, committed, emitInitialContent); } } From f3bd6611f468b28ee655bea2aec92122348ee40f Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Tue, 24 Mar 2020 08:41:32 -0500 Subject: [PATCH 008/101] Fix OWB (Open WebBeans) tests. + OWB needs init-parameter to allow it's own ServletContainerInitializer found at org.apache.webbeans.servlet.WebBeansConfigurationListener$Auto to execute and properly add the required Listener for OWB to function. + See OWB-1296 Signed-off-by: Joakim Erdfelt --- .../src/test/resources/jetty-logging.properties | 1 + tests/test-webapps/test-owb-cdi-webapp/pom.xml | 2 +- .../services/javax.servlet.ServletContainerInitializer | 1 - .../src/main/webapp/WEB-INF/jetty-env.xml | 9 ++++++++- 4 files changed, 10 insertions(+), 3 deletions(-) delete mode 100644 tests/test-webapps/test-owb-cdi-webapp/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer diff --git a/tests/test-distribution/src/test/resources/jetty-logging.properties b/tests/test-distribution/src/test/resources/jetty-logging.properties index 56cc73e5d68..6c7059f135c 100644 --- a/tests/test-distribution/src/test/resources/jetty-logging.properties +++ b/tests/test-distribution/src/test/resources/jetty-logging.properties @@ -1,2 +1,3 @@ # Jetty Logging using jetty-slf4j-impl +org.eclipse.jetty.logging.appender.MESSAGE_ESCAPE=false #org.eclipse.jetty.LEVEL=DEBUG diff --git a/tests/test-webapps/test-owb-cdi-webapp/pom.xml b/tests/test-webapps/test-owb-cdi-webapp/pom.xml index 7540b59c8dd..42b5630fbd5 100644 --- a/tests/test-webapps/test-owb-cdi-webapp/pom.xml +++ b/tests/test-webapps/test-owb-cdi-webapp/pom.xml @@ -64,7 +64,7 @@ org.apache.openwebbeans openwebbeans-jetty9 - 2.0.11 + 2.0.15 diff --git a/tests/test-webapps/test-owb-cdi-webapp/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer b/tests/test-webapps/test-owb-cdi-webapp/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer deleted file mode 100644 index f164a18c5a8..00000000000 --- a/tests/test-webapps/test-owb-cdi-webapp/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer +++ /dev/null @@ -1 +0,0 @@ -org.eclipse.jetty.cdi.owb.OwbServletContainerInitializer \ No newline at end of file diff --git a/tests/test-webapps/test-owb-cdi-webapp/src/main/webapp/WEB-INF/jetty-env.xml b/tests/test-webapps/test-owb-cdi-webapp/src/main/webapp/WEB-INF/jetty-env.xml index 99f1d2befa7..44a3d0cb030 100644 --- a/tests/test-webapps/test-owb-cdi-webapp/src/main/webapp/WEB-INF/jetty-env.xml +++ b/tests/test-webapps/test-owb-cdi-webapp/src/main/webapp/WEB-INF/jetty-env.xml @@ -1,9 +1,16 @@ + + + openwebbeans.web.sci.active + true + + - + BeanManager From 0f2ddc8c9f1ab0e677e3b6999992b2d88a0bc838 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Thu, 26 Mar 2020 17:46:59 +0100 Subject: [PATCH 009/101] Issue #4400 - Review HttpClient's ContentProvider. Review updates. * Now AbstractRequestContent supports multiple subscriptions. * Reviewed abort() path to fail the content and the subscription and notify FailureListener sequentially with other listeners. Signed-off-by: Simone Bordet --- .../eclipse/jetty/client/HttpExchange.java | 7 ++ .../org/eclipse/jetty/client/HttpRequest.java | 4 -- .../org/eclipse/jetty/client/HttpSender.java | 69 +++++++++---------- .../client/util/AbstractRequestContent.java | 23 ++----- .../client/util/MultiPartRequestContent.java | 9 ++- .../util/RequestContentBehaviorTest.java | 57 +++++++++++---- 6 files changed, 98 insertions(+), 71 deletions(-) diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpExchange.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpExchange.java index 1a743f496f9..75a89dcefe4 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpExchange.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpExchange.java @@ -20,6 +20,7 @@ package org.eclipse.jetty.client; import java.util.List; +import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.api.Result; import org.slf4j.Logger; @@ -237,6 +238,12 @@ public class HttpExchange // We failed this exchange, deal with it. + // Applications could be blocked providing + // request content, notify them of the failure. + Request.Content body = request.getBody(); + if (abortRequest && body != null) + body.fail(failure); + // Case #1: exchange was in the destination queue. if (destination.remove(this)) { diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java index e0919bc3f0d..92ec7e7ae8b 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java @@ -824,11 +824,7 @@ public class HttpRequest implements Request public boolean abort(Throwable cause) { if (aborted.compareAndSet(null, Objects.requireNonNull(cause))) - { - if (content != null) - content.fail(cause); return conversation.abort(cause); - } return false; } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java index de4c67c85a5..0fe281890cd 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java @@ -52,9 +52,9 @@ public abstract class HttpSender private final ContentConsumer consumer = new ContentConsumer(); private final AtomicReference requestState = new AtomicReference<>(RequestState.QUEUED); + private final AtomicReference failure = new AtomicReference<>(); private final HttpChannel channel; private Request.Content.Subscription subscription; - private Throwable failure; protected HttpSender(HttpChannel channel) { @@ -78,13 +78,6 @@ public abstract class HttpSender public void send(HttpExchange exchange) { - Request request = exchange.getRequest(); - Request.Content body = request.getBody(); - - consumer.exchange = exchange; - consumer.expect100 = expects100Continue(request); - subscription = body.subscribe(consumer, !consumer.expect100); - if (!queuedToBegin(exchange)) return; @@ -110,10 +103,16 @@ public abstract class HttpSender RequestNotifier notifier = getHttpChannel().getHttpDestination().getRequestNotifier(); notifier.notifyBegin(request); + Request.Content body = request.getBody(); + + consumer.exchange = exchange; + consumer.expect100 = expects100Continue(request); + subscription = body.subscribe(consumer, !consumer.expect100); + if (updateRequestState(RequestState.TRANSIENT, RequestState.BEGIN)) return true; - terminateRequest(exchange); + abortRequest(exchange); return false; } @@ -131,7 +130,7 @@ public abstract class HttpSender if (updateRequestState(RequestState.TRANSIENT, RequestState.HEADERS)) return true; - terminateRequest(exchange); + abortRequest(exchange); return false; } @@ -149,7 +148,7 @@ public abstract class HttpSender if (updateRequestState(RequestState.TRANSIENT, RequestState.COMMIT)) return true; - terminateRequest(exchange); + abortRequest(exchange); return false; } @@ -173,7 +172,7 @@ public abstract class HttpSender if (updateRequestState(RequestState.TRANSIENT, RequestState.CONTENT)) return true; - terminateRequest(exchange); + abortRequest(exchange); return false; } default: @@ -264,13 +263,23 @@ public abstract class HttpSender } } - private void terminateRequest(HttpExchange exchange) + private void abortRequest(HttpExchange exchange) { - // In abort(), the state is updated before the failure is recorded - // to avoid to overwrite it, so here we may read a null failure. - Throwable failure = this.failure; - if (failure == null) - failure = new HttpRequestException("Concurrent failure", exchange.getRequest()); + Throwable failure = this.failure.get(); + + if (subscription != null) + subscription.fail(failure); + + dispose(); + + Request request = exchange.getRequest(); + if (LOG.isDebugEnabled()) + LOG.debug("Request abort {} {} on {}: {}", request, exchange, getHttpChannel(), failure); + HttpDestination destination = getHttpChannel().getHttpDestination(); + destination.getRequestNotifier().notifyFailure(request, failure); + + // Mark atomically the request as terminated, with + // respect to concurrency between request and response. Result result = exchange.terminateRequest(); terminateRequest(exchange, failure, result); } @@ -353,8 +362,11 @@ public abstract class HttpSender public boolean abort(HttpExchange exchange, Throwable failure) { + // Store only the first failure. + this.failure.compareAndSet(null, failure); + // Update the state to avoid more request processing. - boolean terminate; + boolean abort; while (true) { RequestState current = requestState.get(); @@ -366,28 +378,15 @@ public abstract class HttpSender { if (updateRequestState(current, RequestState.FAILURE)) { - terminate = current != RequestState.TRANSIENT; + abort = current != RequestState.TRANSIENT; break; } } } - this.failure = failure; - - dispose(); - - Request request = exchange.getRequest(); - if (LOG.isDebugEnabled()) - LOG.debug("Request abort {} {} on {}: {}", request, exchange, getHttpChannel(), failure); - HttpDestination destination = getHttpChannel().getHttpDestination(); - destination.getRequestNotifier().notifyFailure(request, failure); - - if (terminate) + if (abort) { - // Mark atomically the request as terminated, with - // respect to concurrency between request and response. - Result result = exchange.terminateRequest(); - terminateRequest(exchange, failure, result); + abortRequest(exchange); return true; } else diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/AbstractRequestContent.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/AbstractRequestContent.java index 2b6019cf873..a4cd2469f9e 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/AbstractRequestContent.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/AbstractRequestContent.java @@ -18,7 +18,6 @@ package org.eclipse.jetty.client.util; -import java.io.EOFException; import java.nio.ByteBuffer; import org.eclipse.jetty.client.api.Request; @@ -38,7 +37,6 @@ public abstract class AbstractRequestContent implements Request.Content private final AutoLock lock = new AutoLock(); private final String contentType; - private Subscription subscription; private Throwable failure; protected AbstractRequestContent(String contentType) @@ -55,20 +53,15 @@ public abstract class AbstractRequestContent implements Request.Content @Override public Subscription subscribe(Consumer consumer, boolean emitInitialContent) { - Subscription oldSubscription; - Subscription newSubscription; + Throwable failure; try (AutoLock ignored = lock.lock()) { - if (subscription != null && !isReproducible()) - throw new IllegalStateException("Multiple subscriptions not supported on " + this); - oldSubscription = subscription; - newSubscription = subscription = newSubscription(consumer, emitInitialContent, failure); + failure = this.failure; } - if (oldSubscription != null) - oldSubscription.fail(new EOFException("Content replay")); + Subscription subscription = newSubscription(consumer, emitInitialContent, failure); if (LOG.isDebugEnabled()) - LOG.debug("Content subscription for {}: {}", this, consumer); - return newSubscription; + LOG.debug("Content subscription for {}: {}", subscription, consumer); + return subscription; } protected abstract Subscription newSubscription(Consumer consumer, boolean emitInitialContent, Throwable failure); @@ -76,17 +69,11 @@ public abstract class AbstractRequestContent implements Request.Content @Override public void fail(Throwable failure) { - Subscription subscription = null; try (AutoLock ignored = lock.lock()) { if (this.failure == null) - { this.failure = failure; - subscription = this.subscription; - } } - if (subscription != null) - subscription.fail(failure); } /** diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/MultiPartRequestContent.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/MultiPartRequestContent.java index 9c020c780db..866e3f5c754 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/MultiPartRequestContent.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/MultiPartRequestContent.java @@ -27,6 +27,7 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Random; +import java.util.stream.IntStream; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.http.HttpField; @@ -115,7 +116,8 @@ public class MultiPartRequestContent extends AbstractRequestContent implements C @Override protected Subscription newSubscription(Consumer consumer, boolean emitInitialContent, Throwable failure) { - length = calculateLength(); + if (closed) + length = calculateLength(); return new SubscriptionImpl(consumer, emitInitialContent, failure); } @@ -367,7 +369,10 @@ public class MultiPartRequestContent extends AbstractRequestContent implements C @Override public void onFailure(Throwable failure) { - parts.stream() + if (subscription != null) + subscription.fail(failure); + IntStream.range(index, parts.size()) + .mapToObj(parts::get) .map(part -> part.content) .forEach(content -> content.fail(failure)); } diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/util/RequestContentBehaviorTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/util/RequestContentBehaviorTest.java index 45c3ff305af..c4427e889d0 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/util/RequestContentBehaviorTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/util/RequestContentBehaviorTest.java @@ -23,9 +23,9 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.StandardOpenOption; import java.util.Arrays; import java.util.List; +import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -39,6 +39,7 @@ import org.eclipse.jetty.util.Fields; import org.eclipse.jetty.util.IO; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -56,21 +57,23 @@ public class RequestContentBehaviorTest @BeforeAll public static void prepare() throws IOException { - emptyFile = MavenTestingUtils.getTargetTestingPath().resolve("empty.txt"); - Files.newOutputStream(emptyFile, StandardOpenOption.CREATE).close(); - smallFile = MavenTestingUtils.getTargetTestingPath().resolve("small.txt"); - try (var s = Files.newOutputStream(smallFile, StandardOpenOption.CREATE)) - { - byte[] bytes = new byte[64]; - Arrays.fill(bytes, (byte)'#'); - s.write(bytes); - } + Path testPath = MavenTestingUtils.getTargetTestingPath(); + Files.createDirectories(testPath); + emptyFile = testPath.resolve("empty.txt"); + Files.write(emptyFile, new byte[0]); + smallFile = testPath.resolve("small.txt"); + byte[] bytes = new byte[64]; + Arrays.fill(bytes, (byte)'#'); + Files.write(smallFile, bytes); } @AfterAll public static void dispose() throws IOException { - Files.delete(emptyFile); + if (smallFile != null) + Files.delete(smallFile); + if (emptyFile != null) + Files.delete(emptyFile); } public static List emptyContents() throws IOException @@ -290,7 +293,7 @@ public class RequestContentBehaviorTest assertEquals(1, notified.get()); - content.fail(testFailure); + subscription.fail(testFailure); subscription.demand(); assertEquals(1, notified.get()); @@ -339,4 +342,34 @@ public class RequestContentBehaviorTest assertNotNull(failure); assertEquals(1, failure.getSuppressed().length); } + + @Test + public void testSameContentMultipleSubscriptions() throws Exception + { + byte[] bytes = new byte[64]; + new Random().nextBytes(bytes); + Request.Content content = new BytesRequestContent(bytes); + + CountDownLatch latch1 = new CountDownLatch(1); + Request.Content.Subscription subscription1 = content.subscribe((buffer, last, callback) -> + { + if (last) + latch1.countDown(); + }, true); + + CountDownLatch latch2 = new CountDownLatch(1); + Request.Content.Subscription subscription2 = content.subscribe((buffer, last, callback) -> + { + if (last) + latch2.countDown(); + }, true); + + // Initial demand. + subscription1.demand(); + assertTrue(latch1.await(5, TimeUnit.SECONDS)); + + // Initial demand. + subscription2.demand(); + assertTrue(latch2.await(5, TimeUnit.SECONDS)); + } } From 708115f6095bd65052dbf5ff93fddcf9b1a16053 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Fri, 27 Mar 2020 15:33:21 +0100 Subject: [PATCH 010/101] Issue #4400 - Review HttpClient's ContentProvider. Review updates. * Updated AbstractRequestContent (and subclasses) failure handling. * Updated MultiPartRequestContent failure handling. Signed-off-by: Simone Bordet --- .../client/util/AbstractRequestContent.java | 24 ++-------- .../client/util/ByteBufferRequestContent.java | 8 ++-- .../client/util/BytesRequestContent.java | 8 ++-- .../util/InputStreamRequestContent.java | 11 +++-- .../client/util/MultiPartRequestContent.java | 31 +++++++----- .../jetty/client/util/PathRequestContent.java | 15 ++++-- .../client/HttpClientAuthenticationTest.java | 8 ++-- .../eclipse/jetty/client/HttpClientTest.java | 4 +- .../client/util/MultiPartContentTest.java | 4 +- .../util/RequestContentBehaviorTest.java | 48 +++---------------- 10 files changed, 62 insertions(+), 99 deletions(-) diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/AbstractRequestContent.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/AbstractRequestContent.java index a4cd2469f9e..7b90acb084d 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/AbstractRequestContent.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/AbstractRequestContent.java @@ -29,7 +29,6 @@ import org.slf4j.LoggerFactory; /** *

Partial implementation of {@link Request.Content}.

- *

Manages a single subscription at a time (multiple simultaneous subscriptions are not allowed).

*/ public abstract class AbstractRequestContent implements Request.Content { @@ -37,7 +36,6 @@ public abstract class AbstractRequestContent implements Request.Content private final AutoLock lock = new AutoLock(); private final String contentType; - private Throwable failure; protected AbstractRequestContent(String contentType) { @@ -53,28 +51,13 @@ public abstract class AbstractRequestContent implements Request.Content @Override public Subscription subscribe(Consumer consumer, boolean emitInitialContent) { - Throwable failure; - try (AutoLock ignored = lock.lock()) - { - failure = this.failure; - } - Subscription subscription = newSubscription(consumer, emitInitialContent, failure); + Subscription subscription = newSubscription(consumer, emitInitialContent); if (LOG.isDebugEnabled()) LOG.debug("Content subscription for {}: {}", subscription, consumer); return subscription; } - protected abstract Subscription newSubscription(Consumer consumer, boolean emitInitialContent, Throwable failure); - - @Override - public void fail(Throwable failure) - { - try (AutoLock ignored = lock.lock()) - { - if (this.failure == null) - this.failure = failure; - } - } + protected abstract Subscription newSubscription(Consumer consumer, boolean emitInitialContent); /** *

Partial implementation of {@code Subscription}.

@@ -91,11 +74,10 @@ public abstract class AbstractRequestContent implements Request.Content // Whether the first content has been produced. private boolean committed; - public AbstractSubscription(Consumer consumer, boolean emitInitialContent, Throwable failure) + public AbstractSubscription(Consumer consumer, boolean emitInitialContent) { this.consumer = consumer; this.emitInitialContent = emitInitialContent; - this.failure = failure; this.stalled = true; } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/ByteBufferRequestContent.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/ByteBufferRequestContent.java index c5c9f35f36b..36362acfc3f 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/ByteBufferRequestContent.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/ByteBufferRequestContent.java @@ -63,18 +63,18 @@ public class ByteBufferRequestContent extends AbstractRequestContent } @Override - protected Subscription newSubscription(Consumer consumer, boolean emitInitialContent, Throwable failure) + protected Subscription newSubscription(Consumer consumer, boolean emitInitialContent) { - return new SubscriptionImpl(consumer, emitInitialContent, failure); + return new SubscriptionImpl(consumer, emitInitialContent); } private class SubscriptionImpl extends AbstractSubscription { private int index; - private SubscriptionImpl(Consumer consumer, boolean emitInitialContent, Throwable failure) + private SubscriptionImpl(Consumer consumer, boolean emitInitialContent) { - super(consumer, emitInitialContent, failure); + super(consumer, emitInitialContent); } @Override diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/BytesRequestContent.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/BytesRequestContent.java index c1e2e09ba42..43001d607ce 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/BytesRequestContent.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/BytesRequestContent.java @@ -60,18 +60,18 @@ public class BytesRequestContent extends AbstractRequestContent } @Override - protected Subscription newSubscription(Consumer consumer, boolean emitInitialContent, Throwable failure) + protected Subscription newSubscription(Consumer consumer, boolean emitInitialContent) { - return new SubscriptionImpl(consumer, emitInitialContent, failure); + return new SubscriptionImpl(consumer, emitInitialContent); } private class SubscriptionImpl extends AbstractSubscription { private int index; - private SubscriptionImpl(Consumer consumer, boolean emitInitialContent, Throwable failure) + private SubscriptionImpl(Consumer consumer, boolean emitInitialContent) { - super(consumer, emitInitialContent, failure); + super(consumer, emitInitialContent); } @Override diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamRequestContent.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamRequestContent.java index 8a5e72994ce..586b3f07e4d 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamRequestContent.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamRequestContent.java @@ -42,6 +42,7 @@ public class InputStreamRequestContent extends AbstractRequestContent private final InputStream stream; private final int bufferSize; + private Subscription subscription; public InputStreamRequestContent(InputStream stream) { @@ -66,9 +67,11 @@ public class InputStreamRequestContent extends AbstractRequestContent } @Override - protected Subscription newSubscription(Consumer consumer, boolean emitInitialContent, Throwable failure) + protected Subscription newSubscription(Consumer consumer, boolean emitInitialContent) { - return new SubscriptionImpl(consumer, emitInitialContent, failure); + if (subscription != null) + throw new IllegalStateException("Multiple subscriptions not supported on " + this); + return subscription = new SubscriptionImpl(consumer, emitInitialContent); } @Override @@ -96,9 +99,9 @@ public class InputStreamRequestContent extends AbstractRequestContent { private boolean terminated; - private SubscriptionImpl(Consumer consumer, boolean emitInitialContent, Throwable failure) + private SubscriptionImpl(Consumer consumer, boolean emitInitialContent) { - super(consumer, emitInitialContent, failure); + super(consumer, emitInitialContent); } @Override diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/MultiPartRequestContent.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/MultiPartRequestContent.java index 866e3f5c754..7acfdb20c89 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/MultiPartRequestContent.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/MultiPartRequestContent.java @@ -27,7 +27,6 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Random; -import java.util.stream.IntStream; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.http.HttpField; @@ -86,7 +85,8 @@ public class MultiPartRequestContent extends AbstractRequestContent implements C private final ByteBuffer onlyBoundary; private final ByteBuffer lastBoundary; private long length; - private volatile boolean closed; + private boolean closed; + private Subscription subscription; public MultiPartRequestContent() { @@ -114,11 +114,22 @@ public class MultiPartRequestContent extends AbstractRequestContent implements C } @Override - protected Subscription newSubscription(Consumer consumer, boolean emitInitialContent, Throwable failure) + protected Subscription newSubscription(Consumer consumer, boolean emitInitialContent) { - if (closed) - length = calculateLength(); - return new SubscriptionImpl(consumer, emitInitialContent, failure); + if (!closed) + throw new IllegalStateException("MultiPartRequestContent must be closed before sending the request"); + if (subscription != null) + throw new IllegalStateException("Multiple subscriptions not supported on " + this); + length = calculateLength(); + return subscription = new SubscriptionImpl(consumer, emitInitialContent); + } + + @Override + public void fail(Throwable failure) + { + parts.stream() + .map(part -> part.content) + .forEach(content -> content.fail(failure)); } /** @@ -284,9 +295,9 @@ public class MultiPartRequestContent extends AbstractRequestContent implements C private int index; private Subscription subscription; - private SubscriptionImpl(Consumer consumer, boolean emitInitialContent, Throwable failure) + private SubscriptionImpl(Consumer consumer, boolean emitInitialContent) { - super(consumer, emitInitialContent, failure); + super(consumer, emitInitialContent); } @Override @@ -371,10 +382,6 @@ public class MultiPartRequestContent extends AbstractRequestContent implements C { if (subscription != null) subscription.fail(failure); - IntStream.range(index, parts.size()) - .mapToObj(parts::get) - .map(part -> part.content) - .forEach(content -> content.fail(failure)); } } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/PathRequestContent.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/PathRequestContent.java index 916ad2a2954..20737ab2dec 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/PathRequestContent.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/PathRequestContent.java @@ -114,9 +114,9 @@ public class PathRequestContent extends AbstractRequestContent } @Override - protected Subscription newSubscription(Consumer consumer, boolean emitInitialContent, Throwable failure) + protected Subscription newSubscription(Consumer consumer, boolean emitInitialContent) { - return new SubscriptionImpl(consumer, emitInitialContent, failure); + return new SubscriptionImpl(consumer, emitInitialContent); } private class SubscriptionImpl extends AbstractSubscription @@ -124,9 +124,9 @@ public class PathRequestContent extends AbstractRequestContent private ReadableByteChannel channel; private long readTotal; - private SubscriptionImpl(Consumer consumer, boolean emitInitialContent, Throwable failure) + private SubscriptionImpl(Consumer consumer, boolean emitInitialContent) { - super(consumer, emitInitialContent, failure); + super(consumer, emitInitialContent); } @Override @@ -166,5 +166,12 @@ public class PathRequestContent extends AbstractRequestContent if (bufferPool != null) bufferPool.release(buffer); } + + @Override + public void fail(Throwable failure) + { + super.fail(failure); + IO.close(channel); + } } } diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java index 7545b50e6cd..d054243ebbb 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java @@ -819,18 +819,18 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest } @Override - protected Subscription newSubscription(Consumer consumer, boolean emitInitialContent, Throwable failure) + protected Subscription newSubscription(Consumer consumer, boolean emitInitialContent) { - return new SubscriptionImpl(consumer, emitInitialContent, failure); + return new SubscriptionImpl(consumer, emitInitialContent); } private class SubscriptionImpl extends AbstractSubscription { private int index; - public SubscriptionImpl(Consumer consumer, boolean emitInitialContent, Throwable failure) + public SubscriptionImpl(Consumer consumer, boolean emitInitialContent) { - super(consumer, emitInitialContent, failure); + super(consumer, emitInitialContent); } @Override diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java index b7409702436..fe2efa598db 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java @@ -631,9 +631,9 @@ public class HttpClientTest extends AbstractHttpClientServerTest .body(new AbstractRequestContent("application/octet-stream") { @Override - protected Subscription newSubscription(Consumer consumer, boolean emitInitialContent, Throwable failure) + protected Subscription newSubscription(Consumer consumer, boolean emitInitialContent) { - return new AbstractSubscription(consumer, emitInitialContent, failure) + return new AbstractSubscription(consumer, emitInitialContent) { private int count; diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/util/MultiPartContentTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/util/MultiPartContentTest.java index af3fd7611bb..947fce50d17 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/util/MultiPartContentTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/util/MultiPartContentTest.java @@ -408,6 +408,8 @@ public class MultiPartContentTest extends AbstractHttpClientServerTest multiPart.addFieldPart("field", fieldContent, null); AsyncRequestContent fileContent = new AsyncRequestContent(); multiPart.addFilePart("file", "fileName", fileContent, null); + multiPart.close(); + CountDownLatch responseLatch = new CountDownLatch(1); client.newRequest("localhost", connector.getLocalPort()) .scheme(scenario.getScheme()) @@ -432,8 +434,6 @@ public class MultiPartContentTest extends AbstractHttpClientServerTest fieldContent.offer(encoding.encode(value)); fieldContent.close(); - multiPart.close(); - assertTrue(responseLatch.await(5, TimeUnit.SECONDS)); } diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/util/RequestContentBehaviorTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/util/RequestContentBehaviorTest.java index c4427e889d0..b7ee58d3df3 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/util/RequestContentBehaviorTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/util/RequestContentBehaviorTest.java @@ -25,7 +25,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.Arrays; import java.util.List; -import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -39,7 +38,6 @@ import org.eclipse.jetty.util.Fields; import org.eclipse.jetty.util.IO; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -48,6 +46,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; public class RequestContentBehaviorTest { @@ -171,6 +170,7 @@ public class RequestContentBehaviorTest { { addFieldPart("field", new StringRequestContent("*".repeat(64)), null); + close(); } }, new PathRequestContent(smallFile), @@ -229,41 +229,6 @@ public class RequestContentBehaviorTest assertTrue(latch.await(5, TimeUnit.SECONDS)); } - @ParameterizedTest - @MethodSource("smallContents") - public void testSmallContentFailedBeforeSubscription(Request.Content content) - { - Throwable testFailure = new Throwable("test_failure"); - content.fail(testFailure); - - AtomicInteger notified = new AtomicInteger(); - AtomicReference failureRef = new AtomicReference<>(); - Request.Content.Subscription subscription = content.subscribe(new Request.Content.Consumer() - { - @Override - public void onContent(ByteBuffer buffer, boolean last, Callback callback) - { - notified.getAndIncrement(); - } - - @Override - public void onFailure(Throwable error) - { - testFailure.addSuppressed(new Throwable("suppressed")); - failureRef.compareAndSet(null, error); - } - }, true); - - // Initial demand. - subscription.demand(); - - assertEquals(0, notified.get()); - Throwable failure = failureRef.get(); - assertNotNull(failure); - assertSame(testFailure, failure); - assertEquals(1, failure.getSuppressed().length); - } - @ParameterizedTest @MethodSource("smallContents") public void testSmallContentFailedAfterFirstDemand(Request.Content content) @@ -343,12 +308,11 @@ public class RequestContentBehaviorTest assertEquals(1, failure.getSuppressed().length); } - @Test - public void testSameContentMultipleSubscriptions() throws Exception + @ParameterizedTest + @MethodSource("smallContents") + public void testReproducibleContentCanHaveMultipleSubscriptions(Request.Content content) throws Exception { - byte[] bytes = new byte[64]; - new Random().nextBytes(bytes); - Request.Content content = new BytesRequestContent(bytes); + assumeTrue(content.isReproducible()); CountDownLatch latch1 = new CountDownLatch(1); Request.Content.Subscription subscription1 = content.subscribe((buffer, last, callback) -> From 34b0726eb1993aaa1712d2b44be0f8d56940bf76 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Mon, 30 Mar 2020 12:20:23 +0200 Subject: [PATCH 011/101] A little bit of work on the new Jetty 10 documentation. Signed-off-by: Simone Bordet --- jetty-documentation/pom.xml | 1 - .../embedded-guide/.asciidoctorconfig | 3 ++ .../client/client-concepts.adoc | 30 +++++++++---------- .../main/asciidoc/embedded-guide/io-arch.adoc | 25 +++++++++------- ...Snippets.java => SelectorManagerDocs.java} | 6 ++-- ...Snippets.java => ClientConnectorDocs.java} | 6 ++-- 6 files changed, 40 insertions(+), 31 deletions(-) create mode 100644 jetty-documentation/src/main/asciidoc/embedded-guide/.asciidoctorconfig rename jetty-documentation/src/main/java/embedded/{SelectorManagerDocSnippets.java => SelectorManagerDocs.java} (98%) rename jetty-documentation/src/main/java/embedded/client/{ClientConnectorDocSnippets.java => ClientConnectorDocs.java} (97%) diff --git a/jetty-documentation/pom.xml b/jetty-documentation/pom.xml index 460b4b34f93..4661ac8c479 100644 --- a/jetty-documentation/pom.xml +++ b/jetty-documentation/pom.xml @@ -125,7 +125,6 @@ http://central.maven.org/maven2 ${project.version} ${maven.build.timestamp} - ${basedir}/src/main/java diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/.asciidoctorconfig b/jetty-documentation/src/main/asciidoc/embedded-guide/.asciidoctorconfig new file mode 100644 index 00000000000..b1785c722ed --- /dev/null +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/.asciidoctorconfig @@ -0,0 +1,3 @@ +// Asciidoctor IDE configuration file. +// See https://github.com/asciidoctor/asciidoctor-intellij-plugin/wiki/Support-project-specific-configurations +:doc_code: ../../java diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/client/client-concepts.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/client-concepts.adoc index bf7dae893b1..bf63bce5013 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/client/client-concepts.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/client-concepts.adoc @@ -31,9 +31,9 @@ There are conceptually three layers that compose the Jetty client libraries, fro more abstract to more concrete: . The API layer, that exposes semantic APIs to applications so that they can write -code such as "GET me the resource at this URI" +code such as "GET me the resource at this URI". . The protocol layer, where the API request is converted into the appropriate -protocol bytes, for example encrypted HTTP/2 +protocol bytes, for example encrypted HTTP/2. . The infrastructure layer, that handles the low level I/O and deals with network, buffer, threads, etc. @@ -50,26 +50,31 @@ link:{JDURL}/org/eclipse/jetty/io/ClientConnector.html[`ClientConnector`]. The `ClientConnector` primarily wraps the link:{JDURL}/org/eclipse/jetty/io/SelectorManager.html[`SelectorManager`] -and aggregates other four components: the thread pool (in form of an `Executor`), -the `Scheduler`, the `ByteBufferPool` and the `SslContextFactory.Client`. +and aggregates other four components: + +* a thread pool (in form of an `java.util.concurrent.Executor`) +* a scheduler (in form of `org.eclipse.jetty.util.thread.Scheduler`) +* a byte buffer pool (in form of `org.eclipse.jetty.io.ByteBufferPool`) +* a TLS factory (in form of `org.eclipse.jetty.util.ssl.SslContextFactory.Client`) The `ClientConnector` is where you want to set those components after you have configured them. If you don't explicitly set those components on the `ClientConnector`, then appropriate defaults will be chosen when the `ClientConnector` starts. -The simplest example that creates and starts a `ClientConnector`: +The simplest example that creates and starts a `ClientConnector` is the +following: [source,java,indent=0] ---- -include::{docbits}/embedded/client/ClientConnectorDocSnippets.java[tags=simplest] +include::../{doc_code}/embedded/client/ClientConnectorDocs.java[tags=simplest] ---- A more typical example: [source,java,indent=0] ---- -include::{docbits}/embedded/client/ClientConnectorDocSnippets.java[tags=typical] +include::../{doc_code}/embedded/client/ClientConnectorDocs.java[tags=typical] ---- A more advanced example that customizes the `ClientConnector` by overriding @@ -77,12 +82,11 @@ factory methods: [source,java,indent=0] ---- -include::{docbits}/embedded/client/ClientConnectorDocSnippets.java[tags=advanced] +include::../{doc_code}/embedded/client/ClientConnectorDocs.java[tags=advanced] ---- Since `ClientConnector` is the component that handles the low-level network, it -is also the component where you want to configure the parameters that control -how it should handle the low-level network. +is also the component where you want to configure the low-leven network configuration. The most common parameters are: @@ -122,9 +126,6 @@ Once the `ClientConnector` is configured and started, it can be used to connect to the server via `ClientConnector.connect(SocketAddress, Map)` which in turn will call `SocketChannel.connect(SocketAddress)`. - -// TODO: from down here, moved to io-arch.adoc - When establishing a TCP connection to a server, applications need to tell `ClientConnector` how to create the `Connection` for that particular TCP connection. @@ -134,10 +135,9 @@ that must be passed in the context `Map` as follows: [source,java,indent=0] ---- -include::{docbits}/embedded/client/ClientConnectorDocSnippets.java[tags=connect] +include::../{doc_code}/embedded/client/ClientConnectorDocs.java[tags=connect] ---- - TODO: expand on what is the API to use, what parameters the context Map must have, and basically how we can write a generic network client with it. diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/io-arch.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/io-arch.adoc index e0106e8059b..e083aa1d6ee 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/io-arch.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/io-arch.adoc @@ -40,26 +40,26 @@ NOTE: TODO: add image to a server and by a network server when accepting connections from network clients. In both cases the `SocketChannel` instance is passed to `SelectorManager` -(and to `ManagedSelector` and eventually to `java.nio.channels.Selector`) -to be registered for use within Jetty. +(which passes it to `ManagedSelector` and eventually to +`java.nio.channels.Selector`) to be registered for use within Jetty. -It is therefore possible for an application to create the `SocketChannel` +It is possible for an application to create the `SocketChannel` instances outside Jetty, even perform some initial network traffic also outside Jetty (for example for authentication purposes), and then pass the `SocketChannel` instance to `SelectorManager` for use within Jetty. -This example shows how to connect to a server: +This example shows how a client can connect to a server: [source,java,indent=0] ---- -include::{docbits}/embedded/SelectorManagerDocSnippets.java[tags=connect] +include::{doc_code}/embedded/SelectorManagerDocs.java[tags=connect] ---- -This example shows how to accept a client connection: +This example shows how a server accepts a client connection: [source,java,indent=0] ---- -include::{docbits}/embedded/SelectorManagerDocSnippets.java[tags=accept] +include::{doc_code}/embedded/SelectorManagerDocs.java[tags=accept] ---- [[io-arch-endpoint-connection]] @@ -193,7 +193,7 @@ extends `AbstractConnection`: [source,java,indent=0] ---- -include::{docbits}/embedded/SelectorManagerDocSnippets.java[tags=connection] +include::{doc_code}/embedded/SelectorManagerDocs.java[tags=connection] ---- [[io-arch-echo]] @@ -207,7 +207,7 @@ A naive, but wrong, implementation may be the following: [source,java,indent=0] ---- -include::{docbits}/embedded/SelectorManagerDocSnippets.java[tags=echo-wrong] +include::{doc_code}/embedded/SelectorManagerDocs.java[tags=echo-wrong] ---- WARNING: The implementation above is wrong and leads to `StackOverflowError`. @@ -231,11 +231,16 @@ which leads to `StackOverflowError`. This is a typical side effect of asynchronous programming using non-blocking APIs, and happens in the Jetty I/O library as well. +NOTE: The callback is invoked synchronously for efficiency reasons. +Submitting the invocation of the callback to an `Executor` to be invoked in +a different thread would cause a context switch and make simple writes +extremely inefficient. + A correct implementation is the following: [source,java,indent=0] ---- -include::{docbits}/embedded/SelectorManagerDocSnippets.java[tags=echo-correct] +include::{doc_code}/embedded/SelectorManagerDocs.java[tags=echo-correct] ---- The correct implementation performs consecutive reads in a loop (rather than diff --git a/jetty-documentation/src/main/java/embedded/SelectorManagerDocSnippets.java b/jetty-documentation/src/main/java/embedded/SelectorManagerDocs.java similarity index 98% rename from jetty-documentation/src/main/java/embedded/SelectorManagerDocSnippets.java rename to jetty-documentation/src/main/java/embedded/SelectorManagerDocs.java index 3844c4f9dab..8350b6c120b 100644 --- a/jetty-documentation/src/main/java/embedded/SelectorManagerDocSnippets.java +++ b/jetty-documentation/src/main/java/embedded/SelectorManagerDocs.java @@ -34,7 +34,7 @@ import org.eclipse.jetty.io.SelectorManager; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; -public class SelectorManagerDocSnippets +public class SelectorManagerDocs { // tag::connect[] public void connect(SelectorManager selectorManager, Map context) throws IOException @@ -101,9 +101,9 @@ public class SelectorManagerDocSnippets // tag::echo-wrong[] class WrongEchoConnection extends AbstractConnection implements Callback { - public WrongEchoConnection(EndPoint endp, Executor executor) + public WrongEchoConnection(EndPoint endPoint, Executor executor) { - super(endp, executor); + super(endPoint, executor); } @Override diff --git a/jetty-documentation/src/main/java/embedded/client/ClientConnectorDocSnippets.java b/jetty-documentation/src/main/java/embedded/client/ClientConnectorDocs.java similarity index 97% rename from jetty-documentation/src/main/java/embedded/client/ClientConnectorDocSnippets.java rename to jetty-documentation/src/main/java/embedded/client/ClientConnectorDocs.java index 41f92da336f..840bb63a0b2 100644 --- a/jetty-documentation/src/main/java/embedded/client/ClientConnectorDocSnippets.java +++ b/jetty-documentation/src/main/java/embedded/client/ClientConnectorDocs.java @@ -36,7 +36,7 @@ import org.eclipse.jetty.util.thread.Scheduler; import static java.lang.System.Logger.Level.INFO; -public class ClientConnectorDocSnippets +public class ClientConnectorDocs { public void simplest() throws Exception { @@ -107,6 +107,7 @@ public class ClientConnectorDocSnippets public void connect() throws Exception { + // tag::connect[] class CustomHTTPConnection extends AbstractConnection { public CustomHTTPConnection(EndPoint endPoint, Executor executor) @@ -141,10 +142,11 @@ public class ClientConnectorDocSnippets Map context = new HashMap<>(); context.put(ClientConnector.CLIENT_CONNECTION_FACTORY_CONTEXT_KEY, connectionFactory); clientConnector.connect(address, context); + // end::connect[] } public static void main(String[] args) throws Exception { - new ClientConnectorDocSnippets().connect(); + new ClientConnectorDocs().connect(); } } From 97eb8ec8a4cfeb36b8b3a7465604d993e214ae89 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Mon, 30 Mar 2020 12:26:57 +0200 Subject: [PATCH 012/101] Fixed generation of documentation via Maven Plugin. Now including .asciidoctorconfig so that generation and IDEs render the documentation correctly. Signed-off-by: Simone Bordet --- jetty-documentation/src/main/asciidoc/embedded-guide/index.adoc | 1 + 1 file changed, 1 insertion(+) diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/index.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/index.adoc index 1dfc4bdaa93..5988c442888 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/index.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/index.adoc @@ -58,6 +58,7 @@ endif::[] // suppress automatic id generation :sectids!: +include::.asciidoctorconfig[] include::client/client.adoc[] include::server/server.adoc[] include::io-arch.adoc[] From e224be650b0a31a248dac3902ec31bce9e1862fa Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Mon, 30 Mar 2020 12:34:21 +0200 Subject: [PATCH 013/101] Issue #4400 - Review HttpClient's ContentProvider. Review updates. Closing MultiPartRequestContent before sending it. Signed-off-by: Simone Bordet --- .../jetty/osgi/test/TestJettyOSGiBootWithAnnotations.java | 2 +- .../test/java/org/eclipse/jetty/webapp/HugeResourceTest.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWithAnnotations.java b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWithAnnotations.java index dbd722b628c..00b4d6d016a 100644 --- a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWithAnnotations.java +++ b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWithAnnotations.java @@ -47,7 +47,6 @@ import static org.ops4j.pax.exam.CoreOptions.systemProperty; * top of this. */ @RunWith(PaxExam.class) - public class TestJettyOSGiBootWithAnnotations { private static final String LOG_LEVEL = "WARN"; @@ -135,6 +134,7 @@ public class TestJettyOSGiBootWithAnnotations TestOSGiUtil.assertContains("Response contents", content, "

FRAGMENT

"); MultiPartRequestContent multiPart = new MultiPartRequestContent(); multiPart.addFieldPart("field", new StringRequestContent("foo"), null); + multiPart.close(); response = client.newRequest("http://127.0.0.1:" + port + "/multi").method("POST") .body(multiPart).send(); assertEquals(HttpStatus.OK_200, response.getStatus()); diff --git a/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/HugeResourceTest.java b/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/HugeResourceTest.java index 8d4f46a57ea..8bd2d5c1916 100644 --- a/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/HugeResourceTest.java +++ b/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/HugeResourceTest.java @@ -267,6 +267,7 @@ public class HugeResourceTest Path inputFile = staticBase.resolve(filename); String name = String.format("file-%d", expectedSize); multipart.addFilePart(name, filename, new PathRequestContent(inputFile), null); + multipart.close(); URI destUri = server.getURI().resolve("/multipart"); client.setIdleTimeout(90_000); From cb47b06f1449bb3101d6c2b7616a0ad51045d6e1 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Mon, 30 Mar 2020 12:49:13 +0200 Subject: [PATCH 014/101] Fixed copyright header. Signed-off-by: Simone Bordet --- .../java/embedded/SelectorManagerDocs.java | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/jetty-documentation/src/main/java/embedded/SelectorManagerDocs.java b/jetty-documentation/src/main/java/embedded/SelectorManagerDocs.java index 8350b6c120b..243f6b66ecb 100644 --- a/jetty-documentation/src/main/java/embedded/SelectorManagerDocs.java +++ b/jetty-documentation/src/main/java/embedded/SelectorManagerDocs.java @@ -1,19 +1,19 @@ // -// ======================================================================== -// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd. -// ------------------------------------------------------------------------ -// All rights reserved. This program and the accompanying materials -// are made available under the terms of the Eclipse Public License v1.0 -// and Apache License v2.0 which accompanies this distribution. +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. // -// The Eclipse Public License is available at -// http://www.eclipse.org/legal/epl-v10.html +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 // -// The Apache License v2.0 is available at -// http://www.opensource.org/licenses/apache2.0.php +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 // -// You may elect to redistribute this code under either of these licenses. -// ======================================================================== +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== // package embedded; From e3d670d61d2ef0f83cfdb832833cd05b64d7de8f Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Mon, 30 Mar 2020 16:23:26 +0200 Subject: [PATCH 015/101] Issue #4713 Async dispatch with query. (#4721) * Issue #4713 Async dispatch with query. + Preserve the entire URI with query when startAsync(req,res) is used. + merge any query string from dispatch path with either original query or preserved query from forward Signed-off-by: Greg Wilkins * Issue #4713 asyncDispatch with query parameters Signed-off-by: Greg Wilkins --- .../java/org/eclipse/jetty/http/HttpURI.java | 6 +- .../jetty/server/AsyncContextEvent.java | 29 +++-- .../org/eclipse/jetty/server/Request.java | 20 ++- .../java/org/eclipse/jetty/server/Server.java | 76 ++++++++++-- .../jetty/servlet/AsyncServletTest.java | 116 +++++++++--------- .../java/org/eclipse/jetty/util/URIUtil.java | 14 +++ 6 files changed, 170 insertions(+), 91 deletions(-) diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpURI.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpURI.java index 2bde2c1deaa..2c112940f8b 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpURI.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpURI.java @@ -760,15 +760,15 @@ public class HttpURI _decodedPath = path; } - public void setPathQuery(String path) + public void setPathQuery(String pathQuery) { _uri = null; _path = null; _decodedPath = null; _param = null; _fragment = null; - if (path != null) - parse(State.PATH, path); + if (pathQuery != null) + parse(State.PATH, pathQuery); } public void setQuery(String query) diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncContextEvent.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncContextEvent.java index 4da7cc87e67..c4bc0095aee 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncContextEvent.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncContextEvent.java @@ -25,6 +25,7 @@ import javax.servlet.ServletContext; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; +import org.eclipse.jetty.http.HttpURI; import org.eclipse.jetty.server.handler.ContextHandler.Context; import org.eclipse.jetty.util.thread.Scheduler; @@ -32,6 +33,7 @@ public class AsyncContextEvent extends AsyncEvent implements Runnable { private final Context _context; private final AsyncContextState _asyncContext; + private final HttpURI _baseURI; private volatile HttpChannelState _state; private ServletContext _dispatchContext; private String _dispatchPath; @@ -39,11 +41,17 @@ public class AsyncContextEvent extends AsyncEvent implements Runnable private Throwable _throwable; public AsyncContextEvent(Context context, AsyncContextState asyncContext, HttpChannelState state, Request baseRequest, ServletRequest request, ServletResponse response) + { + this (context, asyncContext, state, baseRequest, request, response, null); + } + + public AsyncContextEvent(Context context, AsyncContextState asyncContext, HttpChannelState state, Request baseRequest, ServletRequest request, ServletResponse response, HttpURI baseURI) { super(null, request, response, null); _context = context; _asyncContext = asyncContext; _state = state; + _baseURI = baseURI; // If we haven't been async dispatched before if (baseRequest.getAttribute(AsyncContext.ASYNC_REQUEST_URI) == null) @@ -74,6 +82,11 @@ public class AsyncContextEvent extends AsyncEvent implements Runnable } } + public HttpURI getBaseURI() + { + return _baseURI; + } + public ServletContext getSuspendedContext() { return _context; @@ -94,14 +107,6 @@ public class AsyncContextEvent extends AsyncEvent implements Runnable return _dispatchContext == null ? _context : _dispatchContext; } - /** - * @return The path in the context (encoded with possible query string) - */ - public String getPath() - { - return _dispatchPath; - } - public void setTimeoutTask(Scheduler.Task task) { _timeoutTask = task; @@ -137,6 +142,14 @@ public class AsyncContextEvent extends AsyncEvent implements Runnable _dispatchContext = context; } + /** + * @return The path in the context (encoded with possible query string) + */ + public String getDispatchPath() + { + return _dispatchPath; + } + /** * @param path encoded URI */ diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java index 6abada24f58..07f2d318be4 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java @@ -1598,7 +1598,10 @@ public class Request implements HttpServletRequest { MetaData.Request metadata = _metaData; if (metadata != null) + { metadata.setURI(uri); + _queryParameters = null; + } } public UserIdentity getUserIdentity() @@ -2178,17 +2181,8 @@ public class Request implements HttpServletRequest HttpChannelState state = getHttpChannelState(); if (_async == null) _async = new AsyncContextState(state); - AsyncContextEvent event = new AsyncContextEvent(_context, _async, state, this, servletRequest, servletResponse); + AsyncContextEvent event = new AsyncContextEvent(_context, _async, state, this, servletRequest, servletResponse, getHttpURI()); event.setDispatchContext(getServletContext()); - - String uri = unwrap(servletRequest).getRequestURI(); - if (_contextPath != null && uri.startsWith(_contextPath)) - uri = uri.substring(_contextPath.length()); - else - // TODO probably need to strip encoded context from requestURI, but will do this for now: - uri = URIUtil.encodePath(URIUtil.addPaths(getServletPath(), getPathInfo())); - - event.setDispatchPath(uri); state.startAsync(event); return _async; } @@ -2391,7 +2385,7 @@ public class Request implements HttpServletRequest setQueryString(oldQuery); else if (oldQuery == null) setQueryString(newQuery); - else + else if (oldQueryParams.keySet().stream().anyMatch(newQueryParams.keySet()::contains)) { // Build the new merged query string, parameters in the // new query string hide parameters in the old query string. @@ -2413,6 +2407,10 @@ public class Request implements HttpServletRequest } setQueryString(mergedQuery.toString()); } + else + { + setQueryString(newQuery + '&' + oldQuery); + } } } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java index d9ade6e27d4..1566d6ba2f7 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java @@ -47,6 +47,8 @@ import org.eclipse.jetty.server.handler.HandlerWrapper; import org.eclipse.jetty.util.Attributes; import org.eclipse.jetty.util.Jetty; import org.eclipse.jetty.util.MultiException; +import org.eclipse.jetty.util.MultiMap; +import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.util.Uptime; import org.eclipse.jetty.util.annotation.ManagedAttribute; @@ -563,22 +565,74 @@ public class Server extends HandlerWrapper implements Attributes { final HttpChannelState state = channel.getRequest().getHttpChannelState(); final AsyncContextEvent event = state.getAsyncContextEvent(); - final Request baseRequest = channel.getRequest(); - final String path = event.getPath(); + final HttpURI baseUri = event.getBaseURI(); + String encodedPathQuery = event.getDispatchPath(); - if (path != null) + if (encodedPathQuery == null && baseUri == null) { - // this is a dispatch with a path - ServletContext context = event.getServletContext(); - String query = baseRequest.getQueryString(); - baseRequest.setURIPathQuery(URIUtil.addEncodedPaths(context == null ? null : URIUtil.encodePath(context.getContextPath()), path)); - HttpURI uri = baseRequest.getHttpURI(); - baseRequest.setPathInfo(uri.getDecodedPath()); - if (uri.getQuery() != null) - baseRequest.mergeQueryParameters(query, uri.getQuery(), true); //we have to assume dispatch path and query are UTF8 + // Simple case, no request modification or merging needed + handleAsync(channel, event, baseRequest); + return; } + // this is a dispatch with either a provided URI and/or a dispatched path + // We will have to modify the request and then revert + final ServletContext context = event.getServletContext(); + final HttpURI oldUri = baseRequest.getHttpURI(); + final String oldQuery = baseRequest.getQueryString(); + final MultiMap oldQueryParams = baseRequest.getQueryParameters(); + try + { + baseRequest.resetParameters(); + HttpURI newUri = baseUri == null ? new HttpURI(oldUri) : baseUri; + if (encodedPathQuery == null) + { + baseRequest.setHttpURI(newUri); + } + else + { + if (context != null && !StringUtil.isEmpty(context.getContextPath())) + encodedPathQuery = URIUtil.addEncodedPaths(URIUtil.encodePath(context.getContextPath()), encodedPathQuery); + + if (newUri.getQuery() == null) + { + // parse new path and query + newUri.setPathQuery(encodedPathQuery); + baseRequest.setHttpURI(newUri); + } + else + { + // do we have a new query in the encodedPathQuery + int q = encodedPathQuery.indexOf('?'); + if (q < 0) + { + // No query, so we can just set the encoded path + newUri.setPath(encodedPathQuery); + baseRequest.setHttpURI(newUri); + } + else + { + newUri.setPath(encodedPathQuery.substring(0, q)); + baseRequest.setHttpURI(newUri); + baseRequest.mergeQueryParameters(oldQuery, encodedPathQuery.substring(q + 1), true); + } + } + } + + baseRequest.setPathInfo(newUri.getDecodedPath()); + handleAsync(channel, event, baseRequest); + } + finally + { + baseRequest.setHttpURI(oldUri); + baseRequest.setQueryParameters(oldQueryParams); + baseRequest.resetParameters(); + } + } + + private void handleAsync(HttpChannel channel, AsyncContextEvent event, Request baseRequest) throws IOException, ServletException + { final String target = baseRequest.getPathInfo(); final HttpServletRequest request = Request.unwrap(event.getSuppliedRequest()); final HttpServletResponse response = Response.unwrap(event.getSuppliedResponse()); diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletTest.java index ac16a759a6a..f04cffee271 100644 --- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletTest.java +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletTest.java @@ -163,7 +163,7 @@ public class AsyncServletTest String response = process("sleep=200", null); assertThat(response, startsWith("HTTP/1.1 200 OK")); assertThat(__history, contains( - "REQUEST /ctx/path/info", + "REQUEST /ctx/path/info?sleep=200", "initial")); assertContains("SLEPT", response); assertFalse(__history.contains("onTimeout")); @@ -173,7 +173,7 @@ public class AsyncServletTest @Test public void testNonAsync() throws Exception { - String response = process("", null); + String response = process(null, null); assertThat(response, Matchers.startsWith("HTTP/1.1 200 OK")); assertThat(__history, contains( "REQUEST /ctx/path/info", @@ -186,7 +186,7 @@ public class AsyncServletTest public void testAsyncNotSupportedNoAsync() throws Exception { _expectedCode = "200 "; - String response = process("noasync", "", null); + String response = process("noasync", null, null); assertThat(response, Matchers.startsWith("HTTP/1.1 200 OK")); assertThat(__history, contains( "REQUEST /ctx/noasync/info", @@ -205,9 +205,9 @@ public class AsyncServletTest String response = process("noasync", "start=200", null); assertThat(response, Matchers.startsWith("HTTP/1.1 500 ")); assertThat(__history, contains( - "REQUEST /ctx/noasync/info", + "REQUEST /ctx/noasync/info?start=200", "initial", - "ERROR /ctx/error/custom", + "ERROR /ctx/error/custom?start=200", "!initial" )); @@ -224,11 +224,11 @@ public class AsyncServletTest String response = process("start=200", null); assertThat(response, Matchers.startsWith("HTTP/1.1 500 Server Error")); assertThat(__history, contains( - "REQUEST /ctx/path/info", + "REQUEST /ctx/path/info?start=200", "initial", "start", "onTimeout", - "ERROR /ctx/error/custom", + "ERROR /ctx/error/custom?start=200", "!initial", "onComplete")); @@ -241,12 +241,12 @@ public class AsyncServletTest String response = process("start=200&timeout=dispatch", null); assertThat(response, startsWith("HTTP/1.1 200 OK")); assertThat(__history, contains( - "REQUEST /ctx/path/info", + "REQUEST /ctx/path/info?start=200&timeout=dispatch", "initial", "start", "onTimeout", "dispatch", - "ASYNC /ctx/path/info", + "ASYNC /ctx/path/info?start=200&timeout=dispatch", "!initial", "onComplete")); @@ -260,12 +260,12 @@ public class AsyncServletTest String response = process("start=200&timeout=error", null); assertThat(response, startsWith("HTTP/1.1 500 Server Error")); assertThat(__history, contains( - "REQUEST /ctx/path/info", + "REQUEST /ctx/path/info?start=200&timeout=error", "initial", "start", "onTimeout", "error", - "ERROR /ctx/error/custom", + "ERROR /ctx/error/custom?start=200&timeout=error", "!initial", "onComplete")); @@ -278,7 +278,7 @@ public class AsyncServletTest String response = process("start=200&timeout=complete", null); assertThat(response, startsWith("HTTP/1.1 200 OK")); assertThat(__history, contains( - "REQUEST /ctx/path/info", + "REQUEST /ctx/path/info?start=200&timeout=complete", "initial", "start", "onTimeout", @@ -294,11 +294,11 @@ public class AsyncServletTest String response = process("start=200&dispatch=10", null); assertThat(response, startsWith("HTTP/1.1 200 OK")); assertThat(__history, contains( - "REQUEST /ctx/path/info", + "REQUEST /ctx/path/info?start=200&dispatch=10", "initial", "start", "dispatch", - "ASYNC /ctx/path/info", + "ASYNC /ctx/path/info?start=200&dispatch=10", "!initial", "onComplete")); assertFalse(__history.contains("onTimeout")); @@ -310,11 +310,11 @@ public class AsyncServletTest String response = process("start=200&dispatch=0", null); assertThat(response, startsWith("HTTP/1.1 200 OK")); assertThat(__history, contains( - "REQUEST /ctx/path/info", + "REQUEST /ctx/path/info?start=200&dispatch=0", "initial", "start", "dispatch", - "ASYNC /ctx/path/info", + "ASYNC /ctx/path/info?start=200&dispatch=0", "!initial", "onComplete")); } @@ -326,11 +326,11 @@ public class AsyncServletTest String response = process("start=200&throw=1", null); assertThat(response, startsWith("HTTP/1.1 500 Server Error")); assertThat(__history, contains( - "REQUEST /ctx/path/info", + "REQUEST /ctx/path/info?start=200&throw=1", "initial", "start", "onError", - "ERROR /ctx/error/custom", + "ERROR /ctx/error/custom?start=200&throw=1", "!initial", "onComplete")); assertContains("ERROR DISPATCH: /ctx/error/custom", response); @@ -342,7 +342,7 @@ public class AsyncServletTest String response = process("start=200&complete=50", null); assertThat(response, startsWith("HTTP/1.1 200 OK")); assertThat(__history, contains( - "REQUEST /ctx/path/info", + "REQUEST /ctx/path/info?start=200&complete=50", "initial", "start", "complete", @@ -358,7 +358,7 @@ public class AsyncServletTest String response = process("start=200&complete=0", null); assertThat(response, startsWith("HTTP/1.1 200 OK")); assertThat(__history, contains( - "REQUEST /ctx/path/info", + "REQUEST /ctx/path/info?start=200&complete=0", "initial", "start", "complete", @@ -374,16 +374,16 @@ public class AsyncServletTest String response = process("start=1000&dispatch=10&start2=1000&dispatch2=10", null); assertThat(response, startsWith("HTTP/1.1 200 OK")); assertThat(__history, contains( - "REQUEST /ctx/path/info", + "REQUEST /ctx/path/info?start=1000&dispatch=10&start2=1000&dispatch2=10", "initial", "start", "dispatch", - "ASYNC /ctx/path/info", + "ASYNC /ctx/path/info?start=1000&dispatch=10&start2=1000&dispatch2=10", "!initial", "onStartAsync", "start", "dispatch", - "ASYNC /ctx/path/info", + "ASYNC /ctx/path/info?start=1000&dispatch=10&start2=1000&dispatch2=10", "!initial", "onComplete")); assertContains("DISPATCHED", response); @@ -395,11 +395,11 @@ public class AsyncServletTest String response = process("start=1000&dispatch=10&start2=1000&complete2=10", null); assertThat(response, startsWith("HTTP/1.1 200 OK")); assertThat(__history, contains( - "REQUEST /ctx/path/info", + "REQUEST /ctx/path/info?start=1000&dispatch=10&start2=1000&complete2=10", "initial", "start", "dispatch", - "ASYNC /ctx/path/info", + "ASYNC /ctx/path/info?start=1000&dispatch=10&start2=1000&complete2=10", "!initial", "onStartAsync", "start", @@ -415,16 +415,16 @@ public class AsyncServletTest String response = process("start=1000&dispatch=10&start2=10", null); assertThat(response, startsWith("HTTP/1.1 500 Server Error")); assertThat(__history, contains( - "REQUEST /ctx/path/info", + "REQUEST /ctx/path/info?start=1000&dispatch=10&start2=10", "initial", "start", "dispatch", - "ASYNC /ctx/path/info", + "ASYNC /ctx/path/info?start=1000&dispatch=10&start2=10", "!initial", "onStartAsync", "start", "onTimeout", - "ERROR /ctx/error/custom", + "ERROR /ctx/error/custom?start=1000&dispatch=10&start2=10", "!initial", "onComplete")); assertContains("ERROR DISPATCH: /ctx/error/custom", response); @@ -436,16 +436,16 @@ public class AsyncServletTest String response = process("start=10&start2=1000&dispatch2=10", null); assertThat(response, startsWith("HTTP/1.1 200 OK")); assertThat(__history, contains( - "REQUEST /ctx/path/info", + "REQUEST /ctx/path/info?start=10&start2=1000&dispatch2=10", "initial", "start", "onTimeout", - "ERROR /ctx/error/custom", + "ERROR /ctx/error/custom?start=10&start2=1000&dispatch2=10", "!initial", "onStartAsync", "start", "dispatch", - "ASYNC /ctx/path/info", + "ASYNC /ctx/path/info?start=10&start2=1000&dispatch2=10", "!initial", "onComplete")); assertContains("DISPATCHED", response); @@ -457,11 +457,11 @@ public class AsyncServletTest String response = process("start=10&start2=1000&complete2=10", null); assertThat(response, startsWith("HTTP/1.1 200 OK")); assertThat(__history, contains( - "REQUEST /ctx/path/info", + "REQUEST /ctx/path/info?start=10&start2=1000&complete2=10", "initial", "start", "onTimeout", - "ERROR /ctx/error/custom", + "ERROR /ctx/error/custom?start=10&start2=1000&complete2=10", "!initial", "onStartAsync", "start", @@ -478,16 +478,16 @@ public class AsyncServletTest String response = process("start=10&start2=10", null); assertThat(__history, contains( - "REQUEST /ctx/path/info", + "REQUEST /ctx/path/info?start=10&start2=10", "initial", "start", "onTimeout", - "ERROR /ctx/path/error", + "ERROR /ctx/path/error?start=10&start2=10", "!initial", "onStartAsync", "start", "onTimeout", - "ERROR /ctx/path/error", + "ERROR /ctx/path/error?start=10&start2=10", "!initial", "onComplete")); // Error Page Loop! assertContains("AsyncContext timeout", response); @@ -499,11 +499,11 @@ public class AsyncServletTest String response = process("wrap=true&start=200&dispatch=20", null); assertThat(response, startsWith("HTTP/1.1 200 OK")); assertThat(__history, contains( - "REQUEST /ctx/path/info", + "REQUEST /ctx/path/info?wrap=true&start=200&dispatch=20", "initial", "start", "dispatch", - "ASYNC /ctx/path/info", + "ASYNC /ctx/path/info?wrap=true&start=200&dispatch=20", "wrapped REQ RSP", "!initial", "onComplete")); @@ -516,11 +516,11 @@ public class AsyncServletTest String response = process("start=200&dispatch=20&path=/p%20th3", null); assertThat(response, startsWith("HTTP/1.1 200 OK")); assertThat(__history, contains( - "REQUEST /ctx/path/info", + "REQUEST /ctx/path/info?start=200&dispatch=20&path=/p%20th3", "initial", "start", "dispatch", - "ASYNC /ctx/p%20th3", + "ASYNC /ctx/p%20th3?start=200&dispatch=20&path=/p%20th3", "!initial", "onComplete")); assertContains("DISPATCHED", response); @@ -532,13 +532,13 @@ public class AsyncServletTest String response = process("fwd", "start=200&dispatch=20", null); assertThat(response, startsWith("HTTP/1.1 200 OK")); assertThat(__history, contains( - "FWD REQUEST /ctx/fwd/info", - "FORWARD /ctx/path1", + "FWD REQUEST /ctx/fwd/info?start=200&dispatch=20", + "FORWARD /ctx/path1?forward=true&start=200&dispatch=20", "initial", "start", "dispatch", - "FWD ASYNC /ctx/fwd/info", - "FORWARD /ctx/path1", + "FWD ASYNC /ctx/fwd/info?start=200&dispatch=20", + "FORWARD /ctx/path1?forward=true&start=200&dispatch=20", "!initial", "onComplete")); assertContains("DISPATCHED", response); @@ -550,12 +550,12 @@ public class AsyncServletTest String response = process("fwd", "start=200&dispatch=20&path=/path2", null); assertThat(response, startsWith("HTTP/1.1 200 OK")); assertThat(__history, contains( - "FWD REQUEST /ctx/fwd/info", - "FORWARD /ctx/path1", + "FWD REQUEST /ctx/fwd/info?start=200&dispatch=20&path=/path2", + "FORWARD /ctx/path1?forward=true&start=200&dispatch=20&path=/path2", "initial", "start", "dispatch", - "ASYNC /ctx/path2", + "ASYNC /ctx/path2?start=200&dispatch=20&path=/path2", "!initial", "onComplete")); assertContains("DISPATCHED", response); @@ -567,12 +567,12 @@ public class AsyncServletTest String response = process("fwd", "wrap=true&start=200&dispatch=20", null); assertThat(response, startsWith("HTTP/1.1 200 OK")); assertThat(__history, contains( - "FWD REQUEST /ctx/fwd/info", - "FORWARD /ctx/path1", + "FWD REQUEST /ctx/fwd/info?wrap=true&start=200&dispatch=20", + "FORWARD /ctx/path1?forward=true&wrap=true&start=200&dispatch=20", "initial", "start", "dispatch", - "ASYNC /ctx/path1", + "ASYNC /ctx/path1?forward=true&wrap=true&start=200&dispatch=20", "wrapped REQ RSP", "!initial", "onComplete")); @@ -585,12 +585,12 @@ public class AsyncServletTest String response = process("fwd", "wrap=true&start=200&dispatch=20&path=/path2", null); assertThat(response, startsWith("HTTP/1.1 200 OK")); assertThat(__history, contains( - "FWD REQUEST /ctx/fwd/info", - "FORWARD /ctx/path1", + "FWD REQUEST /ctx/fwd/info?wrap=true&start=200&dispatch=20&path=/path2", + "FORWARD /ctx/path1?forward=true&wrap=true&start=200&dispatch=20&path=/path2", "initial", "start", "dispatch", - "ASYNC /ctx/path2", + "ASYNC /ctx/path2?forward=true&wrap=true&start=200&dispatch=20&path=/path2", "wrapped REQ RSP", "!initial", "onComplete")); @@ -619,12 +619,12 @@ public class AsyncServletTest __latch.await(1, TimeUnit.SECONDS); assertThat(response, startsWith("HTTP/1.1 200 OK")); assertThat(__history, contains( - "REQUEST /ctx/path/info", + "REQUEST /ctx/path/info?start=2000&dispatch=1500", "initial", "start", "async-read=10", "dispatch", - "ASYNC /ctx/path/info", + "ASYNC /ctx/path/info?start=2000&dispatch=1500", "!initial", "onComplete")); } @@ -685,10 +685,10 @@ public class AsyncServletTest @Override public void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException { - historyAdd("FWD " + request.getDispatcherType() + " " + request.getRequestURI()); + historyAdd("FWD " + request.getDispatcherType() + " " + URIUtil.addPathQuery(request.getRequestURI(), request.getQueryString())); if (request instanceof ServletRequestWrapper || response instanceof ServletResponseWrapper) historyAdd("wrapped" + ((request instanceof ServletRequestWrapper) ? " REQ" : "") + ((response instanceof ServletResponseWrapper) ? " RSP" : "")); - request.getServletContext().getRequestDispatcher("/path1").forward(request, response); + request.getServletContext().getRequestDispatcher("/path1?forward=true").forward(request, response); } } @@ -711,7 +711,7 @@ public class AsyncServletTest // ignored } - historyAdd(request.getDispatcherType() + " " + request.getRequestURI()); + historyAdd(request.getDispatcherType() + " " + URIUtil.addPathQuery(request.getRequestURI(),request.getQueryString())); if (request instanceof ServletRequestWrapper || response instanceof ServletResponseWrapper) historyAdd("wrapped" + ((request instanceof ServletRequestWrapper) ? " REQ" : "") + ((response instanceof ServletResponseWrapper) ? " RSP" : "")); diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java b/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java index 1909e102242..6ae6f609cd9 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java @@ -714,6 +714,20 @@ public class URIUtil return buf.toString(); } + /** Add a path and a query string + * @param path The path which may already contain contain a query + * @param query The query string or null if no query to be added + * @return The path with any non null query added after a '?' or '&' as appropriate. + */ + public static String addPathQuery(String path, String query) + { + if (query == null) + return path; + if (path.indexOf('?') >= 0) + return path + '&' + query; + return path + '?' + query; + } + /** * Given a URI, attempt to get the last segment. *

From e913e7970f58e75133bb2e1f983b3531a9587292 Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Mon, 30 Mar 2020 16:38:42 +0200 Subject: [PATCH 016/101] Fixes #4542 Root pathspec mapping pathInfo (#4705) * Fixes #4542 Root pathspec mapping pathInfo For the "" root pathspec, the pathinfo should always be the full path and the matched path is "" Signed-off-by: Greg Wilkins * updates from review Signed-off-by: Greg Wilkins --- .../jetty/http/pathmap/PathSpecGroup.java | 2 +- .../jetty/http/pathmap/ServletPathSpec.java | 48 ++++++++----------- .../jetty/http/pathmap/PathMappingsTest.java | 4 +- .../http/pathmap/ServletPathSpecTest.java | 6 ++- 4 files changed, 29 insertions(+), 31 deletions(-) diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathSpecGroup.java b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathSpecGroup.java index 0d1a165b96d..54c37dcd72c 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathSpecGroup.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathSpecGroup.java @@ -39,7 +39,7 @@ public enum PathSpecGroup * *

      *   ""           - servlet spec       (Root Servlet)
-     *   null         - servlet spec       (Root Servlet)
+     *   null         - legacy             (Root Servlet)
      * 
* * Note: there is no known uri-template spec variant of this kind of path spec diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/ServletPathSpec.java b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/ServletPathSpec.java index b32097e2217..8f251ec0f34 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/ServletPathSpec.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/ServletPathSpec.java @@ -167,17 +167,19 @@ public class ServletPathSpec extends PathSpec @Override public String getPathInfo(String path) { - // Path Info only valid for PREFIX_GLOB types - if (group == PathSpecGroup.PREFIX_GLOB) + switch (group) { - if (path.length() == (specLength - 2)) - { - return null; - } - return path.substring(specLength - 2); - } + case ROOT: + return path; - return null; + case PREFIX_GLOB: + if (path.length() == (specLength - 2)) + return null; + return path.substring(specLength - 2); + + default: + return null; + } } @Override @@ -185,35 +187,27 @@ public class ServletPathSpec extends PathSpec { switch (group) { + case ROOT: + return ""; + case EXACT: if (pathSpec.equals(path)) - { return path; - } - else - { - return null; - } + return null; + case PREFIX_GLOB: if (isWildcardMatch(path)) - { return path.substring(0, specLength - 2); - } - else - { - return null; - } + return null; + case SUFFIX_GLOB: if (path.regionMatches(path.length() - (specLength - 1), pathSpec, 1, specLength - 1)) - { return path; - } - else - { - return null; - } + return null; + case DEFAULT: return path; + default: return null; } diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/PathMappingsTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/PathMappingsTest.java index 7fab063c890..565a811ba1c 100644 --- a/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/PathMappingsTest.java +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/PathMappingsTest.java @@ -62,6 +62,7 @@ public class PathMappingsTest { PathMappings p = new PathMappings<>(); + p.put(new ServletPathSpec(""), "root"); p.put(new ServletPathSpec("/"), "default"); p.put(new ServletPathSpec("/animal/bird/*"), "birds"); p.put(new ServletPathSpec("/animal/fish/*"), "fishes"); @@ -75,7 +76,8 @@ public class PathMappingsTest assertMatch(p, "/animal/bird/eagle", "birds"); assertMatch(p, "/animal/fish/bass/sea", "fishes"); assertMatch(p, "/animal/peccary/javalina/evolution", "animals"); - assertMatch(p, "/", "default"); + assertMatch(p, "/", "root"); + assertMatch(p, "/other", "default"); assertMatch(p, "/animal/bird/eagle/chat", "animalChat"); assertMatch(p, "/animal/bird/penguin/chat", "animalChat"); assertMatch(p, "/animal/fish/trout/cam", "animalCam"); diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/ServletPathSpecTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/ServletPathSpecTest.java index b9c1c5d6aec..ec52f2c0456 100644 --- a/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/ServletPathSpecTest.java +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/ServletPathSpecTest.java @@ -117,7 +117,8 @@ public class ServletPathSpecTest assertEquals(null, new ServletPathSpec("/Foo/*").getPathInfo("/Foo"), "pathInfo prefix"); assertEquals(null, new ServletPathSpec("*.ext").getPathInfo("/Foo/bar.ext"), "pathInfo suffix"); assertEquals(null, new ServletPathSpec("/").getPathInfo("/Foo/bar.ext"), "pathInfo default"); - + assertEquals("/", new ServletPathSpec("").getPathInfo("/"), "pathInfo root"); + assertEquals("", new ServletPathSpec("").getPathInfo(""), "pathInfo root"); assertEquals("/xxx/zzz", new ServletPathSpec("/*").getPathInfo("/xxx/zzz"), "pathInfo default"); } @@ -146,7 +147,8 @@ public class ServletPathSpecTest assertEquals("/Foo", new ServletPathSpec("/Foo/*").getPathMatch("/Foo"), "pathMatch prefix"); assertEquals("/Foo/bar.ext", new ServletPathSpec("*.ext").getPathMatch("/Foo/bar.ext"), "pathMatch suffix"); assertEquals("/Foo/bar.ext", new ServletPathSpec("/").getPathMatch("/Foo/bar.ext"), "pathMatch default"); - + assertEquals("", new ServletPathSpec("").getPathMatch("/"), "pathInfo root"); + assertEquals("", new ServletPathSpec("").getPathMatch(""), "pathInfo root"); assertEquals("", new ServletPathSpec("/*").getPathMatch("/xxx/zzz"), "pathMatch default"); } From 3f7d04ff96e5ca916af6d196e6d5731d4b5db8db Mon Sep 17 00:00:00 2001 From: Jan Bartel Date: Tue, 31 Mar 2020 14:24:55 +0200 Subject: [PATCH 017/101] Issue #4719 Keep previous charencoding if not set in content type (#4720) * Issue #4719 Keep previous charencoding if not set in content type Signed-off-by: Jan Bartel * Issue #4719 - Adding unit tests for charset reset/change Signed-off-by: Joakim Erdfelt --- .../org/eclipse/jetty/server/Response.java | 16 +-- .../eclipse/jetty/server/ResponseTest.java | 13 +- .../eclipse/jetty/servlet/ErrorPageTest.java | 46 +++++++ .../jetty/servlet/ResponseHeadersTest.java | 123 ++++++++++++++++++ 4 files changed, 188 insertions(+), 10 deletions(-) diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java index 1d934ac14ea..e023a1f1c78 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java @@ -1017,11 +1017,9 @@ public class Response implements HttpServletResponse _contentType = contentType; _mimeType = MimeTypes.CACHE.get(contentType); - String charset; - if (_mimeType != null && _mimeType.getCharset() != null && !_mimeType.isCharsetAssumed()) + String charset = MimeTypes.getCharsetFromContentType(contentType); + if (charset == null && _mimeType != null && _mimeType.isCharsetAssumed()) charset = _mimeType.getCharsetString(); - else - charset = MimeTypes.getCharsetFromContentType(contentType); if (charset == null) { @@ -1030,11 +1028,10 @@ public class Response implements HttpServletResponse case NOT_SET: break; case INFERRED: - case SET_CONTENT_TYPE: if (isWriting()) { - _mimeType = null; _contentType = _contentType + ";charset=" + _characterEncoding; + _mimeType = MimeTypes.CACHE.get(_contentType); } else { @@ -1042,11 +1039,12 @@ public class Response implements HttpServletResponse _characterEncoding = null; } break; + case SET_CONTENT_TYPE: case SET_LOCALE: case SET_CHARACTER_ENCODING: { _contentType = contentType + ";charset=" + _characterEncoding; - _mimeType = null; + _mimeType = MimeTypes.CACHE.get(_contentType); break; } default: @@ -1056,10 +1054,10 @@ public class Response implements HttpServletResponse else if (isWriting() && !charset.equalsIgnoreCase(_characterEncoding)) { // too late to change the character encoding; - _mimeType = null; _contentType = MimeTypes.getContentTypeWithoutCharset(_contentType); - if (_characterEncoding != null) + if (_characterEncoding != null && (_mimeType == null || !_mimeType.isCharsetAssumed())) _contentType = _contentType + ";charset=" + _characterEncoding; + _mimeType = MimeTypes.CACHE.get(_contentType); } else { diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java index 306269754a8..273e28ad360 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java @@ -501,6 +501,17 @@ public class ResponseTest response.setCharacterEncoding("ISO-8859-1"); assertEquals("text/xml;charset=utf-8", response.getContentType()); } + + @Test + public void testContentEncodingViaContentTypeChange() throws Exception + { + Response response = getResponse(); + response.setContentType("text/html;charset=Shift_Jis"); + assertEquals("Shift_Jis", response.getCharacterEncoding()); + + response.setContentType("text/xml"); + assertEquals("Shift_Jis", response.getCharacterEncoding()); + } @Test public void testCharacterEncodingContentType() throws Exception @@ -624,7 +635,7 @@ public class ResponseTest response.setContentType("wrong/answer;charset=utf-8"); response.setContentType("foo/bar"); - assertEquals("foo/bar", response.getContentType()); + assertEquals("foo/bar;charset=utf-8", response.getContentType()); response.setContentType("wrong/answer;charset=utf-8"); response.getWriter(); response.setContentType("foo2/bar2;charset=utf-16"); diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ErrorPageTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ErrorPageTest.java index 915916ff866..a396a84e02d 100644 --- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ErrorPageTest.java +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ErrorPageTest.java @@ -43,6 +43,8 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.BadMessageException; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.tools.HttpTester; import org.eclipse.jetty.logging.StacklessLogging; import org.eclipse.jetty.server.Dispatcher; import org.eclipse.jetty.server.HttpChannel; @@ -60,6 +62,7 @@ import org.slf4j.LoggerFactory; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -101,6 +104,7 @@ public class ErrorPageTest _context.addServlet(UnavailableServlet.class, "/unavailable/*"); _context.addServlet(DeleteServlet.class, "/delete/*"); _context.addServlet(ErrorAndStatusServlet.class, "/error-and-status/*"); + _context.addServlet(ErrorContentTypeCharsetWriterInitializedServlet.class, "/error-mime-charset-writer/*"); HandlerWrapper noopHandler = new HandlerWrapper() { @@ -140,6 +144,36 @@ public class ErrorPageTest _server.join(); } + @Test + public void testErrorOverridesMimeTypeAndCharset() throws Exception + { + StringBuilder rawRequest = new StringBuilder(); + rawRequest.append("GET /error-mime-charset-writer/ HTTP/1.1\r\n"); + rawRequest.append("Host: test\r\n"); + rawRequest.append("Connection: close\r\n"); + rawRequest.append("Accept: */*\r\n"); + rawRequest.append("Accept-Charset: *\r\n"); + rawRequest.append("\r\n"); + + String rawResponse = _connector.getResponse(rawRequest.toString()); + System.out.println(rawResponse); + HttpTester.Response response = HttpTester.parseResponse(rawResponse); + + assertThat(response.getStatus(), is(595)); + String actualContentType = response.get(HttpHeader.CONTENT_TYPE); + // should not expect to see charset line from servlet + assertThat(actualContentType, not(containsString("charset=US-ASCII"))); + String body = response.getContent(); + + assertThat(body, containsString("ERROR_PAGE: /595")); + assertThat(body, containsString("ERROR_MESSAGE: 595")); + assertThat(body, containsString("ERROR_CODE: 595")); + assertThat(body, containsString("ERROR_EXCEPTION: null")); + assertThat(body, containsString("ERROR_EXCEPTION_TYPE: null")); + assertThat(body, containsString("ERROR_SERVLET: org.eclipse.jetty.servlet.ErrorPageTest$ErrorContentTypeCharsetWriterInitializedServlet-")); + assertThat(body, containsString("ERROR_REQUEST_URI: /error-mime-charset-writer/")); + } + @Test public void testErrorOverridesStatus() throws Exception { @@ -612,6 +646,18 @@ public class ErrorPageTest } } + public static class ErrorContentTypeCharsetWriterInitializedServlet extends HttpServlet + { + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + response.setContentType("text/html; charset=US-ASCII"); + PrintWriter writer = response.getWriter(); + writer.println("Intentionally using sendError(595)"); + response.sendError(595); + } + } + public static class ErrorAndStatusServlet extends HttpServlet implements Servlet { @Override diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ResponseHeadersTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ResponseHeadersTest.java index d127980be0d..81145690ef2 100644 --- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ResponseHeadersTest.java +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ResponseHeadersTest.java @@ -19,6 +19,7 @@ package org.eclipse.jetty.servlet; import java.io.IOException; +import java.io.PrintWriter; import java.net.URLDecoder; import java.nio.ByteBuffer; import javax.servlet.ServletException; @@ -73,6 +74,65 @@ public class ResponseHeadersTest } } + public static class CharsetResetToJsonMimeTypeServlet extends HttpServlet + { + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + // We set an initial desired behavior. + response.setContentType("text/html; charset=US-ASCII"); + PrintWriter writer = response.getWriter(); + + // We reset the response, as we don't want it to be HTML anymore. + response.reset(); + + // switch to json operation + // The use of application/json is always assumed to be UTF-8 + // and should never have a `charset=` entry on the `Content-Type` response header + response.setContentType("application/json"); + writer.println("{ \"what\": \"should this be?\" }"); + } + } + + public static class CharsetChangeToJsonMimeTypeServlet extends HttpServlet + { + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + // We set an initial desired behavior. + response.setContentType("text/html; charset=US-ASCII"); + + // switch to json behavior. + // The use of application/json is always assumed to be UTF-8 + // and should never have a `charset=` entry on the `Content-Type` response header + response.setContentType("application/json"); + + PrintWriter writer = response.getWriter(); + writer.println("{ \"what\": \"should this be?\" }"); + } + } + + public static class CharsetChangeToJsonMimeTypeSetCharsetToNullServlet extends HttpServlet + { + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + // We set an initial desired behavior. + + response.setContentType("text/html; charset=us-ascii"); + PrintWriter writer = response.getWriter(); + + // switch to json behavior. + // The use of application/json is always assumed to be UTF-8 + // and should never have a `charset=` entry on the `Content-Type` response header + response.setContentType("application/json"); + // attempt to indicate that there is truly no charset meant to be used in the response header + response.setCharacterEncoding(null); + + writer.println("{ \"what\": \"should this be?\" }"); + } + } + private static Server server; private static LocalConnector connector; @@ -89,6 +149,9 @@ public class ResponseHeadersTest context.addServlet(new ServletHolder(new SimulateUpgradeServlet()), "/ws/*"); context.addServlet(new ServletHolder(new MultilineResponseValueServlet()), "/multiline/*"); + context.addServlet(CharsetResetToJsonMimeTypeServlet.class, "/charset/json-reset/*"); + context.addServlet(CharsetChangeToJsonMimeTypeServlet.class, "/charset/json-change/*"); + context.addServlet(CharsetChangeToJsonMimeTypeSetCharsetToNullServlet.class, "/charset/json-change-null/*"); server.start(); } @@ -149,4 +212,64 @@ public class ResponseHeadersTest expected = expected.trim(); // trim whitespace at start/end assertThat("Response Header X-example", response.get("X-Example"), is(expected)); } + + @Test + public void testCharsetResetToJsonMimeType() throws Exception + { + HttpTester.Request request = new HttpTester.Request(); + request.setMethod("GET"); + request.setURI("/charset/json-reset/"); + request.setVersion(HttpVersion.HTTP_1_1); + request.setHeader("Connection", "close"); + request.setHeader("Host", "test"); + + ByteBuffer responseBuffer = connector.getResponse(request.generate()); + // System.err.println(BufferUtil.toUTF8String(responseBuffer)); + HttpTester.Response response = HttpTester.parseResponse(responseBuffer); + + // Now test for properly formatted HTTP Response Headers. + assertThat("Response Code", response.getStatus(), is(200)); + // The Content-Type should not have a charset= portion + assertThat("Response Header Content-Type", response.get("Content-Type"), is("application/json")); + } + + @Test + public void testCharsetChangeToJsonMimeType() throws Exception + { + HttpTester.Request request = new HttpTester.Request(); + request.setMethod("GET"); + request.setURI("/charset/json-change/"); + request.setVersion(HttpVersion.HTTP_1_1); + request.setHeader("Connection", "close"); + request.setHeader("Host", "test"); + + ByteBuffer responseBuffer = connector.getResponse(request.generate()); + // System.err.println(BufferUtil.toUTF8String(responseBuffer)); + HttpTester.Response response = HttpTester.parseResponse(responseBuffer); + + // Now test for properly formatted HTTP Response Headers. + assertThat("Response Code", response.getStatus(), is(200)); + // The Content-Type should not have a charset= portion + assertThat("Response Header Content-Type", response.get("Content-Type"), is("application/json")); + } + + @Test + public void testCharsetChangeToJsonMimeTypeSetCharsetToNull() throws Exception + { + HttpTester.Request request = new HttpTester.Request(); + request.setMethod("GET"); + request.setURI("/charset/json-change-null/"); + request.setVersion(HttpVersion.HTTP_1_1); + request.setHeader("Connection", "close"); + request.setHeader("Host", "test"); + + ByteBuffer responseBuffer = connector.getResponse(request.generate()); + // System.err.println(BufferUtil.toUTF8String(responseBuffer)); + HttpTester.Response response = HttpTester.parseResponse(responseBuffer); + + // Now test for properly formatted HTTP Response Headers. + assertThat("Response Code", response.getStatus(), is(200)); + // The Content-Type should not have a charset= portion + assertThat("Response Header Content-Type", response.get("Content-Type"), is("application/json")); + } } From 524e690140fd021b29215981130c3a9879e37826 Mon Sep 17 00:00:00 2001 From: Jan Bartel Date: Tue, 31 Mar 2020 14:25:52 +0200 Subject: [PATCH 018/101] Issue #4682 Session with no attributes unreadable from jdbc (#4688) Signed-off-by: Jan Bartel --- .../server/session/JDBCSessionDataStore.java | 40 +++----- .../session/JDBCSessionDataStoreTest.java | 9 +- .../jetty/server/session/JdbcTestHelper.java | 95 ++++++++++++------- .../session/SessionTableSchemaTest.java | 53 +++++++++-- .../session/AbstractSessionDataStoreTest.java | 65 +++++++++++++ 5 files changed, 189 insertions(+), 73 deletions(-) diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionDataStore.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionDataStore.java index 9e48f3a6e22..54e6b5354a7 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionDataStore.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionDataStore.java @@ -709,21 +709,15 @@ public class JDBCSessionDataStore extends AbstractSessionDataStore statement.setLong(10, data.getExpiry()); statement.setLong(11, data.getMaxInactiveMs()); - if (!data.getAllAttributes().isEmpty()) + try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos)) { - try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); - ObjectOutputStream oos = new ObjectOutputStream(baos)) - { - SessionData.serializeAttributes(data, oos); - byte[] bytes = baos.toByteArray(); - ByteArrayInputStream bais = new ByteArrayInputStream(bytes); - statement.setBinaryStream(12, bais, bytes.length);//attribute map as blob - } - } - else - { - statement.setBinaryStream(12, EMPTY, 0); + SessionData.serializeAttributes(data, oos); + byte[] bytes = baos.toByteArray(); + ByteArrayInputStream bais = new ByteArrayInputStream(bytes); + statement.setBinaryStream(12, bais, bytes.length);//attribute map as blob } + statement.executeUpdate(); if (LOG.isDebugEnabled()) LOG.debug("Inserted session " + data); @@ -746,23 +740,17 @@ public class JDBCSessionDataStore extends AbstractSessionDataStore statement.setLong(5, data.getExpiry()); statement.setLong(6, data.getMaxInactiveMs()); - if (!data.getAllAttributes().isEmpty()) + try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos)) { - try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); - ObjectOutputStream oos = new ObjectOutputStream(baos)) + SessionData.serializeAttributes(data, oos); + byte[] bytes = baos.toByteArray(); + try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes)) { - SessionData.serializeAttributes(data, oos); - byte[] bytes = baos.toByteArray(); - try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes)) - { - statement.setBinaryStream(7, bais, bytes.length);//attribute map as blob - } + statement.setBinaryStream(7, bais, bytes.length);//attribute map as blob } } - else - { - statement.setBinaryStream(7, EMPTY, 0); - } + statement.executeUpdate(); if (LOG.isDebugEnabled()) diff --git a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/JDBCSessionDataStoreTest.java b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/JDBCSessionDataStoreTest.java index 77ed3a2eab1..845ddc750e7 100644 --- a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/JDBCSessionDataStoreTest.java +++ b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/JDBCSessionDataStoreTest.java @@ -49,19 +49,16 @@ public class JDBCSessionDataStoreTest extends AbstractSessionDataStoreTest public void persistSession(SessionData data) throws Exception { - JdbcTestHelper.insertSession(data.getId(), data.getContextPath(), data.getVhost(), data.getLastNode(), - data.getCreated(), data.getAccessed(), data.getLastAccessed(), - data.getMaxInactiveMs(), data.getExpiry(), data.getCookieSet(), - data.getLastSaved(), data.getAllAttributes()); + JdbcTestHelper.insertSession(data); } @Override public void persistUnreadableSession(SessionData data) throws Exception { - JdbcTestHelper.insertSession(data.getId(), data.getContextPath(), data.getVhost(), data.getLastNode(), + JdbcTestHelper.insertUnreadableSession(data.getId(), data.getContextPath(), data.getVhost(), data.getLastNode(), data.getCreated(), data.getAccessed(), data.getLastAccessed(), data.getMaxInactiveMs(), data.getExpiry(), data.getCookieSet(), - data.getLastSaved(), null); + data.getLastSaved()); } @Override diff --git a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/JdbcTestHelper.java b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/JdbcTestHelper.java index 686e120d49e..ec9b2235e99 100644 --- a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/JdbcTestHelper.java +++ b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/JdbcTestHelper.java @@ -29,7 +29,6 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.HashSet; -import java.util.Map; import java.util.Set; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; @@ -131,6 +130,35 @@ public class JdbcTestHelper sessionTableSchema.prepareTables(); } + + public static void dumpRow(ResultSet row) throws SQLException + { + if (row != null) + { + String id = row.getString(ID_COL); + long created = row.getLong(CREATE_COL); + long accessed = row.getLong(ACCESS_COL); + long lastAccessed = row.getLong(LAST_ACCESS_COL); + long maxIdle = row.getLong(MAX_IDLE_COL); + long cookieSet = row.getLong(COOKIE_COL); + String node = row.getString(LAST_NODE_COL); + long expires = row.getLong(EXPIRY_COL); + long lastSaved = row.getLong(LAST_SAVE_COL); + String context = row.getString(CONTEXT_COL); + Blob blob = row.getBlob(MAP_COL); + + String dump = "id=" + id + + " ctxt=" + context + + " node=" + node + + " exp=" + expires + + " acc=" + accessed + + " lacc=" + lastAccessed + + " ck=" + cookieSet + + " lsv=" + lastSaved + + " blob length=" + blob.length(); + System.err.println(dump); + } + } public static boolean existsInSessionTable(String id, boolean verbose) throws Exception @@ -151,6 +179,7 @@ public class JdbcTestHelper while (result.next()) { results = true; + dumpRow(result); } return results; } @@ -232,41 +261,53 @@ public class JdbcTestHelper return true; } - - public static void insertSession(String id, String contextPath, String vhost) - throws Exception + + public static void insertSession(SessionData data) throws Exception { + Class.forName(DRIVER_CLASS); try (Connection con = DriverManager.getConnection(DEFAULT_CONNECTION_URL);) { PreparedStatement statement = con.prepareStatement("insert into " + TABLE + " (" + ID_COL + ", " + CONTEXT_COL + ", virtualHost, " + LAST_NODE_COL + ", " + ACCESS_COL + ", " + LAST_ACCESS_COL + ", " + CREATE_COL + ", " + COOKIE_COL + - ", " + LAST_SAVE_COL + ", " + EXPIRY_COL + " " + ") " + - " values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + ", " + LAST_SAVE_COL + ", " + EXPIRY_COL + ", " + MAX_IDLE_COL + "," + MAP_COL + " ) " + + " values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); - statement.setString(1, id); - statement.setString(2, contextPath); - statement.setString(3, vhost); - statement.setString(4, "0"); + statement.setString(1, data.getId()); + statement.setString(2, data.getContextPath()); + statement.setString(3, data.getVhost()); + statement.setString(4, data.getLastNode()); - statement.setLong(5, System.currentTimeMillis()); - statement.setLong(6, System.currentTimeMillis()); - statement.setLong(7, System.currentTimeMillis()); - statement.setLong(8, System.currentTimeMillis()); + statement.setLong(5, data.getAccessed()); + statement.setLong(6, data.getLastAccessed()); + statement.setLong(7, data.getCreated()); + statement.setLong(8, data.getCookieSet()); - statement.setLong(9, System.currentTimeMillis()); - statement.setLong(10, System.currentTimeMillis()); + statement.setLong(9, data.getLastSaved()); + statement.setLong(10, data.getExpiry()); + statement.setLong(11, data.getMaxInactiveMs()); + + try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos);) + { + SessionData.serializeAttributes(data, oos); + byte[] bytes = baos.toByteArray(); + try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes);) + { + statement.setBinaryStream(12, bais, bytes.length); + } + } statement.execute(); assertEquals(1, statement.getUpdateCount()); } } - public static void insertSession(String id, String contextPath, String vhost, + public static void insertUnreadableSession(String id, String contextPath, String vhost, String lastNode, long created, long accessed, long lastAccessed, long maxIdle, long expiry, - long cookieSet, long lastSaved, Map attributes) + long cookieSet, long lastSaved) throws Exception { Class.forName(DRIVER_CLASS); @@ -292,23 +333,7 @@ public class JdbcTestHelper statement.setLong(10, expiry); statement.setLong(11, maxIdle); - if (attributes != null) - { - SessionData tmp = new SessionData(id, contextPath, vhost, created, accessed, lastAccessed, maxIdle); - try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); - ObjectOutputStream oos = new ObjectOutputStream(baos);) - { - SessionData.serializeAttributes(tmp, oos); - byte[] bytes = baos.toByteArray(); - - try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes);) - { - statement.setBinaryStream(12, bais, bytes.length); - } - } - } - else - statement.setBinaryStream(12, new ByteArrayInputStream("".getBytes()), 0); + statement.setBinaryStream(12, new ByteArrayInputStream("".getBytes()), 0); statement.execute(); assertEquals(1, statement.getUpdateCount()); diff --git a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/SessionTableSchemaTest.java b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/SessionTableSchemaTest.java index 3497d281627..a6755e7754f 100644 --- a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/SessionTableSchemaTest.java +++ b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/SessionTableSchemaTest.java @@ -20,6 +20,7 @@ package org.eclipse.jetty.server.session; import java.io.ByteArrayInputStream; import java.sql.Connection; +import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -70,6 +71,46 @@ public class SessionTableSchemaTest JdbcTestHelper.shutdown(null); } + /** + * This inserts a session into the db that does not set the session attributes MAP column. As such + * this results in a row that is unreadable by the JDBCSessionDataStore, but is readable by using + * only jdbc api, which is what this test does. + * + * @param id id of session + * @param contextPath the context path of the session + * @param vhost the virtual host of the session + * @throws Exception + */ + public static void insertSessionWithoutAttributes(String id, String contextPath, String vhost) + throws Exception + { + Class.forName(JdbcTestHelper.DRIVER_CLASS); + try (Connection con = DriverManager.getConnection(JdbcTestHelper.DEFAULT_CONNECTION_URL);) + { + PreparedStatement statement = con.prepareStatement("insert into " + JdbcTestHelper.TABLE + + " (" + JdbcTestHelper.ID_COL + ", " + JdbcTestHelper.CONTEXT_COL + ", virtualHost, " + JdbcTestHelper.LAST_NODE_COL + + ", " + JdbcTestHelper.ACCESS_COL + ", " + JdbcTestHelper.LAST_ACCESS_COL + ", " + JdbcTestHelper.CREATE_COL + ", " + JdbcTestHelper.COOKIE_COL + + ", " + JdbcTestHelper.LAST_SAVE_COL + ", " + JdbcTestHelper.EXPIRY_COL + " " + ") " + + " values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + + statement.setString(1, id); + statement.setString(2, contextPath); + statement.setString(3, vhost); + statement.setString(4, "0"); + + statement.setLong(5, System.currentTimeMillis()); + statement.setLong(6, System.currentTimeMillis()); + statement.setLong(7, System.currentTimeMillis()); + statement.setLong(8, System.currentTimeMillis()); + + statement.setLong(9, System.currentTimeMillis()); + statement.setLong(10, System.currentTimeMillis()); + + statement.execute(); + assertEquals(1, statement.getUpdateCount()); + } + } + @Test public void testLoad() throws Exception @@ -79,7 +120,7 @@ public class SessionTableSchemaTest _tableSchema.prepareTables(); //insert a fake session at the root context - JdbcTestHelper.insertSession("1234", "/", "0.0.0.0"); + insertSessionWithoutAttributes("1234", "/", "0.0.0.0"); //test if it can be seen try (Connection con = _da.getConnection()) @@ -104,7 +145,7 @@ public class SessionTableSchemaTest _tableSchema.prepareTables(); //insert a fake session at the root context - JdbcTestHelper.insertSession("1234", "/", "0.0.0.0"); + insertSessionWithoutAttributes("1234", "/", "0.0.0.0"); //test if it can be seen try (Connection con = _da.getConnection()) @@ -128,7 +169,7 @@ public class SessionTableSchemaTest _tableSchema.prepareTables(); //insert a fake session at the root context - JdbcTestHelper.insertSession("1234", "/", "0.0.0.0"); + insertSessionWithoutAttributes("1234", "/", "0.0.0.0"); //test if it can be deleted try (Connection con = _da.getConnection()) @@ -152,7 +193,7 @@ public class SessionTableSchemaTest _tableSchema.prepareTables(); //insert a fake session at the root context - JdbcTestHelper.insertSession("1234", "/", "0.0.0.0"); + insertSessionWithoutAttributes("1234", "/", "0.0.0.0"); try (Connection con = _da.getConnection()) { @@ -178,7 +219,7 @@ public class SessionTableSchemaTest _tableSchema.prepareTables(); //insert a fake session at the root context - JdbcTestHelper.insertSession("1234", "/", "0.0.0.0"); + insertSessionWithoutAttributes("1234", "/", "0.0.0.0"); try (Connection con = _da.getConnection()) { @@ -203,7 +244,7 @@ public class SessionTableSchemaTest _tableSchema.prepareTables(); //insert a fake session at the root context - JdbcTestHelper.insertSession("1234", "/", "0.0.0.0"); + insertSessionWithoutAttributes("1234", "/", "0.0.0.0"); try (Connection con = _da.getConnection()) { diff --git a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractSessionDataStoreTest.java b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractSessionDataStoreTest.java index 7e3dd6210b4..1b5994f23ae 100644 --- a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractSessionDataStoreTest.java +++ b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractSessionDataStoreTest.java @@ -427,7 +427,72 @@ public abstract class AbstractSessionDataStoreTest //expected exception } } + + + /** + * Test that a session containing no attributes can be stored and re-read + * @throws Exception + */ + @Test + public void testEmptyLoadSession() throws Exception + { + //create the SessionDataStore + ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); + context.setContextPath("/test"); + SessionDataStoreFactory factory = createSessionDataStoreFactory(); + ((AbstractSessionDataStoreFactory)factory).setGracePeriodSec(GRACE_PERIOD_SEC); + SessionDataStore store = factory.getSessionDataStore(context.getSessionHandler()); + SessionContext sessionContext = new SessionContext("foo", context.getServletContext()); + store.initialize(sessionContext); + store.start(); + + //persist a session that has no attributes + long now = System.currentTimeMillis(); + SessionData data = store.newSessionData("222", 100, now, now - 1, -1); + data.setLastNode(sessionContext.getWorkerName()); + //persistSession(data); + store.store("222", data); + //test that we can retrieve it + SessionData savedSession = store.load("222"); + assertEquals(0, savedSession.getAllAttributes().size()); + } + + //Test that a session that had attributes can be modified to contain no + //attributes, and still read + @Test + public void testModifyEmptyLoadSession() throws Exception + { + //create the SessionDataStore + ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); + context.setContextPath("/test"); + SessionDataStoreFactory factory = createSessionDataStoreFactory(); + ((AbstractSessionDataStoreFactory)factory).setGracePeriodSec(GRACE_PERIOD_SEC); + SessionDataStore store = factory.getSessionDataStore(context.getSessionHandler()); + SessionContext sessionContext = new SessionContext("foo", context.getServletContext()); + store.initialize(sessionContext); + store.start(); + + //persist a session that has attributes + long now = System.currentTimeMillis(); + SessionData data = store.newSessionData("222", 100, now, now - 1, -1); + data.setAttribute("foo", "bar"); + data.setLastNode(sessionContext.getWorkerName()); + store.store("222", data); + + //test that we can retrieve it + SessionData savedSession = store.load("222"); + assertEquals("bar", savedSession.getAttribute("foo")); + + //now modify so there are no attributes + savedSession.setAttribute("foo", null); + store.store("222", savedSession); + + //check its still readable + savedSession = store.load("222"); + assertEquals(0, savedSession.getAllAttributes().size()); + } + /** * Test that we can delete a persisted session. */ From ef9e15511834ed8d85b3d1a07145e2b122e76d53 Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Tue, 31 Mar 2020 14:26:25 +0200 Subject: [PATCH 019/101] Issue #2074 no merge query string (#4730) * Issue #4713 Async dispatch with query. + Preserve the entire URI with query when startAsync(req,res) is used. + merge any query string from dispatch path with either original query or preserved query from forward Signed-off-by: Greg Wilkins * Issue #4713 asyncDispatch with query parameters Signed-off-by: Greg Wilkins * Issue #2074 Do not merge query strings Do not merge the query string itself, only the parameter map. The query string is either the original or replaced by the one from the dispatch. Signed-off-by: Greg Wilkins --- .../org/eclipse/jetty/server/Request.java | 35 ++----------------- .../AsyncContextDispatchWithQueryStrings.java | 4 +-- .../jetty/servlet/AsyncServletTest.java | 14 ++++---- .../jetty/servlet/DispatcherForwardTest.java | 21 +++++------ .../eclipse/jetty/servlet/DispatcherTest.java | 6 ++-- 5 files changed, 22 insertions(+), 58 deletions(-) diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java index 07f2d318be4..0572a6abd16 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java @@ -2368,7 +2368,7 @@ public class Request implements HttpServletRequest if (newQueryParams == null || newQueryParams.size() == 0) mergedQueryParams = oldQueryParams == null ? NO_PARAMS : oldQueryParams; else if (oldQueryParams == null || oldQueryParams.size() == 0) - mergedQueryParams = newQueryParams == null ? NO_PARAMS : newQueryParams; + mergedQueryParams = newQueryParams; else { // Parameters values are accumulated. @@ -2380,38 +2380,7 @@ public class Request implements HttpServletRequest resetParameters(); if (updateQueryString) - { - if (newQuery == null) - setQueryString(oldQuery); - else if (oldQuery == null) - setQueryString(newQuery); - else if (oldQueryParams.keySet().stream().anyMatch(newQueryParams.keySet()::contains)) - { - // Build the new merged query string, parameters in the - // new query string hide parameters in the old query string. - StringBuilder mergedQuery = new StringBuilder(); - if (newQuery != null) - mergedQuery.append(newQuery); - for (Map.Entry> entry : mergedQueryParams.entrySet()) - { - if (newQueryParams != null && newQueryParams.containsKey(entry.getKey())) - continue; - for (String value : entry.getValue()) - { - if (mergedQuery.length() > 0) - mergedQuery.append("&"); - URIUtil.encodePath(mergedQuery, entry.getKey()); - mergedQuery.append('='); - URIUtil.encodePath(mergedQuery, value); - } - } - setQueryString(mergedQuery.toString()); - } - else - { - setQueryString(newQuery + '&' + oldQuery); - } - } + setQueryString(newQuery == null ? oldQuery : newQuery); } @Override diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncContextDispatchWithQueryStrings.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncContextDispatchWithQueryStrings.java index 00fb163cd7d..b174c80a9e5 100644 --- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncContextDispatchWithQueryStrings.java +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncContextDispatchWithQueryStrings.java @@ -105,14 +105,14 @@ public class AsyncContextDispatchWithQueryStrings { AsyncContext async = request.startAsync(); async.dispatch("/secondDispatchNewValueForExistingQueryString?newQueryString=newValue"); - assertEquals("newQueryString=initialValue&initialParam=right", queryString); + assertEquals("newQueryString=initialValue", queryString); } else { response.setContentType("text/html"); response.setStatus(HttpServletResponse.SC_OK); response.getWriter().println("

woohhooooo

"); - assertEquals("newQueryString=newValue&initialParam=right", queryString); + assertEquals("newQueryString=newValue", queryString); } } } diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletTest.java index f04cffee271..5db4e65f897 100644 --- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletTest.java +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletTest.java @@ -533,12 +533,12 @@ public class AsyncServletTest assertThat(response, startsWith("HTTP/1.1 200 OK")); assertThat(__history, contains( "FWD REQUEST /ctx/fwd/info?start=200&dispatch=20", - "FORWARD /ctx/path1?forward=true&start=200&dispatch=20", + "FORWARD /ctx/path1?forward=true", "initial", "start", "dispatch", "FWD ASYNC /ctx/fwd/info?start=200&dispatch=20", - "FORWARD /ctx/path1?forward=true&start=200&dispatch=20", + "FORWARD /ctx/path1?forward=true", "!initial", "onComplete")); assertContains("DISPATCHED", response); @@ -551,7 +551,7 @@ public class AsyncServletTest assertThat(response, startsWith("HTTP/1.1 200 OK")); assertThat(__history, contains( "FWD REQUEST /ctx/fwd/info?start=200&dispatch=20&path=/path2", - "FORWARD /ctx/path1?forward=true&start=200&dispatch=20&path=/path2", + "FORWARD /ctx/path1?forward=true", "initial", "start", "dispatch", @@ -568,11 +568,11 @@ public class AsyncServletTest assertThat(response, startsWith("HTTP/1.1 200 OK")); assertThat(__history, contains( "FWD REQUEST /ctx/fwd/info?wrap=true&start=200&dispatch=20", - "FORWARD /ctx/path1?forward=true&wrap=true&start=200&dispatch=20", + "FORWARD /ctx/path1?forward=true", "initial", "start", "dispatch", - "ASYNC /ctx/path1?forward=true&wrap=true&start=200&dispatch=20", + "ASYNC /ctx/path1?forward=true", "wrapped REQ RSP", "!initial", "onComplete")); @@ -586,11 +586,11 @@ public class AsyncServletTest assertThat(response, startsWith("HTTP/1.1 200 OK")); assertThat(__history, contains( "FWD REQUEST /ctx/fwd/info?wrap=true&start=200&dispatch=20&path=/path2", - "FORWARD /ctx/path1?forward=true&wrap=true&start=200&dispatch=20&path=/path2", + "FORWARD /ctx/path1?forward=true", "initial", "start", "dispatch", - "ASYNC /ctx/path2?forward=true&wrap=true&start=200&dispatch=20&path=/path2", + "ASYNC /ctx/path2?forward=true", "wrapped REQ RSP", "!initial", "onComplete")); diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DispatcherForwardTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DispatcherForwardTest.java index b73f73320e7..4e933e75ba9 100644 --- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DispatcherForwardTest.java +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DispatcherForwardTest.java @@ -142,7 +142,6 @@ public class DispatcherForwardTest CountDownLatch latch = new CountDownLatch(1); final String query1 = "a=1%20one&b=2%20two"; final String query2 = "a=3%20three"; - final String query3 = "a=3%20three&b=2%20two"; servlet1 = new HttpServlet() { @Override @@ -163,7 +162,7 @@ public class DispatcherForwardTest @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - checkThat(req.getQueryString(), Matchers.equalTo(query3)); + checkThat(req.getQueryString(), Matchers.equalTo(query2)); checkThat(req.getParameter("a"), Matchers.equalTo("3 three")); checkThat(req.getParameter("b"), Matchers.equalTo("2 two")); } @@ -186,13 +185,12 @@ public class DispatcherForwardTest { // 1. request /one?a=1 // 1. forward /two?b=2 - // 2. assert query => a=1&b=2 + // 2. assert query => b=2 // 1. assert query => a=1 CountDownLatch latch = new CountDownLatch(1); final String query1 = "a=1%20one"; final String query2 = "b=2%20two"; - final String query3 = "b=2%20two&a=1%20one"; servlet1 = new HttpServlet() { @Override @@ -212,7 +210,7 @@ public class DispatcherForwardTest @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - checkThat(req.getQueryString(), Matchers.equalTo(query3)); + checkThat(req.getQueryString(), Matchers.equalTo(query2)); checkThat(req.getParameter("a"), Matchers.equalTo("1 one")); checkThat(req.getParameter("b"), Matchers.equalTo("2 two")); } @@ -348,13 +346,12 @@ public class DispatcherForwardTest { // 1. request /one?a=1 + content b=2 // 1. forward /two?c=3 - // 2. assert query => a=1&c=3 + params => a=1&b=2&c=3 + // 2. assert query => c=3 + params => a=1&b=2&c=3 // 1. assert query => a=1 + params => a=1&b=2 CountDownLatch latch = new CountDownLatch(1); final String query1 = "a=1%20one"; final String query2 = "c=3%20three"; - final String query3 = "c=3%20three&a=1%20one"; final String form = "b=2%20two"; servlet1 = new HttpServlet() { @@ -377,7 +374,7 @@ public class DispatcherForwardTest @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - checkThat(req.getQueryString(), Matchers.equalTo(query3)); + checkThat(req.getQueryString(), Matchers.equalTo(query2)); checkThat(req.getParameter("a"), Matchers.equalTo("1 one")); checkThat(req.getParameter("b"), Matchers.equalTo("2 two")); checkThat(req.getParameter("c"), Matchers.equalTo("3 three")); @@ -405,13 +402,12 @@ public class DispatcherForwardTest // 1. request /one?a=1 + content b=2 // 1. assert params => a=1&b=2 // 1. forward /two?c=3 - // 2. assert query => a=1&c=3 + params => a=1&b=2&c=3 + // 2. assert query => c=3 + params => a=1&b=2&c=3 // 1. assert query => a=1 + params => a=1&b=2 CountDownLatch latch = new CountDownLatch(1); final String query1 = "a=1%20one"; final String query2 = "c=3%20three"; - final String query3 = "c=3%20three&a=1%20one"; final String form = "b=2%20two"; servlet1 = new HttpServlet() { @@ -436,7 +432,7 @@ public class DispatcherForwardTest @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - checkThat(req.getQueryString(), Matchers.equalTo(query3)); + checkThat(req.getQueryString(), Matchers.equalTo(query2)); checkThat(req.getParameter("a"), Matchers.equalTo("1 one")); checkThat(req.getParameter("b"), Matchers.equalTo("2 two")); checkThat(req.getParameter("c"), Matchers.equalTo("3 three")); @@ -513,7 +509,6 @@ public class DispatcherForwardTest CountDownLatch latch = new CountDownLatch(1); final String query1 = "a=1%20one"; final String query2 = "b=2%20two"; - final String query3 = "b=2%20two&a=1%20one"; final String form = "c=3%20three"; servlet1 = new HttpServlet() { @@ -534,7 +529,7 @@ public class DispatcherForwardTest @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - checkThat(req.getQueryString(), Matchers.equalTo(query3)); + checkThat(req.getQueryString(), Matchers.equalTo(query2)); ServletInputStream input = req.getInputStream(); for (int i = 0; i < form.length(); ++i) { diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DispatcherTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DispatcherTest.java index 9cacec898b7..8c886a54149 100644 --- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DispatcherTest.java +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DispatcherTest.java @@ -743,7 +743,7 @@ public class DispatcherTest assertEquals(null, request.getPathInfo()); assertEquals(null, request.getPathTranslated()); - assertEquals("do=end&do=the&test=1", request.getQueryString()); + assertEquals("do=end&do=the", request.getQueryString()); assertEquals("/context/AssertForwardServlet", request.getRequestURI()); assertEquals("/context", request.getContextPath()); assertEquals("/AssertForwardServlet", request.getServletPath()); @@ -788,8 +788,8 @@ public class DispatcherTest q2.decode(query.getString("else")); String russian = q2.encode(); assertThat(russian, is("%D0%B2%D1%8B%D0%B1%D1%80%D0%B0%D0%BD%D0%BE=%D0%A2%D0%B5%D0%BC%D0%BF%D0%B5%D1%80%D0%B0%D1%82%D1%83%D1%80%D0%B0")); - assertThat(query.getString("test"), is("1")); - assertThat(query.containsKey("foreign"), is(true)); + assertThat(query.containsKey("test"), is(false)); + assertThat(query.containsKey("foreign"), is(false)); String[] vals = request.getParameterValues("foreign"); assertTrue(vals != null); From 9a6ad8af62dbe5956465ab9cc071a459c151fbda Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Tue, 31 Mar 2020 17:53:28 +0200 Subject: [PATCH 020/101] Improvements to the Jetty client documentation. Signed-off-by: Simone Bordet --- jetty-documentation/pom.xml | 16 + ...ient-concepts.adoc => client-io-arch.adoc} | 123 +++++--- .../embedded-guide/client/client.adoc | 20 +- .../http/client-http-api.adoc} | 0 .../http/client-http-authentication.adoc} | 4 +- .../http/client-http-configuration.adoc | 86 +++++ .../http/client-http-cookie.adoc} | 2 +- .../client/http/client-http-intro.adoc | 175 +++++++++++ .../http/client-http-proxy.adoc} | 8 +- .../http/client-http-transport.adoc} | 0 .../client/http/client-http.adoc | 9 + .../main/asciidoc/embedded-guide/io-arch.adoc | 2 + .../server/clients/http/chapter.adoc | 27 -- .../clients/http/http-client-intro.adoc | 105 ------- .../embedded/client/ClientConnectorDocs.java | 293 +++++++++++++++++- .../embedded/client/http/HTTPClientDocs.java | 82 +++++ .../org/eclipse/jetty/io/ClientConnector.java | 23 +- .../org/eclipse/jetty/io/ManagedSelector.java | 5 +- .../org/eclipse/jetty/io/SelectorManager.java | 4 +- 19 files changed, 781 insertions(+), 203 deletions(-) rename jetty-documentation/src/main/asciidoc/embedded-guide/client/{client-concepts.adoc => client-io-arch.adoc} (54%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server/clients/http/http-client-api.adoc => client/http/client-http-api.adoc} (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server/clients/http/http-client-authentication.adoc => client/http/client-http-authentication.adoc} (97%) create mode 100644 jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-configuration.adoc rename jetty-documentation/src/main/asciidoc/embedded-guide/{server/clients/http/http-client-cookie.adoc => client/http/client-http-cookie.adoc} (99%) create mode 100644 jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-intro.adoc rename jetty-documentation/src/main/asciidoc/embedded-guide/{server/clients/http/http-client-proxy.adoc => client/http/client-http-proxy.adoc} (96%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server/clients/http/http-client-transport.adoc => client/http/client-http-transport.adoc} (100%) delete mode 100644 jetty-documentation/src/main/asciidoc/embedded-guide/server/clients/http/chapter.adoc delete mode 100644 jetty-documentation/src/main/asciidoc/embedded-guide/server/clients/http/http-client-intro.adoc create mode 100644 jetty-documentation/src/main/java/embedded/client/http/HTTPClientDocs.java diff --git a/jetty-documentation/pom.xml b/jetty-documentation/pom.xml index 4661ac8c479..000675ded84 100644 --- a/jetty-documentation/pom.xml +++ b/jetty-documentation/pom.xml @@ -49,6 +49,11 @@ + + org.eclipse.jetty + jetty-client + ${project.version} + org.eclipse.jetty jetty-io @@ -111,6 +116,13 @@ org.asciidoctor asciidoctor-maven-plugin ${asciidoctor.maven.plugin.version} + + + org.asciidoctor + asciidoctorj-diagram + 2.0.1 + + http://www.eclipse.org/jetty/javadoc/${javadoc.version} @@ -183,6 +195,10 @@ ${basedir}/src/main/asciidoc/embedded-guide index.adoc ${project.build.directory}/html/embedded-guide + coderay + + asciidoctor-diagram +
diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/client/client-concepts.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/client-io-arch.adoc similarity index 54% rename from jetty-documentation/src/main/asciidoc/embedded-guide/client/client-concepts.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/client/client-io-arch.adoc index bf63bce5013..f2afffd946e 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/client/client-concepts.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/client-io-arch.adoc @@ -16,32 +16,25 @@ // ======================================================================== // -[[client-concepts]] -=== Client Libraries Concepts +[[client-io-arch]] +=== Client Libraries Architecture -The Jetty client libraries implement a network client speaking different protocols -such as HTTP/1.1, HTTP/2, WebSocket and FastCGI. +The Jetty client libraries provide the basic components and APIs to implement +a network client. -It is possible to implement your own custom protocol on top of the Jetty client -libraries. +They build on the common link:#io-arch[Jetty I/O Architecture] and provide client +specific concepts (such as establishing a connection to a server). -NOTE: TODO: perhaps add a section about this. +There are conceptually two layers that compose the Jetty client libraries: -There are conceptually three layers that compose the Jetty client libraries, from -more abstract to more concrete: +. link:#client-io-arch-network[The network layer], that handles the low level +I/O and deals with buffers, threads, etc. +. link:#client-io-arch-protocol[The protocol layer], that handles the parsing +of bytes read from the network and the generation of bytes to write to the +network. -. The API layer, that exposes semantic APIs to applications so that they can write -code such as "GET me the resource at this URI". -. The protocol layer, where the API request is converted into the appropriate -protocol bytes, for example encrypted HTTP/2. -. The infrastructure layer, that handles the low level I/O and deals with network, -buffer, threads, etc. - -Let's look at these layers starting from the more concrete (and low level) one -and build up to the more abstract layer. - -[[client-concepts-infrastructure]] -==== Client Libraries Infrastructure Layer +[[client-io-arch-network]] +==== Client Libraries Network Layer The Jetty client libraries use the common I/O design described in link:#io-arch[this section]. @@ -78,7 +71,7 @@ include::../{doc_code}/embedded/client/ClientConnectorDocs.java[tags=typical] ---- A more advanced example that customizes the `ClientConnector` by overriding -factory methods: +some of its methods: [source,java,indent=0] ---- @@ -86,7 +79,7 @@ include::../{doc_code}/embedded/client/ClientConnectorDocs.java[tags=advanced] ---- Since `ClientConnector` is the component that handles the low-level network, it -is also the component where you want to configure the low-leven network configuration. +is also the component where you want to configure the low-level network configuration. The most common parameters are: @@ -122,28 +115,82 @@ Please refer to the `ClientConnector` link:{JDURL}/org/eclipse/jetty/io/ClientConnector.html[javadocs] for the complete list of configurable parameters. -Once the `ClientConnector` is configured and started, it can be used to connect -to the server via `ClientConnector.connect(SocketAddress, Map)` -which in turn will call `SocketChannel.connect(SocketAddress)`. +[[client-io-arch-protocol]] +==== Client Libraries Protocol Layer -When establishing a TCP connection to a server, applications need to tell +The protocol layer builds on top of the network layer to generate the +bytes to be written to the network and to parse the bytes read from the +network. + +Recall from link:#io-arch-connection[this section] that Jetty uses the +`Connection` abstraction to produce and interpret the network bytes. + +On the client side, a `ClientConnectionFactory` implementation is the +component that creates `Connection` instances based on the protocol that +the client wants to "speak" with the server. + +Applications use `ClientConnector.connect(SocketAddress, Map)` +to establish a TCP connection to the server, and must tell `ClientConnector` how to create the `Connection` for that particular -TCP connection. -This is done via a -link:{JDURL}/org/eclipse/jetty/io/ClientConnectionFactory.html[`ClientConnectionFactory`]. -that must be passed in the context `Map` as follows: +TCP connection, and how to notify back the application when the connection +creation succeeds or fails. + +This is done by passing a +link:{JDURL}/org/eclipse/jetty/io/ClientConnectionFactory.html[`ClientConnectionFactory`] +(that creates `Connection` instances) and a +link:{JDURL}/org/eclipse/jetty/util/Promise.html[`Promise`] (that is notified +of connection creation success or failure) in the context `Map` as follows: [source,java,indent=0] ---- include::../{doc_code}/embedded/client/ClientConnectorDocs.java[tags=connect] ---- -TODO: expand on what is the API to use, what parameters the context Map must -have, and basically how we can write a generic network client with it. +When a `Connection` is created successfully, its `onOpen()` method is invoked, +and then the promise is completed successfully. -[[client-concepts-protocol]] -==== Client Libraries Protocol Layer +It is now possible to write a super-simple `telnet` client that reads and writes +string lines: -The protocol layer builds on top of the infrastructure layer to generate the -bytes to be written to the network and to parse the bytes received from the -network. +[source,java,indent=0] +---- +include::../{doc_code}/embedded/client/ClientConnectorDocs.java[tags=telnet] +---- + +Note how a very basic "telnet" API that applications could use is implemented +in the form of the `onLine(Consumer)` for the non-blocking receiving +side and `writeLine(String, Callback)` for the non-blocking sending side. +Note also how the `onFillable()` method implements some basic "parsing" +by looking up the `\n` character in the buffer. + +NOTE: The "telnet" client above looks like a super-simple HTTP client because +HTTP/1.0 can be seen as a line-based protocol. HTTP/1.0 was used just as an +example, but we could have used any other line-based protocol such as +link:https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol[SMTP], +provided that the server was able to understand it. + +This is very similar to what the Jetty client implementation does for real +network protocols. +Real network protocols are of course more complicated and so is the implementation +code that handles them, but the general ideas are similar. + +The Jetty client implementation provides a number of `ClientConnectionFactory` +implementations that can be composed to produce and interpret the network bytes. + +For example, it is simple to modify the above example to use the TLS protocol +so that you will be able to connect to the server on port `443`, typically +reserved for the encrypted HTTP protocol. + +The differences between the clear-text version and the TLS encrypted version +are minimal: + +[source,java,indent=0] +---- +include::../{doc_code}/embedded/client/ClientConnectorDocs.java[tags=tlsTelnet] +---- + +The differences with the clear-text version are only: + +* Change the port from `80` to `443`. +* Wrap the `ClientConnectionFactory` with `SslClientConnectionFactory`. +* Unwrap the `SslConnection` to access `TelnetConnection`. diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/client/client.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/client.adoc index 4673ae1e6b5..ac1bd6e9170 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/client/client.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/client.adoc @@ -17,12 +17,15 @@ // [[client]] -== Jetty Client Libraries +== Client Libraries -The Eclipse Jetty Project provides not only server-side libraries so that you -can embed a server in your code, but it also provides client-side libraries -that allow you to embed a client - for example a HTTP client invoking a third -party HTTP service - in your application. +The Eclipse Jetty Project provides also provides client-side libraries +that allow you to embed a client in your applications. +A typical example is an application that needs to contact a third party +service via HTTP (for example a REST service). +Another example is a proxy application that receives HTTP requests and +forwards them as FCGI requests to a PHP application such as WordPress, +or receives HTTP/1.1 requests and converts them to HTTP/2. The client libraries are designed to be non-blocking and offer both synchronous and asynchronous APIs and come with a large number of configuration options. @@ -32,4 +35,9 @@ There are primarily two client libraries: * link:#client-http[The HTTP client library] * link:#client-websocket[The WebSocket client library] -include::client-concepts.adoc[] +If you are interested in the low-level details of how the Eclipse Jetty +client libraries work, or are interested in writing a custom protocol, +look at the link:#client-io-arch[Client I/O Architecture]. + +include::http/client-http.adoc[] +include::client-io-arch.adoc[] diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/clients/http/http-client-api.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-api.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/clients/http/http-client-api.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-api.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/clients/http/http-client-authentication.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-authentication.adoc similarity index 97% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/clients/http/http-client-authentication.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-authentication.adoc index 8b7515f315f..4e3e48704ae 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/server/clients/http/http-client-authentication.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-authentication.adoc @@ -16,7 +16,7 @@ // ======================================================================== // -[[http-client-authentication]] +[[client-http-authentication]] === Authentication Support Jetty's HTTP client supports the `BASIC` and `DIGEST` authentication mechanisms defined by link:https://tools.ietf.org/html/rfc7235[RFC 7235]. @@ -88,4 +88,4 @@ authn.apply(request); request.send(); ---- -See also the link:#http-client-proxy-authentication[proxy authentication section] for further information about how authentication works with HTTP proxies. +See also the link:#client-http-proxy-authentication[proxy authentication section] for further information about how authentication works with HTTP proxies. diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-configuration.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-configuration.adoc new file mode 100644 index 00000000000..e9a6f0ab7b1 --- /dev/null +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-configuration.adoc @@ -0,0 +1,86 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +[[client-http-configuration]] +=== HttpClient Configuration + +`HttpClient` has a quite large number of configuration parameters. +Please refer to the `HttpClient` +link:{JDURL}/org/eclipse/jetty/client/HttpClient.html[javadocs] +for the complete list of configurable parameters. +The most common parameters are: + +* `HttpClient.idleTimeout`: same as `ClientConnector.idleTimeout` +described in link:#client-io-arch-network[this section]. +* `HttpClient.connectBlocking`: same as `ClientConnector.connectBlocking` +described in link:#client-io-arch-network[this section]. +* `HttpClient.connectTimeout`: same as `ClientConnector.connectTimeout` +described in link:#client-io-arch-network[this section]. +* `HttpClient.maxConnectionsPerDestination`: the max number of TCP +connections that are opened for a particular destination (defaults to 64). +* `HttpClient.maxRequestsQueuedPerDestination`: the max number of requests +queued (defaults to 1024). + +[[client-http-configuration-tls]] +==== HttpClient TLS Configuration + +`HttpClient` supports HTTPS requests out-of-the-box like a browser does. + +The support for HTTPS request is provided by a `SslContextFactory.Client`, +typically configured in the `ClientConnector`. +If not explicitly configured, the `ClientConnector` will allocate a default +one when started. + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tags=tlsExplicit] +---- + +The default `SslContextFactory.Client` verifies the certificate sent by the +server by verifying the certificate chain. +This means that requests to public websites that have a valid certificates +(such as ``https://google.com``) will work out-of-the-box. + +However, requests made to sites (typically ``localhost``) that have invalid +(for example, expired or with a wrong host) or self-signed certificates will +fail (like they will in a browser). + +Certificate validation is performed at two levels: at the TLS implementation +level (in the JDK) and - optionally - at the application level. + +By default, certificate validation at the TLS level is enabled, while +certificate validation at the application level is disabled. + +You can configure the `SslContextFactory.Client` to skip certificate validation +at the TLS level: + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tags=tlsNoValidation] +---- + +You can enable certificate validation at the application level: + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tags=tlsAppValidation] +---- + +Please refer to the `SslContextFactory.Client` +link:{JDURL}/org/eclipse/jetty/util/ssl/SslContextFactory.Client.html[javadocs] +for the complete list of configurable parameters. diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/clients/http/http-client-cookie.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-cookie.adoc similarity index 99% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/clients/http/http-client-cookie.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-cookie.adoc index dbe3193b5c8..3802218cdc8 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/server/clients/http/http-client-cookie.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-cookie.adoc @@ -16,7 +16,7 @@ // ======================================================================== // -[[http-client-cookie]] +[[client-http-cookie]] === Cookies Support Jetty HTTP client supports cookies out of the box. diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-intro.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-intro.adoc new file mode 100644 index 00000000000..34f8c78547f --- /dev/null +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-intro.adoc @@ -0,0 +1,175 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +[[client-http-intro]] +=== HttpClient Introduction + +The Jetty HTTP client module provides easy-to-use APIs and utility classes to perform HTTP (or HTTPS) requests. + +Jetty's HTTP client is non-blocking and asynchronous. +It offers an asynchronous API that never blocks for I/O, making it very efficient in thread utilization and well suited for high performance scenarios such as load testing or parallel computation. + +However, when all you need to do is to perform a `GET` request to a resource, Jetty's HTTP client offers also a synchronous API; a programming interface +where the thread that issued the request blocks until the request/response conversation is complete. + +Jetty's HTTP client supports link:#http-client-transport[different transports]: HTTP/1.1, FastCGI and HTTP/2. +This means that the semantic of a HTTP request (that is, " `GET` me the resource `/index.html` ") can be carried over the network in different formats. +The most common and default format is HTTP/1.1. +That said, Jetty's HTTP client can carry the same request using the FastCGI format or the HTTP/2 format. + +The FastCGI transport is heavily used in Jetty's link:#fastcgi[FastCGI support] that allows Jetty to work as a reverse proxy to PHP (exactly like Apache or Nginx do) and therefore be able to serve - for example - WordPress websites. + +The HTTP/2 transport allows Jetty's HTTP client to perform requests using HTTP/2 to HTTP/2 enabled web sites, see also Jetty's link:#http2[HTTP/2 support]. + +Out of the box features that you get with the Jetty HTTP client include: + +* Redirect support - redirect codes such as 302 or 303 are automatically followed. +* Cookies support - cookies sent by servers are stored and sent back to servers in matching requests. +* Authentication support - HTTP "Basic" and "Digest" authentications are supported, others are pluggable. +* Forward proxy support - HTTP proxying and SOCKS4 proxying. + +[[client-http-start]] +==== Starting HttpClient + +The main class is named `org.eclipse.jetty.client.HttpClient`. + +You can think of a `HttpClient` instance as a browser instance. +Like a browser it can make requests to different domains, it manages redirects, cookies and authentication, you can configure it with a proxy, and +it provides you with the responses to the requests you make. + +In order to use `HttpClient`, you must instantiate it, configure it, and then start it: + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tags=start] +---- + +You may create multiple instances of `HttpClient`, but typically one instance is enough for an application. +There are several reasons for having multiple `HttpClient` instances including, but not limited to: + +* You want to specify different configuration parameters (for example, one instance is configured with a forward proxy while another is not). +* You want the two instances to behave like two different browsers and hence have different cookies, different authentication credentials, etc. +* You want to use link:#http-client-transport[different transports]. + +Like browsers, HTTPS requests are supported out-of-the-box, as long as the server +provides a valid certificate. +In case the server does not provide a valid certificate (or in case it is self-signed) +you want to customize ``HttpClient``'s TLS configuration as described in +link:#client-http-configuration-tls[this section]. + +[[client-http-stop]] +==== Stopping HttpClient + +It is recommended that when your application stops, you also stop the `HttpClient` instance (or instances) that you are using. + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tags=stop] +---- + +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. + +[[client-http-arch]] +==== HttpClient Architecture + +A `HttpClient` instance can be thought as a browser instance, and it manages the +following components: + +* a `CookieStore` (see link:#client-http-cookie[this section]). +* a `AuthenticationStore` (see link:#client-http-authentication[this section]). +* a `ProxyConfiguration` (see link:#client-http-proxy[this section]). +* a set of _destinations_. + +A _destination_ is the client-side component that represent an _origin_ on +a server, and manages a queue of requests for that origin, and a pool of +connections to that origin. + +An _origin_ may be simply thought as the tuple `(scheme, host, port)` and it +is where the client connects to in order to communicate with the server. +However, this is not enough. + +If you use `HttpClient` to write a proxy you may have different clients that +want to contact the same server. +In this case, you may not want to use the same proxy-to-server connection to +proxy requests for both clients, for example for authentication reasons: the +server may associate the connection with authentication credentials and you +do not want to use the same connection for two different users that have +different credentials. +Instead, you want to use different connections for different clients and +this can be achieved by "tagging" a destination with a tag object that +represents the remote client (for example, it could be the remote client IP +address). + +Two origins with the same `(scheme, host, port)` but different `tag` +create two different destinations and therefore two different connection pools. +However, also this is not enough. + +It is possible that a server speaks different protocols on the same `port`. +A connection may start by speaking one protocol, for example HTTP/1.1, but +then be upgraded to speak a different protocol, for example HTTP/2. +After a connection has been upgraded to a second protocol, it cannot speak +the first protocol anymore, so it can only be used to communicate using +the second protocol. + +Two origins with the same `(scheme, host, port)` but different +`protocol` create two different destinations and therefore two different +connection pools. + +Therefore an origin is identified by the tuple +`(scheme, host, port, tag, protocol)`. + +[[client-http-request-processing]] +==== HttpClient Request Processing + +When a request is sent, an origin is computed from the request; `HttpClient` +uses that origin to find (or create if it does not exist) the correspondent +destination. +The request is then queued onto the destination, and this causes the +destination to ask its connection pool for a free connection. +If a connection is available, it is returned, otherwise a new connection is +created. +Once the destination has obtained the connection, it dequeues the request +and sends it over the connection. + +The first request to a destination triggers the opening of the first +connection. +A second request with the same origin sent _after_ the first will reuse the +same connection. +A second request with the same origin sent _concurrently_ with the first +request will cause the opening of a second connection. +The configuration parameter `HttpClient.maxConnectionsPerDestination` +(see also the link:#client-http-configuration[configuration section]) controls +the max number of connections that can be opened for a destination. + +NOTE: If opening connections to a given origin takes a long time, then +requests for that origin will queue up in the corresponding destination. + +Each connection can handle a limited number of requests. +For HTTP/1.1, this number is always `1`: there can only be one outstanding +request for each connection. +For HTTP/2 this number is determined by the server `max_concurrent_stream` +setting (typically around `100`, i.e. there can be up to `100` outstanding +requests for every connection). + +When a destination has maxed out its number of connections, and all +connections have maxed out their number of outstanding requests, more requests +sent to that destination will be queued. +When the request queue is full, the request will be failed. +The configuration parameter `HttpClient.maxRequestsQueuedPerDestination` +(see also the link:#client-http-configuration[configuration section]) controls +the max number of requests that can be queued for a destination. diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/clients/http/http-client-proxy.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-proxy.adoc similarity index 96% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/clients/http/http-client-proxy.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-proxy.adoc index 1d5acd92475..13f53584e93 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/server/clients/http/http-client-proxy.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-proxy.adoc @@ -16,7 +16,7 @@ // ======================================================================== // -[[http-client-proxy]] +[[client-http-proxy]] === Proxy Support Jetty's HTTP client can be configured to use proxies to connect to destinations. @@ -44,10 +44,10 @@ You specify the proxy host and port, and optionally also the addresses that you Configured in this way, `HttpClient` makes requests to the HTTP proxy (for plain-text HTTP requests) or establishes a tunnel via `HTTP CONNECT` (for encrypted HTTPS requests). -[[http-client-proxy-authentication]] +[[client-http-proxy-authentication]] ==== Proxy Authentication Support -Jetty's HTTP client support proxy authentication in the same way it supports link:#http-client-authentication[server authentication]. +Jetty's HTTP client support proxy authentication in the same way it supports link:#client-http-authentication[server authentication]. In the example below, the proxy requires Basic authentication, but the server requires Digest authentication, and therefore: @@ -100,4 +100,4 @@ Application HttpClient Proxy Server The application does not receive events related to the responses with code 407 and 401 since they are handled internally by `HttpClient`. -Similarly to the link:#http-client-authentication[authentication section], the proxy authentication result and the server authentication result can be preempted to avoid, respectively, the 407 and 401 roundtrips. +Similarly to the link:#client-http-authentication[authentication section], the proxy authentication result and the server authentication result can be preempted to avoid, respectively, the 407 and 401 roundtrips. diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/clients/http/http-client-transport.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-transport.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/clients/http/http-client-transport.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-transport.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http.adoc index c1f403bd05e..8d0d71a911e 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http.adoc @@ -18,3 +18,12 @@ [[client-http]] === HTTP Client + +include::client-http-intro.adoc[] +include::client-http-configuration.adoc[] +include::client-http-api.adoc[] +include::client-http-cookie.adoc[] +include::client-http-authentication.adoc[] +include::client-http-proxy.adoc[] +include::client-http-transport.adoc[] + diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/io-arch.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/io-arch.adoc index e083aa1d6ee..178e04eaaa0 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/io-arch.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/io-arch.adoc @@ -261,3 +261,5 @@ Otherwise, the write is now `PENDING` and waiting for the callback to be notified of the completion at a later time. When the callback is notified of the `write()` completion, it checks whether the `write()` was `PENDING`, and if it was it resumes reading. + +NOTE: TODO: Introduce IteratingCallback? diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/clients/http/chapter.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/server/clients/http/chapter.adoc deleted file mode 100644 index fe028abc9b0..00000000000 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/server/clients/http/chapter.adoc +++ /dev/null @@ -1,27 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under -// the terms of the Eclipse Public License 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0 -// -// This Source Code may also be made available under the following -// Secondary Licenses when the conditions for such availability set -// forth in the Eclipse Public License, v. 2.0 are satisfied: -// the Apache License v2.0 which is available at -// https://www.apache.org/licenses/LICENSE-2.0 -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -[[http-client]] -== HTTP Client - -include::http-client-intro.adoc[] -include::http-client-api.adoc[] -include::http-client-cookie.adoc[] -include::http-client-authentication.adoc[] -include::http-client-proxy.adoc[] -include::http-client-transport.adoc[] diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/clients/http/http-client-intro.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/server/clients/http/http-client-intro.adoc deleted file mode 100644 index 3a2f70d9c1b..00000000000 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/server/clients/http/http-client-intro.adoc +++ /dev/null @@ -1,105 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under -// the terms of the Eclipse Public License 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0 -// -// This Source Code may also be made available under the following -// Secondary Licenses when the conditions for such availability set -// forth in the Eclipse Public License, v. 2.0 are satisfied: -// the Apache License v2.0 which is available at -// https://www.apache.org/licenses/LICENSE-2.0 -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -[[http-client-intro]] -=== Introduction - -The Jetty HTTP client module provides easy-to-use APIs and utility classes to perform HTTP (or HTTPS) requests. - -Jetty's HTTP client is non-blocking and asynchronous. -It offers an asynchronous API that never blocks for I/O, making it very efficient in thread utilization and well suited for high performance scenarios such as load testing or parallel computation. - -However, when all you need to do is to perform a `GET` request to a resource, Jetty's HTTP client offers also a synchronous API; a programming interface -where the thread that issued the request blocks until the request/response conversation is complete. - -Jetty's HTTP client supports different link:#http-client-transport[transports]: HTTP/1.1, FastCGI and HTTP/2. -This means that the semantic of a HTTP request (that is, " `GET` me the resource `/index.html` ") can be carried over the network in different formats. -The most common and default format is HTTP/1.1. -That said, Jetty's HTTP client can carry the same request using the FastCGI format or the new HTTP/2 format. - -The FastCGI transport is heavily used in Jetty's link:#fastcgi[FastCGI support] that allows Jetty to work as a reverse proxy to PHP (exactly like Apache or Nginx do) and therefore be able to serve - for example - WordPress websites. - -The HTTP/2 transport allows Jetty's HTTP client to perform requests using HTTP/2 to HTTP/2 enabled web sites, see also Jetty's link:#http2[HTTP/2 support]. - -Out of the box features that you get with the Jetty HTTP client include: - -* Redirect support - redirect codes such as 302 or 303 are automatically followed. -* Cookies support - cookies sent by servers are stored and sent back to servers in matching requests. -* Authentication support - HTTP "Basic" and "Digest" authentications are supported, others are pluggable. -* Forward proxy support - HTTP proxying and SOCKS4 proxying. - -[[http-client-init]] -==== Starting HttpClient - -The main class is named `org.eclipse.jetty.client.HttpClient`. - -You can think of a `HttpClient` instance as a browser instance. -Like a browser it can make requests to different domains, it manages redirects, cookies and authentication, you can configure it with a proxy, and -it provides you with the responses to the requests you make. - -In order to use `HttpClient`, you must instantiate it, configure it, and then start it: - -[source, java, subs="{sub-order}"] ----- -// Instantiate HttpClient -HttpClient httpClient = new HttpClient(); - -// Configure HttpClient, for example: -httpClient.setFollowRedirects(false); - -// Start HttpClient -httpClient.start(); ----- - -You may create multiple instances of `HttpClient`, but typically one instance is enough for an application. -There are several reasons for having multiple `HttpClient` instances including, but not limited to: - -* You want to specify different configuration parameters (for example, one instance is configured with a forward proxy while another is not) -* You want the two instances to behave like two different browsers and hence have different cookies, different authentication credentials...etc. -* You want to use different transports - -When you create a `HttpClient` instance using the parameterless constructor, you will only be able to perform plain HTTP requests and you will not be able to perform HTTPS requests. - -In order to perform HTTPS requests, you should create first a link:{JDURL}/org/eclipse/jetty/util/ssl/SslContextFactory.Client.html[`SslContextFactory.Client`], configure it, and pass it to the `HttpClient` constructor. -When created with a `SslContextFactory`, the `HttpClient` will be able to perform both HTTP and HTTPS requests to any domain. - -[source, java, subs="{sub-order}"] ----- -// Instantiate and configure the SslContextFactory -SslContextFactory.Client sslContextFactory = new SslContextFactory.Client(); - -// Instantiate HttpClient with the SslContextFactory -HttpClient httpClient = new HttpClient(sslContextFactory); - -// Configure HttpClient, for example: -httpClient.setFollowRedirects(false); - -// Start HttpClient -httpClient.start(); ----- - -==== Stopping HttpClient - -It is recommended that when your application stops, you also stop the `HttpClient` instance (or instances) that you are using. - -[source, java, subs="{sub-order}"] ----- -httpClient.stop(); ----- - -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. diff --git a/jetty-documentation/src/main/java/embedded/client/ClientConnectorDocs.java b/jetty-documentation/src/main/java/embedded/client/ClientConnectorDocs.java index 840bb63a0b2..5fcd35c789e 100644 --- a/jetty-documentation/src/main/java/embedded/client/ClientConnectorDocs.java +++ b/jetty-documentation/src/main/java/embedded/client/ClientConnectorDocs.java @@ -18,17 +18,27 @@ package embedded.client; +import java.io.ByteArrayOutputStream; import java.net.InetSocketAddress; import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; +import java.util.function.Consumer; import org.eclipse.jetty.io.AbstractConnection; import org.eclipse.jetty.io.ClientConnectionFactory; import org.eclipse.jetty.io.ClientConnector; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.SelectorManager; +import org.eclipse.jetty.io.ssl.SslClientConnectionFactory; +import org.eclipse.jetty.io.ssl.SslConnection; +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.Promise; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler; @@ -108,9 +118,9 @@ public class ClientConnectorDocs public void connect() throws Exception { // tag::connect[] - class CustomHTTPConnection extends AbstractConnection + class CustomConnection extends AbstractConnection { - public CustomHTTPConnection(EndPoint endPoint, Executor executor) + public CustomConnection(EndPoint endPoint, Executor executor) { super(endPoint, executor); } @@ -119,6 +129,7 @@ public class ClientConnectorDocs public void onOpen() { super.onOpen(); + System.getLogger("connection").log(INFO, "Opened connection {0}", this); } @Override @@ -130,23 +141,289 @@ public class ClientConnectorDocs ClientConnector clientConnector = new ClientConnector(); clientConnector.start(); + String host = "serverHost"; + int port = 8080; + SocketAddress address = new InetSocketAddress(host, port); + + // The ClientConnectionFactory that creates CustomConnection instances. + ClientConnectionFactory connectionFactory = (endPoint, context) -> + { + System.getLogger("connection").log(INFO, "Creating connection for {0}", endPoint); + return new CustomConnection(endPoint, clientConnector.getExecutor()); + }; + + // The Promise to notify of connection creation success or failure. + CompletableFuture connectionPromise = new Promise.Completable<>(); + + // Populate the context with the mandatory keys to create and obtain connections. + Map context = new HashMap<>(); + context.put(ClientConnector.CLIENT_CONNECTION_FACTORY_CONTEXT_KEY, connectionFactory); + context.put(ClientConnector.CONNECTION_PROMISE_CONTEXT_KEY, connectionPromise); + clientConnector.connect(address, context); + + // Use the Connection when it's available. + + // Use it in a non-blocking way via CompletableFuture APIs. + connectionPromise.whenComplete((connection, failure) -> + { + System.getLogger("connection").log(INFO, "Created connection for {0}", connection); + }); + + // Alternatively, you can block waiting for the connection (or a failure). + // CustomConnection connection = connectionPromise.get(); + // end::connect[] + } + + public void telnet() throws Exception + { + // tag::telnet[] + class TelnetConnection extends AbstractConnection + { + private final ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + private Consumer consumer; + + public TelnetConnection(EndPoint endPoint, Executor executor) + { + super(endPoint, executor); + } + + @Override + public void onOpen() + { + super.onOpen(); + + // Declare interest for fill events. + fillInterested(); + } + + @Override + public void onFillable() + { + try + { + ByteBuffer buffer = BufferUtil.allocate(1024); + while (true) + { + int filled = getEndPoint().fill(buffer); + if (filled > 0) + { + while (buffer.hasRemaining()) + { + // Search for newline. + byte read = buffer.get(); + if (read == '\n') + { + // Notify the consumer of the line. + consumer.accept(bytes.toString(StandardCharsets.UTF_8)); + bytes.reset(); + } + else + { + bytes.write(read); + } + } + } + else if (filled == 0) + { + // No more bytes to fill, declare + // again interest for fill events. + fillInterested(); + return; + } + else + { + // The other peer closed the + // connection, close it back. + getEndPoint().close(); + return; + } + } + } + catch (Exception x) + { + getEndPoint().close(x); + } + } + + public void onLine(Consumer consumer) + { + this.consumer = consumer; + } + + public void writeLine(String line, Callback callback) + { + line = line + "\r\n"; + getEndPoint().write(callback, ByteBuffer.wrap(line.getBytes(StandardCharsets.UTF_8))); + } + } + + ClientConnector clientConnector = new ClientConnector(); + clientConnector.start(); + String host = "wikipedia.org"; int port = 80; SocketAddress address = new InetSocketAddress(host, port); ClientConnectionFactory connectionFactory = (endPoint, context) -> - { - System.getLogger("connection").log(INFO, "Creating connection for {0}", endPoint); - return new CustomHTTPConnection(endPoint, clientConnector.getExecutor()); - }; + new TelnetConnection(endPoint, clientConnector.getExecutor()); + + CompletableFuture connectionPromise = new Promise.Completable<>(); + Map context = new HashMap<>(); context.put(ClientConnector.CLIENT_CONNECTION_FACTORY_CONTEXT_KEY, connectionFactory); + context.put(ClientConnector.CONNECTION_PROMISE_CONTEXT_KEY, connectionPromise); clientConnector.connect(address, context); - // end::connect[] + + connectionPromise.whenComplete((connection, failure) -> + { + if (failure == null) + { + // Register a listener that receives string lines. + connection.onLine(line -> System.getLogger("app").log(INFO, "line: {0}", line)); + + // Write a line. + connection.writeLine("" + + "GET / HTTP/1.0\r\n" + + "", Callback.NOOP); + } + else + { + failure.printStackTrace(); + } + }); + // end::telnet[] + } + + public void tlsTelnet() throws Exception + { + // tag::tlsTelnet[] + class TelnetConnection extends AbstractConnection + { + private final ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + private Consumer consumer; + + public TelnetConnection(EndPoint endPoint, Executor executor) + { + super(endPoint, executor); + } + + @Override + public void onOpen() + { + super.onOpen(); + + // Declare interest for fill events. + fillInterested(); + } + + @Override + public void onFillable() + { + try + { + ByteBuffer buffer = BufferUtil.allocate(1024); + while (true) + { + int filled = getEndPoint().fill(buffer); + if (filled > 0) + { + while (buffer.hasRemaining()) + { + // Search for newline. + byte read = buffer.get(); + if (read == '\n') + { + // Notify the consumer of the line. + consumer.accept(bytes.toString(StandardCharsets.UTF_8)); + bytes.reset(); + } + else + { + bytes.write(read); + } + } + } + else if (filled == 0) + { + // No more bytes to fill, declare + // again interest for fill events. + fillInterested(); + return; + } + else + { + // The other peer closed the + // connection, close it back. + getEndPoint().close(); + return; + } + } + } + catch (Exception x) + { + getEndPoint().close(x); + } + } + + public void onLine(Consumer consumer) + { + this.consumer = consumer; + } + + public void writeLine(String line, Callback callback) + { + line = line + "\r\n"; + getEndPoint().write(callback, ByteBuffer.wrap(line.getBytes(StandardCharsets.UTF_8))); + } + } + + ClientConnector clientConnector = new ClientConnector(); + clientConnector.start(); + + // Use port 443 to contact the server using encrypted HTTP. + String host = "wikipedia.org"; + int port = 443; + SocketAddress address = new InetSocketAddress(host, port); + + ClientConnectionFactory connectionFactory = (endPoint, context) -> + new TelnetConnection(endPoint, clientConnector.getExecutor()); + + // Wrap the "telnet" ClientConnectionFactory with the SslClientConnectionFactory. + connectionFactory = new SslClientConnectionFactory(clientConnector.getSslContextFactory(), + clientConnector.getByteBufferPool(), clientConnector.getExecutor(), connectionFactory); + + // We will obtain a SslConnection now. + CompletableFuture connectionPromise = new Promise.Completable<>(); + + Map context = new HashMap<>(); + context.put(ClientConnector.CLIENT_CONNECTION_FACTORY_CONTEXT_KEY, connectionFactory); + context.put(ClientConnector.CONNECTION_PROMISE_CONTEXT_KEY, connectionPromise); + clientConnector.connect(address, context); + + connectionPromise.whenComplete((sslConnection, failure) -> + { + if (failure == null) + { + // Unwrap the SslConnection to access the "line" APIs in TelnetConnection. + TelnetConnection connection = (TelnetConnection)sslConnection.getDecryptedEndPoint().getConnection(); + // Register a listener that receives string lines. + connection.onLine(line -> System.getLogger("app").log(INFO, "line: {0}", line)); + + // Write a line. + connection.writeLine("" + + "GET / HTTP/1.0\r\n" + + "", Callback.NOOP); + } + else + { + failure.printStackTrace(); + } + }); + // end::tlsTelnet[] } public static void main(String[] args) throws Exception { - new ClientConnectorDocs().connect(); + new ClientConnectorDocs().tlsTelnet(); } } diff --git a/jetty-documentation/src/main/java/embedded/client/http/HTTPClientDocs.java b/jetty-documentation/src/main/java/embedded/client/http/HTTPClientDocs.java new file mode 100644 index 00000000000..f531283b41a --- /dev/null +++ b/jetty-documentation/src/main/java/embedded/client/http/HTTPClientDocs.java @@ -0,0 +1,82 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package embedded.client.http; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.dynamic.HttpClientTransportDynamic; +import org.eclipse.jetty.io.ClientConnector; +import org.eclipse.jetty.util.ssl.SslContextFactory; + +public class HTTPClientDocs +{ + public void start() throws Exception + { + // tag::start[] + // Instantiate HttpClient. + HttpClient httpClient = new HttpClient(); + + // Configure HttpClient, for example: + httpClient.setFollowRedirects(false); + + // Start HttpClient. + httpClient.start(); + // end::start[] + } + + public void stop() throws Exception + { + HttpClient httpClient = new HttpClient(); + httpClient.start(); + // tag::stop[] + // Stop HttpClient. + httpClient.stop(); + // end::stop[] + } + + public void tlsExplicit() throws Exception + { + // tag::tlsExplicit[] + SslContextFactory.Client sslContextFactory = new SslContextFactory.Client(); + + ClientConnector clientConnector = new ClientConnector(); + clientConnector.setSslContextFactory(sslContextFactory); + + HttpClient httpClient = new HttpClient(new HttpClientTransportDynamic(clientConnector)); + httpClient.start(); + // end::tlsExplicit[] + } + + public void tlsNoValidation() throws Exception + { + // tag::tlsNoValidation[] + SslContextFactory.Client sslContextFactory = new SslContextFactory.Client(); + // Disable certificate validation at the TLS level. + sslContextFactory.setEndpointIdentificationAlgorithm(null); + // end::tlsNoValidation[] + } + + public void tlsAppValidation() throws Exception + { + // tag::tlsAppValidation[] + SslContextFactory.Client sslContextFactory = new SslContextFactory.Client(); + // Only allow subdomains of domain.com. + sslContextFactory.setHostnameVerifier((hostName, session) -> hostName.endsWith(".domain.com")); + // end::tlsAppValidation[] + } +} diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java index abe77a5673e..5d215abf78a 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java @@ -30,6 +30,7 @@ import java.util.HashMap; import java.util.Map; import java.util.concurrent.Executor; +import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.Promise; import org.eclipse.jetty.util.component.ContainerLifeCycle; import org.eclipse.jetty.util.ssl.SslContextFactory; @@ -281,15 +282,7 @@ public class ClientConnector extends ContainerLifeCycle protected void safeClose(Closeable closeable) { - try - { - if (closeable != null) - closeable.close(); - } - catch (Throwable x) - { - LOG.trace("IGNORED", x); - } + IO.close(closeable); } protected void configure(SocketChannel channel) throws IOException @@ -330,6 +323,18 @@ public class ClientConnector extends ContainerLifeCycle return factory.newConnection(endPoint, context); } + @Override + public void connectionOpened(Connection connection, Object context) + { + super.connectionOpened(connection, context); + @SuppressWarnings("unchecked") + Map contextMap = (Map)context; + @SuppressWarnings("unchecked") + Promise promise = (Promise)contextMap.get(CONNECTION_PROMISE_CONTEXT_KEY); + if (promise != null) + promise.succeeded(connection); + } + @Override protected void connectionFailed(SelectableChannel channel, Throwable failure, Object attachment) { diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ManagedSelector.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ManagedSelector.java index afa3410f882..fbdd71ad77b 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/ManagedSelector.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ManagedSelector.java @@ -268,12 +268,13 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable private void createEndPoint(SelectableChannel channel, SelectionKey selectionKey) throws IOException { EndPoint endPoint = _selectorManager.newEndPoint(channel, this, selectionKey); - Connection connection = _selectorManager.newConnection(channel, endPoint, selectionKey.attachment()); + Object context = selectionKey.attachment(); + Connection connection = _selectorManager.newConnection(channel, endPoint, context); endPoint.setConnection(connection); selectionKey.attach(endPoint); endPoint.onOpen(); endPointOpened(endPoint); - _selectorManager.connectionOpened(connection); + _selectorManager.connectionOpened(connection, context); if (LOG.isDebugEnabled()) LOG.debug("Created {}", endPoint); } diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java b/jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java index fb427e68291..1bcd0080ed8 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java @@ -302,8 +302,10 @@ public abstract class SelectorManager extends ContainerLifeCycle implements Dump *

Callback method invoked when a connection is opened.

* * @param connection the connection just opened + * @param context the attachment associated with the creation of the connection + * @see #newConnection(SelectableChannel, EndPoint, Object) */ - public void connectionOpened(Connection connection) + public void connectionOpened(Connection connection, Object context) { try { From 929ce34640716fc6231142828672005722e5d108 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Tue, 31 Mar 2020 14:58:17 -0500 Subject: [PATCH 021/101] Issue #4529 - Fixing HTML error page from showing servlet info if configured not to do so Signed-off-by: Joakim Erdfelt --- .../org/eclipse/jetty/server/handler/ErrorHandler.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ErrorHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ErrorHandler.java index 98fdb08b596..7d11d60bdd4 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ErrorHandler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ErrorHandler.java @@ -416,7 +416,10 @@ public class ErrorHandler extends AbstractHandler htmlRow(writer, "URI", uri); htmlRow(writer, "STATUS", status); htmlRow(writer, "MESSAGE", message); - htmlRow(writer, "SERVLET", request.getAttribute(Dispatcher.ERROR_SERVLET_NAME)); + if (isShowServlet()) + { + htmlRow(writer, "SERVLET", request.getAttribute(Dispatcher.ERROR_SERVLET_NAME)); + } Throwable cause = (Throwable)request.getAttribute(Dispatcher.ERROR_EXCEPTION); while (cause != null) { @@ -457,7 +460,7 @@ public class ErrorHandler extends AbstractHandler while (cause != null) { writer.printf("CAUSED BY %s%n", cause); - if (_showStacks && !_disableStacks) + if (isShowStacks() && !_disableStacks) { cause.printStackTrace(writer); } From b5956b975d2158d1178634a78e869555c3822423 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Tue, 31 Mar 2020 23:12:48 +0200 Subject: [PATCH 022/101] Improvements to the Jetty client documentation. Signed-off-by: Simone Bordet --- .../client/http/client-http-api.adoc | 353 ++++++---------- .../client/http/client-http.adoc | 2 +- .../embedded/client/http/HTTPClientDocs.java | 399 +++++++++++++++++- 3 files changed, 532 insertions(+), 222 deletions(-) diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-api.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-api.adoc index 6d0cd736ae4..4765fb47a18 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-api.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-api.adoc @@ -16,17 +16,19 @@ // ======================================================================== // -[[http-client-api]] -=== API Usage +[[client-http-api]] +=== HttpClient API Usage -[[http-client-blocking]] -==== Blocking APIs +`HttpClient` provides two types of APIs: a blocking API and a non-blocking API. -The simple way to perform a HTTP request is the following: +[[client-http-blocking]] +==== HttpClient Blocking APIs -[source, java, subs="{sub-order}"] +The simpler way to perform a HTTP request is the following: + +[source,java,indent=0] ---- -ContentResponse response = httpClient.GET("http://domain.com/path?query"); +include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tags=simpleBlockingGet] ---- The method `HttpClient.GET(...)` performs a HTTP `GET` request to the given URI and returns a `ContentResponse` when the request/response conversation completes successfully. @@ -36,22 +38,16 @@ The content length is limited by default to 2 MiB; for larger content see xref:h If you want to customize the request, for example by issuing a `HEAD` request instead of a `GET`, and simulating a browser user agent, you can do it in this way: -[source, java, subs="{sub-order}"] +[source,java,indent=0] ---- -ContentResponse response = httpClient.newRequest("http://domain.com/path?query") - .method(HttpMethod.HEAD) - .agent("Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:17.0) Gecko/20100101 Firefox/17.0") - .send(); +include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tags=headFluent] ---- This is a shorthand for: -[source, java, subs="{sub-order}"] +[source,java,indent=0] ---- -Request request = httpClient.newRequest("http://domain.com/path?query"); -request.method(HttpMethod.HEAD); -request.agent("Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:17.0) Gecko/20100101 Firefox/17.0"); -ContentResponse response = request.send(); +include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tags=headNonFluent] ---- You first create a request object using `httpClient.newRequest(...)`, and then you customize it using the fluent API style (that is, a chained invocation of methods on the request object). @@ -59,52 +55,45 @@ When the request object is customized, you call `request.send()` that produces t Simple `POST` requests also have a shortcut method: -[source, java, subs="{sub-order}"] +[source,java,indent=0] ---- -ContentResponse response = httpClient.POST("http://domain.com/entity/1") - .param("p", "value") - .send(); +include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tags=postFluent] ---- The `POST` parameter values added via the `param()` method are automatically URL-encoded. -Jetty's HTTP client automatically follows redirects, so it handles the typical web pattern http://en.wikipedia.org/wiki/Post/Redirect/Get[POST/Redirect/GET], and the response object contains the content of the response of the `GET` request. +Jetty's `HttpClient` automatically follows redirects, so it handles the typical web pattern http://en.wikipedia.org/wiki/Post/Redirect/Get[POST/Redirect/GET], and the response object contains the content of the response of the `GET` request. Following redirects is a feature that you can enable/disable on a per-request basis or globally. -File uploads also require one line, and make use of JDK 7′s `java.nio.file` classes: +File uploads also require one line, and make use of `java.nio.file` classes: -[source, java, subs="{sub-order}"] +[source,java,indent=0] ---- -ContentResponse response = httpClient.newRequest("http://domain.com/upload") - .method(HttpMethod.POST) - .file(Paths.get("file_to_upload.txt"), "text/plain") - .send(); +include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tags=fileFluent] ---- It is possible to impose a total timeout for the request/response conversation using the `Request.timeout(...)` method as follows: -[source, java, subs="{sub-order}"] +[source,java,indent=0] ---- -ContentResponse response = httpClient.newRequest("http://domain.com/path?query") - .timeout(5, TimeUnit.SECONDS) - .send(); +include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tags=totalTimeout] ---- In the example above, when the 5 seconds expire, the request is aborted and a `java.util.concurrent.TimeoutException` is thrown. -[[http-client-async]] -==== Non-Blocking APIs +[[client-http-non-blocking]] +==== HttpClient Non-Blocking APIs So far we have shown how to use Jetty HTTP client in a blocking style - that is, the thread that issues the request blocks until the request/response conversation is complete. -This section will look at Jetty's HTTP client non-blocking, asynchronous APIs that are perfectly suited for large content downloads, for parallel processing of requests/responses and in cases where performance and efficient thread and resource utilization is a key factor. +This section will look at Jetty's `HttpClient` non-blocking, asynchronous APIs that are perfectly suited for large content downloads, for parallel processing of requests/responses and in cases where performance and efficient thread and resource utilization is a key factor. The asynchronous APIs rely heavily on listeners that are invoked at various stages of request and response processing. These listeners are implemented by applications and may perform any kind of logic. The implementation invokes these listeners in the same thread that is used to process the request or response. Therefore, if the application code in these listeners takes a long time to execute, the request or response processing is delayed until the listener returns. -If you need to execute application code that takes long time inside a listener, you must spawn your own thread and remember to deep copy any data provided by the listener that you will need in your code, because when the listener returns the data it provides may be recycled/cleared/destroyed. +If you need to execute application code that takes long time inside a listener, you must spawn your own thread. Request and response processing are executed by two different threads and therefore may happen concurrently. A typical example of this concurrent processing is an echo server, where a large upload may be concurrent with the large download echoed back. @@ -119,148 +108,74 @@ Response processing continues until either the response is fully processed or un If it would block for I/O, the thread asks the I/O system to emit an event when the I/O will be ready to continue, then returns. When such an event is fired, a thread taken from the `HttpClient` thread pool will resume the processing of the response. -When the request and the response are both fully processed, the thread that finished the last processing (usually the thread that processes the response, but may also be the thread that processes the request - if the request takes more time than the response to be processed) is used to de-queue the next request for the same destination and processes it. +When the request and the response are both fully processed, the thread that finished the last processing (usually the thread that processes the response, but may also be the thread that processes the request - if the request takes more time than the response to be processed) is used to dequeue the next request for the same destination and processes it. -A simple asynchronous `GET` request that discards the response content can be written in this way: +A simple non-blocking `GET` request that discards the response content can be written in this way: -[source, java, subs="{sub-order}"] +[source,java,indent=0] ---- -httpClient.newRequest("http://domain.com/path") - .send(new Response.CompleteListener() - { - @Override - public void onComplete(Result result) - { - // Your logic here - } - }); +include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tags=simpleNonBlocking] ---- -Method `Request.send(Response.CompleteListener)` returns `void` and does not block; the `Response.CompleteListener` provided as a parameter is notified when the request/response conversation is complete, and the `Result` parameter allows you to access the response object. - -You can write the same code using JDK 8′s lambda expressions: - -[source, java, subs="{sub-order}"] ----- -httpClient.newRequest("http://domain.com/path") - .send(result -> { /* Your logic here */ }); ----- +Method `Request.send(Response.CompleteListener)` returns `void` and does not block; the `Response.CompleteListener` lambda provided as a parameter is notified when the request/response conversation is complete, and the `Result` parameter allows you to access the request and response objects as well as failures, if any. You can impose a total timeout for the request/response conversation in the same way used by the synchronous API: -[source, java, subs="{sub-order}"] +[source,java,indent=0] ---- -httpClient.newRequest("http://domain.com/path") - .timeout(3, TimeUnit.SECONDS) - .send(result -> { /* Your logic here */ }); +include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tags=nonBlockingTotalTimeout] ---- The example above will impose a total timeout of 3 seconds on the request/response conversation. -The HTTP client APIs use listeners extensively to provide hooks for all possible request and response events, and with JDK 8′s lambda expressions they are even more fun to use: +The HTTP client APIs use listeners extensively to provide hooks for all possible request and response events: -[source, java, subs="{sub-order}"] +[source,java,indent=0] ---- -httpClient.newRequest("http://domain.com/path") - // Add request hooks - .onRequestQueued(request -> { ... }) - .onRequestBegin(request -> { ... }) - ... // More request hooks available - - // Add response hooks - .onResponseBegin(response -> { ... }) - .onResponseHeaders(response -> { ... }) - .onResponseContent((response, buffer) -> { ... }) - ... // More response hooks available - - .send(result -> { ... }); +include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tags=listeners] ---- This makes Jetty HTTP client suitable for HTTP load testing because, for example, you can accurately time every step of the request/response conversation (thus knowing where the request/response time is really spent). Have a look at the link:{JDURL}/org/eclipse/jetty/client/api/Request.Listener.html[`Request.Listener`] class to know about request events, and to the link:{JDURL}/org/eclipse/jetty/client/api/Response.Listener.html[`Response.Listener`] class to know about response events. -[[http-client-content]] -==== Content Handling +[[client-http-content]] +==== HttpClient Content Handling -[[http-client-request-content]] +[[client-http-content-request]] ===== Request Content Handling -Jetty's HTTP client provides a number of utility classes off the shelf to handle request content. +Jetty's `HttpClient` provides a number of utility classes off the shelf to handle request content. You can provide request content as `String`, `byte[]`, `ByteBuffer`, `java.nio.file.Path`, `InputStream`, and provide your own implementation of `org.eclipse.jetty.client.api.Request.Content`. Here’s an example that provides the request content using `java.nio.file.Paths`: -[source, java, subs="{sub-order}"] +[source,java,indent=0] ---- -ContentResponse response = httpClient.newRequest("http://domain.com/upload") - .method(HttpMethod.POST) - .file(Paths.get("file_to_upload.txt"), "text/plain") - .send(); ----- - -This is equivalent to using the `PathRequestContent` utility class: - -[source, java, subs="{sub-order}"] ----- -ContentResponse response = httpClient.newRequest("http://domain.com/upload") - .method(HttpMethod.POST) - .body(new PathRequestContent("text/plain", Paths.get("file_to_upload.txt"))) - .send(); +include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tags=pathRequestContent] ---- Alternatively, you can use `FileInputStream` via the `InputStreamRequestContent` utility class: -[source, java, subs="{sub-order}"] +[source,java,indent=0] ---- -ContentResponse response = httpClient.newRequest("http://domain.com/upload") - .method(HttpMethod.POST) - .body(new InputStreamRequestContent("text/plain", new FileInputStream("file_to_upload.txt"))) - .send(); +include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tags=inputStreamRequestContent] ---- -Since `InputStream` is blocking, then also the send of the request will block if the input stream blocks, even in case of usage of the asynchronous `HttpClient` APIs. +Since `InputStream` is blocking, then also the send of the request will block if the input stream blocks, even in case of usage of the non-blocking `HttpClient` APIs. -If you have already read the content in memory, you can pass it as a `byte[]` using the `BytesRequestContent` utility class: +If you have already read the content in memory, you can pass it as a `byte[]` (or a `String`) using the `BytesRequestContent` (or `StringRequestContent`) utility class: -[source, java, subs="{sub-order}"] +[source,java,indent=0] ---- -byte[] bytes = ...; -ContentResponse response = httpClient.newRequest("http://domain.com/upload") - .method(HttpMethod.POST) - .body(new BytesRequestContent("text/plain", bytes)) - .send(); +include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tags=bytesStringRequestContent] ---- If the request content is not immediately available, but your application will be notified of the content to send, you can use `AsyncRequestContent` in this way: -[source, java, subs="{sub-order}"] +[source,java,indent=0] ---- -AsyncRequestContent content = new AsyncRequestContent(); -httpClient.newRequest("http://domain.com/upload") - .method(HttpMethod.POST) - .body(content) - .send(new Response.CompleteListener() - { - @Override - public void onComplete(Result result) - { - // Your logic here - } - }); - -// Content not available yet here. - -... - -// An event happens, now content is available. -byte[] bytes = ...; -content.offer(ByteBuffer.wrap(bytes)); - -... - -// All content has arrived. -content.close(); +include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tags=asyncRequestContent] ---- While the request content is awaited and consequently uploaded by the client application, the server may be able to respond (at least with the response headers) completely asynchronously. @@ -270,113 +185,113 @@ This allows fine-grained control of the request/response conversation: for examp Another way to provide request content is by using an `OutputStreamRequestContent`, which allows applications to write request content when it is available to the `OutputStream` provided by `OutputStreamRequestContent`: -[source, java, subs="{sub-order}"] +[source,java,indent=0] ---- -OutputStreamRequestContent content = new OutputStreamRequestContent(); - -// Use try-with-resources to close the OutputStream when all content is written. -try (OutputStream output = content.getOutputStream()) -{ - client.newRequest("localhost", 8080) - .method(HttpMethod.POST) - .body(content) - .send(new Response.CompleteListener() - { - @Override - public void onComplete(Result result) - { - // Your logic here - } - }); - - ... - - // Write content. - byte[] bytes = ...; - output.write(bytes); -} -// End of try-with-resource, output.close() called automatically to signal end of content. +include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tags=outputStreamRequestContent] ---- [[http-client-response-content]] ===== Response Content Handling -Jetty HTTP client allows applications to handle response content in different ways. +Jetty's `HttpClient` allows applications to handle response content in different ways. -The first way is to buffer the response content in memory; this is done when using the blocking APIs (see xref:http-client-blocking[]) and the content is buffered within a `ContentResponse` up to 2 MiB. +You can buffer the response content in memory; this is done when using the xref:client-http-blocking[blocking APIs] and the content is buffered within a `ContentResponse` up to 2 MiB. If you want to control the length of the response content (for example limiting to values smaller than the default of 2 MiB), then you can use a `org.eclipse.jetty.client.util.FutureResponseListener` in this way: -[source, java, subs="{sub-order}"] +[source,java,indent=0] ---- -Request request = httpClient.newRequest("http://domain.com/path"); - -// Limit response content buffer to 512 KiB -FutureResponseListener listener = new FutureResponseListener(request, 512 * 1024); - -request.send(listener); - -ContentResponse response = listener.get(5, TimeUnit.SECONDS); +include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tags=futureResponseListener] ---- -If the response content length is exceeded, the response will be aborted, and an exception will be thrown by method `get()`. +If the response content length is exceeded, the response will be aborted, and an exception will be thrown by method `get(...)`. -If you are using the asynchronous APIs (see xref:http-client-async[]), you can use the `BufferingResponseListener` utility class: +You can buffer the response content in memory also using the xref:client-http-non-blocking[non-blocking APIs], via the `BufferingResponseListener` utility class: -[source, java, subs="{sub-order}"] +[source,java,indent=0] ---- -httpClient.newRequest("http://domain.com/path") - // Buffer response content up to 8 MiB - .send(new BufferingResponseListener(8 * 1024 * 1024) - { - @Override - public void onComplete(Result result) - { - if (!result.isFailed()) - { - byte[] responseContent = getContent(); - // Your logic here - } - } - }); +include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tags=bufferingResponseListener] ---- -The second way is the most efficient (because it avoids content copies) and allows you to specify a `Response.ContentListener`, or a subclass, to handle the content as soon as it arrives. -In the example below, `Response.Listener.Adapter` is a class that implements both `Response.ContentListener` and `Response.CompleteListener` and can be passed to `Request.send()`. -Jetty's HTTP client will invoke the `onContent()` method zero or more times (until there is content), and finally invoke the `onComplete()` method. +If you want to avoid buffering, you can wait for the response and then stream the content using the `InputStreamResponseListener` utility class: -[source, java, subs="{sub-order}"] +[source,java,indent=0] ---- -httpClient .newRequest("http://domain.com/path") - .send(new Response.Listener.Adapter() - { - @Override - public void onContent(Response response, ByteBuffer buffer) - { - // Your logic here - } - }); +include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tags=inputStreamResponseListener] ---- -The third way allows you to wait for the response and then stream the content using the `InputStreamResponseListener` utility class: +Finally, let's look at the advanced usage of the response content handling. -[source, java, subs="{sub-order}"] +The response content is provided by the `HttpClient` implementation to application +listeners following a reactive model similar to that of `java.util.concurrent.Flow`. + +The listener that follows this model is `Response.DemandedContentListener`. + +After the response headers have been processed by the `HttpClient` implementation, +`Response.DemandedContentListener.onBeforeContent(response, demand)` is +invoked. This allows the application to control whether to demand the first +content or not. The default implementation of this method calls `demand.accept(1)`, +which demands one chunk of content to the implementation. +The implementation will deliver the chunk of content as soon as it is available. + +The chunks of content are delivered to the application by invoking +`Response.DemandedContentListener.onContent(response, demand, buffer, callback)`. +Applications implement this method to process the content bytes in the `buffer`. +Succeeding the `callback` signals to the implementation that the application +has consumed the `buffer` so that the implementation can dispose/recycle the +`buffer`. Failing the `callback` signals to the implementation to fail the +response (no more content will be delivered, and the _response failed_ event +will be emitted). + +IMPORTANT: Succeeding the `callback` must be done only after the `buffer` +bytes have been consumed. When the `callback` is succeeded, the `HttpClient` +implementation may reuse the `buffer` and overwrite the bytes with different +bytes; if the application looks at the `buffer` _after_ having succeeded +the `callback` is may see other, unrelated, bytes. + +The application uses the `demand` object to demand more content chunks. +Applications will typically demand for just one more content via +`demand.accept(1)`, but may decide to demand for more via `demand.accept(2)` +or demand "infinitely" once via `demand.accept(Long.MAX_VALUE)`. +Applications that demand for more than 1 chunk of content must be prepared +to receive all the content that they have demanded. + +Demanding for content and consuming the content are orthogonal activities. + +An application can demand "infinitely" and store aside the pairs +`(buffer, callback)` to consume them later. +If not done carefully, this may lead to excessive memory consumption, since +the ``buffer``s are not consumed. +Succeeding the ``callback``s will result in the ``buffer``s to be +disposed/recycled and may be performed at any time. + +An application can also demand one chunk of content, consume it (by +succeeding the associated `callback`) and then _not_ demand for more content +until a later time. + +Subclass `Response.AsyncContentListener` overrides the behavior of +`Response.DemandedContentListener`; when an application implementing its +`onContent(response, buffer, callback)` succeeds the `callback`, it +will have _both_ the effect of disposing/recycling the `buffer` _and_ the +effect of demanding one more chunk of content. + +Subclass `Response.ContentListener` overrides the behavior of +`Response.AsyncContentListener`; when an application implementing its +`onContent(response, buffer)` returns from the method itself, it will +_both_ the effect of disposing/recycling the `buffer` _and_ the effect +of demanding one more chunk of content. + +Previous examples of response content handling were inefficient because they +involved copying the `buffer` bytes, either to accumulate them aside so that +the application could use them when the request was completed, or because +they were provided to an API such as `InputStream` that made use of `byte[]` +(and therefore a copy from `ByteBuffer` to `byte[]` is necessary). + +An application that implements a forwarder between two servers can be +implemented efficiently by handling the response content without copying +the `buffer` bytes as in the following example: + +[source,java,indent=0] ---- - -InputStreamResponseListener listener = new InputStreamResponseListener(); -httpClient.newRequest("http://domain.com/path") - .send(listener); - -// Wait for the response headers to arrive -Response response = listener.get(5, TimeUnit.SECONDS); - -// Look at the response -if (response.getStatus() == HttpStatus.OK_200) -{ - // Use try-with-resources to close input stream. - try (InputStream responseContent = listener.getInputStream()) - { - // Your logic here - } -} +include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tags=demandedContentListener] ---- diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http.adoc index 8d0d71a911e..53b5833b1cf 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http.adoc @@ -20,8 +20,8 @@ === HTTP Client include::client-http-intro.adoc[] -include::client-http-configuration.adoc[] include::client-http-api.adoc[] +include::client-http-configuration.adoc[] include::client-http-cookie.adoc[] include::client-http-authentication.adoc[] include::client-http-proxy.adoc[] diff --git a/jetty-documentation/src/main/java/embedded/client/http/HTTPClientDocs.java b/jetty-documentation/src/main/java/embedded/client/http/HTTPClientDocs.java index f531283b41a..8549f712e64 100644 --- a/jetty-documentation/src/main/java/embedded/client/http/HTTPClientDocs.java +++ b/jetty-documentation/src/main/java/embedded/client/http/HTTPClientDocs.java @@ -18,11 +18,40 @@ package embedded.client.http; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.file.Paths; +import java.util.concurrent.TimeUnit; +import java.util.function.LongConsumer; + import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.client.api.Response; +import org.eclipse.jetty.client.api.Result; import org.eclipse.jetty.client.dynamic.HttpClientTransportDynamic; +import org.eclipse.jetty.client.util.AsyncRequestContent; +import org.eclipse.jetty.client.util.BufferingResponseListener; +import org.eclipse.jetty.client.util.BytesRequestContent; +import org.eclipse.jetty.client.util.FutureResponseListener; +import org.eclipse.jetty.client.util.InputStreamRequestContent; +import org.eclipse.jetty.client.util.InputStreamResponseListener; +import org.eclipse.jetty.client.util.OutputStreamRequestContent; +import org.eclipse.jetty.client.util.PathRequestContent; +import org.eclipse.jetty.client.util.StringRequestContent; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.ClientConnector; +import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.ssl.SslContextFactory; +import static java.lang.System.Logger.Level.INFO; + +@SuppressWarnings("unused") public class HTTPClientDocs { public void start() throws Exception @@ -62,7 +91,7 @@ public class HTTPClientDocs // end::tlsExplicit[] } - public void tlsNoValidation() throws Exception + public void tlsNoValidation() { // tag::tlsNoValidation[] SslContextFactory.Client sslContextFactory = new SslContextFactory.Client(); @@ -71,7 +100,7 @@ public class HTTPClientDocs // end::tlsNoValidation[] } - public void tlsAppValidation() throws Exception + public void tlsAppValidation() { // tag::tlsAppValidation[] SslContextFactory.Client sslContextFactory = new SslContextFactory.Client(); @@ -79,4 +108,370 @@ public class HTTPClientDocs sslContextFactory.setHostnameVerifier((hostName, session) -> hostName.endsWith(".domain.com")); // end::tlsAppValidation[] } + + public void simpleBlockingGet() throws Exception + { + // tag::simpleBlockingGet[] + HttpClient httpClient = new HttpClient(); + httpClient.start(); + + // Perform a simple GET and wait for the response. + ContentResponse response = httpClient.GET("http://domain.com/path?query"); + // end::simpleBlockingGet[] + } + + public void headFluent() throws Exception + { + HttpClient httpClient = new HttpClient(); + httpClient.start(); + + // tag::headFluent[] + ContentResponse response = httpClient.newRequest("http://domain.com/path?query") + .method(HttpMethod.HEAD) + .agent("Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:17.0) Gecko/20100101 Firefox/17.0") + .send(); + // end::headFluent[] + } + + public void headNonFluent() throws Exception + { + HttpClient httpClient = new HttpClient(); + httpClient.start(); + + // tag::headNonFluent[] + Request request = httpClient.newRequest("http://domain.com/path?query"); + request.method(HttpMethod.HEAD); + request.agent("Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:17.0) Gecko/20100101 Firefox/17.0"); + ContentResponse response = request.send(); + // end::headNonFluent[] + } + + public void postFluent() throws Exception + { + HttpClient httpClient = new HttpClient(); + httpClient.start(); + + // tag::postFluent[] + ContentResponse response = httpClient.POST("http://domain.com/entity/1") + .param("p", "value") + .send(); + // end::postFluent[] + } + + public void fileFluent() throws Exception + { + HttpClient httpClient = new HttpClient(); + httpClient.start(); + + // tag::fileFluent[] + ContentResponse response = httpClient.POST("http://domain.com/upload") + .file(Paths.get("file_to_upload.txt"), "text/plain") + .send(); + // end::fileFluent[] + } + + public void totalTimeout() throws Exception + { + HttpClient httpClient = new HttpClient(); + httpClient.start(); + + // tag::totalTimeout[] + ContentResponse response = httpClient.newRequest("http://domain.com/path?query") + .timeout(5, TimeUnit.SECONDS) + .send(); + // end::totalTimeout[] + } + + public void simpleNonBlocking() throws Exception + { + HttpClient httpClient = new HttpClient(); + httpClient.start(); + + // tag::simpleNonBlocking[] + httpClient.newRequest("http://domain.com/path") + .send(result -> + { + // Your logic here + }); + // end::simpleNonBlocking[] + } + + public void nonBlockingTotalTimeout() throws Exception + { + HttpClient httpClient = new HttpClient(); + httpClient.start(); + + // tag::nonBlockingTotalTimeout[] + httpClient.newRequest("http://domain.com/path") + .timeout(3, TimeUnit.SECONDS) + .send(result -> + { + /* Your logic here */ + }); + // end::nonBlockingTotalTimeout[] + } + + // @checkstyle-disable-check : LeftCurly + public void listeners() throws Exception + { + HttpClient httpClient = new HttpClient(); + httpClient.start(); + + // tag::listeners[] + httpClient.newRequest("http://domain.com/path") + // Add request hooks. + .onRequestQueued(request -> { /* ... */ }) + .onRequestBegin(request -> { /* ... */ }) + .onRequestHeaders(request -> { /* ... */ }) + .onRequestCommit(request -> { /* ... */ }) + .onRequestContent((request, content) -> { /* ... */ }) + .onRequestFailure((request, failure) -> { /* ... */ }) + .onRequestSuccess(request -> { /* ... */ }) + // Add response hooks. + .onResponseBegin(response -> { /* ... */ }) + .onResponseHeader((response, field) -> true) + .onResponseHeaders(response -> { /* ... */ }) + .onResponseContentAsync((response, buffer, callback) -> callback.succeeded()) + .onResponseFailure((response, failure) -> { /* ... */ }) + .onResponseSuccess(response -> { /* ... */ }) + // Result hook. + .send(result -> { /* ... */ }); + // end::listeners[] + } + + public void pathRequestContent() throws Exception + { + HttpClient httpClient = new HttpClient(); + httpClient.start(); + + // tag::pathRequestContent[] + ContentResponse response = httpClient.POST("http://domain.com/upload") + .body(new PathRequestContent("text/plain", Paths.get("file_to_upload.txt"))) + .send(); + // end::pathRequestContent[] + } + + public void inputStreamRequestContent() throws Exception + { + HttpClient httpClient = new HttpClient(); + httpClient.start(); + + // tag::inputStreamRequestContent[] + ContentResponse response = httpClient.POST("http://domain.com/upload") + .body(new InputStreamRequestContent("text/plain", new FileInputStream("file_to_upload.txt"))) + .send(); + // end::inputStreamRequestContent[] + } + + public void bytesStringRequestContent() throws Exception + { + HttpClient httpClient = new HttpClient(); + httpClient.start(); + + byte[] bytes = new byte[1024]; + String string = new String(bytes); + // tag::bytesStringRequestContent[] + ContentResponse bytesResponse = httpClient.POST("http://domain.com/upload") + .body(new BytesRequestContent("text/plain", bytes)) + .send(); + + ContentResponse stringResponse = httpClient.POST("http://domain.com/upload") + .body(new StringRequestContent("text/plain", string)) + .send(); + // end::bytesStringRequestContent[] + } + + public void asyncRequestContent() throws Exception + { + HttpClient httpClient = new HttpClient(); + httpClient.start(); + + // tag::asyncRequestContent[] + AsyncRequestContent content = new AsyncRequestContent(); + httpClient.POST("http://domain.com/upload") + .body(content) + .send(result -> + { + // Your logic here + }); + + // Content not available yet here. + + // An event happens in some other class, in some other thread. + class ContentPublisher + { + void publish(ByteBufferPool bufferPool, byte[] bytes, boolean lastContent) + { + // Wrap the bytes into a new ByteBuffer. + ByteBuffer buffer = ByteBuffer.wrap(bytes); + + // Offer the content, and release the ByteBuffer + // to the pool when the Callback is completed. + content.offer(buffer, Callback.from(() -> bufferPool.release(buffer))); + + // Close AsyncRequestContent when all the content is arrived. + if (lastContent) + content.close(); + } + } + // end::asyncRequestContent[] + } + + public void outputStreamRequestContent() throws Exception + { + HttpClient httpClient = new HttpClient(); + httpClient.start(); + + // tag::outputStreamRequestContent[] + OutputStreamRequestContent content = new OutputStreamRequestContent(); + + // Use try-with-resources to close the OutputStream when all content is written. + try (OutputStream output = content.getOutputStream()) + { + httpClient.POST("http://localhost:8080/") + .body(content) + .send(result -> + { + // Your logic here + }); + + // Content not available yet here. + + // Content is now available. + byte[] bytes = new byte[]{'h', 'e', 'l', 'l', 'o'}; + output.write(bytes); + } + // End of try-with-resource, output.close() called automatically to signal end of content. + // end::outputStreamRequestContent[] + } + + public void futureResponseListener() throws Exception + { + HttpClient httpClient = new HttpClient(); + httpClient.start(); + + // tag::futureResponseListener[] + Request request = httpClient.newRequest("http://domain.com/path"); + + // Limit response content buffer to 512 KiB. + FutureResponseListener listener = new FutureResponseListener(request, 512 * 1024); + + request.send(listener); + + // Wait at most 5 seconds for request+response to complete. + ContentResponse response = listener.get(5, TimeUnit.SECONDS); + // end::futureResponseListener[] + } + + public void bufferingResponseListener() throws Exception + { + HttpClient httpClient = new HttpClient(); + httpClient.start(); + + // tag::bufferingResponseListener[] + httpClient.newRequest("http://domain.com/path") + // Buffer response content up to 8 MiB + .send(new BufferingResponseListener(8 * 1024 * 1024) + { + @Override + public void onComplete(Result result) + { + if (!result.isFailed()) + { + byte[] responseContent = getContent(); + // Your logic here + } + } + }); + // end::bufferingResponseListener[] + } + + public void inputStreamResponseListener() throws Exception + { + HttpClient httpClient = new HttpClient(); + httpClient.start(); + + // tag::inputStreamResponseListener[] + InputStreamResponseListener listener = new InputStreamResponseListener(); + httpClient.newRequest("http://domain.com/path") + .send(listener); + + // Wait for the response headers to arrive. + Response response = listener.get(5, TimeUnit.SECONDS); + + // Look at the response before streaming the content. + if (response.getStatus() == HttpStatus.OK_200) + { + // Use try-with-resources to close input stream. + try (InputStream responseContent = listener.getInputStream()) + { + // Your logic here + } + } + else + { + response.abort(new IOException("Unexpected HTTP response")); + } + // end::inputStreamResponseListener[] + } + + public void demandedContentListener() throws Exception + { + HttpClient httpClient = new HttpClient(); + httpClient.start(); + + String host1 = "localhost"; + String host2 = "localhost"; + int port1 = 8080; + int port2 = 8080; + // tag::demandedContentListener[] + // Prepare a request to server1, the source. + Request request1 = httpClient.newRequest(host1, port1) + .path("/source"); + + // Prepare a request to server2, the sink. + AsyncRequestContent content2 = new AsyncRequestContent(); + Request request2 = httpClient.newRequest(host2, port2) + .path("/sink") + .body(content2); + + request1.onResponseContentDemanded(new Response.DemandedContentListener() + { + @Override + public void onBeforeContent(Response response, LongConsumer demand) + { + request2.onRequestCommit(request -> + { + // Only when the request to server2 has been sent, + // then demand response content from server1. + demand.accept(1); + }); + + // Send the request to server2. + request2.send(result -> System.getLogger("forwarder").log(INFO, "Forwarding to server2 complete")); + } + + @Override + public void onContent(Response response, LongConsumer demand, ByteBuffer content, Callback callback) + { + // When response content is received from server1, forward it to server2. + content2.offer(content, Callback.from(() -> + { + // When the request content to server2 is sent, + // succeed the callback to recycle the buffer. + callback.succeeded(); + // Then demand more response content from server1. + demand.accept(1); + }, callback::failed)); + } + }); + + // When the response content from server1 is complete, + // complete also the request content to server2. + request1.onResponseSuccess(response -> content2.close()); + + // Send the request to server1. + request1.send(result -> System.getLogger("forwarder").log(INFO, "Sourcing from server1 complete")); + // end::demandedContentListener[] + } } From ec38e99630d83b737499a046ac0f06fe6b1ccc6a Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Wed, 1 Apr 2020 10:06:35 +0200 Subject: [PATCH 023/101] Fixed handling of ClientConnector promises. Signed-off-by: Simone Bordet --- .../jetty/client/AbstractConnectorHttpClientTransport.java | 2 +- .../main/java/org/eclipse/jetty/http2/client/HTTP2Client.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractConnectorHttpClientTransport.java b/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractConnectorHttpClientTransport.java index 129cdcb3748..d0b41f16f88 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractConnectorHttpClientTransport.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractConnectorHttpClientTransport.java @@ -72,7 +72,7 @@ public abstract class AbstractConnectorHttpClientTransport extends AbstractHttpC context.put(ClientConnector.CLIENT_CONNECTION_FACTORY_CONTEXT_KEY, destination.getClientConnectionFactory()); @SuppressWarnings("unchecked") Promise promise = (Promise)context.get(HTTP_CONNECTION_PROMISE_CONTEXT_KEY); - context.put(ClientConnector.CONNECTION_PROMISE_CONTEXT_KEY, promise); + context.put(ClientConnector.CONNECTION_PROMISE_CONTEXT_KEY, Promise.from(ioConnection -> {}, promise::failed)); connector.connect(address, context); } } diff --git a/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2Client.java b/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2Client.java index 8ea720523c3..fd45d8c934f 100644 --- a/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2Client.java +++ b/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2Client.java @@ -359,7 +359,7 @@ public class HTTP2Client extends ContainerLifeCycle public void connect(SocketAddress address, ClientConnectionFactory factory, Session.Listener listener, Promise promise, Map context) { context = contextFrom(factory, listener, promise, context); - context.put(ClientConnector.CONNECTION_PROMISE_CONTEXT_KEY, promise); + context.put(ClientConnector.CONNECTION_PROMISE_CONTEXT_KEY, Promise.from(ioConnection -> {}, promise::failed)); connector.connect(address, context); } @@ -372,7 +372,7 @@ public class HTTP2Client extends ContainerLifeCycle public void accept(SocketChannel channel, ClientConnectionFactory factory, Session.Listener listener, Promise promise) { Map context = contextFrom(factory, listener, promise, null); - context.put(ClientConnector.CONNECTION_PROMISE_CONTEXT_KEY, promise); + context.put(ClientConnector.CONNECTION_PROMISE_CONTEXT_KEY, Promise.from(ioConnection -> {}, promise::failed)); connector.accept(channel, context); } From 5edff904feef6405ee97c0bddfaa37e722e580f0 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Wed, 1 Apr 2020 15:15:45 +0200 Subject: [PATCH 024/101] Fixes #4735 - Get env variables in PHP scripts served through FastCGIProxyServlet. Introduced fastCGI.envNames to specify a comma separated list of environment variable names that are forwarded to the PHP process. Signed-off-by: Simone Bordet --- jetty-fcgi/fcgi-server/pom.xml | 7 ++---- .../server/proxy/FastCGIProxyServlet.java | 25 ++++++++++++++++++- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/jetty-fcgi/fcgi-server/pom.xml b/jetty-fcgi/fcgi-server/pom.xml index 3fea6cd08de..add0f2a11d0 100644 --- a/jetty-fcgi/fcgi-server/pom.xml +++ b/jetty-fcgi/fcgi-server/pom.xml @@ -14,10 +14,6 @@ ${project.groupId}.server - - - - javax.servlet @@ -38,6 +34,7 @@ jetty-server ${project.version} + org.eclipse.jetty jetty-servlet @@ -52,7 +49,7 @@ org.eclipse.jetty - jetty-alpn-server + jetty-alpn-java-server ${project.version} test diff --git a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServlet.java b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServlet.java index fe3c0e19171..6422245d95e 100644 --- a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServlet.java +++ b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServlet.java @@ -19,11 +19,14 @@ package org.eclipse.jetty.fcgi.server.proxy; import java.net.URI; +import java.util.Collections; import java.util.List; +import java.util.Set; import java.util.TreeMap; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; +import java.util.stream.Stream; import javax.servlet.RequestDispatcher; import javax.servlet.ServletConfig; import javax.servlet.ServletException; @@ -48,7 +51,7 @@ import org.eclipse.jetty.util.ProcessorUtils; * that is sent to the FastCGI server specified in the {@code proxyTo} * init-param. *

- * This servlet accepts two additional init-params: + * This servlet accepts these additional {@code init-param}s: *

    *
  • {@code scriptRoot}, mandatory, that must be set to the directory where * the application that must be served via FastCGI is installed and corresponds to @@ -62,6 +65,8 @@ import org.eclipse.jetty.util.ProcessorUtils; *
*
  • {@code fastCGI.HTTPS}, optional, defaults to false, that specifies whether * to force the FastCGI {@code HTTPS} parameter to the value {@code on}
  • + *
  • {@code fastCGI.envNames}, optional, a comma separated list of environment variable + * names read via {@link System#getenv(String)} that are forwarded as FastCGI parameters.
  • * * * @see TryFilesFilter @@ -73,6 +78,7 @@ public class FastCGIProxyServlet extends AsyncProxyServlet.Transparent public static final String ORIGINAL_URI_ATTRIBUTE_INIT_PARAM = "originalURIAttribute"; public static final String ORIGINAL_QUERY_ATTRIBUTE_INIT_PARAM = "originalQueryAttribute"; public static final String FASTCGI_HTTPS_INIT_PARAM = "fastCGI.HTTPS"; + public static final String FASTCGI_ENV_NAMES_INIT_PARAM = "fastCGI.envNames"; private static final String REMOTE_ADDR_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".remoteAddr"; private static final String REMOTE_PORT_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".remotePort"; @@ -87,6 +93,7 @@ public class FastCGIProxyServlet extends AsyncProxyServlet.Transparent private String originalURIAttribute; private String originalQueryAttribute; private boolean fcgiHTTPS; + private Set fcgiEnvNames; @Override public void init() throws ServletException @@ -102,6 +109,15 @@ public class FastCGIProxyServlet extends AsyncProxyServlet.Transparent originalQueryAttribute = getInitParameter(ORIGINAL_QUERY_ATTRIBUTE_INIT_PARAM); fcgiHTTPS = Boolean.parseBoolean(getInitParameter(FASTCGI_HTTPS_INIT_PARAM)); + + fcgiEnvNames = Collections.emptySet(); + String envNames = getInitParameter(FASTCGI_ENV_NAMES_INIT_PARAM); + if (envNames != null) + { + fcgiEnvNames = Stream.of(envNames.split(",")) + .map(String::trim) + .collect(Collectors.toSet()); + } } @Override @@ -195,6 +211,13 @@ public class FastCGIProxyServlet extends AsyncProxyServlet.Transparent protected void customizeFastCGIHeaders(Request proxyRequest, HttpFields fastCGIHeaders) { + for (String envName : fcgiEnvNames) + { + String envValue = System.getenv(envName); + if (envValue != null) + fastCGIHeaders.put(envName, envValue); + } + fastCGIHeaders.remove("HTTP_PROXY"); fastCGIHeaders.put(FCGI.Headers.REMOTE_ADDR, (String)proxyRequest.getAttributes().get(REMOTE_ADDR_ATTRIBUTE)); From af895966fa05e4422288391125f470ddb77f6e37 Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Wed, 1 Apr 2020 17:55:33 +0200 Subject: [PATCH 025/101] Issue #4711 trailers Standard trailers cannot be set if committed or HTTP/1.0 Signed-off-by: Greg Wilkins --- .../src/main/java/org/eclipse/jetty/server/Response.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java index e023a1f1c78..4a8eea246a9 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java @@ -1229,7 +1229,10 @@ public class Response implements HttpServletResponse @Override public void setTrailerFields(Supplier> trailers) { - // TODO new for 4.0 - avoid transient supplier? + if (isCommitted()) + throw new IllegalStateException("Committed"); + if (getHttpChannel().getRequest().getHttpVersion().ordinal() <= HttpVersion.HTTP_1_0.ordinal()) + throw new IllegalStateException("Trailers not supported"); this._trailers = new HttpFieldsSupplier(trailers); } From ed675b03cd1c1b8b515d16b4e8e9e13cb165b9b1 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Wed, 1 Apr 2020 18:41:25 +0200 Subject: [PATCH 026/101] Fixes #4735 - Get env variables in PHP scripts served through FastCGIProxyServlet. Fixed test dependency on ALPN. Signed-off-by: Simone Bordet --- jetty-fcgi/fcgi-server/pom.xml | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/jetty-fcgi/fcgi-server/pom.xml b/jetty-fcgi/fcgi-server/pom.xml index add0f2a11d0..e7df5968c92 100644 --- a/jetty-fcgi/fcgi-server/pom.xml +++ b/jetty-fcgi/fcgi-server/pom.xml @@ -14,6 +14,23 @@ ${project.groupId}.server + + + jdk9 + + [9,) + + + + org.eclipse.jetty + jetty-alpn-java-server + ${project.version} + test + + + + + javax.servlet @@ -49,7 +66,7 @@ org.eclipse.jetty - jetty-alpn-java-server + jetty-alpn-openjdk8-server ${project.version} test From bdaf86d38d67f2fbf8d4bdc04a4c1f79b224b8b4 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Wed, 1 Apr 2020 12:44:44 -0500 Subject: [PATCH 027/101] Issue #4638 - updating documentation about form limits Signed-off-by: Joakim Erdfelt --- .../security/configuring-form-size.adoc | 51 ++++++++++--------- .../jetty/server/handler/ContextHandler.java | 15 ++++-- 2 files changed, 37 insertions(+), 29 deletions(-) diff --git a/jetty-documentation/src/main/asciidoc/configuring/security/configuring-form-size.adoc b/jetty-documentation/src/main/asciidoc/configuring/security/configuring-form-size.adoc index b93117b755e..57a21870d8f 100644 --- a/jetty-documentation/src/main/asciidoc/configuring/security/configuring-form-size.adoc +++ b/jetty-documentation/src/main/asciidoc/configuring/security/configuring-form-size.adoc @@ -26,11 +26,37 @@ Thus Jetty limits the amount of data and keys that can be in a form posted to Je The default maximum size Jetty permits is 200000 bytes and 1000 keys. You can change this default for a particular webapp or for all webapps on a particular Server instance. +==== Configuring Default Form Limits via System Properties + +There exists 2 system properties that will adjust the default maximum form sizes. + +* `org.eclipse.jetty.server.Request.maxFormKeys` - the maximum number of Form Keys allowed +* `org.eclipse.jetty.server.Request.maxFormContentSize` - the maximum size of Form Content allowed + +Used from command line as such: + +[source,shell,subs="{sub-order}"] +---- +$ java -Dorg.eclipse.jetty.server.Request.maxFormKeys=200 -jar ... + +$ java -Dorg.eclipse.jetty.server.Request.maxFormContentSize=400000 -jar ... + +---- + +Or via Java code (make sure you do this before you instantiate any `ContextHandler`, `ServletContextHandler`, or `WebAppContext`) + +[source,java,subs="{sub-order}"] +---- +System.setProperty(ContextHandler.MAX_FORM_KEYS_KEY, "200"); +System.setProperty(ContextHandler.MAX_FORM_CONTENT_SIZE_KEY, "400000"); + +---- + ==== Configuring Form Limits for a Webapp To configure the form limits for a single web application, the context handler (or webappContext) instance must be configured using the following methods: -[source, java, subs="{sub-order}"] +[source,java,subs="{sub-order}"] ---- ContextHandler.setMaxFormContentSize(int maxSizeInBytes); ContextHandler.setMaxFormKeys(int formKeys); @@ -50,26 +76,3 @@ These methods may be called directly when embedding Jetty, but more commonly are
    ---- - -==== Configuring Form Limits for the Server - -If a context does not have specific form limits configured, then the server attributes are inspected to see if a server wide limit has been set on the size or keys. -The following XML shows how these attributes can be set in `jetty.xml`: - -[source, xml, subs="{sub-order}"] ----- - - - ... - - - org.eclipse.jetty.server.Request.maxFormContentSize - 100000 - - - org.eclipse.jetty.server.Request.maxFormKeys - 2000 - - - ----- diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java index 9d8d03a73a4..764cc78a5f8 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java @@ -93,19 +93,24 @@ import org.eclipse.jetty.util.resource.Resource; /** * ContextHandler. * + *

    * This handler wraps a call to handle by setting the context and servlet path, plus setting the context classloader. - * + *

    *

    - * If the context init parameter "org.eclipse.jetty.server.context.ManagedAttributes" is set to a comma separated list of names, then they are treated as + * If the context init parameter {@code org.eclipse.jetty.server.context.ManagedAttributes} is set to a comma separated list of names, then they are treated as * context attribute names, which if set as attributes are passed to the servers Container so that they may be managed with JMX. + *

    *

    - * The maximum size of a form that can be processed by this context is controlled by the system properties org.eclipse.jetty.server.Request.maxFormKeys and - * org.eclipse.jetty.server.Request.maxFormContentSize. These can also be configured with {@link #setMaxFormContentSize(int)} and {@link #setMaxFormKeys(int)} + * The maximum size of a form that can be processed by this context is controlled by the system properties {@code org.eclipse.jetty.server.Request.maxFormKeys} and + * {@code org.eclipse.jetty.server.Request.maxFormContentSize}. These can also be configured with {@link #setMaxFormContentSize(int)} and {@link #setMaxFormKeys(int)} + *

    *

    - * This servers executor is made available via a context attributed "org.eclipse.jetty.server.Executor". + * The executor is made available via a context attributed {@code org.eclipse.jetty.server.Executor}. + *

    *

    * By default, the context is created with alias checkers for {@link AllowSymLinkAliasChecker} (unix only) and {@link ApproveNonExistentDirectoryAliases}. If * these alias checkers are not required, then {@link #clearAliasChecks()} or {@link #setAliasChecks(List)} should be called. + *

    */ @ManagedObject("URI Context") public class ContextHandler extends ScopedHandler implements Attributes, Graceful From ea06ba18656accd25ccc2d8be283dd5b3696b351 Mon Sep 17 00:00:00 2001 From: Olivier Lamy Date: Thu, 2 Apr 2020 08:06:10 +1000 Subject: [PATCH 028/101] Issue #4699 fix jar files from multimodules not scanned (#4742) * Issue #4699 fix jar files from multimodules not scanned Signed-off-by: olivier lamy * fix license headers Signed-off-by: olivier lamy --- .../MyLibrary/pom.xml | 21 ++++ .../main/java/jettyissue/MyAnnotation.java | 29 +++++ .../MyServletContainerInitializer.java | 32 +++++ .../javax.servlet.ServletContainerInitializer | 1 + .../MyWebApp/pom.xml | 112 ++++++++++++++++++ .../MyWebApp/src/config/context.xml | 7 ++ .../MyWebApp/src/config/jetty.xml | 39 ++++++ .../src/main/java/jettyissue/NormalClass.java | 24 ++++ .../MyWebApp/src/main/webapp/index.html | 10 ++ .../invoker.properties | 1 + .../src/it/jetty-run-mojo-jar-scan-it/pom.xml | 32 +++++ .../postbuild.groovy | 3 + .../jetty/maven/plugin/JettyRunMojo.java | 12 +- 13 files changed, 312 insertions(+), 11 deletions(-) create mode 100644 jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyLibrary/pom.xml create mode 100644 jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyLibrary/src/main/java/jettyissue/MyAnnotation.java create mode 100644 jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyLibrary/src/main/java/jettyissue/MyServletContainerInitializer.java create mode 100644 jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyLibrary/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer create mode 100644 jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyWebApp/pom.xml create mode 100644 jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyWebApp/src/config/context.xml create mode 100644 jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyWebApp/src/config/jetty.xml create mode 100644 jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyWebApp/src/main/java/jettyissue/NormalClass.java create mode 100644 jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyWebApp/src/main/webapp/index.html create mode 100644 jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/invoker.properties create mode 100644 jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/pom.xml create mode 100644 jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/postbuild.groovy diff --git a/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyLibrary/pom.xml b/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyLibrary/pom.xml new file mode 100644 index 00000000000..34b03a21e11 --- /dev/null +++ b/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyLibrary/pom.xml @@ -0,0 +1,21 @@ + + + + jetty-issue + org.mehdi + 1.0-SNAPSHOT + + 4.0.0 + + MyLibrary + + + + javax.servlet + javax.servlet-api + provided + + + diff --git a/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyLibrary/src/main/java/jettyissue/MyAnnotation.java b/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyLibrary/src/main/java/jettyissue/MyAnnotation.java new file mode 100644 index 00000000000..ae234730a87 --- /dev/null +++ b/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyLibrary/src/main/java/jettyissue/MyAnnotation.java @@ -0,0 +1,29 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package jettyissue; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface MyAnnotation { +} diff --git a/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyLibrary/src/main/java/jettyissue/MyServletContainerInitializer.java b/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyLibrary/src/main/java/jettyissue/MyServletContainerInitializer.java new file mode 100644 index 00000000000..bcab61c875d --- /dev/null +++ b/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyLibrary/src/main/java/jettyissue/MyServletContainerInitializer.java @@ -0,0 +1,32 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package jettyissue; + +import javax.servlet.ServletContainerInitializer; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.annotation.HandlesTypes; +import java.util.Set; + +@HandlesTypes({MyAnnotation.class}) +public class MyServletContainerInitializer implements ServletContainerInitializer { + public void onStartup(Set> c, ServletContext ctx) throws ServletException { + System.out.println("STARTED"+c); + } +} diff --git a/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyLibrary/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer b/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyLibrary/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer new file mode 100644 index 00000000000..9e9784f1616 --- /dev/null +++ b/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyLibrary/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer @@ -0,0 +1 @@ +jettyissue.MyServletContainerInitializer diff --git a/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyWebApp/pom.xml b/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyWebApp/pom.xml new file mode 100644 index 00000000000..f09564cf1a0 --- /dev/null +++ b/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyWebApp/pom.xml @@ -0,0 +1,112 @@ + + + + jetty-issue + org.mehdi + 1.0-SNAPSHOT + + 4.0.0 + + MyWebApp + jar + + + ${project.build.directory}/jetty-run-mojo.txt + + + + + javax.servlet + javax.servlet-api + provided + + + org.mehdi + MyLibrary + + + + org.eclipse.jetty + jetty-client + test + + + org.apache.commons + commons-lang3 + test + + + org.eclipse.jetty + jetty-maven-plugin + tests + test-jar + test + + + * + * + + + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + ${jetty.port.file} + /setbycontextxml + false + false + false + ${project.groupId}:${project.artifactId} + + + org.eclipse.jetty:jetty-maven-plugin + + + + + org.eclipse.jetty + jetty-maven-plugin + + + start-jetty + test-compile + + start + + + + + jetty.port.file + ${jetty.port.file} + + + true + ${basedir}/src/config/jetty.xml + ${basedir}/src/config/context.xml + true + + jar + + + + + + + + + diff --git a/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyWebApp/src/config/context.xml b/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyWebApp/src/config/context.xml new file mode 100644 index 00000000000..3eb5570a37d --- /dev/null +++ b/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyWebApp/src/config/context.xml @@ -0,0 +1,7 @@ + + + + + /setbycontextxml + + diff --git a/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyWebApp/src/config/jetty.xml b/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyWebApp/src/config/jetty.xml new file mode 100644 index 00000000000..9193b42df99 --- /dev/null +++ b/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyWebApp/src/config/jetty.xml @@ -0,0 +1,39 @@ + + + + + https + + 32768 + 8192 + 8192 + 1024 + + + + + + + + + + + + + + + + + + + + + + + + 0 + 30000 + + + + diff --git a/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyWebApp/src/main/java/jettyissue/NormalClass.java b/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyWebApp/src/main/java/jettyissue/NormalClass.java new file mode 100644 index 00000000000..9cea88de8a8 --- /dev/null +++ b/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyWebApp/src/main/java/jettyissue/NormalClass.java @@ -0,0 +1,24 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package jettyissue; + + +@MyAnnotation +public class NormalClass { +} diff --git a/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyWebApp/src/main/webapp/index.html b/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyWebApp/src/main/webapp/index.html new file mode 100644 index 00000000000..b7b5cdc61de --- /dev/null +++ b/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyWebApp/src/main/webapp/index.html @@ -0,0 +1,10 @@ + + + + + Title + + + Hello World! + + diff --git a/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/invoker.properties b/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/invoker.properties new file mode 100644 index 00000000000..e0222d4d54e --- /dev/null +++ b/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/invoker.properties @@ -0,0 +1 @@ +invoker.goals = test \ No newline at end of file diff --git a/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/pom.xml b/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/pom.xml new file mode 100644 index 00000000000..1380e37256e --- /dev/null +++ b/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/pom.xml @@ -0,0 +1,32 @@ + + + 4.0.0 + + org.eclipse.jetty.its + it-parent-pom + 0.0.1-SNAPSHOT + + + + org.mehdi + jetty-issue + pom + 1.0-SNAPSHOT + + MyLibrary + MyWebApp + + + + + + org.mehdi + MyLibrary + ${project.version} + + + + + diff --git a/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/postbuild.groovy b/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/postbuild.groovy new file mode 100644 index 00000000000..87a9e7b26cc --- /dev/null +++ b/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/postbuild.groovy @@ -0,0 +1,3 @@ +File buildLog = new File( basedir, 'build.log' ) +assert buildLog.text.contains( 'Started Jetty Server' ) +assert buildLog.text.contains( 'STARTED[class jettyissue.NormalClass]') diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunMojo.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunMojo.java index 39b5a43bf6b..a826dc23c17 100644 --- a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunMojo.java +++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunMojo.java @@ -257,7 +257,7 @@ public class JettyRunMojo extends AbstractJettyMojo webApp.setTestClasses(testClassesDirectory); MavenProjectHelper mavenProjectHelper = new MavenProjectHelper(project); - List webInfLibs = getWebInfLibArtifacts(project).stream() + List webInfLibs = getWebInfLibArtifacts(project.getArtifacts()).stream() .map(a -> { Path p = mavenProjectHelper.getArtifactPath(a); @@ -479,16 +479,6 @@ public class JettyRunMojo extends AbstractJettyMojo .collect(Collectors.toList()); } - private Collection getWebInfLibArtifacts(MavenProject mavenProject) - { - String type = mavenProject.getArtifact().getType(); - if (!"war".equalsIgnoreCase(type) && !"zip".equalsIgnoreCase(type)) - { - return Collections.emptyList(); - } - return getWebInfLibArtifacts(mavenProject.getArtifacts()); - } - private boolean canPutArtifactInWebInfLib(Artifact artifact) { if ("war".equalsIgnoreCase(artifact.getType())) From 5c38ae3549c2706329036402c4a016ae7775bfb4 Mon Sep 17 00:00:00 2001 From: olivier lamy Date: Tue, 31 Mar 2020 12:53:49 +1000 Subject: [PATCH 029/101] Issue #4727 setConfigurationDiscovered true per default Signed-off-by: olivier lamy --- .../jetty/annotations/TestAnnotationConfiguration.java | 7 ++++--- .../main/java/org/eclipse/jetty/webapp/WebAppContext.java | 2 +- .../org/eclipse/jetty/webapp/MetaInfConfigurationTest.java | 6 +++--- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestAnnotationConfiguration.java b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestAnnotationConfiguration.java index 13753551f7c..2e169e62266 100644 --- a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestAnnotationConfiguration.java +++ b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestAnnotationConfiguration.java @@ -118,25 +118,25 @@ public class TestAnnotationConfiguration @Test public void testAnnotationScanControl() throws Exception { - //check that a 2.5 webapp won't discover annotations + //check that a 2.5 webapp with configurationDiscovered will discover annotations TestableAnnotationConfiguration config25 = new TestableAnnotationConfiguration(); WebAppContext context25 = new WebAppContext(); context25.setClassLoader(Thread.currentThread().getContextClassLoader()); context25.setAttribute(AnnotationConfiguration.MULTI_THREADED, Boolean.FALSE); context25.setAttribute(AnnotationConfiguration.MAX_SCAN_WAIT, 0); + context25.setConfigurationDiscovered(false); context25.getMetaData().setWebDescriptor(new WebDescriptor(Resource.newResource(web25))); context25.getServletContext().setEffectiveMajorVersion(2); context25.getServletContext().setEffectiveMinorVersion(5); config25.configure(context25); config25.assertAnnotationDiscovery(false); - //check that a 2.5 webapp with configurationDiscovered will discover annotations + //check that a 2.5 webapp discover annotations TestableAnnotationConfiguration config25b = new TestableAnnotationConfiguration(); WebAppContext context25b = new WebAppContext(); context25b.setClassLoader(Thread.currentThread().getContextClassLoader()); context25b.setAttribute(AnnotationConfiguration.MULTI_THREADED, Boolean.FALSE); context25b.setAttribute(AnnotationConfiguration.MAX_SCAN_WAIT, 0); - context25b.setConfigurationDiscovered(true); context25b.getMetaData().setWebDescriptor(new WebDescriptor(Resource.newResource(web25))); context25b.getServletContext().setEffectiveMajorVersion(2); context25b.getServletContext().setEffectiveMinorVersion(5); @@ -293,6 +293,7 @@ public class TestAnnotationConfiguration AnnotationConfiguration config = new AnnotationConfiguration(); WebAppContext context = new WebAppContext(); List scis; + context.setConfigurationDiscovered(false); context.setClassLoader(webAppLoader); context.getMetaData().setWebDescriptor(new WebDescriptor(Resource.newResource(web25))); context.getMetaData().setWebInfClassesResources(classes); diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebAppContext.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebAppContext.java index 5b55d08712f..284d1332247 100644 --- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebAppContext.java +++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebAppContext.java @@ -209,7 +209,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL private Map _resourceAliases; private boolean _ownClassLoader = false; - private boolean _configurationDiscovered = false; + private boolean _configurationDiscovered = true; private boolean _allowDuplicateFragmentNames = false; private boolean _throwUnavailableOnStartupException = false; diff --git a/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/MetaInfConfigurationTest.java b/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/MetaInfConfigurationTest.java index 599872a30d6..c28aa703713 100644 --- a/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/MetaInfConfigurationTest.java +++ b/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/MetaInfConfigurationTest.java @@ -84,20 +84,20 @@ public class MetaInfConfigurationTest File web31 = MavenTestingUtils.getTestResourceFile("web31.xml"); File web31false = MavenTestingUtils.getTestResourceFile("web31false.xml"); - //test a 2.5 webapp will not look for fragments by default + //test a 2.5 webapp will not look for fragments as manually configured MetaInfConfiguration meta25 = new TestableMetaInfConfiguration(MetaInfConfiguration.__allScanTypes, Arrays.asList(MetaInfConfiguration.METAINF_TLDS, MetaInfConfiguration.METAINF_RESOURCES)); WebAppContext context25 = new WebAppContext(); + context25.setConfigurationDiscovered(false); context25.getMetaData().setWebDescriptor(new WebDescriptor(Resource.newResource(web25))); context25.getServletContext().setEffectiveMajorVersion(2); context25.getServletContext().setEffectiveMinorVersion(5); meta25.preConfigure(context25); - //test a 2.5 webapp will look for fragments if configurationDiscovered==true + //test a 2.5 webapp will look for fragments as configurationDiscovered default true MetaInfConfiguration meta25b = new TestableMetaInfConfiguration(MetaInfConfiguration.__allScanTypes, MetaInfConfiguration.__allScanTypes); WebAppContext context25b = new WebAppContext(); - context25b.setConfigurationDiscovered(true); context25b.getMetaData().setWebDescriptor(new WebDescriptor(Resource.newResource(web25))); context25b.getServletContext().setEffectiveMajorVersion(2); context25b.getServletContext().setEffectiveMinorVersion(5); From e3bab289241f9db54bd498087b6e46762fb505c2 Mon Sep 17 00:00:00 2001 From: olivier lamy Date: Wed, 1 Apr 2020 12:07:48 +1000 Subject: [PATCH 030/101] document new default behaviour with annotations scanning Signed-off-by: olivier lamy --- .../distribution-guide/annotations/using-annotations.adoc | 5 ++--- .../quickstart-guide/upgrading/upgrading-9.4-to.10.0.adoc | 7 ++++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/jetty-documentation/src/main/asciidoc/distribution-guide/annotations/using-annotations.adoc b/jetty-documentation/src/main/asciidoc/distribution-guide/annotations/using-annotations.adoc index 93eb15c82ef..765bb61a22a 100644 --- a/jetty-documentation/src/main/asciidoc/distribution-guide/annotations/using-annotations.adoc +++ b/jetty-documentation/src/main/asciidoc/distribution-guide/annotations/using-annotations.adoc @@ -121,9 +121,8 @@ As is the case with annotation scanning, the link:#using-extra-classpath-method[ ____ [NOTE] -As of Jetty-9.4.4, unless the `web.xml` is version 3.0 or greater, only `ServletContainerInitializers` that are on the container classpath will be discovered. -Users wishing to use `ServletContainerInitializers` from within the webapp with older versions of `web.xml` must either upgrade their `web.xml` version, or call `WebAppContext.setConfigurationDiscovered(true)` either programmatically or in xml. -Upgrading the `web.xml` version is preferable. +As of Jetty 10, Annotations will be discovered even for old versions of `web.xml` (2.5). +Users wishing to not use `ServletContainerInitializers` from within the webapp with older versions of `web.xml` must call `WebAppContext.setConfigurationDiscovered(false)` either programmatically or in xml. ____ ===== Controlling the order of ServletContainerInitializer invocation diff --git a/jetty-documentation/src/main/asciidoc/quickstart-guide/upgrading/upgrading-9.4-to.10.0.adoc b/jetty-documentation/src/main/asciidoc/quickstart-guide/upgrading/upgrading-9.4-to.10.0.adoc index 0b3653c10c9..fb0da833c5b 100644 --- a/jetty-documentation/src/main/asciidoc/quickstart-guide/upgrading/upgrading-9.4-to.10.0.adoc +++ b/jetty-documentation/src/main/asciidoc/quickstart-guide/upgrading/upgrading-9.4-to.10.0.adoc @@ -23,9 +23,14 @@ It is not comprehensive, but covers many of the major changes included in the re ==== Required Java Version -Jetty 10 requires, at a minimum, Java 9 to function. +Jetty 10 requires, at a minimum, Java 11 to function. Items such as the Java Platform Module System (JPMS), which Jetty 10 supports, are not available in earlier versions of Java. +==== ServletContainerInitializers + +As of Jetty 10, Annotations will be discovered even for old versions of `web.xml` (2.5). +Users wishing to not use `ServletContainerInitializers` from within the webapp with older versions of `web.xml` must call `WebAppContext.setConfigurationDiscovered(false)` either programmatically or in xml. + ==== Removed Classes //TODO - Insert major removed/refactored classes from Jetty-9.x.x to Jetty-10.0.x From f14560a22803d1b1ad66072a027564e821c891eb Mon Sep 17 00:00:00 2001 From: olivier lamy Date: Wed, 1 Apr 2020 22:11:34 +1000 Subject: [PATCH 031/101] fix documentation Signed-off-by: olivier lamy --- .../distribution-guide/annotations/using-annotations.adoc | 2 +- .../quickstart-guide/upgrading/upgrading-9.4-to.10.0.adoc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/jetty-documentation/src/main/asciidoc/distribution-guide/annotations/using-annotations.adoc b/jetty-documentation/src/main/asciidoc/distribution-guide/annotations/using-annotations.adoc index 765bb61a22a..091a68dd4b0 100644 --- a/jetty-documentation/src/main/asciidoc/distribution-guide/annotations/using-annotations.adoc +++ b/jetty-documentation/src/main/asciidoc/distribution-guide/annotations/using-annotations.adoc @@ -122,7 +122,7 @@ As is the case with annotation scanning, the link:#using-extra-classpath-method[ ____ [NOTE] As of Jetty 10, Annotations will be discovered even for old versions of `web.xml` (2.5). -Users wishing to not use `ServletContainerInitializers` from within the webapp with older versions of `web.xml` must call `WebAppContext.setConfigurationDiscovered(false)` either programmatically or in xml. +Users wishing not to use annotations from the webapp classpath must call `WebAppContext.setConfigurationDiscovered(false)` either programmatically or in xml. ____ ===== Controlling the order of ServletContainerInitializer invocation diff --git a/jetty-documentation/src/main/asciidoc/quickstart-guide/upgrading/upgrading-9.4-to.10.0.adoc b/jetty-documentation/src/main/asciidoc/quickstart-guide/upgrading/upgrading-9.4-to.10.0.adoc index fb0da833c5b..7b78916a4d2 100644 --- a/jetty-documentation/src/main/asciidoc/quickstart-guide/upgrading/upgrading-9.4-to.10.0.adoc +++ b/jetty-documentation/src/main/asciidoc/quickstart-guide/upgrading/upgrading-9.4-to.10.0.adoc @@ -29,7 +29,7 @@ Items such as the Java Platform Module System (JPMS), which Jetty 10 supports, a ==== ServletContainerInitializers As of Jetty 10, Annotations will be discovered even for old versions of `web.xml` (2.5). -Users wishing to not use `ServletContainerInitializers` from within the webapp with older versions of `web.xml` must call `WebAppContext.setConfigurationDiscovered(false)` either programmatically or in xml. +Users wishing not to use annotations from the webapp classpath with older versions of `web.xml` must call `WebAppContext.setConfigurationDiscovered(false)` either programmatically or in xml. ==== Removed Classes From 512f31b3af45127d46c710fdadf60d2c22475a79 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Thu, 2 Apr 2020 10:39:23 +0200 Subject: [PATCH 032/101] Improvements to the Jetty client documentation, cookies section. Signed-off-by: Simone Bordet --- jetty-documentation/pom.xml | 4 + .../client/http/client-http-cookie.adoc | 76 +++++++--------- .../client/http/client-http-intro.adoc | 12 +++ .../embedded/client/http/HTTPClientDocs.java | 89 +++++++++++++++++++ 4 files changed, 135 insertions(+), 46 deletions(-) diff --git a/jetty-documentation/pom.xml b/jetty-documentation/pom.xml index 000675ded84..2c5a472ec73 100644 --- a/jetty-documentation/pom.xml +++ b/jetty-documentation/pom.xml @@ -49,6 +49,10 @@ + + org.eclipse.jetty.toolchain + jetty-servlet-api + org.eclipse.jetty jetty-client diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-cookie.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-cookie.adoc index 3802218cdc8..dc060ebf5cf 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-cookie.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-cookie.adoc @@ -17,103 +17,87 @@ // [[client-http-cookie]] -=== Cookies Support +=== HttpClient Cookie Support -Jetty HTTP client supports cookies out of the box. +Jetty's `HttpClient` supports cookies out of the box. The `HttpClient` instance receives cookies from HTTP responses and stores them in a `java.net.CookieStore`, a class that is part of the JDK. When new requests are made, the cookie store is consulted and if there are matching cookies (that is, cookies that are not expired and that match domain and path of the request) then they are added to the requests. Applications can programmatically access the cookie store to find the cookies that have been set: -[source, java, subs="{sub-order}"] +[source,java,indent=0] ---- -CookieStore cookieStore = httpClient.getCookieStore(); -List cookies = cookieStore.get(URI.create("http://domain.com/path")); +include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=getCookies] ---- Applications can also programmatically set cookies as if they were returned from a HTTP response: -[source, java, subs="{sub-order}"] +[source,java,indent=0] ---- -CookieStore cookieStore = httpClient.getCookieStore(); -HttpCookie cookie = new HttpCookie("foo", "bar"); -cookie.setDomain("domain.com"); -cookie.setPath("/"); -cookie.setMaxAge(TimeUnit.DAYS.toSeconds(1)); -cookieStore.add(URI.create("http://domain.com"), cookie); +include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=setCookie] ---- -Cookies may be added only for a particular request: +Cookies may be added explicitly only for a particular request: -[source, java, subs="{sub-order}"] +[source,java,indent=0] ---- -ContentResponse response = httpClient.newRequest("http://domain.com/path") - .cookie(new HttpCookie("foo", "bar")) - .send(); +include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=requestCookie] ---- You can remove cookies that you do not want to be sent in future HTTP requests: -[source, java, subs="{sub-order}"] +[source,java,indent=0] ---- -CookieStore cookieStore = httpClient.getCookieStore(); -URI uri = URI.create("http://domain.com"); -List cookies = cookieStore.get(uri); -for (HttpCookie cookie : cookies) - cookieStore.remove(uri, cookie); +include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=removeCookie] ---- -If you want to totally disable cookie handling, you can install a `HttpCookieStore.Empty` instance in this way: +If you want to totally disable cookie handling, you can install a +`HttpCookieStore.Empty`. This must be done when `HttpClient` is used in a +proxy application, in this way: -[source, java, subs="{sub-order}"] +[source,java,indent=0] ---- -httpClient.setCookieStore(new HttpCookieStore.Empty()); +include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=emptyCookieStore] ---- You can enable cookie filtering by installing a cookie store that performs the filtering logic in this way: -[source, java, subs="{sub-order}"] +[source,java,indent=0] ---- -httpClient.setCookieStore(new GoogleOnlyCookieStore()); - -public class GoogleOnlyCookieStore extends HttpCookieStore -{ - @Override - public void add(URI uri, HttpCookie cookie) - { - if (uri.getHost().endsWith("google.com")) - super.add(uri, cookie); - } -} +include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=filteringCookieStore] ---- The example above will retain only cookies that come from the `google.com` domain or sub-domains. +// TODO: move this section to server-side ==== Special Characters in Cookies Jetty is compliant with link:https://tools.ietf.org/html/rfc6265[RFC6265], and as such care must be taken when setting a cookie value that includes special characters such as `;`. Previously, Version=1 cookies defined in link:https://tools.ietf.org/html/rfc2109[RFC2109] (and continued in link:https://tools.ietf.org/html/rfc2965[RFC2965]) allowed for special/reserved characters to be enclosed within double quotes when declared in a `Set-Cookie` response header: -[source, java, subs="{sub-order}"] +[source,subs="{sub-order}"] ---- Set-Cookie: foo="bar;baz";Version=1;Path="/secur" ---- -This was added to the HTTP Response header as follows: +This was added to the HTTP Response as follows: -[source, java, subs="{sub-order}"] +[source,java,subs="{sub-order}"] ---- -Cookie cookie = new Cookie("foo", "bar;baz"); -cookie.setPath("/secur"); -response.addCookie(cookie); +protected void service(HttpServletRequest request, HttpServletResponse response) +{ + javax.servlet.http.Cookie cookie = new Cookie("foo", "bar;baz"); + cookie.setPath("/secur"); + response.addCookie(cookie); +} ---- The introduction of RFC6265 has rendered this approach no longer possible; users are now required to encode cookie values that use these special characters. This can be done utilizing `javax.servlet.http.Cookie` as follows: -[source, java, subs="{sub-order}"] +[source,java,subs="{sub-order}"] ---- -Cookie cookie = new Cookie("foo", URLEncoder.encode("bar;baz", "utf-8")); +javax.servlet.http.Cookie cookie = new Cookie("foo", URLEncoder.encode("bar;baz", "UTF-8")); ---- Jetty validates all cookie names and values being added to the `HttpServletResponse` via the `addCookie(Cookie)` method. diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-intro.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-intro.adoc index 34f8c78547f..6492e3fe1b8 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-intro.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-intro.adoc @@ -46,6 +46,18 @@ Out of the box features that you get with the Jetty HTTP client include: [[client-http-start]] ==== Starting HttpClient +The Jetty artifact that provides the main HTTP client implementation is `jetty-client`. +The Maven artifact coordinates are the following: + +[source,xml,subs="{sub-order}"] +---- + + org.eclipse.jetty + jetty-client + {version} + +---- + The main class is named `org.eclipse.jetty.client.HttpClient`. You can think of a `HttpClient` instance as a browser instance. diff --git a/jetty-documentation/src/main/java/embedded/client/http/HTTPClientDocs.java b/jetty-documentation/src/main/java/embedded/client/http/HTTPClientDocs.java index 8549f712e64..d36b9fa9618 100644 --- a/jetty-documentation/src/main/java/embedded/client/http/HTTPClientDocs.java +++ b/jetty-documentation/src/main/java/embedded/client/http/HTTPClientDocs.java @@ -22,8 +22,12 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.net.CookieStore; +import java.net.HttpCookie; +import java.net.URI; import java.nio.ByteBuffer; import java.nio.file.Paths; +import java.util.List; import java.util.concurrent.TimeUnit; import java.util.function.LongConsumer; @@ -47,6 +51,7 @@ import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.ClientConnector; import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.HttpCookieStore; import org.eclipse.jetty.util.ssl.SslContextFactory; import static java.lang.System.Logger.Level.INFO; @@ -474,4 +479,88 @@ public class HTTPClientDocs request1.send(result -> System.getLogger("forwarder").log(INFO, "Sourcing from server1 complete")); // end::demandedContentListener[] } + + public void getCookies() throws Exception + { + HttpClient httpClient = new HttpClient(); + httpClient.start(); + + // tag::getCookies[] + CookieStore cookieStore = httpClient.getCookieStore(); + List cookies = cookieStore.get(URI.create("http://domain.com/path")); + // end::getCookies[] + } + + public void setCookie() throws Exception + { + HttpClient httpClient = new HttpClient(); + httpClient.start(); + + // tag::setCookie[] + CookieStore cookieStore = httpClient.getCookieStore(); + HttpCookie cookie = new HttpCookie("foo", "bar"); + cookie.setDomain("domain.com"); + cookie.setPath("/"); + cookie.setMaxAge(TimeUnit.DAYS.toSeconds(1)); + cookieStore.add(URI.create("http://domain.com"), cookie); + // end::setCookie[] + } + + public void requestCookie() throws Exception + { + HttpClient httpClient = new HttpClient(); + httpClient.start(); + + // tag::requestCookie[] + ContentResponse response = httpClient.newRequest("http://domain.com/path") + .cookie(new HttpCookie("foo", "bar")) + .send(); + // end::requestCookie[] + } + + public void removeCookie() throws Exception + { + HttpClient httpClient = new HttpClient(); + httpClient.start(); + + // tag::removeCookie[] + CookieStore cookieStore = httpClient.getCookieStore(); + URI uri = URI.create("http://domain.com"); + List cookies = cookieStore.get(uri); + for (HttpCookie cookie : cookies) + { + cookieStore.remove(uri, cookie); + } + // end::removeCookie[] + } + + public void emptyCookieStore() throws Exception + { + HttpClient httpClient = new HttpClient(); + httpClient.start(); + + // tag::emptyCookieStore[] + httpClient.setCookieStore(new HttpCookieStore.Empty()); + // end::emptyCookieStore[] + } + + public void filteringCookieStore() throws Exception + { + HttpClient httpClient = new HttpClient(); + httpClient.start(); + + // tag::filteringCookieStore[] + class GoogleOnlyCookieStore extends HttpCookieStore + { + @Override + public void add(URI uri, HttpCookie cookie) + { + if (uri.getHost().endsWith("google.com")) + super.add(uri, cookie); + } + } + + httpClient.setCookieStore(new GoogleOnlyCookieStore()); + // end::filteringCookieStore[] + } } From 9f38e433d40b1281e08b9358893e4e9cd26b121c Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Thu, 2 Apr 2020 12:19:23 +0200 Subject: [PATCH 033/101] Improvements to the Jetty client documentation, authentication section. Signed-off-by: Simone Bordet --- .../http/client-http-authentication.adoc | 100 ++++++++++-------- .../embedded/client/http/HTTPClientDocs.java | 56 ++++++++++ 2 files changed, 111 insertions(+), 45 deletions(-) diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-authentication.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-authentication.adoc index 4e3e48704ae..fcdd2fc3b01 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-authentication.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-authentication.adoc @@ -17,75 +17,85 @@ // [[client-http-authentication]] -=== Authentication Support +=== HttpClient Authentication Support -Jetty's HTTP client supports the `BASIC` and `DIGEST` authentication mechanisms defined by link:https://tools.ietf.org/html/rfc7235[RFC 7235]. +Jetty's `HttpClient` supports the `BASIC` and `DIGEST` authentication +mechanisms defined by link:https://tools.ietf.org/html/rfc7235[RFC 7235], +as well as the SPNEGO authentication mechanism defined in +link:https://tools.ietf.org/html/rfc4559[RFC 4559]. -You can configure authentication credentials in the HTTP client instance as follows: +The HTTP _conversation_ - the sequence of related HTTP requests - for a +request that needs authentication is the following: -[source, java, subs="{sub-order}"] +[plantuml] ---- -URI uri = new URI("http://domain.com/secure"); -String realm = "MyRealm"; -String user = "username"; -String pass = "password"; +skinparam backgroundColor transparent +skinparam monochrome true +skinparam shadowing false -// Add authentication credentials -AuthenticationStore auth = httpClient.getAuthenticationStore(); -auth.addAuthentication(new BasicAuthentication(uri, realm, user, pass)); +participant Application +participant HttpClient +participant Server -ContentResponse response = httpClient - .newRequest(uri) - .send() - .get(5, TimeUnit.SECONDS); +Application -> Server: GET /path +Server -> HttpClient: 401 + WWW-Authenticate +HttpClient -> Server: GET + Authentication +Server -> Application: 200 OK ---- -Jetty's HTTP client tests authentication credentials against the challenge(s) the server issues (see our section here on link:#configuring-security-secure-passwords[secure password obfuscation]), and if they match it automatically sends the right authentication headers to the server for authentication. -If the authentication is successful, it caches the result and reuses it for subsequent requests for the same domain and matching URIs. +Upon receiving a HTTP 401 response code, `HttpClient` looks at the +`WWW-Authenticate` response header (the server _challenge_) and then tries to +match configured authentication credentials to produce an `Authentication` +header that contains the authentication credentials to access the resource. -The HTTP conversation for a successful match is the following: +You can configure authentication credentials in the `HttpClient` instance as +follows: +[source,java,indent=0] ---- -Application HttpClient Server - | | | - |--- GET ---|------------ GET ----------->| - | | | - | |<-- 401 + WWW-Authenticate --| - | | | - | |--- GET + Authentication --->| - | | | - |<-- 200 ---|------------ 200 ------------| +include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=addAuthentication] ---- -The application does not receive events related to the response with code 401, they are handled internally by `HttpClient` which produces a request similar to the original but with the correct `Authorization` header, and then relays the response with code 200 to the application. +``Authentication``s are matched against the server challenge first by +mechanism (e.g. `BASIC` or `DIGEST`), then by realm and then by URI. -Successful authentications are cached, but it is possible to clear them in order to force authentication again: +If an `Authentication` match is found, the application does not receive events +related to the HTTP 401 response. These events are handled internally by +`HttpClient` which produces another (internal) request similar to the original +request but with an additional `Authorization` header. -[source, java, subs="{sub-order}"] +If the authentication is successful, the server responds with a HTTP 200 and +`HttpClient` caches the `Authentication.Result` so that subsequent requests +for a matching URI will not incur in the additional rountrip caused by the +HTTP 401 response. + +It is possible to clear ``Authentication.Result``s in order to force +authentication again: + +[source,java,indent=0] ---- -httpClient.getAuthenticationStore().clearAuthenticationResults(); +include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=clearResults] ---- -Authentications may be preempted to avoid the additional roundtrip due to the server challenge in this way: +Authentication results may be preempted to avoid the additional roundtrip +due to the server challenge in this way: -[source, java, subs="{sub-order}"] +[source,java,indent=0] ---- -AuthenticationStore auth = httpClient.getAuthenticationStore(); -URI uri = URI.create("http://domain.com/secure"); -auth.addAuthenticationResult(new BasicAuthentication.BasicResult(uri, "username", "password")); +include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=preemptedResult] ---- -In this way, requests for the given URI are enriched by `HttpClient` immediately with the `Authorization` header, and the server should respond with a 200 and the resource content rather than with the 401 and the challenge. +In this way, requests for the given URI are enriched immediately with the +`Authorization` header, and the server should respond with HTTP 200 (and the +resource content) rather than with the 401 and the challenge. -It is also possible to preempt the authentication for a single request only, in this way: +It is also possible to preempt the authentication for a single request only, +in this way: -[source, java, subs="{sub-order}"] +[source,java,indent=0] ---- -URI uri = URI.create("http://domain.com/secure"); -Authentication.Result authn = new BasicAuthentication.BasicResult(uri, "username", "password") -Request request = httpClient.newRequest(uri); -authn.apply(request); -request.send(); +include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=requestPreemptedResult] ---- -See also the link:#client-http-proxy-authentication[proxy authentication section] for further information about how authentication works with HTTP proxies. +See also the link:#client-http-proxy-authentication[proxy authentication section] +for further information about how authentication works with HTTP proxies. diff --git a/jetty-documentation/src/main/java/embedded/client/http/HTTPClientDocs.java b/jetty-documentation/src/main/java/embedded/client/http/HTTPClientDocs.java index d36b9fa9618..3e44f7c6bb8 100644 --- a/jetty-documentation/src/main/java/embedded/client/http/HTTPClientDocs.java +++ b/jetty-documentation/src/main/java/embedded/client/http/HTTPClientDocs.java @@ -32,12 +32,15 @@ import java.util.concurrent.TimeUnit; import java.util.function.LongConsumer; import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.Authentication; +import org.eclipse.jetty.client.api.AuthenticationStore; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.api.Result; import org.eclipse.jetty.client.dynamic.HttpClientTransportDynamic; import org.eclipse.jetty.client.util.AsyncRequestContent; +import org.eclipse.jetty.client.util.BasicAuthentication; import org.eclipse.jetty.client.util.BufferingResponseListener; import org.eclipse.jetty.client.util.BytesRequestContent; import org.eclipse.jetty.client.util.FutureResponseListener; @@ -563,4 +566,57 @@ public class HTTPClientDocs httpClient.setCookieStore(new GoogleOnlyCookieStore()); // end::filteringCookieStore[] } + + public void addAuthentication() throws Exception + { + HttpClient httpClient = new HttpClient(); + httpClient.start(); + + // tag::addAuthentication[] + // Add authentication credentials. + AuthenticationStore auth = httpClient.getAuthenticationStore(); + + URI uri1 = new URI("http://mydomain.com/secure"); + auth.addAuthentication(new BasicAuthentication(uri1, "MyRealm", "userName1", "password1")); + + URI uri2 = new URI("http://otherdomain.com/admin"); + auth.addAuthentication(new BasicAuthentication(uri1, "AdminRealm", "admin", "password")); + // end::addAuthentication[] + } + + public void clearResults() throws Exception + { + HttpClient httpClient = new HttpClient(); + httpClient.start(); + + // tag::clearResults[] + httpClient.getAuthenticationStore().clearAuthenticationResults(); + // end::clearResults[] + } + + public void preemptedResult() throws Exception + { + HttpClient httpClient = new HttpClient(); + httpClient.start(); + + // tag::preemptedResult[] + AuthenticationStore auth = httpClient.getAuthenticationStore(); + URI uri = URI.create("http://domain.com/secure"); + auth.addAuthenticationResult(new BasicAuthentication.BasicResult(uri, "username", "password")); + // end::preemptedResult[] + } + + public void requestPreemptedResult() throws Exception + { + HttpClient httpClient = new HttpClient(); + httpClient.start(); + + // tag::requestPreemptedResult[] + URI uri = URI.create("http://domain.com/secure"); + Authentication.Result authn = new BasicAuthentication.BasicResult(uri, "username", "password"); + Request request = httpClient.newRequest(uri); + authn.apply(request); + request.send(); + // end::requestPreemptedResult[] + } } From eaf9d43a0bc4772ede477b2719a16b45a9f13dd4 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Thu, 2 Apr 2020 13:22:54 +0200 Subject: [PATCH 034/101] Improvements to the Jetty client documentation, proxy section. Signed-off-by: Simone Bordet --- .../client/http/client-http-proxy.adoc | 109 ++++++++---------- .../embedded/client/http/HTTPClientDocs.java | 47 ++++++++ 2 files changed, 96 insertions(+), 60 deletions(-) diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-proxy.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-proxy.adoc index 13f53584e93..88554725612 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-proxy.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-proxy.adoc @@ -17,87 +17,76 @@ // [[client-http-proxy]] -=== Proxy Support +=== HttpClient Proxy Support -Jetty's HTTP client can be configured to use proxies to connect to destinations. +Jetty's `HttpClient` can be configured to use proxies to connect to destinations. -Two types of proxies are available out of the box: a HTTP proxy (provided by class `org.eclipse.jetty.client.HttpProxy`) and a SOCKS 4 proxy (provided by class `org.eclipse.jetty.client.Socks4Proxy`). +Two types of proxies are available out of the box: a HTTP proxy (provided by +class `org.eclipse.jetty.client.HttpProxy`) and a SOCKS 4 proxy (provided by +class `org.eclipse.jetty.client.Socks4Proxy`). Other implementations may be written by subclassing `ProxyConfiguration.Proxy`. The following is a typical configuration: -[source, java, subs="{sub-order}"] +[source,java,indent=0] ---- -ProxyConfiguration proxyConfig = httpClient.getProxyConfiguration(); -HttpProxy proxy = new HttpProxy("proxyHost", proxyPort); - -// Do not proxy requests for localhost:8080 -proxy.getExcludedAddresses().add("localhost:8080"); - -// add the new proxy to the list of proxies already registered -proxyConfig.getProxies().add(proxy); - -ContentResponse response = httpClient.GET(uri); +include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=proxy] ---- -You specify the proxy host and port, and optionally also the addresses that you do not want to be proxied, and then add the proxy configuration on the `ProxyConfiguration` instance. +You specify the proxy host and proxy port, and optionally also the addresses +that you do not want to be proxied, and then add the proxy configuration on +the `ProxyConfiguration` instance. -Configured in this way, `HttpClient` makes requests to the HTTP proxy (for plain-text HTTP requests) or establishes a tunnel via `HTTP CONNECT` (for encrypted HTTPS requests). +Configured in this way, `HttpClient` makes requests to the HTTP proxy (for +plain-text HTTP requests) or establishes a tunnel via HTTP `CONNECT` (for +encrypted HTTPS requests). + +Proxying is supported for both HTTP/1.1 and HTTP/2. [[client-http-proxy-authentication]] ==== Proxy Authentication Support -Jetty's HTTP client support proxy authentication in the same way it supports link:#client-http-authentication[server authentication]. +Jetty's `HttpClient` supports proxy authentication in the same way it supports +link:#client-http-authentication[server authentication]. -In the example below, the proxy requires Basic authentication, but the server requires Digest authentication, and therefore: +In the example below, the proxy requires `BASIC` authentication, but the server +requires `DIGEST` authentication, and therefore: -[source, java, subs="{sub-order}"] +[source,java,indent=0] ---- -URI proxyURI = new URI("http://proxy.net:8080"); -URI serverURI = new URI("http://domain.com/secure"); - -AuthenticationStore auth = httpClient.getAuthenticationStore(); - -// Proxy credentials. -auth.addAuthentication(new BasicAuthentication(proxyURI, "ProxyRealm", "proxyUser", "proxyPass")); - -// Server credentials. -auth.addAuthentication(new DigestAuthentication(serverURI, "ServerRealm", "serverUser", "serverPass")); - -// Proxy configuration. -ProxyConfiguration proxyConfig = httpClient.getProxyConfiguration(); -HttpProxy proxy = new HttpProxy("proxy.net", 8080); -proxyConfig.getProxies().add(proxy); - -ContentResponse response = httpClient.newRequest(serverURI) - .send() - .get(5, TimeUnit.SECONDS); +include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=proxyAuthentication] ---- -The HTTP conversation for successful authentications on both the proxy and the server is the following: +The HTTP conversation for successful authentications on both the proxy and the +server is the following: +[plantuml] ---- -Application HttpClient Proxy Server - | | | | - |--- GET -->|------------- GET ------------->| | - | | | | - | |<----- 407 + Proxy-Authn -------| | - | | | | - | |------ GET + Proxy-Authz ------>| | - | | | | - | | |---------- GET --------->| - | | | | - | | |<--- 401 + WWW-Authn ----| - | | | | - | |<------ 401 + WWW-Authn --------| | - | | | | - | |-- GET + Proxy-Authz + Authz -->| | - | | | | - | | |------ GET + Authz ----->| - | | | | - |<-- 200 ---|<------------ 200 --------------|<--------- 200 ----------| +skinparam backgroundColor transparent +skinparam monochrome true +skinparam shadowing false + +participant Application +participant HttpClient +participant Proxy +participant Server + +Application -> Proxy: GET /path +Proxy -> HttpClient: 407 + Proxy-Authenticate +HttpClient -> Proxy: GET /path + Proxy-Authorization +Proxy -> Server: GET /path +Server -> Proxy: 401 + WWW-Authenticate +Proxy -> HttpClient: 401 + WWW-Authenticate +HttpClient -> Proxy: GET /path + Proxy-Authorization + Authorization +Proxy -> Server: GET /path + Authorization +Server -> Proxy: 200 OK +Proxy -> HttpClient: 200 OK +HttpClient -> Application: 200 OK ---- -The application does not receive events related to the responses with code 407 and 401 since they are handled internally by `HttpClient`. +The application does not receive events related to the responses with code 407 +and 401 since they are handled internally by `HttpClient`. -Similarly to the link:#client-http-authentication[authentication section], the proxy authentication result and the server authentication result can be preempted to avoid, respectively, the 407 and 401 roundtrips. +Similarly to the link:#client-http-authentication[authentication section], the +proxy authentication result and the server authentication result can be +preempted to avoid, respectively, the 407 and 401 roundtrips. diff --git a/jetty-documentation/src/main/java/embedded/client/http/HTTPClientDocs.java b/jetty-documentation/src/main/java/embedded/client/http/HTTPClientDocs.java index 3e44f7c6bb8..decd799c0d4 100644 --- a/jetty-documentation/src/main/java/embedded/client/http/HTTPClientDocs.java +++ b/jetty-documentation/src/main/java/embedded/client/http/HTTPClientDocs.java @@ -32,6 +32,8 @@ import java.util.concurrent.TimeUnit; import java.util.function.LongConsumer; import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.HttpProxy; +import org.eclipse.jetty.client.ProxyConfiguration; import org.eclipse.jetty.client.api.Authentication; import org.eclipse.jetty.client.api.AuthenticationStore; import org.eclipse.jetty.client.api.ContentResponse; @@ -43,6 +45,7 @@ import org.eclipse.jetty.client.util.AsyncRequestContent; import org.eclipse.jetty.client.util.BasicAuthentication; import org.eclipse.jetty.client.util.BufferingResponseListener; import org.eclipse.jetty.client.util.BytesRequestContent; +import org.eclipse.jetty.client.util.DigestAuthentication; import org.eclipse.jetty.client.util.FutureResponseListener; import org.eclipse.jetty.client.util.InputStreamRequestContent; import org.eclipse.jetty.client.util.InputStreamResponseListener; @@ -619,4 +622,48 @@ public class HTTPClientDocs request.send(); // end::requestPreemptedResult[] } + + public void proxy() throws Exception + { + HttpClient httpClient = new HttpClient(); + httpClient.start(); + + // tag::proxy[] + HttpProxy proxy = new HttpProxy("proxyHost", 8888); + + // Do not proxy requests for localhost:8080. + proxy.getExcludedAddresses().add("localhost:8080"); + + // Add the new proxy to the list of proxies already registered. + ProxyConfiguration proxyConfig = httpClient.getProxyConfiguration(); + proxyConfig.getProxies().add(proxy); + + ContentResponse response = httpClient.GET("http://domain.com/path"); + // end::proxy[] + } + + public void proxyAuthentication() throws Exception + { + HttpClient httpClient = new HttpClient(); + httpClient.start(); + + // tag::proxyAuthentication[] + AuthenticationStore auth = httpClient.getAuthenticationStore(); + + // Proxy credentials. + URI proxyURI = new URI("http://proxy.net:8080"); + auth.addAuthentication(new BasicAuthentication(proxyURI, "ProxyRealm", "proxyUser", "proxyPass")); + + // Server credentials. + URI serverURI = new URI("http://domain.com/secure"); + auth.addAuthentication(new DigestAuthentication(serverURI, "ServerRealm", "serverUser", "serverPass")); + + // Proxy configuration. + ProxyConfiguration proxyConfig = httpClient.getProxyConfiguration(); + HttpProxy proxy = new HttpProxy("proxy.net", 8080); + proxyConfig.getProxies().add(proxy); + + ContentResponse response = httpClient.newRequest(serverURI).send(); + // end::proxyAuthentication[] + } } From 51c42f2849b5e6a3db9dfd935e58e489418394e5 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Fri, 3 Apr 2020 15:48:31 +0200 Subject: [PATCH 035/101] Improvements to the Jetty client documentation, protocols section. Signed-off-by: Simone Bordet --- .../alpn/client/ALPNClientConnection.java | 5 +- .../client/ALPNClientConnectionFactory.java | 8 - .../java/client/JDK9ClientALPNProcessor.java | 7 +- .../dynamic/HttpClientTransportDynamic.java | 64 ++++--- .../http/HttpClientConnectionFactory.java | 28 ++- jetty-documentation/pom.xml | 9 +- .../http2/introduction.adoc | 2 +- .../client/http/client-http-api.adoc | 4 +- .../client/http/client-http-transport.adoc | 179 +++++++++++++----- .../examples/embedded-minimal-servlet.adoc | 2 +- .../examples/embedded-one-webapp.adoc | 2 +- .../embedded/client/http/HTTPClientDocs.java | 134 +++++++++++++ .../ClientConnectionFactoryOverHTTP2.java | 34 +++- .../jetty/io/ClientConnectionFactory.java | 23 +-- .../tests/WebSocketOverHTTP2Test.java | 16 +- .../HttpClientTransportDynamicTest.java | 73 ++++--- .../client/ProxyWithDynamicTransportTest.java | 10 +- 17 files changed, 445 insertions(+), 155 deletions(-) diff --git a/jetty-alpn/jetty-alpn-client/src/main/java/org/eclipse/jetty/alpn/client/ALPNClientConnection.java b/jetty-alpn/jetty-alpn-client/src/main/java/org/eclipse/jetty/alpn/client/ALPNClientConnection.java index 3b46fbf013e..295242a908f 100644 --- a/jetty-alpn/jetty-alpn-client/src/main/java/org/eclipse/jetty/alpn/client/ALPNClientConnection.java +++ b/jetty-alpn/jetty-alpn-client/src/main/java/org/eclipse/jetty/alpn/client/ALPNClientConnection.java @@ -44,9 +44,6 @@ public class ALPNClientConnection extends NegotiatingClientConnection public void selected(String protocol) { - if (protocol == null || !protocols.contains(protocol)) - close(); - else - completed(protocol); + completed(protocol); } } diff --git a/jetty-alpn/jetty-alpn-client/src/main/java/org/eclipse/jetty/alpn/client/ALPNClientConnectionFactory.java b/jetty-alpn/jetty-alpn-client/src/main/java/org/eclipse/jetty/alpn/client/ALPNClientConnectionFactory.java index 900013fa1f4..63c2ef63231 100644 --- a/jetty-alpn/jetty-alpn-client/src/main/java/org/eclipse/jetty/alpn/client/ALPNClientConnectionFactory.java +++ b/jetty-alpn/jetty-alpn-client/src/main/java/org/eclipse/jetty/alpn/client/ALPNClientConnectionFactory.java @@ -110,12 +110,4 @@ public class ALPNClientConnectionFactory extends NegotiatingClientConnectionFact } throw new IllegalStateException("No ALPNProcessor for " + engine); } - - public static class ALPN extends Info - { - public ALPN(Executor executor, ClientConnectionFactory factory, List protocols) - { - super(List.of("alpn"), new ALPNClientConnectionFactory(executor, factory, protocols)); - } - } } diff --git a/jetty-alpn/jetty-alpn-java-client/src/main/java/org/eclipse/jetty/alpn/java/client/JDK9ClientALPNProcessor.java b/jetty-alpn/jetty-alpn-java-client/src/main/java/org/eclipse/jetty/alpn/java/client/JDK9ClientALPNProcessor.java index 3a455ffb024..c1c392242fa 100644 --- a/jetty-alpn/jetty-alpn-java-client/src/main/java/org/eclipse/jetty/alpn/java/client/JDK9ClientALPNProcessor.java +++ b/jetty-alpn/jetty-alpn-java-client/src/main/java/org/eclipse/jetty/alpn/java/client/JDK9ClientALPNProcessor.java @@ -75,8 +75,11 @@ public class JDK9ClientALPNProcessor implements ALPNProcessor.Client { String protocol = alpnConnection.getSSLEngine().getApplicationProtocol(); if (LOG.isDebugEnabled()) - LOG.debug("selected protocol {}", protocol); - alpnConnection.selected(protocol); + LOG.debug("selected protocol '{}'", protocol); + if (protocol != null && !protocol.isEmpty()) + alpnConnection.selected(protocol); + else + alpnConnection.selected(null); } } } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/dynamic/HttpClientTransportDynamic.java b/jetty-client/src/main/java/org/eclipse/jetty/client/dynamic/HttpClientTransportDynamic.java index 732d6d843ef..2ab93cdd6a8 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/dynamic/HttpClientTransportDynamic.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/dynamic/HttpClientTransportDynamic.java @@ -19,12 +19,14 @@ package org.eclipse.jetty.client.dynamic; import java.io.IOException; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; +import java.util.stream.Stream; import org.eclipse.jetty.alpn.client.ALPNClientConnection; import org.eclipse.jetty.alpn.client.ALPNClientConnectionFactory; @@ -37,6 +39,7 @@ import org.eclipse.jetty.client.MultiplexConnectionPool; import org.eclipse.jetty.client.MultiplexHttpDestination; import org.eclipse.jetty.client.Origin; import org.eclipse.jetty.client.http.HttpClientConnectionFactory; +import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.io.ClientConnectionFactory; import org.eclipse.jetty.io.ClientConnector; @@ -105,7 +108,7 @@ public class HttpClientTransportDynamic extends AbstractConnectorHttpClientTrans factoryInfos = new Info[]{HttpClientConnectionFactory.HTTP11}; this.factoryInfos = Arrays.asList(factoryInfos); this.protocols = Arrays.stream(factoryInfos) - .flatMap(info -> info.getProtocols().stream()) + .flatMap(info -> Stream.concat(info.getProtocols(false).stream(), info.getProtocols(true).stream())) .distinct() .map(p -> p.toLowerCase(Locale.ENGLISH)) .collect(Collectors.toList()); @@ -117,9 +120,9 @@ public class HttpClientTransportDynamic extends AbstractConnectorHttpClientTrans @Override public Origin newOrigin(HttpRequest request) { - boolean ssl = HttpClient.isSchemeSecure(request.getScheme()); + boolean secure = HttpClient.isSchemeSecure(request.getScheme()); String http1 = "http/1.1"; - String http2 = ssl ? "h2" : "h2c"; + String http2 = secure ? "h2" : "h2c"; List protocols = List.of(); if (request.isVersionExplicit()) { @@ -130,16 +133,23 @@ public class HttpClientTransportDynamic extends AbstractConnectorHttpClientTrans } else { - if (ssl) + if (secure) { // There may be protocol negotiation, so preserve the order // of protocols chosen by the application. // We need to keep multiple protocols in case the protocol // is negotiated: e.g. [http/1.1, h2] negotiates [h2], but // here we don't know yet what will be negotiated. + List http = List.of("http/1.1", "h2c", "h2"); protocols = this.protocols.stream() - .filter(p -> p.equals(http1) || p.equals(http2)) - .collect(Collectors.toList()); + .filter(http::contains) + .collect(Collectors.toCollection(ArrayList::new)); + + // The http/1.1 upgrade to http/2 over TLS implicitly + // "negotiates" [h2c], so we need to remove [h2] + // because we don't want to negotiate using ALPN. + if (request.getHeaders().contains(HttpHeader.UPGRADE, "h2c")) + protocols.remove("h2"); } else { @@ -149,7 +159,7 @@ public class HttpClientTransportDynamic extends AbstractConnectorHttpClientTrans } Origin.Protocol protocol = null; if (!protocols.isEmpty()) - protocol = new Origin.Protocol(protocols, ssl && protocols.contains(http2)); + protocol = new Origin.Protocol(protocols, secure && protocols.contains(http2)); return getHttpClient().createOrigin(request, protocol); } @@ -164,32 +174,33 @@ public class HttpClientTransportDynamic extends AbstractConnectorHttpClientTrans { HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY); Origin.Protocol protocol = destination.getOrigin().getProtocol(); - ClientConnectionFactory.Info factoryInfo; + ClientConnectionFactory factory; if (protocol == null) { // Use the default ClientConnectionFactory. - factoryInfo = factoryInfos.get(0); + factory = factoryInfos.get(0).getClientConnectionFactory(); } else { if (destination.isSecure() && protocol.isNegotiate()) { - factoryInfo = new ALPNClientConnectionFactory.ALPN(getClientConnector().getExecutor(), this::newNegotiatedConnection, protocol.getProtocols()); + factory = new ALPNClientConnectionFactory(getClientConnector().getExecutor(), this::newNegotiatedConnection, protocol.getProtocols()); } else { - factoryInfo = findClientConnectionFactoryInfo(protocol.getProtocols()) - .orElseThrow(() -> new IOException("Cannot find " + ClientConnectionFactory.class.getSimpleName() + " for " + protocol)); + factory = findClientConnectionFactoryInfo(protocol.getProtocols(), destination.isSecure()) + .orElseThrow(() -> new IOException("Cannot find " + ClientConnectionFactory.class.getSimpleName() + " for " + protocol)) + .getClientConnectionFactory(); } } - return factoryInfo.getClientConnectionFactory().newConnection(endPoint, context); + return factory.newConnection(endPoint, context); } public void upgrade(EndPoint endPoint, Map context) { HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY); Origin.Protocol protocol = destination.getOrigin().getProtocol(); - Info info = findClientConnectionFactoryInfo(protocol.getProtocols()) + Info info = findClientConnectionFactoryInfo(protocol.getProtocols(), destination.isSecure()) .orElseThrow(() -> new IllegalStateException("Cannot find " + ClientConnectionFactory.class.getSimpleName() + " to upgrade to " + protocol)); info.upgrade(endPoint, context); } @@ -200,13 +211,22 @@ public class HttpClientTransportDynamic extends AbstractConnectorHttpClientTrans { ALPNClientConnection alpnConnection = (ALPNClientConnection)endPoint.getConnection(); String protocol = alpnConnection.getProtocol(); - if (LOG.isDebugEnabled()) - LOG.debug("ALPN negotiated {} among {}", protocol, alpnConnection.getProtocols()); - if (protocol == null) - throw new IOException("Could not negotiate protocol among " + alpnConnection.getProtocols()); - List protocols = List.of(protocol); - Info factoryInfo = findClientConnectionFactoryInfo(protocols) + Info factoryInfo; + if (protocol != null) + { + if (LOG.isDebugEnabled()) + LOG.debug("ALPN negotiated {} among {}", protocol, alpnConnection.getProtocols()); + List protocols = List.of(protocol); + factoryInfo = findClientConnectionFactoryInfo(protocols, true) .orElseThrow(() -> new IOException("Cannot find " + ClientConnectionFactory.class.getSimpleName() + " for negotiated protocol " + protocol)); + } + else + { + // Server does not support ALPN, let's try the first protocol. + factoryInfo = factoryInfos.get(0); + if (LOG.isDebugEnabled()) + LOG.debug("No ALPN protocol, using {}", factoryInfo); + } return factoryInfo.getClientConnectionFactory().newConnection(endPoint, context); } catch (Throwable failure) @@ -216,10 +236,10 @@ public class HttpClientTransportDynamic extends AbstractConnectorHttpClientTrans } } - private Optional findClientConnectionFactoryInfo(List protocols) + private Optional findClientConnectionFactoryInfo(List protocols, boolean secure) { return factoryInfos.stream() - .filter(info -> info.matches(protocols)) + .filter(info -> info.matches(protocols, secure)) .findFirst(); } } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpClientConnectionFactory.java b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpClientConnectionFactory.java index 418616175f6..2063596c359 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpClientConnectionFactory.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpClientConnectionFactory.java @@ -21,12 +21,16 @@ package org.eclipse.jetty.client.http; import java.util.List; import java.util.Map; +import org.eclipse.jetty.client.dynamic.HttpClientTransportDynamic; import org.eclipse.jetty.io.ClientConnectionFactory; import org.eclipse.jetty.io.EndPoint; public class HttpClientConnectionFactory implements ClientConnectionFactory { - public static final Info HTTP11 = new Info(List.of("http/1.1"), new HttpClientConnectionFactory()); + /** + *

    Representation of the {@code HTTP/1.1} application protocol used by {@link HttpClientTransportDynamic}.

    + */ + public static final Info HTTP11 = new HTTP11(new HttpClientConnectionFactory()); @Override public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map context) @@ -34,4 +38,26 @@ public class HttpClientConnectionFactory implements ClientConnectionFactory HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, context); return customize(connection, context); } + + private static class HTTP11 extends Info + { + private static final List protocols = List.of("http/1.1"); + + private HTTP11(ClientConnectionFactory factory) + { + super(factory); + } + + @Override + public List getProtocols(boolean secure) + { + return protocols; + } + + @Override + public String toString() + { + return String.format("%s@%x%s", getClass().getSimpleName(), hashCode(), protocols); + } + } } diff --git a/jetty-documentation/pom.xml b/jetty-documentation/pom.xml index 2c5a472ec73..3ad33457458 100644 --- a/jetty-documentation/pom.xml +++ b/jetty-documentation/pom.xml @@ -59,8 +59,13 @@ ${project.version}
    - org.eclipse.jetty - jetty-io + org.eclipse.jetty.http2 + http2-http-client-transport + ${project.version} + + + org.eclipse.jetty.fcgi + fcgi-client ${project.version}
    diff --git a/jetty-documentation/src/main/asciidoc/distribution-guide/http2/introduction.adoc b/jetty-documentation/src/main/asciidoc/distribution-guide/http2/introduction.adoc index 92fa37c7e8c..2c81f182022 100644 --- a/jetty-documentation/src/main/asciidoc/distribution-guide/http2/introduction.adoc +++ b/jetty-documentation/src/main/asciidoc/distribution-guide/http2/introduction.adoc @@ -50,5 +50,5 @@ The Jetty HTTP/2 implementation consists of the following sub-projects (each pro 2. `http2-hpack`: Contains the HTTP/2 HPACK implementation for HTTP header compression. 3. `http2-server`: Provides the server-side implementation of HTTP/2. 4. `http2-client`: Provides the implementation of HTTP/2 client with a low level HTTP/2 API, dealing with HTTP/2 streams, frames, etc. -5. `http2-http-client-transport`: Provides the implementation of the HTTP/2 transport for `HttpClient` (see xref:http-client[]). +5. `http2-http-client-transport`: Provides the implementation of the HTTP/2 transport for `HttpClient` (see xref:client-http[this section]). Applications can use the higher level API provided by `HttpClient` to send HTTP requests and receive HTTP responses, and the HTTP/2 transport will take care of converting them in HTTP/2 format (see also https://webtide.com/http2-support-for-httpclient/[this blog entry]). diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-api.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-api.adoc index 4765fb47a18..b369bb33e91 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-api.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-api.adoc @@ -34,7 +34,7 @@ include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tags=simpleBl The method `HttpClient.GET(...)` performs a HTTP `GET` request to the given URI and returns a `ContentResponse` when the request/response conversation completes successfully. The `ContentResponse` object contains the HTTP response information: status code, headers and possibly content. -The content length is limited by default to 2 MiB; for larger content see xref:http-client-response-content[]. +The content length is limited by default to 2 MiB; for larger content see xref:client-http-content-response[]. If you want to customize the request, for example by issuing a `HEAD` request instead of a `GET`, and simulating a browser user agent, you can do it in this way: @@ -190,7 +190,7 @@ which allows applications to write request content when it is available to the ` include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tags=outputStreamRequestContent] ---- -[[http-client-response-content]] +[[client-http-content-response]] ===== Response Content Handling Jetty's `HttpClient` allows applications to handle response content in different ways. diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-transport.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-transport.adoc index 21870cc9460..518483296b4 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-transport.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-transport.adoc @@ -16,21 +16,25 @@ // ======================================================================== // -[[http-client-transport]] -=== Pluggable Transports +[[client-http-transport]] +=== HttpClient Pluggable Transports -Jetty's HTTP client can be configured to use different transports to carry the semantic of HTTP requests and responses. +Jetty's `HttpClient` can be configured to use different transports to carry the +semantic of HTTP requests and responses. -This means that the intention of a client to request resource `/index.html` using the `GET` method can be carried over the network in different formats. +This means that the intention of a client to request resource `/index.html` +using the `GET` method can be carried over the network in different formats. -A HTTP client transport is the component that is in charge of converting a high-level, semantic, HTTP requests such as "GET resource /index.html" into the specific format understood by the server (for example, HTTP/2), and to convert the server response from the specific format (HTTP/2) into high-level, semantic objects that can be used by applications. +A `HttpClient` transport is the component that is in charge of converting a +high-level, semantic, HTTP requests such as "`GET` resource ``/index.html``" +into the specific format understood by the server (for example, HTTP/2), and to +convert the server response from the specific format (HTTP/2) into high-level, +semantic objects that can be used by applications. -In this way, applications are not aware of the actual protocol being used. -This allows them to write their logic against a high-level API that hides the details of the specific protocol being used over the network. +The most common protocol format is HTTP/1.1, a textual protocol with lines +separated by `\r\n`: -The most common protocol format is HTTP/1.1, a text-based protocol with lines separated by `\r\n`: - -[source, screen, subs="{sub-order}"] +[source,screen,subs="{sub-order}"] ---- GET /index.html HTTP/1.1\r\n Host: domain.com\r\n @@ -40,7 +44,7 @@ Host: domain.com\r\n However, the same request can be made using FastCGI, a binary protocol: -[source, screen, subs="{sub-order}"] +[source,screen,subs="{sub-order}"] ---- x01 x01 x00 x01 x00 x08 x00 x00 x00 x01 x01 x00 x00 x00 x00 x00 @@ -52,64 +56,153 @@ x0C x0B D O C U M E ... ---- -Similarly, HTTP/2 is a binary protocol that transports the same information in a yet different format. +Similarly, HTTP/2 is a binary protocol that transports the same information +in a yet different format. +A protocol may be _negotiated_ between client and server. A request for a +resource may be sent using one protocol (for example, HTTP/1.1), but the +response may arrive in a different protocol (for example, HTTP/2). + +`HttpClient` supports 3 static transports, each speaking only one protocol: +link:#client-http-transport-http11[HTTP/1.1], +link:#client-http-transport-http2[HTTP/2] and +link:#client-http-transport-fcgi[FastCGI], +all of them with 2 variants: clear-text and TLS encrypted. + +`HttpClient` also supports one +link:#client-http-transport-dynamic[dynamic transport], +that can speak different protocols and can select the right protocol by +negotiating it with the server or by explicit indication from applications. + +Applications are typically not aware of the actual protocol being used. +This allows them to write their logic against a high-level API that hides the +details of the specific protocol being used over the network. + +[[client-http-transport-http11]] ==== HTTP/1.1 Transport HTTP/1.1 is the default transport. -[source, java, subs="{sub-order}"] +[source,java,indent=0] ---- -// No transport specified, using default. -HttpClient client = new HttpClient(); -client.start(); +include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=defaultTransport] ---- -If you want to customize the HTTP/1.1 transport, you can explicitly configure `HttpClient` in this way: +If you want to customize the HTTP/1.1 transport, you can explicitly configure +it in this way: -[source, java, subs="{sub-order}"] +[source,java,indent=0] ---- -int selectors = 1; -HttpClientTransportOverHTTP transport = new HttpClientTransportOverHTTP(selectors); - -HttpClient client = new HttpClient(transport, null); -client.start(); +include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=http11Transport] ---- -The example above allows you to customize the number of NIO selectors that `HttpClient` will be using. - +[[client-http-transport-http2]] ==== HTTP/2 Transport The HTTP/2 transport can be configured in this way: -[source, java, subs="{sub-order}"] +[source,java,indent=0] ---- -HTTP2Client h2Client = new HTTP2Client(); -h2Client.setSelectors(1); -HttpClientTransportOverHTTP2 transport = new HttpClientTransportOverHTTP2(h2Client); - -HttpClient client = new HttpClient(transport, null); -client.start(); +include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=http2Transport] ---- -`HTTP2Client` is the lower-level client that provides an API based on HTTP/2 concepts such as _sessions_, _streams_ and _frames_ that are specific to HTTP/2. +`HTTP2Client` is the lower-level client that provides an API based on HTTP/2 +concepts such as _sessions_, _streams_ and _frames_ that are specific to HTTP/2. +See link:#client-http2[the HTTP/2 client section] for more information. -`HttpClientTransportOverHTTP2` uses `HTTP2Client` to format high-level semantic HTTP requests ("GET resource /index.html") into the HTTP/2 specific format. +`HttpClientTransportOverHTTP2` uses `HTTP2Client` to format high-level semantic +HTTP requests (like "GET resource /index.html") into the HTTP/2 specific format. +[[client-http-transport-fcgi]] ==== FastCGI Transport The FastCGI transport can be configured in this way: -[source, java, subs="{sub-order}"] +[source,java,indent=0] ---- -int selectors = 1; -String scriptRoot = "/var/www/wordpress"; -HttpClientTransportOverFCGI transport = new HttpClientTransportOverFCGI(selectors, false, scriptRoot); - -HttpClient client = new HttpClient(transport, null); -client.start(); +include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=fcgiTransport] ---- -In order to make requests using the FastCGI transport, you need to have a FastCGI server such as https://en.wikipedia.org/wiki/PHP#PHPFPM[PHP-FPM] (see also http://php.net/manual/en/install.fpm.php). +In order to make requests using the FastCGI transport, you need to have a +FastCGI server such as https://en.wikipedia.org/wiki/PHP#PHPFPM[PHP-FPM] +(see also http://php.net/manual/en/install.fpm.php). -The FastCGI transport is primarily used by Jetty's link:#fastcgi[FastCGI support] to serve PHP pages (WordPress for example). +The FastCGI transport is primarily used by Jetty's link:#fastcgi[FastCGI support] +to serve PHP pages (WordPress for example). + +[[client-http-transport-dynamic]] +==== Dynamic Transport + +The static transports work well if you know in advance the protocol you want +to speak with the server, or if the server only supports one protocol (such +as FastCGI). + +With the advent of HTTP/2, however, servers are now able to support multiple +protocols, at least both HTTP/1.1 and HTTP/2. + +The HTTP/2 protocol is typically negotiated between client and server. +This negotiation can happen via ALPN, a TLS extension that allows the client +to tell the server the list of protocol that the client supports, so that the +server can pick one of the client supported protocols that also the server +supports; or via HTTP/1.1 upgrade by means of the `Upgrade` header. + +Applications can configure the dynamic transport with one or more +_application_ protocols such as HTTP/1.1 or HTTP/2. The implementation will +take care of using TLS for HTTPS URIs, using ALPN, negotiating protocols, +upgrading from one protocol to another, etc. + +By default, the dynamic transport only speaks HTTP/1.1: + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=dynamicDefault] +---- + +The dynamic transport can be configured with just one protocol, making it +equivalent to the corresponding static transport: + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=dynamicOneProtocol] +---- + +The dynamic transport, however, has been implemented to support multiple +transports, in particular both HTTP/1.1 and HTTP/2: + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=dynamicH1H2] +---- + +IMPORTANT: The order in which the protocols are specified to +`HttpClientTransportDynamic` indicates what is the client preference. +If the protocol is negotiated via ALPN, it is the server that decides what is +the protocol to use for the communication, regardless of the client preference. +If the protocol is not negotiated, the client preference is honored. + +Provided that the server supports both HTTP/1.1 and HTTP/2 clear-text, client +applications can explicitly hint the version they want to use: + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=dynamicClearText] +---- + +In case of TLS encrypted communication using the HTTPS scheme, things are a +little more complicated. + +If the client application explicitly specifies the HTTP version, then ALPN +is not used on the client. By specifying the HTTP version explicitly, the +client application has prior-knowledge of what HTTP version the server +supports, and therefore ALPN is not needed. +If the server does not support the HTTP version chosen by the client, then +the communication will fail. + +If the client application does not explicitly specify the HTTP version, +then ALPN will be used on the client. +If the server also supports ALPN, then the protocol will be negotiated via +ALPN and the server will choose the protocol to use. +If the server does not support ALPN, the client will try to use the first +protocol configured in `HttpClientTransportDynamic`, and the communication +may succeed or fail depending on whether the server supports the protocol +chosen by the client. diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/embedding/examples/embedded-minimal-servlet.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/server/embedding/examples/embedded-minimal-servlet.adoc index 6d440c591fd..068f62faa1d 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/server/embedding/examples/embedded-minimal-servlet.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/server/embedding/examples/embedded-minimal-servlet.adoc @@ -22,7 +22,7 @@ This example shows the bare minimum required for deploying a servlet into Jetty. Note that this is strictly a servlet, not a servlet in the context of a web application, that example comes later. This is purely just a servlet deployed and mounted on a context and able to process requests. -This example is excellent for situations where you have a simple servlet that you need to unit test, just mount it on a context and issue requests using your favorite http client library (like our Jetty client found in xref:http-client[]). +This example is excellent for situations where you have a simple servlet that you need to unit test, just mount it on a context and issue requests using your favorite http client library (like our Jetty client found in xref:client-http[]). [source, java, subs="{sub-order}"] ---- diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/embedding/examples/embedded-one-webapp.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/server/embedding/examples/embedded-one-webapp.adoc index 048d5ddd4d3..61c06a4d145 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/server/embedding/examples/embedded-one-webapp.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/server/embedding/examples/embedded-one-webapp.adoc @@ -21,7 +21,7 @@ This example shows how to deploy a simple webapp with an embedded instance of Jetty. This is useful when you want to manage the lifecycle of a server programmatically, either within a production application or as a simple way to deploying and debugging a full scale application deployment. -In many ways it is easier then traditional deployment since you control the classpath yourself, making this easy to wire up in a test case in Maven and issue requests using your favorite http client library (like our Jetty client found in xref:http-client[]). +In many ways it is easier then traditional deployment since you control the classpath yourself, making this easy to wire up in a test case in Maven and issue requests using your favorite http client library (like our Jetty client found in xref:client-http[]). [source, java, subs="{sub-order}"] ---- diff --git a/jetty-documentation/src/main/java/embedded/client/http/HTTPClientDocs.java b/jetty-documentation/src/main/java/embedded/client/http/HTTPClientDocs.java index decd799c0d4..f72655f916f 100644 --- a/jetty-documentation/src/main/java/embedded/client/http/HTTPClientDocs.java +++ b/jetty-documentation/src/main/java/embedded/client/http/HTTPClientDocs.java @@ -41,6 +41,8 @@ import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.api.Result; import org.eclipse.jetty.client.dynamic.HttpClientTransportDynamic; +import org.eclipse.jetty.client.http.HttpClientConnectionFactory; +import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP; import org.eclipse.jetty.client.util.AsyncRequestContent; import org.eclipse.jetty.client.util.BasicAuthentication; import org.eclipse.jetty.client.util.BufferingResponseListener; @@ -52,9 +54,16 @@ import org.eclipse.jetty.client.util.InputStreamResponseListener; import org.eclipse.jetty.client.util.OutputStreamRequestContent; import org.eclipse.jetty.client.util.PathRequestContent; import org.eclipse.jetty.client.util.StringRequestContent; +import org.eclipse.jetty.fcgi.client.http.HttpClientTransportOverFCGI; +import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.http.HttpVersion; +import org.eclipse.jetty.http2.client.HTTP2Client; +import org.eclipse.jetty.http2.client.http.ClientConnectionFactoryOverHTTP2; +import org.eclipse.jetty.http2.client.http.HttpClientTransportOverHTTP2; import org.eclipse.jetty.io.ByteBufferPool; +import org.eclipse.jetty.io.ClientConnectionFactory; import org.eclipse.jetty.io.ClientConnector; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.HttpCookieStore; @@ -666,4 +675,129 @@ public class HTTPClientDocs ContentResponse response = httpClient.newRequest(serverURI).send(); // end::proxyAuthentication[] } + + public void defaultTransport() throws Exception + { + // tag::defaultTransport[] + // No transport specified, using default. + HttpClient httpClient = new HttpClient(); + httpClient.start(); + // end::defaultTransport[] + } + + public void http11Transport() throws Exception + { + // tag::http11Transport[] + // Configure HTTP/1.1 transport. + HttpClientTransportOverHTTP transport = new HttpClientTransportOverHTTP(); + transport.setHeaderCacheSize(16384); + + HttpClient client = new HttpClient(transport); + client.start(); + // end::http11Transport[] + } + + public void http2Transport() throws Exception + { + // tag::http2Transport[] + // The HTTP2Client powers the HTTP/2 transport. + HTTP2Client h2Client = new HTTP2Client(); + h2Client.setInitialSessionRecvWindow(64 * 1024 * 1024); + + // Create and configure the HTTP/2 transport. + HttpClientTransportOverHTTP2 transport = new HttpClientTransportOverHTTP2(h2Client); + transport.setUseALPN(true); + + HttpClient client = new HttpClient(transport); + client.start(); + // end::http2Transport[] + } + + public void fcgiTransport() throws Exception + { + // tag::fcgiTransport[] + String scriptRoot = "/var/www/wordpress"; + HttpClientTransportOverFCGI transport = new HttpClientTransportOverFCGI(scriptRoot); + + HttpClient client = new HttpClient(transport); + client.start(); + // end::fcgiTransport[] + } + + public void dynamicDefault() throws Exception + { + // tag::dynamicDefault[] + // Dynamic transport speaks HTTP/1.1 by default. + HttpClientTransportDynamic transport = new HttpClientTransportDynamic(); + + HttpClient client = new HttpClient(transport); + client.start(); + // end::dynamicDefault[] + } + + public void dynamicOneProtocol() throws Exception + { + // tag::dynamicOneProtocol[] + ClientConnector connector = new ClientConnector(); + + // Equivalent to HttpClientTransportOverHTTP. + HttpClientTransportDynamic http11Transport = new HttpClientTransportDynamic(connector, HttpClientConnectionFactory.HTTP11); + + // Equivalent to HttpClientTransportOverHTTP2. + HTTP2Client http2Client = new HTTP2Client(connector); + HttpClientTransportDynamic http2Transport = new HttpClientTransportDynamic(connector, new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client)); + // end::dynamicOneProtocol[] + } + + public void dynamicH1H2() throws Exception + { + // tag::dynamicH1H2[] + ClientConnector connector = new ClientConnector(); + + ClientConnectionFactory.Info http1 = HttpClientConnectionFactory.HTTP11; + + HTTP2Client http2Client = new HTTP2Client(connector); + ClientConnectionFactoryOverHTTP2.HTTP2 http2 = new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client); + + HttpClientTransportDynamic transport = new HttpClientTransportDynamic(connector, http1, http2); + + HttpClient client = new HttpClient(transport); + client.start(); + // end::dynamicH1H2[] + } + + public void dynamicClearText() throws Exception + { + // tag::dynamicClearText[] + ClientConnector connector = new ClientConnector(); + ClientConnectionFactory.Info http1 = HttpClientConnectionFactory.HTTP11; + HTTP2Client http2Client = new HTTP2Client(connector); + ClientConnectionFactoryOverHTTP2.HTTP2 http2 = new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client); + HttpClientTransportDynamic transport = new HttpClientTransportDynamic(connector, http1, http2); + HttpClient client = new HttpClient(transport); + client.start(); + + // The server supports both HTTP/1.1 and HTTP/2 clear-text on port 8080. + + // Make a clear-text request without explicit version. + // The first protocol specified to HttpClientTransportDynamic + // is picked, in this example will be HTTP/1.1. + ContentResponse http1Response = client.newRequest("host", 8080).send(); + + // Make a clear-text request with explicit version. + // Clear-text HTTP/2 is used for this request. + ContentResponse http2Response = client.newRequest("host", 8080) + // Specify the version explicitly. + .version(HttpVersion.HTTP_2) + .send(); + + // Make a clear-text upgrade request from HTTP/1.1 to HTTP/2. + // The request will start as HTTP/1.1, but the response will be HTTP/2. + ContentResponse upgradedResponse = client.newRequest("host", 8080) + .header(HttpHeader.UPGRADE, "h2c") + .header(HttpHeader.HTTP2_SETTINGS, "") + .header(HttpHeader.CONNECTION, "Upgrade, HTTP2-Settings") + .send(); + // end::dynamicClearText[] + } } diff --git a/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/ClientConnectionFactoryOverHTTP2.java b/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/ClientConnectionFactoryOverHTTP2.java index 1f17ae4bfcd..2493fbf6678 100644 --- a/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/ClientConnectionFactoryOverHTTP2.java +++ b/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/ClientConnectionFactoryOverHTTP2.java @@ -26,6 +26,8 @@ import java.util.Map; import org.eclipse.jetty.client.HttpClientTransport; import org.eclipse.jetty.client.HttpDestination; import org.eclipse.jetty.client.api.Connection; +import org.eclipse.jetty.client.dynamic.HttpClientTransportDynamic; +import org.eclipse.jetty.client.http.HttpClientConnectionFactory; import org.eclipse.jetty.http2.client.HTTP2Client; import org.eclipse.jetty.http2.client.HTTP2ClientConnectionFactory; import org.eclipse.jetty.io.ClientConnectionFactory; @@ -56,19 +58,25 @@ public class ClientConnectionFactoryOverHTTP2 extends ContainerLifeCycle impleme return factory.newConnection(endPoint, context); } - public static class H2 extends Info + /** + *

    Representation of the {@code HTTP/2} application protocol used by {@link HttpClientTransportDynamic}.

    + * + * @see HttpClientConnectionFactory#HTTP11 + */ + public static class HTTP2 extends Info { - public H2(HTTP2Client client) - { - super(List.of("h2"), new ClientConnectionFactoryOverHTTP2(client)); - } - } + private static final List protocols = List.of("h2", "h2c"); + private static final List h2c = List.of("h2c"); - public static class H2C extends Info - { - public H2C(HTTP2Client client) + public HTTP2(HTTP2Client client) { - super(List.of("h2c"), new ClientConnectionFactoryOverHTTP2(client)); + super(new ClientConnectionFactoryOverHTTP2(client)); + } + + @Override + public List getProtocols(boolean secure) + { + return secure ? protocols : h2c; } @Override @@ -119,5 +127,11 @@ public class ClientConnectionFactoryOverHTTP2 extends ContainerLifeCycle impleme throw new UncheckedIOException(x); } } + + @Override + public String toString() + { + return String.format("%s@%x%s", getClass().getSimpleName(), hashCode(), protocols); + } } } diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnectionFactory.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnectionFactory.java index 4e555e7878e..d66a5901b38 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnectionFactory.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnectionFactory.java @@ -66,26 +66,21 @@ public interface ClientConnectionFactory } /** - *

    A holder for a list of protocol strings identifying a network protocol + *

    A holder for a list of protocol strings identifying an application protocol * (for example {@code ["h2", "h2-17", "h2-16"]}) and a {@link ClientConnectionFactory} * that creates connections that speak that network protocol.

    */ - public static class Info extends ContainerLifeCycle + public abstract static class Info extends ContainerLifeCycle { - private final List protocols; private final ClientConnectionFactory factory; - public Info(List protocols, ClientConnectionFactory factory) + public Info(ClientConnectionFactory factory) { - this.protocols = protocols; this.factory = factory; addBean(factory); } - public List getProtocols() - { - return protocols; - } + public abstract List getProtocols(boolean secure); public ClientConnectionFactory getClientConnectionFactory() { @@ -98,20 +93,14 @@ public interface ClientConnectionFactory * @param candidates the candidates to match against * @return whether one of the protocols of this class is present in the candidates */ - public boolean matches(List candidates) + public boolean matches(List candidates, boolean secure) { - return protocols.stream().anyMatch(p -> candidates.stream().anyMatch(c -> c.equalsIgnoreCase(p))); + return getProtocols(secure).stream().anyMatch(p -> candidates.stream().anyMatch(c -> c.equalsIgnoreCase(p))); } public void upgrade(EndPoint endPoint, Map context) { throw new UnsupportedOperationException(this + " does not support upgrade to another protocol"); } - - @Override - public String toString() - { - return String.format("%s@%x%s", getClass().getSimpleName(), hashCode(), protocols); - } } } diff --git a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketOverHTTP2Test.java b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketOverHTTP2Test.java index 1cd8a05caa4..cd590d1507f 100644 --- a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketOverHTTP2Test.java +++ b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketOverHTTP2Test.java @@ -152,7 +152,7 @@ public class WebSocketOverHTTP2Test @Test public void testWebSocketOverDynamicHTTP2() throws Exception { - testWebSocketOverDynamicTransport(clientConnector -> new ClientConnectionFactoryOverHTTP2.H2C(new HTTP2Client(clientConnector))); + testWebSocketOverDynamicTransport(clientConnector -> new ClientConnectionFactoryOverHTTP2.HTTP2(new HTTP2Client(clientConnector))); } private void testWebSocketOverDynamicTransport(Function protocolFn) throws Exception @@ -184,7 +184,7 @@ public class WebSocketOverHTTP2Test AbstractHTTP2ServerConnectionFactory h2c = connector.getBean(AbstractHTTP2ServerConnectionFactory.class); h2c.setConnectProtocolEnabled(false); - startClient(clientConnector -> new ClientConnectionFactoryOverHTTP2.H2C(new HTTP2Client(clientConnector))); + startClient(clientConnector -> new ClientConnectionFactoryOverHTTP2.HTTP2(new HTTP2Client(clientConnector))); EventSocket wsEndPoint = new EventSocket(); URI uri = URI.create("ws://localhost:" + connector.getLocalPort() + "/ws/echo"); @@ -220,7 +220,7 @@ public class WebSocketOverHTTP2Test } }); - startClient(clientConnector -> new ClientConnectionFactoryOverHTTP2.H2(new HTTP2Client(clientConnector))); + startClient(clientConnector -> new ClientConnectionFactoryOverHTTP2.HTTP2(new HTTP2Client(clientConnector))); // Connect and send immediately a message, so the message // arrives to the server while the server is still upgrading. @@ -242,7 +242,7 @@ public class WebSocketOverHTTP2Test public void testWebSocketConnectPortDoesNotExist() throws Exception { startServer(); - startClient(clientConnector -> new ClientConnectionFactoryOverHTTP2.H2(new HTTP2Client(clientConnector))); + startClient(clientConnector -> new ClientConnectionFactoryOverHTTP2.HTTP2(new HTTP2Client(clientConnector))); EventSocket wsEndPoint = new EventSocket(); URI uri = URI.create("ws://localhost:" + (connector.getLocalPort() + 1) + "/ws/echo"); @@ -259,7 +259,7 @@ public class WebSocketOverHTTP2Test public void testWebSocketNotFound() throws Exception { startServer(); - startClient(clientConnector -> new ClientConnectionFactoryOverHTTP2.H2(new HTTP2Client(clientConnector))); + startClient(clientConnector -> new ClientConnectionFactoryOverHTTP2.HTTP2(new HTTP2Client(clientConnector))); EventSocket wsEndPoint = new EventSocket(); URI uri = URI.create("ws://localhost:" + connector.getLocalPort() + "/nothing"); @@ -276,7 +276,7 @@ public class WebSocketOverHTTP2Test public void testNotNegotiated() throws Exception { startServer(); - startClient(clientConnector -> new ClientConnectionFactoryOverHTTP2.H2(new HTTP2Client(clientConnector))); + startClient(clientConnector -> new ClientConnectionFactoryOverHTTP2.HTTP2(new HTTP2Client(clientConnector))); EventSocket wsEndPoint = new EventSocket(); URI uri = URI.create("ws://localhost:" + connector.getLocalPort() + "/ws/null"); @@ -293,7 +293,7 @@ public class WebSocketOverHTTP2Test public void testThrowFromCreator() throws Exception { startServer(); - startClient(clientConnector -> new ClientConnectionFactoryOverHTTP2.H2(new HTTP2Client(clientConnector))); + startClient(clientConnector -> new ClientConnectionFactoryOverHTTP2.HTTP2(new HTTP2Client(clientConnector))); CountDownLatch latch = new CountDownLatch(1); connector.addBean(new HttpChannel.Listener() @@ -327,7 +327,7 @@ public class WebSocketOverHTTP2Test public void testServerConnectionClose() throws Exception { startServer(); - startClient(clientConnector -> new ClientConnectionFactoryOverHTTP2.H2(new HTTP2Client(clientConnector))); + startClient(clientConnector -> new ClientConnectionFactoryOverHTTP2.HTTP2(new HTTP2Client(clientConnector))); EventSocket wsEndPoint = new EventSocket(); URI uri = URI.create("ws://localhost:" + connector.getLocalPort() + "/ws/connectionClose"); diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientTransportDynamicTest.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientTransportDynamicTest.java index 8649870c55a..4ce25b5eb2e 100644 --- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientTransportDynamicTest.java +++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientTransportDynamicTest.java @@ -42,7 +42,7 @@ import org.eclipse.jetty.client.api.Result; import org.eclipse.jetty.client.dynamic.HttpClientTransportDynamic; import org.eclipse.jetty.client.http.HttpClientConnectionFactory; import org.eclipse.jetty.client.util.BufferingResponseListener; -import org.eclipse.jetty.client.util.BytesContentProvider; +import org.eclipse.jetty.client.util.BytesRequestContent; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpScheme; @@ -244,7 +244,7 @@ public class HttpClientTransportDynamicTest startServer(this::h1H2C, new EmptyServerHandler()); ClientConnector clientConnector = new ClientConnector(); HTTP2Client http2Client = new HTTP2Client(clientConnector); - ClientConnectionFactory.Info h2c = new ClientConnectionFactoryOverHTTP2.H2C(http2Client); + ClientConnectionFactory.Info h2c = new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client); startClient(clientConnector, h2c); ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) // .version(HttpVersion.HTTP_2) @@ -273,14 +273,15 @@ public class HttpClientTransportDynamicTest clientConnector.setSslContextFactory(newClientSslContextFactory()); HttpClientConnectionFactory.Info h1 = HttpClientConnectionFactory.HTTP11; HTTP2Client http2Client = new HTTP2Client(clientConnector); - ClientConnectionFactory.Info h2c = new ClientConnectionFactoryOverHTTP2.H2C(http2Client); - HttpClientTransportDynamic transport = new HttpClientTransportDynamic(clientConnector, h1, h2c) + ClientConnectionFactory.Info http2 = new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client); + HttpClientTransportDynamic transport = new HttpClientTransportDynamic(clientConnector, h1, http2) { @Override public Origin newOrigin(HttpRequest request) { // Use prior-knowledge, i.e. negotiate==false. - List protocols = HttpVersion.HTTP_2 == request.getVersion() ? h2c.getProtocols() : h1.getProtocols(); + boolean secure = HttpClient.isSchemeSecure(request.getScheme()); + List protocols = HttpVersion.HTTP_2 == request.getVersion() ? http2.getProtocols(secure) : h1.getProtocols(secure); return new Origin(request.getScheme(), request.getHost(), request.getPort(), request.getTag(), new Origin.Protocol(protocols, false)); } }; @@ -320,8 +321,8 @@ public class HttpClientTransportDynamicTest startServer(this::sslAlpnH1H2, new EmptyServerHandler()); ClientConnector clientConnector = new ClientConnector(); HTTP2Client http2Client = new HTTP2Client(clientConnector); - ClientConnectionFactory.Info h2 = new ClientConnectionFactoryOverHTTP2.H2(http2Client); - startClient(clientConnector, HttpClientConnectionFactory.HTTP11, h2); + ClientConnectionFactory.Info http2 = new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client); + startClient(clientConnector, HttpClientConnectionFactory.HTTP11, http2); // Make a request, should be HTTP/1.1 because of the order of protocols on server. ContentResponse h1cResponse = client.newRequest("localhost", connector.getLocalPort()) @@ -355,8 +356,8 @@ public class HttpClientTransportDynamicTest startServer(this::sslAlpnH1, new EmptyServerHandler()); ClientConnector clientConnector = new ClientConnector(); HTTP2Client http2Client = new HTTP2Client(clientConnector); - ClientConnectionFactory.Info h2 = new ClientConnectionFactoryOverHTTP2.H2(http2Client); - startClient(clientConnector, h2, HttpClientConnectionFactory.HTTP11); + ClientConnectionFactory.Info http2 = new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client); + startClient(clientConnector, http2, HttpClientConnectionFactory.HTTP11); // The client prefers h2 over h1, and use of TLS and ALPN will allow the fallback to h1. ContentResponse h1cResponse = client.newRequest("localhost", connector.getLocalPort()) @@ -372,8 +373,8 @@ public class HttpClientTransportDynamicTest startServer(this::h1, new EmptyServerHandler()); ClientConnector clientConnector = new ClientConnector(); HTTP2Client http2Client = new HTTP2Client(clientConnector); - ClientConnectionFactory.Info h2c = new ClientConnectionFactoryOverHTTP2.H2C(http2Client); - startClient(clientConnector, HttpClientConnectionFactory.HTTP11, h2c); + ClientConnectionFactory.Info http2 = new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client); + startClient(clientConnector, HttpClientConnectionFactory.HTTP11, http2); // The client forces HTTP/2, but the server cannot speak it, so the request fails. // There is no fallback to HTTP/1 because the protocol version is set explicitly. @@ -450,9 +451,8 @@ public class HttpClientTransportDynamicTest server.start(); ClientConnector clientConnector = new ClientConnector(); HTTP2Client http2Client = new HTTP2Client(clientConnector); - ClientConnectionFactory.Info h2c = new ClientConnectionFactoryOverHTTP2.H2C(http2Client); - ClientConnectionFactory.Info h2 = new ClientConnectionFactoryOverHTTP2.H2(http2Client); - startClient(clientConnector, h2, HttpClientConnectionFactory.HTTP11, h2c); + ClientConnectionFactory.Info http2 = new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client); + startClient(clientConnector, HttpClientConnectionFactory.HTTP11, http2); // Make a clear-text request using HTTP/1.1. ContentResponse h1cResponse = client.newRequest("localhost", clearConnector.getLocalPort()) @@ -510,8 +510,8 @@ public class HttpClientTransportDynamicTest }); ClientConnector clientConnector = new ClientConnector(); HTTP2Client http2Client = new HTTP2Client(clientConnector); - ClientConnectionFactory.Info h2c = new ClientConnectionFactoryOverHTTP2.H2C(http2Client); - startClient(clientConnector, HttpClientConnectionFactory.HTTP11, h2c); + ClientConnectionFactory.Info http2 = new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client); + startClient(clientConnector, HttpClientConnectionFactory.HTTP11, http2); // Make an upgrade request from HTTP/1.1 to H2C. ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) @@ -584,8 +584,8 @@ public class HttpClientTransportDynamicTest }); ClientConnector clientConnector = new ClientConnector(); HTTP2Client http2Client = new HTTP2Client(clientConnector); - ClientConnectionFactory.Info h2c = new ClientConnectionFactoryOverHTTP2.H2C(http2Client); - startClient(clientConnector, HttpClientConnectionFactory.HTTP11, h2c); + ClientConnectionFactory.Info http2 = new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client); + startClient(clientConnector, HttpClientConnectionFactory.HTTP11, http2); int proxyPort = connector.getLocalPort(); // The proxy speaks both http/1.1 and h2c. @@ -622,8 +622,8 @@ public class HttpClientTransportDynamicTest }); ClientConnector clientConnector = new ClientConnector(); HTTP2Client http2Client = new HTTP2Client(clientConnector); - ClientConnectionFactory.Info h2c = new ClientConnectionFactoryOverHTTP2.H2C(http2Client); - startClient(clientConnector, HttpClientConnectionFactory.HTTP11, h2c); + ClientConnectionFactory.Info http2 = new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client); + startClient(clientConnector, HttpClientConnectionFactory.HTTP11, http2); // Make an upgrade request from HTTP/1.1 to H2C. ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) @@ -653,8 +653,8 @@ public class HttpClientTransportDynamicTest }); ClientConnector clientConnector = new ClientConnector(); HTTP2Client http2Client = new HTTP2Client(clientConnector); - ClientConnectionFactory.Info h2c = new ClientConnectionFactoryOverHTTP2.H2C(http2Client); - startClient(clientConnector, HttpClientConnectionFactory.HTTP11, h2c); + ClientConnectionFactory.Info http2 = new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client); + startClient(clientConnector, HttpClientConnectionFactory.HTTP11, http2); // Make a POST upgrade request from HTTP/1.1 to H2C. // We don't support upgrades with request content because @@ -668,7 +668,7 @@ public class HttpClientTransportDynamicTest .header(HttpHeader.UPGRADE, "h2c") .header(HttpHeader.HTTP2_SETTINGS, "") .header(HttpHeader.CONNECTION, "Upgrade, HTTP2-Settings") - .content(new BytesContentProvider(bytes)) + .body(new BytesRequestContent(bytes)) .timeout(5, TimeUnit.SECONDS) .send(new BufferingResponseListener(bytes.length) { @@ -693,8 +693,8 @@ public class HttpClientTransportDynamicTest startServer(this::h1H2C, new EmptyServerHandler()); ClientConnector clientConnector = new ClientConnector(); HTTP2Client http2Client = new HTTP2Client(clientConnector); - ClientConnectionFactory.Info h2c = new ClientConnectionFactoryOverHTTP2.H2C(http2Client); - startClient(clientConnector, HttpClientConnectionFactory.HTTP11, h2c); + ClientConnectionFactory.Info http2 = new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client); + startClient(clientConnector, HttpClientConnectionFactory.HTTP11, http2); // The upgrade request is missing the required HTTP2-Settings header. ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) @@ -719,8 +719,8 @@ public class HttpClientTransportDynamicTest }); ClientConnector clientConnector = new ClientConnector(); HTTP2Client http2Client = new HTTP2Client(clientConnector); - ClientConnectionFactory.Info h2c = new ClientConnectionFactoryOverHTTP2.H2C(http2Client); - startClient(clientConnector, HttpClientConnectionFactory.HTTP11, h2c); + ClientConnectionFactory.Info http2 = new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client); + startClient(clientConnector, HttpClientConnectionFactory.HTTP11, http2); // Make an upgrade request from HTTP/1.1 to H2C. CountDownLatch latch = new CountDownLatch(1); @@ -736,4 +736,23 @@ public class HttpClientTransportDynamicTest assertTrue(latch.await(5, TimeUnit.SECONDS)); } + + @Test + public void testClientWithALPNServerWithoutALPN() throws Exception + { + startServer(this::sslH1H2C, new EmptyServerHandler()); + ClientConnector clientConnector = new ClientConnector(); + HTTP2Client http2Client = new HTTP2Client(clientConnector); + ClientConnectionFactory.Info http2 = new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client); + startClient(clientConnector, HttpClientConnectionFactory.HTTP11, http2); + + // Make a request without explicit version, so ALPN is used on the client. + // Since the server does not support ALPN, the first protocol is used. + ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) + .scheme(HttpScheme.HTTPS.asString()) + .timeout(5, TimeUnit.SECONDS) + .send(); + + assertEquals(HttpStatus.OK_200, response.getStatus()); + } } diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/ProxyWithDynamicTransportTest.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/ProxyWithDynamicTransportTest.java index 7dbd499b063..1988361ca0c 100644 --- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/ProxyWithDynamicTransportTest.java +++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/ProxyWithDynamicTransportTest.java @@ -180,9 +180,8 @@ public class ProxyWithDynamicTransportTest { ClientConnectionFactory.Info h1 = HttpClientConnectionFactory.HTTP11; HTTP2Client http2Client = new HTTP2Client(clientConnector); - ClientConnectionFactory.Info h2c = new ClientConnectionFactoryOverHTTP2.H2C(http2Client); - ClientConnectionFactory.Info h2 = new ClientConnectionFactoryOverHTTP2.H2(http2Client); - return new HttpClient(new HttpClientTransportDynamic(clientConnector, h1, h2c, h2)); + ClientConnectionFactory.Info http2 = new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client); + return new HttpClient(new HttpClientTransportDynamic(clientConnector, h1, http2)); } }); context.addServlet(holder, "/*"); @@ -200,9 +199,8 @@ public class ProxyWithDynamicTransportTest clientConnector.setSslContextFactory(new SslContextFactory.Client(true)); http2Client = new HTTP2Client(clientConnector); ClientConnectionFactory.Info h1 = HttpClientConnectionFactory.HTTP11; - ClientConnectionFactory.Info h2c = new ClientConnectionFactoryOverHTTP2.H2C(http2Client); - ClientConnectionFactory.Info h2 = new ClientConnectionFactoryOverHTTP2.H2(http2Client); - client = new HttpClient(new HttpClientTransportDynamic(clientConnector, h1, h2c, h2)); + ClientConnectionFactory.Info http2 = new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client); + client = new HttpClient(new HttpClientTransportDynamic(clientConnector, h1, http2)); client.start(); } From 8f4de31d3779567e87ebb18c60eab7a1a1fbe721 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Fri, 3 Apr 2020 16:54:24 -0500 Subject: [PATCH 036/101] Issue #4745 - update documentation for centralized-webapp-logging Signed-off-by: Joakim Erdfelt --- .../example-logback-centralized-logging.adoc | 132 +++++++++++------- 1 file changed, 82 insertions(+), 50 deletions(-) diff --git a/jetty-documentation/src/main/asciidoc/administration/logging/example-logback-centralized-logging.adoc b/jetty-documentation/src/main/asciidoc/administration/logging/example-logback-centralized-logging.adoc index 3becdb3e312..ef5e328d00b 100644 --- a/jetty-documentation/src/main/asciidoc/administration/logging/example-logback-centralized-logging.adoc +++ b/jetty-documentation/src/main/asciidoc/administration/logging/example-logback-centralized-logging.adoc @@ -34,74 +34,105 @@ This configuration is essentially the multiple logger configuration with added c The technique used by this configuration is to provide an link:{JDURL}org/eclipse/jetty/deploy/AppLifeCycle.Binding.html[AppLifeCycle.Binding] against the link:{JDURL}/org/eclipse/jetty/deploy/AppLifeCycle.html[`"deploying"`node] that modifies the link:{JDURL}/org/eclipse/jetty/webapp/WebAppContext.html#getSystemClasspathPattern()[WebAppContext.getSystemClasspathPattern().add(String)] for the common logging classes. -See https://github.com/jetty-project/jetty-webapp-logging/blob/master/src/main/java/org/eclipse/jetty/webapp/logging/CentralizedWebAppLoggingBinding.java[org.eclipse.jetty.logging.CentralizedWebAppLoggingBinding] for actual implementation. +See https://github.com/jetty-project/jetty-webapp-logging/blob/master/jetty-webapp-logging/src/main/java/org/eclipse/jetty/webapp/logging/CentralizedWebAppLoggingBinding.java[org.eclipse.jetty.logging.CentralizedWebAppLoggingBinding] for actual implementation. A convenient replacement `logging` module has been created to bootstrap your `${jetty.base}` directory for capturing all Jetty server logging from multiple logging frameworks into a single logging output file managed by Logback. -[source, screen, subs="{sub-order}"] +[source,screen,subs="{sub-order}"] .... -[mybase]$ mkdir modules -[mybase]$ cd modules - -[modules]$ curl -O https://raw.githubusercontent.com/jetty-project/logging-modules/master/capture-all/logging.mod +[mybase]$ curl -O https://repo1.maven.org/maven2/org/eclipse/jetty/jetty-webapp-logging/9.4.27/jetty-webapp-logging-9.4.27-config.jar % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed -100 1416 100 1416 0 0 4241 0 --:--:-- --:--:-- --:--:-- 4252 +100 3402 100 3402 0 0 15823 0 --:--:-- --:--:-- --:--:-- 15750 -[master]$ curl -O https://raw.githubusercontent.com/jetty-project/logging-modules/master/centralized/webapp-logging.mod - % Total % Received % Xferd Average Speed Time Time Time Current - Dload Upload Total Spent Left Speed -100 660 100 660 0 0 2032 0 --:--:-- --:--:-- --:--:-- 2037 -[modules]$ cd .. +[mybase]$ jar -xf jetty-webapp-logging-9.4.27-config.jar -[mybase]$ java -jar /opt/jetty-dist/start.jar --add-to-start=logging,webapp-logging -INFO: logging initialised in ${jetty.base}/start.ini (appended) -MKDIR: ${jetty.base}/logs -DOWNLOAD: https://repo1.maven.org/maven2/org/slf4j/slf4j-api/1.6.6/slf4j-api-1.6.6.jar to lib/logging/slf4j-api-1.6.6.jar -DOWNLOAD: https://repo1.maven.org/maven2/org/slf4j/log4j-over-slf4j/1.6.6/log4j-over-slf4j-1.6.6.jar to lib/logging/log4j-over-slf4j-1.6.6.jar -DOWNLOAD: https://repo1.maven.org/maven2/org/slf4j/jul-to-slf4j/1.6.6/jul-to-slf4j-1.6.6.jar to lib/logging/jul-to-slf4j-1.6.6.jar -DOWNLOAD: https://repo1.maven.org/maven2/org/slf4j/jcl-over-slf4j/1.6.6/jcl-over-slf4j-1.6.6.jar to lib/logging/jcl-over-slf4j-1.6.6.jar -DOWNLOAD: https://repo1.maven.org/maven2/ch/qos/logback/logback-core/1.0.7/logback-core-1.0.7.jar to lib/logging/logback-core-1.0.7.jar -DOWNLOAD: https://repo1.maven.org/maven2/ch/qos/logback/logback-classic/1.0.7/logback-classic-1.0.7.jar to lib/logging/logback-classic-1.0.7.jar -DOWNLOAD: https://raw.githubusercontent.com/jetty-project/logging-modules/master/capture-all/logback.xml to resources/logback.xml -DOWNLOAD: https://raw.githubusercontent.com/jetty-project/logging-modules/master/capture-all/jetty-logging.properties to resources/jetty-logging.properties -DOWNLOAD: https://raw.githubusercontent.com/jetty-project/logging-modules/master/capture-all/jetty-logging.xml to etc/jetty-logging.xml -INFO: resources initialised transitively -INFO: resources enabled in ${jetty.base}/start.ini -INFO: webapp-logging initialised in ${jetty.base}/start.ini (appended) -DOWNLOAD: https://repo1.maven.org/maven2/org/eclipse/jetty/jetty-webapp-logging/9.0.0/jetty-webapp-logging-9.0.0.jar to lib/webapp-logging/jetty-webapp-logging-9.0.0.jar -DOWNLOAD: https://raw.githubusercontent.com/jetty-project/jetty-webapp-logging/master/src/main/config/etc/jetty-webapp-logging.xml to etc/jetty-webapp-logging.xml -DOWNLOAD: https://raw.githubusercontent.com/jetty-project/jetty-webapp-logging/master/src/main/config/etc/jetty-mdc-handler.xml to etc/jetty-mdc-handler.xml -INFO: deploy initialised transitively -INFO: deploy enabled in ${jetty.base}/start.ini -INFO: logging initialised transitively -INFO: resources initialised transitively -INFO: resources enabled in ${jetty.base}/start.ini +[mybase]$ java -jar /opt/jetty-hom/start.jar --create-startd --add-to-start=centralized-webapp-logging -[mybase]$ java -jar /opt/jetty-dist/start.jar +ALERT: There are enabled module(s) with licenses. +The following 2 module(s): + + contains software not provided by the Eclipse Foundation! + + contains software not covered by the Eclipse Public License! + + has not been audited for compliance with its license + + Module: logback-impl + + Logback: the reliable, generic, fast and flexible logging framework. + + Copyright (C) 1999-2012, QOS.ch. All rights reserved. + + This program and the accompanying materials are dual-licensed under + + either: + + the terms of the Eclipse Public License v1.0 + + as published by the Eclipse Foundation: + + http://www.eclipse.org/legal/epl-v10.html + + or (per the licensee's choosing) under + + the terms of the GNU Lesser General Public License version 2.1 + + as published by the Free Software Foundation: + + http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html + + Module: slf4j-api + + SLF4J is distributed under the MIT License. + + Copyright (c) 2004-2013 QOS.ch + + All rights reserved. + + Permission is hereby granted, free of charge, to any person obtaining + + a copy of this software and associated documentation files (the + + "Software"), to deal in the Software without restriction, including + + without limitation the rights to use, copy, modify, merge, publish, + + distribute, sublicense, and/or sell copies of the Software, and to + + permit persons to whom the Software is furnished to do so, subject to + + the following conditions: + + The above copyright notice and this permission notice shall be + + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Proceed (y/N)? y +INFO : slf4j-api transitively enabled +INFO : log4j-over-slf4j transitively enabled +INFO : jcl-slf4j transitively enabled +INFO : logback-impl transitively enabled +INFO : jul-slf4j transitively enabled +INFO : slf4j-logback transitively enabled +INFO : centralized-webapp-logging initialized in ${jetty.base}/start.d/centralized-webapp-logging.ini +INFO : logging-logback transitively enabled +INFO : resources transitively enabled +MKDIR : ${jetty.base}/lib/slf4j +DOWNLD: https://repo1.maven.org/maven2/org/slf4j/slf4j-api/1.7.25/slf4j-api-1.7.25.jar to ${jetty.base}/lib/slf4j/slf4j-api-1.7.25.jar +MKDIR : ${jetty.base}/lib/logging +DOWNLD: https://repo1.maven.org/maven2/org/slf4j/log4j-over-slf4j/1.7.25/log4j-over-slf4j-1.7.25.jar to ${jetty.base}/lib/logging/log4j-over-slf4j-1.7.25.jar +DOWNLD: https://repo1.maven.org/maven2/org/slf4j/jcl-over-slf4j/1.7.25/jcl-over-slf4j-1.7.25.jar to ${jetty.base}/lib/slf4j/jcl-over-slf4j-1.7.25.jar +MKDIR : ${jetty.base}/lib/logback +DOWNLD: https://repo1.maven.org/maven2/ch/qos/logback/logback-core/1.2.3/logback-core-1.2.3.jar to ${jetty.base}/lib/logback/logback-core-1.2.3.jar +DOWNLD: https://repo1.maven.org/maven2/org/slf4j/jul-to-slf4j/1.7.25/jul-to-slf4j-1.7.25.jar to ${jetty.base}/lib/slf4j/jul-to-slf4j-1.7.25.jar +COPY : ${jetty.home}/modules/jul-slf4j/etc/java-util-logging.properties to ${jetty.base}/etc/java-util-logging.properties +DOWNLD: https://repo1.maven.org/maven2/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar to ${jetty.base}/lib/logback/logback-classic-1.2.3.jar +MKDIR : ${jetty.base}/logs +DOWNLD: https://repo1.maven.org/maven2/org/eclipse/jetty/jetty-webapp-logging/9.4.27/jetty-webapp-logging-9.4.27.jar to ${jetty.base}/lib/logging/jetty-webapp-logging-9.4.27.jar +INFO : Base directory was modified + +$ .... -The replacement `logging.mod` performs a number of tasks. +This replacement `centralized-webapp-logging.mod` performs a number of tasks. -. `mybase` is a `${jetty.base}` directory. -. The jetty-distribution is unpacked (and untouched) into `/opt/jetty-dist/` and becomes the `${jetty.home}` directory for this demonstration. -. The `curl` command downloads the replacement `logging.mod` and puts it into the `${jetty.base}/modules/` directory for use by mybase only. -. The `start.jar --add-to-start=logging,webapp-logging` command performs a number of steps to make the logging module available to the `${jetty.base}` configuration. -.. Several entries are added to the `${jetty.base}/start.ini` configuration. -* `--module=logging` is added to enable the logging module. -* `--module=webapp-logging` is added to enable the webapp-logging module. -.. Required `${jetty.base}` directories are created: `${jetty.base}/logs` and `${jetty.base}/resources`. -.. Required logging libraries are downloaded (if not present already) to the `${jetty.base}/lib/logging/` directory: +. `mybase` is a `${jetty.base}` directory. +. The jetty-distribution is unpacked (and untouched) into `/opt/jetty-dist/` and becomes the `${jetty.home}` directory for this demonstration. +. The `curl` command downloads the replacement config overlay for the `${jetty.base}/modules/` directory to use. +. The `start.jar --add-to-start=centralized-webapp-logging` command performs a number of steps to make the centralized-webapp-logging module available to the `${jetty.base}` configuration. +.. A new `${jetty.base}/start.d/centralized-webapp-logging.ini` configuration was created. +.. Required `${jetty.base}` directories are created: `${jetty.base}/logs` and `${jetty.base}/resources`. +.. Required logging libraries are downloaded (if not present already) to the `${jetty.base}/lib/logging/` directory: * `slf4j-api.jar` - API jar for Slf4j (used by most of the rest of the jars) * `log4j-over-slf4j.jar` - Slf4j jar that captures all log4j emitted logging events * `jul-to-slf4j.jar` - Slf4j jar that captures all java.util.logging events * `jcl-over-slf4j.jar` - Slf4j jar that captures all commons-logging events * `logback-classic.jar` - the Slf4j adapter jar that routes all of the captured logging events to logback itself. * `logback-core.jar` - the logback implementation jar, that handles all of the filtering and output of the logging events. -.. Required webapp-logging library is downloaded (if not present already) to the `${jetty.base}/lib/webapp-logging/` directory: +.. Required webapp-logging library is downloaded (if not present already) to the `${jetty.base}/lib/webapp-logging/` directory: * `jetty-webapp-logging.jar` - the Jetty side deployment manger app-lifecycle bindings for modifying the `WebAppClassloaders` of deployed webapps. -.. Required configuration files are downloaded (if not present already) to the `${jetty.base}/resources/` directory: `jetty-logging.properties`, and `logback.xml`. -.. Required initialization commands are downloaded (if not present already) to the `${jetty.base}/etc/` directory: `jetty-logging.xml`, `jetty-webapp-logging.xml`, and `jetty-mdc-handler.xml`. At this point the Jetty `mybase` is configured so that the jetty server itself will log using slf4j, and all other logging events from other Jetty Server components (such as database drivers, security layers, jsp, mail, and other 3rd party server components) are routed to logback for filtering and output. @@ -109,4 +140,5 @@ All webapps deployed via the `DeploymentManager` have their `WebAppClassLoader` The server classpath can be verified by using the `start.jar --list-config` command. -In essence, Jetty is now configured to emit its own logging events to slf4j, and various slf4j bridge jars are acting on behalf of log4j, `java.util.logging`, and `commons-logging`, routing all of the logging events to logback (a slf4j adapter) for routing (to console, file, etc...). +In essence, Jetty is now configured to emit its own logging events to slf4j, and various slf4j bridge jars are acting on behalf of `log4j`, `java.util.logging`, and `commons-logging`, routing all of the logging events to `logback` +(a slf4j implementation) for routing (to console, file, etc...). From 6e0b5f387b4dfb0cc5461b4b6581c1fad9602dd7 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Sat, 4 Apr 2020 11:50:48 +0200 Subject: [PATCH 037/101] Improvements to the Jetty client documentation, connection pool section. Signed-off-by: Simone Bordet --- .../http/client-http-authentication.adoc | 8 +- .../client/http/client-http-intro.adoc | 76 +++++++++++++++++-- .../client/http/client-http-proxy.adoc | 22 +++--- .../embedded/client/http/HTTPClientDocs.java | 47 ++++++++++++ 4 files changed, 133 insertions(+), 20 deletions(-) diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-authentication.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-authentication.adoc index fcdd2fc3b01..40a8addb58d 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-authentication.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-authentication.adoc @@ -37,10 +37,10 @@ participant Application participant HttpClient participant Server -Application -> Server: GET /path -Server -> HttpClient: 401 + WWW-Authenticate -HttpClient -> Server: GET + Authentication -Server -> Application: 200 OK +Application -> Server : GET /path +Server -> HttpClient : 401 + WWW-Authenticate +HttpClient -> Server : GET + Authentication +Server -> Application : 200 OK ---- Upon receiving a HTTP 401 response code, `HttpClient` looks at the diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-intro.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-intro.adoc index 6492e3fe1b8..acd412130c8 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-intro.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-intro.adoc @@ -108,8 +108,8 @@ following components: * a set of _destinations_. A _destination_ is the client-side component that represent an _origin_ on -a server, and manages a queue of requests for that origin, and a pool of -connections to that origin. +a server, and manages a queue of requests for that origin, and a +link:#client-http-connection-pool[pool of connections] to that origin. An _origin_ may be simply thought as the tuple `(scheme, host, port)` and it is where the client connects to in order to communicate with the server. @@ -145,9 +145,75 @@ connection pools. Therefore an origin is identified by the tuple `(scheme, host, port, tag, protocol)`. +[[client-http-connection-pool]] +==== HttpClient Connection Pooling + +A destination manages a `org.eclipse.jetty.client.ConnectionPool`, where +connections to a particular origin are pooled for performance reasons: +opening a connection is a costly operation and it's better to reuse them +for multiple requests. + +NOTE: Remember that to select a specific destination you must select a +specific origin, and that an origin is identified by the tuple +`(scheme, host, port, tag, protocol)`, so you can have multiple destinations +for the same `host` and `port`. + +You can access the `ConnectionPool` in this way: + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tags=getConnectionPool] +---- + +Jetty's client library provides the following `ConnectionPool` implementations: + +* `DuplexConnectionPool`, historically the first implementation, only used by +the HTTP/1.1 transport. +* `MultiplexConnectionPool`, the generic implementation valid for any transport +where connections are reused with a MRU (most recently used) algorithm (that is, +the connections most recently returned to the connection pool are the more +likely to be used again). +* `RoundRobinConnectionPool`, similar to `MultiplexConnectionPool` but where +connections are reused with a round-robin algorithm. + +The `ConnectionPool` implementation can be customized for each destination in +by setting a `ConnectionPool.Factory` on the `HttpClientTransport`: + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tags=setConnectionPool] +---- + [[client-http-request-processing]] ==== HttpClient Request Processing +[plantuml] +---- +skinparam backgroundColor transparent +skinparam monochrome true +skinparam shadowing false + +participant Application +participant Request +participant HttpClient +participant Destination +participant ConnectionPool +participant Connection + +Application -> HttpClient : newRequest() +HttpClient -> Request ** +Application -> Request : send() +Request -> HttpClient : send() +HttpClient -> Destination ** : get or create +Destination -> ConnectionPool ** : create +HttpClient -> Destination : send(Request) +Destination -> Destination : enqueue(Request) +Destination -> ConnectionPool : acquire() +ConnectionPool -> Connection ** : create +Destination -> Destination : dequeue(Request) +Destination -> Connection : send(Request) +---- + When a request is sent, an origin is computed from the request; `HttpClient` uses that origin to find (or create if it does not exist) the correspondent destination. @@ -160,8 +226,8 @@ and sends it over the connection. The first request to a destination triggers the opening of the first connection. -A second request with the same origin sent _after_ the first will reuse the -same connection. +A second request with the same origin sent _after_ the first request/response +cycle is completed will reuse the same connection. A second request with the same origin sent _concurrently_ with the first request will cause the opening of a second connection. The configuration parameter `HttpClient.maxConnectionsPerDestination` @@ -171,7 +237,7 @@ the max number of connections that can be opened for a destination. NOTE: If opening connections to a given origin takes a long time, then requests for that origin will queue up in the corresponding destination. -Each connection can handle a limited number of requests. +Each connection can handle a limited number of concurrent requests. For HTTP/1.1, this number is always `1`: there can only be one outstanding request for each connection. For HTTP/2 this number is determined by the server `max_concurrent_stream` diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-proxy.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-proxy.adoc index 88554725612..cea87ff73a1 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-proxy.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-proxy.adoc @@ -71,17 +71,17 @@ participant HttpClient participant Proxy participant Server -Application -> Proxy: GET /path -Proxy -> HttpClient: 407 + Proxy-Authenticate -HttpClient -> Proxy: GET /path + Proxy-Authorization -Proxy -> Server: GET /path -Server -> Proxy: 401 + WWW-Authenticate -Proxy -> HttpClient: 401 + WWW-Authenticate -HttpClient -> Proxy: GET /path + Proxy-Authorization + Authorization -Proxy -> Server: GET /path + Authorization -Server -> Proxy: 200 OK -Proxy -> HttpClient: 200 OK -HttpClient -> Application: 200 OK +Application -> Proxy : GET /path +Proxy -> HttpClient : 407 + Proxy-Authenticate +HttpClient -> Proxy : GET /path + Proxy-Authorization +Proxy -> Server : GET /path +Server -> Proxy : 401 + WWW-Authenticate +Proxy -> HttpClient : 401 + WWW-Authenticate +HttpClient -> Proxy : GET /path + Proxy-Authorization + Authorization +Proxy -> Server : GET /path + Authorization +Server -> Proxy : 200 OK +Proxy -> HttpClient : 200 OK +HttpClient -> Application : 200 OK ---- The application does not receive events related to the responses with code 407 diff --git a/jetty-documentation/src/main/java/embedded/client/http/HTTPClientDocs.java b/jetty-documentation/src/main/java/embedded/client/http/HTTPClientDocs.java index f72655f916f..60161cf3c21 100644 --- a/jetty-documentation/src/main/java/embedded/client/http/HTTPClientDocs.java +++ b/jetty-documentation/src/main/java/embedded/client/http/HTTPClientDocs.java @@ -31,9 +31,13 @@ import java.util.List; import java.util.concurrent.TimeUnit; import java.util.function.LongConsumer; +import org.eclipse.jetty.client.ConnectionPool; import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.HttpClientTransport; +import org.eclipse.jetty.client.HttpDestination; import org.eclipse.jetty.client.HttpProxy; import org.eclipse.jetty.client.ProxyConfiguration; +import org.eclipse.jetty.client.RoundRobinConnectionPool; import org.eclipse.jetty.client.api.Authentication; import org.eclipse.jetty.client.api.AuthenticationStore; import org.eclipse.jetty.client.api.ContentResponse; @@ -800,4 +804,47 @@ public class HTTPClientDocs .send(); // end::dynamicClearText[] } + + public void getConnectionPool() throws Exception + { + // tag::getConnectionPool[] + HttpClient httpClient = new HttpClient(); + httpClient.start(); + + ConnectionPool connectionPool = httpClient.getDestinations().stream() + // Cast to HttpDestination. + .map(HttpDestination.class::cast) + // Find the destination by filtering on the Origin. + .filter(destination -> destination.getOrigin().getAddress().getHost().equals("domain.com")) + .findAny() + // Get the ConnectionPool. + .map(HttpDestination::getConnectionPool) + .orElse(null); + // end::getConnectionPool[] + } + + public void setConnectionPool() throws Exception + { + // tag::setConnectionPool[] + HttpClient httpClient = new HttpClient(); + httpClient.start(); + + // The max number of connections in the pool. + int maxConnectionsPerDestination = httpClient.getMaxConnectionsPerDestination(); + + // The max number of requests per connection (multiplexing). + // Start with 1, since this value is dynamically set to larger values if + // the transport supports multiplexing requests on the same connection. + int maxRequestsPerConnection = 1; + + HttpClientTransport transport = httpClient.getTransport(); + + // Set the ConnectionPool.Factory using a lambda. + transport.setConnectionPoolFactory(destination -> + new RoundRobinConnectionPool(destination, + maxConnectionsPerDestination, + destination, + maxRequestsPerConnection)); + // end::setConnectionPool[] + } } From 664cb81e6ddba5ac3d8683864228081f4ba61972 Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Mon, 6 Apr 2020 07:48:14 +1000 Subject: [PATCH 038/101] Issue #4747 - Default close status code should be normal status Signed-off-by: Lachlan Roberts --- .../jetty/websocket/javax/common/JavaxWebSocketSession.java | 2 +- .../jetty/websocket/javax/tests/JettySpecificConfigTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketSession.java b/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketSession.java index 9cbe7d6056d..eed6587ab10 100644 --- a/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketSession.java +++ b/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketSession.java @@ -179,7 +179,7 @@ public class JavaxWebSocketSession implements javax.websocket.Session @Override public void close() { - close(new CloseReason(CloseReason.CloseCodes.NO_STATUS_CODE, null)); + close(new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE, null)); } /** diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/JettySpecificConfigTest.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/JettySpecificConfigTest.java index 4c48c350898..e1c61dab3a2 100644 --- a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/JettySpecificConfigTest.java +++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/JettySpecificConfigTest.java @@ -146,7 +146,7 @@ public class JettySpecificConfigTest // Close the Session. session.close(); assertTrue(clientEndpoint.closeLatch.await(5, TimeUnit.SECONDS)); - assertThat(clientEndpoint.closeReason.getCloseCode(), is(CloseReason.CloseCodes.NO_STATUS_CODE)); + assertThat(clientEndpoint.closeReason.getCloseCode(), is(CloseReason.CloseCodes.NORMAL_CLOSURE)); assertNull(clientEndpoint.error); } } From 2028b99e83f1304388ab572edc40682e3e7a0823 Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Mon, 6 Apr 2020 11:54:16 +1000 Subject: [PATCH 039/101] Issue #4747 - SessionID should return same String instance Using the object hash code is not random enough to use as a unique ID. Now using UUID.randomUUID() instead which sufficiently random. Signed-off-by: Lachlan Roberts --- .../jetty/websocket/javax/common/JavaxWebSocketSession.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketSession.java b/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketSession.java index eed6587ab10..7c40cd411a2 100644 --- a/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketSession.java +++ b/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketSession.java @@ -28,6 +28,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import javax.websocket.CloseReason; @@ -62,6 +63,7 @@ public class JavaxWebSocketSession implements javax.websocket.Session private final AvailableDecoders availableDecoders; private final AvailableEncoders availableEncoders; private final Map pathParameters; + private final String sessionId; private Map userProperties; private List negotiatedExtensions; @@ -76,8 +78,8 @@ public class JavaxWebSocketSession implements javax.websocket.Session this.container = container; this.coreSession = coreSession; this.frameHandler = frameHandler; + this.sessionId = UUID.randomUUID().toString(); this.config = Objects.requireNonNull(endpointConfig); - this.availableDecoders = new AvailableDecoders(this.config); this.availableEncoders = new AvailableEncoders(this.config); @@ -315,7 +317,7 @@ public class JavaxWebSocketSession implements javax.websocket.Session @Override public String getId() { - return this.frameHandler.getUpgradeRequest().toString(); + return sessionId; } /** From cf0e3c530f4ab878c8e0d83449fd9225ed8446f4 Mon Sep 17 00:00:00 2001 From: Jan Bartel Date: Mon, 6 Apr 2020 10:12:19 +0200 Subject: [PATCH 040/101] Issue #4662 Ensure contextDestroyed called after filters and servlets destroyed (#4667) * Issue #4662 Ensure contextDestroyed called after filters and servlets destroyed Signed-off-by: Jan Bartel --- .../jetty/server/handler/ContextHandler.java | 128 ++++++++++++----- .../server/handler/ContextHandlerTest.java | 37 +++++ .../eclipse/jetty/servlet/ServletHandler.java | 52 ++++--- .../servlet/ServletContextHandlerTest.java | 136 ++++++++++++++++++ .../jetty/servlet/ServletLifeCycleTest.java | 8 +- 5 files changed, 303 insertions(+), 58 deletions(-) diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java index 764cc78a5f8..725aeb5f40d 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java @@ -182,6 +182,14 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu __serverInfo = serverInfo; } + public enum ContextStatus + { + NOTSET, + INITIALIZED, + DESTROYED + } + + protected ContextStatus _contextStatus = ContextStatus.NOTSET; protected Context _scontext; private final AttributesMap _attributes; private final Map _initParams; @@ -828,6 +836,8 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu // defers the calling of super.doStart() startContext(); + + contextInitialized(); _availability = Availability.AVAILABLE; LOG.info("Started {}", this); @@ -886,49 +896,97 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu addEventListener(new ManagedAttributeListener(this, StringUtil.csvSplit(managedAttributes))); super.doStart(); + } + /** + * Call the ServletContextListeners contextInitialized methods. + * This can be called from a ServletHandler during the proper sequence + * of initializing filters, servlets and listeners. However, if there is + * no ServletHandler, the ContextHandler will call this method during + * doStart(). + * + * @throws Exception + */ + public void contextInitialized() throws Exception + { // Call context listeners - _destroyServletContextListeners.clear(); - if (!_servletContextListeners.isEmpty()) + switch (_contextStatus) { - ServletContextEvent event = new ServletContextEvent(_scontext); - for (ServletContextListener listener : _servletContextListeners) + case NOTSET: { - callContextInitialized(listener, event); - _destroyServletContextListeners.add(listener); + try + { + _destroyServletContextListeners.clear(); + if (!_servletContextListeners.isEmpty()) + { + ServletContextEvent event = new ServletContextEvent(_scontext); + for (ServletContextListener listener : _servletContextListeners) + { + callContextInitialized(listener, event); + _destroyServletContextListeners.add(listener); + } + } + } + finally + { + _contextStatus = ContextStatus.INITIALIZED; + } + break; } + default: + break; + } + } + + /** + * Call the ServletContextListeners with contextDestroyed. + * This method can be called from a ServletHandler in the + * proper sequence of destroying filters, servlets and listeners. + * If there is no ServletHandler, the ContextHandler must ensure + * these listeners are called instead. + * + * @throws Exception + */ + public void contextDestroyed() throws Exception + { + switch (_contextStatus) + { + case INITIALIZED: + { + try + { + //Call context listeners + MultiException ex = new MultiException(); + ServletContextEvent event = new ServletContextEvent(_scontext); + Collections.reverse(_destroyServletContextListeners); + for (ServletContextListener listener : _destroyServletContextListeners) + { + try + { + callContextDestroyed(listener, event); + } + catch (Exception x) + { + ex.add(x); + } + } + ex.ifExceptionThrow(); + } + finally + { + _contextStatus = ContextStatus.DESTROYED; + } + break; + } + default: + break; } } protected void stopContext() throws Exception { - // Call the context listeners - ServletContextEvent event = new ServletContextEvent(_scontext); - Collections.reverse(_destroyServletContextListeners); - MultiException ex = new MultiException(); - for (ServletContextListener listener : _destroyServletContextListeners) - { - try - { - callContextDestroyed(listener, event); - } - catch (Exception x) - { - ex.add(x); - } - } - // stop all the handler hierarchy - try - { - super.doStop(); - } - catch (Exception x) - { - ex.add(x); - } - - ex.ifExceptionThrow(); + super.doStop(); } protected void callContextInitialized(ServletContextListener l, ServletContextEvent e) @@ -945,9 +1003,6 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu l.contextDestroyed(e); } - /* - * @see org.eclipse.thread.AbstractLifeCycle#doStop() - */ @Override protected void doStop() throws Exception { @@ -987,6 +1042,8 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu stopContext(); + contextDestroyed(); + // retain only durable listeners setEventListeners(_durableListeners.toArray(new EventListener[0])); _durableListeners.clear(); @@ -1019,6 +1076,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu } finally { + _contextStatus = ContextStatus.NOTSET; __context.set(oldContext); exitScope(null); LOG.info("Stopped {}", this); diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerTest.java index cd10401fae3..dbc2973e3a8 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerTest.java @@ -27,6 +27,9 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; import java.util.List; + +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -445,6 +448,22 @@ public class ContextHandlerTest assertThat(connector.getResponse("GET /foo/xxx HTTP/1.0\n\n"), Matchers.containsString("ctx='/foo'")); assertThat(connector.getResponse("GET /foo/bar/xxx HTTP/1.0\n\n"), Matchers.containsString("ctx='/foo/bar'")); } + + @Test + public void testContextInitializationDestruction() throws Exception + { + Server server = new Server(); + ContextHandlerCollection contexts = new ContextHandlerCollection(); + server.setHandler(contexts); + + ContextHandler noServlets = new ContextHandler(contexts, "/noservlets"); + TestServletContextListener listener = new TestServletContextListener(); + noServlets.addEventListener(listener); + server.start(); + assertEquals(1, listener.initialized); + server.stop(); + assertEquals(1, listener.destroyed); + } @Test public void testContextVirtualGetContext() throws Exception @@ -840,4 +859,22 @@ public class ContextHandlerTest writer.println("ctx='" + request.getContextPath() + "'"); } } + + private static class TestServletContextListener implements ServletContextListener + { + public int initialized = 0; + public int destroyed = 0; + + @Override + public void contextInitialized(ServletContextEvent sce) + { + initialized++; + } + + @Override + public void contextDestroyed(ServletContextEvent sce) + { + destroyed++; + } + } } diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java index 4f51a5605b7..eebc610f0b3 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java @@ -33,6 +33,7 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentMap; +import java.util.function.Consumer; import java.util.stream.Stream; import javax.servlet.DispatcherType; import javax.servlet.Filter; @@ -307,6 +308,9 @@ public class ServletHandler extends ScopedHandler _servlets = shs; ServletMapping[] sms = (ServletMapping[])LazyList.toArray(servletMappings, ServletMapping.class); _servletMappings = sms; + + if (_contextHandler != null) + _contextHandler.contextDestroyed(); //Retain only Listeners added via jetty apis (is Source.EMBEDDED) List listenerHolders = new ArrayList<>(); @@ -733,30 +737,40 @@ public class ServletHandler extends ScopedHandler public void initialize() throws Exception { - _initialized = true; - MultiException mx = new MultiException(); - Stream.concat(Stream.concat( - Arrays.stream(_filters), - Arrays.stream(_servlets).sorted()), - Arrays.stream(_listeners)) - .forEach(h -> + Consumer> c = h -> + { + try { - try + if (!h.isStarted()) { - if (!h.isStarted()) - { - h.start(); - h.initialize(); - } + h.start(); + h.initialize(); } - catch (Throwable e) - { - LOG.debug(Log.EXCEPTION, e); - mx.add(e); - } - }); + } + catch (Throwable e) + { + LOG.debug(Log.EXCEPTION, e); + mx.add(e); + } + }; + + //Start the listeners so we can call them + Arrays.stream(_listeners).forEach(c); + + //call listeners contextInitialized + if (_contextHandler != null) + _contextHandler.contextInitialized(); + + //Only set initialized true AFTER the listeners have been called + _initialized = true; + + //Start the filters then the servlets + Stream.concat( + Arrays.stream(_filters), + Arrays.stream(_servlets).sorted()) + .forEach(c); mx.ifExceptionThrow(); } diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletContextHandlerTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletContextHandlerTest.java index 771c1931571..7b9a4e885e9 100644 --- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletContextHandlerTest.java +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletContextHandlerTest.java @@ -35,6 +35,7 @@ import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.FilterRegistration; +import javax.servlet.GenericServlet; import javax.servlet.Servlet; import javax.servlet.ServletContainerInitializer; import javax.servlet.ServletContext; @@ -106,6 +107,75 @@ public class ServletContextHandlerTest private LocalConnector _connector; private static final AtomicInteger __testServlets = new AtomicInteger(); + private static int __initIndex = 0; + private static int __destroyIndex = 0; + + public class StopTestFilter implements Filter + { + int _initIndex; + int _destroyIndex; + + @Override + public void init(FilterConfig filterConfig) throws ServletException + { + _initIndex = __initIndex++; + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, + ServletException + { + } + + @Override + public void destroy() + { + _destroyIndex = __destroyIndex++; + } + } + + public class StopTestServlet extends GenericServlet + { + int _initIndex; + int _destroyIndex; + + @Override + public void destroy() + { + _destroyIndex = __destroyIndex++; + super.destroy(); + } + + @Override + public void init() throws ServletException + { + _initIndex = __initIndex++; + super.init(); + } + + @Override + public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException + { + } + } + + public class StopTestListener implements ServletContextListener + { + int _initIndex; + int _destroyIndex; + + @Override + public void contextInitialized(ServletContextEvent sce) + { + _initIndex = __initIndex++; + } + + @Override + public void contextDestroyed(ServletContextEvent sce) + { + _destroyIndex = __destroyIndex++; + } + } public static class MySCI implements ServletContainerInitializer { @@ -405,6 +475,37 @@ public class ServletContextHandlerTest _server.join(); } + @Test + public void testDestroyOrder() throws Exception + { + ContextHandlerCollection contexts = new ContextHandlerCollection(); + _server.setHandler(contexts); + + ServletContextHandler root = new ServletContextHandler(contexts, "/", ServletContextHandler.SESSIONS); + ListenerHolder listenerHolder = new ListenerHolder(); + StopTestListener stopTestListener = new StopTestListener(); + listenerHolder.setListener(stopTestListener); + root.getServletHandler().addListener(listenerHolder); + ServletHolder servletHolder = new ServletHolder(); + StopTestServlet stopTestServlet = new StopTestServlet(); + servletHolder.setServlet(stopTestServlet); + root.addServlet(servletHolder, "/test"); + FilterHolder filterHolder = new FilterHolder(); + StopTestFilter stopTestFilter = new StopTestFilter(); + filterHolder.setFilter(stopTestFilter); + root.addFilter(filterHolder, "/*", EnumSet.of(DispatcherType.REQUEST)); + _server.start(); + _server.stop(); + + assertEquals(0, stopTestListener._initIndex); //listeners contextInitialized called first + assertEquals(1, stopTestFilter._initIndex); //filters init + assertEquals(2, stopTestServlet._initIndex); //servlets init + + assertEquals(0, stopTestFilter._destroyIndex); //filters destroyed first + assertEquals(1, stopTestServlet._destroyIndex); //servlets destroyed next + assertEquals(2, stopTestListener._destroyIndex); //listener contextDestroyed last + } + @Test public void testAddSessionListener() throws Exception { @@ -436,6 +537,41 @@ public class ServletContextHandlerTest assertTrue((Boolean)root.getServletContext().getAttribute("MyContextListener.contextInitialized")); } + @Test + public void testContextInitializationDestruction() throws Exception + { + Server server = new Server(); + ContextHandlerCollection contexts = new ContextHandlerCollection(); + server.setHandler(contexts); + + ServletContextHandler root = new ServletContextHandler(contexts, "/"); + class TestServletContextListener implements ServletContextListener + { + public int initialized = 0; + public int destroyed = 0; + + @Override + public void contextInitialized(ServletContextEvent sce) + { + initialized++; + } + + @Override + public void contextDestroyed(ServletContextEvent sce) + { + destroyed++; + } + } + + TestServletContextListener listener = new TestServletContextListener(); + root.addEventListener(listener); + server.start(); + server.stop(); + assertEquals(1, listener.initialized); + server.stop(); + assertEquals(1, listener.destroyed); + } + @Test public void testListenersFromContextListener() throws Exception { diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletLifeCycleTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletLifeCycleTest.java index f41c3bbbf93..4587572027f 100644 --- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletLifeCycleTest.java +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletLifeCycleTest.java @@ -60,8 +60,8 @@ public class ServletLifeCycleTest context.getObjectFactory().addDecorator(new TestDecorator()); ServletHandler sh = context.getServletHandler(); - sh.addListener(new ListenerHolder(TestListener.class)); - context.addEventListener(context.getServletContext().createListener(TestListener2.class)); + sh.addListener(new ListenerHolder(TestListener.class)); //added directly to ServletHandler + context.addEventListener(context.getServletContext().createListener(TestListener2.class));//create,decorate and add listener to context - no holder! sh.addFilterWithMapping(TestFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST)); sh.addFilterWithMapping(new FilterHolder(context.getServletContext().createFilter(TestFilter2.class)), "/*", EnumSet.of(DispatcherType.REQUEST)); @@ -110,8 +110,6 @@ public class ServletLifeCycleTest server.stop(); assertThat(events, Matchers.contains( - "contextDestroyed class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestListener", - "contextDestroyed class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestListener2", "destroy class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestFilter2", "Destroy class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestFilter2", "destroy class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestFilter", @@ -122,6 +120,8 @@ public class ServletLifeCycleTest "destroy class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestServlet2", "Destroy class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestServlet", "destroy class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestServlet", + "contextDestroyed class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestListener", + "contextDestroyed class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestListener2", "Destroy class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestListener" )); From 4b2842265ac80641f88447de9f23666925cf2b7b Mon Sep 17 00:00:00 2001 From: Jan Bartel Date: Mon, 6 Apr 2020 11:30:33 +0200 Subject: [PATCH 041/101] Issue #4737 Ensure lifecycle callbacks happen for run-as and non async servlets (#4744) * Issue #4737 Ensure lifecycle callbacks happen for run-as and non async servlets Signed-off-by: Jan Bartel --- .../LifeCycleCallbackCollectionTest.java | 100 ++++++++++++++++++ .../eclipse/jetty/servlet/ServletHolder.java | 17 ++- 2 files changed, 116 insertions(+), 1 deletion(-) diff --git a/jetty-plus/src/test/java/org/eclipse/jetty/plus/annotation/LifeCycleCallbackCollectionTest.java b/jetty-plus/src/test/java/org/eclipse/jetty/plus/annotation/LifeCycleCallbackCollectionTest.java index a6d3466f445..9916b57ab09 100644 --- a/jetty-plus/src/test/java/org/eclipse/jetty/plus/annotation/LifeCycleCallbackCollectionTest.java +++ b/jetty-plus/src/test/java/org/eclipse/jetty/plus/annotation/LifeCycleCallbackCollectionTest.java @@ -20,15 +20,38 @@ package org.eclipse.jetty.plus.annotation; import java.lang.reflect.Method; +import javax.servlet.http.HttpServlet; + +import org.eclipse.jetty.plus.webapp.PlusDecorator; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.toolchain.test.MavenTestingUtils; +import org.eclipse.jetty.webapp.WebAppContext; import org.hamcrest.Matchers; import org.junit.jupiter.api.Test; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasSize; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; public class LifeCycleCallbackCollectionTest { + public static class TestServlet extends HttpServlet + { + public static int postConstructCount = 0; + public static int preDestroyCount = 0; + + public void postconstruct() + { + ++postConstructCount; + } + + public void predestroy() + { + ++preDestroyCount; + } + } /** * An unsupported lifecycle callback type @@ -154,6 +177,83 @@ public class LifeCycleCallbackCollectionTest //expected } } + + @Test + public void testServletPostConstructPreDestroy() throws Exception + { + Server server = new Server(); + WebAppContext context = new WebAppContext(); + context.setResourceBase(MavenTestingUtils.getTargetTestingDir("predestroy-test").toURI().toURL().toString()); + context.setContextPath("/"); + server.setHandler(context); + + //add a non-async servlet + ServletHolder notAsync = new ServletHolder(); + notAsync.setHeldClass(TestServlet.class); + notAsync.setName("notAsync"); + notAsync.setAsyncSupported(false); + notAsync.setInitOrder(1); + context.getServletHandler().addServletWithMapping(notAsync, "/notasync/*"); + + //add an async servlet + ServletHolder async = new ServletHolder(); + async.setHeldClass(TestServlet.class); + async.setName("async"); + async.setAsyncSupported(true); + async.setInitOrder(1); + context.getServletHandler().addServletWithMapping(async, "/async/*"); + + //add a run-as servlet + ServletHolder runas = new ServletHolder(); + runas.setHeldClass(TestServlet.class); + runas.setName("runas"); + runas.setRunAsRole("admin"); + runas.setInitOrder(1); + context.getServletHandler().addServletWithMapping(runas, "/runas/*"); + + //add both run-as and non async servlet + ServletHolder both = new ServletHolder(); + both.setHeldClass(TestServlet.class); + both.setName("both"); + both.setRunAsRole("admin"); + both.setAsyncSupported(false); + both.setInitOrder(1); + context.getServletHandler().addServletWithMapping(both, "/both/*"); + + //Make fake lifecycle callbacks for all servlets + LifeCycleCallbackCollection collection = new LifeCycleCallbackCollection(); + context.setAttribute(LifeCycleCallbackCollection.LIFECYCLE_CALLBACK_COLLECTION, collection); + PostConstructCallback pcNotAsync = new PostConstructCallback(TestServlet.class, "postconstruct"); + collection.add(pcNotAsync); + PreDestroyCallback pdNotAsync = new PreDestroyCallback(TestServlet.class, "predestroy"); + collection.add(pdNotAsync); + + PostConstructCallback pcAsync = new PostConstructCallback(TestServlet.class, "postconstruct"); + collection.add(pcAsync); + PreDestroyCallback pdAsync = new PreDestroyCallback(TestServlet.class, "predestroy"); + collection.add(pdAsync); + + PostConstructCallback pcRunAs = new PostConstructCallback(TestServlet.class, "postconstruct"); + collection.add(pcRunAs); + PreDestroyCallback pdRunAs = new PreDestroyCallback(TestServlet.class, "predestroy"); + collection.add(pdRunAs); + + PostConstructCallback pcBoth = new PostConstructCallback(TestServlet.class, "postconstruct"); + collection.add(pcBoth); + PreDestroyCallback pdBoth = new PreDestroyCallback(TestServlet.class, "predestroy"); + collection.add(pdBoth); + + //ensure we invoke the lifecyclecallbacks + context.getObjectFactory().addDecorator(new PlusDecorator(context)); + + server.start(); + + assertEquals(4, TestServlet.postConstructCount); + + server.stop(); + + assertEquals(4, TestServlet.preDestroyCount); + } @Test public void testAddForPreDestroy() throws Exception diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java index 20d31710ea6..28c4974ba6f 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java @@ -457,7 +457,14 @@ public class ServletHolder extends Holder implements UserIdentity.Scope if (o == null) return; Servlet servlet = ((Servlet)o); - getServletHandler().destroyServlet(servlet); + //need to use the unwrapped servlet because lifecycle callbacks such as + //postconstruct and predestroy are based off the classname and the wrapper + //classes are unknown outside the ServletHolder + Servlet unwrapped = servlet; + while (WrapperServlet.class.isAssignableFrom(unwrapped.getClass())) + unwrapped = ((WrapperServlet)unwrapped).getWrappedServlet(); + getServletHandler().destroyServlet(unwrapped); + //destroy the wrapped servlet, in case there is special behaviour servlet.destroy(); } @@ -1304,6 +1311,14 @@ public class ServletHolder extends Holder implements UserIdentity.Scope { _servlet.destroy(); } + + /** + * @return the original servlet + */ + public Servlet getWrappedServlet() + { + return _servlet; + } @Override public String toString() From 8eb4bb98a4c204ac3e8db038759257eb5668e21a Mon Sep 17 00:00:00 2001 From: Jan Bartel Date: Mon, 6 Apr 2020 11:33:09 +0200 Subject: [PATCH 042/101] @RunAs not honoured (#4743) * Issue #4739 Fix @RunAs Signed-off-by: Jan Bartel --- .../annotations/RunAsAnnotationHandler.java | 10 +--- .../annotations/TestRunAsAnnotation.java | 60 +++++++++++++++++++ .../eclipse/jetty/plus/annotation/RunAs.java | 2 + .../plus/annotation/RunAsCollection.java | 2 + .../jetty/plus/webapp/PlusDecorator.java | 6 -- .../plus/webapp/PlusDescriptorProcessor.java | 9 --- .../eclipse/jetty/servlet/ServletHolder.java | 31 +++++----- 7 files changed, 81 insertions(+), 39 deletions(-) create mode 100644 jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestRunAsAnnotation.java diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/RunAsAnnotationHandler.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/RunAsAnnotationHandler.java index 923c7002932..2e4381ad5dc 100644 --- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/RunAsAnnotationHandler.java +++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/RunAsAnnotationHandler.java @@ -21,7 +21,6 @@ package org.eclipse.jetty.annotations; import javax.servlet.Servlet; import org.eclipse.jetty.annotations.AnnotationIntrospector.AbstractIntrospectableAnnotationHandler; -import org.eclipse.jetty.plus.annotation.RunAsCollection; import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -64,14 +63,7 @@ public class RunAsAnnotationHandler extends AbstractIntrospectableAnnotationHand if (d == null) { metaData.setOrigin(holder.getName() + ".servlet.run-as", runAs, clazz); - org.eclipse.jetty.plus.annotation.RunAs ra = new org.eclipse.jetty.plus.annotation.RunAs(clazz.getName(), role); - RunAsCollection raCollection = (RunAsCollection)_context.getAttribute(RunAsCollection.RUNAS_COLLECTION); - if (raCollection == null) - { - raCollection = new RunAsCollection(); - _context.setAttribute(RunAsCollection.RUNAS_COLLECTION, raCollection); - } - raCollection.add(ra); + holder.setRunAsRole(role); } } } diff --git a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestRunAsAnnotation.java b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestRunAsAnnotation.java new file mode 100644 index 00000000000..e79a4ffb286 --- /dev/null +++ b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestRunAsAnnotation.java @@ -0,0 +1,60 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.annotations; + +import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.webapp.WebAppContext; +import org.eclipse.jetty.webapp.WebDescriptor; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class TestRunAsAnnotation +{ + @Test + public void testRunAsAnnotation() throws Exception + { + WebAppContext wac = new WebAppContext(); + + //pre-add a servlet but not by descriptor + ServletHolder holder = new ServletHolder(); + holder.setName("foo1"); + holder.setHeldClass(ServletC.class); + holder.setInitOrder(1); //load on startup + wac.getServletHandler().addServletWithMapping(holder, "/foo/*"); + + //add another servlet of the same class, but as if by descriptor + ServletHolder holder2 = new ServletHolder(); + holder2.setName("foo2"); + holder2.setHeldClass(ServletC.class); + holder2.setInitOrder(1); + wac.getServletHandler().addServletWithMapping(holder2, "/foo2/*"); + wac.getMetaData().setOrigin(holder2.getName() + ".servlet.run-as", new WebDescriptor(null)); + + AnnotationIntrospector parser = new AnnotationIntrospector(); + RunAsAnnotationHandler handler = new RunAsAnnotationHandler(wac); + parser.registerHandler(handler); + parser.introspect(ServletC.class); + + assertEquals("admin", holder.getRunAsRole()); + assertEquals(null, holder2.getRunAsRole()); + + + } +} diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/RunAs.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/RunAs.java index 09169337700..b5ccfb71738 100644 --- a/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/RunAs.java +++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/RunAs.java @@ -26,7 +26,9 @@ import org.eclipse.jetty.servlet.ServletHolder; * RunAs *

    * Represents a <run-as> element in web.xml, or a @RunAs annotation. + * @deprecated unused as of 9.4.28 due for removal in 10.0.0 */ +@Deprecated public class RunAs { private String _className; diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/RunAsCollection.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/RunAsCollection.java index 86f7dd682ff..ad43dd080c3 100644 --- a/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/RunAsCollection.java +++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/RunAsCollection.java @@ -27,7 +27,9 @@ import org.eclipse.jetty.util.log.Logger; /** * RunAsCollection + * @deprecated class unused as of 9.4.28 due for removal in 10.0.0 */ +@Deprecated public class RunAsCollection { private static final Logger LOG = Log.getLogger(RunAsCollection.class); diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/webapp/PlusDecorator.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/webapp/PlusDecorator.java index be30c521292..b93d91764f6 100644 --- a/jetty-plus/src/main/java/org/eclipse/jetty/plus/webapp/PlusDecorator.java +++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/webapp/PlusDecorator.java @@ -20,7 +20,6 @@ package org.eclipse.jetty.plus.webapp; import org.eclipse.jetty.plus.annotation.InjectionCollection; import org.eclipse.jetty.plus.annotation.LifeCycleCallbackCollection; -import org.eclipse.jetty.plus.annotation.RunAsCollection; import org.eclipse.jetty.util.Decorator; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -43,11 +42,6 @@ public class PlusDecorator implements Decorator @Override public Object decorate(Object o) { - - RunAsCollection runAses = (RunAsCollection)_context.getAttribute(RunAsCollection.RUNAS_COLLECTION); - if (runAses != null) - runAses.setRunAs(o); - InjectionCollection injections = (InjectionCollection)_context.getAttribute(InjectionCollection.INJECTION_COLLECTION); if (injections != null) injections.inject(o); diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/webapp/PlusDescriptorProcessor.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/webapp/PlusDescriptorProcessor.java index 410a61cba73..c434ade24b5 100644 --- a/jetty-plus/src/main/java/org/eclipse/jetty/plus/webapp/PlusDescriptorProcessor.java +++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/webapp/PlusDescriptorProcessor.java @@ -20,7 +20,6 @@ package org.eclipse.jetty.plus.webapp; import java.util.Iterator; import java.util.Objects; - import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NameNotFoundException; @@ -33,7 +32,6 @@ import org.eclipse.jetty.plus.annotation.LifeCycleCallback; import org.eclipse.jetty.plus.annotation.LifeCycleCallbackCollection; import org.eclipse.jetty.plus.annotation.PostConstructCallback; import org.eclipse.jetty.plus.annotation.PreDestroyCallback; -import org.eclipse.jetty.plus.annotation.RunAsCollection; import org.eclipse.jetty.plus.jndi.EnvEntry; import org.eclipse.jetty.plus.jndi.Link; import org.eclipse.jetty.plus.jndi.NamingEntry; @@ -93,13 +91,6 @@ public class PlusDescriptorProcessor extends IterativeDescriptorProcessor callbacks = new LifeCycleCallbackCollection(); context.setAttribute(LifeCycleCallbackCollection.LIFECYCLE_CALLBACK_COLLECTION, callbacks); } - - RunAsCollection runAsCollection = (RunAsCollection)context.getAttribute(RunAsCollection.RUNAS_COLLECTION); - if (runAsCollection == null) - { - runAsCollection = new RunAsCollection(); - context.setAttribute(RunAsCollection.RUNAS_COLLECTION, runAsCollection); - } } /** diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java index 28c4974ba6f..8a39f77b83e 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java @@ -392,18 +392,6 @@ public class ServletHolder extends Holder implements UserIdentity.Scope //check if we need to forcibly set load-on-startup checkInitOnStartup(); - if (_runAsRole == null) - { - _identityService = null; - _runAsToken = null; - } - else - { - _identityService = getServletHandler().getIdentityService(); - if (_identityService != null) - _runAsToken = _identityService.newRunAsToken(_runAsRole); - } - _config = new Config(); synchronized (this) @@ -577,10 +565,23 @@ public class ServletHolder extends Holder implements UserIdentity.Scope _servlet = newInstance(); if (_config == null) _config = new Config(); + + //check run-as rolename and convert to token from IdentityService + if (_runAsRole == null) + { + _identityService = null; + _runAsToken = null; + } + else + { + _identityService = getServletHandler().getIdentityService(); + if (_identityService != null) + { - // Handle run as - if (_identityService != null && _runAsToken != null) - _servlet = new RunAsServlet(_servlet, _identityService, _runAsToken); + _runAsToken = _identityService.newRunAsToken(_runAsRole); + _servlet = new RunAsServlet(_servlet, _identityService, _runAsToken); + } + } if (!isAsyncSupported()) _servlet = new NotAsyncServlet(_servlet); From 3e2f27c197783c10ed00f477fe904623294f3f8c Mon Sep 17 00:00:00 2001 From: Jan Bartel Date: Mon, 6 Apr 2020 11:35:57 +0200 Subject: [PATCH 043/101] Issue #4683 Fix jetty-slf4j-impl for osgi (#4684) * Issue #4683 Fix jetty-slf4j-impl for osgi Signed-off-by: Jan Bartel --- jetty-osgi/jetty-osgi-boot-jsp/pom.xml | 72 ++++++++++++++++++- jetty-osgi/jetty-osgi-boot/pom.xml | 2 +- jetty-osgi/test-jetty-osgi/README.txt | 32 +++++++++ jetty-osgi/test-jetty-osgi/pom.xml | 37 ++++++---- .../main/resources/jetty-logging.properties | 1 - .../TestJettyOSGiBootContextAsService.java | 8 +-- .../test/TestJettyOSGiBootHTTP2Conscrypt.java | 8 +-- .../osgi/test/TestJettyOSGiBootHTTP2JDK9.java | 8 +-- .../TestJettyOSGiBootWebAppAsService.java | 8 +-- .../TestJettyOSGiBootWithAnnotations.java | 11 ++- .../test/TestJettyOSGiBootWithBundle.java | 9 +-- .../TestJettyOSGiBootWithJavaxWebSocket.java | 12 ++-- .../osgi/test/TestJettyOSGiBootWithJsp.java | 7 +- .../test/TestJettyOSGiBootWithWebSocket.java | 8 +-- .../eclipse/jetty/osgi/test/TestOSGiUtil.java | 54 +++++++++++++- .../test/resources/jetty-logging.properties | 1 + .../src/test/resources/log4j.xml | 28 -------- jetty-slf4j-impl/pom.xml | 5 +- 18 files changed, 212 insertions(+), 99 deletions(-) create mode 100644 jetty-osgi/test-jetty-osgi/README.txt delete mode 100644 jetty-osgi/test-jetty-osgi/src/main/resources/jetty-logging.properties create mode 100644 jetty-osgi/test-jetty-osgi/src/test/resources/jetty-logging.properties delete mode 100644 jetty-osgi/test-jetty-osgi/src/test/resources/log4j.xml diff --git a/jetty-osgi/jetty-osgi-boot-jsp/pom.xml b/jetty-osgi/jetty-osgi-boot-jsp/pom.xml index b8ebe520cab..28a8ee7bb65 100644 --- a/jetty-osgi/jetty-osgi-boot-jsp/pom.xml +++ b/jetty-osgi/jetty-osgi-boot-jsp/pom.xml @@ -56,9 +56,77 @@ org.eclipse.jetty.osgi.boot !org.eclipse.jetty.osgi.boot.* - org.eclipse.jdt.*;resolution:=optional, org.eclipse.jdt.core.compiler.*;resolution:=optional, com.sun.el;resolution:=optional, com.sun.el.lang;resolution:=optional, com.sun.el.parser;resolution:=optional, com.sun.el.util;resolution:=optional, javax.el;version="[3.0,3.1)", javax.servlet;version="[3.1,4.1)", javax.servlet.resources;version="[3.1,4.1)", javax.servlet.jsp.resources;version="[2.3,4.1)", javax.servlet.jsp;version="[2.3,2.4.1)", javax.servlet.jsp.el;version="[2.3,2.4.1)", javax.servlet.jsp.tagext;version="[2.3,2.4.1)", javax.servlet.jsp.jstl.core;version="1.2";resolution:=optional, javax.servlet.jsp.jstl.fmt;version="1.2";resolution:=optional, javax.servlet.jsp.jstl.sql;version="1.2";resolution:=optional, javax.servlet.jsp.jstl.tlv;version="1.2";resolution:=optional, org.apache.el;version="[8.0.23,10)";resolution:=optional, org.apache.el.lang;version="[8.0.23,10)";resolution:=optional, org.apache.el.stream;version="[8.0.23,10)";resolution:=optional, org.apache.el.util;version="[8.0.23,10)";resolution:=optional, org.apache.el.parser;version="[8.0.23,10)";resolution:=optional, org.apache.jasper;version="[8.0.23,10)";resolution:=optional, org.apache.jasper.compiler;version="[8.0.23,10)";resolution:=optional, org.apache.jasper.compiler.tagplugin;version="[8.0.23,10)";resolution:=optional, org.apache.jasper.runtime;version="[8.0.23,10)";resolution:=optional, org.apache.jasper.security;version="[8.0.23,10)";resolution:=optional, org.apache.jasper.servlet;version="[8.0.23,10)";resolution:=optional, org.apache.jasper.tagplugins.jstl;version="[8.0.23,10)";resolution:=optional, org.apache.jasper.util;version="[8.0.23,10)";resolution:=optional, org.apache.jasper.xmlparser;version="[8.0.23,10)";resolution:=optional, org.apache.taglibs.standard;version="1.2";resolution:=optional, org.apache.taglibs.standard.extra.spath;version="1.2";resolution:=optional, org.apache.taglibs.standard.functions;version="1.2";resolution:=optional, org.apache.taglibs.standard.lang.jstl;version="1.2";resolution:=optional, org.apache.taglibs.standard.lang.jstl.parser;version="1.2";resolution:=optional, org.apache.taglibs.standard.lang.jstl.test;version="1.2";resolution:=optional, org.apache.taglibs.standard.lang.jstl.test.beans;version="1.2";resolution:=optional, org.apache.taglibs.standard.lang.support;version="1.2";resolution:=optional, org.apache.taglibs.standard.resources;version="1.2";resolution:=optional, org.apache.taglibs.standard.tag.common.core;version="1.2";resolution:=optional, org.apache.taglibs.standard.tag.common.fmt;version="1.2";resolution:=optional, org.apache.taglibs.standard.tag.common.sql;version="1.2";resolution:=optional, org.apache.taglibs.standard.tag.common.xml;version="1.2";resolution:=optional, org.apache.taglibs.standard.tag.el.core;version="1.2";resolution:=optional, org.apache.taglibs.standard.tag.el.fmt;version="1.2";resolution:=optional, org.apache.taglibs.standard.tag.el.sql;version="1.2";resolution:=optional, org.apache.taglibs.standard.tag.el.xml;version="1.2";resolution:=optional, org.apache.taglibs.standard.tag.rt.core;version="1.2";resolution:=optional, org.apache.taglibs.standard.tag.rt.fmt;version="1.2";resolution:=optional, org.apache.taglibs.standard.tag.rt.sql;version="1.2";resolution:=optional, org.apache.taglibs.standard.tag.rt.xml;version="1.2";resolution:=optional, org.apache.taglibs.standard.tei;version="1.2";resolution:=optional, org.apache.taglibs.standard.tlv;version="1.2";resolution:=optional, org.apache.tomcat;version="[8.0.23,10)";resolution:=optional, org.eclipse.jetty.jsp;version="[$(version;===;${parsedVersion.osgiVersion}),$(version;==+;${parsedVersion.osgiVersion}))";resolution:=optional, org.osgi.*, org.xml.*;resolution:=optional, org.xml.sax.*;resolution:=optional, javax.xml.*;resolution:=optional, org.w3c.dom;resolution:=optional, org.w3c.dom.ls;resolution:=optional, javax.xml.parser;resolution:=optional + + org.eclipse.jdt.*;resolution:=optional, + org.eclipse.jdt.core.compiler.*;resolution:=optional, + com.sun.el;resolution:=optional, + com.sun.el.lang;resolution:=optional, + com.sun.el.parser;resolution:=optional, + com.sun.el.util;resolution:=optional, + javax.el;version="[3.0,3.1)", + javax.servlet;version="[3.1,4.1)", + javax.servlet.resources;version="[3.1,4.1)", + javax.servlet.jsp.resources;version="[2.3,4.1)", + javax.servlet.jsp;version="[2.3,2.4.1)", + javax.servlet.jsp.el;version="[2.3,2.4.1)", + javax.servlet.jsp.tagext;version="[2.3,2.4.1)", + javax.servlet.jsp.jstl.core;version="1.2";resolution:=optional, + javax.servlet.jsp.jstl.fmt;version="1.2";resolution:=optional, + javax.servlet.jsp.jstl.sql;version="1.2";resolution:=optional, + javax.servlet.jsp.jstl.tlv;version="1.2";resolution:=optional, + org.apache.el;version="[8.0.23,10)";resolution:=optional, + org.apache.el.lang;version="[8.0.23,10)";resolution:=optional, + org.apache.el.stream;version="[8.0.23,10)";resolution:=optional, + org.apache.el.util;version="[8.0.23,10)";resolution:=optional, + org.apache.el.parser;version="[8.0.23,10)";resolution:=optional, + org.apache.jasper;version="[8.0.23,10)";resolution:=optional, + org.apache.jasper.compiler;version="[8.0.23,10)";resolution:=optional, + org.apache.jasper.compiler.tagplugin;version="[8.0.23,10)";resolution:=optional, + org.apache.jasper.runtime;version="[8.0.23,10)";resolution:=optional, + org.apache.jasper.security;version="[8.0.23,10)";resolution:=optional, + org.apache.jasper.servlet;version="[8.0.23,10)";resolution:=optional, + org.apache.jasper.tagplugins.jstl;version="[8.0.23,10)";resolution:=optional, + org.apache.jasper.util;version="[8.0.23,10)";resolution:=optional, + org.apache.jasper.xmlparser;version="[8.0.23,10)";resolution:=optional, + org.apache.taglibs.standard;version="1.2";resolution:=optional, + org.apache.taglibs.standard.extra.spath;version="1.2";resolution:=optional, + org.apache.taglibs.standard.functions;version="1.2";resolution:=optional, + org.apache.taglibs.standard.lang.jstl;version="1.2";resolution:=optional, + org.apache.taglibs.standard.lang.jstl.parser;version="1.2";resolution:=optional, + org.apache.taglibs.standard.lang.jstl.test;version="1.2";resolution:=optional, + org.apache.taglibs.standard.lang.jstl.test.beans;version="1.2";resolution:=optional, + org.apache.taglibs.standard.lang.support;version="1.2";resolution:=optional, + org.apache.taglibs.standard.resources;version="1.2";resolution:=optional, + org.apache.taglibs.standard.tag.common.core;version="1.2";resolution:=optional, + org.apache.taglibs.standard.tag.common.fmt;version="1.2";resolution:=optional, + org.apache.taglibs.standard.tag.common.sql;version="1.2";resolution:=optional, + org.apache.taglibs.standard.tag.common.xml;version="1.2";resolution:=optional, + org.apache.taglibs.standard.tag.el.core;version="1.2";resolution:=optional, + org.apache.taglibs.standard.tag.el.fmt;version="1.2";resolution:=optional, + org.apache.taglibs.standard.tag.el.sql;version="1.2";resolution:=optional, + org.apache.taglibs.standard.tag.el.xml;version="1.2";resolution:=optional, + org.apache.taglibs.standard.tag.rt.core;version="1.2";resolution:=optional, + org.apache.taglibs.standard.tag.rt.fmt;version="1.2";resolution:=optional, + org.apache.taglibs.standard.tag.rt.sql;version="1.2";resolution:=optional, + org.apache.taglibs.standard.tag.rt.xml;version="1.2";resolution:=optional, + org.apache.taglibs.standard.tei;version="1.2";resolution:=optional, + org.apache.taglibs.standard.tlv;version="1.2";resolution:=optional, + org.apache.tomcat;version="[8.0.23,10)";resolution:=optional, + org.eclipse.jetty.jsp;version="[$(version;===;${parsedVersion.osgiVersion}),$(version;==+;${parsedVersion.osgiVersion}))";resolution:=optional, + org.slf4j.*, + org.osgi.*, + org.xml.*;resolution:=optional, + org.xml.sax.*;resolution:=optional, + javax.xml.*;resolution:=optional, + org.w3c.dom;resolution:=optional, + org.w3c.dom.ls;resolution:=optional, + javax.xml.parser;resolution:=optional - org.eclipse.jetty.jsp.*;version="[$(version;===;${parsedVersion.osgiVersion}),$(version;==+;${parsedVersion.osgiVersion}))",org.apache.jasper.*;version="8.0.23",org.apache.el.*;version="8.0.23" + + org.eclipse.jetty.jsp.*;version="[$(version;===;${parsedVersion.osgiVersion}),$(version;==+;${parsedVersion.osgiVersion}))", + org.apache.jasper.*;version="8.0.23", + org.apache.el.*;version="8.0.23" + diff --git a/jetty-osgi/jetty-osgi-boot/pom.xml b/jetty-osgi/jetty-osgi-boot/pom.xml index 443fe24c095..1bfdd01b97c 100644 --- a/jetty-osgi/jetty-osgi-boot/pom.xml +++ b/jetty-osgi/jetty-osgi-boot/pom.xml @@ -71,7 +71,7 @@ org.eclipse.jetty.osgi.boot;singleton:=true org.eclipse.jetty.osgi.boot.JettyBootstrapActivator org.eclipse.jetty.*;version="[$(version;===;${parsedVersion.osgiVersion}),$(version;==+;${parsedVersion.osgiVersion}))" - javax.mail;version="1.4.0";resolution:=optional, javax.mail.event;version="1.4.0";resolution:=optional, javax.mail.internet;version="1.4.0";resolution:=optional, javax.mail.search;version="1.4.0";resolution:=optional, javax.mail.util;version="1.4.0";resolution:=optional, javax.servlet;version="[3.1,4.1)", javax.servlet.http;version="[3.1,4.1)", javax.transaction;version="1.1.0";resolution:=optional, javax.transaction.xa;version="1.1.0";resolution:=optional, org.objectweb.asm;version="5";resolution:=optional, org.osgi.framework, org.osgi.service.cm;version="1.2.0", org.osgi.service.packageadmin, org.osgi.service.startlevel;version="1.0.0", org.osgi.service.url;version="1.0.0", org.osgi.util.tracker;version="1.3.0", org.slf4j;resolution:=optional, org.slf4j.spi;resolution:=optional, org.slf4j.helpers;resolution:=optional, org.xml.sax, org.xml.sax.helpers, org.eclipse.jetty.annotations;resolution:=optional, * + javax.mail;version="1.4.0";resolution:=optional, javax.mail.event;version="1.4.0";resolution:=optional, javax.mail.internet;version="1.4.0";resolution:=optional, javax.mail.search;version="1.4.0";resolution:=optional, javax.mail.util;version="1.4.0";resolution:=optional, javax.servlet;version="[3.1,4.1)", javax.servlet.http;version="[3.1,4.1)", javax.transaction;version="1.1.0";resolution:=optional, javax.transaction.xa;version="1.1.0";resolution:=optional, org.objectweb.asm;version="5";resolution:=optional, org.osgi.framework, org.osgi.service.cm;version="1.2.0", org.osgi.service.packageadmin, org.osgi.service.startlevel;version="1.0.0", org.osgi.service.url;version="1.0.0", org.osgi.util.tracker;version="1.3.0", org.xml.sax, org.xml.sax.helpers, org.eclipse.jetty.annotations;resolution:=optional, * osgi.extender; filter:="(osgi.extender=osgi.serviceloader.registrar)" diff --git a/jetty-osgi/test-jetty-osgi/README.txt b/jetty-osgi/test-jetty-osgi/README.txt new file mode 100644 index 00000000000..22f22dce121 --- /dev/null +++ b/jetty-osgi/test-jetty-osgi/README.txt @@ -0,0 +1,32 @@ +Unit Tests with OSGi +-------------------- + +The unit tests use PaxExam https://ops4j1.jira.com/wiki/spaces/PAXEXAM4/overview +to fork a jvm to start an OSGi container (currently eclipse) and deploy the jetty +jars as osgi bundles, along with the jetty-osgi infrastructure (like jetty-osgi-boot). + +To run all the tests: + mvn test + +To run a particular test: + mvn test -Dtest=[name of test] + + +At the time of writing, PaxExam only works with junit-4, so you may not be +able to invoke them easily from your IDE. + +Logging +------- +By default, very little log info comes out of the tests. If you wish to see more +logging information, you can control this from the command line. + +There are 2 sources of logging information: 1) the pax environment and 2) jetty logs. + +To set the logging level for the pax environment use the following system property: + + mvn -Dpax.exam.LEVEL=[log level] + +INFO, WARN and TRACE are known to work. + +To set the logging level for the jetty logs edit the src/test/resources/jetty-logging.properties +to set the logging level you want and rerun your tests. The usual jetty logging levels apply. diff --git a/jetty-osgi/test-jetty-osgi/pom.xml b/jetty-osgi/test-jetty-osgi/pom.xml index d82c7e2dde9..01be0b6dfdb 100644 --- a/jetty-osgi/test-jetty-osgi/pom.xml +++ b/jetty-osgi/test-jetty-osgi/pom.xml @@ -72,18 +72,31 @@ org.ops4j.pax.tinybundles tinybundles - 2.1.1 + 3.0.0 + test + + + biz.aQute.bnd + bnd + + org.ops4j.pax.url pax-url-wrap ${pax.url.version} test + + + biz.aQute.bnd + bndlib + + biz.aQute.bnd - bndlib - 2.4.0 + biz.aQute.bndlib + 5.0.0 org.osgi @@ -105,6 +118,12 @@ + + org.eclipse.jetty + jetty-slf4j-impl + ${project.version} + test + org.eclipse.jetty.osgi jetty-osgi-boot @@ -409,18 +428,6 @@ jetty-test-helper test - - org.slf4j - slf4j-api - ${slf4j.version} - test - - - org.slf4j - slf4j-log4j12 - ${slf4j.version} - test - org.ow2.asm asm diff --git a/jetty-osgi/test-jetty-osgi/src/main/resources/jetty-logging.properties b/jetty-osgi/test-jetty-osgi/src/main/resources/jetty-logging.properties deleted file mode 100644 index c2be11c689a..00000000000 --- a/jetty-osgi/test-jetty-osgi/src/main/resources/jetty-logging.properties +++ /dev/null @@ -1 +0,0 @@ -org.eclipse.jetty.LEVEL=INFO \ No newline at end of file diff --git a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootContextAsService.java b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootContextAsService.java index 3e34573a5bc..2e9f7393b82 100644 --- a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootContextAsService.java +++ b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootContextAsService.java @@ -51,8 +51,6 @@ import static org.ops4j.pax.exam.CoreOptions.systemProperty; @RunWith(PaxExam.class) public class TestJettyOSGiBootContextAsService { - private static final String LOG_LEVEL = "WARN"; - @Inject BundleContext bundleContext = null; @@ -60,6 +58,9 @@ public class TestJettyOSGiBootContextAsService public static Option[] configure() { ArrayList

    *

    The smaller bucket is defined as a fraction of the bigger bucket.

    *

    For a more visual representation, see the - * rocking bamboo fountain.

    + * rocking bamboo fountain, + * where the bamboo is the smaller bucket and the pool is the bigger bucket.

    *

    The algorithm works in this way.

    *

    The initial bigger bucket (BB) capacity is 100, and let's imagine the smaller * bucket (SB) being 40% of the bigger bucket: 40.

    @@ -50,6 +51,16 @@ import org.eclipse.jetty.util.annotation.ManagedObject; * with delta=45.

    *

    The application consumes the remaining 15, so now SB=15, and no window * control frame is emitted.

    + *

    The {@code bufferRatio} controls how often the window control frame is + * emitted.

    + *

    A {@code bufferRatio=0.0} means that a window control frame is emitted + * every time the application consumes a data frame. This may result in too many + * window control frames be emitted, but may allow the sender to avoid stalling.

    + *

    A {@code bufferRatio=1.0} means that a window control frame is emitted + * only when the application has consumed a whole window. This minimizes the + * number of window control frames emitted, but may cause the sender to stall, + * waiting for the window control frame.

    + *

    The default value is {@code bufferRatio=0.5}.

    */ @ManagedObject public class BufferingFlowControlStrategy extends AbstractFlowControlStrategy diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Session.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Session.java index 097fecb5a59..6166aba962d 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Session.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Session.java @@ -541,7 +541,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio synchronized (this) { HeadersFrame[] frameOut = new HeadersFrame[1]; - stream = newStream(frame, frameOut); + stream = newLocalStream(frame, frameOut); stream.setListener(listener); ControlEntry entry = new ControlEntry(frameOut[0], stream, new StreamPromiseCallback(promise, stream)); queued = flusher.append(entry); @@ -567,7 +567,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio * allocated stream id, or null if not interested in the modified headers frame * @return a new stream */ - public IStream newStream(HeadersFrame frameIn, HeadersFrame[] frameOut) + public IStream newLocalStream(HeadersFrame frameIn, HeadersFrame[] frameOut) { HeadersFrame frame = frameIn; int streamId = frameIn.getStreamId(); diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/api/Session.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/api/Session.java index 5b5aa5c6ade..19f11d7b2ec 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/api/Session.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/api/Session.java @@ -21,6 +21,7 @@ package org.eclipse.jetty.http2.api; import java.net.InetSocketAddress; import java.util.Collection; import java.util.Map; +import java.util.concurrent.CompletableFuture; import org.eclipse.jetty.http2.frames.DataFrame; import org.eclipse.jetty.http2.frames.GoAwayFrame; @@ -55,6 +56,20 @@ import org.eclipse.jetty.util.Promise; */ public interface Session { + /** + *

    Sends the given HEADERS {@code frame} to create a new {@link Stream}.

    + * + * @param frame the HEADERS frame containing the HTTP headers + * @param listener the listener that gets notified of stream events + * @return a CompletableFuture that is notified of the stream creation + */ + public default CompletableFuture newStream(HeadersFrame frame, Stream.Listener listener) + { + Promise.Completable result = new Promise.Completable<>(); + newStream(frame, result, listener); + return result; + } + /** *

    Sends the given HEADERS {@code frame} to create a new {@link Stream}.

    * diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/api/Stream.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/api/Stream.java index cfba9e089e9..ae48a46159a 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/api/Stream.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/api/Stream.java @@ -18,6 +18,8 @@ package org.eclipse.jetty.http2.api; +import java.util.concurrent.CompletableFuture; + import org.eclipse.jetty.http2.frames.DataFrame; import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.http2.frames.PushPromiseFrame; @@ -67,6 +69,19 @@ public interface Stream */ public void push(PushPromiseFrame frame, Promise promise, Listener listener); + /** + *

    Sends the given DATA {@code frame}.

    + * + * @param frame the DATA frame to send + * @return the CompletableFuture that gets notified when the frame has been sent + */ + public default CompletableFuture data(DataFrame frame) + { + Callback.Completable result = new Callback.Completable(); + data(frame, result); + return result; + } + /** *

    Sends the given DATA {@code frame}.

    * @@ -174,7 +189,7 @@ public interface Stream /** *

    Callback method invoked when a PUSH_PROMISE frame has been received.

    * - * @param stream the stream + * @param stream the pushed stream * @param frame the PUSH_PROMISE frame received * @return a Stream.Listener that will be notified of pushed stream events */ diff --git a/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpConnectionOverHTTP2.java b/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpConnectionOverHTTP2.java index 40e22015323..4f537b3fe57 100644 --- a/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpConnectionOverHTTP2.java +++ b/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpConnectionOverHTTP2.java @@ -108,7 +108,7 @@ public class HttpConnectionOverHTTP2 extends HttpConnection implements Sweeper.S MetaData.Request metaData = new MetaData.Request(request.getMethod(), new HttpURI(request.getURI()), HttpVersion.HTTP_2, request.getHeaders()); // We do not support upgrade requests with content, so endStream=true. HeadersFrame frame = new HeadersFrame(metaData, null, true); - IStream stream = ((HTTP2Session)session).newStream(frame, null); + IStream stream = ((HTTP2Session)session).newLocalStream(frame, null); stream.updateClose(frame.isEndStream(), CloseState.Event.AFTER_SEND); HttpExchange exchange = request.getConversation().getExchanges().peekLast(); From ba2fe581112f683bc7d897a137c2fbb34d5a2cae Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Mon, 6 Apr 2020 13:31:34 +0200 Subject: [PATCH 048/101] Improvements to the Jetty client documentation, fixed section ids and xrefs. Signed-off-by: Simone Bordet --- .../embedded-guide/client/client-io-arch.adoc | 10 +++---- .../embedded-guide/client/client.adoc | 10 +++---- .../client/http/client-http-api.adoc | 12 ++++---- .../http/client-http-authentication.adoc | 4 +-- .../http/client-http-configuration.adoc | 10 +++---- .../client/http/client-http-cookie.adoc | 2 +- .../client/http/client-http-intro.adoc | 30 +++++++++---------- .../client/http/client-http-proxy.adoc | 8 ++--- .../client/http/client-http-transport.adoc | 20 ++++++------- .../client/http/client-http.adoc | 2 +- .../client/http2/client-http2.adoc | 30 +++++++++---------- 11 files changed, 69 insertions(+), 69 deletions(-) diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/client/client-io-arch.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/client-io-arch.adoc index f2afffd946e..83e59d0ed7a 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/client/client-io-arch.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/client-io-arch.adoc @@ -16,7 +16,7 @@ // ======================================================================== // -[[client-io-arch]] +[[eg-client-io-arch]] === Client Libraries Architecture The Jetty client libraries provide the basic components and APIs to implement @@ -27,13 +27,13 @@ specific concepts (such as establishing a connection to a server). There are conceptually two layers that compose the Jetty client libraries: -. link:#client-io-arch-network[The network layer], that handles the low level +. xref:eg-client-io-arch-network[The network layer], that handles the low level I/O and deals with buffers, threads, etc. -. link:#client-io-arch-protocol[The protocol layer], that handles the parsing +. xref:eg-client-io-arch-protocol[The protocol layer], that handles the parsing of bytes read from the network and the generation of bytes to write to the network. -[[client-io-arch-network]] +[[eg-client-io-arch-network]] ==== Client Libraries Network Layer The Jetty client libraries use the common I/O design described in @@ -115,7 +115,7 @@ Please refer to the `ClientConnector` link:{JDURL}/org/eclipse/jetty/io/ClientConnector.html[javadocs] for the complete list of configurable parameters. -[[client-io-arch-protocol]] +[[eg-client-io-arch-protocol]] ==== Client Libraries Protocol Layer The protocol layer builds on top of the network layer to generate the diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/client/client.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/client.adoc index 013edfc2d8f..28a17421373 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/client/client.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/client.adoc @@ -16,7 +16,7 @@ // ======================================================================== // -[[client]] +[[eg-client]] == Client Libraries The Eclipse Jetty Project provides also provides client-side libraries @@ -34,13 +34,13 @@ and asynchronous APIs and come with a large number of configuration options. These are the available client libraries: -* link:#client-http[The HTTP Client Library] -* link:#client-http2[The HTTP/2 Client Library] -* link:#client-websocket[The WebSocket client library] +* xref:eg-client-http[The HTTP Client Library] +* xref:eg-client-http2[The HTTP/2 Client Library] +* xref:eg-client-websocket[The WebSocket client library] If you are interested in the low-level details of how the Eclipse Jetty client libraries work, or are interested in writing a custom protocol, -look at the link:#client-io-arch[Client I/O Architecture]. +look at the xref:eg-client-io-arch[Client I/O Architecture]. include::http/client-http.adoc[] include::http2/client-http2.adoc[] diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-api.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-api.adoc index b369bb33e91..e2aa147bb42 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-api.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-api.adoc @@ -16,12 +16,12 @@ // ======================================================================== // -[[client-http-api]] +[[eg-client-http-api]] === HttpClient API Usage `HttpClient` provides two types of APIs: a blocking API and a non-blocking API. -[[client-http-blocking]] +[[eg-client-http-blocking]] ==== HttpClient Blocking APIs The simpler way to perform a HTTP request is the following: @@ -81,7 +81,7 @@ include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tags=totalTim In the example above, when the 5 seconds expire, the request is aborted and a `java.util.concurrent.TimeoutException` is thrown. -[[client-http-non-blocking]] +[[eg-client-http-non-blocking]] ==== HttpClient Non-Blocking APIs So far we have shown how to use Jetty HTTP client in a blocking style - that is, the thread that issues the request blocks until the request/response conversation is complete. @@ -139,10 +139,10 @@ This makes Jetty HTTP client suitable for HTTP load testing because, for example Have a look at the link:{JDURL}/org/eclipse/jetty/client/api/Request.Listener.html[`Request.Listener`] class to know about request events, and to the link:{JDURL}/org/eclipse/jetty/client/api/Response.Listener.html[`Response.Listener`] class to know about response events. -[[client-http-content]] +[[eg-client-http-content]] ==== HttpClient Content Handling -[[client-http-content-request]] +[[eg-client-http-content-request]] ===== Request Content Handling Jetty's `HttpClient` provides a number of utility classes off the shelf to handle request content. @@ -190,7 +190,7 @@ which allows applications to write request content when it is available to the ` include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tags=outputStreamRequestContent] ---- -[[client-http-content-response]] +[[eg-client-http-content-response]] ===== Response Content Handling Jetty's `HttpClient` allows applications to handle response content in different ways. diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-authentication.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-authentication.adoc index 40a8addb58d..0f9a0134a52 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-authentication.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-authentication.adoc @@ -16,7 +16,7 @@ // ======================================================================== // -[[client-http-authentication]] +[[eg-client-http-authentication]] === HttpClient Authentication Support Jetty's `HttpClient` supports the `BASIC` and `DIGEST` authentication @@ -97,5 +97,5 @@ in this way: include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=requestPreemptedResult] ---- -See also the link:#client-http-proxy-authentication[proxy authentication section] +See also the xref:eg-client-http-proxy-authentication[proxy authentication section] for further information about how authentication works with HTTP proxies. diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-configuration.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-configuration.adoc index e9a6f0ab7b1..800d2c24adb 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-configuration.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-configuration.adoc @@ -16,7 +16,7 @@ // ======================================================================== // -[[client-http-configuration]] +[[eg-client-http-configuration]] === HttpClient Configuration `HttpClient` has a quite large number of configuration parameters. @@ -26,17 +26,17 @@ for the complete list of configurable parameters. The most common parameters are: * `HttpClient.idleTimeout`: same as `ClientConnector.idleTimeout` -described in link:#client-io-arch-network[this section]. +described in xref:eg-client-io-arch-network[this section]. * `HttpClient.connectBlocking`: same as `ClientConnector.connectBlocking` -described in link:#client-io-arch-network[this section]. +described in xref:eg-client-io-arch-network[this section]. * `HttpClient.connectTimeout`: same as `ClientConnector.connectTimeout` -described in link:#client-io-arch-network[this section]. +described in xref:eg-client-io-arch-network[this section]. * `HttpClient.maxConnectionsPerDestination`: the max number of TCP connections that are opened for a particular destination (defaults to 64). * `HttpClient.maxRequestsQueuedPerDestination`: the max number of requests queued (defaults to 1024). -[[client-http-configuration-tls]] +[[eg-client-http-configuration-tls]] ==== HttpClient TLS Configuration `HttpClient` supports HTTPS requests out-of-the-box like a browser does. diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-cookie.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-cookie.adoc index dc060ebf5cf..8c414de749f 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-cookie.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-cookie.adoc @@ -16,7 +16,7 @@ // ======================================================================== // -[[client-http-cookie]] +[[eg-client-http-cookie]] === HttpClient Cookie Support Jetty's `HttpClient` supports cookies out of the box. diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-intro.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-intro.adoc index acd412130c8..ae1c4a364a3 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-intro.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-intro.adoc @@ -16,7 +16,7 @@ // ======================================================================== // -[[client-http-intro]] +[[eg-client-http-intro]] === HttpClient Introduction The Jetty HTTP client module provides easy-to-use APIs and utility classes to perform HTTP (or HTTPS) requests. @@ -27,7 +27,7 @@ It offers an asynchronous API that never blocks for I/O, making it very efficien However, when all you need to do is to perform a `GET` request to a resource, Jetty's HTTP client offers also a synchronous API; a programming interface where the thread that issued the request blocks until the request/response conversation is complete. -Jetty's HTTP client supports link:#http-client-transport[different transports]: HTTP/1.1, FastCGI and HTTP/2. +Jetty's HTTP client supports xref:#eg-client-http-transport[different transports]: HTTP/1.1, FastCGI and HTTP/2. This means that the semantic of a HTTP request (that is, " `GET` me the resource `/index.html` ") can be carried over the network in different formats. The most common and default format is HTTP/1.1. That said, Jetty's HTTP client can carry the same request using the FastCGI format or the HTTP/2 format. @@ -43,7 +43,7 @@ Out of the box features that you get with the Jetty HTTP client include: * Authentication support - HTTP "Basic" and "Digest" authentications are supported, others are pluggable. * Forward proxy support - HTTP proxying and SOCKS4 proxying. -[[client-http-start]] +[[eg-client-http-start]] ==== Starting HttpClient The Jetty artifact that provides the main HTTP client implementation is `jetty-client`. @@ -76,15 +76,15 @@ There are several reasons for having multiple `HttpClient` instances including, * You want to specify different configuration parameters (for example, one instance is configured with a forward proxy while another is not). * You want the two instances to behave like two different browsers and hence have different cookies, different authentication credentials, etc. -* You want to use link:#http-client-transport[different transports]. +* You want to use link:#eg-client-http-transport[different transports]. Like browsers, HTTPS requests are supported out-of-the-box, as long as the server provides a valid certificate. In case the server does not provide a valid certificate (or in case it is self-signed) you want to customize ``HttpClient``'s TLS configuration as described in -link:#client-http-configuration-tls[this section]. +xref:eg-client-http-configuration-tls[this section]. -[[client-http-stop]] +[[eg-client-http-stop]] ==== Stopping HttpClient It is recommended that when your application stops, you also stop the `HttpClient` instance (or instances) that you are using. @@ -96,20 +96,20 @@ include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tags=stop] 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. -[[client-http-arch]] +[[eg-client-http-arch]] ==== HttpClient Architecture A `HttpClient` instance can be thought as a browser instance, and it manages the following components: -* a `CookieStore` (see link:#client-http-cookie[this section]). -* a `AuthenticationStore` (see link:#client-http-authentication[this section]). -* a `ProxyConfiguration` (see link:#client-http-proxy[this section]). +* a `CookieStore` (see xref:eg-client-http-cookie[this section]). +* a `AuthenticationStore` (see xref:eg-client-http-authentication[this section]). +* a `ProxyConfiguration` (see xref:eg-client-http-proxy[this section]). * a set of _destinations_. A _destination_ is the client-side component that represent an _origin_ on a server, and manages a queue of requests for that origin, and a -link:#client-http-connection-pool[pool of connections] to that origin. +xref:eg-client-http-connection-pool[pool of connections] to that origin. An _origin_ may be simply thought as the tuple `(scheme, host, port)` and it is where the client connects to in order to communicate with the server. @@ -145,7 +145,7 @@ connection pools. Therefore an origin is identified by the tuple `(scheme, host, port, tag, protocol)`. -[[client-http-connection-pool]] +[[eg-client-http-connection-pool]] ==== HttpClient Connection Pooling A destination manages a `org.eclipse.jetty.client.ConnectionPool`, where @@ -184,7 +184,7 @@ by setting a `ConnectionPool.Factory` on the `HttpClientTransport`: include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tags=setConnectionPool] ---- -[[client-http-request-processing]] +[[eg-client-http-request-processing]] ==== HttpClient Request Processing [plantuml] @@ -231,7 +231,7 @@ cycle is completed will reuse the same connection. A second request with the same origin sent _concurrently_ with the first request will cause the opening of a second connection. The configuration parameter `HttpClient.maxConnectionsPerDestination` -(see also the link:#client-http-configuration[configuration section]) controls +(see also the xref:eg-client-http-configuration[configuration section]) controls the max number of connections that can be opened for a destination. NOTE: If opening connections to a given origin takes a long time, then @@ -249,5 +249,5 @@ connections have maxed out their number of outstanding requests, more requests sent to that destination will be queued. When the request queue is full, the request will be failed. The configuration parameter `HttpClient.maxRequestsQueuedPerDestination` -(see also the link:#client-http-configuration[configuration section]) controls +(see also the xref:eg-client-http-configuration[configuration section]) controls the max number of requests that can be queued for a destination. diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-proxy.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-proxy.adoc index cea87ff73a1..a4b8fa9cc26 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-proxy.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-proxy.adoc @@ -16,7 +16,7 @@ // ======================================================================== // -[[client-http-proxy]] +[[eg-client-http-proxy]] === HttpClient Proxy Support Jetty's `HttpClient` can be configured to use proxies to connect to destinations. @@ -43,11 +43,11 @@ encrypted HTTPS requests). Proxying is supported for both HTTP/1.1 and HTTP/2. -[[client-http-proxy-authentication]] +[[eg-client-http-proxy-authentication]] ==== Proxy Authentication Support Jetty's `HttpClient` supports proxy authentication in the same way it supports -link:#client-http-authentication[server authentication]. +xref:eg-client-http-authentication[server authentication]. In the example below, the proxy requires `BASIC` authentication, but the server requires `DIGEST` authentication, and therefore: @@ -87,6 +87,6 @@ HttpClient -> Application : 200 OK The application does not receive events related to the responses with code 407 and 401 since they are handled internally by `HttpClient`. -Similarly to the link:#client-http-authentication[authentication section], the +Similarly to the xref:eg-client-http-authentication[authentication section], the proxy authentication result and the server authentication result can be preempted to avoid, respectively, the 407 and 401 roundtrips. diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-transport.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-transport.adoc index 518483296b4..e5840c61390 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-transport.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-transport.adoc @@ -16,7 +16,7 @@ // ======================================================================== // -[[client-http-transport]] +[[eg-client-http-transport]] === HttpClient Pluggable Transports Jetty's `HttpClient` can be configured to use different transports to carry the @@ -64,13 +64,13 @@ resource may be sent using one protocol (for example, HTTP/1.1), but the response may arrive in a different protocol (for example, HTTP/2). `HttpClient` supports 3 static transports, each speaking only one protocol: -link:#client-http-transport-http11[HTTP/1.1], -link:#client-http-transport-http2[HTTP/2] and -link:#client-http-transport-fcgi[FastCGI], +xref:eg-client-http-transport-http11[HTTP/1.1], +xref:eg-client-http-transport-http2[HTTP/2] and +xref:eg-client-http-transport-fcgi[FastCGI], all of them with 2 variants: clear-text and TLS encrypted. `HttpClient` also supports one -link:#client-http-transport-dynamic[dynamic transport], +xref:eg-client-http-transport-dynamic[dynamic transport], that can speak different protocols and can select the right protocol by negotiating it with the server or by explicit indication from applications. @@ -78,7 +78,7 @@ Applications are typically not aware of the actual protocol being used. This allows them to write their logic against a high-level API that hides the details of the specific protocol being used over the network. -[[client-http-transport-http11]] +[[eg-client-http-transport-http11]] ==== HTTP/1.1 Transport HTTP/1.1 is the default transport. @@ -96,7 +96,7 @@ it in this way: include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=http11Transport] ---- -[[client-http-transport-http2]] +[[eg-client-http-transport-http2]] ==== HTTP/2 Transport The HTTP/2 transport can be configured in this way: @@ -108,12 +108,12 @@ include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=http2Tran `HTTP2Client` is the lower-level client that provides an API based on HTTP/2 concepts such as _sessions_, _streams_ and _frames_ that are specific to HTTP/2. -See link:#client-http2[the HTTP/2 client section] for more information. +See xref:eg-client-http2[the HTTP/2 client section] for more information. `HttpClientTransportOverHTTP2` uses `HTTP2Client` to format high-level semantic HTTP requests (like "GET resource /index.html") into the HTTP/2 specific format. -[[client-http-transport-fcgi]] +[[eg-client-http-transport-fcgi]] ==== FastCGI Transport The FastCGI transport can be configured in this way: @@ -130,7 +130,7 @@ FastCGI server such as https://en.wikipedia.org/wiki/PHP#PHPFPM[PHP-FPM] The FastCGI transport is primarily used by Jetty's link:#fastcgi[FastCGI support] to serve PHP pages (WordPress for example). -[[client-http-transport-dynamic]] +[[eg-client-http-transport-dynamic]] ==== Dynamic Transport The static transports work well if you know in advance the protocol you want diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http.adoc index 53b5833b1cf..25821e403c1 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http.adoc @@ -16,7 +16,7 @@ // ======================================================================== // -[[client-http]] +[[eg-client-http]] === HTTP Client include::client-http-intro.adoc[] diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http2/client-http2.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http2/client-http2.adoc index 4333955859e..546b61fa9fc 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http2/client-http2.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http2/client-http2.adoc @@ -16,14 +16,14 @@ // ======================================================================== // -[[client-http2]] +[[eg-client-http2]] === HTTP/2 Client Library In the vast majority of cases, client applications should use the generic, -high-level, link:#client-http[HTTP client library] that also provides +high-level, xref:eg-client-http[HTTP client library] that also provides HTTP/2 support via the pluggable -link:#client-http-transport-http2[HTTP/2 transport] or the -link:#client-http-transport-dynamic[dynamic transport]. +xref:eg-client-http-transport-http2[HTTP/2 transport] or the +xref:eg-client-http-transport-dynamic[dynamic transport]. The high-level HTTP library supports cookies, authentication, redirection, connection pooling and a number of other features that are absent in the @@ -33,7 +33,7 @@ The HTTP/2 client library has been designed for those applications that need low-level access to HTTP/2 features such as _sessions_, _streams_ and _frames_, and this is quite a rare use case. -[[client-http2-intro]] +[[eg-client-http2-intro]] ==== Introducing HTTP2Client The Maven artifact coordinates for the HTTP/2 client library are the following: @@ -80,7 +80,7 @@ A _stream_ has typically a very short life compared to the _session_: a _stream_ only exists for the duration of the request/response cycle and then disappears. -[[client-http2-flow-control]] +[[eg-client-http2-flow-control]] ===== HTTP/2 Flow Control The HTTP/2 protocol is _flow controlled_ (see @@ -132,12 +132,12 @@ have consumed data bytes as soon as possible, so that the implementation `WINDOW_UPDATE` frame) with the information to enlarge the flow control window, therefore reducing the possibility that sender stalls due to the flow control windows being reduced to `0`. -This is discussed in details in link:#client-http2-response[this section]. +This is discussed in details in xref:eg-client-http2-response[this section]. -[[client-http2-connect]] +[[eg-client-http2-connect]] ==== Connecting to the Server The first thing an application should do is to connect to the server and @@ -160,7 +160,7 @@ IMPORTANT: Applications must know in advance whether they want to connect to a clear-text or encrypted port, and pass the `SslContextFactory` parameter accordingly to the `connect(...)` method. -[[client-http2-configure]] +[[eg-client-http2-configure]] ===== Configuring the Session The `connect(...)` method takes a `Session.Listener` parameter. @@ -185,7 +185,7 @@ Once a `Session` has been established, the communication with the server happens by exchanging _frames_, as specified in the link:https://tools.ietf.org/html/rfc7540#section-4[HTTP/2 specification]. -[[client-http2-request]] +[[eg-client-http2-request]] ==== Sending a Request Sending an HTTP request to the server, and receiving a response, creates a @@ -206,7 +206,7 @@ include::../../{doc_code}/embedded/client/http2/HTTP2ClientDocs.java[tags=newStr Note how `Session.newStream(...)` takes a `Stream.Listener` parameter. This listener is notified of stream events originated by the server such as receiving `HEADERS` or `DATA` frames that are part of the response, discussed -in more details in the link:#client-http2-response[section below]. +in more details in the xref:eg-client-http2-response[section below]. Please refer to the `Stream.Listener` link:{JDURL}/org/eclipse/jetty/http2/api/Stream.Listener.html[javadocs] for the complete list of events. @@ -224,7 +224,7 @@ IMPORTANT: When sending two `DATA` frames consecutively, the second call to Use the `Callback` APIs or `CompletableFuture` APIs to ensure that the second `Stream.data(...)` call is performed when the first completed successfully. -[[client-http2-response]] +[[eg-client-http2-response]] ==== Receiving a Response Response events are delivered to the `Stream.Listener` passed to @@ -278,7 +278,7 @@ to call `Stream.demand(...)`. If they don't, the implementation will not deliver `DATA` frames and the application will stall threadlessly until an idle timeout fires to close the stream or the session. -[[client-http2-reset]] +[[eg-client-http2-reset]] ==== Resetting a Request or Response In HTTP/2, clients and servers have the ability to tell to the other peer that @@ -293,7 +293,7 @@ The `HTTP2Client` APIs allow client applications to send and receive this include::../../{doc_code}/embedded/client/http2/HTTP2ClientDocs.java[tags=reset] ---- -[[client-http2-push]] +[[eg-client-http2-push]] ==== Receiving HTTP/2 Pushes HTTP/2 servers have the ability to push resources related to a primary @@ -303,7 +303,7 @@ frame that contains the request URI and headers that a client would use to request explicitly that resource. Client applications can be configured to tell the server to never push -resources, see link:#client-http2-configure[this section]. +resources, see xref:eg-client-http2-configure[this section]. Client applications can listen to the push events, and act accordingly: From f57a170e999e48f2b964a5e62cde703435e2a5a4 Mon Sep 17 00:00:00 2001 From: Jan Bartel Date: Mon, 6 Apr 2020 14:30:38 +0200 Subject: [PATCH 049/101] Fix merge from 9.4 for #4737 --- .../jetty/plus/annotation/LifeCycleCallbackCollectionTest.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/jetty-plus/src/test/java/org/eclipse/jetty/plus/annotation/LifeCycleCallbackCollectionTest.java b/jetty-plus/src/test/java/org/eclipse/jetty/plus/annotation/LifeCycleCallbackCollectionTest.java index b6bf8e47275..c93c8ffd920 100644 --- a/jetty-plus/src/test/java/org/eclipse/jetty/plus/annotation/LifeCycleCallbackCollectionTest.java +++ b/jetty-plus/src/test/java/org/eclipse/jetty/plus/annotation/LifeCycleCallbackCollectionTest.java @@ -243,9 +243,6 @@ public class LifeCycleCallbackCollectionTest PreDestroyCallback pdBoth = new PreDestroyCallback(TestServlet.class, "predestroy"); collection.add(pdBoth); - //ensure we invoke the lifecyclecallbacks - context.getObjectFactory().addDecorator(new PlusDecorator(context)); - server.start(); assertEquals(4, TestServlet.postConstructCount); From be50caf5917e730ff48dfa439fc0203bef521feb Mon Sep 17 00:00:00 2001 From: olivier lamy Date: Tue, 24 Mar 2020 17:41:54 +1000 Subject: [PATCH 050/101] Issue #4690 Allow DistributionTester to work with log files Signed-off-by: olivier lamy --- .../distribution/DistributionTester.java | 101 ++++++++++++++++-- .../tests/distribution/DistributionTests.java | 47 ++++++++ .../src/test/resources/log4j2.xml | 39 +++++++ 3 files changed, 181 insertions(+), 6 deletions(-) create mode 100644 tests/test-distribution/src/test/resources/log4j2.xml diff --git a/tests/test-distribution/src/main/java/org/eclipse/jetty/tests/distribution/DistributionTester.java b/tests/test-distribution/src/main/java/org/eclipse/jetty/tests/distribution/DistributionTester.java index aae25684091..646be66fc7a 100644 --- a/tests/test-distribution/src/main/java/org/eclipse/jetty/tests/distribution/DistributionTester.java +++ b/tests/test-distribution/src/main/java/org/eclipse/jetty/tests/distribution/DistributionTester.java @@ -21,10 +21,13 @@ package org.eclipse.jetty.tests.distribution; import java.io.BufferedReader; import java.io.Closeable; import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; +import java.io.RandomAccessFile; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.nio.file.Files; @@ -164,7 +167,7 @@ public class DistributionTester pbCmd.directory(jettyBaseDir); Process process = pbCmd.start(); - return new Run(process); + return new Run(process, config); } /** @@ -370,6 +373,7 @@ public class DistributionTester private String jettyVersion; private String mavenLocalRepository = System.getProperty("user.home") + "/.m2/repository"; private Map mavenRemoteRepositories = new HashMap<>(); + private Path logFile; @Override public String toString() @@ -413,12 +417,17 @@ public class DistributionTester private final Process process; private final List consoleStreamers = new ArrayList<>(); private final Queue logs = new ConcurrentLinkedQueue<>(); + private LogFileStreamer logFileStreamer; - private Run(Process process) + private Run(Process process, DistributionTester.Config config) { this.process = process; consoleStreamers.add(startPump("STDOUT", process.getInputStream())); consoleStreamers.add(startPump("STDERR", process.getErrorStream())); + if (config.logFile != null) + { + logFileStreamer = new LogFileStreamer(config.logFile); + } } private ConsoleStreamer startPump(String mode, InputStream stream) @@ -441,7 +450,7 @@ public class DistributionTester { boolean result = process.waitFor(time, unit); if (result) - stopConsoleStreamers(); + stopStreamers(); return result; } @@ -462,7 +471,7 @@ public class DistributionTester public void stop() { process.destroy(); - stopConsoleStreamers(); + stopStreamers(); } /** @@ -471,12 +480,14 @@ public class DistributionTester public void destroy() { process.destroyForcibly(); - stopConsoleStreamers(); + stopStreamers(); } - private void stopConsoleStreamers() + private void stopStreamers() { consoleStreamers.forEach(ConsoleStreamer::stop); + if (logFileStreamer != null) + logFileStreamer.stop(); } @Override @@ -507,6 +518,38 @@ public class DistributionTester return false; } + /** + * Awaits the logs file to contain the given text, for the given amount of time. + * + * @param logFile the log file to test + * @param txt the text that must be present in the console logs + * @param time the time to wait + * @param unit the unit of time + * @return true if the text was found, false if the timeout elapsed + * @throws InterruptedException if the wait is interrupted + */ + public boolean awaitLogsFileFor(Path logFile, String txt, long time, TimeUnit unit) throws InterruptedException + { + LogFileStreamer logFileStreamer = new LogFileStreamer(logFile); + Thread thread = new Thread(logFileStreamer, "LogFileStreamer/" + logFile); + thread.start(); + try + { + long end = System.nanoTime() + unit.toNanos(time); + while (System.nanoTime() < end) + { + boolean result = logs.stream().anyMatch(s -> s.contains(txt)); + if (result) return true; + Thread.sleep(250); + } + return false; + } + finally + { + logFileStreamer.stop(); + } + } + /** * Simple streamer for the console output from a Process */ @@ -549,6 +592,52 @@ public class DistributionTester } } + private class LogFileStreamer implements Runnable + { + private RandomAccessFile inputFile; + private volatile boolean stop; + private final Path logFile; + + public LogFileStreamer(Path logFile) + { + this.logFile = logFile; + } + + @Override + public void run() + { + String currentLine; + long pos = 0; + while (!stop) + { + try + { + inputFile = new RandomAccessFile(logFile.toFile(), "r"); + inputFile.seek(pos); + if ((currentLine = inputFile.readLine()) != null) + { + logs.add(currentLine); + } + pos = inputFile.getFilePointer(); + } + catch (IOException e) + { + //ignore + } + finally + { + IO.close(inputFile); + } + } + } + + public void stop() + { + stop = true; + IO.close(inputFile); + } + } + public Queue getLogs() { return logs; diff --git a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java index b1225195332..ee8911be9d0 100644 --- a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java +++ b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java @@ -21,9 +21,11 @@ package org.eclipse.jetty.tests.distribution; import java.io.BufferedWriter; import java.io.File; import java.nio.charset.StandardCharsets; +import java.nio.file.CopyOption; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; import java.nio.file.StandardOpenOption; import java.util.concurrent.TimeUnit; @@ -453,4 +455,49 @@ public class DistributionTests extends AbstractDistributionTest } } } + + @Test + public void testStartStopLog4j2Modules() throws Exception + { + Path jettyBase = Files.createTempDirectory("jetty_base"); + + String jettyVersion = System.getProperty("jettyVersion"); + DistributionTester distribution = DistributionTester.Builder.newInstance() // + .jettyVersion(jettyVersion) // + .jettyBase(jettyBase) // + .mavenLocalRepository(System.getProperty("mavenRepoPath")) // + .build(); + + String[] args = { + "--create-startd", + "--approve-all-licenses", + "--add-to-start=http,logging-log4j2" + }; + + try (DistributionTester.Run run1 = distribution.start(args)) + { + assertTrue(run1.awaitFor(5, TimeUnit.SECONDS)); + assertEquals(0, run1.getExitValue()); + + Files.copy(Paths.get("src/test/resources/log4j2.xml"), // + Paths.get(jettyBase.toString(),"resources").resolve("log4j2.xml"), // + StandardCopyOption.REPLACE_EXISTING); + + int port = distribution.freePort(); + try (DistributionTester.Run run2 = distribution.start("jetty.http.port=" + port)) + { + assertTrue(run2.awaitLogsFileFor( + jettyBase.resolve("logs").resolve("jetty.log"), // + "Started Server@", 10, TimeUnit.SECONDS)); + + startHttpClient(); + ContentResponse response = client.GET("http://localhost:" + port); + assertEquals(HttpStatus.NOT_FOUND_404, response.getStatus()); + + run2.stop(); + assertTrue(run2.awaitFor(5, TimeUnit.SECONDS)); + } + } + } + } diff --git a/tests/test-distribution/src/test/resources/log4j2.xml b/tests/test-distribution/src/test/resources/log4j2.xml new file mode 100644 index 00000000000..468c1d25f06 --- /dev/null +++ b/tests/test-distribution/src/test/resources/log4j2.xml @@ -0,0 +1,39 @@ + + + + + ${sys:jetty.logging.dir:-logs} + + + + + + %d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n + + + + + + + %d [%t] %-5p %c %x - %m%n + + + + + + + + + + + + + + + + + + From a189d13fd548b2acb626c501148d5364a2a085a7 Mon Sep 17 00:00:00 2001 From: olivier lamy Date: Tue, 24 Mar 2020 19:17:14 +1000 Subject: [PATCH 051/101] cleanup Signed-off-by: olivier lamy --- .../distribution/DistributionTester.java | 21 ++++++------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/tests/test-distribution/src/main/java/org/eclipse/jetty/tests/distribution/DistributionTester.java b/tests/test-distribution/src/main/java/org/eclipse/jetty/tests/distribution/DistributionTester.java index 646be66fc7a..eb9a14df5d0 100644 --- a/tests/test-distribution/src/main/java/org/eclipse/jetty/tests/distribution/DistributionTester.java +++ b/tests/test-distribution/src/main/java/org/eclipse/jetty/tests/distribution/DistributionTester.java @@ -21,8 +21,6 @@ package org.eclipse.jetty.tests.distribution; import java.io.BufferedReader; import java.io.Closeable; import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -167,7 +165,7 @@ public class DistributionTester pbCmd.directory(jettyBaseDir); Process process = pbCmd.start(); - return new Run(process, config); + return new Run(process); } /** @@ -417,17 +415,12 @@ public class DistributionTester private final Process process; private final List consoleStreamers = new ArrayList<>(); private final Queue logs = new ConcurrentLinkedQueue<>(); - private LogFileStreamer logFileStreamer; - private Run(Process process, DistributionTester.Config config) + private Run(Process process) { this.process = process; consoleStreamers.add(startPump("STDOUT", process.getInputStream())); consoleStreamers.add(startPump("STDERR", process.getErrorStream())); - if (config.logFile != null) - { - logFileStreamer = new LogFileStreamer(config.logFile); - } } private ConsoleStreamer startPump(String mode, InputStream stream) @@ -450,7 +443,7 @@ public class DistributionTester { boolean result = process.waitFor(time, unit); if (result) - stopStreamers(); + stopConsoleStreamers(); return result; } @@ -471,7 +464,7 @@ public class DistributionTester public void stop() { process.destroy(); - stopStreamers(); + stopConsoleStreamers(); } /** @@ -480,14 +473,12 @@ public class DistributionTester public void destroy() { process.destroyForcibly(); - stopStreamers(); + stopConsoleStreamers(); } - private void stopStreamers() + private void stopConsoleStreamers() { consoleStreamers.forEach(ConsoleStreamer::stop); - if (logFileStreamer != null) - logFileStreamer.stop(); } @Override From 625dfd1a4d0b82b6174452b3c669e723388eebe5 Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Tue, 7 Apr 2020 12:18:29 +1000 Subject: [PATCH 052/101] Issue #4747 - Fix WS path params to work within a context path Signed-off-by: Lachlan Roberts --- .../client/internal/JavaxClientUpgradeRequest.java | 6 ++++++ .../JavaxWebSocketClientFrameHandlerFactory.java | 2 +- .../common/JavaxWebSocketFrameHandlerFactory.java | 7 +++---- .../jetty/websocket/javax/common/UpgradeRequest.java | 11 ++++++++--- .../javax/common/UpgradeRequestAdapter.java | 12 ++++++++++-- .../websocket/javax/common/AbstractSessionTest.java | 2 +- .../javax/common/DummyFrameHandlerFactory.java | 2 +- .../server/internal/JavaxServerUpgradeRequest.java | 8 +++++++- .../jetty/websocket/javax/tests/PathParamTest.java | 6 +++--- ...WebSocketFrameHandlerOnMessageTextStreamTest.java | 3 ++- .../websocket/servlet/ServletUpgradeRequest.java | 9 +++++++++ 11 files changed, 51 insertions(+), 17 deletions(-) diff --git a/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/internal/JavaxClientUpgradeRequest.java b/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/internal/JavaxClientUpgradeRequest.java index b2fd4d5ae52..d2c8856db66 100644 --- a/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/internal/JavaxClientUpgradeRequest.java +++ b/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/internal/JavaxClientUpgradeRequest.java @@ -64,4 +64,10 @@ public class JavaxClientUpgradeRequest extends ClientUpgradeRequest implements U { return getURI(); } + + @Override + public String getPathInContext() + { + throw new UnsupportedOperationException(); + } } diff --git a/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/internal/JavaxWebSocketClientFrameHandlerFactory.java b/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/internal/JavaxWebSocketClientFrameHandlerFactory.java index 39f3221b877..5a1b5252e0e 100644 --- a/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/internal/JavaxWebSocketClientFrameHandlerFactory.java +++ b/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/internal/JavaxWebSocketClientFrameHandlerFactory.java @@ -40,7 +40,7 @@ public class JavaxWebSocketClientFrameHandlerFactory extends JavaxWebSocketFrame } @Override - public EndpointConfig newDefaultEndpointConfig(Class endpointClass, String path) + public EndpointConfig newDefaultEndpointConfig(Class endpointClass) { return new BasicClientEndpointConfig(); } diff --git a/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketFrameHandlerFactory.java b/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketFrameHandlerFactory.java index 5eeea251808..eb51e98d4fa 100644 --- a/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketFrameHandlerFactory.java +++ b/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketFrameHandlerFactory.java @@ -144,7 +144,7 @@ public abstract class JavaxWebSocketFrameHandlerFactory public abstract JavaxWebSocketFrameHandlerMetadata getMetadata(Class endpointClass, EndpointConfig endpointConfig); - public abstract EndpointConfig newDefaultEndpointConfig(Class endpointClass, String path); + public abstract EndpointConfig newDefaultEndpointConfig(Class endpointClass); public JavaxWebSocketFrameHandler newJavaxWebSocketFrameHandler(Object endpointInstance, UpgradeRequest upgradeRequest) { @@ -160,8 +160,7 @@ public abstract class JavaxWebSocketFrameHandlerFactory else { endpoint = endpointInstance; - String path = (upgradeRequest.getRequestURI() == null) ? null : upgradeRequest.getRequestURI().getPath(); - config = newDefaultEndpointConfig(endpoint.getClass(), path); + config = newDefaultEndpointConfig(endpoint.getClass()); } JavaxWebSocketFrameHandlerMetadata metadata = getMetadata(endpoint.getClass(), config); @@ -180,7 +179,7 @@ public abstract class JavaxWebSocketFrameHandlerFactory if (templatePathSpec != null) { String[] namedVariables = templatePathSpec.getVariables(); - Map pathParams = templatePathSpec.getPathParams(upgradeRequest.getRequestURI().getRawPath()); + Map pathParams = templatePathSpec.getPathParams(upgradeRequest.getPathInContext()); // Handle parameterized @PathParam entries openHandle = bindTemplateVariables(openHandle, namedVariables, pathParams); diff --git a/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/UpgradeRequest.java b/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/UpgradeRequest.java index 90de5c9c492..5764910685a 100644 --- a/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/UpgradeRequest.java +++ b/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/UpgradeRequest.java @@ -31,9 +31,14 @@ public interface UpgradeRequest Principal getUserPrincipal(); /** - * For obtaining {@link javax.websocket.server.PathParam} values from Request URI path - * - * @return the request URI + * @return the full URI of this request. */ URI getRequestURI(); + + /** + * For obtaining {@link javax.websocket.server.PathParam} values from the Request context path. + * + * @return the path in Context. + */ + String getPathInContext(); } diff --git a/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/UpgradeRequestAdapter.java b/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/UpgradeRequestAdapter.java index 4ab2345d669..42e2f94e7fa 100644 --- a/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/UpgradeRequestAdapter.java +++ b/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/UpgradeRequestAdapter.java @@ -24,16 +24,18 @@ import java.security.Principal; public class UpgradeRequestAdapter implements UpgradeRequest { private final URI requestURI; + private final String pathInContext; public UpgradeRequestAdapter() { /* anonymous, no requestURI, upgrade request */ - this(null); + this(null, null); } - public UpgradeRequestAdapter(URI uri) + public UpgradeRequestAdapter(URI uri, String pathInContext) { this.requestURI = uri; + this.pathInContext = pathInContext; } @Override @@ -47,4 +49,10 @@ public class UpgradeRequestAdapter implements UpgradeRequest { return requestURI; } + + @Override + public String getPathInContext() + { + return pathInContext; + } } diff --git a/jetty-websocket/websocket-javax-common/src/test/java/org/eclipse/jetty/websocket/javax/common/AbstractSessionTest.java b/jetty-websocket/websocket-javax-common/src/test/java/org/eclipse/jetty/websocket/javax/common/AbstractSessionTest.java index e425420c99b..c96927c7dc6 100644 --- a/jetty-websocket/websocket-javax-common/src/test/java/org/eclipse/jetty/websocket/javax/common/AbstractSessionTest.java +++ b/jetty-websocket/websocket-javax-common/src/test/java/org/eclipse/jetty/websocket/javax/common/AbstractSessionTest.java @@ -41,7 +41,7 @@ public abstract class AbstractSessionTest JavaxWebSocketFrameHandler frameHandler = container.newFrameHandler(websocketPojo, upgradeRequest); CoreSession coreSession = new CoreSession.Empty(); session = new JavaxWebSocketSession(container, coreSession, frameHandler, container.getFrameHandlerFactory() - .newDefaultEndpointConfig(websocketPojo.getClass(), null)); + .newDefaultEndpointConfig(websocketPojo.getClass())); } @AfterAll diff --git a/jetty-websocket/websocket-javax-common/src/test/java/org/eclipse/jetty/websocket/javax/common/DummyFrameHandlerFactory.java b/jetty-websocket/websocket-javax-common/src/test/java/org/eclipse/jetty/websocket/javax/common/DummyFrameHandlerFactory.java index d7468d6a2d0..12d00d0c67d 100644 --- a/jetty-websocket/websocket-javax-common/src/test/java/org/eclipse/jetty/websocket/javax/common/DummyFrameHandlerFactory.java +++ b/jetty-websocket/websocket-javax-common/src/test/java/org/eclipse/jetty/websocket/javax/common/DummyFrameHandlerFactory.java @@ -33,7 +33,7 @@ public class DummyFrameHandlerFactory extends JavaxWebSocketFrameHandlerFactory } @Override - public EndpointConfig newDefaultEndpointConfig(Class endpointClass, String path) + public EndpointConfig newDefaultEndpointConfig(Class endpointClass) { return ClientEndpointConfig.Builder.create().build(); } diff --git a/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JavaxServerUpgradeRequest.java b/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JavaxServerUpgradeRequest.java index 9b4d4d55939..69624723ec8 100644 --- a/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JavaxServerUpgradeRequest.java +++ b/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JavaxServerUpgradeRequest.java @@ -42,6 +42,12 @@ public class JavaxServerUpgradeRequest implements UpgradeRequest @Override public URI getRequestURI() { - return this.servletRequest.getRequestURI(); + return servletRequest.getRequestURI(); + } + + @Override + public String getPathInContext() + { + return servletRequest.getPathInContext(); } } diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/PathParamTest.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/PathParamTest.java index 2b3e28eeff8..b67e44a462a 100644 --- a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/PathParamTest.java +++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/PathParamTest.java @@ -53,7 +53,7 @@ public class PathParamTest _server.addConnector(_connector); _context = new ServletContextHandler(ServletContextHandler.SESSIONS); - _context.setContextPath("/"); + _context.setContextPath("/context"); _server.setHandler(_context); JavaxWebSocketServletContainerInitializer.configure(_context, (context, container) -> @@ -68,7 +68,7 @@ public class PathParamTest _server.stop(); } - @ServerEndpoint("/pathparam/echo/{name}") + @ServerEndpoint("/pathParam/echo/{name}") public static class EchoParamSocket { private Session session; @@ -92,7 +92,7 @@ public class PathParamTest WebSocketContainer container = ContainerProvider.getWebSocketContainer(); EventSocket clientEndpoint = new EventSocket(); - URI serverUri = URI.create("ws://localhost:" + _connector.getLocalPort() + "/pathparam/echo/myParam"); + URI serverUri = URI.create("ws://localhost:" + _connector.getLocalPort() + "/context/pathParam/echo/myParam"); Session session = container.connectToServer(clientEndpoint, serverUri); session.getBasicRemote().sendText("echo"); diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/JavaxWebSocketFrameHandlerOnMessageTextStreamTest.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/JavaxWebSocketFrameHandlerOnMessageTextStreamTest.java index 78117206672..3b1b6b5abe4 100644 --- a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/JavaxWebSocketFrameHandlerOnMessageTextStreamTest.java +++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/JavaxWebSocketFrameHandlerOnMessageTextStreamTest.java @@ -46,7 +46,8 @@ public class JavaxWebSocketFrameHandlerOnMessageTextStreamTest extends AbstractJ @SuppressWarnings("Duplicates") private T performOnMessageInvocation(T socket, Consumer func) throws Exception { - UpgradeRequest request = new UpgradeRequestAdapter(URI.create("http://localhost:8080/msg/foo")); + URI uri = URI.create("http://localhost:8080/msg/foo"); + UpgradeRequest request = new UpgradeRequestAdapter(uri, uri.getPath()); // Establish endpoint function JavaxWebSocketFrameHandler frameHandler = container.newFrameHandler(socket, request); diff --git a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/ServletUpgradeRequest.java b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/ServletUpgradeRequest.java index ef81155a00b..f327a424a95 100644 --- a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/ServletUpgradeRequest.java +++ b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/ServletUpgradeRequest.java @@ -38,6 +38,7 @@ import javax.servlet.http.HttpSession; import org.eclipse.jetty.http.BadMessageException; import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.websocket.core.ExtensionConfig; import org.eclipse.jetty.websocket.core.WebSocketConstants; import org.eclipse.jetty.websocket.core.server.Negotiation; @@ -314,6 +315,14 @@ public class ServletUpgradeRequest return requestURI; } + /** + * @return the path within the context, combination of the ServletPath with the PathInfo. + */ + public String getPathInContext() + { + return URIUtil.addPaths(request.getServletPath(), request.getPathInfo()); + } + /** * @param name Attribute name * @return Attribute value or null From 3d40e0e25d4710760fd071556e6e7912d5ad3ede Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Tue, 7 Apr 2020 15:06:25 +1000 Subject: [PATCH 053/101] Issue #4747 - correctly copy headers in websocket JsrUpgradeListener Headers with the same name may not have been copied properly for header values inspected and modified with a Configurator. Signed-off-by: Lachlan Roberts --- .../client/internal/JsrUpgradeListener.java | 26 +++++++------------ 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/internal/JsrUpgradeListener.java b/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/internal/JsrUpgradeListener.java index 8ce0df47cdb..acc50088c95 100644 --- a/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/internal/JsrUpgradeListener.java +++ b/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/internal/JsrUpgradeListener.java @@ -19,10 +19,10 @@ package org.eclipse.jetty.websocket.javax.client.internal; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.stream.Stream; import javax.websocket.ClientEndpointConfig.Configurator; import javax.websocket.HandshakeResponse; @@ -44,18 +44,15 @@ public class JsrUpgradeListener implements UpgradeListener public void onHandshakeRequest(HttpRequest request) { if (configurator == null) - { return; - } HttpFields fields = request.getHeaders(); - Map> originalHeaders = new HashMap<>(); - fields.forEach((field) -> + fields.forEach(field -> { - List values = new ArrayList<>(); - Stream.of(field.getValues()).forEach((val) -> values.add(val)); - originalHeaders.put(field.getName(), values); + originalHeaders.putIfAbsent(field.getName(), new ArrayList<>()); + List values = originalHeaders.get(field.getName()); + Collections.addAll(values, field.getValues()); }); // Give headers to configurator @@ -63,26 +60,23 @@ public class JsrUpgradeListener implements UpgradeListener // Reset headers on HttpRequest per configurator fields.clear(); - originalHeaders.forEach((name, values) -> fields.put(name, values)); + originalHeaders.forEach(fields::put); } @Override public void onHandshakeResponse(HttpRequest request, HttpResponse response) { if (configurator == null) - { return; - } HandshakeResponse handshakeResponse = () -> { - HttpFields fields = response.getHeaders(); Map> ret = new HashMap<>(); - fields.forEach((field) -> + response.getHeaders().forEach(field -> { - List values = new ArrayList<>(); - Stream.of(field.getValues()).forEach((val) -> values.add(val)); - ret.put(field.getName(), values); + ret.putIfAbsent(field.getName(), new ArrayList<>()); + List values = ret.get(field.getName()); + Collections.addAll(values, field.getValues()); }); return ret; }; From 4e3c0c8cd7c05fb50774d5abda18bcca0181a7bf Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Tue, 7 Apr 2020 12:04:24 +0200 Subject: [PATCH 054/101] Fixes #4751 - Refresh NetworkTraffic* classes. Introduced NetworkTrafficSocketChannelEndPoint to replace NetworkTrafficSelectChannelEndPoint, now deprecated. Code and javadocs cleanup. Moved the tests to jetty-client so that also the client is tested. Signed-off-by: Simone Bordet --- .../client/NetworkTrafficListenerTest.java | 526 ++++++++++++++++++ .../jetty/io/NetworkTrafficListener.java | 20 +- .../NetworkTrafficSelectChannelEndPoint.java | 127 +---- .../NetworkTrafficSocketChannelEndPoint.java | 136 +++++ .../server/NetworkTrafficServerConnector.java | 8 +- .../server/NetworkTrafficListenerTest.java | 484 ---------------- 6 files changed, 687 insertions(+), 614 deletions(-) create mode 100644 jetty-client/src/test/java/org/eclipse/jetty/client/NetworkTrafficListenerTest.java create mode 100644 jetty-io/src/main/java/org/eclipse/jetty/io/NetworkTrafficSocketChannelEndPoint.java delete mode 100644 jetty-server/src/test/java/org/eclipse/jetty/server/NetworkTrafficListenerTest.java diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/NetworkTrafficListenerTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/NetworkTrafficListenerTest.java new file mode 100644 index 00000000000..cc93df28d29 --- /dev/null +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/NetworkTrafficListenerTest.java @@ -0,0 +1,526 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.client; + +import java.io.IOException; +import java.io.InputStream; +import java.net.Socket; +import java.nio.ByteBuffer; +import java.nio.channels.SelectableChannel; +import java.nio.channels.SelectionKey; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP; +import org.eclipse.jetty.client.util.FormContentProvider; +import org.eclipse.jetty.client.util.StringContentProvider; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpHeaderValue; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.ManagedSelector; +import org.eclipse.jetty.io.NetworkTrafficListener; +import org.eclipse.jetty.io.NetworkTrafficSocketChannelEndPoint; +import org.eclipse.jetty.io.SelectorManager; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.NetworkTrafficServerConnector; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.Fields; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class NetworkTrafficListenerTest +{ + private static final String END_OF_CONTENT = "~"; + + private Server server; + private NetworkTrafficServerConnector connector; + private NetworkTrafficHttpClient client; + + private void start(Handler handler) throws Exception + { + startServer(handler); + startClient(); + } + + private void startServer(Handler handler) throws Exception + { + server = new Server(); + connector = new NetworkTrafficServerConnector(server); + connector.getConnectionFactory(HttpConfiguration.ConnectionFactory.class).getHttpConfiguration().setSendDateHeader(false); + connector.getConnectionFactory(HttpConfiguration.ConnectionFactory.class).getHttpConfiguration().setSendServerVersion(false); + server.addConnector(connector); + server.setHandler(handler); + server.start(); + } + + private void startClient() throws Exception + { + client = new NetworkTrafficHttpClient(new ArrayList<>()); + client.start(); + } + + @AfterEach + public void dispose() throws Exception + { + if (client != null) + client.stop(); + if (server != null) + server.stop(); + } + + @Test + public void testOpenedClosedAreInvoked() throws Exception + { + startServer(null); + + CountDownLatch openedLatch = new CountDownLatch(1); + CountDownLatch closedLatch = new CountDownLatch(1); + connector.addNetworkTrafficListener(new NetworkTrafficListener() + { + public volatile Socket socket; + + @Override + public void opened(Socket socket) + { + this.socket = socket; + openedLatch.countDown(); + } + + @Override + public void closed(Socket socket) + { + if (this.socket == socket) + closedLatch.countDown(); + } + }); + int port = connector.getLocalPort(); + + // Connect to the server + try (Socket ignored = new Socket("localhost", port)) + { + assertTrue(openedLatch.await(10, TimeUnit.SECONDS)); + } + assertTrue(closedLatch.await(10, TimeUnit.SECONDS)); + } + + @Test + public void testTrafficWithNoResponseContentOnNonPersistentConnection() throws Exception + { + start(new AbstractHandler() + { + @Override + public void handle(String uri, Request request, HttpServletRequest servletRequest, HttpServletResponse servletResponse) + { + request.setHandled(true); + } + }); + + AtomicReference serverIncoming = new AtomicReference<>(""); + CountDownLatch serverIncomingLatch = new CountDownLatch(1); + AtomicReference serverOutgoing = new AtomicReference<>(""); + CountDownLatch serverOutgoingLatch = new CountDownLatch(1); + connector.addNetworkTrafficListener(new NetworkTrafficListener() + { + @Override + public void incoming(Socket socket, ByteBuffer bytes) + { + serverIncoming.set(serverIncoming.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8)); + serverIncomingLatch.countDown(); + } + + @Override + public void outgoing(Socket socket, ByteBuffer bytes) + { + serverOutgoing.set(serverOutgoing.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8)); + serverOutgoingLatch.countDown(); + } + }); + + AtomicReference clientIncoming = new AtomicReference<>(""); + CountDownLatch clientIncomingLatch = new CountDownLatch(1); + AtomicReference clientOutgoing = new AtomicReference<>(""); + CountDownLatch clientOutgoingLatch = new CountDownLatch(1); + client.listeners.add(new NetworkTrafficListener() + { + @Override + public void outgoing(Socket socket, ByteBuffer bytes) + { + clientOutgoing.set(clientOutgoing.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8)); + clientOutgoingLatch.countDown(); + } + + @Override + public void incoming(Socket socket, ByteBuffer bytes) + { + clientIncoming.set(clientIncoming.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8)); + clientIncomingLatch.countDown(); + } + }); + + ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) + .header(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString()) + .send(); + assertEquals(HttpStatus.OK_200, response.getStatus()); + + assertTrue(clientOutgoingLatch.await(1, TimeUnit.SECONDS)); + assertTrue(serverIncomingLatch.await(1, TimeUnit.SECONDS)); + assertTrue(serverOutgoingLatch.await(1, TimeUnit.SECONDS)); + assertTrue(clientIncomingLatch.await(1, TimeUnit.SECONDS)); + assertEquals(clientOutgoing.get(), serverIncoming.get()); + assertEquals(serverOutgoing.get(), clientIncoming.get()); + } + + @Test + public void testTrafficWithResponseContentOnPersistentConnection() throws Exception + { + String responseContent = "response_content" + END_OF_CONTENT; + start(new AbstractHandler() + { + @Override + public void handle(String uri, Request request, HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws IOException + { + request.setHandled(true); + ServletOutputStream output = servletResponse.getOutputStream(); + output.write(responseContent.getBytes(StandardCharsets.UTF_8)); + } + }); + + AtomicReference serverIncoming = new AtomicReference<>(""); + CountDownLatch serverIncomingLatch = new CountDownLatch(1); + AtomicReference serverOutgoing = new AtomicReference<>(""); + CountDownLatch serverOutgoingLatch = new CountDownLatch(1); + connector.addNetworkTrafficListener(new NetworkTrafficListener() + { + @Override + public void incoming(Socket socket, ByteBuffer bytes) + { + serverIncoming.set(serverIncoming.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8)); + serverIncomingLatch.countDown(); + } + + @Override + public void outgoing(Socket socket, ByteBuffer bytes) + { + serverOutgoing.set(serverOutgoing.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8)); + serverOutgoingLatch.countDown(); + } + }); + + AtomicReference clientIncoming = new AtomicReference<>(""); + CountDownLatch clientIncomingLatch = new CountDownLatch(1); + AtomicReference clientOutgoing = new AtomicReference<>(""); + CountDownLatch clientOutgoingLatch = new CountDownLatch(1); + client.listeners.add(new NetworkTrafficListener() + { + @Override + public void outgoing(Socket socket, ByteBuffer bytes) + { + clientOutgoing.set(clientOutgoing.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8)); + clientOutgoingLatch.countDown(); + } + + @Override + public void incoming(Socket socket, ByteBuffer bytes) + { + clientIncoming.set(clientIncoming.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8)); + clientIncomingLatch.countDown(); + } + }); + + ContentResponse response = client.newRequest("localhost", connector.getLocalPort()).send(); + assertEquals(HttpStatus.OK_200, response.getStatus()); + assertEquals(responseContent, response.getContentAsString()); + + assertTrue(clientOutgoingLatch.await(1, TimeUnit.SECONDS)); + assertTrue(serverIncomingLatch.await(1, TimeUnit.SECONDS)); + assertTrue(serverOutgoingLatch.await(1, TimeUnit.SECONDS)); + assertTrue(clientIncomingLatch.await(1, TimeUnit.SECONDS)); + assertEquals(clientOutgoing.get(), serverIncoming.get()); + assertEquals(serverOutgoing.get(), clientIncoming.get()); + } + + @Test + public void testTrafficWithResponseContentChunkedOnPersistentConnection() throws Exception + { + String responseContent = "response_content"; + String responseChunk1 = responseContent.substring(0, responseContent.length() / 2); + String responseChunk2 = responseContent.substring(responseContent.length() / 2); + start(new AbstractHandler() + { + @Override + public void handle(String uri, Request request, HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws IOException + { + request.setHandled(true); + ServletOutputStream output = servletResponse.getOutputStream(); + output.write(responseChunk1.getBytes(StandardCharsets.UTF_8)); + output.flush(); + output.write(responseChunk2.getBytes(StandardCharsets.UTF_8)); + output.flush(); + } + }); + + AtomicReference serverIncoming = new AtomicReference<>(""); + CountDownLatch serverIncomingLatch = new CountDownLatch(1); + AtomicReference serverOutgoing = new AtomicReference<>(""); + CountDownLatch serverOutgoingLatch = new CountDownLatch(1); + connector.addNetworkTrafficListener(new NetworkTrafficListener() + { + @Override + public void incoming(Socket socket, ByteBuffer bytes) + { + serverIncoming.set(serverIncoming.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8)); + serverIncomingLatch.countDown(); + } + + @Override + public void outgoing(Socket socket, ByteBuffer bytes) + { + serverOutgoing.set(serverOutgoing.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8)); + if (serverOutgoing.get().endsWith("\r\n0\r\n\r\n")) + serverOutgoingLatch.countDown(); + } + }); + + AtomicReference clientIncoming = new AtomicReference<>(""); + CountDownLatch clientIncomingLatch = new CountDownLatch(1); + AtomicReference clientOutgoing = new AtomicReference<>(""); + CountDownLatch clientOutgoingLatch = new CountDownLatch(1); + client.listeners.add(new NetworkTrafficListener() + { + @Override + public void outgoing(Socket socket, ByteBuffer bytes) + { + clientOutgoing.set(clientOutgoing.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8)); + clientOutgoingLatch.countDown(); + } + + @Override + public void incoming(Socket socket, ByteBuffer bytes) + { + clientIncoming.set(clientIncoming.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8)); + if (clientIncoming.get().endsWith("\r\n0\r\n\r\n")) + clientIncomingLatch.countDown(); + } + }); + + ContentResponse response = client.newRequest("localhost", connector.getLocalPort()).send(); + assertEquals(HttpStatus.OK_200, response.getStatus()); + + assertTrue(clientOutgoingLatch.await(1, TimeUnit.SECONDS)); + assertTrue(serverIncomingLatch.await(1, TimeUnit.SECONDS)); + assertTrue(serverOutgoingLatch.await(1, TimeUnit.SECONDS)); + assertTrue(clientIncomingLatch.await(1, TimeUnit.SECONDS)); + assertEquals(clientOutgoing.get(), serverIncoming.get()); + assertEquals(serverOutgoing.get(), clientIncoming.get()); + } + + @Test + public void testTrafficWithRequestContentWithResponseRedirectOnPersistentConnection() throws Exception + { + String location = "/redirect"; + start(new AbstractHandler() + { + @Override + public void handle(String uri, Request request, HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws IOException + { + request.setHandled(true); + servletResponse.sendRedirect(location); + } + }); + + AtomicReference serverIncoming = new AtomicReference<>(""); + CountDownLatch serverIncomingLatch = new CountDownLatch(1); + AtomicReference serverOutgoing = new AtomicReference<>(""); + CountDownLatch serverOutgoingLatch = new CountDownLatch(1); + connector.addNetworkTrafficListener(new NetworkTrafficListener() + { + @Override + public void incoming(Socket socket, ByteBuffer bytes) + { + serverIncoming.set(serverIncoming.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8)); + serverIncomingLatch.countDown(); + } + + @Override + public void outgoing(Socket socket, ByteBuffer bytes) + { + serverOutgoing.set(serverOutgoing.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8)); + serverOutgoingLatch.countDown(); + } + }); + + AtomicReference clientIncoming = new AtomicReference<>(""); + CountDownLatch clientIncomingLatch = new CountDownLatch(1); + AtomicReference clientOutgoing = new AtomicReference<>(""); + CountDownLatch clientOutgoingLatch = new CountDownLatch(1); + client.listeners.add(new NetworkTrafficListener() + { + @Override + public void outgoing(Socket socket, ByteBuffer bytes) + { + clientOutgoing.set(clientOutgoing.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8)); + clientOutgoingLatch.countDown(); + } + + @Override + public void incoming(Socket socket, ByteBuffer bytes) + { + clientIncoming.set(clientIncoming.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8)); + clientIncomingLatch.countDown(); + } + }); + + client.setFollowRedirects(false); + Fields fields = new Fields(); + fields.put("a", "1"); + fields.put("b", "2"); + ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) + .content(new FormContentProvider(fields)) + .send(); + assertEquals(HttpStatus.FOUND_302, response.getStatus()); + + assertTrue(clientOutgoingLatch.await(1, TimeUnit.SECONDS)); + assertTrue(serverIncomingLatch.await(1, TimeUnit.SECONDS)); + assertTrue(serverOutgoingLatch.await(1, TimeUnit.SECONDS)); + assertTrue(clientIncomingLatch.await(1, TimeUnit.SECONDS)); + assertEquals(clientOutgoing.get(), serverIncoming.get()); + assertEquals(serverOutgoing.get(), clientIncoming.get()); + } + + @Test + public void testTrafficWithBigRequestContentOnPersistentConnection() throws Exception + { + start(new AbstractHandler() + { + @Override + public void handle(String uri, Request request, HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws IOException + { + // Read and discard the request body to make the test more + // reliable, otherwise there is a race between request body + // upload and response download + InputStream input = servletRequest.getInputStream(); + byte[] buffer = new byte[4096]; + while (true) + { + int read = input.read(buffer); + if (read < 0) + break; + } + request.setHandled(true); + } + }); + + AtomicReference serverIncoming = new AtomicReference<>(""); + AtomicReference serverOutgoing = new AtomicReference<>(""); + CountDownLatch serverOutgoingLatch = new CountDownLatch(1); + connector.addNetworkTrafficListener(new NetworkTrafficListener() + { + @Override + public void incoming(Socket socket, ByteBuffer bytes) + { + serverIncoming.set(serverIncoming.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8)); + } + + @Override + public void outgoing(Socket socket, ByteBuffer bytes) + { + serverOutgoing.set(serverOutgoing.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8)); + serverOutgoingLatch.countDown(); + } + }); + + AtomicReference clientIncoming = new AtomicReference<>(""); + CountDownLatch clientIncomingLatch = new CountDownLatch(1); + AtomicReference clientOutgoing = new AtomicReference<>(""); + client.listeners.add(new NetworkTrafficListener() + { + @Override + public void outgoing(Socket socket, ByteBuffer bytes) + { + clientOutgoing.set(clientOutgoing.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8)); + } + + @Override + public void incoming(Socket socket, ByteBuffer bytes) + { + clientIncoming.set(clientIncoming.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8)); + clientIncomingLatch.countDown(); + } + }); + + // Generate a large request content. + String requestContent = "0123456789ABCDEF"; + for (int i = 0; i < 16; ++i) + { + requestContent += requestContent; + } + + ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) + .content(new StringContentProvider(requestContent)) + .send(); + assertEquals(HttpStatus.OK_200, response.getStatus()); + + assertTrue(serverOutgoingLatch.await(1, TimeUnit.SECONDS)); + assertTrue(clientIncomingLatch.await(1, TimeUnit.SECONDS)); + assertEquals(clientOutgoing.get(), serverIncoming.get()); + assertTrue(clientOutgoing.get().length() > requestContent.length()); + assertEquals(serverOutgoing.get(), clientIncoming.get()); + } + + private static class NetworkTrafficHttpClient extends HttpClient + { + private final List listeners; + + private NetworkTrafficHttpClient(List listeners) + { + super(new HttpClientTransportOverHTTP() + { + @Override + protected SelectorManager newSelectorManager(HttpClient client) + { + return new AbstractConnectorHttpClientTransport.ClientSelectorManager(client, getSelectors()) + { + @Override + protected EndPoint newEndPoint(SelectableChannel channel, ManagedSelector selector, SelectionKey key) + { + return new NetworkTrafficSocketChannelEndPoint(channel, selector, key, getScheduler(), client.getIdleTimeout(), listeners); + } + }; + } + }, null); + this.listeners = listeners; + } + } +} diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/NetworkTrafficListener.java b/jetty-io/src/main/java/org/eclipse/jetty/io/NetworkTrafficListener.java index 92075bda28f..f2897ba2398 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/NetworkTrafficListener.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/NetworkTrafficListener.java @@ -24,7 +24,7 @@ import java.nio.ByteBuffer; /** *

    A listener for raw network traffic within Jetty.

    *

    {@link NetworkTrafficListener}s can be installed in a - * org.eclipse.jetty.server.nio.NetworkTrafficSelectChannelConnector, + * {@code org.eclipse.jetty.server.NetworkTrafficServerConnector}, * and are notified of the following network traffic events:

    *
      *
    • Connection opened, when the server has accepted the connection from a remote client
    • @@ -45,7 +45,9 @@ public interface NetworkTrafficListener * * @param socket the socket associated with the remote client */ - void opened(Socket socket); + default void opened(Socket socket) + { + } /** *

      Callback method invoked when bytes sent by a remote client arrived on the server.

      @@ -53,7 +55,9 @@ public interface NetworkTrafficListener * @param socket the socket associated with the remote client * @param bytes the read-only buffer containing the incoming bytes */ - void incoming(Socket socket, ByteBuffer bytes); + default void incoming(Socket socket, ByteBuffer bytes) + { + } /** *

      Callback method invoked when bytes are sent to a remote client from the server.

      @@ -62,7 +66,9 @@ public interface NetworkTrafficListener * @param socket the socket associated with the remote client * @param bytes the read-only buffer containing the outgoing bytes */ - void outgoing(Socket socket, ByteBuffer bytes); + default void outgoing(Socket socket, ByteBuffer bytes) + { + } /** *

      Callback method invoked when a connection to a remote client has been closed.

      @@ -74,11 +80,15 @@ public interface NetworkTrafficListener * * @param socket the (closed) socket associated with the remote client */ - void closed(Socket socket); + default void closed(Socket socket) + { + } /** *

      A commodity class that implements {@link NetworkTrafficListener} with empty methods.

      + * @deprecated use {@link NetworkTrafficListener} instead */ + @Deprecated class Adapter implements NetworkTrafficListener { @Override diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/NetworkTrafficSelectChannelEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/NetworkTrafficSelectChannelEndPoint.java index c403df4aa68..375bc6477d4 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/NetworkTrafficSelectChannelEndPoint.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/NetworkTrafficSelectChannelEndPoint.java @@ -18,133 +18,20 @@ package org.eclipse.jetty.io; -import java.io.IOException; -import java.net.Socket; -import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.SocketChannel; import java.util.List; -import org.eclipse.jetty.util.log.Log; -import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.thread.Scheduler; -public class NetworkTrafficSelectChannelEndPoint extends SelectChannelEndPoint +/** + * @deprecated use {@link NetworkTrafficSocketChannelEndPoint} instead + */ +@Deprecated +public class NetworkTrafficSelectChannelEndPoint extends NetworkTrafficSocketChannelEndPoint { - private static final Logger LOG = Log.getLogger(NetworkTrafficSelectChannelEndPoint.class); - - private final List listeners; - - public NetworkTrafficSelectChannelEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key, Scheduler scheduler, long idleTimeout, List listeners) throws IOException + public NetworkTrafficSelectChannelEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key, Scheduler scheduler, long idleTimeout, List listeners) { - super(channel, selectSet, key, scheduler, idleTimeout); - this.listeners = listeners; - } - - @Override - public int fill(ByteBuffer buffer) throws IOException - { - int read = super.fill(buffer); - notifyIncoming(buffer, read); - return read; - } - - @Override - public boolean flush(ByteBuffer... buffers) throws IOException - { - boolean flushed = true; - for (ByteBuffer b : buffers) - { - if (b.hasRemaining()) - { - int position = b.position(); - ByteBuffer view = b.slice(); - flushed &= super.flush(b); - int l = b.position() - position; - view.limit(view.position() + l); - notifyOutgoing(view); - if (!flushed) - break; - } - } - return flushed; - } - - @Override - public void onOpen() - { - super.onOpen(); - if (listeners != null && !listeners.isEmpty()) - { - for (NetworkTrafficListener listener : listeners) - { - try - { - listener.opened(getSocket()); - } - catch (Exception x) - { - LOG.warn(x); - } - } - } - } - - @Override - public void onClose() - { - super.onClose(); - if (listeners != null && !listeners.isEmpty()) - { - for (NetworkTrafficListener listener : listeners) - { - try - { - listener.closed(getSocket()); - } - catch (Exception x) - { - LOG.warn(x); - } - } - } - } - - public void notifyIncoming(ByteBuffer buffer, int read) - { - if (listeners != null && !listeners.isEmpty() && read > 0) - { - for (NetworkTrafficListener listener : listeners) - { - try - { - ByteBuffer view = buffer.asReadOnlyBuffer(); - listener.incoming(getSocket(), view); - } - catch (Exception x) - { - LOG.warn(x); - } - } - } - } - - public void notifyOutgoing(ByteBuffer view) - { - if (listeners != null && !listeners.isEmpty() && view.hasRemaining()) - { - Socket socket = getSocket(); - for (NetworkTrafficListener listener : listeners) - { - try - { - listener.outgoing(socket, view); - } - catch (Exception x) - { - LOG.warn(x); - } - } - } + super(channel, selectSet, key, scheduler, idleTimeout, listeners); } } diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/NetworkTrafficSocketChannelEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/NetworkTrafficSocketChannelEndPoint.java new file mode 100644 index 00000000000..f8d74e0acfc --- /dev/null +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/NetworkTrafficSocketChannelEndPoint.java @@ -0,0 +1,136 @@ +package org.eclipse.jetty.io; + +import java.io.IOException; +import java.net.Socket; +import java.nio.ByteBuffer; +import java.nio.channels.SelectableChannel; +import java.nio.channels.SelectionKey; +import java.util.List; + +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.thread.Scheduler; + +/** + *

      A specialized version of {@link SocketChannelEndPoint} that supports {@link NetworkTrafficListener}s.

      + */ +public class NetworkTrafficSocketChannelEndPoint extends SocketChannelEndPoint +{ + private static final Logger LOG = Log.getLogger(NetworkTrafficSocketChannelEndPoint.class); + + private final List listeners; + + public NetworkTrafficSocketChannelEndPoint(SelectableChannel channel, ManagedSelector selectSet, SelectionKey key, Scheduler scheduler, long idleTimeout, List listeners) + { + super(channel, selectSet, key, scheduler); + setIdleTimeout(idleTimeout); + this.listeners = listeners; + } + + @Override + public int fill(ByteBuffer buffer) throws IOException + { + int read = super.fill(buffer); + notifyIncoming(buffer, read); + return read; + } + + @Override + public boolean flush(ByteBuffer... buffers) throws IOException + { + boolean flushed = true; + for (ByteBuffer b : buffers) + { + if (b.hasRemaining()) + { + int position = b.position(); + ByteBuffer view = b.slice(); + flushed = super.flush(b); + int l = b.position() - position; + view.limit(view.position() + l); + notifyOutgoing(view); + if (!flushed) + break; + } + } + return flushed; + } + + @Override + public void onOpen() + { + super.onOpen(); + if (listeners != null && !listeners.isEmpty()) + { + for (NetworkTrafficListener listener : listeners) + { + try + { + listener.opened(getSocket()); + } + catch (Exception x) + { + LOG.warn(x); + } + } + } + } + + @Override + public void onClose() + { + super.onClose(); + if (listeners != null && !listeners.isEmpty()) + { + for (NetworkTrafficListener listener : listeners) + { + try + { + listener.closed(getSocket()); + } + catch (Exception x) + { + LOG.warn(x); + } + } + } + } + + public void notifyIncoming(ByteBuffer buffer, int read) + { + if (listeners != null && !listeners.isEmpty() && read > 0) + { + for (NetworkTrafficListener listener : listeners) + { + try + { + ByteBuffer view = buffer.asReadOnlyBuffer(); + listener.incoming(getSocket(), view); + } + catch (Exception x) + { + LOG.warn(x); + } + } + } + } + + public void notifyOutgoing(ByteBuffer view) + { + if (listeners != null && !listeners.isEmpty() && view.hasRemaining()) + { + Socket socket = getSocket(); + for (NetworkTrafficListener listener : listeners) + { + try + { + listener.outgoing(socket, view); + } + catch (Exception x) + { + LOG.warn(x); + } + } + } + } +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/NetworkTrafficServerConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/NetworkTrafficServerConnector.java index dab17e501b0..4c635be66ed 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/NetworkTrafficServerConnector.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/NetworkTrafficServerConnector.java @@ -18,7 +18,6 @@ package org.eclipse.jetty.server; -import java.io.IOException; import java.nio.channels.SelectionKey; import java.nio.channels.SocketChannel; import java.util.List; @@ -29,7 +28,7 @@ import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.ChannelEndPoint; import org.eclipse.jetty.io.ManagedSelector; import org.eclipse.jetty.io.NetworkTrafficListener; -import org.eclipse.jetty.io.NetworkTrafficSelectChannelEndPoint; +import org.eclipse.jetty.io.NetworkTrafficSocketChannelEndPoint; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.Scheduler; @@ -84,9 +83,8 @@ public class NetworkTrafficServerConnector extends ServerConnector } @Override - protected ChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key) throws IOException + protected ChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key) { - NetworkTrafficSelectChannelEndPoint endPoint = new NetworkTrafficSelectChannelEndPoint(channel, selectSet, key, getScheduler(), getIdleTimeout(), listeners); - return endPoint; + return new NetworkTrafficSocketChannelEndPoint(channel, selectSet, key, getScheduler(), getIdleTimeout(), listeners); } } diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/NetworkTrafficListenerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/NetworkTrafficListenerTest.java deleted file mode 100644 index a64b7569466..00000000000 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/NetworkTrafficListenerTest.java +++ /dev/null @@ -1,484 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. -// ------------------------------------------------------------------------ -// All rights reserved. This program and the accompanying materials -// are made available under the terms of the Eclipse Public License v1.0 -// and Apache License v2.0 which accompanies this distribution. -// -// The Eclipse Public License is available at -// http://www.eclipse.org/legal/epl-v10.html -// -// The Apache License v2.0 is available at -// http://www.opensource.org/licenses/apache2.0.php -// -// You may elect to redistribute this code under either of these licenses. -// ======================================================================== -// - -package org.eclipse.jetty.server; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.Socket; -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; -import javax.servlet.ServletException; -import javax.servlet.ServletOutputStream; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.eclipse.jetty.io.NetworkTrafficListener; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.eclipse.jetty.util.BufferUtil; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -public class NetworkTrafficListenerTest -{ - private static final byte END_OF_CONTENT = '~'; - - private Server server; - private NetworkTrafficServerConnector connector; - - public void initConnector(Handler handler) throws Exception - { - server = new Server(); - - connector = new NetworkTrafficServerConnector(server); - connector.getConnectionFactory(HttpConfiguration.ConnectionFactory.class).getHttpConfiguration().setSendDateHeader(false); - connector.getConnectionFactory(HttpConfiguration.ConnectionFactory.class).getHttpConfiguration().setSendServerVersion(false); - server.addConnector(connector); - server.setHandler(handler); - server.start(); - } - - @AfterEach - public void destroyConnector() throws Exception - { - if (server != null) - { - server.stop(); - server.join(); - } - } - - @Test - public void testOpenedClosedAreInvoked() throws Exception - { - initConnector(null); - - final CountDownLatch openedLatch = new CountDownLatch(1); - final CountDownLatch closedLatch = new CountDownLatch(1); - connector.addNetworkTrafficListener(new NetworkTrafficListener.Adapter() - { - public volatile Socket socket; - - @Override - public void opened(Socket socket) - { - this.socket = socket; - openedLatch.countDown(); - } - - @Override - public void closed(Socket socket) - { - if (this.socket == socket) - closedLatch.countDown(); - } - }); - int port = connector.getLocalPort(); - - // Connect to the server - Socket socket = new Socket("localhost", port); - assertTrue(openedLatch.await(10, TimeUnit.SECONDS)); - - socket.close(); - assertTrue(closedLatch.await(10, TimeUnit.SECONDS)); - } - - @Test - public void testTrafficWithNoResponseContentOnNonPersistentConnection() throws Exception - { - initConnector(new AbstractHandler() - { - @Override - public void handle(String uri, Request request, HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws IOException, ServletException - { - request.setHandled(true); - } - }); - - final AtomicReference incomingData = new AtomicReference<>(); - final CountDownLatch incomingLatch = new CountDownLatch(1); - final AtomicReference outgoingData = new AtomicReference<>(""); - final CountDownLatch outgoingLatch = new CountDownLatch(1); - connector.addNetworkTrafficListener(new NetworkTrafficListener.Adapter() - { - @Override - public void incoming(Socket socket, ByteBuffer bytes) - { - incomingData.set(BufferUtil.toString(bytes, StandardCharsets.UTF_8)); - incomingLatch.countDown(); - } - - @Override - public void outgoing(Socket socket, ByteBuffer bytes) - { - outgoingData.set(outgoingData.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8)); - outgoingLatch.countDown(); - } - }); - int port = connector.getLocalPort(); - - String request = - "GET / HTTP/1.1\r\n" + - "Host: localhost:" + port + "\r\n" + - "Connection: close\r\n" + - "\r\n"; - String expectedResponse = - "HTTP/1.1 200 OK\r\n" + - "Connection: close\r\n" + - "\r\n"; - - Socket socket = new Socket("localhost", port); - OutputStream output = socket.getOutputStream(); - output.write(request.getBytes(StandardCharsets.UTF_8)); - output.flush(); - - assertTrue(incomingLatch.await(1, TimeUnit.SECONDS)); - assertEquals(request, incomingData.get()); - - assertTrue(outgoingLatch.await(1, TimeUnit.SECONDS)); - assertEquals(expectedResponse, outgoingData.get()); - - byte[] responseBytes = readResponse(socket); - String response = new String(responseBytes, StandardCharsets.UTF_8); - assertEquals(expectedResponse, response); - - socket.close(); - } - - @Test - public void testTrafficWithResponseContentOnPersistentConnection() throws Exception - { - final String responseContent = "response_content"; - initConnector(new AbstractHandler() - { - @Override - public void handle(String uri, Request request, HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws IOException, ServletException - { - request.setHandled(true); - ServletOutputStream output = servletResponse.getOutputStream(); - output.write(responseContent.getBytes(StandardCharsets.UTF_8)); - output.write(END_OF_CONTENT); - } - }); - - final AtomicReference incomingData = new AtomicReference<>(); - final CountDownLatch incomingLatch = new CountDownLatch(1); - final AtomicReference outgoingData = new AtomicReference<>(""); - final CountDownLatch outgoingLatch = new CountDownLatch(2); - connector.addNetworkTrafficListener(new NetworkTrafficListener.Adapter() - { - @Override - public void incoming(Socket socket, ByteBuffer bytes) - { - incomingData.set(BufferUtil.toString(bytes, StandardCharsets.UTF_8)); - incomingLatch.countDown(); - } - - @Override - public void outgoing(Socket socket, ByteBuffer bytes) - { - outgoingData.set(outgoingData.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8)); - outgoingLatch.countDown(); - } - }); - int port = connector.getLocalPort(); - - String request = - "GET / HTTP/1.1\r\n" + - "Host: localhost:" + port + "\r\n" + - "\r\n"; - String expectedResponse = - "HTTP/1.1 200 OK\r\n" + - "Content-Length: " + (responseContent.length() + 1) + "\r\n" + - "\r\n" + - "" + responseContent + (char)END_OF_CONTENT; - - Socket socket = new Socket("localhost", port); - OutputStream output = socket.getOutputStream(); - output.write(request.getBytes(StandardCharsets.UTF_8)); - output.flush(); - - assertTrue(incomingLatch.await(1, TimeUnit.SECONDS)); - assertEquals(request, incomingData.get()); - - assertTrue(outgoingLatch.await(1, TimeUnit.SECONDS)); - assertEquals(expectedResponse, outgoingData.get()); - - byte[] responseBytes = readResponse(socket); - String response = new String(responseBytes, StandardCharsets.UTF_8); - assertEquals(expectedResponse, response); - - socket.close(); - } - - @Test - public void testTrafficWithResponseContentChunkedOnPersistentConnection() throws Exception - { - final String responseContent = "response_content"; - final String responseChunk1 = "response_content".substring(0, responseContent.length() / 2); - final String responseChunk2 = "response_content".substring(responseContent.length() / 2, responseContent.length()); - initConnector(new AbstractHandler() - { - @Override - public void handle(String uri, Request request, HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws IOException, ServletException - { - request.setHandled(true); - ServletOutputStream output = servletResponse.getOutputStream(); - output.write(responseChunk1.getBytes(StandardCharsets.UTF_8)); - output.flush(); - output.write(responseChunk2.getBytes(StandardCharsets.UTF_8)); - output.flush(); - } - }); - - final AtomicReference incomingData = new AtomicReference<>(); - final CountDownLatch incomingLatch = new CountDownLatch(1); - final AtomicReference outgoingData = new AtomicReference<>(""); - final CountDownLatch outgoingLatch = new CountDownLatch(1); - connector.addNetworkTrafficListener(new NetworkTrafficListener.Adapter() - { - @Override - public void incoming(Socket socket, ByteBuffer bytes) - { - incomingData.set(BufferUtil.toString(bytes, StandardCharsets.UTF_8)); - incomingLatch.countDown(); - } - - @Override - public void outgoing(Socket socket, ByteBuffer bytes) - { - outgoingData.set(outgoingData.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8)); - if (outgoingData.get().endsWith("\r\n0\r\n\r\n")) - outgoingLatch.countDown(); - } - }); - int port = connector.getLocalPort(); - - String request = - "GET / HTTP/1.1\r\n" + - "Host: localhost:" + port + "\r\n" + - "\r\n"; - String expectedResponse = - "HTTP/1.1 200 OK\r\n" + - "Transfer-Encoding: chunked\r\n" + - "\r\n" + - responseChunk1.length() + "\r\n" + - responseChunk1 + "\r\n" + - responseChunk2.length() + "\r\n" + - responseChunk2 + "\r\n" + - "0\r\n" + - "\r\n"; - - Socket socket = new Socket("localhost", port); - OutputStream output = socket.getOutputStream(); - output.write(request.getBytes(StandardCharsets.UTF_8)); - output.flush(); - - assertTrue(incomingLatch.await(1, TimeUnit.SECONDS)); - assertEquals(request, incomingData.get()); - - assertTrue(outgoingLatch.await(1, TimeUnit.SECONDS)); - assertEquals(expectedResponse, outgoingData.get()); - - byte[] responseBytes = readResponse(socket); - String response = new String(responseBytes, StandardCharsets.UTF_8); - assertEquals(expectedResponse, response); - - socket.close(); - } - - @Test - public void testTrafficWithRequestContentWithResponseRedirectOnPersistentConnection() throws Exception - { - final String location = "/redirect"; - initConnector(new AbstractHandler() - { - @Override - public void handle(String uri, Request request, HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws IOException, ServletException - { - request.setHandled(true); - servletResponse.sendRedirect(location); - } - }); - - final AtomicReference incomingData = new AtomicReference<>(); - final CountDownLatch incomingLatch = new CountDownLatch(1); - final AtomicReference outgoingData = new AtomicReference<>(""); - final CountDownLatch outgoingLatch = new CountDownLatch(1); - connector.addNetworkTrafficListener(new NetworkTrafficListener.Adapter() - { - @Override - public void incoming(Socket socket, ByteBuffer bytes) - { - incomingData.set(BufferUtil.toString(bytes, StandardCharsets.UTF_8)); - incomingLatch.countDown(); - } - - @Override - public void outgoing(Socket socket, ByteBuffer bytes) - { - outgoingData.set(outgoingData.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8)); - outgoingLatch.countDown(); - } - }); - int port = connector.getLocalPort(); - - String requestContent = "a=1&b=2"; - String request = - "POST / HTTP/1.1\r\n" + - "Host: localhost:" + port + "\r\n" + - "Content-Type: application/x-www-form-urlencoded\r\n" + - "Content-Length: " + requestContent.length() + "\r\n" + - "\r\n" + - requestContent; - String expectedResponse = - "HTTP/1.1 302 Found\r\n" + - "Location: http://localhost:" + port + location + "\r\n" + - "Content-Length: 0\r\n" + - "\r\n"; - - Socket socket = new Socket("localhost", port); - OutputStream output = socket.getOutputStream(); - output.write(request.getBytes(StandardCharsets.UTF_8)); - output.flush(); - - assertTrue(incomingLatch.await(1, TimeUnit.SECONDS)); - assertEquals(request, incomingData.get()); - - assertTrue(outgoingLatch.await(1, TimeUnit.SECONDS)); - assertEquals(expectedResponse, outgoingData.get()); - - byte[] responseBytes = readResponse(socket); - String response = new String(responseBytes, StandardCharsets.UTF_8); - assertEquals(expectedResponse, response); - - socket.close(); - } - - @Test - public void testTrafficWithBigRequestContentOnPersistentConnection() throws Exception - { - initConnector(new AbstractHandler() - { - @Override - public void handle(String uri, Request request, HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws IOException, ServletException - { - // Read and discard the request body to make the test more - // reliable, otherwise there is a race between request body - // upload and response download - InputStream input = servletRequest.getInputStream(); - byte[] buffer = new byte[4096]; - while (true) - { - int read = input.read(buffer); - if (read < 0) - break; - } - request.setHandled(true); - } - }); - - final AtomicReference incomingData = new AtomicReference<>(""); - final AtomicReference outgoingData = new AtomicReference<>(""); - final CountDownLatch outgoingLatch = new CountDownLatch(1); - connector.addNetworkTrafficListener(new NetworkTrafficListener.Adapter() - { - @Override - public void incoming(Socket socket, ByteBuffer bytes) - { - incomingData.set(incomingData.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8)); - } - - @Override - public void outgoing(Socket socket, ByteBuffer bytes) - { - outgoingData.set(outgoingData.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8)); - outgoingLatch.countDown(); - } - }); - int port = connector.getLocalPort(); - - // Generate 32 KiB of request content - String requestContent = "0123456789ABCDEF"; - for (int i = 0; i < 11; ++i) - { - requestContent += requestContent; - } - String request = - "POST / HTTP/1.1\r\n" + - "Host: localhost:" + port + "\r\n" + - "Content-Type: text/plain\r\n" + - "Content-Length: " + requestContent.length() + "\r\n" + - "\r\n" + - requestContent; - String expectedResponse = - "HTTP/1.1 200 OK\r\n" + - "Content-Length: 0\r\n" + - "\r\n"; - - Socket socket = new Socket("localhost", port); - OutputStream output = socket.getOutputStream(); - output.write(request.getBytes(StandardCharsets.UTF_8)); - output.flush(); - - assertTrue(outgoingLatch.await(1, TimeUnit.SECONDS)); - assertEquals(expectedResponse, outgoingData.get()); - - byte[] responseBytes = readResponse(socket); - String response = new String(responseBytes, StandardCharsets.UTF_8); - assertEquals(expectedResponse, response); - - assertEquals(request, incomingData.get()); - - socket.close(); - } - - private byte[] readResponse(Socket socket) throws IOException - { - socket.setSoTimeout(5000); - InputStream input = socket.getInputStream(); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - int read; - while ((read = input.read()) >= 0) - { - baos.write(read); - - // Handle non-chunked end of response - if (read == END_OF_CONTENT) - break; - - // Handle chunked end of response - String response = baos.toString("UTF-8"); - if (response.endsWith("\r\n0\r\n\r\n")) - break; - - // Handle non-content responses - if (response.contains("Content-Length: 0") && response.endsWith("\r\n\r\n")) - break; - } - return baos.toByteArray(); - } -} From 7ef05860ee88e6621bed2002f008de656e62927d Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Tue, 7 Apr 2020 12:19:49 +0200 Subject: [PATCH 055/101] Fixes #4751 - Refresh NetworkTraffic* classes. Added missing license header. Signed-off-by: Simone Bordet --- .../NetworkTrafficSocketChannelEndPoint.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/NetworkTrafficSocketChannelEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/NetworkTrafficSocketChannelEndPoint.java index f8d74e0acfc..5d8d63f5806 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/NetworkTrafficSocketChannelEndPoint.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/NetworkTrafficSocketChannelEndPoint.java @@ -1,3 +1,21 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + package org.eclipse.jetty.io; import java.io.IOException; From 4e69b483446f6ce6214c2f0d4ab001b0fd204aff Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Wed, 8 Apr 2020 15:31:42 +1000 Subject: [PATCH 056/101] Issue #4747 - filter out synthetic annotated methods Signed-off-by: Lachlan Roberts --- .../javax/tests/SyntheticOnMessageTest.java | 112 ++++++++++++++++++ .../jetty/websocket/util/ReflectUtils.java | 21 ++-- 2 files changed, 119 insertions(+), 14 deletions(-) create mode 100644 jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/SyntheticOnMessageTest.java diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/SyntheticOnMessageTest.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/SyntheticOnMessageTest.java new file mode 100644 index 00000000000..8130905ee42 --- /dev/null +++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/SyntheticOnMessageTest.java @@ -0,0 +1,112 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.websocket.javax.tests; + +import java.lang.reflect.Method; +import java.net.URI; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.websocket.CloseReason; +import javax.websocket.ContainerProvider; +import javax.websocket.OnMessage; +import javax.websocket.Session; +import javax.websocket.WebSocketContainer; +import javax.websocket.server.ServerEndpoint; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.util.component.LifeCycle; +import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class SyntheticOnMessageTest +{ + private Server server; + private URI serverUri; + private ServerConnector connector; + private WebSocketContainer client; + + @BeforeEach + public void before() throws Exception + { + server = new Server(); + connector = new ServerConnector(server); + server.addConnector(connector); + + ServletContextHandler contextHandler = new ServletContextHandler(); + contextHandler.setContextPath("/"); + JavaxWebSocketServletContainerInitializer.configure(contextHandler, (context, container) -> + container.addEndpoint(ServerSocket.class)); + server.setHandler(contextHandler); + server.start(); + serverUri = URI.create("ws://localhost:" + connector.getLocalPort()); + client = ContainerProvider.getWebSocketContainer(); + } + + @AfterEach + public void after() throws Exception + { + LifeCycle.stop(client); + server.stop(); + } + + public static class AnnotatedEndpoint + { + public void onMessage(T message) + { + } + } + + @ServerEndpoint("/") + public static class ServerSocket extends AnnotatedEndpoint + { + @OnMessage + public void onMessage(String message) + { + } + } + + @Test + public void syntheticOnMessageTest() throws Exception + { + // ServerSocket has two annotated onMessage methods, one is a synthetic bridge method generated + // by the compiler and shouldn't be used. + List annotatedOnMessages = Stream.of(ServerSocket.class.getDeclaredMethods()) + .filter(method -> method.getAnnotation(OnMessage.class) != null) + .collect(Collectors.toList()); + assertThat(annotatedOnMessages.size(), is(2)); + + // We should correctly filter out all synthetic methods so we should not get an InvalidSignatureException. + EventSocket clientSocket = new EventSocket(); + Session session = client.connectToServer(clientSocket, serverUri); + assertTrue(clientSocket.openLatch.await(5, TimeUnit.SECONDS)); + session.close(); + assertTrue(clientSocket.closeLatch.await(5, TimeUnit.SECONDS)); + assertThat(clientSocket.closeReason.getCloseCode(), is(CloseReason.CloseCodes.NORMAL_CLOSURE)); + } +} diff --git a/jetty-websocket/websocket-util/src/main/java/org/eclipse/jetty/websocket/util/ReflectUtils.java b/jetty-websocket/websocket-util/src/main/java/org/eclipse/jetty/websocket/util/ReflectUtils.java index c038b45ead2..bb1a33e6e99 100644 --- a/jetty-websocket/websocket-util/src/main/java/org/eclipse/jetty/websocket/util/ReflectUtils.java +++ b/jetty-websocket/websocket-util/src/main/java/org/eclipse/jetty/websocket/util/ReflectUtils.java @@ -29,6 +29,7 @@ import java.lang.reflect.TypeVariable; import java.util.ArrayList; import java.util.List; import java.util.regex.Pattern; +import java.util.stream.Stream; public class ReflectUtils { @@ -220,27 +221,19 @@ public class ReflectUtils public static Method[] findAnnotatedMethods(Class pojo, Class anno) { - List methods = null; Class clazz = pojo; - + List methods = new ArrayList<>(); while ((clazz != null) && Object.class.isAssignableFrom(clazz)) { - for (Method method : clazz.getDeclaredMethods()) - { - if (method.getAnnotation(anno) != null) - { - if (methods == null) - methods = new ArrayList<>(); - methods.add(method); - } - } + Stream.of(clazz.getDeclaredMethods()) + .filter(method -> !method.isSynthetic() && (method.getAnnotation(anno) != null)) + .forEach(methods::add); clazz = clazz.getSuperclass(); } - if (methods == null) + if (methods.isEmpty()) return null; - int len = methods.size(); - return methods.toArray(new Method[len]); + return methods.toArray(new Method[0]); } /** From 45f822ed328a93e0457c27308bed12290a827780 Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Wed, 8 Apr 2020 17:08:52 +1000 Subject: [PATCH 057/101] Issue #4747 - server is allowed to not select a websocket subprotocol Signed-off-by: Lachlan Roberts --- .../core/client/ClientUpgradeRequest.java | 2 -- .../server/internal/AbstractHandshaker.java | 7 +---- .../core/WebSocketNegotiationTest.java | 27 +++++++++++++------ 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/client/ClientUpgradeRequest.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/client/ClientUpgradeRequest.java index c37918952fe..3583989e71d 100644 --- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/client/ClientUpgradeRequest.java +++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/client/ClientUpgradeRequest.java @@ -409,8 +409,6 @@ public abstract class ClientUpgradeRequest extends HttpRequest implements Respon // Verify the negotiated subprotocol List offeredSubProtocols = getSubProtocols(); - if (negotiatedSubProtocol == null && !offeredSubProtocols.isEmpty()) - throw new WebSocketException("Upgrade failed: no subprotocol selected from offered subprotocols "); if (negotiatedSubProtocol != null && !offeredSubProtocols.contains(negotiatedSubProtocol)) throw new WebSocketException("Upgrade failed: subprotocol [" + negotiatedSubProtocol + "] not found in offered subprotocols " + offeredSubProtocols); diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/server/internal/AbstractHandshaker.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/server/internal/AbstractHandshaker.java index afd98b662d8..1a0d84c1c5b 100644 --- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/server/internal/AbstractHandshaker.java +++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/server/internal/AbstractHandshaker.java @@ -96,7 +96,7 @@ public abstract class AbstractHandshaker implements Handshaker return false; } - // Validate negotiated protocol + // Validate negotiated protocol. String protocol = negotiation.getSubprotocol(); List offeredProtocols = negotiation.getOfferedSubprotocols(); if (protocol != null) @@ -104,11 +104,6 @@ public abstract class AbstractHandshaker implements Handshaker if (!offeredProtocols.contains(protocol)) throw new WebSocketException("not upgraded: selected a protocol not present in offered protocols"); } - else - { - if (!offeredProtocols.isEmpty()) - throw new WebSocketException("not upgraded: no protocol selected from offered protocols"); - } // validate negotiated extensions for (ExtensionConfig config : negotiation.getNegotiatedExtensions()) diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/WebSocketNegotiationTest.java b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/WebSocketNegotiationTest.java index 5f9e5482426..237f8012625 100644 --- a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/WebSocketNegotiationTest.java +++ b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/WebSocketNegotiationTest.java @@ -317,17 +317,28 @@ public class WebSocketNegotiationTest extends WebSocketTester public void testNoSubProtocolSelected() throws Exception { TestFrameHandler clientHandler = new TestFrameHandler(); - ClientUpgradeRequest upgradeRequest = ClientUpgradeRequest.from(client, server.getUri(), clientHandler); upgradeRequest.setSubProtocols("testNoSubProtocolSelected"); - - try (StacklessLogging stacklessLogging = new StacklessLogging(HttpChannel.class)) + CompletableFuture headers = new CompletableFuture<>(); + upgradeRequest.addListener(new UpgradeListener() { - CompletableFuture connect = client.connect(upgradeRequest); - Throwable t = assertThrows(ExecutionException.class, () -> connect.get(5, TimeUnit.SECONDS)); - assertThat(t.getMessage(), containsString("Failed to upgrade to websocket:")); - assertThat(t.getMessage(), containsString("500 Server Error")); - } + @Override + public void onHandshakeResponse(HttpRequest request, HttpResponse response) + { + headers.complete(response.getHeaders()); + } + }); + + CoreSession session = client.connect(upgradeRequest).get(5, TimeUnit.SECONDS); + session.close(Callback.NOOP); + assertTrue(clientHandler.closed.await(5, TimeUnit.SECONDS)); + assertThat(clientHandler.closeStatus.getCode(), is(CloseStatus.NO_CODE)); + + // RFC6455: If the server does not agree to any of the client's requested subprotocols, the only acceptable + // value is null. It MUST NOT send back a |Sec-WebSocket-Protocol| header field in its response. + HttpFields httpFields = headers.get(); + assertThat(httpFields.get(HttpHeader.UPGRADE), is("WebSocket")); + assertNull(httpFields.get(HttpHeader.SEC_WEBSOCKET_SUBPROTOCOL)); } @Test From efe2dc1e90706f3d6ffc5711969f85c65e970ffe Mon Sep 17 00:00:00 2001 From: Jan Bartel Date: Wed, 8 Apr 2020 11:16:20 +0200 Subject: [PATCH 058/101] Issue #4752 Call HttpSessionListener.sessionCreated in add order. (#4753) Signed-off-by: Jan Bartel --- .../jetty/server/session/SessionHandler.java | 10 +-- .../server/session/SessionHandlerTest.java | 66 ++++++++++++++++++- 2 files changed, 70 insertions(+), 6 deletions(-) diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionHandler.java index a3d624e5163..98c91bd0694 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionHandler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionHandler.java @@ -279,7 +279,8 @@ public class SessionHandler extends ScopedHandler } /** - * Call the session lifecycle listeners + * Call the session lifecycle listeners in + * the reverse order they were added. * * @param session the session on which to call the lifecycle listeners */ @@ -310,7 +311,8 @@ public class SessionHandler extends ScopedHandler } /** - * Call the session lifecycle listeners + * Call the session lifecycle listeners in the order + * they were added. * * @param session the session on which to call the lifecycle listeners */ @@ -322,9 +324,9 @@ public class SessionHandler extends ScopedHandler if (_sessionListeners != null) { HttpSessionEvent event = new HttpSessionEvent(session); - for (int i = _sessionListeners.size() - 1; i >= 0; i--) + for (HttpSessionListener l : _sessionListeners) { - _sessionListeners.get(i).sessionCreated(event); + l.sessionCreated(event); } } } diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/session/SessionHandlerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/session/SessionHandlerTest.java index 8f5201ab539..5be4b2d508a 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/session/SessionHandlerTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/session/SessionHandlerTest.java @@ -22,9 +22,14 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import javax.servlet.SessionTrackingMode; +import javax.servlet.http.HttpSession; +import javax.servlet.http.HttpSessionEvent; +import javax.servlet.http.HttpSessionListener; +import org.eclipse.jetty.server.Server; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; public class SessionHandlerTest @@ -35,7 +40,64 @@ public class SessionHandlerTest SessionHandler sessionHandler = new SessionHandler(); sessionHandler.setSessionTrackingModes(new HashSet<>(Arrays.asList(SessionTrackingMode.COOKIE, SessionTrackingMode.URL))); sessionHandler.setSessionTrackingModes(Collections.singleton(SessionTrackingMode.SSL)); - assertThrows(IllegalArgumentException.class,() -> - sessionHandler.setSessionTrackingModes(new HashSet<>(Arrays.asList(SessionTrackingMode.SSL, SessionTrackingMode.URL)))); + assertThrows(IllegalArgumentException.class, () -> sessionHandler.setSessionTrackingModes(new HashSet<>(Arrays.asList(SessionTrackingMode.SSL, SessionTrackingMode.URL)))); + } + + @Test + public void testSessionListenerOrdering() + throws Exception + { + final StringBuffer result = new StringBuffer(); + + class Listener1 implements HttpSessionListener + { + + @Override + public void sessionCreated(HttpSessionEvent se) + { + result.append("Listener1 create;"); + } + + @Override + public void sessionDestroyed(HttpSessionEvent se) + { + result.append("Listener1 destroy;"); + } + } + + class Listener2 implements HttpSessionListener + { + + @Override + public void sessionCreated(HttpSessionEvent se) + { + result.append("Listener2 create;"); + } + + @Override + public void sessionDestroyed(HttpSessionEvent se) + { + result.append("Listener2 destroy;"); + } + + } + + Server server = new Server(); + SessionHandler sessionHandler = new SessionHandler(); + try + { + sessionHandler.addEventListener(new Listener1()); + sessionHandler.addEventListener(new Listener2()); + sessionHandler.setServer(server); + sessionHandler.start(); + Session session = new Session(sessionHandler, new SessionData("aa", "_", "0.0", 0, 0, 0, 0)); + sessionHandler.callSessionCreatedListeners(session); + sessionHandler.callSessionDestroyedListeners(session); + assertEquals("Listener1 create;Listener2 create;Listener2 destroy;Listener1 destroy;", result.toString()); + } + finally + { + sessionHandler.stop(); + } } } From 3981ec1342381ad75bc01886ece735f4144dbe50 Mon Sep 17 00:00:00 2001 From: Jan Bartel Date: Wed, 8 Apr 2020 12:27:43 +0200 Subject: [PATCH 059/101] Issue #4758 Fix doc for DefaultServlet.welcomeServlets Signed-off-by: Jan Bartel --- .../asciidoc/distribution-guide/extras/default-servlet.adoc | 2 +- .../src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java | 2 +- jetty-webapp/src/main/config/etc/webdefault.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/jetty-documentation/src/main/asciidoc/distribution-guide/extras/default-servlet.adoc b/jetty-documentation/src/main/asciidoc/distribution-guide/extras/default-servlet.adoc index b9ba8c5b7ee..856a19ca2d5 100644 --- a/jetty-documentation/src/main/asciidoc/distribution-guide/extras/default-servlet.adoc +++ b/jetty-documentation/src/main/asciidoc/distribution-guide/extras/default-servlet.adoc @@ -49,7 +49,7 @@ If `true`, welcome files are redirected rather that forwarded. welcomeServlets:: If `true`, attempt to dispatch to welcome files that are servlets, but only after no matching static resources could be found. If `false`, then a welcome file must exist on disk. If `exact`, then exact -servlet matches are supported without an existing file. Default is `true`. This must be `false` if you want directory listings, +servlet matches are supported without an existing file. Default is `false`. This must be `false` if you want directory listings, but have index.jsp in your welcome file list. precompressed:: If set to a comma separated list of encoding types (that may be listed in a requests Accept-Encoding header) to file extension mappings to look for and serve. diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java index 000f84874d6..8db55db6ec0 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java @@ -67,7 +67,7 @@ import org.slf4j.LoggerFactory; * resources could be found. If false, then a welcome * file must exist on disk. If "exact", then exact * servlet matches are supported without an existing file. - * Default is true. + * Default is false. * * This must be false if you want directory listings, * but have index.jsp in your welcome file list. diff --git a/jetty-webapp/src/main/config/etc/webdefault.xml b/jetty-webapp/src/main/config/etc/webdefault.xml index 39a9efc4381..a52ecc2152b 100644 --- a/jetty-webapp/src/main/config/etc/webdefault.xml +++ b/jetty-webapp/src/main/config/etc/webdefault.xml @@ -82,7 +82,7 @@ * resources could be found. If false, then a welcome * file must exist on disk. If "exact", then exact * servlet matches are supported without an existing file. - * Default is true. + * Default is false. * * This must be false if you want directory listings, * but have index.jsp in your welcome file list. From a640701d78f622c27aaccd98121901dc017e9d55 Mon Sep 17 00:00:00 2001 From: Michael Mayer Date: Wed, 8 Apr 2020 15:36:52 +0200 Subject: [PATCH 060/101] Improve keystore exception message when keystore is not valid (#4759) Signed-off-by: Michael Mayer --- .../java/org/eclipse/jetty/util/security/CertificateUtils.java | 2 +- .../java/org/eclipse/jetty/util/ssl/SslContextFactoryTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/security/CertificateUtils.java b/jetty-util/src/main/java/org/eclipse/jetty/util/security/CertificateUtils.java index 49b55ec99da..d39d00ff2f6 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/security/CertificateUtils.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/security/CertificateUtils.java @@ -47,7 +47,7 @@ public class CertificateUtils } if (!store.exists()) - throw new IllegalStateException("no valid keystore"); + throw new IllegalStateException(store.getName() + " is not a valid keystore"); try (InputStream inStream = store.getInputStream()) { diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/ssl/SslContextFactoryTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/ssl/SslContextFactoryTest.java index 15d8228f427..f6270c1eace 100644 --- a/jetty-util/src/test/java/org/eclipse/jetty/util/ssl/SslContextFactoryTest.java +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/ssl/SslContextFactoryTest.java @@ -217,7 +217,7 @@ public class SslContextFactoryTest cf.setTrustStorePath("/foo"); cf.start(); }); - assertThat(x.getMessage(), containsString("no valid keystore")); + assertThat(x.getMessage(), equalTo("/foo is not a valid keystore")); } } From 9079fa63b3b4c852e9b62af9804c9a9daa4998e1 Mon Sep 17 00:00:00 2001 From: Michael Mayer Date: Wed, 8 Apr 2020 15:36:52 +0200 Subject: [PATCH 061/101] Improve keystore exception message when keystore is not valid (#4759) Signed-off-by: Michael Mayer --- .../java/org/eclipse/jetty/util/security/CertificateUtils.java | 2 +- .../java/org/eclipse/jetty/util/ssl/SslContextFactoryTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/security/CertificateUtils.java b/jetty-util/src/main/java/org/eclipse/jetty/util/security/CertificateUtils.java index e5261cc3787..7a91878c80d 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/security/CertificateUtils.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/security/CertificateUtils.java @@ -47,7 +47,7 @@ public class CertificateUtils } if (!store.exists()) - throw new IllegalStateException("no valid keystore"); + throw new IllegalStateException(store.getName() + " is not a valid keystore"); try (InputStream inStream = store.getInputStream()) { diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/ssl/SslContextFactoryTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/ssl/SslContextFactoryTest.java index d676dcbb77e..edce0f218be 100644 --- a/jetty-util/src/test/java/org/eclipse/jetty/util/ssl/SslContextFactoryTest.java +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/ssl/SslContextFactoryTest.java @@ -244,7 +244,7 @@ public class SslContextFactoryTest cf.setTrustStorePath("/foo"); cf.start(); }); - assertThat(x.getMessage(), containsString("no valid keystore")); + assertThat(x.getMessage(), equalTo("/foo is not a valid keystore")); } } From bfc9738f8685f14a1868a2741dc0b9957ca8df63 Mon Sep 17 00:00:00 2001 From: Jan Bartel Date: Wed, 8 Apr 2020 17:28:14 +0200 Subject: [PATCH 062/101] Issue #4760 Response.setLocale should override previous (#4761) Signed-off-by: Jan Bartel --- .../eclipse/jetty/http/encoding.properties | 4 +- .../org/eclipse/jetty/server/Response.java | 2 +- .../eclipse/jetty/server/ResponseTest.java | 62 +++++++++++++++++++ 3 files changed, 65 insertions(+), 3 deletions(-) diff --git a/jetty-http/src/main/resources/org/eclipse/jetty/http/encoding.properties b/jetty-http/src/main/resources/org/eclipse/jetty/http/encoding.properties index 3f2d2dd358e..9fbebc6191a 100644 --- a/jetty-http/src/main/resources/org/eclipse/jetty/http/encoding.properties +++ b/jetty-http/src/main/resources/org/eclipse/jetty/http/encoding.properties @@ -1,6 +1,6 @@ # Mapping of mime type to inferred or assumed charset -# inferred charsets are used for encoding/decoding and explicitly set in associated metadata -# assumed charsets are used for encoding/decoding, but are not set in associated metadata +# inferred charsets are used for encoding/decoding and explicitly set in Content-Type +# assumed charsets are used for encoding/decoding, but are not set in Content-Type # In this file, assumed charsets are indicated with a leading '-' text/html=utf-8 diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java index 4a8eea246a9..c4441d9ff80 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java @@ -107,7 +107,7 @@ public class Response implements HttpServletResponse NOT_SET, INFERRED, SET_LOCALE, SET_CONTENT_TYPE, SET_CHARACTER_ENCODING } - private static final EnumSet __localeOverride = EnumSet.of(EncodingFrom.NOT_SET, EncodingFrom.INFERRED); + private static final EnumSet __localeOverride = EnumSet.of(EncodingFrom.NOT_SET, EncodingFrom.INFERRED, EncodingFrom.SET_LOCALE); private static final EnumSet __explicitCharset = EnumSet.of(EncodingFrom.SET_LOCALE, EncodingFrom.SET_CHARACTER_ENCODING); public Response(HttpChannel channel, HttpOutput out) diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java index 273e28ad360..cb79e3f3923 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java @@ -53,6 +53,7 @@ import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpURI; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; +import org.eclipse.jetty.http.MimeTypes; import org.eclipse.jetty.io.AbstractEndPoint; import org.eclipse.jetty.io.ByteArrayEndPoint; import org.eclipse.jetty.server.handler.AbstractHandler; @@ -473,6 +474,67 @@ public class ResponseTest response.getWriter(); assertThat("iso-8859-1", Matchers.equalTo(response.getCharacterEncoding())); } + + @Test + public void testLocaleAndContentTypeEncoding() throws Exception + { + _server.stop(); + MimeTypes.getInferredEncodings().put("text/html", "iso-8859-1"); + ContextHandler handler = new ContextHandler(); + handler.addLocaleEncoding("ja", "euc-jp"); + handler.addLocaleEncoding("zh_CN", "gb18030"); + _server.setHandler(handler); + handler.setHandler(new DumpHandler()); + _server.start(); + + Response response = getResponse(); + response.getHttpChannel().getRequest().setContext(handler.getServletContext()); + + response.setContentType("text/html"); + assertEquals("iso-8859-1", response.getCharacterEncoding()); + + // setLocale should change character encoding based on + // locale-encoding-mapping-list + response.setLocale(Locale.JAPAN); + assertEquals("euc-jp", response.getCharacterEncoding()); + + // setLocale should change character encoding based on + // locale-encoding-mapping-list + response.setLocale(Locale.CHINA); + assertEquals("gb18030", response.getCharacterEncoding()); + + // setContentType here doesn't define character encoding + response.setContentType("text/html"); + assertEquals("gb18030", response.getCharacterEncoding()); + + // setCharacterEncoding should still be able to change encoding + response.setCharacterEncoding("utf-8"); + assertEquals("utf-8", response.getCharacterEncoding()); + + // setLocale should not override explicit character encoding request + response.setLocale(Locale.JAPAN); + assertEquals("utf-8", response.getCharacterEncoding()); + + // setContentType should still be able to change encoding + response.setContentType("text/html;charset=gb18030"); + assertEquals("gb18030", response.getCharacterEncoding()); + + // setCharacterEncoding should still be able to change encoding + response.setCharacterEncoding("utf-8"); + assertEquals("utf-8", response.getCharacterEncoding()); + + // getWriter should freeze the character encoding + PrintWriter pw = response.getWriter(); + assertEquals("utf-8", response.getCharacterEncoding()); + + // setCharacterEncoding should no longer be able to change the encoding + response.setCharacterEncoding("iso-8859-1"); + assertEquals("utf-8", response.getCharacterEncoding()); + + // setLocale should not override explicit character encoding request + response.setLocale(Locale.JAPAN); + assertEquals("utf-8", response.getCharacterEncoding()); + } @Test public void testContentTypeCharacterEncoding() throws Exception From ab228fde9e55e9164c738d7fa121f8ac5acd51c9 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Wed, 8 Apr 2020 12:33:56 -0500 Subject: [PATCH 063/101] Updating to version 9.4.28.v20200408 --- VERSION.txt | 40 +++++- aggregates/jetty-all-compact3/pom.xml | 2 +- aggregates/jetty-all/pom.xml | 2 +- apache-jsp/pom.xml | 2 +- apache-jstl/pom.xml | 2 +- build-resources/pom.xml | 2 +- examples/async-rest/async-rest-jar/pom.xml | 2 +- examples/async-rest/async-rest-webapp/pom.xml | 2 +- examples/async-rest/pom.xml | 2 +- examples/embedded/pom.xml | 2 +- examples/pom.xml | 2 +- jetty-alpn/jetty-alpn-client/pom.xml | 2 +- .../jetty-alpn-conscrypt-client/pom.xml | 2 +- .../jetty-alpn-conscrypt-server/pom.xml | 2 +- jetty-alpn/jetty-alpn-java-client/pom.xml | 2 +- jetty-alpn/jetty-alpn-java-server/pom.xml | 2 +- jetty-alpn/jetty-alpn-openjdk8-client/pom.xml | 2 +- jetty-alpn/jetty-alpn-openjdk8-server/pom.xml | 2 +- jetty-alpn/jetty-alpn-server/pom.xml | 2 +- jetty-alpn/pom.xml | 2 +- jetty-annotations/pom.xml | 2 +- jetty-ant/pom.xml | 2 +- jetty-bom/pom.xml | 134 +++++++++--------- jetty-cdi/pom.xml | 2 +- jetty-client/pom.xml | 2 +- jetty-continuation/pom.xml | 2 +- jetty-deploy/pom.xml | 2 +- jetty-distribution/pom.xml | 2 +- jetty-documentation/pom.xml | 2 +- jetty-fcgi/fcgi-client/pom.xml | 2 +- jetty-fcgi/fcgi-server/pom.xml | 2 +- jetty-fcgi/pom.xml | 2 +- .../jetty-gcloud-session-manager/pom.xml | 2 +- jetty-gcloud/pom.xml | 2 +- jetty-hazelcast/pom.xml | 2 +- jetty-home/pom.xml | 2 +- jetty-http-spi/pom.xml | 2 +- jetty-http/pom.xml | 2 +- jetty-http2/http2-alpn-tests/pom.xml | 2 +- jetty-http2/http2-client/pom.xml | 2 +- jetty-http2/http2-common/pom.xml | 2 +- jetty-http2/http2-hpack/pom.xml | 2 +- .../http2-http-client-transport/pom.xml | 2 +- jetty-http2/http2-server/pom.xml | 2 +- jetty-http2/pom.xml | 2 +- jetty-infinispan/infinispan-common/pom.xml | 2 +- .../infinispan-embedded-query/pom.xml | 2 +- jetty-infinispan/infinispan-embedded/pom.xml | 2 +- .../infinispan-remote-query/pom.xml | 2 +- jetty-infinispan/infinispan-remote/pom.xml | 2 +- jetty-infinispan/pom.xml | 2 +- jetty-io/pom.xml | 2 +- jetty-jaas/pom.xml | 2 +- jetty-jaspi/pom.xml | 2 +- jetty-jmh/pom.xml | 2 +- jetty-jmx/pom.xml | 2 +- jetty-jndi/pom.xml | 2 +- jetty-jspc-maven-plugin/pom.xml | 2 +- jetty-maven-plugin/pom.xml | 2 +- .../jetty-memcached-sessions/pom.xml | 2 +- jetty-memcached/pom.xml | 2 +- jetty-nosql/pom.xml | 2 +- jetty-openid/pom.xml | 2 +- jetty-osgi/jetty-osgi-alpn/pom.xml | 2 +- jetty-osgi/jetty-osgi-boot-jsp/pom.xml | 2 +- jetty-osgi/jetty-osgi-boot-warurl/pom.xml | 2 +- jetty-osgi/jetty-osgi-boot/pom.xml | 2 +- jetty-osgi/jetty-osgi-httpservice/pom.xml | 2 +- jetty-osgi/pom.xml | 2 +- jetty-osgi/test-jetty-osgi-context/pom.xml | 2 +- jetty-osgi/test-jetty-osgi-fragment/pom.xml | 2 +- jetty-osgi/test-jetty-osgi-server/pom.xml | 2 +- jetty-osgi/test-jetty-osgi-webapp/pom.xml | 2 +- jetty-osgi/test-jetty-osgi/pom.xml | 2 +- jetty-plus/pom.xml | 2 +- jetty-proxy/pom.xml | 2 +- jetty-quickstart/pom.xml | 2 +- jetty-rewrite/pom.xml | 2 +- jetty-runner/pom.xml | 2 +- jetty-security/pom.xml | 2 +- jetty-server/pom.xml | 2 +- jetty-servlet/pom.xml | 2 +- jetty-servlets/pom.xml | 2 +- jetty-spring/pom.xml | 2 +- jetty-start/pom.xml | 2 +- jetty-unixsocket/pom.xml | 2 +- jetty-util-ajax/pom.xml | 2 +- jetty-util/pom.xml | 2 +- jetty-webapp/pom.xml | 2 +- .../javax-websocket-client-impl/pom.xml | 2 +- .../javax-websocket-server-impl/pom.xml | 2 +- jetty-websocket/jetty-websocket-tests/pom.xml | 2 +- jetty-websocket/pom.xml | 2 +- jetty-websocket/websocket-api/pom.xml | 2 +- jetty-websocket/websocket-client/pom.xml | 2 +- jetty-websocket/websocket-common/pom.xml | 2 +- jetty-websocket/websocket-server/pom.xml | 2 +- jetty-websocket/websocket-servlet/pom.xml | 2 +- jetty-xml/pom.xml | 2 +- pom.xml | 2 +- tests/pom.xml | 2 +- tests/test-continuation/pom.xml | 2 +- tests/test-distribution/pom.xml | 2 +- tests/test-http-client-transport/pom.xml | 2 +- tests/test-integration/pom.xml | 2 +- tests/test-jmx/jmx-webapp-it/pom.xml | 2 +- tests/test-jmx/jmx-webapp/pom.xml | 2 +- tests/test-jmx/pom.xml | 2 +- tests/test-loginservice/pom.xml | 2 +- tests/test-quickstart/pom.xml | 2 +- tests/test-sessions/pom.xml | 2 +- .../test-sessions/test-file-sessions/pom.xml | 2 +- .../test-gcloud-sessions/pom.xml | 2 +- .../test-hazelcast-sessions/pom.xml | 2 +- .../test-infinispan-sessions/pom.xml | 2 +- .../test-sessions/test-jdbc-sessions/pom.xml | 2 +- .../test-memcached-sessions/pom.xml | 2 +- .../test-mongodb-sessions/pom.xml | 2 +- .../test-sessions-common/pom.xml | 2 +- tests/test-webapps/pom.xml | 2 +- .../test-cdi-common-webapp/pom.xml | 2 +- tests/test-webapps/test-felix-webapp/pom.xml | 2 +- tests/test-webapps/test-http2-webapp/pom.xml | 2 +- tests/test-webapps/test-jaas-webapp/pom.xml | 2 +- tests/test-webapps/test-jetty-webapp/pom.xml | 2 +- tests/test-webapps/test-jndi-webapp/pom.xml | 2 +- .../test-webapps/test-mock-resources/pom.xml | 2 +- .../test-webapps/test-owb-cdi-webapp/pom.xml | 2 +- tests/test-webapps/test-proxy-webapp/pom.xml | 2 +- tests/test-webapps/test-servlet-spec/pom.xml | 2 +- .../test-container-initializer/pom.xml | 2 +- .../test-spec-webapp/pom.xml | 2 +- .../test-web-fragment/pom.xml | 2 +- tests/test-webapps/test-simple-webapp/pom.xml | 2 +- .../test-webapps/test-webapp-rfc2616/pom.xml | 2 +- .../test-webapps/test-weld-cdi-webapp/pom.xml | 2 +- 136 files changed, 240 insertions(+), 202 deletions(-) diff --git a/VERSION.txt b/VERSION.txt index ccae5569bb9..a18b2cc03c5 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1,4 +1,42 @@ -jetty-9.4.28-SNAPSHOT +jetty-9.4.28.v20200408 - 08 April 2020 + + 847 Setting async timeout on WebSocketClient does not seem to timeout writes + + 2896 Wrong Certificate Selected When Using Multiple Virtual Host Names in + Conscrypt + + 4443 Track backport of ALPN APIs to Java 8 + + 4529 ErrorHandler showing servlet info, can not be disabled unless + overriding most of its functionality + + 4542 servlet context root mapping incorrect + + 4619 Inconsistent library versions notice. + + 4620 Using console-capture with StdErrLog results in empty log file + + 4621 jetty-jaspi in jetty-all uber aggregate artifact requires + javax.security.auth.message.AuthException which cannot be included + + 4628 Add support for conditional module dependencies in jetty-start + + 4631 Startup XmlConfiguration WARN on Arg threadpool + + 4638 maxFormContentSize fix in Issue #3856 broke JenkinsCI/Winstone + + 4644 no injection of env-entry if env-entry-value is whitespace only or + missing + + 4645 Empty "X-Forwarded-Port" header results in NumberFormatException + + 4647 Hazelcast remote.xml configuration file do not configure hazelcast + remote addresses + + 4650 Do not use ServiceLoader every time a WebSocketSession is created + + 4654 Hazelcast configurationLocation is not configurable via mod files + + 4662 Jetty 9.4.x calls ServletContextListener.contextDestroyed() too early + + 4671 CustomRequestLog throws NullPointerException when no request cookie is + present + + 4673 Short reads break form-data multipart parsing + + 4676 ALPN support for Java 15 + + 4682 "UnreadableSessionDataException Unreadable session ..." after upgrading + to 9.4.27 + + 4693 Version 9.4.25 breaks binary compatibility by renaming + Response.closeOutput() + + 4699 ServletContainerInitializer.onStartUp is not called with maven jar + packaging using Jetty Maven Plugin + + 4711 Reset trailers on recycled response + + 4714 Low setMaxConcurrentStreams causes "1/unexpected_data_frame" errors + + 4735 Get env variables in PHP scripts served through FastCGIProxyServlet + + 4737 PreDestroy not called for non-async and run-as servlets + + 4739 @RunAs not honoured on servlets + + 4751 Refresh NetworkTraffic* classes jetty-9.4.27.v20200227 - 27 February 2020 + 3247 Generate jetty-maven-plugin website diff --git a/aggregates/jetty-all-compact3/pom.xml b/aggregates/jetty-all-compact3/pom.xml index aaeda8838b1..1eabf07ded7 100644 --- a/aggregates/jetty-all-compact3/pom.xml +++ b/aggregates/jetty-all-compact3/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28-SNAPSHOT + 9.4.28.v20200408 ../../pom.xml 4.0.0 diff --git a/aggregates/jetty-all/pom.xml b/aggregates/jetty-all/pom.xml index 7bf293ff067..b0a64b4e124 100644 --- a/aggregates/jetty-all/pom.xml +++ b/aggregates/jetty-all/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28-SNAPSHOT + 9.4.28.v20200408 ../../pom.xml 4.0.0 diff --git a/apache-jsp/pom.xml b/apache-jsp/pom.xml index 9a7ea5ed3ff..3cfcb4f2b95 100644 --- a/apache-jsp/pom.xml +++ b/apache-jsp/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 apache-jsp diff --git a/apache-jstl/pom.xml b/apache-jstl/pom.xml index eeff5bc5e5a..60209ea0f8e 100644 --- a/apache-jstl/pom.xml +++ b/apache-jstl/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 apache-jstl diff --git a/build-resources/pom.xml b/build-resources/pom.xml index fc25f203f41..23b8184c142 100644 --- a/build-resources/pom.xml +++ b/build-resources/pom.xml @@ -2,7 +2,7 @@ 4.0.0 org.eclipse.jetty build-resources - 9.4.28-SNAPSHOT + 9.4.28.v20200408 jar Jetty :: Build Resources diff --git a/examples/async-rest/async-rest-jar/pom.xml b/examples/async-rest/async-rest-jar/pom.xml index 10e592c3080..944c5cb2a8b 100644 --- a/examples/async-rest/async-rest-jar/pom.xml +++ b/examples/async-rest/async-rest-jar/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty example-async-rest - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 diff --git a/examples/async-rest/async-rest-webapp/pom.xml b/examples/async-rest/async-rest-webapp/pom.xml index 2bd6113930e..9d8a5820245 100644 --- a/examples/async-rest/async-rest-webapp/pom.xml +++ b/examples/async-rest/async-rest-webapp/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty example-async-rest - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 diff --git a/examples/async-rest/pom.xml b/examples/async-rest/pom.xml index 14b9fc444c6..dad7888ce4e 100644 --- a/examples/async-rest/pom.xml +++ b/examples/async-rest/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty.examples examples-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 diff --git a/examples/embedded/pom.xml b/examples/embedded/pom.xml index 25fa3bf60c9..393ec89f1b1 100644 --- a/examples/embedded/pom.xml +++ b/examples/embedded/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty.examples examples-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 ../pom.xml 4.0.0 diff --git a/examples/pom.xml b/examples/pom.xml index e0c9fbba50b..fab43bf94f1 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty jetty-project - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 diff --git a/jetty-alpn/jetty-alpn-client/pom.xml b/jetty-alpn/jetty-alpn-client/pom.xml index 2b56cc739c0..fc655e73f86 100644 --- a/jetty-alpn/jetty-alpn-client/pom.xml +++ b/jetty-alpn/jetty-alpn-client/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-alpn-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 jetty-alpn-client diff --git a/jetty-alpn/jetty-alpn-conscrypt-client/pom.xml b/jetty-alpn/jetty-alpn-conscrypt-client/pom.xml index 915d9177dfc..173d2bbc3e2 100644 --- a/jetty-alpn/jetty-alpn-conscrypt-client/pom.xml +++ b/jetty-alpn/jetty-alpn-conscrypt-client/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty jetty-alpn-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 diff --git a/jetty-alpn/jetty-alpn-conscrypt-server/pom.xml b/jetty-alpn/jetty-alpn-conscrypt-server/pom.xml index 1e1d9d24680..e5d8910158f 100644 --- a/jetty-alpn/jetty-alpn-conscrypt-server/pom.xml +++ b/jetty-alpn/jetty-alpn-conscrypt-server/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty jetty-alpn-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 diff --git a/jetty-alpn/jetty-alpn-java-client/pom.xml b/jetty-alpn/jetty-alpn-java-client/pom.xml index cd38c4a9069..af355a8f4f5 100644 --- a/jetty-alpn/jetty-alpn-java-client/pom.xml +++ b/jetty-alpn/jetty-alpn-java-client/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty jetty-alpn-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 diff --git a/jetty-alpn/jetty-alpn-java-server/pom.xml b/jetty-alpn/jetty-alpn-java-server/pom.xml index 5582dccfa56..2406fde0c73 100644 --- a/jetty-alpn/jetty-alpn-java-server/pom.xml +++ b/jetty-alpn/jetty-alpn-java-server/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty jetty-alpn-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 diff --git a/jetty-alpn/jetty-alpn-openjdk8-client/pom.xml b/jetty-alpn/jetty-alpn-openjdk8-client/pom.xml index a76767a2f0a..d43d013b807 100644 --- a/jetty-alpn/jetty-alpn-openjdk8-client/pom.xml +++ b/jetty-alpn/jetty-alpn-openjdk8-client/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty jetty-alpn-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 diff --git a/jetty-alpn/jetty-alpn-openjdk8-server/pom.xml b/jetty-alpn/jetty-alpn-openjdk8-server/pom.xml index fa8fc721763..406b540a343 100644 --- a/jetty-alpn/jetty-alpn-openjdk8-server/pom.xml +++ b/jetty-alpn/jetty-alpn-openjdk8-server/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty jetty-alpn-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 diff --git a/jetty-alpn/jetty-alpn-server/pom.xml b/jetty-alpn/jetty-alpn-server/pom.xml index 78ce0638f5a..172f0247238 100644 --- a/jetty-alpn/jetty-alpn-server/pom.xml +++ b/jetty-alpn/jetty-alpn-server/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-alpn-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 jetty-alpn-server diff --git a/jetty-alpn/pom.xml b/jetty-alpn/pom.xml index ea355904386..a0fd982dce6 100644 --- a/jetty-alpn/pom.xml +++ b/jetty-alpn/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 jetty-alpn-parent diff --git a/jetty-annotations/pom.xml b/jetty-annotations/pom.xml index d282ba92614..1fff4ec4d88 100644 --- a/jetty-annotations/pom.xml +++ b/jetty-annotations/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 jetty-annotations diff --git a/jetty-ant/pom.xml b/jetty-ant/pom.xml index b6cbf070fd2..f67827cf3b3 100644 --- a/jetty-ant/pom.xml +++ b/jetty-ant/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 jetty-ant diff --git a/jetty-bom/pom.xml b/jetty-bom/pom.xml index 88c71a8ee6f..fb246fb98b0 100644 --- a/jetty-bom/pom.xml +++ b/jetty-bom/pom.xml @@ -9,7 +9,7 @@ org.eclipse.jetty jetty-project - 9.4.28-SNAPSHOT + 9.4.28.v20200408 @@ -53,336 +53,336 @@ org.eclipse.jetty apache-jsp - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty apache-jstl - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty jetty-alpn-client - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty jetty-alpn-java-client - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty jetty-alpn-java-server - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty jetty-alpn-openjdk8-client - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty jetty-alpn-openjdk8-server - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty jetty-alpn-conscrypt-client - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty jetty-alpn-conscrypt-server - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty jetty-alpn-server - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty jetty-annotations - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty jetty-ant - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty jetty-client - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty jetty-continuation - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty jetty-deploy - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty jetty-distribution - 9.4.28-SNAPSHOT + 9.4.28.v20200408 zip org.eclipse.jetty jetty-distribution - 9.4.28-SNAPSHOT + 9.4.28.v20200408 tar.gz org.eclipse.jetty.fcgi fcgi-client - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty.fcgi fcgi-server - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty.gcloud jetty-gcloud-session-manager - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty jetty-home - 9.4.28-SNAPSHOT + 9.4.28.v20200408 zip org.eclipse.jetty jetty-home - 9.4.28-SNAPSHOT + 9.4.28.v20200408 tar.gz org.eclipse.jetty jetty-http - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty.http2 http2-client - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty.http2 http2-common - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty.http2 http2-hpack - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty.http2 http2-http-client-transport - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty.http2 http2-server - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty jetty-http-spi - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty infinispan-common - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty infinispan-remote-query - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty infinispan-embedded-query - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty jetty-hazelcast - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty jetty-io - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty jetty-jaas - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty jetty-jaspi - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty jetty-jmx - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty jetty-jndi - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty.memcached jetty-memcached-sessions - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty jetty-nosql - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty.osgi jetty-osgi-boot - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty.osgi jetty-osgi-boot-jsp - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty.osgi jetty-osgi-boot-warurl - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty.osgi jetty-httpservice - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty jetty-plus - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty jetty-proxy - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty jetty-quickstart - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty jetty-rewrite - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty jetty-security - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty jetty-openid - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty jetty-server - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty jetty-servlet - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty jetty-servlets - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty jetty-spring - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty jetty-unixsocket - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty jetty-util - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty jetty-util-ajax - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty jetty-webapp - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty.websocket javax-websocket-client-impl - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty.websocket javax-websocket-server-impl - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty.websocket websocket-api - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty.websocket websocket-client - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty.websocket websocket-common - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty.websocket websocket-server - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty.websocket websocket-servlet - 9.4.28-SNAPSHOT + 9.4.28.v20200408 org.eclipse.jetty jetty-xml - 9.4.28-SNAPSHOT + 9.4.28.v20200408 diff --git a/jetty-cdi/pom.xml b/jetty-cdi/pom.xml index 709ee31fbe7..22b8255f18b 100644 --- a/jetty-cdi/pom.xml +++ b/jetty-cdi/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 org.eclipse.jetty diff --git a/jetty-client/pom.xml b/jetty-client/pom.xml index 9c1e35bf6db..cdd49444081 100644 --- a/jetty-client/pom.xml +++ b/jetty-client/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 diff --git a/jetty-continuation/pom.xml b/jetty-continuation/pom.xml index 9b3e9f76b70..b8f0f187835 100644 --- a/jetty-continuation/pom.xml +++ b/jetty-continuation/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 jetty-continuation diff --git a/jetty-deploy/pom.xml b/jetty-deploy/pom.xml index ff231159a36..417d7d953dc 100644 --- a/jetty-deploy/pom.xml +++ b/jetty-deploy/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 jetty-deploy diff --git a/jetty-distribution/pom.xml b/jetty-distribution/pom.xml index d8bc99f5c1d..3f003be87bd 100644 --- a/jetty-distribution/pom.xml +++ b/jetty-distribution/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 jetty-distribution diff --git a/jetty-documentation/pom.xml b/jetty-documentation/pom.xml index ab0c8281a00..119e62f4662 100644 --- a/jetty-documentation/pom.xml +++ b/jetty-documentation/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty jetty-project - 9.4.28-SNAPSHOT + 9.4.28.v20200408 jetty-documentation Jetty :: Documentation diff --git a/jetty-fcgi/fcgi-client/pom.xml b/jetty-fcgi/fcgi-client/pom.xml index c49c236bd1c..06756f19b0a 100644 --- a/jetty-fcgi/fcgi-client/pom.xml +++ b/jetty-fcgi/fcgi-client/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.fcgi fcgi-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 diff --git a/jetty-fcgi/fcgi-server/pom.xml b/jetty-fcgi/fcgi-server/pom.xml index e7df5968c92..221312a6899 100644 --- a/jetty-fcgi/fcgi-server/pom.xml +++ b/jetty-fcgi/fcgi-server/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.fcgi fcgi-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 diff --git a/jetty-fcgi/pom.xml b/jetty-fcgi/pom.xml index c3d93c657ac..e2f1b186d8d 100644 --- a/jetty-fcgi/pom.xml +++ b/jetty-fcgi/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty jetty-project - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 diff --git a/jetty-gcloud/jetty-gcloud-session-manager/pom.xml b/jetty-gcloud/jetty-gcloud-session-manager/pom.xml index 965b2b3de39..464abb4ebf5 100644 --- a/jetty-gcloud/jetty-gcloud-session-manager/pom.xml +++ b/jetty-gcloud/jetty-gcloud-session-manager/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.gcloud gcloud-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 diff --git a/jetty-gcloud/pom.xml b/jetty-gcloud/pom.xml index e982ff2e620..273544d929d 100644 --- a/jetty-gcloud/pom.xml +++ b/jetty-gcloud/pom.xml @@ -3,7 +3,7 @@ jetty-project org.eclipse.jetty - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 diff --git a/jetty-hazelcast/pom.xml b/jetty-hazelcast/pom.xml index f32f3a9bec2..a5ae15d32df 100644 --- a/jetty-hazelcast/pom.xml +++ b/jetty-hazelcast/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty jetty-project - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 diff --git a/jetty-home/pom.xml b/jetty-home/pom.xml index 7cc9e6cf16b..1932babcfd2 100644 --- a/jetty-home/pom.xml +++ b/jetty-home/pom.xml @@ -3,7 +3,7 @@ jetty-project org.eclipse.jetty - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 jetty-home diff --git a/jetty-http-spi/pom.xml b/jetty-http-spi/pom.xml index ec89e2470da..ca132b03a41 100644 --- a/jetty-http-spi/pom.xml +++ b/jetty-http-spi/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 jetty-http-spi diff --git a/jetty-http/pom.xml b/jetty-http/pom.xml index c3f0c4b9e90..b6edfd2bece 100644 --- a/jetty-http/pom.xml +++ b/jetty-http/pom.xml @@ -3,7 +3,7 @@ jetty-project org.eclipse.jetty - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 jetty-http diff --git a/jetty-http2/http2-alpn-tests/pom.xml b/jetty-http2/http2-alpn-tests/pom.xml index 0eb37365467..62491f58b28 100644 --- a/jetty-http2/http2-alpn-tests/pom.xml +++ b/jetty-http2/http2-alpn-tests/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.http2 http2-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 diff --git a/jetty-http2/http2-client/pom.xml b/jetty-http2/http2-client/pom.xml index afdbb0205b4..55c6e48265b 100644 --- a/jetty-http2/http2-client/pom.xml +++ b/jetty-http2/http2-client/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.http2 http2-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 diff --git a/jetty-http2/http2-common/pom.xml b/jetty-http2/http2-common/pom.xml index 9d704efefd1..195767af9bd 100644 --- a/jetty-http2/http2-common/pom.xml +++ b/jetty-http2/http2-common/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.http2 http2-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 diff --git a/jetty-http2/http2-hpack/pom.xml b/jetty-http2/http2-hpack/pom.xml index d03adb5253c..159b8296c63 100644 --- a/jetty-http2/http2-hpack/pom.xml +++ b/jetty-http2/http2-hpack/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.http2 http2-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 diff --git a/jetty-http2/http2-http-client-transport/pom.xml b/jetty-http2/http2-http-client-transport/pom.xml index 6381d1269ce..efc7ff381f0 100644 --- a/jetty-http2/http2-http-client-transport/pom.xml +++ b/jetty-http2/http2-http-client-transport/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.http2 http2-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 diff --git a/jetty-http2/http2-server/pom.xml b/jetty-http2/http2-server/pom.xml index c788e2fa413..a882ab53477 100644 --- a/jetty-http2/http2-server/pom.xml +++ b/jetty-http2/http2-server/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.http2 http2-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 diff --git a/jetty-http2/pom.xml b/jetty-http2/pom.xml index 4139a840928..b0bed74236d 100644 --- a/jetty-http2/pom.xml +++ b/jetty-http2/pom.xml @@ -3,7 +3,7 @@ jetty-project org.eclipse.jetty - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 diff --git a/jetty-infinispan/infinispan-common/pom.xml b/jetty-infinispan/infinispan-common/pom.xml index 1c5d45c0ece..8179109783f 100644 --- a/jetty-infinispan/infinispan-common/pom.xml +++ b/jetty-infinispan/infinispan-common/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty infinispan-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 infinispan-common diff --git a/jetty-infinispan/infinispan-embedded-query/pom.xml b/jetty-infinispan/infinispan-embedded-query/pom.xml index 4540ddf1e96..5a2247ca7d2 100644 --- a/jetty-infinispan/infinispan-embedded-query/pom.xml +++ b/jetty-infinispan/infinispan-embedded-query/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty infinispan-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 infinispan-embedded-query diff --git a/jetty-infinispan/infinispan-embedded/pom.xml b/jetty-infinispan/infinispan-embedded/pom.xml index 2e4384157c1..29e41e4c692 100644 --- a/jetty-infinispan/infinispan-embedded/pom.xml +++ b/jetty-infinispan/infinispan-embedded/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty infinispan-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 infinispan-embedded diff --git a/jetty-infinispan/infinispan-remote-query/pom.xml b/jetty-infinispan/infinispan-remote-query/pom.xml index 210e7fe0ccc..d49d6822640 100644 --- a/jetty-infinispan/infinispan-remote-query/pom.xml +++ b/jetty-infinispan/infinispan-remote-query/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty infinispan-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 infinispan-remote-query diff --git a/jetty-infinispan/infinispan-remote/pom.xml b/jetty-infinispan/infinispan-remote/pom.xml index e0d6487d748..c21b228bf94 100644 --- a/jetty-infinispan/infinispan-remote/pom.xml +++ b/jetty-infinispan/infinispan-remote/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty infinispan-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 infinispan-remote diff --git a/jetty-infinispan/pom.xml b/jetty-infinispan/pom.xml index 0c3f15357fe..026bb5d9d57 100644 --- a/jetty-infinispan/pom.xml +++ b/jetty-infinispan/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty jetty-project - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 diff --git a/jetty-io/pom.xml b/jetty-io/pom.xml index 8e8a813ed8f..605a8aaa02e 100644 --- a/jetty-io/pom.xml +++ b/jetty-io/pom.xml @@ -3,7 +3,7 @@ jetty-project org.eclipse.jetty - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 jetty-io diff --git a/jetty-jaas/pom.xml b/jetty-jaas/pom.xml index 5909961fae1..174444c0eb9 100644 --- a/jetty-jaas/pom.xml +++ b/jetty-jaas/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 jetty-jaas diff --git a/jetty-jaspi/pom.xml b/jetty-jaspi/pom.xml index e3e29a8a3a8..d07dd54c914 100644 --- a/jetty-jaspi/pom.xml +++ b/jetty-jaspi/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 diff --git a/jetty-jmh/pom.xml b/jetty-jmh/pom.xml index c777ddbf14e..f6febdf1185 100644 --- a/jetty-jmh/pom.xml +++ b/jetty-jmh/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 diff --git a/jetty-jmx/pom.xml b/jetty-jmx/pom.xml index 608708778ee..d1f4fb8fe72 100644 --- a/jetty-jmx/pom.xml +++ b/jetty-jmx/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 jetty-jmx diff --git a/jetty-jndi/pom.xml b/jetty-jndi/pom.xml index cda6724747a..4a362722962 100644 --- a/jetty-jndi/pom.xml +++ b/jetty-jndi/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 jetty-jndi diff --git a/jetty-jspc-maven-plugin/pom.xml b/jetty-jspc-maven-plugin/pom.xml index 568252f1b52..e6a033ad8fb 100644 --- a/jetty-jspc-maven-plugin/pom.xml +++ b/jetty-jspc-maven-plugin/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty jetty-project - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 jetty-jspc-maven-plugin diff --git a/jetty-maven-plugin/pom.xml b/jetty-maven-plugin/pom.xml index 4342c4b1e7f..623c6db72b1 100644 --- a/jetty-maven-plugin/pom.xml +++ b/jetty-maven-plugin/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 jetty-maven-plugin diff --git a/jetty-memcached/jetty-memcached-sessions/pom.xml b/jetty-memcached/jetty-memcached-sessions/pom.xml index 872aa42e681..3e3fb5b4af0 100644 --- a/jetty-memcached/jetty-memcached-sessions/pom.xml +++ b/jetty-memcached/jetty-memcached-sessions/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.memcached memcached-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 diff --git a/jetty-memcached/pom.xml b/jetty-memcached/pom.xml index 4fd544ac7b8..ef1ea741272 100644 --- a/jetty-memcached/pom.xml +++ b/jetty-memcached/pom.xml @@ -3,7 +3,7 @@ jetty-project org.eclipse.jetty - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 diff --git a/jetty-nosql/pom.xml b/jetty-nosql/pom.xml index 6bc6479a319..9f5baa72093 100644 --- a/jetty-nosql/pom.xml +++ b/jetty-nosql/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 jetty-nosql diff --git a/jetty-openid/pom.xml b/jetty-openid/pom.xml index 2b6dc89741a..62e4120046d 100644 --- a/jetty-openid/pom.xml +++ b/jetty-openid/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 diff --git a/jetty-osgi/jetty-osgi-alpn/pom.xml b/jetty-osgi/jetty-osgi-alpn/pom.xml index 7045631aae3..963027b94ec 100644 --- a/jetty-osgi/jetty-osgi-alpn/pom.xml +++ b/jetty-osgi/jetty-osgi-alpn/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty.osgi jetty-osgi-project - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 jetty-osgi-alpn diff --git a/jetty-osgi/jetty-osgi-boot-jsp/pom.xml b/jetty-osgi/jetty-osgi-boot-jsp/pom.xml index 2c8f933f206..d1958d9f06d 100644 --- a/jetty-osgi/jetty-osgi-boot-jsp/pom.xml +++ b/jetty-osgi/jetty-osgi-boot-jsp/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty.osgi jetty-osgi-project - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 jetty-osgi-boot-jsp diff --git a/jetty-osgi/jetty-osgi-boot-warurl/pom.xml b/jetty-osgi/jetty-osgi-boot-warurl/pom.xml index 1850bb462df..336ed587621 100644 --- a/jetty-osgi/jetty-osgi-boot-warurl/pom.xml +++ b/jetty-osgi/jetty-osgi-boot-warurl/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty.osgi jetty-osgi-project - 9.4.28-SNAPSHOT + 9.4.28.v20200408 ../pom.xml 4.0.0 diff --git a/jetty-osgi/jetty-osgi-boot/pom.xml b/jetty-osgi/jetty-osgi-boot/pom.xml index 985f89c780b..9fef576bfb6 100644 --- a/jetty-osgi/jetty-osgi-boot/pom.xml +++ b/jetty-osgi/jetty-osgi-boot/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty.osgi jetty-osgi-project - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 jetty-osgi-boot diff --git a/jetty-osgi/jetty-osgi-httpservice/pom.xml b/jetty-osgi/jetty-osgi-httpservice/pom.xml index 492fc79154e..15847b7db59 100644 --- a/jetty-osgi/jetty-osgi-httpservice/pom.xml +++ b/jetty-osgi/jetty-osgi-httpservice/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty.osgi jetty-osgi-project - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 jetty-httpservice diff --git a/jetty-osgi/pom.xml b/jetty-osgi/pom.xml index f1d974b0873..cd3e4ae41f8 100644 --- a/jetty-osgi/pom.xml +++ b/jetty-osgi/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 diff --git a/jetty-osgi/test-jetty-osgi-context/pom.xml b/jetty-osgi/test-jetty-osgi-context/pom.xml index 9424cefe540..63496271666 100644 --- a/jetty-osgi/test-jetty-osgi-context/pom.xml +++ b/jetty-osgi/test-jetty-osgi-context/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty.osgi jetty-osgi-project - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 test-jetty-osgi-context diff --git a/jetty-osgi/test-jetty-osgi-fragment/pom.xml b/jetty-osgi/test-jetty-osgi-fragment/pom.xml index 08231073e44..16ce2bb7979 100644 --- a/jetty-osgi/test-jetty-osgi-fragment/pom.xml +++ b/jetty-osgi/test-jetty-osgi-fragment/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty.osgi jetty-osgi-project - 9.4.28-SNAPSHOT + 9.4.28.v20200408 ../pom.xml 4.0.0 diff --git a/jetty-osgi/test-jetty-osgi-server/pom.xml b/jetty-osgi/test-jetty-osgi-server/pom.xml index cc75d28878d..9f8d8b6c455 100644 --- a/jetty-osgi/test-jetty-osgi-server/pom.xml +++ b/jetty-osgi/test-jetty-osgi-server/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty.osgi jetty-osgi-project - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 test-jetty-osgi-server diff --git a/jetty-osgi/test-jetty-osgi-webapp/pom.xml b/jetty-osgi/test-jetty-osgi-webapp/pom.xml index 1d7f3386da3..685531fe2ca 100644 --- a/jetty-osgi/test-jetty-osgi-webapp/pom.xml +++ b/jetty-osgi/test-jetty-osgi-webapp/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty.osgi jetty-osgi-project - 9.4.28-SNAPSHOT + 9.4.28.v20200408 ../pom.xml 4.0.0 diff --git a/jetty-osgi/test-jetty-osgi/pom.xml b/jetty-osgi/test-jetty-osgi/pom.xml index 600946d2241..a2bb0c47e2b 100644 --- a/jetty-osgi/test-jetty-osgi/pom.xml +++ b/jetty-osgi/test-jetty-osgi/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty.osgi jetty-osgi-project - 9.4.28-SNAPSHOT + 9.4.28.v20200408 ../pom.xml 4.0.0 diff --git a/jetty-plus/pom.xml b/jetty-plus/pom.xml index 25245b0ff34..83895ad6219 100644 --- a/jetty-plus/pom.xml +++ b/jetty-plus/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 jetty-plus diff --git a/jetty-proxy/pom.xml b/jetty-proxy/pom.xml index 5a652934487..3bb5e9aed93 100644 --- a/jetty-proxy/pom.xml +++ b/jetty-proxy/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 jetty-proxy diff --git a/jetty-quickstart/pom.xml b/jetty-quickstart/pom.xml index 8fd5acee465..ef81c7d9f50 100644 --- a/jetty-quickstart/pom.xml +++ b/jetty-quickstart/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 org.eclipse.jetty diff --git a/jetty-rewrite/pom.xml b/jetty-rewrite/pom.xml index 2f0a67774bc..e48c3db8c42 100644 --- a/jetty-rewrite/pom.xml +++ b/jetty-rewrite/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 jetty-rewrite diff --git a/jetty-runner/pom.xml b/jetty-runner/pom.xml index 383d42c8c9a..1e218b4ed60 100644 --- a/jetty-runner/pom.xml +++ b/jetty-runner/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 jetty-runner diff --git a/jetty-security/pom.xml b/jetty-security/pom.xml index fe03a87ab3f..b5a7ec859b6 100644 --- a/jetty-security/pom.xml +++ b/jetty-security/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 jetty-security diff --git a/jetty-server/pom.xml b/jetty-server/pom.xml index e33b32679d5..e1b8513a5be 100644 --- a/jetty-server/pom.xml +++ b/jetty-server/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 jetty-server diff --git a/jetty-servlet/pom.xml b/jetty-servlet/pom.xml index a29402ed6fa..ba3d42d4139 100644 --- a/jetty-servlet/pom.xml +++ b/jetty-servlet/pom.xml @@ -3,7 +3,7 @@ jetty-project org.eclipse.jetty - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 jetty-servlet diff --git a/jetty-servlets/pom.xml b/jetty-servlets/pom.xml index 0f44cad59ea..0cd87fe7d60 100644 --- a/jetty-servlets/pom.xml +++ b/jetty-servlets/pom.xml @@ -3,7 +3,7 @@ jetty-project org.eclipse.jetty - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 jetty-servlets diff --git a/jetty-spring/pom.xml b/jetty-spring/pom.xml index 8f962377568..8a73d6ab249 100644 --- a/jetty-spring/pom.xml +++ b/jetty-spring/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 jetty-spring diff --git a/jetty-start/pom.xml b/jetty-start/pom.xml index 768b7b02cff..21c8aa5f615 100644 --- a/jetty-start/pom.xml +++ b/jetty-start/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 jetty-start diff --git a/jetty-unixsocket/pom.xml b/jetty-unixsocket/pom.xml index 90391db6849..fe6dfe32879 100644 --- a/jetty-unixsocket/pom.xml +++ b/jetty-unixsocket/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 jetty-unixsocket diff --git a/jetty-util-ajax/pom.xml b/jetty-util-ajax/pom.xml index bc40146a46a..50009f5ea60 100644 --- a/jetty-util-ajax/pom.xml +++ b/jetty-util-ajax/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 jetty-util-ajax diff --git a/jetty-util/pom.xml b/jetty-util/pom.xml index 00837fc5a1b..455f784102e 100644 --- a/jetty-util/pom.xml +++ b/jetty-util/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 jetty-util diff --git a/jetty-webapp/pom.xml b/jetty-webapp/pom.xml index 039c9dffad6..194abb28d41 100644 --- a/jetty-webapp/pom.xml +++ b/jetty-webapp/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 jetty-webapp diff --git a/jetty-websocket/javax-websocket-client-impl/pom.xml b/jetty-websocket/javax-websocket-client-impl/pom.xml index fbdff4fcbe2..693d47beabf 100644 --- a/jetty-websocket/javax-websocket-client-impl/pom.xml +++ b/jetty-websocket/javax-websocket-client-impl/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.websocket websocket-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 diff --git a/jetty-websocket/javax-websocket-server-impl/pom.xml b/jetty-websocket/javax-websocket-server-impl/pom.xml index 7ab40c2b1d3..0e0c0a0b318 100644 --- a/jetty-websocket/javax-websocket-server-impl/pom.xml +++ b/jetty-websocket/javax-websocket-server-impl/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.websocket websocket-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 diff --git a/jetty-websocket/jetty-websocket-tests/pom.xml b/jetty-websocket/jetty-websocket-tests/pom.xml index 6e6598bb51b..aecb624ee35 100644 --- a/jetty-websocket/jetty-websocket-tests/pom.xml +++ b/jetty-websocket/jetty-websocket-tests/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.websocket websocket-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 diff --git a/jetty-websocket/pom.xml b/jetty-websocket/pom.xml index c29ebedf926..9c0f8af898f 100644 --- a/jetty-websocket/pom.xml +++ b/jetty-websocket/pom.xml @@ -3,7 +3,7 @@ jetty-project org.eclipse.jetty - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 diff --git a/jetty-websocket/websocket-api/pom.xml b/jetty-websocket/websocket-api/pom.xml index 9eec5d56611..929ecae0165 100644 --- a/jetty-websocket/websocket-api/pom.xml +++ b/jetty-websocket/websocket-api/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.websocket websocket-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 diff --git a/jetty-websocket/websocket-client/pom.xml b/jetty-websocket/websocket-client/pom.xml index 19042689ad8..ca624c66825 100644 --- a/jetty-websocket/websocket-client/pom.xml +++ b/jetty-websocket/websocket-client/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.websocket websocket-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 diff --git a/jetty-websocket/websocket-common/pom.xml b/jetty-websocket/websocket-common/pom.xml index f97d68617fe..f0ed7a19eb0 100644 --- a/jetty-websocket/websocket-common/pom.xml +++ b/jetty-websocket/websocket-common/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.websocket websocket-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 diff --git a/jetty-websocket/websocket-server/pom.xml b/jetty-websocket/websocket-server/pom.xml index afd8c2f53ec..4ea0c98a90f 100644 --- a/jetty-websocket/websocket-server/pom.xml +++ b/jetty-websocket/websocket-server/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.websocket websocket-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 diff --git a/jetty-websocket/websocket-servlet/pom.xml b/jetty-websocket/websocket-servlet/pom.xml index 875cd4f7820..fe62e9468a6 100644 --- a/jetty-websocket/websocket-servlet/pom.xml +++ b/jetty-websocket/websocket-servlet/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.websocket websocket-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 diff --git a/jetty-xml/pom.xml b/jetty-xml/pom.xml index 4af2979745b..f27aefed9a0 100644 --- a/jetty-xml/pom.xml +++ b/jetty-xml/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 jetty-xml diff --git a/pom.xml b/pom.xml index f148d8d433a..c73480e9fc1 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 org.eclipse.jetty jetty-project - 9.4.28-SNAPSHOT + 9.4.28.v20200408 Jetty :: Project The Eclipse Jetty Project pom diff --git a/tests/pom.xml b/tests/pom.xml index 502c070c810..860d9ad1ca8 100644 --- a/tests/pom.xml +++ b/tests/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty jetty-project - 9.4.28-SNAPSHOT + 9.4.28.v20200408 ../pom.xml org.eclipse.jetty.tests diff --git a/tests/test-continuation/pom.xml b/tests/test-continuation/pom.xml index 500de02b4b5..6df822f6b45 100644 --- a/tests/test-continuation/pom.xml +++ b/tests/test-continuation/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests tests-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 ../pom.xml 4.0.0 diff --git a/tests/test-distribution/pom.xml b/tests/test-distribution/pom.xml index a63680c64ca..38d2189ce3b 100644 --- a/tests/test-distribution/pom.xml +++ b/tests/test-distribution/pom.xml @@ -2,7 +2,7 @@ tests-parent org.eclipse.jetty.tests - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 diff --git a/tests/test-http-client-transport/pom.xml b/tests/test-http-client-transport/pom.xml index f647f245df5..838d2e6a259 100644 --- a/tests/test-http-client-transport/pom.xml +++ b/tests/test-http-client-transport/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests tests-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 diff --git a/tests/test-integration/pom.xml b/tests/test-integration/pom.xml index 881f5145965..c5788875a98 100644 --- a/tests/test-integration/pom.xml +++ b/tests/test-integration/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests tests-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 test-integration diff --git a/tests/test-jmx/jmx-webapp-it/pom.xml b/tests/test-jmx/jmx-webapp-it/pom.xml index ec9a807727c..8a780fcfdf8 100644 --- a/tests/test-jmx/jmx-webapp-it/pom.xml +++ b/tests/test-jmx/jmx-webapp-it/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests test-jmx-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 jmx-webapp-it diff --git a/tests/test-jmx/jmx-webapp/pom.xml b/tests/test-jmx/jmx-webapp/pom.xml index b09aaf898ac..0f18e74ab0c 100644 --- a/tests/test-jmx/jmx-webapp/pom.xml +++ b/tests/test-jmx/jmx-webapp/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-jmx-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 jmx-webapp war diff --git a/tests/test-jmx/pom.xml b/tests/test-jmx/pom.xml index f5a352f2e95..5d9f49b83b4 100644 --- a/tests/test-jmx/pom.xml +++ b/tests/test-jmx/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests tests-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 test-jmx-parent diff --git a/tests/test-loginservice/pom.xml b/tests/test-loginservice/pom.xml index ee1c08be5e7..4bec180d21f 100644 --- a/tests/test-loginservice/pom.xml +++ b/tests/test-loginservice/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests tests-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 test-loginservice Jetty Tests :: Login Service diff --git a/tests/test-quickstart/pom.xml b/tests/test-quickstart/pom.xml index 182f33e4636..f84a342be8f 100644 --- a/tests/test-quickstart/pom.xml +++ b/tests/test-quickstart/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests tests-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 ../pom.xml 4.0.0 diff --git a/tests/test-sessions/pom.xml b/tests/test-sessions/pom.xml index f5de90f2753..e4ee636a4e6 100644 --- a/tests/test-sessions/pom.xml +++ b/tests/test-sessions/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests tests-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 test-sessions-parent Jetty Tests :: Sessions :: Parent diff --git a/tests/test-sessions/test-file-sessions/pom.xml b/tests/test-sessions/test-file-sessions/pom.xml index 9fbcbf548c4..7d1311177b9 100644 --- a/tests/test-sessions/test-file-sessions/pom.xml +++ b/tests/test-sessions/test-file-sessions/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-sessions-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 test-file-sessions Jetty Tests :: Sessions :: File diff --git a/tests/test-sessions/test-gcloud-sessions/pom.xml b/tests/test-sessions/test-gcloud-sessions/pom.xml index 197ef52d32c..7fc9f9bb6b9 100644 --- a/tests/test-sessions/test-gcloud-sessions/pom.xml +++ b/tests/test-sessions/test-gcloud-sessions/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-sessions-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 test-gcloud-sessions Jetty Tests :: Sessions :: GCloud diff --git a/tests/test-sessions/test-hazelcast-sessions/pom.xml b/tests/test-sessions/test-hazelcast-sessions/pom.xml index 831aee39f57..94f97a1a6d1 100644 --- a/tests/test-sessions/test-hazelcast-sessions/pom.xml +++ b/tests/test-sessions/test-hazelcast-sessions/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-sessions-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 test-hazelcast-sessions Jetty Tests :: Sessions :: Hazelcast diff --git a/tests/test-sessions/test-infinispan-sessions/pom.xml b/tests/test-sessions/test-infinispan-sessions/pom.xml index 13fb1351891..92a354df743 100644 --- a/tests/test-sessions/test-infinispan-sessions/pom.xml +++ b/tests/test-sessions/test-infinispan-sessions/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-sessions-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 test-infinispan-sessions Jetty Tests :: Sessions :: Infinispan diff --git a/tests/test-sessions/test-jdbc-sessions/pom.xml b/tests/test-sessions/test-jdbc-sessions/pom.xml index 0ed9795be05..63acd900c15 100644 --- a/tests/test-sessions/test-jdbc-sessions/pom.xml +++ b/tests/test-sessions/test-jdbc-sessions/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-sessions-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 test-jdbc-sessions Jetty Tests :: Sessions :: JDBC diff --git a/tests/test-sessions/test-memcached-sessions/pom.xml b/tests/test-sessions/test-memcached-sessions/pom.xml index 1c875e092ab..b5520a67486 100644 --- a/tests/test-sessions/test-memcached-sessions/pom.xml +++ b/tests/test-sessions/test-memcached-sessions/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-sessions-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 test-memcached-sessions Jetty Tests :: Sessions :: Memcached diff --git a/tests/test-sessions/test-mongodb-sessions/pom.xml b/tests/test-sessions/test-mongodb-sessions/pom.xml index 5e424945fd5..266241d83af 100644 --- a/tests/test-sessions/test-mongodb-sessions/pom.xml +++ b/tests/test-sessions/test-mongodb-sessions/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-sessions-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 test-mongodb-sessions Jetty Tests :: Sessions :: Mongo diff --git a/tests/test-sessions/test-sessions-common/pom.xml b/tests/test-sessions/test-sessions-common/pom.xml index 6f5b268398a..faa26d2a9fd 100644 --- a/tests/test-sessions/test-sessions-common/pom.xml +++ b/tests/test-sessions/test-sessions-common/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-sessions-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 test-sessions-common Jetty Tests :: Sessions :: Common diff --git a/tests/test-webapps/pom.xml b/tests/test-webapps/pom.xml index 641c70f20bb..c63f0966b47 100644 --- a/tests/test-webapps/pom.xml +++ b/tests/test-webapps/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests tests-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 ../pom.xml test-webapps-parent diff --git a/tests/test-webapps/test-cdi-common-webapp/pom.xml b/tests/test-webapps/test-cdi-common-webapp/pom.xml index a0f0f030950..d72012db769 100644 --- a/tests/test-webapps/test-cdi-common-webapp/pom.xml +++ b/tests/test-webapps/test-cdi-common-webapp/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests test-webapps-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 diff --git a/tests/test-webapps/test-felix-webapp/pom.xml b/tests/test-webapps/test-felix-webapp/pom.xml index 4d7f2faba6f..85bfc7220de 100644 --- a/tests/test-webapps/test-felix-webapp/pom.xml +++ b/tests/test-webapps/test-felix-webapp/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests test-webapps-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 diff --git a/tests/test-webapps/test-http2-webapp/pom.xml b/tests/test-webapps/test-http2-webapp/pom.xml index e20377fb867..544682c765a 100644 --- a/tests/test-webapps/test-http2-webapp/pom.xml +++ b/tests/test-webapps/test-http2-webapp/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests test-webapps-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 diff --git a/tests/test-webapps/test-jaas-webapp/pom.xml b/tests/test-webapps/test-jaas-webapp/pom.xml index dc56b91cab1..7a3cff0b6bf 100644 --- a/tests/test-webapps/test-jaas-webapp/pom.xml +++ b/tests/test-webapps/test-jaas-webapp/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-webapps-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 test-jaas-webapp Jetty Tests :: WebApp :: JAAS diff --git a/tests/test-webapps/test-jetty-webapp/pom.xml b/tests/test-webapps/test-jetty-webapp/pom.xml index 342851e7726..07d1e669709 100644 --- a/tests/test-webapps/test-jetty-webapp/pom.xml +++ b/tests/test-webapps/test-jetty-webapp/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests test-webapps-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 ../pom.xml 4.0.0 diff --git a/tests/test-webapps/test-jndi-webapp/pom.xml b/tests/test-webapps/test-jndi-webapp/pom.xml index 4cf66fc72c0..1e3cd33b08f 100644 --- a/tests/test-webapps/test-jndi-webapp/pom.xml +++ b/tests/test-webapps/test-jndi-webapp/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-webapps-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 test-jndi-webapp Jetty Tests :: WebApp :: JNDI diff --git a/tests/test-webapps/test-mock-resources/pom.xml b/tests/test-webapps/test-mock-resources/pom.xml index 27ba0bc06e8..7b1b46c85b2 100644 --- a/tests/test-webapps/test-mock-resources/pom.xml +++ b/tests/test-webapps/test-mock-resources/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests test-webapps-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 Jetty Tests :: WebApp :: Mock Resources test-mock-resources diff --git a/tests/test-webapps/test-owb-cdi-webapp/pom.xml b/tests/test-webapps/test-owb-cdi-webapp/pom.xml index 323eca72cf5..6157ecd0f4f 100644 --- a/tests/test-webapps/test-owb-cdi-webapp/pom.xml +++ b/tests/test-webapps/test-owb-cdi-webapp/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests test-webapps-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 diff --git a/tests/test-webapps/test-proxy-webapp/pom.xml b/tests/test-webapps/test-proxy-webapp/pom.xml index abe8613dcfb..5fcd1c21c30 100644 --- a/tests/test-webapps/test-proxy-webapp/pom.xml +++ b/tests/test-webapps/test-proxy-webapp/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests test-webapps-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 ../pom.xml 4.0.0 diff --git a/tests/test-webapps/test-servlet-spec/pom.xml b/tests/test-webapps/test-servlet-spec/pom.xml index 111d09f08c2..26512ed5941 100644 --- a/tests/test-webapps/test-servlet-spec/pom.xml +++ b/tests/test-webapps/test-servlet-spec/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-webapps-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 test-servlet-spec-parent Jetty Tests :: Spec Test WebApp :: Parent diff --git a/tests/test-webapps/test-servlet-spec/test-container-initializer/pom.xml b/tests/test-webapps/test-servlet-spec/test-container-initializer/pom.xml index c2c0ec69ea1..7b9b8cbc1e5 100644 --- a/tests/test-webapps/test-servlet-spec/test-container-initializer/pom.xml +++ b/tests/test-webapps/test-servlet-spec/test-container-initializer/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests test-servlet-spec-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 test-container-initializer jar diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/pom.xml b/tests/test-webapps/test-servlet-spec/test-spec-webapp/pom.xml index 39f7cdf38b7..5b401b80488 100644 --- a/tests/test-webapps/test-servlet-spec/test-spec-webapp/pom.xml +++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-servlet-spec-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 Jetty Tests :: Webapps :: Spec Webapp test-spec-webapp diff --git a/tests/test-webapps/test-servlet-spec/test-web-fragment/pom.xml b/tests/test-webapps/test-servlet-spec/test-web-fragment/pom.xml index 8d9e1abf36b..3e38da60b57 100644 --- a/tests/test-webapps/test-servlet-spec/test-web-fragment/pom.xml +++ b/tests/test-webapps/test-servlet-spec/test-web-fragment/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests test-servlet-spec-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 Jetty Tests :: WebApp :: Servlet Spec :: Fragment Jar diff --git a/tests/test-webapps/test-simple-webapp/pom.xml b/tests/test-webapps/test-simple-webapp/pom.xml index f4c08cbec0d..f815056dcae 100644 --- a/tests/test-webapps/test-simple-webapp/pom.xml +++ b/tests/test-webapps/test-simple-webapp/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-webapps-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 test-simple-webapp diff --git a/tests/test-webapps/test-webapp-rfc2616/pom.xml b/tests/test-webapps/test-webapp-rfc2616/pom.xml index 6267f01dbbf..571534ccedc 100644 --- a/tests/test-webapps/test-webapp-rfc2616/pom.xml +++ b/tests/test-webapps/test-webapp-rfc2616/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-webapps-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 test-webapp-rfc2616 Jetty Tests :: WebApp :: RFC2616 diff --git a/tests/test-webapps/test-weld-cdi-webapp/pom.xml b/tests/test-webapps/test-weld-cdi-webapp/pom.xml index 444615d9251..c31107d0259 100644 --- a/tests/test-webapps/test-weld-cdi-webapp/pom.xml +++ b/tests/test-webapps/test-weld-cdi-webapp/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests test-webapps-parent - 9.4.28-SNAPSHOT + 9.4.28.v20200408 4.0.0 From 5699b9ff3d3ff94cd16385e20def30cbff2f2512 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Wed, 8 Apr 2020 13:15:04 -0500 Subject: [PATCH 064/101] Updating to version 9.4.29-SNAPSHOT --- VERSION.txt | 2 + aggregates/jetty-all-compact3/pom.xml | 2 +- aggregates/jetty-all/pom.xml | 2 +- apache-jsp/pom.xml | 2 +- apache-jstl/pom.xml | 2 +- build-resources/pom.xml | 2 +- examples/async-rest/async-rest-jar/pom.xml | 2 +- examples/async-rest/async-rest-webapp/pom.xml | 2 +- examples/async-rest/pom.xml | 2 +- examples/embedded/pom.xml | 2 +- examples/pom.xml | 2 +- jetty-alpn/jetty-alpn-client/pom.xml | 2 +- .../jetty-alpn-conscrypt-client/pom.xml | 2 +- .../jetty-alpn-conscrypt-server/pom.xml | 2 +- jetty-alpn/jetty-alpn-java-client/pom.xml | 2 +- jetty-alpn/jetty-alpn-java-server/pom.xml | 2 +- jetty-alpn/jetty-alpn-openjdk8-client/pom.xml | 2 +- jetty-alpn/jetty-alpn-openjdk8-server/pom.xml | 2 +- jetty-alpn/jetty-alpn-server/pom.xml | 2 +- jetty-alpn/pom.xml | 2 +- jetty-annotations/pom.xml | 2 +- jetty-ant/pom.xml | 2 +- jetty-bom/pom.xml | 134 +++++++++--------- jetty-cdi/pom.xml | 2 +- jetty-client/pom.xml | 2 +- jetty-continuation/pom.xml | 2 +- jetty-deploy/pom.xml | 2 +- jetty-distribution/pom.xml | 2 +- jetty-documentation/pom.xml | 2 +- jetty-fcgi/fcgi-client/pom.xml | 2 +- jetty-fcgi/fcgi-server/pom.xml | 2 +- jetty-fcgi/pom.xml | 2 +- .../jetty-gcloud-session-manager/pom.xml | 2 +- jetty-gcloud/pom.xml | 2 +- jetty-hazelcast/pom.xml | 2 +- jetty-home/pom.xml | 2 +- jetty-http-spi/pom.xml | 2 +- jetty-http/pom.xml | 2 +- jetty-http2/http2-alpn-tests/pom.xml | 2 +- jetty-http2/http2-client/pom.xml | 2 +- jetty-http2/http2-common/pom.xml | 2 +- jetty-http2/http2-hpack/pom.xml | 2 +- .../http2-http-client-transport/pom.xml | 2 +- jetty-http2/http2-server/pom.xml | 2 +- jetty-http2/pom.xml | 2 +- jetty-infinispan/infinispan-common/pom.xml | 2 +- .../infinispan-embedded-query/pom.xml | 2 +- jetty-infinispan/infinispan-embedded/pom.xml | 2 +- .../infinispan-remote-query/pom.xml | 2 +- jetty-infinispan/infinispan-remote/pom.xml | 2 +- jetty-infinispan/pom.xml | 2 +- jetty-io/pom.xml | 2 +- jetty-jaas/pom.xml | 2 +- jetty-jaspi/pom.xml | 2 +- jetty-jmh/pom.xml | 2 +- jetty-jmx/pom.xml | 2 +- jetty-jndi/pom.xml | 2 +- jetty-jspc-maven-plugin/pom.xml | 2 +- jetty-maven-plugin/pom.xml | 2 +- .../jetty-memcached-sessions/pom.xml | 2 +- jetty-memcached/pom.xml | 2 +- jetty-nosql/pom.xml | 2 +- jetty-openid/pom.xml | 2 +- jetty-osgi/jetty-osgi-alpn/pom.xml | 2 +- jetty-osgi/jetty-osgi-boot-jsp/pom.xml | 2 +- jetty-osgi/jetty-osgi-boot-warurl/pom.xml | 2 +- jetty-osgi/jetty-osgi-boot/pom.xml | 2 +- jetty-osgi/jetty-osgi-httpservice/pom.xml | 2 +- jetty-osgi/pom.xml | 2 +- jetty-osgi/test-jetty-osgi-context/pom.xml | 2 +- jetty-osgi/test-jetty-osgi-fragment/pom.xml | 2 +- jetty-osgi/test-jetty-osgi-server/pom.xml | 2 +- jetty-osgi/test-jetty-osgi-webapp/pom.xml | 2 +- jetty-osgi/test-jetty-osgi/pom.xml | 2 +- jetty-plus/pom.xml | 2 +- jetty-proxy/pom.xml | 2 +- jetty-quickstart/pom.xml | 2 +- jetty-rewrite/pom.xml | 2 +- jetty-runner/pom.xml | 2 +- jetty-security/pom.xml | 2 +- jetty-server/pom.xml | 2 +- jetty-servlet/pom.xml | 2 +- jetty-servlets/pom.xml | 2 +- jetty-spring/pom.xml | 2 +- jetty-start/pom.xml | 2 +- jetty-unixsocket/pom.xml | 2 +- jetty-util-ajax/pom.xml | 2 +- jetty-util/pom.xml | 2 +- jetty-webapp/pom.xml | 2 +- .../javax-websocket-client-impl/pom.xml | 2 +- .../javax-websocket-server-impl/pom.xml | 2 +- jetty-websocket/jetty-websocket-tests/pom.xml | 2 +- jetty-websocket/pom.xml | 2 +- jetty-websocket/websocket-api/pom.xml | 2 +- jetty-websocket/websocket-client/pom.xml | 2 +- jetty-websocket/websocket-common/pom.xml | 2 +- jetty-websocket/websocket-server/pom.xml | 2 +- jetty-websocket/websocket-servlet/pom.xml | 2 +- jetty-xml/pom.xml | 2 +- pom.xml | 2 +- tests/pom.xml | 2 +- tests/test-continuation/pom.xml | 2 +- tests/test-distribution/pom.xml | 2 +- tests/test-http-client-transport/pom.xml | 2 +- tests/test-integration/pom.xml | 2 +- tests/test-jmx/jmx-webapp-it/pom.xml | 2 +- tests/test-jmx/jmx-webapp/pom.xml | 2 +- tests/test-jmx/pom.xml | 2 +- tests/test-loginservice/pom.xml | 2 +- tests/test-quickstart/pom.xml | 2 +- tests/test-sessions/pom.xml | 2 +- .../test-sessions/test-file-sessions/pom.xml | 2 +- .../test-gcloud-sessions/pom.xml | 2 +- .../test-hazelcast-sessions/pom.xml | 2 +- .../test-infinispan-sessions/pom.xml | 2 +- .../test-sessions/test-jdbc-sessions/pom.xml | 2 +- .../test-memcached-sessions/pom.xml | 2 +- .../test-mongodb-sessions/pom.xml | 2 +- .../test-sessions-common/pom.xml | 2 +- tests/test-webapps/pom.xml | 2 +- .../test-cdi-common-webapp/pom.xml | 2 +- tests/test-webapps/test-felix-webapp/pom.xml | 2 +- tests/test-webapps/test-http2-webapp/pom.xml | 2 +- tests/test-webapps/test-jaas-webapp/pom.xml | 2 +- tests/test-webapps/test-jetty-webapp/pom.xml | 2 +- tests/test-webapps/test-jndi-webapp/pom.xml | 2 +- .../test-webapps/test-mock-resources/pom.xml | 2 +- .../test-webapps/test-owb-cdi-webapp/pom.xml | 2 +- tests/test-webapps/test-proxy-webapp/pom.xml | 2 +- tests/test-webapps/test-servlet-spec/pom.xml | 2 +- .../test-container-initializer/pom.xml | 2 +- .../test-spec-webapp/pom.xml | 2 +- .../test-web-fragment/pom.xml | 2 +- tests/test-webapps/test-simple-webapp/pom.xml | 2 +- .../test-webapps/test-webapp-rfc2616/pom.xml | 2 +- .../test-webapps/test-weld-cdi-webapp/pom.xml | 2 +- 136 files changed, 203 insertions(+), 201 deletions(-) diff --git a/VERSION.txt b/VERSION.txt index a18b2cc03c5..c5f5ce782ea 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1,3 +1,5 @@ +jetty-9.4.29-SNAPSHOT + jetty-9.4.28.v20200408 - 08 April 2020 + 847 Setting async timeout on WebSocketClient does not seem to timeout writes + 2896 Wrong Certificate Selected When Using Multiple Virtual Host Names in diff --git a/aggregates/jetty-all-compact3/pom.xml b/aggregates/jetty-all-compact3/pom.xml index 1eabf07ded7..9aa8de61f7d 100644 --- a/aggregates/jetty-all-compact3/pom.xml +++ b/aggregates/jetty-all-compact3/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28.v20200408 + 9.4.29-SNAPSHOT ../../pom.xml 4.0.0 diff --git a/aggregates/jetty-all/pom.xml b/aggregates/jetty-all/pom.xml index b0a64b4e124..c7ba43155b5 100644 --- a/aggregates/jetty-all/pom.xml +++ b/aggregates/jetty-all/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28.v20200408 + 9.4.29-SNAPSHOT ../../pom.xml 4.0.0 diff --git a/apache-jsp/pom.xml b/apache-jsp/pom.xml index 3cfcb4f2b95..d65d9fbcbcc 100644 --- a/apache-jsp/pom.xml +++ b/apache-jsp/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 apache-jsp diff --git a/apache-jstl/pom.xml b/apache-jstl/pom.xml index 60209ea0f8e..7ddb0b852a2 100644 --- a/apache-jstl/pom.xml +++ b/apache-jstl/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 apache-jstl diff --git a/build-resources/pom.xml b/build-resources/pom.xml index 23b8184c142..2c9845d2625 100644 --- a/build-resources/pom.xml +++ b/build-resources/pom.xml @@ -2,7 +2,7 @@ 4.0.0 org.eclipse.jetty build-resources - 9.4.28.v20200408 + 9.4.29-SNAPSHOT jar Jetty :: Build Resources diff --git a/examples/async-rest/async-rest-jar/pom.xml b/examples/async-rest/async-rest-jar/pom.xml index 944c5cb2a8b..85d69939487 100644 --- a/examples/async-rest/async-rest-jar/pom.xml +++ b/examples/async-rest/async-rest-jar/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty example-async-rest - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 diff --git a/examples/async-rest/async-rest-webapp/pom.xml b/examples/async-rest/async-rest-webapp/pom.xml index 9d8a5820245..19486ffe653 100644 --- a/examples/async-rest/async-rest-webapp/pom.xml +++ b/examples/async-rest/async-rest-webapp/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty example-async-rest - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 diff --git a/examples/async-rest/pom.xml b/examples/async-rest/pom.xml index dad7888ce4e..799db801363 100644 --- a/examples/async-rest/pom.xml +++ b/examples/async-rest/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty.examples examples-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 diff --git a/examples/embedded/pom.xml b/examples/embedded/pom.xml index 393ec89f1b1..ef0b4eba461 100644 --- a/examples/embedded/pom.xml +++ b/examples/embedded/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty.examples examples-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT ../pom.xml 4.0.0 diff --git a/examples/pom.xml b/examples/pom.xml index fab43bf94f1..284ef573fee 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty jetty-project - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 diff --git a/jetty-alpn/jetty-alpn-client/pom.xml b/jetty-alpn/jetty-alpn-client/pom.xml index fc655e73f86..8886bd3d421 100644 --- a/jetty-alpn/jetty-alpn-client/pom.xml +++ b/jetty-alpn/jetty-alpn-client/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-alpn-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 jetty-alpn-client diff --git a/jetty-alpn/jetty-alpn-conscrypt-client/pom.xml b/jetty-alpn/jetty-alpn-conscrypt-client/pom.xml index 173d2bbc3e2..a6f273200ae 100644 --- a/jetty-alpn/jetty-alpn-conscrypt-client/pom.xml +++ b/jetty-alpn/jetty-alpn-conscrypt-client/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty jetty-alpn-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 diff --git a/jetty-alpn/jetty-alpn-conscrypt-server/pom.xml b/jetty-alpn/jetty-alpn-conscrypt-server/pom.xml index e5d8910158f..585cfcec66d 100644 --- a/jetty-alpn/jetty-alpn-conscrypt-server/pom.xml +++ b/jetty-alpn/jetty-alpn-conscrypt-server/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty jetty-alpn-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 diff --git a/jetty-alpn/jetty-alpn-java-client/pom.xml b/jetty-alpn/jetty-alpn-java-client/pom.xml index af355a8f4f5..17a626e7e11 100644 --- a/jetty-alpn/jetty-alpn-java-client/pom.xml +++ b/jetty-alpn/jetty-alpn-java-client/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty jetty-alpn-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 diff --git a/jetty-alpn/jetty-alpn-java-server/pom.xml b/jetty-alpn/jetty-alpn-java-server/pom.xml index 2406fde0c73..af2f2fbe46e 100644 --- a/jetty-alpn/jetty-alpn-java-server/pom.xml +++ b/jetty-alpn/jetty-alpn-java-server/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty jetty-alpn-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 diff --git a/jetty-alpn/jetty-alpn-openjdk8-client/pom.xml b/jetty-alpn/jetty-alpn-openjdk8-client/pom.xml index d43d013b807..e7b25f6c49f 100644 --- a/jetty-alpn/jetty-alpn-openjdk8-client/pom.xml +++ b/jetty-alpn/jetty-alpn-openjdk8-client/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty jetty-alpn-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 diff --git a/jetty-alpn/jetty-alpn-openjdk8-server/pom.xml b/jetty-alpn/jetty-alpn-openjdk8-server/pom.xml index 406b540a343..4a646c4078a 100644 --- a/jetty-alpn/jetty-alpn-openjdk8-server/pom.xml +++ b/jetty-alpn/jetty-alpn-openjdk8-server/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty jetty-alpn-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 diff --git a/jetty-alpn/jetty-alpn-server/pom.xml b/jetty-alpn/jetty-alpn-server/pom.xml index 172f0247238..9d79ac35845 100644 --- a/jetty-alpn/jetty-alpn-server/pom.xml +++ b/jetty-alpn/jetty-alpn-server/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-alpn-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 jetty-alpn-server diff --git a/jetty-alpn/pom.xml b/jetty-alpn/pom.xml index a0fd982dce6..5744bbbbf71 100644 --- a/jetty-alpn/pom.xml +++ b/jetty-alpn/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 jetty-alpn-parent diff --git a/jetty-annotations/pom.xml b/jetty-annotations/pom.xml index 1fff4ec4d88..fe43d65faeb 100644 --- a/jetty-annotations/pom.xml +++ b/jetty-annotations/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 jetty-annotations diff --git a/jetty-ant/pom.xml b/jetty-ant/pom.xml index f67827cf3b3..5bb1b54ea29 100644 --- a/jetty-ant/pom.xml +++ b/jetty-ant/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 jetty-ant diff --git a/jetty-bom/pom.xml b/jetty-bom/pom.xml index fb246fb98b0..25fb9d37eb3 100644 --- a/jetty-bom/pom.xml +++ b/jetty-bom/pom.xml @@ -9,7 +9,7 @@ org.eclipse.jetty jetty-project - 9.4.28.v20200408 + 9.4.29-SNAPSHOT @@ -53,336 +53,336 @@ org.eclipse.jetty apache-jsp - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty apache-jstl - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty jetty-alpn-client - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty jetty-alpn-java-client - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty jetty-alpn-java-server - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty jetty-alpn-openjdk8-client - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty jetty-alpn-openjdk8-server - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty jetty-alpn-conscrypt-client - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty jetty-alpn-conscrypt-server - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty jetty-alpn-server - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty jetty-annotations - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty jetty-ant - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty jetty-client - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty jetty-continuation - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty jetty-deploy - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty jetty-distribution - 9.4.28.v20200408 + 9.4.29-SNAPSHOT zip org.eclipse.jetty jetty-distribution - 9.4.28.v20200408 + 9.4.29-SNAPSHOT tar.gz org.eclipse.jetty.fcgi fcgi-client - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty.fcgi fcgi-server - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty.gcloud jetty-gcloud-session-manager - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty jetty-home - 9.4.28.v20200408 + 9.4.29-SNAPSHOT zip org.eclipse.jetty jetty-home - 9.4.28.v20200408 + 9.4.29-SNAPSHOT tar.gz org.eclipse.jetty jetty-http - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty.http2 http2-client - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty.http2 http2-common - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty.http2 http2-hpack - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty.http2 http2-http-client-transport - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty.http2 http2-server - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty jetty-http-spi - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty infinispan-common - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty infinispan-remote-query - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty infinispan-embedded-query - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty jetty-hazelcast - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty jetty-io - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty jetty-jaas - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty jetty-jaspi - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty jetty-jmx - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty jetty-jndi - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty.memcached jetty-memcached-sessions - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty jetty-nosql - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty.osgi jetty-osgi-boot - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty.osgi jetty-osgi-boot-jsp - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty.osgi jetty-osgi-boot-warurl - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty.osgi jetty-httpservice - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty jetty-plus - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty jetty-proxy - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty jetty-quickstart - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty jetty-rewrite - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty jetty-security - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty jetty-openid - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty jetty-server - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty jetty-servlet - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty jetty-servlets - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty jetty-spring - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty jetty-unixsocket - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty jetty-util - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty jetty-util-ajax - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty jetty-webapp - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty.websocket javax-websocket-client-impl - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty.websocket javax-websocket-server-impl - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty.websocket websocket-api - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty.websocket websocket-client - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty.websocket websocket-common - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty.websocket websocket-server - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty.websocket websocket-servlet - 9.4.28.v20200408 + 9.4.29-SNAPSHOT org.eclipse.jetty jetty-xml - 9.4.28.v20200408 + 9.4.29-SNAPSHOT diff --git a/jetty-cdi/pom.xml b/jetty-cdi/pom.xml index 22b8255f18b..192eeba6002 100644 --- a/jetty-cdi/pom.xml +++ b/jetty-cdi/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 org.eclipse.jetty diff --git a/jetty-client/pom.xml b/jetty-client/pom.xml index cdd49444081..76409b9bfa7 100644 --- a/jetty-client/pom.xml +++ b/jetty-client/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 diff --git a/jetty-continuation/pom.xml b/jetty-continuation/pom.xml index b8f0f187835..0c1e7a6540d 100644 --- a/jetty-continuation/pom.xml +++ b/jetty-continuation/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 jetty-continuation diff --git a/jetty-deploy/pom.xml b/jetty-deploy/pom.xml index 417d7d953dc..aac2955999a 100644 --- a/jetty-deploy/pom.xml +++ b/jetty-deploy/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 jetty-deploy diff --git a/jetty-distribution/pom.xml b/jetty-distribution/pom.xml index 3f003be87bd..1e60b7b3190 100644 --- a/jetty-distribution/pom.xml +++ b/jetty-distribution/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 jetty-distribution diff --git a/jetty-documentation/pom.xml b/jetty-documentation/pom.xml index 119e62f4662..62da69a54b1 100644 --- a/jetty-documentation/pom.xml +++ b/jetty-documentation/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty jetty-project - 9.4.28.v20200408 + 9.4.29-SNAPSHOT jetty-documentation Jetty :: Documentation diff --git a/jetty-fcgi/fcgi-client/pom.xml b/jetty-fcgi/fcgi-client/pom.xml index 06756f19b0a..8519df4d6f2 100644 --- a/jetty-fcgi/fcgi-client/pom.xml +++ b/jetty-fcgi/fcgi-client/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.fcgi fcgi-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 diff --git a/jetty-fcgi/fcgi-server/pom.xml b/jetty-fcgi/fcgi-server/pom.xml index 221312a6899..72775eaae08 100644 --- a/jetty-fcgi/fcgi-server/pom.xml +++ b/jetty-fcgi/fcgi-server/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.fcgi fcgi-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 diff --git a/jetty-fcgi/pom.xml b/jetty-fcgi/pom.xml index e2f1b186d8d..93483624a72 100644 --- a/jetty-fcgi/pom.xml +++ b/jetty-fcgi/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty jetty-project - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 diff --git a/jetty-gcloud/jetty-gcloud-session-manager/pom.xml b/jetty-gcloud/jetty-gcloud-session-manager/pom.xml index 464abb4ebf5..07bc061d319 100644 --- a/jetty-gcloud/jetty-gcloud-session-manager/pom.xml +++ b/jetty-gcloud/jetty-gcloud-session-manager/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.gcloud gcloud-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 diff --git a/jetty-gcloud/pom.xml b/jetty-gcloud/pom.xml index 273544d929d..db7045d9a4a 100644 --- a/jetty-gcloud/pom.xml +++ b/jetty-gcloud/pom.xml @@ -3,7 +3,7 @@ jetty-project org.eclipse.jetty - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 diff --git a/jetty-hazelcast/pom.xml b/jetty-hazelcast/pom.xml index a5ae15d32df..c9b01e3d9cd 100644 --- a/jetty-hazelcast/pom.xml +++ b/jetty-hazelcast/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty jetty-project - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 diff --git a/jetty-home/pom.xml b/jetty-home/pom.xml index 1932babcfd2..e97c7dbf02f 100644 --- a/jetty-home/pom.xml +++ b/jetty-home/pom.xml @@ -3,7 +3,7 @@ jetty-project org.eclipse.jetty - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 jetty-home diff --git a/jetty-http-spi/pom.xml b/jetty-http-spi/pom.xml index ca132b03a41..bc4f4b040bd 100644 --- a/jetty-http-spi/pom.xml +++ b/jetty-http-spi/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 jetty-http-spi diff --git a/jetty-http/pom.xml b/jetty-http/pom.xml index b6edfd2bece..d5c97ec4f16 100644 --- a/jetty-http/pom.xml +++ b/jetty-http/pom.xml @@ -3,7 +3,7 @@ jetty-project org.eclipse.jetty - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 jetty-http diff --git a/jetty-http2/http2-alpn-tests/pom.xml b/jetty-http2/http2-alpn-tests/pom.xml index 62491f58b28..d397a5e22d1 100644 --- a/jetty-http2/http2-alpn-tests/pom.xml +++ b/jetty-http2/http2-alpn-tests/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.http2 http2-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 diff --git a/jetty-http2/http2-client/pom.xml b/jetty-http2/http2-client/pom.xml index 55c6e48265b..b57b00b5a24 100644 --- a/jetty-http2/http2-client/pom.xml +++ b/jetty-http2/http2-client/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.http2 http2-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 diff --git a/jetty-http2/http2-common/pom.xml b/jetty-http2/http2-common/pom.xml index 195767af9bd..a8e58c545b7 100644 --- a/jetty-http2/http2-common/pom.xml +++ b/jetty-http2/http2-common/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.http2 http2-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 diff --git a/jetty-http2/http2-hpack/pom.xml b/jetty-http2/http2-hpack/pom.xml index 159b8296c63..add16c2afd1 100644 --- a/jetty-http2/http2-hpack/pom.xml +++ b/jetty-http2/http2-hpack/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.http2 http2-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 diff --git a/jetty-http2/http2-http-client-transport/pom.xml b/jetty-http2/http2-http-client-transport/pom.xml index efc7ff381f0..3f8490b9ab8 100644 --- a/jetty-http2/http2-http-client-transport/pom.xml +++ b/jetty-http2/http2-http-client-transport/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.http2 http2-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 diff --git a/jetty-http2/http2-server/pom.xml b/jetty-http2/http2-server/pom.xml index a882ab53477..77eb61f3b35 100644 --- a/jetty-http2/http2-server/pom.xml +++ b/jetty-http2/http2-server/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.http2 http2-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 diff --git a/jetty-http2/pom.xml b/jetty-http2/pom.xml index b0bed74236d..d11f82a9f25 100644 --- a/jetty-http2/pom.xml +++ b/jetty-http2/pom.xml @@ -3,7 +3,7 @@ jetty-project org.eclipse.jetty - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 diff --git a/jetty-infinispan/infinispan-common/pom.xml b/jetty-infinispan/infinispan-common/pom.xml index 8179109783f..620748bdb67 100644 --- a/jetty-infinispan/infinispan-common/pom.xml +++ b/jetty-infinispan/infinispan-common/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty infinispan-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 infinispan-common diff --git a/jetty-infinispan/infinispan-embedded-query/pom.xml b/jetty-infinispan/infinispan-embedded-query/pom.xml index 5a2247ca7d2..d2555f9d845 100644 --- a/jetty-infinispan/infinispan-embedded-query/pom.xml +++ b/jetty-infinispan/infinispan-embedded-query/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty infinispan-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 infinispan-embedded-query diff --git a/jetty-infinispan/infinispan-embedded/pom.xml b/jetty-infinispan/infinispan-embedded/pom.xml index 29e41e4c692..f33d4474e97 100644 --- a/jetty-infinispan/infinispan-embedded/pom.xml +++ b/jetty-infinispan/infinispan-embedded/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty infinispan-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 infinispan-embedded diff --git a/jetty-infinispan/infinispan-remote-query/pom.xml b/jetty-infinispan/infinispan-remote-query/pom.xml index d49d6822640..a9ec47afbaa 100644 --- a/jetty-infinispan/infinispan-remote-query/pom.xml +++ b/jetty-infinispan/infinispan-remote-query/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty infinispan-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 infinispan-remote-query diff --git a/jetty-infinispan/infinispan-remote/pom.xml b/jetty-infinispan/infinispan-remote/pom.xml index c21b228bf94..63235a66170 100644 --- a/jetty-infinispan/infinispan-remote/pom.xml +++ b/jetty-infinispan/infinispan-remote/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty infinispan-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 infinispan-remote diff --git a/jetty-infinispan/pom.xml b/jetty-infinispan/pom.xml index 026bb5d9d57..75103e182df 100644 --- a/jetty-infinispan/pom.xml +++ b/jetty-infinispan/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty jetty-project - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 diff --git a/jetty-io/pom.xml b/jetty-io/pom.xml index 605a8aaa02e..5efa95c6c7e 100644 --- a/jetty-io/pom.xml +++ b/jetty-io/pom.xml @@ -3,7 +3,7 @@ jetty-project org.eclipse.jetty - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 jetty-io diff --git a/jetty-jaas/pom.xml b/jetty-jaas/pom.xml index 174444c0eb9..72b221acfc5 100644 --- a/jetty-jaas/pom.xml +++ b/jetty-jaas/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 jetty-jaas diff --git a/jetty-jaspi/pom.xml b/jetty-jaspi/pom.xml index d07dd54c914..eb3f6deedc1 100644 --- a/jetty-jaspi/pom.xml +++ b/jetty-jaspi/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 diff --git a/jetty-jmh/pom.xml b/jetty-jmh/pom.xml index f6febdf1185..ab479d23d85 100644 --- a/jetty-jmh/pom.xml +++ b/jetty-jmh/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 diff --git a/jetty-jmx/pom.xml b/jetty-jmx/pom.xml index d1f4fb8fe72..3a650bd610b 100644 --- a/jetty-jmx/pom.xml +++ b/jetty-jmx/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 jetty-jmx diff --git a/jetty-jndi/pom.xml b/jetty-jndi/pom.xml index 4a362722962..5a8c24718a2 100644 --- a/jetty-jndi/pom.xml +++ b/jetty-jndi/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 jetty-jndi diff --git a/jetty-jspc-maven-plugin/pom.xml b/jetty-jspc-maven-plugin/pom.xml index e6a033ad8fb..c75714cf367 100644 --- a/jetty-jspc-maven-plugin/pom.xml +++ b/jetty-jspc-maven-plugin/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty jetty-project - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 jetty-jspc-maven-plugin diff --git a/jetty-maven-plugin/pom.xml b/jetty-maven-plugin/pom.xml index 623c6db72b1..ff00b926c2d 100644 --- a/jetty-maven-plugin/pom.xml +++ b/jetty-maven-plugin/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 jetty-maven-plugin diff --git a/jetty-memcached/jetty-memcached-sessions/pom.xml b/jetty-memcached/jetty-memcached-sessions/pom.xml index 3e3fb5b4af0..270f026712a 100644 --- a/jetty-memcached/jetty-memcached-sessions/pom.xml +++ b/jetty-memcached/jetty-memcached-sessions/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.memcached memcached-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 diff --git a/jetty-memcached/pom.xml b/jetty-memcached/pom.xml index ef1ea741272..e96cf2d6061 100644 --- a/jetty-memcached/pom.xml +++ b/jetty-memcached/pom.xml @@ -3,7 +3,7 @@ jetty-project org.eclipse.jetty - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 diff --git a/jetty-nosql/pom.xml b/jetty-nosql/pom.xml index 9f5baa72093..8b6c28b4465 100644 --- a/jetty-nosql/pom.xml +++ b/jetty-nosql/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 jetty-nosql diff --git a/jetty-openid/pom.xml b/jetty-openid/pom.xml index 62e4120046d..32f4dda6715 100644 --- a/jetty-openid/pom.xml +++ b/jetty-openid/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 diff --git a/jetty-osgi/jetty-osgi-alpn/pom.xml b/jetty-osgi/jetty-osgi-alpn/pom.xml index 963027b94ec..921e25a3a70 100644 --- a/jetty-osgi/jetty-osgi-alpn/pom.xml +++ b/jetty-osgi/jetty-osgi-alpn/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty.osgi jetty-osgi-project - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 jetty-osgi-alpn diff --git a/jetty-osgi/jetty-osgi-boot-jsp/pom.xml b/jetty-osgi/jetty-osgi-boot-jsp/pom.xml index d1958d9f06d..f9214dab3b1 100644 --- a/jetty-osgi/jetty-osgi-boot-jsp/pom.xml +++ b/jetty-osgi/jetty-osgi-boot-jsp/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty.osgi jetty-osgi-project - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 jetty-osgi-boot-jsp diff --git a/jetty-osgi/jetty-osgi-boot-warurl/pom.xml b/jetty-osgi/jetty-osgi-boot-warurl/pom.xml index 336ed587621..ba3721467c0 100644 --- a/jetty-osgi/jetty-osgi-boot-warurl/pom.xml +++ b/jetty-osgi/jetty-osgi-boot-warurl/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty.osgi jetty-osgi-project - 9.4.28.v20200408 + 9.4.29-SNAPSHOT ../pom.xml 4.0.0 diff --git a/jetty-osgi/jetty-osgi-boot/pom.xml b/jetty-osgi/jetty-osgi-boot/pom.xml index 9fef576bfb6..e31b62d64d7 100644 --- a/jetty-osgi/jetty-osgi-boot/pom.xml +++ b/jetty-osgi/jetty-osgi-boot/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty.osgi jetty-osgi-project - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 jetty-osgi-boot diff --git a/jetty-osgi/jetty-osgi-httpservice/pom.xml b/jetty-osgi/jetty-osgi-httpservice/pom.xml index 15847b7db59..4d20c4f092a 100644 --- a/jetty-osgi/jetty-osgi-httpservice/pom.xml +++ b/jetty-osgi/jetty-osgi-httpservice/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty.osgi jetty-osgi-project - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 jetty-httpservice diff --git a/jetty-osgi/pom.xml b/jetty-osgi/pom.xml index cd3e4ae41f8..eade9567f7e 100644 --- a/jetty-osgi/pom.xml +++ b/jetty-osgi/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 diff --git a/jetty-osgi/test-jetty-osgi-context/pom.xml b/jetty-osgi/test-jetty-osgi-context/pom.xml index 63496271666..5cc2e8d5fea 100644 --- a/jetty-osgi/test-jetty-osgi-context/pom.xml +++ b/jetty-osgi/test-jetty-osgi-context/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty.osgi jetty-osgi-project - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 test-jetty-osgi-context diff --git a/jetty-osgi/test-jetty-osgi-fragment/pom.xml b/jetty-osgi/test-jetty-osgi-fragment/pom.xml index 16ce2bb7979..ff6c269a776 100644 --- a/jetty-osgi/test-jetty-osgi-fragment/pom.xml +++ b/jetty-osgi/test-jetty-osgi-fragment/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty.osgi jetty-osgi-project - 9.4.28.v20200408 + 9.4.29-SNAPSHOT ../pom.xml 4.0.0 diff --git a/jetty-osgi/test-jetty-osgi-server/pom.xml b/jetty-osgi/test-jetty-osgi-server/pom.xml index 9f8d8b6c455..1ca62e2f40f 100644 --- a/jetty-osgi/test-jetty-osgi-server/pom.xml +++ b/jetty-osgi/test-jetty-osgi-server/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty.osgi jetty-osgi-project - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 test-jetty-osgi-server diff --git a/jetty-osgi/test-jetty-osgi-webapp/pom.xml b/jetty-osgi/test-jetty-osgi-webapp/pom.xml index 685531fe2ca..a2c965a77a2 100644 --- a/jetty-osgi/test-jetty-osgi-webapp/pom.xml +++ b/jetty-osgi/test-jetty-osgi-webapp/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty.osgi jetty-osgi-project - 9.4.28.v20200408 + 9.4.29-SNAPSHOT ../pom.xml 4.0.0 diff --git a/jetty-osgi/test-jetty-osgi/pom.xml b/jetty-osgi/test-jetty-osgi/pom.xml index a2bb0c47e2b..48d65d94c39 100644 --- a/jetty-osgi/test-jetty-osgi/pom.xml +++ b/jetty-osgi/test-jetty-osgi/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty.osgi jetty-osgi-project - 9.4.28.v20200408 + 9.4.29-SNAPSHOT ../pom.xml 4.0.0 diff --git a/jetty-plus/pom.xml b/jetty-plus/pom.xml index 83895ad6219..40959c65258 100644 --- a/jetty-plus/pom.xml +++ b/jetty-plus/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 jetty-plus diff --git a/jetty-proxy/pom.xml b/jetty-proxy/pom.xml index 3bb5e9aed93..43cc78553d8 100644 --- a/jetty-proxy/pom.xml +++ b/jetty-proxy/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 jetty-proxy diff --git a/jetty-quickstart/pom.xml b/jetty-quickstart/pom.xml index ef81c7d9f50..8b9619227d0 100644 --- a/jetty-quickstart/pom.xml +++ b/jetty-quickstart/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 org.eclipse.jetty diff --git a/jetty-rewrite/pom.xml b/jetty-rewrite/pom.xml index e48c3db8c42..2d9265edf06 100644 --- a/jetty-rewrite/pom.xml +++ b/jetty-rewrite/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 jetty-rewrite diff --git a/jetty-runner/pom.xml b/jetty-runner/pom.xml index 1e218b4ed60..4bd67315249 100644 --- a/jetty-runner/pom.xml +++ b/jetty-runner/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 jetty-runner diff --git a/jetty-security/pom.xml b/jetty-security/pom.xml index b5a7ec859b6..8a74827cb50 100644 --- a/jetty-security/pom.xml +++ b/jetty-security/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 jetty-security diff --git a/jetty-server/pom.xml b/jetty-server/pom.xml index e1b8513a5be..8e549a01400 100644 --- a/jetty-server/pom.xml +++ b/jetty-server/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 jetty-server diff --git a/jetty-servlet/pom.xml b/jetty-servlet/pom.xml index ba3d42d4139..54eee174c55 100644 --- a/jetty-servlet/pom.xml +++ b/jetty-servlet/pom.xml @@ -3,7 +3,7 @@ jetty-project org.eclipse.jetty - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 jetty-servlet diff --git a/jetty-servlets/pom.xml b/jetty-servlets/pom.xml index 0cd87fe7d60..4a97a896d45 100644 --- a/jetty-servlets/pom.xml +++ b/jetty-servlets/pom.xml @@ -3,7 +3,7 @@ jetty-project org.eclipse.jetty - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 jetty-servlets diff --git a/jetty-spring/pom.xml b/jetty-spring/pom.xml index 8a73d6ab249..6bbbbfb8752 100644 --- a/jetty-spring/pom.xml +++ b/jetty-spring/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 jetty-spring diff --git a/jetty-start/pom.xml b/jetty-start/pom.xml index 21c8aa5f615..e4f9c8adda1 100644 --- a/jetty-start/pom.xml +++ b/jetty-start/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 jetty-start diff --git a/jetty-unixsocket/pom.xml b/jetty-unixsocket/pom.xml index fe6dfe32879..5f7bfe1722d 100644 --- a/jetty-unixsocket/pom.xml +++ b/jetty-unixsocket/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 jetty-unixsocket diff --git a/jetty-util-ajax/pom.xml b/jetty-util-ajax/pom.xml index 50009f5ea60..bb793877902 100644 --- a/jetty-util-ajax/pom.xml +++ b/jetty-util-ajax/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 jetty-util-ajax diff --git a/jetty-util/pom.xml b/jetty-util/pom.xml index 455f784102e..8c4eed9715e 100644 --- a/jetty-util/pom.xml +++ b/jetty-util/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 jetty-util diff --git a/jetty-webapp/pom.xml b/jetty-webapp/pom.xml index 194abb28d41..f405910ae23 100644 --- a/jetty-webapp/pom.xml +++ b/jetty-webapp/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 jetty-webapp diff --git a/jetty-websocket/javax-websocket-client-impl/pom.xml b/jetty-websocket/javax-websocket-client-impl/pom.xml index 693d47beabf..045ef095e7b 100644 --- a/jetty-websocket/javax-websocket-client-impl/pom.xml +++ b/jetty-websocket/javax-websocket-client-impl/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.websocket websocket-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 diff --git a/jetty-websocket/javax-websocket-server-impl/pom.xml b/jetty-websocket/javax-websocket-server-impl/pom.xml index 0e0c0a0b318..abeb0bc434e 100644 --- a/jetty-websocket/javax-websocket-server-impl/pom.xml +++ b/jetty-websocket/javax-websocket-server-impl/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.websocket websocket-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 diff --git a/jetty-websocket/jetty-websocket-tests/pom.xml b/jetty-websocket/jetty-websocket-tests/pom.xml index aecb624ee35..ac4e5befd7b 100644 --- a/jetty-websocket/jetty-websocket-tests/pom.xml +++ b/jetty-websocket/jetty-websocket-tests/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.websocket websocket-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 diff --git a/jetty-websocket/pom.xml b/jetty-websocket/pom.xml index 9c0f8af898f..1215318c6c7 100644 --- a/jetty-websocket/pom.xml +++ b/jetty-websocket/pom.xml @@ -3,7 +3,7 @@ jetty-project org.eclipse.jetty - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 diff --git a/jetty-websocket/websocket-api/pom.xml b/jetty-websocket/websocket-api/pom.xml index 929ecae0165..975d700d283 100644 --- a/jetty-websocket/websocket-api/pom.xml +++ b/jetty-websocket/websocket-api/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.websocket websocket-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 diff --git a/jetty-websocket/websocket-client/pom.xml b/jetty-websocket/websocket-client/pom.xml index ca624c66825..a5c7894f114 100644 --- a/jetty-websocket/websocket-client/pom.xml +++ b/jetty-websocket/websocket-client/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.websocket websocket-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 diff --git a/jetty-websocket/websocket-common/pom.xml b/jetty-websocket/websocket-common/pom.xml index f0ed7a19eb0..ced91037eab 100644 --- a/jetty-websocket/websocket-common/pom.xml +++ b/jetty-websocket/websocket-common/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.websocket websocket-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 diff --git a/jetty-websocket/websocket-server/pom.xml b/jetty-websocket/websocket-server/pom.xml index 4ea0c98a90f..48ec0c302cc 100644 --- a/jetty-websocket/websocket-server/pom.xml +++ b/jetty-websocket/websocket-server/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.websocket websocket-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 diff --git a/jetty-websocket/websocket-servlet/pom.xml b/jetty-websocket/websocket-servlet/pom.xml index fe62e9468a6..52759d5c128 100644 --- a/jetty-websocket/websocket-servlet/pom.xml +++ b/jetty-websocket/websocket-servlet/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.websocket websocket-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 diff --git a/jetty-xml/pom.xml b/jetty-xml/pom.xml index f27aefed9a0..5c1653c42cd 100644 --- a/jetty-xml/pom.xml +++ b/jetty-xml/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 jetty-xml diff --git a/pom.xml b/pom.xml index c73480e9fc1..87af7001236 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 org.eclipse.jetty jetty-project - 9.4.28.v20200408 + 9.4.29-SNAPSHOT Jetty :: Project The Eclipse Jetty Project pom diff --git a/tests/pom.xml b/tests/pom.xml index 860d9ad1ca8..c13d4db6ea4 100644 --- a/tests/pom.xml +++ b/tests/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty jetty-project - 9.4.28.v20200408 + 9.4.29-SNAPSHOT ../pom.xml org.eclipse.jetty.tests diff --git a/tests/test-continuation/pom.xml b/tests/test-continuation/pom.xml index 6df822f6b45..1494e53b224 100644 --- a/tests/test-continuation/pom.xml +++ b/tests/test-continuation/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests tests-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT ../pom.xml 4.0.0 diff --git a/tests/test-distribution/pom.xml b/tests/test-distribution/pom.xml index 38d2189ce3b..85d592e07c6 100644 --- a/tests/test-distribution/pom.xml +++ b/tests/test-distribution/pom.xml @@ -2,7 +2,7 @@ tests-parent org.eclipse.jetty.tests - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 diff --git a/tests/test-http-client-transport/pom.xml b/tests/test-http-client-transport/pom.xml index 838d2e6a259..55fc990bddc 100644 --- a/tests/test-http-client-transport/pom.xml +++ b/tests/test-http-client-transport/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests tests-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 diff --git a/tests/test-integration/pom.xml b/tests/test-integration/pom.xml index c5788875a98..6a1feabddbc 100644 --- a/tests/test-integration/pom.xml +++ b/tests/test-integration/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests tests-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 test-integration diff --git a/tests/test-jmx/jmx-webapp-it/pom.xml b/tests/test-jmx/jmx-webapp-it/pom.xml index 8a780fcfdf8..5046fcdde62 100644 --- a/tests/test-jmx/jmx-webapp-it/pom.xml +++ b/tests/test-jmx/jmx-webapp-it/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests test-jmx-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 jmx-webapp-it diff --git a/tests/test-jmx/jmx-webapp/pom.xml b/tests/test-jmx/jmx-webapp/pom.xml index 0f18e74ab0c..01c5fdc2fe0 100644 --- a/tests/test-jmx/jmx-webapp/pom.xml +++ b/tests/test-jmx/jmx-webapp/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-jmx-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT jmx-webapp war diff --git a/tests/test-jmx/pom.xml b/tests/test-jmx/pom.xml index 5d9f49b83b4..0604c93a4c9 100644 --- a/tests/test-jmx/pom.xml +++ b/tests/test-jmx/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests tests-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 test-jmx-parent diff --git a/tests/test-loginservice/pom.xml b/tests/test-loginservice/pom.xml index 4bec180d21f..e64495991a7 100644 --- a/tests/test-loginservice/pom.xml +++ b/tests/test-loginservice/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests tests-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT test-loginservice Jetty Tests :: Login Service diff --git a/tests/test-quickstart/pom.xml b/tests/test-quickstart/pom.xml index f84a342be8f..a748977848b 100644 --- a/tests/test-quickstart/pom.xml +++ b/tests/test-quickstart/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests tests-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT ../pom.xml 4.0.0 diff --git a/tests/test-sessions/pom.xml b/tests/test-sessions/pom.xml index e4ee636a4e6..0ca22dcd1c2 100644 --- a/tests/test-sessions/pom.xml +++ b/tests/test-sessions/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests tests-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT test-sessions-parent Jetty Tests :: Sessions :: Parent diff --git a/tests/test-sessions/test-file-sessions/pom.xml b/tests/test-sessions/test-file-sessions/pom.xml index 7d1311177b9..b828eb93c93 100644 --- a/tests/test-sessions/test-file-sessions/pom.xml +++ b/tests/test-sessions/test-file-sessions/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-sessions-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT test-file-sessions Jetty Tests :: Sessions :: File diff --git a/tests/test-sessions/test-gcloud-sessions/pom.xml b/tests/test-sessions/test-gcloud-sessions/pom.xml index 7fc9f9bb6b9..ae19f2de755 100644 --- a/tests/test-sessions/test-gcloud-sessions/pom.xml +++ b/tests/test-sessions/test-gcloud-sessions/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-sessions-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT test-gcloud-sessions Jetty Tests :: Sessions :: GCloud diff --git a/tests/test-sessions/test-hazelcast-sessions/pom.xml b/tests/test-sessions/test-hazelcast-sessions/pom.xml index 94f97a1a6d1..56db185935a 100644 --- a/tests/test-sessions/test-hazelcast-sessions/pom.xml +++ b/tests/test-sessions/test-hazelcast-sessions/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-sessions-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT test-hazelcast-sessions Jetty Tests :: Sessions :: Hazelcast diff --git a/tests/test-sessions/test-infinispan-sessions/pom.xml b/tests/test-sessions/test-infinispan-sessions/pom.xml index 92a354df743..4a96db6f65e 100644 --- a/tests/test-sessions/test-infinispan-sessions/pom.xml +++ b/tests/test-sessions/test-infinispan-sessions/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-sessions-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT test-infinispan-sessions Jetty Tests :: Sessions :: Infinispan diff --git a/tests/test-sessions/test-jdbc-sessions/pom.xml b/tests/test-sessions/test-jdbc-sessions/pom.xml index 63acd900c15..1b60ce85229 100644 --- a/tests/test-sessions/test-jdbc-sessions/pom.xml +++ b/tests/test-sessions/test-jdbc-sessions/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-sessions-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT test-jdbc-sessions Jetty Tests :: Sessions :: JDBC diff --git a/tests/test-sessions/test-memcached-sessions/pom.xml b/tests/test-sessions/test-memcached-sessions/pom.xml index b5520a67486..2a691adc0f6 100644 --- a/tests/test-sessions/test-memcached-sessions/pom.xml +++ b/tests/test-sessions/test-memcached-sessions/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-sessions-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT test-memcached-sessions Jetty Tests :: Sessions :: Memcached diff --git a/tests/test-sessions/test-mongodb-sessions/pom.xml b/tests/test-sessions/test-mongodb-sessions/pom.xml index 266241d83af..a3ffc36c528 100644 --- a/tests/test-sessions/test-mongodb-sessions/pom.xml +++ b/tests/test-sessions/test-mongodb-sessions/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-sessions-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT test-mongodb-sessions Jetty Tests :: Sessions :: Mongo diff --git a/tests/test-sessions/test-sessions-common/pom.xml b/tests/test-sessions/test-sessions-common/pom.xml index faa26d2a9fd..aa45c63cc2a 100644 --- a/tests/test-sessions/test-sessions-common/pom.xml +++ b/tests/test-sessions/test-sessions-common/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-sessions-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT test-sessions-common Jetty Tests :: Sessions :: Common diff --git a/tests/test-webapps/pom.xml b/tests/test-webapps/pom.xml index c63f0966b47..e69c4c8566a 100644 --- a/tests/test-webapps/pom.xml +++ b/tests/test-webapps/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests tests-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT ../pom.xml test-webapps-parent diff --git a/tests/test-webapps/test-cdi-common-webapp/pom.xml b/tests/test-webapps/test-cdi-common-webapp/pom.xml index d72012db769..7a10f068c71 100644 --- a/tests/test-webapps/test-cdi-common-webapp/pom.xml +++ b/tests/test-webapps/test-cdi-common-webapp/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests test-webapps-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 diff --git a/tests/test-webapps/test-felix-webapp/pom.xml b/tests/test-webapps/test-felix-webapp/pom.xml index 85bfc7220de..e170509eff9 100644 --- a/tests/test-webapps/test-felix-webapp/pom.xml +++ b/tests/test-webapps/test-felix-webapp/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests test-webapps-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 diff --git a/tests/test-webapps/test-http2-webapp/pom.xml b/tests/test-webapps/test-http2-webapp/pom.xml index 544682c765a..a7e2a1c358c 100644 --- a/tests/test-webapps/test-http2-webapp/pom.xml +++ b/tests/test-webapps/test-http2-webapp/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests test-webapps-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 diff --git a/tests/test-webapps/test-jaas-webapp/pom.xml b/tests/test-webapps/test-jaas-webapp/pom.xml index 7a3cff0b6bf..82adc28bbfc 100644 --- a/tests/test-webapps/test-jaas-webapp/pom.xml +++ b/tests/test-webapps/test-jaas-webapp/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-webapps-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT test-jaas-webapp Jetty Tests :: WebApp :: JAAS diff --git a/tests/test-webapps/test-jetty-webapp/pom.xml b/tests/test-webapps/test-jetty-webapp/pom.xml index 07d1e669709..e37b5b22de6 100644 --- a/tests/test-webapps/test-jetty-webapp/pom.xml +++ b/tests/test-webapps/test-jetty-webapp/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests test-webapps-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT ../pom.xml 4.0.0 diff --git a/tests/test-webapps/test-jndi-webapp/pom.xml b/tests/test-webapps/test-jndi-webapp/pom.xml index 1e3cd33b08f..07c04f866e9 100644 --- a/tests/test-webapps/test-jndi-webapp/pom.xml +++ b/tests/test-webapps/test-jndi-webapp/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-webapps-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT test-jndi-webapp Jetty Tests :: WebApp :: JNDI diff --git a/tests/test-webapps/test-mock-resources/pom.xml b/tests/test-webapps/test-mock-resources/pom.xml index 7b1b46c85b2..0607935a35f 100644 --- a/tests/test-webapps/test-mock-resources/pom.xml +++ b/tests/test-webapps/test-mock-resources/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests test-webapps-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT Jetty Tests :: WebApp :: Mock Resources test-mock-resources diff --git a/tests/test-webapps/test-owb-cdi-webapp/pom.xml b/tests/test-webapps/test-owb-cdi-webapp/pom.xml index 6157ecd0f4f..a673347d33e 100644 --- a/tests/test-webapps/test-owb-cdi-webapp/pom.xml +++ b/tests/test-webapps/test-owb-cdi-webapp/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests test-webapps-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 diff --git a/tests/test-webapps/test-proxy-webapp/pom.xml b/tests/test-webapps/test-proxy-webapp/pom.xml index 5fcd1c21c30..884d0b1afab 100644 --- a/tests/test-webapps/test-proxy-webapp/pom.xml +++ b/tests/test-webapps/test-proxy-webapp/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests test-webapps-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT ../pom.xml 4.0.0 diff --git a/tests/test-webapps/test-servlet-spec/pom.xml b/tests/test-webapps/test-servlet-spec/pom.xml index 26512ed5941..2dec350ab6c 100644 --- a/tests/test-webapps/test-servlet-spec/pom.xml +++ b/tests/test-webapps/test-servlet-spec/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-webapps-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT test-servlet-spec-parent Jetty Tests :: Spec Test WebApp :: Parent diff --git a/tests/test-webapps/test-servlet-spec/test-container-initializer/pom.xml b/tests/test-webapps/test-servlet-spec/test-container-initializer/pom.xml index 7b9b8cbc1e5..efd55e56e25 100644 --- a/tests/test-webapps/test-servlet-spec/test-container-initializer/pom.xml +++ b/tests/test-webapps/test-servlet-spec/test-container-initializer/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests test-servlet-spec-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT test-container-initializer jar diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/pom.xml b/tests/test-webapps/test-servlet-spec/test-spec-webapp/pom.xml index 5b401b80488..71aa95c4066 100644 --- a/tests/test-webapps/test-servlet-spec/test-spec-webapp/pom.xml +++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-servlet-spec-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT Jetty Tests :: Webapps :: Spec Webapp test-spec-webapp diff --git a/tests/test-webapps/test-servlet-spec/test-web-fragment/pom.xml b/tests/test-webapps/test-servlet-spec/test-web-fragment/pom.xml index 3e38da60b57..a225644e50c 100644 --- a/tests/test-webapps/test-servlet-spec/test-web-fragment/pom.xml +++ b/tests/test-webapps/test-servlet-spec/test-web-fragment/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests test-servlet-spec-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT Jetty Tests :: WebApp :: Servlet Spec :: Fragment Jar diff --git a/tests/test-webapps/test-simple-webapp/pom.xml b/tests/test-webapps/test-simple-webapp/pom.xml index f815056dcae..ddea1b73710 100644 --- a/tests/test-webapps/test-simple-webapp/pom.xml +++ b/tests/test-webapps/test-simple-webapp/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-webapps-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT test-simple-webapp diff --git a/tests/test-webapps/test-webapp-rfc2616/pom.xml b/tests/test-webapps/test-webapp-rfc2616/pom.xml index 571534ccedc..11f24e990bd 100644 --- a/tests/test-webapps/test-webapp-rfc2616/pom.xml +++ b/tests/test-webapps/test-webapp-rfc2616/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty.tests test-webapps-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT test-webapp-rfc2616 Jetty Tests :: WebApp :: RFC2616 diff --git a/tests/test-webapps/test-weld-cdi-webapp/pom.xml b/tests/test-webapps/test-weld-cdi-webapp/pom.xml index c31107d0259..32013b4fe89 100644 --- a/tests/test-webapps/test-weld-cdi-webapp/pom.xml +++ b/tests/test-webapps/test-weld-cdi-webapp/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty.tests test-webapps-parent - 9.4.28.v20200408 + 9.4.29-SNAPSHOT 4.0.0 From 0db9210f17cc729ed625ef5ecf59483f55cbfcf9 Mon Sep 17 00:00:00 2001 From: Jesse McConnell Date: Thu, 9 Apr 2020 10:31:38 -0500 Subject: [PATCH 065/101] update to released web resources artifact --- jetty-documentation/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jetty-documentation/pom.xml b/jetty-documentation/pom.xml index acfb503753f..4e601a707ae 100644 --- a/jetty-documentation/pom.xml +++ b/jetty-documentation/pom.xml @@ -41,7 +41,7 @@ 1.5.0-alpha.8.1 1.5.8.1 1.7.27 - 1.1 + 1.2 ${project.build.directory}/web-resources ${project.build.directory}/current From c512f41488a9085cc472745116312db2d37de393 Mon Sep 17 00:00:00 2001 From: Jesse McConnell Date: Thu, 9 Apr 2020 10:32:25 -0500 Subject: [PATCH 066/101] set render styles --- .../main/asciidoc/contribution-guide/index.adoc | 13 +++++-------- .../main/asciidoc/distribution-guide/index.adoc | 13 +++++-------- .../src/main/asciidoc/embedded-guide/index.adoc | 14 ++++++-------- .../main/asciidoc/quickstart-guide/index.adoc | 17 ++++++----------- 4 files changed, 22 insertions(+), 35 deletions(-) diff --git a/jetty-documentation/src/main/asciidoc/contribution-guide/index.adoc b/jetty-documentation/src/main/asciidoc/contribution-guide/index.adoc index 98e27818725..5543d145f28 100644 --- a/jetty-documentation/src/main/asciidoc/contribution-guide/index.adoc +++ b/jetty-documentation/src/main/asciidoc/contribution-guide/index.adoc @@ -23,8 +23,11 @@ :revdate: {TIMESTAMP} :toc: left :toc-title: Contribution Guide -:toc-image: ../../common/images/jetty-logo.svg -:toc-image-url: /jetty/index.html +:toc-style: + +:header-style: eclipse-thin +:breadcrumb-style: eclipse-thin +:footer-style: default :breadcrumb: Home:../index.html | Contribution Guide:./index.html // docinfo lets you pull in shared content and/or influence via render type @@ -43,12 +46,6 @@ endif::[] // options for special blocks, code snippets, screen, etc :sub-order: attributes+ -// suppress document footer generation -//:nofooter: - -// suppress Eclipse footer -:no-eclipse-footer: - // uncomment to allow include::https:// style content inclusion //:allow-uri-read: true diff --git a/jetty-documentation/src/main/asciidoc/distribution-guide/index.adoc b/jetty-documentation/src/main/asciidoc/distribution-guide/index.adoc index 20a8c1a9022..130c16ce985 100644 --- a/jetty-documentation/src/main/asciidoc/distribution-guide/index.adoc +++ b/jetty-documentation/src/main/asciidoc/distribution-guide/index.adoc @@ -23,8 +23,11 @@ :revdate: {TIMESTAMP} :toc: left :toc-title: Distribution Guide -:toc-image: ../../common/images/jetty-logo.svg -:toc-image-url: /jetty/index.html +:toc-style: + +:header-style: eclipse-thin +:breadcrumb-style: eclipse-thin +:footer-style: default :breadcrumb: Home:../index.html | Distribution Guide:./index.html // docinfo lets you pull in shared content and/or influence via render type @@ -43,12 +46,6 @@ endif::[] // options for special blocks, code snippets, screen, etc :sub-order: attributes+ -// suppress document footer generation -//:nofooter: - -// suppress Eclipse footer -:no-eclipse-footer: - // uncomment to allow include::https:// style content inclusion //:allow-uri-read: true diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/index.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/index.adoc index 1dfc4bdaa93..6754f278b87 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/index.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/index.adoc @@ -23,8 +23,12 @@ :revdate: {TIMESTAMP} :toc: left :toc-title: Embedded Guide -:toc-image: ../../common/images/jetty-logo.svg -:toc-image-url: /jetty/index.html +:toc-style: + +:header-style: eclipse-thin +:breadcrumb-style: eclipse-thin +:footer-style: default + :breadcrumb: Home:../index.html | Embedded Guide:./index.html // docinfo lets you pull in shared content and/or influence via render type @@ -43,12 +47,6 @@ endif::[] // options for special blocks, code snippets, screen, etc :sub-order: attributes+ -// suppress document footer generation -//:nofooter: - -// suppress Eclipse footer -:no-eclipse-footer: - // uncomment to allow include::https:// style content inclusion //:allow-uri-read: true diff --git a/jetty-documentation/src/main/asciidoc/quickstart-guide/index.adoc b/jetty-documentation/src/main/asciidoc/quickstart-guide/index.adoc index a3a369ae02d..0cbb0dd608c 100644 --- a/jetty-documentation/src/main/asciidoc/quickstart-guide/index.adoc +++ b/jetty-documentation/src/main/asciidoc/quickstart-guide/index.adoc @@ -23,15 +23,14 @@ :revdate: {TIMESTAMP} :toc: left :toc-title: Quickstart Guide -:toc-image: ../../common/images/jetty-logo.svg -:toc-image-url: /jetty/index.html +:toc-style: + +:header-style: eclipse-thin +:breadcrumb-style: eclipse-thin +:footer-style: default + :breadcrumb: Home:../index.html | Quickstart Guide:./index.html - -// docinfo lets you pull in shared content and/or influence via render type -//:docinfodir: {DOCINFODIR}/documentation -//:docinfo1: - // html specific directives ifdef::backend-html5[] :safe-mode-unsafe: @@ -44,10 +43,6 @@ endif::[] // options for special blocks, code snippets, screen, etc :sub-order: attributes+ -// suppress document footer generation -//:nofooter: -:no-eclipse-footer: - // uncomment to allow include::https:// style content inclusion //:allow-uri-read: true From d2844bb102f76b0d12c5c0663105617d04eb34e1 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Wed, 8 Apr 2020 12:51:44 +0200 Subject: [PATCH 067/101] Improvements to the Jetty server documentation.adoc Moved old documentation to old_docs/. Signed-off-by: Simone Bordet --- .../jetty/embedded/SimplestServer.java | 1 + jetty-documentation/pom.xml | 24 +- .../embedded-guide/client/client-io-arch.adoc | 8 +- .../main/asciidoc/embedded-guide/io-arch.adoc | 14 +- .../{server => old_docs}/ant/chapter.adoc | 0 .../{server => old_docs}/ant/jetty-ant.adoc | 0 .../architecture/1xx-responses.adoc | 0 .../architecture/basic-architecture.adoc | 0 .../architecture/chapter.adoc | 0 .../architecture/jetty-classloading.adoc | 0 .../server-side-architecture.adoc | 0 .../contributing/bugs.adoc | 0 .../contributing/chapter.adoc | 0 .../contributing/coding-standards.adoc | 0 .../contributing/community.adoc | 0 .../contributing/documentation.adoc | 0 .../contributing/patches.adoc | 0 .../contributing/release-testing.adoc | 0 .../contributing/releasing-jetty.adoc | 0 .../contributing/security.adoc | 0 .../contributing/source-build.adoc | 0 .../debugging/chapter.adoc | 0 .../debugging/debugging-with-eclipse.adoc | 0 .../debugging/debugging-with-intellij.adoc | 0 .../debugging/enable-remote-debugging.adoc | 0 .../embedding/chapter.adoc | 0 .../embedding/embedded-examples.adoc | 0 .../embedding/embedding-jetty.adoc | 0 .../examples/embedded-file-server.adoc | 0 .../examples/embedded-many-connectors.adoc | 0 .../examples/embedded-minimal-servlet.adoc | 0 .../examples/embedded-one-webapp.adoc | 0 .../embedded-secured-hello-handler.adoc | 0 .../examples/embedded-split-file-server.adoc | 0 .../embedding/jetty-helloworld.adoc | 0 .../{server => old_docs}/faq/chapter.adoc | 0 .../frameworks/chapter.adoc | 0 .../frameworks/metro.adoc | 0 .../{server => old_docs}/frameworks/osgi.adoc | 0 .../frameworks/spring-usage.adoc | 0 .../{server => old_docs}/frameworks/weld.adoc | 0 .../handlers/chapter.adoc | 0 .../handlers/writing-custom-handlers.adoc | 0 .../images/basic-architecture-handlers.png | Bin .../basic-architecture-nested-handlers.png | Bin .../images/basic-architecture-patterns.png | Bin .../basic-architecture-servlet-handler.png | Bin .../basic-architecture-web-application.png | Bin .../images/debug-eclipse-1.png | Bin .../images/debug-eclipse-2.png | Bin .../images/debug-eclipse-3.png | Bin .../images/intellij_debug_view.png | Bin .../images/intellij_new_remote_config.png | Bin .../images/intellij_select_debug.png | Bin .../images/intellij_set_breakpoint.png | Bin .../images/jetty-high-level-architecture.png | Bin .../jetty-xml/chapter.adoc | 0 .../jetty-xml/jetty-env-xml.adoc | 0 .../jetty-xml/jetty-web-xml-config.adoc | 0 .../jetty-xml/jetty-xml-config.adoc | 0 .../jetty-xml/jetty-xml-syntax.adoc | 0 .../jetty-xml/jetty-xml-usage.adoc | 0 .../jetty-xml/override-web-xml.adoc | 0 .../jetty-xml/webdefault-xml.adoc | 0 .../{server => old_docs}/maven/chapter.adoc | 0 .../maven/jetty-jspc-maven-plugin.adoc | 0 .../maven/jetty-maven-helloworld.adoc | 0 .../maven/jetty-maven-plugin.adoc | 0 .../maven/jetty-maven-scanning.adoc | 0 .../platforms/chapter.adoc | 0 .../platforms/cloudfoundry.adoc | 0 .../platforms/elastic-beanstalk.adoc | 0 .../platforms/fedora.adoc | 0 .../platforms/jelastic.adoc | 0 .../platforms/ubuntu.adoc | 0 .../embedded-guide/old_docs/server.adoc | 36 ++ .../troubleshooting/chapter.adoc | 0 .../preventing-memory-leaks.adoc | 0 .../troubleshooting/security-reports.adoc | 0 .../troubleshooting/slow-deployment.adoc | 0 .../troubleshooting-locked-files.adoc | 0 .../troubleshooting-zip-exceptions.adoc | 0 .../troubleshooting/watchservice.adoc | 0 .../websockets/intro/chapter.adoc | 0 .../websockets/java/chapter.adoc | 0 .../java/java-websocket-client-api.adoc | 0 .../java/java-websocket-server-api.adoc | 0 .../websockets/jetty/chapter.adoc | 0 .../jetty/jetty-websocket-api-adapter.adoc | 0 .../jetty-websocket-api-annotations.adoc | 0 .../jetty/jetty-websocket-api-events.adoc | 0 .../jetty/jetty-websocket-api-listener.adoc | 0 .../jetty-websocket-api-send-message.adoc | 0 .../jetty/jetty-websocket-api-session.adoc | 0 .../websockets/jetty/jetty-websocket-api.adoc | 0 .../jetty/jetty-websocket-client-api.adoc | 0 .../jetty/jetty-websocket-server-api.adoc | 0 .../server/http/server-http.adoc | 297 +++++++++++++++++ .../embedded-guide/server/server-io-arch.adoc | 36 ++ .../embedded-guide/server/server.adoc | 49 ++- .../embedded/server/http/HTTPServerDocs.java | 314 ++++++++++++++++++ 101 files changed, 749 insertions(+), 30 deletions(-) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/ant/chapter.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/ant/jetty-ant.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/architecture/1xx-responses.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/architecture/basic-architecture.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/architecture/chapter.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/architecture/jetty-classloading.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/architecture/server-side-architecture.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/contributing/bugs.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/contributing/chapter.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/contributing/coding-standards.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/contributing/community.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/contributing/documentation.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/contributing/patches.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/contributing/release-testing.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/contributing/releasing-jetty.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/contributing/security.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/contributing/source-build.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/debugging/chapter.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/debugging/debugging-with-eclipse.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/debugging/debugging-with-intellij.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/debugging/enable-remote-debugging.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/embedding/chapter.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/embedding/embedded-examples.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/embedding/embedding-jetty.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/embedding/examples/embedded-file-server.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/embedding/examples/embedded-many-connectors.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/embedding/examples/embedded-minimal-servlet.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/embedding/examples/embedded-one-webapp.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/embedding/examples/embedded-secured-hello-handler.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/embedding/examples/embedded-split-file-server.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/embedding/jetty-helloworld.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/faq/chapter.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/frameworks/chapter.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/frameworks/metro.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/frameworks/osgi.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/frameworks/spring-usage.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/frameworks/weld.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/handlers/chapter.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/handlers/writing-custom-handlers.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/images/basic-architecture-handlers.png (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/images/basic-architecture-nested-handlers.png (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/images/basic-architecture-patterns.png (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/images/basic-architecture-servlet-handler.png (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/images/basic-architecture-web-application.png (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/images/debug-eclipse-1.png (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/images/debug-eclipse-2.png (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/images/debug-eclipse-3.png (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/images/intellij_debug_view.png (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/images/intellij_new_remote_config.png (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/images/intellij_select_debug.png (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/images/intellij_set_breakpoint.png (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/images/jetty-high-level-architecture.png (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/jetty-xml/chapter.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/jetty-xml/jetty-env-xml.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/jetty-xml/jetty-web-xml-config.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/jetty-xml/jetty-xml-config.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/jetty-xml/jetty-xml-syntax.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/jetty-xml/jetty-xml-usage.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/jetty-xml/override-web-xml.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/jetty-xml/webdefault-xml.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/maven/chapter.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/maven/jetty-jspc-maven-plugin.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/maven/jetty-maven-helloworld.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/maven/jetty-maven-plugin.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/maven/jetty-maven-scanning.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/platforms/chapter.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/platforms/cloudfoundry.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/platforms/elastic-beanstalk.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/platforms/fedora.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/platforms/jelastic.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/platforms/ubuntu.adoc (100%) create mode 100644 jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/server.adoc rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/troubleshooting/chapter.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/troubleshooting/preventing-memory-leaks.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/troubleshooting/security-reports.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/troubleshooting/slow-deployment.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/troubleshooting/troubleshooting-locked-files.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/troubleshooting/troubleshooting-zip-exceptions.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/troubleshooting/watchservice.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/websockets/intro/chapter.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/websockets/java/chapter.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/websockets/java/java-websocket-client-api.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/websockets/java/java-websocket-server-api.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/websockets/jetty/chapter.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/websockets/jetty/jetty-websocket-api-adapter.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/websockets/jetty/jetty-websocket-api-annotations.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/websockets/jetty/jetty-websocket-api-events.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/websockets/jetty/jetty-websocket-api-listener.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/websockets/jetty/jetty-websocket-api-send-message.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/websockets/jetty/jetty-websocket-api-session.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/websockets/jetty/jetty-websocket-api.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/websockets/jetty/jetty-websocket-client-api.adoc (100%) rename jetty-documentation/src/main/asciidoc/embedded-guide/{server => old_docs}/websockets/jetty/jetty-websocket-server-api.adoc (100%) create mode 100644 jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http.adoc create mode 100644 jetty-documentation/src/main/asciidoc/embedded-guide/server/server-io-arch.adoc create mode 100644 jetty-documentation/src/main/java/embedded/server/http/HTTPServerDocs.java diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/SimplestServer.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/SimplestServer.java index bb3c4a6641b..74f3aa0cb14 100644 --- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/SimplestServer.java +++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/SimplestServer.java @@ -23,6 +23,7 @@ import org.eclipse.jetty.server.Server; /** * The simplest possible Jetty server. */ +// TODO: remove this class, only used in documentation. public class SimplestServer { public static Server createServer(int port) diff --git a/jetty-documentation/pom.xml b/jetty-documentation/pom.xml index 5c723d17eeb..e318d4a57b1 100644 --- a/jetty-documentation/pom.xml +++ b/jetty-documentation/pom.xml @@ -55,12 +55,27 @@ org.eclipse.jetty - jetty-client + jetty-server + ${project.version} + + + org.eclipse.jetty + jetty-servlet + ${project.version} + + + org.eclipse.jetty + jetty-alpn-server ${project.version} org.eclipse.jetty.http2 - http2-http-client-transport + http2-server + ${project.version} + + + org.eclipse.jetty + jetty-client ${project.version} @@ -68,6 +83,11 @@ fcgi-client ${project.version} + + org.eclipse.jetty.http2 + http2-http-client-transport + ${project.version} + diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/client/client-io-arch.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/client-io-arch.adoc index 83e59d0ed7a..8b11d289e06 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/client/client-io-arch.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/client-io-arch.adoc @@ -17,12 +17,12 @@ // [[eg-client-io-arch]] -=== Client Libraries Architecture +=== Client Libraries I/O Architecture The Jetty client libraries provide the basic components and APIs to implement a network client. -They build on the common link:#io-arch[Jetty I/O Architecture] and provide client +They build on the common xref:eg-io-arch[Jetty I/O Architecture] and provide client specific concepts (such as establishing a connection to a server). There are conceptually two layers that compose the Jetty client libraries: @@ -37,7 +37,7 @@ network. ==== Client Libraries Network Layer The Jetty client libraries use the common I/O design described in -link:#io-arch[this section]. +link:#eg-io-arch[this section]. The main client-side component is the link:{JDURL}/org/eclipse/jetty/io/ClientConnector.html[`ClientConnector`]. @@ -122,7 +122,7 @@ The protocol layer builds on top of the network layer to generate the bytes to be written to the network and to parse the bytes read from the network. -Recall from link:#io-arch-connection[this section] that Jetty uses the +Recall from link:#eg-io-arch-connection[this section] that Jetty uses the `Connection` abstraction to produce and interpret the network bytes. On the client side, a `ClientConnectionFactory` implementation is the diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/io-arch.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/io-arch.adoc index 178e04eaaa0..3d11dc73008 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/io-arch.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/io-arch.adoc @@ -17,13 +17,13 @@ // [appendix] -[[io-arch]] +[[eg-io-arch]] == Jetty I/O Architecture Jetty libraries (both client and server) use Java NIO to handle I/O, so that at its core Jetty I/O is completely non-blocking. -[[io-arch-selector-manager]] +[[eg-io-arch-selector-manager]] === Jetty I/O: `SelectorManager` The core class of Jetty I/O is @@ -62,7 +62,7 @@ This example shows how a server accepts a client connection: include::{doc_code}/embedded/SelectorManagerDocs.java[tags=accept] ---- -[[io-arch-endpoint-connection]] +[[eg-io-arch-endpoint-connection]] === Jetty I/O: `EndPoint` and `Connection` ``SocketChannel``s that are passed to `SelectorManager` are wrapped into two @@ -138,7 +138,7 @@ subclasses. NOTE: TODO: add a link to a client-side specific architecture section -[[io-arch-endpoint]] +[[eg-io-arch-endpoint]] === Jetty I/O: `EndPoint` The Jetty I/O library use Java NIO to handle I/O, so that I/O is non-blocking. @@ -162,9 +162,9 @@ fully written, even if they are delayed by TCP congestion/uncongestion). The `EndPoint` APIs abstract out the Java NIO details by providing non-blocking APIs based on `Callback` objects for I/O operations. The `EndPoint` APIs are typically called by `Connection` implementations, see -link:#io-arch-connection[this section]. +xref:eg-io-arch-connection[this section]. -[[io-arch-connection]] +[[eg-io-arch-connection]] === Jetty I/O: `Connection` `Connection` is the abstraction that deserializes incoming bytes into objects, @@ -196,7 +196,7 @@ extends `AbstractConnection`: include::{doc_code}/embedded/SelectorManagerDocs.java[tags=connection] ---- -[[io-arch-echo]] +[[eg-io-arch-echo]] === Jetty I/O: Network Echo With the concepts above it is now possible to write a simple, fully non-blocking, diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/ant/chapter.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/ant/chapter.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/ant/chapter.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/ant/chapter.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/ant/jetty-ant.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/ant/jetty-ant.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/ant/jetty-ant.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/ant/jetty-ant.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/architecture/1xx-responses.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/architecture/1xx-responses.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/architecture/1xx-responses.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/architecture/1xx-responses.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/architecture/basic-architecture.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/architecture/basic-architecture.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/architecture/basic-architecture.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/architecture/basic-architecture.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/architecture/chapter.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/architecture/chapter.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/architecture/chapter.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/architecture/chapter.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/architecture/jetty-classloading.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/architecture/jetty-classloading.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/architecture/jetty-classloading.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/architecture/jetty-classloading.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/architecture/server-side-architecture.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/architecture/server-side-architecture.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/architecture/server-side-architecture.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/architecture/server-side-architecture.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/contributing/bugs.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/contributing/bugs.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/contributing/bugs.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/contributing/bugs.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/contributing/chapter.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/contributing/chapter.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/contributing/chapter.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/contributing/chapter.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/contributing/coding-standards.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/contributing/coding-standards.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/contributing/coding-standards.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/contributing/coding-standards.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/contributing/community.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/contributing/community.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/contributing/community.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/contributing/community.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/contributing/documentation.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/contributing/documentation.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/contributing/documentation.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/contributing/documentation.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/contributing/patches.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/contributing/patches.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/contributing/patches.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/contributing/patches.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/contributing/release-testing.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/contributing/release-testing.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/contributing/release-testing.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/contributing/release-testing.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/contributing/releasing-jetty.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/contributing/releasing-jetty.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/contributing/releasing-jetty.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/contributing/releasing-jetty.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/contributing/security.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/contributing/security.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/contributing/security.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/contributing/security.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/contributing/source-build.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/contributing/source-build.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/contributing/source-build.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/contributing/source-build.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/debugging/chapter.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/debugging/chapter.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/debugging/chapter.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/debugging/chapter.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/debugging/debugging-with-eclipse.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/debugging/debugging-with-eclipse.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/debugging/debugging-with-eclipse.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/debugging/debugging-with-eclipse.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/debugging/debugging-with-intellij.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/debugging/debugging-with-intellij.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/debugging/debugging-with-intellij.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/debugging/debugging-with-intellij.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/debugging/enable-remote-debugging.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/debugging/enable-remote-debugging.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/debugging/enable-remote-debugging.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/debugging/enable-remote-debugging.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/embedding/chapter.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/embedding/chapter.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/embedding/chapter.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/embedding/chapter.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/embedding/embedded-examples.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/embedding/embedded-examples.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/embedding/embedded-examples.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/embedding/embedded-examples.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/embedding/embedding-jetty.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/embedding/embedding-jetty.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/embedding/embedding-jetty.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/embedding/embedding-jetty.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/embedding/examples/embedded-file-server.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/embedding/examples/embedded-file-server.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/embedding/examples/embedded-file-server.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/embedding/examples/embedded-file-server.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/embedding/examples/embedded-many-connectors.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/embedding/examples/embedded-many-connectors.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/embedding/examples/embedded-many-connectors.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/embedding/examples/embedded-many-connectors.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/embedding/examples/embedded-minimal-servlet.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/embedding/examples/embedded-minimal-servlet.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/embedding/examples/embedded-minimal-servlet.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/embedding/examples/embedded-minimal-servlet.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/embedding/examples/embedded-one-webapp.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/embedding/examples/embedded-one-webapp.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/embedding/examples/embedded-one-webapp.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/embedding/examples/embedded-one-webapp.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/embedding/examples/embedded-secured-hello-handler.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/embedding/examples/embedded-secured-hello-handler.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/embedding/examples/embedded-secured-hello-handler.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/embedding/examples/embedded-secured-hello-handler.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/embedding/examples/embedded-split-file-server.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/embedding/examples/embedded-split-file-server.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/embedding/examples/embedded-split-file-server.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/embedding/examples/embedded-split-file-server.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/embedding/jetty-helloworld.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/embedding/jetty-helloworld.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/embedding/jetty-helloworld.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/embedding/jetty-helloworld.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/faq/chapter.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/faq/chapter.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/faq/chapter.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/faq/chapter.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/frameworks/chapter.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/frameworks/chapter.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/frameworks/chapter.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/frameworks/chapter.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/frameworks/metro.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/frameworks/metro.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/frameworks/metro.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/frameworks/metro.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/frameworks/osgi.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/frameworks/osgi.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/frameworks/osgi.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/frameworks/osgi.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/frameworks/spring-usage.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/frameworks/spring-usage.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/frameworks/spring-usage.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/frameworks/spring-usage.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/frameworks/weld.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/frameworks/weld.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/frameworks/weld.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/frameworks/weld.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/handlers/chapter.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/handlers/chapter.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/handlers/chapter.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/handlers/chapter.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/handlers/writing-custom-handlers.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/handlers/writing-custom-handlers.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/handlers/writing-custom-handlers.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/handlers/writing-custom-handlers.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/images/basic-architecture-handlers.png b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/images/basic-architecture-handlers.png similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/images/basic-architecture-handlers.png rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/images/basic-architecture-handlers.png diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/images/basic-architecture-nested-handlers.png b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/images/basic-architecture-nested-handlers.png similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/images/basic-architecture-nested-handlers.png rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/images/basic-architecture-nested-handlers.png diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/images/basic-architecture-patterns.png b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/images/basic-architecture-patterns.png similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/images/basic-architecture-patterns.png rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/images/basic-architecture-patterns.png diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/images/basic-architecture-servlet-handler.png b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/images/basic-architecture-servlet-handler.png similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/images/basic-architecture-servlet-handler.png rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/images/basic-architecture-servlet-handler.png diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/images/basic-architecture-web-application.png b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/images/basic-architecture-web-application.png similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/images/basic-architecture-web-application.png rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/images/basic-architecture-web-application.png diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/images/debug-eclipse-1.png b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/images/debug-eclipse-1.png similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/images/debug-eclipse-1.png rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/images/debug-eclipse-1.png diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/images/debug-eclipse-2.png b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/images/debug-eclipse-2.png similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/images/debug-eclipse-2.png rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/images/debug-eclipse-2.png diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/images/debug-eclipse-3.png b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/images/debug-eclipse-3.png similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/images/debug-eclipse-3.png rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/images/debug-eclipse-3.png diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/images/intellij_debug_view.png b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/images/intellij_debug_view.png similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/images/intellij_debug_view.png rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/images/intellij_debug_view.png diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/images/intellij_new_remote_config.png b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/images/intellij_new_remote_config.png similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/images/intellij_new_remote_config.png rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/images/intellij_new_remote_config.png diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/images/intellij_select_debug.png b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/images/intellij_select_debug.png similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/images/intellij_select_debug.png rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/images/intellij_select_debug.png diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/images/intellij_set_breakpoint.png b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/images/intellij_set_breakpoint.png similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/images/intellij_set_breakpoint.png rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/images/intellij_set_breakpoint.png diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/images/jetty-high-level-architecture.png b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/images/jetty-high-level-architecture.png similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/images/jetty-high-level-architecture.png rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/images/jetty-high-level-architecture.png diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/jetty-xml/chapter.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/jetty-xml/chapter.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/jetty-xml/chapter.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/jetty-xml/chapter.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/jetty-xml/jetty-env-xml.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/jetty-xml/jetty-env-xml.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/jetty-xml/jetty-env-xml.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/jetty-xml/jetty-env-xml.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/jetty-xml/jetty-web-xml-config.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/jetty-xml/jetty-web-xml-config.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/jetty-xml/jetty-web-xml-config.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/jetty-xml/jetty-web-xml-config.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/jetty-xml/jetty-xml-config.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/jetty-xml/jetty-xml-config.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/jetty-xml/jetty-xml-config.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/jetty-xml/jetty-xml-config.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/jetty-xml/jetty-xml-syntax.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/jetty-xml/jetty-xml-syntax.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/jetty-xml/jetty-xml-syntax.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/jetty-xml/jetty-xml-syntax.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/jetty-xml/jetty-xml-usage.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/jetty-xml/jetty-xml-usage.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/jetty-xml/jetty-xml-usage.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/jetty-xml/jetty-xml-usage.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/jetty-xml/override-web-xml.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/jetty-xml/override-web-xml.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/jetty-xml/override-web-xml.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/jetty-xml/override-web-xml.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/jetty-xml/webdefault-xml.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/jetty-xml/webdefault-xml.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/jetty-xml/webdefault-xml.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/jetty-xml/webdefault-xml.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/maven/chapter.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/maven/chapter.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/maven/chapter.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/maven/chapter.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/maven/jetty-jspc-maven-plugin.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/maven/jetty-jspc-maven-plugin.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/maven/jetty-jspc-maven-plugin.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/maven/jetty-jspc-maven-plugin.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/maven/jetty-maven-helloworld.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/maven/jetty-maven-helloworld.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/maven/jetty-maven-helloworld.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/maven/jetty-maven-helloworld.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/maven/jetty-maven-plugin.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/maven/jetty-maven-plugin.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/maven/jetty-maven-plugin.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/maven/jetty-maven-plugin.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/maven/jetty-maven-scanning.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/maven/jetty-maven-scanning.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/maven/jetty-maven-scanning.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/maven/jetty-maven-scanning.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/platforms/chapter.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/platforms/chapter.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/platforms/chapter.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/platforms/chapter.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/platforms/cloudfoundry.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/platforms/cloudfoundry.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/platforms/cloudfoundry.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/platforms/cloudfoundry.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/platforms/elastic-beanstalk.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/platforms/elastic-beanstalk.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/platforms/elastic-beanstalk.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/platforms/elastic-beanstalk.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/platforms/fedora.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/platforms/fedora.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/platforms/fedora.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/platforms/fedora.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/platforms/jelastic.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/platforms/jelastic.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/platforms/jelastic.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/platforms/jelastic.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/platforms/ubuntu.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/platforms/ubuntu.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/platforms/ubuntu.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/platforms/ubuntu.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/server.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/server.adoc new file mode 100644 index 00000000000..82d815d7d1b --- /dev/null +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/server.adoc @@ -0,0 +1,36 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +[[server]] +== Jetty Server Libraries + +include::embedding/chapter.adoc[] +include::maven/chapter.adoc[] +include::clients/http/chapter.adoc[] +include::handlers/chapter.adoc[] +include::websockets/intro/chapter.adoc[] +include::websockets/jetty/chapter.adoc[] +include::ant/chapter.adoc[] +include::frameworks/chapter.adoc[] +include::architecture/chapter.adoc[] +include::platforms/chapter.adoc[] +include::jetty-xml/chapter.adoc[] +include::troubleshooting/chapter.adoc[] +include::debugging/chapter.adoc[] +//include::contributing/chapter.adoc[] +include::upgrading/chapter.adoc[] diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/troubleshooting/chapter.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/troubleshooting/chapter.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/troubleshooting/chapter.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/troubleshooting/chapter.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/troubleshooting/preventing-memory-leaks.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/troubleshooting/preventing-memory-leaks.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/troubleshooting/preventing-memory-leaks.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/troubleshooting/preventing-memory-leaks.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/troubleshooting/security-reports.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/troubleshooting/security-reports.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/troubleshooting/security-reports.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/troubleshooting/security-reports.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/troubleshooting/slow-deployment.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/troubleshooting/slow-deployment.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/troubleshooting/slow-deployment.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/troubleshooting/slow-deployment.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/troubleshooting/troubleshooting-locked-files.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/troubleshooting/troubleshooting-locked-files.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/troubleshooting/troubleshooting-locked-files.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/troubleshooting/troubleshooting-locked-files.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/troubleshooting/troubleshooting-zip-exceptions.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/troubleshooting/troubleshooting-zip-exceptions.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/troubleshooting/troubleshooting-zip-exceptions.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/troubleshooting/troubleshooting-zip-exceptions.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/troubleshooting/watchservice.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/troubleshooting/watchservice.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/troubleshooting/watchservice.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/troubleshooting/watchservice.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/websockets/intro/chapter.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/websockets/intro/chapter.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/websockets/intro/chapter.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/websockets/intro/chapter.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/websockets/java/chapter.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/websockets/java/chapter.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/websockets/java/chapter.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/websockets/java/chapter.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/websockets/java/java-websocket-client-api.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/websockets/java/java-websocket-client-api.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/websockets/java/java-websocket-client-api.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/websockets/java/java-websocket-client-api.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/websockets/java/java-websocket-server-api.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/websockets/java/java-websocket-server-api.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/websockets/java/java-websocket-server-api.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/websockets/java/java-websocket-server-api.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/websockets/jetty/chapter.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/websockets/jetty/chapter.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/websockets/jetty/chapter.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/websockets/jetty/chapter.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/websockets/jetty/jetty-websocket-api-adapter.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/websockets/jetty/jetty-websocket-api-adapter.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/websockets/jetty/jetty-websocket-api-adapter.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/websockets/jetty/jetty-websocket-api-adapter.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/websockets/jetty/jetty-websocket-api-annotations.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/websockets/jetty/jetty-websocket-api-annotations.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/websockets/jetty/jetty-websocket-api-annotations.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/websockets/jetty/jetty-websocket-api-annotations.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/websockets/jetty/jetty-websocket-api-events.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/websockets/jetty/jetty-websocket-api-events.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/websockets/jetty/jetty-websocket-api-events.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/websockets/jetty/jetty-websocket-api-events.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/websockets/jetty/jetty-websocket-api-listener.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/websockets/jetty/jetty-websocket-api-listener.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/websockets/jetty/jetty-websocket-api-listener.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/websockets/jetty/jetty-websocket-api-listener.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/websockets/jetty/jetty-websocket-api-send-message.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/websockets/jetty/jetty-websocket-api-send-message.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/websockets/jetty/jetty-websocket-api-send-message.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/websockets/jetty/jetty-websocket-api-send-message.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/websockets/jetty/jetty-websocket-api-session.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/websockets/jetty/jetty-websocket-api-session.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/websockets/jetty/jetty-websocket-api-session.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/websockets/jetty/jetty-websocket-api-session.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/websockets/jetty/jetty-websocket-api.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/websockets/jetty/jetty-websocket-api.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/websockets/jetty/jetty-websocket-api.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/websockets/jetty/jetty-websocket-api.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/websockets/jetty/jetty-websocket-client-api.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/websockets/jetty/jetty-websocket-client-api.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/websockets/jetty/jetty-websocket-client-api.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/websockets/jetty/jetty-websocket-client-api.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/websockets/jetty/jetty-websocket-server-api.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/websockets/jetty/jetty-websocket-server-api.adoc similarity index 100% rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/websockets/jetty/jetty-websocket-server-api.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/websockets/jetty/jetty-websocket-server-api.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http.adoc new file mode 100644 index 00000000000..1b2a86adc43 --- /dev/null +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http.adoc @@ -0,0 +1,297 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +[[eg-server-http]] +=== HTTP Server Libraries + +The Eclipse Jetty Project has historically provided libraries to embed an HTTP +server and a Servlet Container. + +An `org.eclipse.jetty.server.Server` instance is the central component that +links together a collection of ``Connector``s and a collection of +``Handler``s, with threads from a `ThreadPool` doing the work. + +[plantuml] +---- +skinparam backgroundColor transparent +skinparam monochrome true +skinparam shadowing false +skinparam padding 5 + +scale 1.5 + +hide members +hide circle + +Server - ThreadPool +Connectors - Server +Server -- Handlers +---- + +The components that accept connections from clients are +`org.eclipse.jetty.server.Connector` instances. + +When a Jetty server interprets the HTTP protocol (both HTTP/1.1 and HTTP/2), +it uses `org.eclipse.jetty.server.Handler` instances to process incoming +requests and eventually produce responses. + +A `Server` must be created, configured and started: + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=simple] +---- + +The example above shows the simplest HTTP/1.1 server; it has no support for +HTTP sessions, for HTTP authentication, or for any of the features required +by the Servlet specification. + +All these features are provided by the Jetty Server Libraries and server +applications only need to put the required components together to provide +all the required features. +// TODO: link to a place where we discuss the handlers in more details. + +[[eg-server-connector]] +=== Server Connectors + +A `Connector` is the component that handles incoming requests from clients, +and works in conjunction with `ConnectionFactory` instances. + +The primary implementation is `org.eclipse.jetty.server.ServerConnector`. +`ServerConnector` uses a `java.nio.channels.ServerSocketChannel` to listen +to a TCP port and to accept TCP connections. + +Since `ServerConnector` wraps a `ServerSocketChannel`, it can be configured +in a similar way, for example the port to listen to, the network address +to bind to, etc.: + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=configureConnector] +---- + +The _acceptors_ are threads that compete to accept TCP connections on the +listening port, typically only one. +When a connection is accepted, `ServerConnector` wraps it and passes it to +the xref:eg-io-arch-selector-manager[`SelectorManager`]. +Therefore there is a little moment where the acceptor thread is not accepting +new connections because it is busy wrapping the just accepted one to pass it +to the `SelectorManager`. +Connections that are ready to be accepted but are not accepted yet are queued +in a bounded queue (at the OS level) whose capacity can be configured with the +`ServerConnector.acceptQueueSize` parameter. + +If your application must withstand a very high rate of connections opened, +configuring more than one acceptor thread may be beneficial: when one acceptor +thread accepts one connection, another acceptor thread can take over accepting +connections. + +The _selectors_ are components that manage a set of connected sockets, +implemented by xref:eg-io-arch-selector-manager[`ManagedSelector`]. +Each selector requires one thread and uses the Java NIO mechanism to +efficiently handle the set of connected sockets. +As a rule of thumb, a single selector can easily manage 1000-5000 sockets, +although the number may vary greatly depending on the application. + +It is possible to configure more than one `ServerConnector`, each listening +on different ports: + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=configureConnectors] +---- + +[[eg-server-connector-protocol]] +==== Configuring Protocols + +For each accepted TCP connection, `ServerConnector` asks a `ConnectionFactory` +to create a `Connection` object that handles the network traffic on that TCP +connection, parsing and generating bytes for a specific protocol (see +xref:eg-io-arch[this section] for more details about `Connection` objects). + +A `ServerConnector` can be configured with one or more ``ConnectionFactory``s. +If no `ConnectionFactory` is specified then `HttpConnectionFactory` is +implicitly configured. + +[[eg-server-connector-protocol-http11]] +===== Configuring HTTP/1.1 + +`HttpConnectionFactory` creates `HttpConnection` objects that parse bytes +and generate bytes for the HTTP/1.1 protocol. + +This is how you configure Jetty to support clear-text HTTP/1.1: + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=http11] +---- + +Supporting encrypted HTTP/1.1 (that is, requests with the HTTPS scheme) +is supported by configuring an `SslContextFactory` that has access to the +keyStore containing the private server key and public server certificate, +in this way: + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=tlsHttp11] +---- + +[[eg-server-connector-protocol-proxy-http11]] +===== Configuring Jetty behind a Load Balancer + +It is often the case that Jetty receives connections from a load balancer +configured to distribute the load among many Jetty backend servers. + +From the Jetty point of view, all the connections arrive from the load +balancer, rather than the real clients, but is possible to forward the real +client IP address and port to the backend Jetty server using the +link:https://www.haproxy.org/download/2.1/doc/proxy-protocol.txt[PROXY protocol]. + +NOTE: The PROXY protocol is widely supported by load balancers such as +link:http://cbonte.github.io/haproxy-dconv/2.2/configuration.html#5.2-send-proxy[HAProxy] +(via its `send-proxy` directive), or +link:https://docs.nginx.com/nginx/admin-guide/load-balancer/using-proxy-protocol[Nginx] +(via its `proxy_protocol on` directive), and others. + +To support this case, Jetty can be configured in this way: + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=proxyHTTP] +---- + +Note how the ``ConnectionFactory``s passed to `ServerConnector` are in order: +first PROXY, then HTTP/1.1. +Note also how the PROXY `ConnectionFactory` needs to know its _next_ protocol +(in this example, HTTP/1.1). + +Each `ConnectionFactory` is asked to create a `Connection` object for each +accepted TCP connection; the `Connection` objects will be chained together +to handle the bytes, each for its own protocol. +Therefore the `ProxyConnection` will handle the PROXY protocol bytes and +`HttpConnection` will handle the HTTP/1.1 bytes producing a request object +and response object that will be processed by ``Handler``s. + +[[eg-server-connector-protocol-http2]] +===== Configuring HTTP/2 + +It is well know that the HTTP ports are `80` (for clear-text HTTP) and `443` +for encrypted HTTP. +By using those ports, a client had _prior knowledge_ that the server would +speak, respectively, the HTTP/1.x protocol and the TLS protocol (and, after +decryption, the HTTP/1.x protocol). + +HTTP/2 was designed to be a smooth transition from HTTP/1.1 for users and +as such the HTTP ports were not changed. +However the HTTP/2 protocol is, on the wire, a binary protocol, completely +different from HTTP/1.1. +Therefore, with HTTP/2, clients that connect to port `80` may speak either +HTTP/1.1 or HTTP/2, and the server must figure out which version of the HTTP +protocol the client is speaking. + +Jetty can support both HTTP/1.1 and HTTP/2 on the same clear-text port by +configuring both the HTTP/1.1 and the HTTP/2 ``ConnectionFactory``s: + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=http11H2C] +---- + +Note how the ``ConnectionFactory``s passed to `ServerConnector` are in order: +first HTTP/1.1, then HTTP/2. +This is necessary to support both protocols on the same port: Jetty will +start parsing the incoming bytes as HTTP/1.1, but then realize that they +are HTTP/2 bytes and will therefore _upgrade_ from HTTP/1.1 to HTTP/2. + +This configuration is also typical when Jetty is installed in backend servers +behind a load balancer that also takes care of offloading TLS. +When Jetty is behind a load balancer, you can always prepend the PROXY +protocol as described in +xref:eg-server-connector-protocol-proxy-http11[this section]. + +When using encrypted HTTP/2, the unencrypted protocol is negotiated by client +and server using an extension to the TLS protocol called ALPN. + +Jetty supports ALPN and encrypted HTTP/2 with this configuration: + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=tlsALPNHTTP] +---- + +[[eg-server-handler]] +=== Server Handlers + +A `Handler` is the component that processes incoming HTTP requests and +eventually produces HTTP responses. + +``Handler``s can be organized in different ways: + +* in a sequence, where ``Handler``s are invoked one after the other +** `HandlerCollection` invokes _all_ ``Handler``s one after the other +** `HandlerList` invokes ``Handlers``s until one calls `Request.setHandled(true)` +to indicate that the request has been handled and no further `Handler` should +be invoked. +* nested, where one `Handler` invokes the next `Handler` +** `HandlerWrapper` implements this behavior + +The `HandlerCollection` behavior (invoking _all_ handlers) is useful when +for example the last `Handler` is a logging `Handler` that logs the the +request(that may have been modified by previous handlers). + +The `HandlerList` behavior (invoking handlers up to the first that calls +`Request.setHandled(true)`) is useful when different handlers process different +URIs or different virtual hosts: invoke one after the other until one matches +the URI or virtual host. + +The nested behavior is useful to enrich the request with additional services +such as HTTP session support (`SessionHandler`), or with specific behaviors +dictated by the Servlet specification (`ServletHandler`). + +``Handler``s can be organized in a tree by composing them together: + +[plantuml] +---- +skinparam backgroundColor transparent +skinparam monochrome true +skinparam shadowing false +skinparam padding 5 + +scale 1.5 + +hide members +hide circle + +HandlerCollection -- HandlerList +HandlerCollection -- LoggingHandler +HandlerList -- App1Handler +HandlerList -- App2Handler +App2Handler -- ServletHandler +---- + +In code it looks like this: + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=tree] +---- + +// TODO: old docs introduces briefly ServletHandler but I think it deserves its own section + +// TODO: old docs introduce ContextHandler here and WebAppContext + diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/server-io-arch.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/server/server-io-arch.adoc new file mode 100644 index 00000000000..2efff583192 --- /dev/null +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/server/server-io-arch.adoc @@ -0,0 +1,36 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +[[eg-server-io-arch]] +=== Server Libraries I/O Architecture + +The Jetty server libraries provide the basic components and APIs to implement +a network server. + +They build on the common xref:eg-io-arch[Jetty I/O Architecture] and provide server +specific concepts. + +The main I/O server-side class is `org.eclipse.jetty.server.ServerConnector`. + +A `ServerConnector` manages a list of factories. + +// TODO: here we want to be generic and talk about writing a custom factory +// without the use of Handler. Existing factories we document elsewhere. + +// TODO +include::../old_docs/architecture/server-side-architecture.adoc[] diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/server.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/server/server.adoc index 82d815d7d1b..a6dcc1feb45 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/server/server.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/server/server.adoc @@ -16,21 +16,36 @@ // ======================================================================== // -[[server]] -== Jetty Server Libraries +[[eg-server]] +== Server Libraries -include::embedding/chapter.adoc[] -include::maven/chapter.adoc[] -include::clients/http/chapter.adoc[] -include::handlers/chapter.adoc[] -include::websockets/intro/chapter.adoc[] -include::websockets/jetty/chapter.adoc[] -include::ant/chapter.adoc[] -include::frameworks/chapter.adoc[] -include::architecture/chapter.adoc[] -include::platforms/chapter.adoc[] -include::jetty-xml/chapter.adoc[] -include::troubleshooting/chapter.adoc[] -include::debugging/chapter.adoc[] -//include::contributing/chapter.adoc[] -include::upgrading/chapter.adoc[] +The Eclipse Jetty Project provides server-side libraries +that allow you to embed an HTTP or WebSocket server in your applications. +A typical example is a HTTP server that needs to expose a REST endpoint. +Another example is a proxy application that receives HTTP requests and +forwards them to third party services possibly using also the Jetty +xref:eg-client[client libraries]. + +While historically Jetty is an HTTP server, it is possible to use the Jetty +server-side libraries to write a generic network server that interprets +any network protocol (not only HTTP). + +If you are interested in the low-level details of how the Eclipse Jetty +server libraries work, or are interested in writing a custom protocol, +look at the xref:eg-server-io-arch[Server I/O Architecture]. + +The Jetty server-side libraries provide: + +* HTTP support for HTTP/1.0, HTTP/1.1, HTTP/2, clear-text or encrypted, for +applications that want to embed Jetty as a generic HTTP server or proxy, +via the xref:eg-server-http[HTTP libraries] +* HTTP/2 low-level support, for applications that want to explicitly handle +low-level HTTP/2 _sessions_, _streams_ and _frames_, via the +xref:eg-server-http2[HTTP/2 libraries] +* WebSocket support, for applications that want to embed a WebSocket server, +via the xref:eg-server-websocket[WebSocket libraries] + +include::http/server-http.adoc[] +include::http/server-http2.adoc[] +include::http/server-websocket.adoc[] +include::server-io-arch.adoc[] diff --git a/jetty-documentation/src/main/java/embedded/server/http/HTTPServerDocs.java b/jetty-documentation/src/main/java/embedded/server/http/HTTPServerDocs.java new file mode 100644 index 00000000000..1fdd72eafcd --- /dev/null +++ b/jetty-documentation/src/main/java/embedded/server/http/HTTPServerDocs.java @@ -0,0 +1,314 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package embedded.server.http; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory; +import org.eclipse.jetty.http.HttpCompliance; +import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory; +import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory; +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.HttpConnectionFactory; +import org.eclipse.jetty.server.ProxyConnectionFactory; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.SecureRequestCustomizer; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.SslConnectionFactory; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.server.handler.HandlerCollection; +import org.eclipse.jetty.server.handler.HandlerList; +import org.eclipse.jetty.server.handler.HandlerWrapper; +import org.eclipse.jetty.servlet.ServletHandler; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.eclipse.jetty.util.thread.QueuedThreadPool; + +@SuppressWarnings("unused") +public class HTTPServerDocs +{ + public void simple() throws Exception + { + // tag::simple[] + // Create and configure a ThreadPool. + QueuedThreadPool threadPool = new QueuedThreadPool(); + threadPool.setName("server"); + + // Create a Server instance. + Server server = new Server(threadPool); + + // Create a ServerConnector to accept connections from clients. + Connector connector = new ServerConnector(server); + + // Add the Connector to the Server + server.addConnector(connector); + + // Set a simple Handler to handle requests/responses. + server.setHandler(new AbstractHandler() + { + @Override + public void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) + { + // Mark the request as handled so that it + // will not be processed by other handlers. + jettyRequest.setHandled(true); + } + }); + + // Start the Server so it starts accepting connections from clients. + server.start(); + // end::simple[] + } + + public void configureConnector() throws Exception + { + // tag::configureConnector[] + Server server = new Server(); + + // The number of acceptor threads. + int acceptors = 1; + + // The number of selectors. + int selectors = 1; + + // Create a ServerConnector instance. + ServerConnector connector = new ServerConnector(server, 1, 1, new HttpConnectionFactory()); + + // Configure TCP parameters. + + // The TCP port to listen to. + connector.setPort(8080); + // The TCP address to bind to. + connector.setHost("127.0.0.1"); + // The TCP accept queue size. + connector.setAcceptQueueSize(128); + + server.addConnector(connector); + server.start(); + // end::configureConnector[] + } + + public void configureConnectors() throws Exception + { + // tag::configureConnectors[] + Server server = new Server(); + + // Create a ServerConnector instance on port 8080. + ServerConnector connector1 = new ServerConnector(server, 1, 1, new HttpConnectionFactory()); + connector1.setPort(8080); + server.addConnector(connector1); + + // Create another ServerConnector instance on port 9090, + // for example with a different HTTP configuration. + HttpConfiguration httpConfig2 = new HttpConfiguration(); + httpConfig2.setHttpCompliance(HttpCompliance.LEGACY); + ServerConnector connector2 = new ServerConnector(server, 1, 1, new HttpConnectionFactory(httpConfig2)); + connector2.setPort(9090); + server.addConnector(connector2); + + server.start(); + // end::configureConnectors[] + } + + public void http11() throws Exception + { + // tag::http11[] + Server server = new Server(); + + // The HTTP configuration object. + HttpConfiguration httpConfig = new HttpConfiguration(); + // Configure the HTTP support, for example: + httpConfig.setSendServerVersion(false); + + // The ConnectionFactory for HTTP/1.1. + HttpConnectionFactory http11 = new HttpConnectionFactory(httpConfig); + + // Create the ServerConnector. + ServerConnector connector = new ServerConnector(server, http11); + connector.setPort(8080); + + server.addConnector(connector); + server.start(); + // end::http11[] + } + + public void proxyHTTP() throws Exception + { + // tag::proxyHTTP[] + Server server = new Server(); + + // The HTTP configuration object. + HttpConfiguration httpConfig = new HttpConfiguration(); + // Configure the HTTP support, for example: + httpConfig.setSendServerVersion(false); + + // The ConnectionFactory for HTTP/1.1. + HttpConnectionFactory http11 = new HttpConnectionFactory(httpConfig); + + // The ConnectionFactory for the PROXY protocol. + ProxyConnectionFactory proxy = new ProxyConnectionFactory(http11.getProtocol()); + + // Create the ServerConnector. + ServerConnector connector = new ServerConnector(server, proxy, http11); + connector.setPort(8080); + + server.addConnector(connector); + server.start(); + // end::proxyHTTP[] + } + + public void tlsHttp11() throws Exception + { + // tag::tlsHttp11[] + Server server = new Server(); + + // The HTTP configuration object. + HttpConfiguration httpConfig = new HttpConfiguration(); + // Add the SecureRequestCustomizer because we are using TLS. + httpConfig.addCustomizer(new SecureRequestCustomizer()); + + // The ConnectionFactory for HTTP/1.1. + HttpConnectionFactory http11 = new HttpConnectionFactory(httpConfig); + + // Configure the SslContextFactory with the keyStore information. + SslContextFactory.Server sslContextFactory = new SslContextFactory.Server(); + sslContextFactory.setKeyStorePath("/path/to/keystore"); + sslContextFactory.setKeyStorePassword("secret"); + + // The ConnectionFactory for TLS. + SslConnectionFactory tls = new SslConnectionFactory(sslContextFactory, http11.getProtocol()); + + // The ServerConnector instance. + ServerConnector connector = new ServerConnector(server, tls, http11); + connector.setPort(8443); + + server.addConnector(connector); + server.start(); + // end::tlsHttp11[] + } + + public void http11H2C() throws Exception + { + // tag::http11H2C[] + Server server = new Server(); + + // The HTTP configuration object. + HttpConfiguration httpConfig = new HttpConfiguration(); + + // The ConnectionFactory for HTTP/1.1. + HttpConnectionFactory http11 = new HttpConnectionFactory(httpConfig); + + // The ConnectionFactory for clear-text HTTP/2. + HTTP2CServerConnectionFactory h2c = new HTTP2CServerConnectionFactory(httpConfig); + + // The ServerConnector instance. + ServerConnector connector = new ServerConnector(server, http11, h2c); + connector.setPort(8080); + + server.addConnector(connector); + server.start(); + // end::http11H2C[] + } + + public void tlsALPNHTTP() throws Exception + { + // tag::tlsALPNHTTP[] + Server server = new Server(); + + // The HTTP configuration object. + HttpConfiguration httpConfig = new HttpConfiguration(); + // Add the SecureRequestCustomizer because we are using TLS. + httpConfig.addCustomizer(new SecureRequestCustomizer()); + + // The ConnectionFactory for HTTP/1.1. + HttpConnectionFactory http11 = new HttpConnectionFactory(httpConfig); + + // The ConnectionFactory for HTTP/2. + HTTP2ServerConnectionFactory h2 = new HTTP2ServerConnectionFactory(httpConfig); + + // The ALPN ConnectionFactory. + ALPNServerConnectionFactory alpn = new ALPNServerConnectionFactory(); + // The default protocol to use in case there is no negotiation. + alpn.setDefaultProtocol(http11.getProtocol()); + + // Configure the SslContextFactory with the keyStore information. + SslContextFactory.Server sslContextFactory = new SslContextFactory.Server(); + sslContextFactory.setKeyStorePath("/path/to/keystore"); + sslContextFactory.setKeyStorePassword("secret"); + + // The ConnectionFactory for TLS. + SslConnectionFactory tls = new SslConnectionFactory(sslContextFactory, alpn.getProtocol()); + + // The ServerConnector instance. + ServerConnector connector = new ServerConnector(server, tls, alpn, http11, h2); + connector.setPort(8443); + + server.addConnector(connector); + server.start(); + // end::tlsALPNHTTP[] + } + + public void tree() throws Exception + { + class LoggingHandler extends AbstractHandler + { + @Override + public void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) + { + } + } + + class App1Handler extends AbstractHandler + { + @Override + public void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) + { + } + } + + class App2Handler extends HandlerWrapper + { + @Override + public void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) + { + } + } + + // tag::tree[] + // Create a Server instance. + Server server = new Server(); + + HandlerCollection collection = new HandlerCollection(); + // Link the root Handler with the Server. + server.setHandler(collection); + + HandlerList list = new HandlerList(); + collection.addHandler(list); + collection.addHandler(new LoggingHandler()); + + list.addHandler(new App1Handler()); + App2Handler app2Handler = new App2Handler(); + list.addHandler(app2Handler); + + app2Handler.setHandler(new ServletHandler()); + // end::tree[] + } +} From 35dce5f31510ce5a5d52010e76f624f3f0f46993 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Fri, 10 Apr 2020 15:40:02 +0200 Subject: [PATCH 068/101] Improvements to the Jetty server documentation. Written connector and handler sections. Signed-off-by: Simone Bordet --- jetty-documentation/pom.xml | 41 +- .../client/http/client-http-cookie.adoc | 6 +- .../client/http/client-http-intro.adoc | 2 +- .../client/http/client-http-transport.adoc | 4 +- .../client/http2/client-http2.adoc | 4 +- .../embedded-guide/old_docs/server.adoc | 2 +- .../server/http/server-http-connector.adoc | 195 +++++++ .../http/server-http-handler-implement.adoc | 71 +++ .../server/http/server-http-handler-use.adoc | 464 +++++++++++++++++ .../server/http/server-http-handler.adoc | 103 ++++ .../server/http/server-http.adoc | 244 +-------- .../server/http2/server-http2.adoc | 22 + .../embedded-guide/server/server.adoc | 5 +- .../server/websocket/server-websocket.adoc | 22 + .../embedded/server/http/HTTPServerDocs.java | 488 +++++++++++++++++- .../jetty/server/handler/DefaultHandler.java | 2 +- .../handler/SecuredRedirectHandler.java | 4 +- 17 files changed, 1408 insertions(+), 271 deletions(-) create mode 100644 jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-connector.adoc create mode 100644 jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-handler-implement.adoc create mode 100644 jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-handler-use.adoc create mode 100644 jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-handler.adoc create mode 100644 jetty-documentation/src/main/asciidoc/embedded-guide/server/http2/server-http2.adoc create mode 100644 jetty-documentation/src/main/asciidoc/embedded-guide/server/websocket/server-websocket.adoc diff --git a/jetty-documentation/pom.xml b/jetty-documentation/pom.xml index e318d4a57b1..acf0277731c 100644 --- a/jetty-documentation/pom.xml +++ b/jetty-documentation/pom.xml @@ -1,24 +1,4 @@ - - org.eclipse.jetty @@ -63,6 +43,21 @@ jetty-servlet ${project.version} + + org.eclipse.jetty + jetty-servlets + ${project.version} + + + org.eclipse.jetty + jetty-rewrite + ${project.version} + + + org.eclipse.jetty + jetty-webapp + ${project.version} + org.eclipse.jetty jetty-alpn-server @@ -88,6 +83,12 @@ http2-http-client-transport ${project.version} + + org.eclipse.jetty + jetty-slf4j-impl + ${project.version} + compile + diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-cookie.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-cookie.adoc index 8c414de749f..c5b92f7c723 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-cookie.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-cookie.adoc @@ -75,14 +75,14 @@ Jetty is compliant with link:https://tools.ietf.org/html/rfc6265[RFC6265], and a Previously, Version=1 cookies defined in link:https://tools.ietf.org/html/rfc2109[RFC2109] (and continued in link:https://tools.ietf.org/html/rfc2965[RFC2965]) allowed for special/reserved characters to be enclosed within double quotes when declared in a `Set-Cookie` response header: -[source,subs="{sub-order}"] +[source,screen] ---- Set-Cookie: foo="bar;baz";Version=1;Path="/secur" ---- This was added to the HTTP Response as follows: -[source,java,subs="{sub-order}"] +[source,java] ---- protected void service(HttpServletRequest request, HttpServletResponse response) { @@ -95,7 +95,7 @@ protected void service(HttpServletRequest request, HttpServletResponse response) The introduction of RFC6265 has rendered this approach no longer possible; users are now required to encode cookie values that use these special characters. This can be done utilizing `javax.servlet.http.Cookie` as follows: -[source,java,subs="{sub-order}"] +[source,java] ---- javax.servlet.http.Cookie cookie = new Cookie("foo", URLEncoder.encode("bar;baz", "UTF-8")); ---- diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-intro.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-intro.adoc index ae1c4a364a3..ab78e59dedc 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-intro.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-intro.adoc @@ -49,7 +49,7 @@ Out of the box features that you get with the Jetty HTTP client include: The Jetty artifact that provides the main HTTP client implementation is `jetty-client`. The Maven artifact coordinates are the following: -[source,xml,subs="{sub-order}"] +[source,xml,subs=normal] ---- org.eclipse.jetty diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-transport.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-transport.adoc index e5840c61390..5c59f5f8920 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-transport.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-transport.adoc @@ -34,7 +34,7 @@ semantic objects that can be used by applications. The most common protocol format is HTTP/1.1, a textual protocol with lines separated by `\r\n`: -[source,screen,subs="{sub-order}"] +[source,screen] ---- GET /index.html HTTP/1.1\r\n Host: domain.com\r\n @@ -44,7 +44,7 @@ Host: domain.com\r\n However, the same request can be made using FastCGI, a binary protocol: -[source,screen,subs="{sub-order}"] +[source,screen] ---- x01 x01 x00 x01 x00 x08 x00 x00 x00 x01 x01 x00 x00 x00 x00 x00 diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http2/client-http2.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http2/client-http2.adoc index 546b61fa9fc..e7c48438eaa 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http2/client-http2.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http2/client-http2.adoc @@ -38,7 +38,7 @@ _frames_, and this is quite a rare use case. The Maven artifact coordinates for the HTTP/2 client library are the following: -[source,xml,subs="{sub-order}"] +[source,xml,subs=normal] ---- org.eclipse.jetty.http2 @@ -198,7 +198,7 @@ In order to send a HTTP request to the server, the client must send a headers. Sending the `HEADERS` frame opens the `Stream`: -[source,java,indent=0,subs={sub-order}] +[source,java,indent=0,subs=normal] ---- include::../../{doc_code}/embedded/client/http2/HTTP2ClientDocs.java[tags=newStream] ---- diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/server.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/server.adoc index 82d815d7d1b..bfd8444bab5 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/server.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/server.adoc @@ -17,7 +17,7 @@ // [[server]] -== Jetty Server Libraries +== OLD DOCUMENTATION include::embedding/chapter.adoc[] include::maven/chapter.adoc[] diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-connector.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-connector.adoc new file mode 100644 index 00000000000..d5598d5adeb --- /dev/null +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-connector.adoc @@ -0,0 +1,195 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +[[eg-server-http-connector]] +=== Server Connectors + +A `Connector` is the component that handles incoming requests from clients, +and works in conjunction with `ConnectionFactory` instances. + +The primary implementation is `org.eclipse.jetty.server.ServerConnector`. +`ServerConnector` uses a `java.nio.channels.ServerSocketChannel` to listen +to a TCP port and to accept TCP connections. + +Since `ServerConnector` wraps a `ServerSocketChannel`, it can be configured +in a similar way, for example the port to listen to, the network address +to bind to, etc.: + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=configureConnector] +---- + +The _acceptors_ are threads (typically only one) that compete to accept TCP +connections on the listening port. +When a connection is accepted, `ServerConnector` wraps the accepted +`SocketChannel` and passes it to the +xref:eg-io-arch-selector-manager[`SelectorManager`]. +Therefore there is a little moment where the acceptor thread is not accepting +new connections because it is busy wrapping the just accepted one to pass it +to the `SelectorManager`. +Connections that are ready to be accepted but are not accepted yet are queued +in a bounded queue (at the OS level) whose capacity can be configured with the +`ServerConnector.acceptQueueSize` parameter. + +If your application must withstand a very high rate of connections opened, +configuring more than one acceptor thread may be beneficial: when one acceptor +thread accepts one connection, another acceptor thread can take over accepting +connections. + +The _selectors_ are components that manage a set of connected sockets, +implemented by xref:eg-io-arch-selector-manager[`ManagedSelector`]. +Each selector requires one thread and uses the Java NIO mechanism to +efficiently handle the set of connected sockets. +As a rule of thumb, a single selector can easily manage up to 1000-5000 +sockets, although the number may vary greatly depending on the application. + +It is possible to configure more than one `ServerConnector`, each listening +on a different port: + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=configureConnectors] +---- + +[[eg-server-http-connector-protocol]] +==== Configuring Protocols + +For each accepted TCP connection, `ServerConnector` asks a `ConnectionFactory` +to create a `Connection` object that handles the network traffic on that TCP +connection, parsing and generating bytes for a specific protocol (see +xref:eg-io-arch[this section] for more details about `Connection` objects). + +A `ServerConnector` can be configured with one or more ``ConnectionFactory``s. +If no `ConnectionFactory` is specified then `HttpConnectionFactory` is +implicitly configured. + +[[eg-server-http-connector-protocol-http11]] +===== Configuring HTTP/1.1 + +`HttpConnectionFactory` creates `HttpConnection` objects that parse bytes +and generate bytes for the HTTP/1.1 protocol. + +This is how you configure Jetty to support clear-text HTTP/1.1: + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=http11] +---- + +Supporting encrypted HTTP/1.1 (that is, requests with the HTTPS scheme) +is supported by configuring an `SslContextFactory` that has access to the +keyStore containing the private server key and public server certificate, +in this way: + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=tlsHttp11] +---- + +[[eg-server-http-connector-protocol-proxy-http11]] +===== Configuring Jetty behind a Load Balancer + +It is often the case that Jetty receives connections from a load balancer +configured to distribute the load among many Jetty backend servers. + +From the Jetty point of view, all the connections arrive from the load +balancer, rather than the real clients, but is possible to configure the load +balancer to forward the real client IP address and port to the backend Jetty +server using the +link:https://www.haproxy.org/download/2.1/doc/proxy-protocol.txt[PROXY protocol]. + +NOTE: The PROXY protocol is widely supported by load balancers such as +link:http://cbonte.github.io/haproxy-dconv/2.2/configuration.html#5.2-send-proxy[HAProxy] +(via its `send-proxy` directive), +link:https://docs.nginx.com/nginx/admin-guide/load-balancer/using-proxy-protocol[Nginx] +(via its `proxy_protocol on` directive) and others. + +To support this case, Jetty can be configured in this way: + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=proxyHTTP] +---- + +Note how the ``ConnectionFactory``s passed to `ServerConnector` are in order: +first PROXY, then HTTP/1.1. +Note also how the PROXY `ConnectionFactory` needs to know its _next_ protocol +(in this example, HTTP/1.1). + +Each `ConnectionFactory` is asked to create a `Connection` object for each +accepted TCP connection; the `Connection` objects will be chained together +to handle the bytes, each for its own protocol. +Therefore the `ProxyConnection` will handle the PROXY protocol bytes and +`HttpConnection` will handle the HTTP/1.1 bytes producing a request object +and response object that will be processed by ``Handler``s. + +[[eg-server-http-connector-protocol-http2]] +===== Configuring HTTP/2 + +It is well known that the HTTP ports are `80` (for clear-text HTTP) and `443` +for encrypted HTTP. +By using those ports, a client had _prior knowledge_ that the server would +speak, respectively, the HTTP/1.x protocol and the TLS protocol (and, after +decryption, the HTTP/1.x protocol). + +HTTP/2 was designed to be a smooth transition from HTTP/1.1 for users and +as such the HTTP ports were not changed. +However the HTTP/2 protocol is, on the wire, a binary protocol, completely +different from HTTP/1.1. +Therefore, with HTTP/2, clients that connect to port `80` may speak either +HTTP/1.1 or HTTP/2, and the server must figure out which version of the HTTP +protocol the client is speaking. + +Jetty can support both HTTP/1.1 and HTTP/2 on the same clear-text port by +configuring both the HTTP/1.1 and the HTTP/2 ``ConnectionFactory``s: + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=http11H2C] +---- + +Note how the ``ConnectionFactory``s passed to `ServerConnector` are in order: +first HTTP/1.1, then HTTP/2. +This is necessary to support both protocols on the same port: Jetty will +start parsing the incoming bytes as HTTP/1.1, but then realize that they +are HTTP/2 bytes and will therefore _upgrade_ from HTTP/1.1 to HTTP/2. + +This configuration is also typical when Jetty is installed in backend servers +behind a load balancer that also takes care of offloading TLS. +When Jetty is behind a load balancer, you can always prepend the PROXY +protocol as described in +xref:eg-server-http-connector-protocol-proxy-http11[this section]. + +When using encrypted HTTP/2, the unencrypted protocol is negotiated by client +and server using an extension to the TLS protocol called ALPN. + +Jetty supports ALPN and encrypted HTTP/2 with this configuration: + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=tlsALPNHTTP] +---- + +Note how the ``ConnectionFactory``s passed to `ServerConnector` are in order: +TLS, ALPN, HTTP/1.1, HTTP/2. + +Jetty starts parsing TLS bytes so that it can obtain the ALPN extension. +With the ALPN extension information, Jetty can negotiate a protocol and +pick, among the ``ConnectionFactory``s supported by the `ServerConnector`, +the `ConnectionFactory` correspondent to the negotiated protocol. diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-handler-implement.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-handler-implement.adoc new file mode 100644 index 00000000000..625d206f2e0 --- /dev/null +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-handler-implement.adoc @@ -0,0 +1,71 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +[[eg-server-http-handler-implement]] +==== Implementing Handler + +The `Handler` API consist fundamentally of just one method: + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=handlerAPI] +---- + +The `target` parameter is an identifier for the resource. +This is normally the URI that is parsed from an HTTP request. +However, a request could be forwarded to either a named resource, in which case +`target` will be the name of the resource, or to a different URI, in which case +`target` will be the new URI. + +Applications may wrap the request or response (or both) and forward the wrapped +request or response to a different URI (which may be possibly handled by a +different `Handler`). +This is the reason why there are two request parameters in the `Handler` APIs: +the first is the unwrapped, original, request while the second is the +application-wrapped request. + +[[eg-server-http-handler-impl-hello]] +===== Hello World Handler + +A simple "Hello World" `Handler` is the following: + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=handlerHello] +---- + +Such a simple `Handler` extends from `AbstractHandler` and can access the +request and response main features, such as reading request headers and +content, or writing response headers and content. + +[[eg-server-http-handler-impl-filter]] +===== Filtering Handler + +A filtering `Handler` is a handler that perform some modification to the +request or response, and then either forwards the request to another +`Handler` or produces an error response: + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=handlerFilter] +---- + +Note how a filtering `Handler` extends from `HandlerWrapper` and as such +needs another handler to forward the request processing to, and how the +two``Handler``s needs to be linked together to work properly. + diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-handler-use.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-handler-use.adoc new file mode 100644 index 00000000000..50c46854783 --- /dev/null +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-handler-use.adoc @@ -0,0 +1,464 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +[[eg-server-http-handler-use]] +==== Using Provided Handlers + +Web applications are the unit of deployment in an HTTP server or Servlet +container such as Jetty. + +Two different web applications are typically deployed on different +__context path__s, where a _context path_ is the initial segment of the URI +path. +For example, web application `webappA` that implements a web user interface +for an e-commerce site may be deployed to context path `/shop`, while web +application `webappB` that implements a REST API for the e-commerce business +may be deployed to `/api`. + +A client making a request to URI `/shop/cart` is directed by Jetty to +`webappA`, while a request to URI `/api/products` is directed to `webappB`. + +An alternative way to deploy the two web applications of the example above +is to use _virtual hosts_. +A _virtual host_ is a subdomain of the primary domain that shares the same +IP address with the primary domain. +If the e-commerce business primary domain is `domain.com`, then a virtual +host for `webappA` could be `shop.domain.com`, while a virtual host for +`webappB` could be `api.domain.com`. + +Web application `webappA` can now be deployed to virtual host +`shop.domain.com` and context path `/`, while web application `webappB` +can be deployed to virtual host `api.domain.com` and context path `/`. +Both applications have the same context path `/`, but they can be +distinguished by the subdomain. + +A client making a request to `+https://shop.domain.com/cart+` is +directed by Jetty to `webappA`, while a request to +`+https://api.domain.com/products+` is directed to `webappB`. + +Therefore, in general, a web application is deployed to a _context_ +which can be seen as the pair `(virtual_host, context_path)`. +In the first case the contexts were `(domain.com, /shop)` and +`(domain.com, /api)`, while in the second case the contexts were +`(shop.domain.com, /)` and `(api.domain.com, /)`. +Server applications using the Jetty Server Libraries create and +configure a _context_ for each web application. + +[[eg-server-http-handler-use-context]] +===== ContextHandler + +`ContextHandler` is a `Handler` that represents a _context_ for a web +application. It is a `HandlerWrapper` that performs some action before +and after delegating to the nested `Handler`. +// TODO: expand on what the ContextHandler does, e.g. ServletContext. + +The simplest use of `ContextHandler` is the following: + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=contextHandler] +---- + +The `Handler` tree structure looks like the following: + +[source,screen] +---- +Server +└── ContextHandler /shop + └── ShopHandler +---- + +[[eg-server-http-handler-use-context-collection]] +===== ContextHandlerCollection + +Server applications may need to deploy to Jetty more than one web application. + +Recall from the xref:eg-server-http-handler[introduction] that Jetty offers +`HandlerCollection` and `HandlerList` that may contain a sequence of children +``Handler``s. +However, both of these have no knowledge of the concept of _context_ and just +iterate through the sequence of ``Handler``s. + +A better choice for multiple web application is `ContextHandlerCollection`, +that matches a _context_ from either its _context path_ or _virtual host_, +without iterating through the ``Handler``s. + +If `ContextHandlerCollection` does not find a match, it just returns. +What happens next depends on the `Handler` tree structure: other ``Handler``s +may be invoked after `ContextHandlerCollection`, for example `DefaultHandler` +(see xref:eg-server-http-handler-use-util-default-handler[this section]). +Eventually, if `Request.setHandled(true)` is not called, Jetty returns a HTTP +`404` response to the client. + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=contextHandlerCollection] +---- + +The `Handler` tree structure looks like the following: + +[source,screen] +---- +Server +└── ContextHandlerCollection + ├── ContextHandler /shop + │ └── ShopHandler + └── ContextHandler /api + └── RESTHandler +---- + +[[eg-server-http-handler-use-servlet-context]] +===== ServletContextHandler + +``Handler``s are easy to write, but often web applications have already been +written using the Servlet APIs, using ``Servlet``s and ``Filter``s. + +`ServletContextHandler` is a `ContextHandler` that provides support for the +Servlet APIs and implements the behaviors required by the Servlet specification. + +The Maven artifact coordinates are: + +[source,xml,subs=normal] +---- + + org.eclipse.jetty + jetty-servlet + {version} + +---- + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=servletContextHandler] +---- + +The `Handler` and Servlet components tree structure looks like the following: + +[source,screen,subs=normal] +---- +Server +└── ServletContextHandler /shop + ├── _ShopCartServlet /cart/*_ + └── _CrossOriginFilter /*_ +---- + +Note how the Servlet components (they are not ``Handler``s) are represented in +_italic_. + +Note also how adding a `Servlet` or a `Filter` returns a _holder_ object that +can be used to specify additional configuration for that particular `Servlet` +or `Filter`. + +When a request arrives to `ServletContextHandler` the request URI will be +matched against the ``Filter``s and ``Servlet`` mappings and only those that +match will process the request, as dictated by the Servlet specification. + +IMPORTANT: `ServletContextHandler` is a terminal `Handler`, that is it always +calls `Request.setHandled(true)` when invoked. +Server applications must be careful when creating the `Handler` +tree to put ``ServletContextHandler``s as last ``Handler``s in a `HandlerList` +or as children of `ContextHandlerCollection`. + +[[eg-server-http-handler-use-webapp-context]] +===== WebAppContext + +`WebAppContext` is a `ServletContextHandler` that auto configures itself by +reading a `web.xml` Servlet configuration file. + +Server applications can specify a `+*.war+` file or a directory with the +structure of a `+*.war+` file to `WebAppContext` to deploy a standard Servlet +web application packaged as a `war` (as defined by the Servlet specification). + +Where server applications using `ServletContextHandler` must manually invoke +methods to add ``Servlet``s and ``Filter``s, `WebAppContext` reads +`WEB-INF/web.xml` to add ``Servlet``s and ``Filter``s. + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=webAppContextHandler] +---- + +[[eg-server-http-handler-use-resource-handler]] +===== ResourceHandler -- Static Content + +Static content such as images or files (HTML, JavaScript, CSS) can be sent +by Jetty very efficiently because Jetty can write the content asynchronously, +using direct ``ByteBuffer``s to minimize data copy, and using a memory cache +for faster access to the data to send. + +Being able to write content asynchronously means that if the network gets +congested (for example, the client reads the content very slowly) and the +server stalls the send of the requested data, then Jetty will wait to resume +the send _without_ blocking a thread to finish the send. + +`ResourceHandler` supports the following features: + +* Welcome files, for example serving `/index.html` for request URI `/` +* Precompressed resources, serving a precompressed `/document.txt.gz` for +request URI `/document.txt` +* link:https://tools.ietf.org/html/rfc7233[Range requests], for requests +containing the `Range` header, which allows clients to pause and resume +downloads of large files +* Directory listing, serving a HTML page with the file list of the requested +directory +* Conditional headers, for requests containing the `If-Match`, `If-None-Match`, +`If-Modified-Since`, `If-Unmodified-Since` headers. + +The number of features supported and the efficiency in sending static content +are on the same level as those of common front-end servers used to serve +static content such as Nginx or Apache. +Therefore, the traditional architecture where Nginx/Apache was the front-end +server used only to send static content and Jetty was the back-end server used +only to send dynamic content is somehow obsolete as Jetty can perform +efficiently both tasks. +This leads to simpler systems (less components to configure and manage) and +more performance (no need to proxy dynamic requests from front-end servers +to back-end servers). + +NOTE: It is common to use Nginx/Apache as load balancers, or as rewrite/redirect +servers. We typically recommend link:https://haproxy.org[HAProxy] as load +balancer, and Jetty has +xref:eg-server-http-handler-use-util-rewrite-handler[rewrite/redirect features] +as well. + +This is how you configure a `ResourceHandler` to create a simple file server: + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=resourceHandler] +---- + +If you need to serve static resources from multiple directories: + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=multipleResourcesHandler] +---- + +If the resource is not found, `ResourceHandler` will not call +`Request.setHandled(true)` so what happens next depends on the `Handler` +tree structure. See also +xref:eg-server-http-handler-use-util-default-handler[how to use] `DefaultHandler`. + +[[eg-server-http-handler-use-default-servlet]] +===== DefaultServlet -- Static Content for Servlets + +If you have a +xref:eg-server-http-handler-use-servlet-context[Servlet web application], +you may want to use a `DefaultServlet` instead of `ResourceHandler`. +The features are similar, but `DefaultServlet` is more commonly used to +serve static files for Servlet web applications. + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=defaultServlet] +---- + +[[eg-server-http-handler-use-util-gzip-handler]] +===== GzipHandler + +`GzipHandler` provides supports for automatic decompression of compressed +request content and automatic compression of response content. + +`GzipHandler` is a `HandlerWrapper` that inspects the request and, if the +request matches the `GzipHandler` configuration, just installs the required +components to eventually perform decompression of the request content or +compression of the response content. +The decompression/compression is not performed until the web application +reads request content or writes response content. + +`GzipHandler` can be configured at the server level in this way: + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=serverGzipHandler] +---- + +The `Handler` tree structure looks like the following: + +[source,screen] +---- +Server +└── GzipHandler + └── ContextHandlerCollection + ├── ContextHandler 1 + :── ... + └── ContextHandler N +---- + +However, in less common cases, you can configure `GzipHandler` on a +per-context basis, for example because you want to configure `GzipHandler` +with different parameters for each context, or because you want only some +contexts to have compression support: + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=contextGzipHandler] +---- + +The `Handler` tree structure looks like the following: + +[source,screen] +---- +Server +└── ContextHandlerCollection + └── ContextHandlerCollection + ├── GzipHandler + │ └── ContextHandler /shop + │ └── ShopHandler + └── ContextHandler /api + └── RESTHandler +---- + +// TODO: does ServletContextHandler really need a special configuration? + +[[eg-server-http-handler-use-util-rewrite-handler]] +===== RewriteHandler + +`RewriteHandler` provides support for URL rewriting, very similarly to +link:https://httpd.apache.org/docs/current/mod/mod_rewrite.html[Apache's mod_rewrite] +or +link:https://nginx.org/en/docs/http/ngx_http_rewrite_module.html[Nginx rewrite module]. + +The Maven artifact coordinates are: + +[source,xml,subs=normal] +---- + + org.eclipse.jetty + jetty-rewrite + {version} + +---- + +`RewriteHandler` can be configured with a set of __rule__s; a _rule_ inspects +the request and when it matches it performs some change to the request (for +example, changes the URI path, adds/removes headers, etc.). + +The Jetty Server Libraries provide rules for the most common usages, but you +can write your own rules by extending the +`org.eclipse.jetty.rewrite.handler.Rule` class. + +Please refer to the `jetty-rewrite` module +link:{JDURL}/org/eclipse/jetty/rewrite/handler/package-summary.html[javadocs] +for the complete list of available rules. + +You typically want to configure `RewriteHandler` at the server level, although +it is possible to configure it on a per-context basis. + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=rewriteHandler] +---- + +The `Handler` tree structure looks like the following: + +[source,screen] +---- +Server +└── RewriteHandler + └── ContextHandlerCollection + ├── ContextHandler 1 + :── ... + └── ContextHandler N +---- + +[[eg-server-http-handler-use-util-stats-handler]] +===== StatisticsHandler + +`StatisticsHandler` gathers and exposes a number of statistic values related +to request processing such as: + +* Total number of requests +* Current number of concurrent requests +* Minimum, maximum, average and standard deviation of request processing times +* Number of responses grouped by HTTP code (i.e. how many `2xx` responses, how +many `3xx` responses, etc.) +* Total response content bytes + +Server applications can read these values and use them internally, or expose +them via some service, or export them via JMX. +// TODO: xref to the JMX section. + +`StatisticsHandler` can be configured at the server level or at the context +level. + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=statsHandler] +---- + +The `Handler` tree structure looks like the following: + +[source,screen] +---- +Server +└── StatisticsHandler + └── ContextHandlerCollection + ├── ContextHandler 1 + :── ... + └── ContextHandler N +---- + +[[eg-server-http-handler-use-util-secure-handler]] +===== SecuredRedirectHandler -- Redirect from HTTP to HTTPS + +// TODO: wait for issue #4766 +TODO + +[[eg-server-http-handler-use-util-default-handler]] +===== DefaultHandler + +`DefaultHandler` is a terminal `Handler` that always calls +`Request.setHandled(true)` and performs the following: + +* Serves the `favicon.ico` Jetty icon when it is requested +* Sends a HTTP `404` response for any other request +* The HTTP `404` response content nicely shows a HTML table with all the +contexts deployed on the `Server` instance + +`DefaultHandler` is best used as the last `Handler` of a `HandlerList`, +for example: + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=defaultHandler] +---- + +The `Handler` tree structure looks like the following: + +[source,screen] +---- +Server +└── HandlerList + ├── ContextHandlerCollection + │ ├── ContextHandler 1 + │ :── ... + │ └── ContextHandler N + └── DefaultHandler +---- + +In the example above, `ContextHandlerCollection` will try to match a request +to one of the contexts; if the match fails, `HandlerList` will call the next +`Handler` which is `DefaultHandler` that will return a HTTP `404` with an +HTML page showing the existing contexts deployed on the `Server`. + +NOTE: `DefaultHandler` just sends a nicer HTTP `404` response in case of +wrong requests from clients. +Jetty will send an HTTP `404` response anyway if `DefaultHandler` is not +used. diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-handler.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-handler.adoc new file mode 100644 index 00000000000..104f2c50ece --- /dev/null +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-handler.adoc @@ -0,0 +1,103 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +[[eg-server-http-handler]] +=== Server Handlers + +An `org.eclipse.jetty.server.Handler` is the component that processes +incoming HTTP requests and eventually produces HTTP responses. + +``Handler``s can be organized in different ways: + +* in a sequence, where ``Handler``s are invoked one after the other +** `HandlerCollection` invokes _all_ ``Handler``s one after the other +** `HandlerList` invokes ``Handlers``s until one calls `Request.setHandled(true)` +to indicate that the request has been handled and no further `Handler` should +be invoked +* nested, where one `Handler` invokes the next, nested, `Handler` +** `HandlerWrapper` implements this behavior + +The `HandlerCollection` behavior (invoking _all_ handlers) is useful when +for example the last `Handler` is a logging `Handler` that logs the request +(that may have been modified by previous handlers). + +The `HandlerList` behavior (invoking handlers up to the first that calls +`Request.setHandled(true)`) is useful when each handler processes a different +URIs or a different virtual hosts: ``Handler``s are invoked one after the +other until one matches the URI or virtual host. + +The nested behavior is useful to enrich the request with additional services +such as HTTP session support (`SessionHandler`), or with specific behaviors +dictated by the Servlet specification (`ServletHandler`). + +``Handler``s can be organized in a tree by composing them together: + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=handlerTree] +---- + +The corresponding `Handler` tree structure looks like the following: + +[source,screen] +---- +HandlerCollection +├── HandlerList +│ ├── App1Handler +│ └── HandlerWrapper +│ └── App2Handler +└── LoggingHandler +---- + +//// +PlantUML cannot render a tree left-aligned :( +[plantuml] +---- +skinparam backgroundColor transparent +skinparam monochrome true +skinparam shadowing false +skinparam padding 5 + +scale 1.5 + +hide members +hide circle + +HandlerCollection -- HandlerList +HandlerCollection -- LoggingHandler +HandlerList -- App1Handler +HandlerList -- App2Handler +App2Handler -- ServletHandler +---- +//// + +Server applications should rarely write custom ``Handler``s, preferring +instead to use existing ``Handler``s provided by the Jetty Server Libraries +for managing web application contexts, security, HTTP sessions and Servlet +support. +Refer to xref:eg-server-http-handler-use[this section] for more information about +how to use the ``Handler``s provided by the Jetty Server Libraries. + +However, in some cases the additional features are not required, or additional +constraints on memory footprint, or performance, or just simplicity must be met. +In these cases, implementing your own `Handler` may be a better solution. +Refer to xref:eg-server-http-handler-implement[this section] for more information +about how to write your own ``Handler``s. + +include::server-http-handler-use.adoc[] +include::server-http-handler-implement.adoc[] diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http.adoc index 1b2a86adc43..39334067a3b 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http.adoc @@ -22,6 +22,17 @@ The Eclipse Jetty Project has historically provided libraries to embed an HTTP server and a Servlet Container. +The Maven artifact coordinates are: + +[source,xml,subs=normal] +---- + + org.eclipse.jetty + jetty-server + {version} + +---- + An `org.eclipse.jetty.server.Server` instance is the central component that links together a collection of ``Connector``s and a collection of ``Handler``s, with threads from a `ThreadPool` doing the work. @@ -44,7 +55,7 @@ Server -- Handlers ---- The components that accept connections from clients are -`org.eclipse.jetty.server.Connector` instances. +`org.eclipse.jetty.server.Connector` implementations. When a Jetty server interprets the HTTP protocol (both HTTP/1.1 and HTTP/2), it uses `org.eclipse.jetty.server.Handler` instances to process incoming @@ -66,232 +77,5 @@ applications only need to put the required components together to provide all the required features. // TODO: link to a place where we discuss the handlers in more details. -[[eg-server-connector]] -=== Server Connectors - -A `Connector` is the component that handles incoming requests from clients, -and works in conjunction with `ConnectionFactory` instances. - -The primary implementation is `org.eclipse.jetty.server.ServerConnector`. -`ServerConnector` uses a `java.nio.channels.ServerSocketChannel` to listen -to a TCP port and to accept TCP connections. - -Since `ServerConnector` wraps a `ServerSocketChannel`, it can be configured -in a similar way, for example the port to listen to, the network address -to bind to, etc.: - -[source,java,indent=0] ----- -include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=configureConnector] ----- - -The _acceptors_ are threads that compete to accept TCP connections on the -listening port, typically only one. -When a connection is accepted, `ServerConnector` wraps it and passes it to -the xref:eg-io-arch-selector-manager[`SelectorManager`]. -Therefore there is a little moment where the acceptor thread is not accepting -new connections because it is busy wrapping the just accepted one to pass it -to the `SelectorManager`. -Connections that are ready to be accepted but are not accepted yet are queued -in a bounded queue (at the OS level) whose capacity can be configured with the -`ServerConnector.acceptQueueSize` parameter. - -If your application must withstand a very high rate of connections opened, -configuring more than one acceptor thread may be beneficial: when one acceptor -thread accepts one connection, another acceptor thread can take over accepting -connections. - -The _selectors_ are components that manage a set of connected sockets, -implemented by xref:eg-io-arch-selector-manager[`ManagedSelector`]. -Each selector requires one thread and uses the Java NIO mechanism to -efficiently handle the set of connected sockets. -As a rule of thumb, a single selector can easily manage 1000-5000 sockets, -although the number may vary greatly depending on the application. - -It is possible to configure more than one `ServerConnector`, each listening -on different ports: - -[source,java,indent=0] ----- -include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=configureConnectors] ----- - -[[eg-server-connector-protocol]] -==== Configuring Protocols - -For each accepted TCP connection, `ServerConnector` asks a `ConnectionFactory` -to create a `Connection` object that handles the network traffic on that TCP -connection, parsing and generating bytes for a specific protocol (see -xref:eg-io-arch[this section] for more details about `Connection` objects). - -A `ServerConnector` can be configured with one or more ``ConnectionFactory``s. -If no `ConnectionFactory` is specified then `HttpConnectionFactory` is -implicitly configured. - -[[eg-server-connector-protocol-http11]] -===== Configuring HTTP/1.1 - -`HttpConnectionFactory` creates `HttpConnection` objects that parse bytes -and generate bytes for the HTTP/1.1 protocol. - -This is how you configure Jetty to support clear-text HTTP/1.1: - -[source,java,indent=0] ----- -include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=http11] ----- - -Supporting encrypted HTTP/1.1 (that is, requests with the HTTPS scheme) -is supported by configuring an `SslContextFactory` that has access to the -keyStore containing the private server key and public server certificate, -in this way: - -[source,java,indent=0] ----- -include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=tlsHttp11] ----- - -[[eg-server-connector-protocol-proxy-http11]] -===== Configuring Jetty behind a Load Balancer - -It is often the case that Jetty receives connections from a load balancer -configured to distribute the load among many Jetty backend servers. - -From the Jetty point of view, all the connections arrive from the load -balancer, rather than the real clients, but is possible to forward the real -client IP address and port to the backend Jetty server using the -link:https://www.haproxy.org/download/2.1/doc/proxy-protocol.txt[PROXY protocol]. - -NOTE: The PROXY protocol is widely supported by load balancers such as -link:http://cbonte.github.io/haproxy-dconv/2.2/configuration.html#5.2-send-proxy[HAProxy] -(via its `send-proxy` directive), or -link:https://docs.nginx.com/nginx/admin-guide/load-balancer/using-proxy-protocol[Nginx] -(via its `proxy_protocol on` directive), and others. - -To support this case, Jetty can be configured in this way: - -[source,java,indent=0] ----- -include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=proxyHTTP] ----- - -Note how the ``ConnectionFactory``s passed to `ServerConnector` are in order: -first PROXY, then HTTP/1.1. -Note also how the PROXY `ConnectionFactory` needs to know its _next_ protocol -(in this example, HTTP/1.1). - -Each `ConnectionFactory` is asked to create a `Connection` object for each -accepted TCP connection; the `Connection` objects will be chained together -to handle the bytes, each for its own protocol. -Therefore the `ProxyConnection` will handle the PROXY protocol bytes and -`HttpConnection` will handle the HTTP/1.1 bytes producing a request object -and response object that will be processed by ``Handler``s. - -[[eg-server-connector-protocol-http2]] -===== Configuring HTTP/2 - -It is well know that the HTTP ports are `80` (for clear-text HTTP) and `443` -for encrypted HTTP. -By using those ports, a client had _prior knowledge_ that the server would -speak, respectively, the HTTP/1.x protocol and the TLS protocol (and, after -decryption, the HTTP/1.x protocol). - -HTTP/2 was designed to be a smooth transition from HTTP/1.1 for users and -as such the HTTP ports were not changed. -However the HTTP/2 protocol is, on the wire, a binary protocol, completely -different from HTTP/1.1. -Therefore, with HTTP/2, clients that connect to port `80` may speak either -HTTP/1.1 or HTTP/2, and the server must figure out which version of the HTTP -protocol the client is speaking. - -Jetty can support both HTTP/1.1 and HTTP/2 on the same clear-text port by -configuring both the HTTP/1.1 and the HTTP/2 ``ConnectionFactory``s: - -[source,java,indent=0] ----- -include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=http11H2C] ----- - -Note how the ``ConnectionFactory``s passed to `ServerConnector` are in order: -first HTTP/1.1, then HTTP/2. -This is necessary to support both protocols on the same port: Jetty will -start parsing the incoming bytes as HTTP/1.1, but then realize that they -are HTTP/2 bytes and will therefore _upgrade_ from HTTP/1.1 to HTTP/2. - -This configuration is also typical when Jetty is installed in backend servers -behind a load balancer that also takes care of offloading TLS. -When Jetty is behind a load balancer, you can always prepend the PROXY -protocol as described in -xref:eg-server-connector-protocol-proxy-http11[this section]. - -When using encrypted HTTP/2, the unencrypted protocol is negotiated by client -and server using an extension to the TLS protocol called ALPN. - -Jetty supports ALPN and encrypted HTTP/2 with this configuration: - -[source,java,indent=0] ----- -include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=tlsALPNHTTP] ----- - -[[eg-server-handler]] -=== Server Handlers - -A `Handler` is the component that processes incoming HTTP requests and -eventually produces HTTP responses. - -``Handler``s can be organized in different ways: - -* in a sequence, where ``Handler``s are invoked one after the other -** `HandlerCollection` invokes _all_ ``Handler``s one after the other -** `HandlerList` invokes ``Handlers``s until one calls `Request.setHandled(true)` -to indicate that the request has been handled and no further `Handler` should -be invoked. -* nested, where one `Handler` invokes the next `Handler` -** `HandlerWrapper` implements this behavior - -The `HandlerCollection` behavior (invoking _all_ handlers) is useful when -for example the last `Handler` is a logging `Handler` that logs the the -request(that may have been modified by previous handlers). - -The `HandlerList` behavior (invoking handlers up to the first that calls -`Request.setHandled(true)`) is useful when different handlers process different -URIs or different virtual hosts: invoke one after the other until one matches -the URI or virtual host. - -The nested behavior is useful to enrich the request with additional services -such as HTTP session support (`SessionHandler`), or with specific behaviors -dictated by the Servlet specification (`ServletHandler`). - -``Handler``s can be organized in a tree by composing them together: - -[plantuml] ----- -skinparam backgroundColor transparent -skinparam monochrome true -skinparam shadowing false -skinparam padding 5 - -scale 1.5 - -hide members -hide circle - -HandlerCollection -- HandlerList -HandlerCollection -- LoggingHandler -HandlerList -- App1Handler -HandlerList -- App2Handler -App2Handler -- ServletHandler ----- - -In code it looks like this: - -[source,java,indent=0] ----- -include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=tree] ----- - -// TODO: old docs introduces briefly ServletHandler but I think it deserves its own section - -// TODO: old docs introduce ContextHandler here and WebAppContext - +include::server-http-connector.adoc[] +include::server-http-handler.adoc[] diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/http2/server-http2.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/server/http2/server-http2.adoc new file mode 100644 index 00000000000..c6728c6ef0e --- /dev/null +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/server/http2/server-http2.adoc @@ -0,0 +1,22 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +[[eg-server-http2]] +=== HTTP/2 Server Libraries + +TODO diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/server.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/server/server.adoc index a6dcc1feb45..a8cc9ceffd2 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/server/server.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/server/server.adoc @@ -46,6 +46,7 @@ xref:eg-server-http2[HTTP/2 libraries] via the xref:eg-server-websocket[WebSocket libraries] include::http/server-http.adoc[] -include::http/server-http2.adoc[] -include::http/server-websocket.adoc[] +include::http2/server-http2.adoc[] +include::websocket/server-websocket.adoc[] include::server-io-arch.adoc[] +include::../old_docs/server.adoc[] diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/websocket/server-websocket.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/server/websocket/server-websocket.adoc new file mode 100644 index 00000000000..0ea9473f72b --- /dev/null +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/server/websocket/server-websocket.adoc @@ -0,0 +1,22 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +[[eg-server-websocket]] +=== WebSocket Server Libraries + +TODO diff --git a/jetty-documentation/src/main/java/embedded/server/http/HTTPServerDocs.java b/jetty-documentation/src/main/java/embedded/server/http/HTTPServerDocs.java index 1fdd72eafcd..263d94484f5 100644 --- a/jetty-documentation/src/main/java/embedded/server/http/HTTPServerDocs.java +++ b/jetty-documentation/src/main/java/embedded/server/http/HTTPServerDocs.java @@ -18,13 +18,24 @@ package embedded.server.http; +import java.io.IOException; +import java.util.EnumSet; +import javax.servlet.DispatcherType; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory; import org.eclipse.jetty.http.HttpCompliance; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.http.HttpURI; import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory; import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory; +import org.eclipse.jetty.rewrite.handler.CompactPathRule; +import org.eclipse.jetty.rewrite.handler.RedirectRegexRule; +import org.eclipse.jetty.rewrite.handler.RewriteHandler; +import org.eclipse.jetty.rewrite.handler.RewriteRegexRule; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConnectionFactory; @@ -35,12 +46,26 @@ import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.SslConnectionFactory; import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.server.handler.ContextHandlerCollection; +import org.eclipse.jetty.server.handler.DefaultHandler; import org.eclipse.jetty.server.handler.HandlerCollection; import org.eclipse.jetty.server.handler.HandlerList; import org.eclipse.jetty.server.handler.HandlerWrapper; -import org.eclipse.jetty.servlet.ServletHandler; +import org.eclipse.jetty.server.handler.ResourceHandler; +import org.eclipse.jetty.server.handler.StatisticsHandler; +import org.eclipse.jetty.server.handler.gzip.GzipHandler; +import org.eclipse.jetty.servlet.DefaultServlet; +import org.eclipse.jetty.servlet.FilterHolder; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.servlets.CrossOriginFilter; +import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.util.resource.ResourceCollection; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.QueuedThreadPool; +import org.eclipse.jetty.webapp.WebAppContext; @SuppressWarnings("unused") public class HTTPServerDocs @@ -266,7 +291,7 @@ public class HTTPServerDocs // end::tlsALPNHTTP[] } - public void tree() throws Exception + public void handlerTree() { class LoggingHandler extends AbstractHandler { @@ -292,7 +317,7 @@ public class HTTPServerDocs } } - // tag::tree[] + // tag::handlerTree[] // Create a Server instance. Server server = new Server(); @@ -305,10 +330,459 @@ public class HTTPServerDocs collection.addHandler(new LoggingHandler()); list.addHandler(new App1Handler()); - App2Handler app2Handler = new App2Handler(); - list.addHandler(app2Handler); + HandlerWrapper wrapper = new HandlerWrapper(); + list.addHandler(wrapper); - app2Handler.setHandler(new ServletHandler()); - // end::tree[] + wrapper.setHandler(new App2Handler()); + // end::handlerTree[] + } + + public void handlerAPI() + { + class MyHandler extends AbstractHandler + { + @Override + // tag::handlerAPI[] + public void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) + { + } + // end::handlerAPI[] + } + } + + public void handlerHello() throws Exception + { + // tag::handlerHello[] + class HelloWorldHandler extends AbstractHandler + { + @Override + public void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + { + // Mark the request as handled by this Handler. + jettyRequest.setHandled(true); + + response.setStatus(200); + response.setContentType("text/html; charset=UTF-8"); + + // Write a Hello World response. + response.getWriter().print("" + + "" + + "" + + "" + + " Jetty Hello World Handler" + + "" + + "" + + "

      Hello World

      " + + "" + + "" + + ""); + } + } + + Server server = new Server(); + Connector connector = new ServerConnector(server); + server.addConnector(connector); + + // Set the Hello World Handler. + server.setHandler(new HelloWorldHandler()); + + server.start(); + // end::handlerHello[] + } + + public void handlerFilter() throws Exception + { + class HelloWorldHandler extends AbstractHandler + { + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) + { + } + } + + // tag::handlerFilter[] + class FilterHandler extends HandlerWrapper + { + @Override + public void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + String path = request.getRequestURI(); + if (path.startsWith("/old_path/")) + { + // Rewrite old paths to new paths. + HttpURI uri = jettyRequest.getHttpURI(); + HttpURI newURI = new HttpURI(uri); + String newPath = "/new_path/" + path.substring("/old_path/".length()); + newURI.setPath(newPath); + // Modify the request object. + jettyRequest.setHttpURI(newURI); + } + + // This Handler is not handling the request, so + // it does not call jettyRequest.setHandled(true). + + // Forward to the next Handler. + super.handle(target, jettyRequest, request, response); + } + } + + Server server = new Server(); + Connector connector = new ServerConnector(server); + server.addConnector(connector); + + // Link the Handlers. + FilterHandler filter = new FilterHandler(); + filter.setHandler(new HelloWorldHandler()); + server.setHandler(filter); + + server.start(); + // end::handlerFilter[] + } + + public void contextHandler() throws Exception + { + // tag::contextHandler[] + class ShopHandler extends AbstractHandler + { + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) + { + baseRequest.setHandled(true); + // Implement the shop. + } + } + + Server server = new Server(); + Connector connector = new ServerConnector(server); + server.addConnector(connector); + + // Create a ContextHandler with contextPath. + ContextHandler context = new ContextHandler(); + context.setContextPath("/shop"); + context.setHandler(new ShopHandler()); + + // Link the context to the server. + server.setHandler(context); + + server.start(); + // end::contextHandler[] + } + + public void contextHandlerCollection() throws Exception + { + // tag::contextHandlerCollection[] + class ShopHandler extends AbstractHandler + { + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) + { + baseRequest.setHandled(true); + // Implement the shop. + } + } + + class RESTHandler extends AbstractHandler + { + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) + { + baseRequest.setHandled(true); + // Implement the REST APIs. + } + } + + Server server = new Server(); + Connector connector = new ServerConnector(server); + server.addConnector(connector); + + // Create a ContextHandlerCollection to hold contexts. + ContextHandlerCollection contextCollection = new ContextHandlerCollection(); + // Link the ContextHandlerCollection to the Server. + server.setHandler(contextCollection); + + // Create the context for the shop web application. + ContextHandler shopContext = new ContextHandler("/shop"); + shopContext.setHandler(new ShopHandler()); + // Add it to ContextHandlerCollection. + contextCollection.addHandler(shopContext); + + server.start(); + + // Create the context for the API web application. + ContextHandler apiContext = new ContextHandler("/api"); + apiContext.setHandler(new RESTHandler()); + // Web applications can be deployed after the Server is started. + contextCollection.deployHandler(apiContext, Callback.NOOP); + // end::contextHandlerCollection[] + } + + public void servletContextHandler() throws Exception + { + // tag::servletContextHandler[] + class ShopCartServlet extends HttpServlet + { + @Override + protected void service(HttpServletRequest request, HttpServletResponse response) + { + // Implement the shop cart functionality. + } + } + + Server server = new Server(); + Connector connector = new ServerConnector(server); + server.addConnector(connector); + + // Create a ServletContextHandler with contextPath. + ServletContextHandler context = new ServletContextHandler(); + context.setContextPath("/shop"); + + // Add the Servlet implementing the cart functionality to the context. + ServletHolder servletHolder = context.addServlet(ShopCartServlet.class, "/cart/*"); + // Configure the Servlet with init-parameters. + servletHolder.setInitParameter("maxItems", "128"); + + // Add the CrossOriginFilter to protect from CSRF attacks. + FilterHolder filterHolder = context.addFilter(CrossOriginFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST)); + // Configure the filter. + filterHolder.setAsyncSupported(true); + + // Link the context to the server. + server.setHandler(context); + + server.start(); + // end::servletContextHandler[] + } + + public void webAppContextHandler() throws Exception + { + // tag::webAppContextHandler[] + Server server = new Server(); + Connector connector = new ServerConnector(server); + server.addConnector(connector); + + // Create a WebAppContext. + WebAppContext context = new WebAppContext(); + // Configure the path of the packaged web application (file or directory). + context.setWar("/path/to/webapp.war"); + // Configure the contextPath. + context.setContextPath("/app"); + + // Link the context to the server. + server.setHandler(context); + + server.start(); + // end::webAppContextHandler[] + } + + public void resourceHandler() throws Exception + { + // tag::resourceHandler[] + Server server = new Server(); + Connector connector = new ServerConnector(server); + server.addConnector(connector); + + // Create and configure a ResourceHandler. + ResourceHandler handler = new ResourceHandler(); + // Configure the directory where static resources are located. + handler.setBaseResource(Resource.newResource("/path/to/static/resources/")); + // Configure directory listing. + handler.setDirectoriesListed(false); + // Configure welcome files. + handler.setWelcomeFiles(new String[]{"index.html"}); + // Configure whether to accept range requests. + handler.setAcceptRanges(true); + + // Link the context to the server. + server.setHandler(handler); + + server.start(); + // end::resourceHandler[] + } + + public void multipleResourcesHandler() throws Exception + { + // tag::multipleResourcesHandler[] + ResourceHandler handler = new ResourceHandler(); + + // For multiple directories, use ResourceCollection. + ResourceCollection directories = new ResourceCollection(); + directories.addPath("/path/to/static/resources/"); + directories.addPath("/another/path/to/static/resources/"); + + handler.setBaseResource(directories); + // end::multipleResourcesHandler[] + } + + public void defaultServlet() + { + // tag::defaultServlet[] + // Create a ServletContextHandler with contextPath. + ServletContextHandler context = new ServletContextHandler(); + context.setContextPath("/app"); + + // Add the DefaultServlet to serve static content. + ServletHolder servletHolder = context.addServlet(DefaultServlet.class, "/"); + // Configure the DefaultServlet with init-parameters. + servletHolder.setInitParameter("resourceBase", "/path/to/static/resources/"); + servletHolder.setAsyncSupported(true); + // end::defaultServlet[] + } + + public void serverGzipHandler() throws Exception + { + // tag::serverGzipHandler[] + Server server = new Server(); + Connector connector = new ServerConnector(server); + server.addConnector(connector); + + // Create and configure GzipHandler. + GzipHandler gzipHandler = new GzipHandler(); + // Only compress response content larger than this. + gzipHandler.setMinGzipSize(1024); + // Do not compress these URI paths. + gzipHandler.setExcludedPaths("/uncompressed"); + // Also compress POST responses. + gzipHandler.addIncludedMethods("POST"); + // Do not compress these mime types. + gzipHandler.addExcludedMimeTypes("font/ttf"); + + // Link a ContextHandlerCollection to manage contexts. + ContextHandlerCollection contexts = new ContextHandlerCollection(); + gzipHandler.setHandler(contexts); + + // Link the GzipHandler to the Server. + server.setHandler(gzipHandler); + + server.start(); + // end::serverGzipHandler[] + } + + public void contextGzipHandler() throws Exception + { + class ShopHandler extends AbstractHandler + { + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + { + baseRequest.setHandled(true); + // Implement the shop. + } + } + + class RESTHandler extends AbstractHandler + { + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) + { + baseRequest.setHandled(true); + // Implement the REST APIs. + } + } + + Server server = new Server(); + ServerConnector connector = new ServerConnector(server); + server.addConnector(connector); + + // tag::contextGzipHandler[] + // Create a ContextHandlerCollection to hold contexts. + ContextHandlerCollection contextCollection = new ContextHandlerCollection(); + // Link the ContextHandlerCollection to the Server. + server.setHandler(contextCollection); + + // Create the context for the shop web application. + ContextHandler shopContext = new ContextHandler("/shop"); + shopContext.setHandler(new ShopHandler()); + + // You want to gzip the shop web application only. + GzipHandler shopGzipHandler = new GzipHandler(); + shopGzipHandler.setHandler(shopContext); + + // Add it to ContextHandlerCollection. + contextCollection.addHandler(shopGzipHandler); + + // Create the context for the API web application. + ContextHandler apiContext = new ContextHandler("/api"); + apiContext.setHandler(new RESTHandler()); + + // Add it to ContextHandlerCollection. + contextCollection.addHandler(apiContext); + // end::contextGzipHandler[] + + server.start(); + } + + public void rewriteHandler() throws Exception + { + // tag::rewriteHandler[] + Server server = new Server(); + ServerConnector connector = new ServerConnector(server); + server.addConnector(connector); + + RewriteHandler rewriteHandler = new RewriteHandler(); + // Compacts URI paths with double slashes, e.g. /ctx//path/to//resource. + rewriteHandler.addRule(new CompactPathRule()); + // Rewrites */products/* to */p/*. + rewriteHandler.addRule(new RewriteRegexRule("/(.*)/product/(.*)", "/$1/p/$2")); + // Redirects permanently to a different URI. + RedirectRegexRule redirectRule = new RedirectRegexRule("/documentation/(.*)", "https://docs.domain.com/$1"); + redirectRule.setStatusCode(HttpStatus.MOVED_PERMANENTLY_301); + rewriteHandler.addRule(redirectRule); + + // Link the RewriteHandler to the Server. + server.setHandler(rewriteHandler); + + // Create a ContextHandlerCollection to hold contexts. + ContextHandlerCollection contextCollection = new ContextHandlerCollection(); + // Link the ContextHandlerCollection to the RewriteHandler. + rewriteHandler.setHandler(contextCollection); + + server.start(); + // end::rewriteHandler[] + } + + public void statsHandler() throws Exception + { + // tag::statsHandler[] + Server server = new Server(); + ServerConnector connector = new ServerConnector(server); + server.addConnector(connector); + + StatisticsHandler statsHandler = new StatisticsHandler(); + + // Link the StatisticsHandler to the Server. + server.setHandler(statsHandler); + + // Create a ContextHandlerCollection to hold contexts. + ContextHandlerCollection contextCollection = new ContextHandlerCollection(); + // Link the ContextHandlerCollection to the StatisticsHandler. + statsHandler.setHandler(contextCollection); + + server.start(); + // end::statsHandler[] + } + + public void defaultHandler() throws Exception + { + // tag::defaultHandler[] + Server server = new Server(); + Connector connector = new ServerConnector(server); + server.addConnector(connector); + + // Create a HandlerList. + HandlerList handlerList = new HandlerList(); + + // Add as first a ContextHandlerCollection to manage contexts. + ContextHandlerCollection contexts = new ContextHandlerCollection(); + handlerList.addHandler(contexts); + + // Add as last a DefaultHandler. + DefaultHandler defaultHandler = new DefaultHandler(); + handlerList.addHandler(defaultHandler); + + // Link the HandlerList to the Server. + server.setHandler(handlerList); + + server.start(); + // end::defaultHandler[] } } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DefaultHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DefaultHandler.java index 67c7c1b3efb..38f76792078 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DefaultHandler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DefaultHandler.java @@ -47,7 +47,7 @@ import static java.nio.charset.StandardCharsets.UTF_8; * * This handle will deal with unhandled requests in the server. * For requests for favicon.ico, the Jetty icon is served. - * For reqests to '/' a 404 with a list of known contexts is served. + * For requests to '/' a 404 with a list of known contexts is served. * For all other requests a normal 404 is served. */ public class DefaultHandler extends AbstractHandler diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/SecuredRedirectHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/SecuredRedirectHandler.java index bef74df7b94..450ced06987 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/SecuredRedirectHandler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/SecuredRedirectHandler.java @@ -47,6 +47,8 @@ public class SecuredRedirectHandler extends AbstractHandler return; } + baseRequest.setHandled(true); + HttpConfiguration httpConfig = channel.getHttpConfiguration(); if (httpConfig == null) { @@ -68,7 +70,5 @@ public class SecuredRedirectHandler extends AbstractHandler { response.sendError(HttpStatus.FORBIDDEN_403, "Not Secure"); } - - baseRequest.setHandled(true); } } From 5b1d4d492ee970ae000b0dbc670b3501ea3ff33d Mon Sep 17 00:00:00 2001 From: Chris Walker Date: Fri, 20 Mar 2020 15:39:11 -0500 Subject: [PATCH 069/101] Manual move of Weld documentation changes. --- .../old_docs/frameworks/weld.adoc | 143 ++++++++++++++---- 1 file changed, 113 insertions(+), 30 deletions(-) diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/frameworks/weld.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/frameworks/weld.adoc index dd48cc34531..57f35e0a6bb 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/frameworks/weld.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/frameworks/weld.adoc @@ -1,19 +1,19 @@ // -// ======================================================================== -// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// ======================================================================== +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. // -// This program and the accompanying materials are made available under -// the terms of the Eclipse Public License 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0 +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html // -// This Source Code may also be made available under the following -// Secondary Licenses when the conditions for such availability set -// forth in the Eclipse Public License, v. 2.0 are satisfied: -// the Apache License v2.0 which is available at -// https://www.apache.org/licenses/LICENSE-2.0 +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php // -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== // [[framework-weld]] @@ -25,30 +25,74 @@ It is easily configured with Jetty 9. [[weld-setup-distro]] ==== Weld Setup -The easiest way to configure weld is within the jetty distribution itself: +The easiest way to configure weld is within the Jetty distribution itself. +This can be accomplished either by enabling one of the startup link:#startup-modules[modules] for Weld, or by creating/editing a `jetty-web.xml` descriptor (see also https://www.eclipse.org/jetty/documentation/current/jetty-web-xml-config.html[Jetty XML Reference]). -1. Enable the startup link:#startup-modules[module] called "cdi". -2. Ensure your `WEB-INF/web.xml` contains the following: -+ -[source, xml, subs="{sub-order}"] ----- - - org.jboss.weld.environment.servlet.Listener - +===== Jetty Weld Modules - - Object factory for the CDI Bean Manager - BeanManager - javax.enterprise.inject.spi.BeanManager - ----- +====== Jetty `cdi-decorate` Module -That should be it so when you start up your jetty distribution with the webapp you should see output similar to the following (providing your logging is the default configuration): +Since Jetty 9.4.20 and Weld 3.1.2.Final, the Weld/Jetty integration uses the jetty `cdi-decorate` module. +To activate this module in Jetty the `cdi-decorate` module needs activated on the command line, which can be done as follows: + +------------------------- +cd $JETTY_BASE +java -jar $JETTY_HOME/start.jar --add-to-start=cdi-decorate +------------------------- + +====== Jetty `cdi2` Module + +For versions prior to Jetty 9.4.20 and Weld 3.1.2, the Weld/Jetty integration required some internal Jetty APIs to be made visible to the web application. +This can be done using the deprecated `cdi2` module either by activating the `cdi2` module: + +------------------------- +cd $JETTY_BASE +java -jar $JETTY_HOME/start.jar --add-to-start=cdi2 +------------------------- + + +===== Jetty-Web XML + + +[source.XML, xml] +------------------------------------------------------------- + + + + + -org.eclipse.jetty.util.Decorator + + + -org.eclipse.jetty.util.DecoratedObjectFactory + + + -org.eclipse.jetty.server.handler.ContextHandler. + + + -org.eclipse.jetty.server.handler.ContextHandler + + + -org.eclipse.jetty.servlet.ServletContextHandler + + +------------------------------------------------------------- + +//TODO Fix for 10 +____ +[TIP] +Directly modifying the web application classpath via `jetty-web.xml` will not work for Jetty 10.0.0 and later. +____ + +===== Jetty `cdi-spi` Module +Since Jetty 9.4.20 the Jetty `cdi-spi` module has been available that integrates any compliant CDI implementation by directly calling the CDI SPI. +Since Weld support specific Jetty integration, it is not recommended to use this module with Weld. + +When you start up your Jetty distribution with the webapp you should see output similar to the following (providing your logging is the default configuration): [source, screen, subs="{sub-order}"] .... 2015-06-18 12:13:54.924:INFO::main: Logging initialized @485ms -2015-06-18 12:13:55.231:INFO:oejs.Server:main: jetty-9.3.1-SNAPSHOT +2015-06-18 12:13:55.231:INFO:oejs.Server:main: jetty-{VERSION} 2015-06-18 12:13:55.264:INFO:oejdp.ScanningAppProvider:main: Deployment monitor [file:///tmp/cdi-demo/webapps/] at interval 1 2015-06-18 12:13:55.607:WARN:oeja.AnnotationConfiguration:main: ServletContainerInitializers: detected. Class hierarchy: empty Jun 18, 2015 12:13:55 PM org.jboss.weld.environment.servlet.EnhancedListener onStartup @@ -80,4 +124,43 @@ INFO: WELD-ENV-001009: org.jboss.weld.environment.servlet.Listener used for Serv .... -For use with the jetty-maven-plugin, the best idea is to make the org.jboss.weld.servlet:weld-servlet artifact a _plugin_ dependency (__not__ a webapp dependency), then follow step 2 above. +For use with the jetty-maven-plugin, the best idea is to make the org.jboss.weld.servlet:weld-servlet artifact a _plugin_ dependency (__not__ a webapp dependency). + +[[weld-embedded]] +==== Embedded Jetty + +When starting embedded Jetty programmatically from the `main` method it is necessary to register Weld's listener: + +[source.JAVA, java] +---- +public class Main { + + public static void main(String[] args) throws Exception { + Server jetty = new Server(8080); + WebAppContext context = new WebAppContext(); + context.setContextPath("/"); + context.setResourceBase("src/main/resources"); + jetty.setHandler(context); + context.addServlet(HelloWorldServlet.class, "/*"); + + context.addEventListener(new DecoratingListener()); # <1> + context.addEventListener(new Listener()); # <2> + + jetty.start(); + jetty.join(); + } + + public static class HelloWorldServlet extends HttpServlet { + + @Inject BeanManager manager; + + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + resp.setContentType("text/plain"); + resp.getWriter().append("Hello from " + manager); + } + } +} + +<1> Jetty's `org.eclipse.jetty.webapp.DecoratingListener` registered programmatically (since Jetty-9.4.20) +<2> Weld's `org.jboss.weld.environment.servlet.Listener` registered programmatically +---- From 34a2d6e673fc1d72606a3bc45cf783f17c767c80 Mon Sep 17 00:00:00 2001 From: olivier lamy Date: Sat, 11 Apr 2020 10:41:47 +1000 Subject: [PATCH 070/101] fix header Signed-off-by: olivier lamy --- .../old_docs/frameworks/weld.adoc | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/frameworks/weld.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/frameworks/weld.adoc index 57f35e0a6bb..cfd3e7fccd6 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/frameworks/weld.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/frameworks/weld.adoc @@ -1,19 +1,19 @@ // -// ======================================================================== -// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. -// ======================================================================== -// All rights reserved. This program and the accompanying materials -// are made available under the terms of the Eclipse Public License v1.0 -// and Apache License v2.0 which accompanies this distribution. +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. // -// The Eclipse Public License is available at -// http://www.eclipse.org/legal/epl-v10.html +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 // -// The Apache License v2.0 is available at -// http://www.opensource.org/licenses/apache2.0.php +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 // -// You may elect to redistribute this code under either of these licenses. -// ======================================================================== +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== // [[framework-weld]] From 14fda86944ca5bce9d5f090e22dd1e3ab67d8b68 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Mon, 13 Apr 2020 12:30:46 +0200 Subject: [PATCH 071/101] Improvements to the Jetty server documentation. Written HTTP/2 low-level section. Signed-off-by: Simone Bordet --- .../client/http2/client-http2.adoc | 118 +------ .../main/asciidoc/embedded-guide/http2.adoc | 118 +++++++ .../server/http2/server-http2.adoc | 170 +++++++++- .../src/main/java/embedded/HTTP2Docs.java | 97 ++++++ .../client/http2/HTTP2ClientDocs.java | 60 +--- .../server/http2/HTTP2ServerDocs.java | 316 ++++++++++++++++++ .../org/eclipse/jetty/http2/api/Stream.java | 33 +- .../RawHTTP2ServerConnectionFactory.java | 5 + 8 files changed, 753 insertions(+), 164 deletions(-) create mode 100644 jetty-documentation/src/main/asciidoc/embedded-guide/http2.adoc create mode 100644 jetty-documentation/src/main/java/embedded/HTTP2Docs.java create mode 100644 jetty-documentation/src/main/java/embedded/server/http2/HTTP2ServerDocs.java diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http2/client-http2.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http2/client-http2.adoc index e7c48438eaa..8cdc48187e6 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http2/client-http2.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http2/client-http2.adoc @@ -33,6 +33,8 @@ The HTTP/2 client library has been designed for those applications that need low-level access to HTTP/2 features such as _sessions_, _streams_ and _frames_, and this is quite a rare use case. +See also the correspondent xref:eg-server-http2[HTTP/2 server library]. + [[eg-client-http2-intro]] ==== Introducing HTTP2Client @@ -63,79 +65,24 @@ it should stop the `HTTP2Client` instance (or instances) that were started: include::../../{doc_code}/embedded/client/http2/HTTP2ClientDocs.java[tags=stop] ---- -`HTTP2Client` allows client applications to connect to a HTTP/2 server. -A _session_ represents a single TCP connection to a HTTP/2 server and is defined -by class `org.eclipse.jetty.http2.api.Session`. +`HTTP2Client` allows client applications to connect to an HTTP/2 server. +A _session_ represents a single TCP connection to an HTTP/2 server and is +defined by class `org.eclipse.jetty.http2.api.Session`. A _session_ typically has a long life - once the TCP connection is established, it remains open until it is not used anymore (and therefore it is closed by the idle timeout mechanism), until a fatal error occurs (for example, a network failure), or if one of the peers decides unilaterally to close the TCP connection. -HTTP/2 is a multiplexed protocol: it allows multiple HTTP/2 requests to be sent -on the same TCP connection. -Each request/response cycle is represented by a _stream_. -Therefore, a single _session_ manages multiple concurrent _streams_. -A _stream_ has typically a very short life compared to the _session_: a -_stream_ only exists for the duration of the request/response cycle and then -disappears. +include::../../http2.adoc[tag=multiplex] [[eg-client-http2-flow-control]] ===== HTTP/2 Flow Control -The HTTP/2 protocol is _flow controlled_ (see -link:https://tools.ietf.org/html/rfc7540#section-5.2[the specification]). -This means that a sender and a receiver maintain a _flow control window_ that -tracks the number of data bytes sent and received, respectively. -When a sender sends data bytes, it reduces its flow control window. When a -receiver receives data bytes, it also reduces its flow control window, and -then passes the received data bytes to the application. -The application consumes the data bytes and tells back the receiver that it -has consumed the data bytes. -The receiver then enlarges the flow control window, and arranges to send a -message to the sender with the number of bytes consumed, so that the sender -can enlarge its flow control window. - -A sender can send data bytes up to its whole flow control window, then it must -stop sending until it receives a message from the receiver that the data bytes -have been consumed, which enlarges the flow control window, which allows the -sender to send more data bytes. - -HTTP/2 defines _two_ flow control windows: one for each _session_, and one -for each _stream_. Let's see with an example how they interact, assuming that -in this example the session flow control window is 120 bytes and the stream -flow control window is 100 bytes. - -The sender opens a session, and then opens `stream_1` on that session, and -sends `80` data bytes. -At this point the session flow control window is `40` bytes (`120 - 80`), and -``stream_1``'s flow control window is `20` bytes (`100 - 80`). -The sender now opens `stream_2` on the same session and sends `40` data bytes. -At this point, the session flow control window is `0` bytes (`40 - 40`), -while ``stream_2``'s flow control window is `60` (`100 - 40`). -Since now the session flow control window is `0`, the sender cannot send more -data bytes, neither on `stream_1` nor on `stream_2` despite both have their -stream flow control windows greater than `0`. - -The receiver consumes ``stream_2``'s `40` data bytes and sends a message to -the sender with this information. -At this point, the session flow control window is `40` (`0 + 40`), -``stream_1``'s flow control window is still `20` and ``stream_2``'s flow -control window is `100` (`60 + 40`). -If the sender opens `stream_3` and would like to send 50 data bytes, it would -only be able to send `40` because that is the maximum allowed by the session -flow control window at this point. - -It is therefore very important that applications notify the fact that they -have consumed data bytes as soon as possible, so that the implementation -(the receiver) can send a message to the sender (in the form of a -`WINDOW_UPDATE` frame) with the information to enlarge the flow control -window, therefore reducing the possibility that sender stalls due to the flow -control windows being reduced to `0`. -This is discussed in details in xref:eg-client-http2-response[this section]. - - +include::../../http2.adoc[tag=flowControl] +How a client application should handle HTTP/2 flow control is discussed in +details in xref:eg-client-http2-response[this section]. [[eg-client-http2-connect]] ==== Connecting to the Server @@ -192,7 +139,7 @@ Sending an HTTP request to the server, and receiving a response, creates a _stream_ that encapsulates the exchange of HTTP/2 frames that compose the request and the response. -In order to send a HTTP request to the server, the client must send a +In order to send an HTTP request to the server, the client must send a `HEADERS` frame. `HEADERS` frames carry the request method, the request URI and the request headers. @@ -230,9 +177,9 @@ Use the `Callback` APIs or `CompletableFuture` APIs to ensure that the second Response events are delivered to the `Stream.Listener` passed to `Session.newStream(...)`. -A HTTP response is typically composed of a `HEADERS` frame containing the HTTP -status code and the response headers, and optionally one or more `DATA` frames -containing the response content bytes. +An HTTP response is typically composed of a `HEADERS` frame containing the +HTTP status code and the response headers, and optionally one or more `DATA` +frames containing the response content bytes. The HTTP/2 protocol also supports response trailers (that is, headers that are sent after the response content) that also are sent using a `HEADERS` frame. @@ -245,38 +192,7 @@ by implementing the relevant methods in `Stream.Listener`: include::../../{doc_code}/embedded/client/http2/HTTP2ClientDocs.java[tags=responseListener] ---- -NOTE: Returning from the `onData(...)` method implicitly demands for -more `DATA` frames (unless the one just delivered was the last). -Additional `DATA` frames may be delivered immediately if they are available -or later, asynchronously, when they arrive. - -Client applications that consume the content buffer within `onData(...)` -(for example, writing it to a file, or copying the bytes to another storage) -should succeed the callback as soon as they have consumed the content buffer. -This allows the implementation to reuse the buffer, reducing the memory -requirements needed to handle the response content. - -Alternatively, a client application may store away _both_ the buffer and the -callback to consume the buffer bytes later. - -IMPORTANT: Completing the `Callback` is very important not only to allow the -implementation to reuse the buffer, but also tells the implementation to -enlarge the stream and session flow control windows so that the server will -be able to send more `DATA` frames without stalling. - -Client applications can also precisely control _when_ to demand more `DATA` -frames, by implementing the `onDataDemanded(...)` method instead of -`onData(...)`: - -[source,java,indent=0] ----- -include::../../{doc_code}/embedded/client/http2/HTTP2ClientDocs.java[tags=responseDataDemanded] ----- - -IMPORTANT: Applications that implement `onDataDemanded(...)` must remember -to call `Stream.demand(...)`. If they don't, the implementation will not -deliver `DATA` frames and the application will stall threadlessly until an -idle timeout fires to close the stream or the session. +include::../../http2.adoc[tag=apiFlowControl] [[eg-client-http2-reset]] ==== Resetting a Request or Response @@ -298,9 +214,9 @@ include::../../{doc_code}/embedded/client/http2/HTTP2ClientDocs.java[tags=reset] HTTP/2 servers have the ability to push resources related to a primary resource. -When a HTTP/2 server pushes a resource, it send to the client a `PUSH_PROMISE` -frame that contains the request URI and headers that a client would use to -request explicitly that resource. +When an HTTP/2 server pushes a resource, it sends to the client a +`PUSH_PROMISE` frame that contains the request URI and headers that a client +would use to request explicitly that resource. Client applications can be configured to tell the server to never push resources, see xref:eg-client-http2-configure[this section]. diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/http2.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/http2.adoc new file mode 100644 index 00000000000..b77a1768235 --- /dev/null +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/http2.adoc @@ -0,0 +1,118 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +// Snippets of HTTP/2 documentation that are common between client and server. + +tag::multiplex[] +HTTP/2 is a multiplexed protocol: it allows multiple HTTP/2 requests to be sent +on the same TCP connection. +Each request/response cycle is represented by a _stream_. +Therefore, a single _session_ manages multiple concurrent _streams_. +A _stream_ has typically a very short life compared to the _session_: a +_stream_ only exists for the duration of the request/response cycle and then +disappears. +end::multiplex[] + +tag::flowControl[] +The HTTP/2 protocol is _flow controlled_ (see +link:https://tools.ietf.org/html/rfc7540#section-5.2[the specification]). +This means that a sender and a receiver maintain a _flow control window_ that +tracks the number of data bytes sent and received, respectively. +When a sender sends data bytes, it reduces its flow control window. When a +receiver receives data bytes, it also reduces its flow control window, and +then passes the received data bytes to the application. +The application consumes the data bytes and tells back the receiver that it +has consumed the data bytes. +The receiver then enlarges the flow control window, and arranges to send a +message to the sender with the number of bytes consumed, so that the sender +can enlarge its flow control window. + +A sender can send data bytes up to its whole flow control window, then it must +stop sending until it receives a message from the receiver that the data bytes +have been consumed, which enlarges the flow control window, which allows the +sender to send more data bytes. + +HTTP/2 defines _two_ flow control windows: one for each _session_, and one +for each _stream_. Let's see with an example how they interact, assuming that +in this example the session flow control window is 120 bytes and the stream +flow control window is 100 bytes. + +The sender opens a session, and then opens `stream_1` on that session, and +sends `80` data bytes. +At this point the session flow control window is `40` bytes (`120 - 80`), and +``stream_1``'s flow control window is `20` bytes (`100 - 80`). +The sender now opens `stream_2` on the same session and sends `40` data bytes. +At this point, the session flow control window is `0` bytes (`40 - 40`), +while ``stream_2``'s flow control window is `60` (`100 - 40`). +Since now the session flow control window is `0`, the sender cannot send more +data bytes, neither on `stream_1` nor on `stream_2` despite both have their +stream flow control windows greater than `0`. + +The receiver consumes ``stream_2``'s `40` data bytes and sends a message to +the sender with this information. +At this point, the session flow control window is `40` (`0 + 40`), +``stream_1``'s flow control window is still `20` and ``stream_2``'s flow +control window is `100` (`60 + 40`). +If the sender opens `stream_3` and would like to send 50 data bytes, it would +only be able to send `40` because that is the maximum allowed by the session +flow control window at this point. + +It is therefore very important that applications notify the fact that they +have consumed data bytes as soon as possible, so that the implementation +(the receiver) can send a message to the sender (in the form of a +`WINDOW_UPDATE` frame) with the information to enlarge the flow control +window, therefore reducing the possibility that sender stalls due to the flow +control windows being reduced to `0`. +end::flowControl[] + +tag::apiFlowControl[] +NOTE: Returning from the `onData(...)` method implicitly demands for +more `DATA` frames (unless the one just delivered was the last). +Additional `DATA` frames may be delivered immediately if they are available +or later, asynchronously, when they arrive. + +Applications that consume the content buffer within `onData(...)` +(for example, writing it to a file, or copying the bytes to another storage) +should succeed the callback as soon as they have consumed the content buffer. +This allows the implementation to reuse the buffer, reducing the memory +requirements needed to handle the content buffers. + +Alternatively, a client application may store away _both_ the buffer and the +callback to consume the buffer bytes later, or pass _both_ the buffer and +the callback to another asynchronous API (this is typical in proxy +applications). + +IMPORTANT: Completing the `Callback` is very important not only to allow the +implementation to reuse the buffer, but also tells the implementation to +enlarge the stream and session flow control windows so that the sender will +be able to send more `DATA` frames without stalling. + +Applications can also precisely control _when_ to demand more `DATA` +frames, by implementing the `onDataDemanded(...)` method instead of +`onData(...)`: + +[source,java,indent=0] +---- +include::{doc_code}/embedded/HTTP2Docs.java[tags=dataDemanded] +---- + +IMPORTANT: Applications that implement `onDataDemanded(...)` must remember +to call `Stream.demand(...)`. If they don't, the implementation will not +deliver `DATA` frames and the application will stall threadlessly until an +idle timeout fires to close the stream or the session. +end::apiFlowControl[] diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/http2/server-http2.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/server/http2/server-http2.adoc index c6728c6ef0e..470ac968793 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/server/http2/server-http2.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/server/http2/server-http2.adoc @@ -17,6 +17,172 @@ // [[eg-server-http2]] -=== HTTP/2 Server Libraries +=== HTTP/2 Server Library -TODO +In the vast majority of cases, server applications should use the generic, +high-level, xref:eg-server-http[HTTP server library] that also provides +HTTP/2 support via the HTTP/2 ``ConnectionFactory``s as described in details +xref:eg-server-http-connector-protocol-http2[here]. + +The low-level HTTP/2 server library has been designed for those applications +that need low-level access to HTTP/2 features such as _sessions_, _streams_ +and _frames_, and this is quite a rare use case. + +See also the correspondent xref:eg-client-http2[HTTP/2 client library]. + +[[eg-server-http2-intro]] +==== Introduction + +The Maven artifact coordinates for the HTTP/2 client library are the following: + +[source,xml,subs=normal] +---- + + org.eclipse.jetty.http2 + http2-server + {version} + +---- + +include::../../http2.adoc[tag=multiplex] + +[[eg-server-http2-flow-control]] +===== HTTP/2 Flow Control + +include::../../http2.adoc[tag=flowControl] + +How a server application should handle HTTP/2 flow control is discussed in +details in xref:eg-server-http2-request[this section]. + +[[eg-server-http2-setup]] +==== Server Setup + +The low-level HTTP/2 support is provided by +`org.eclipse.jetty.http2.server.RawHTTP2ServerConnectionFactory` and +`org.eclipse.jetty.http2.api.server.ServerSessionListener`: + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/server/http2/HTTP2ServerDocs.java[tags=setup] +---- + +Where server applications using the +xref:eg-server-http[high-level server library] deal with HTTP +requests and responses in ``Handler``s, server applications using the +low-level HTTP/2 server library deal directly with HTTP/2 __session__s, +__stream__s and __frame__s in a `ServerSessionListener` implementation. + +The `ServerSessionListener` interface defines a number of methods that are +invoked by the implementation upon the occurrence of HTTP/2 events, and that +server applications can override to react to those events. + +Please refer to the `ServerSessionListener` +link:{JDURL}/org/eclipse/jetty/http2/api/server/ServerSessionListener.html[javadocs] +for the complete list of events. + +The first event is the _accept_ event and happens when a client opens a new +TCP connection to the server and the server accepts the connection. +This is the first occasion where server applications have access to the HTTP/2 +`Session` object: + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/server/http2/HTTP2ServerDocs.java[tags=accept] +---- + +After connecting to the server, a compliant HTTP/2 client must send the +link:https://tools.ietf.org/html/rfc7540#section-3.5[HTTP/2 client preface], +and when the server receives it, it generates the _preface_ event on the server. +This is where server applications can customize the connection settings by +returning a map of settings that the implementation will send to the client: + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/server/http2/HTTP2ServerDocs.java[tags=preface] +---- + +[[eg-server-http2-request]] +==== Receiving a Request + +Receiving an HTTP request from the client, and sending a response, creates +a _stream_ that encapsulates the exchange of HTTP/2 frames that compose the +request and the response. + +An HTTP request is made of a `HEADERS` frame, that carries the request method, +the request URI and the request headers, and optional `DATA` frames that carry +the request content. + +Receiving the `HEADERS` frame opens the `Stream`: + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/server/http2/HTTP2ServerDocs.java[tags=request] +---- + +Server applications should return a `Stream.Listener` implementation from +`onNewStream(...)` to be notified of events generated by the client, such as +`DATA` frames carrying request content, or a `RST_STREAM` frame indicating +that the client wants to _reset_ the request, or an idle timeout event +indicating that the client was supposed to send more frames but it did not. + +The example below shows how to receive request content: + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/server/http2/HTTP2ServerDocs.java[tags=requestContent] +---- + +include::../../http2.adoc[tag=apiFlowControl] + +[[eg-server-http2-response]] +==== Sending a Response + +After receiving an HTTP request, a server application must send an HTTP +response. + +An HTTP response is typically composed of a `HEADERS` frame containing the +HTTP status code and the response headers, and optionally one or more `DATA` +frames containing the response content bytes. + +The HTTP/2 protocol also supports response trailers (that is, headers that +are sent after the response content) that also are sent using a `HEADERS` +frame. + +A server application can send a response in this way: + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/server/http2/HTTP2ServerDocs.java[tags=response;!exclude] +---- + +[[eg-server-http2-reset]] +==== Resetting a Request + +A server application may decide that it does not want to accept the request. +For example, it may throttle the client because it sent too many requests in +a time window, or the request is invalid (and does not deserve a proper HTTP +response), etc. + +A request can be reset in this way: + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/server/http2/HTTP2ServerDocs.java[tags=reset;!exclude] +---- + +[[eg-server-http2-push]] +==== HTTP/2 Push of Resources + +A server application may _push_ secondary resources related to a primary +resource. + +A client may inform the server that it does not accept pushed resources +(see link:https://tools.ietf.org/html/rfc7540#section-8.2[this section] +of the specification) via a `SETTINGS` frame. +Server applications must track `SETTINGS` frames and verify whether the +client supports HTTP/2 push, and only push if the client supports it: + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/server/http2/HTTP2ServerDocs.java[tags=push] +---- diff --git a/jetty-documentation/src/main/java/embedded/HTTP2Docs.java b/jetty-documentation/src/main/java/embedded/HTTP2Docs.java new file mode 100644 index 00000000000..cd32db6cd05 --- /dev/null +++ b/jetty-documentation/src/main/java/embedded/HTTP2Docs.java @@ -0,0 +1,97 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package embedded; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.util.Queue; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentLinkedQueue; + +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpURI; +import org.eclipse.jetty.http.HttpVersion; +import org.eclipse.jetty.http.MetaData; +import org.eclipse.jetty.http2.api.Session; +import org.eclipse.jetty.http2.api.Stream; +import org.eclipse.jetty.http2.client.HTTP2Client; +import org.eclipse.jetty.http2.frames.DataFrame; +import org.eclipse.jetty.http2.frames.HeadersFrame; +import org.eclipse.jetty.util.Callback; + +@SuppressWarnings("unused") +public class HTTP2Docs +{ + public void dataDemanded() throws Exception + { + HTTP2Client http2Client = new HTTP2Client(); + http2Client.start(); + SocketAddress serverAddress = new InetSocketAddress("localhost", 8080); + CompletableFuture sessionCF = http2Client.connect(serverAddress, new Session.Listener.Adapter()); + Session session = sessionCF.get(); + + HttpFields requestHeaders = new HttpFields(); + requestHeaders.put(HttpHeader.USER_AGENT, "Jetty HTTP2Client {version}"); + MetaData.Request request = new MetaData.Request("GET", new HttpURI("http://localhost:8080/path"), HttpVersion.HTTP_2, requestHeaders); + HeadersFrame headersFrame = new HeadersFrame(request, null, true); + + // tag::dataDemanded[] + class Chunk + { + private final ByteBuffer buffer; + private final Callback callback; + + Chunk(ByteBuffer buffer, Callback callback) + { + this.buffer = buffer; + this.callback = callback; + } + } + + // A queue that consumers poll to consume content asynchronously. + Queue dataQueue = new ConcurrentLinkedQueue<>(); + + // Implementation of Stream.Listener.onDataDemanded(...) + // in case of asynchronous content consumption and demand. + Stream.Listener listener = new Stream.Listener.Adapter() + { + @Override + public void onDataDemanded(Stream stream, DataFrame frame, Callback callback) + { + // Get the content buffer. + ByteBuffer buffer = frame.getData(); + + // Store buffer to consume it asynchronously, and wrap the callback. + dataQueue.offer(new Chunk(buffer, Callback.from(() -> + { + // When the buffer has been consumed, then: + // A) succeed the nested callback. + callback.succeeded(); + // B) demand more DATA frames. + stream.demand(1); + }, callback::failed))); + + // Do not demand more content here, to avoid to overflow the queue. + } + }; + // end::dataDemanded[] + } +} diff --git a/jetty-documentation/src/main/java/embedded/client/http2/HTTP2ClientDocs.java b/jetty-documentation/src/main/java/embedded/client/http2/HTTP2ClientDocs.java index c5abbf7eacb..f15637ad8e5 100644 --- a/jetty-documentation/src/main/java/embedded/client/http2/HTTP2ClientDocs.java +++ b/jetty-documentation/src/main/java/embedded/client/http2/HTTP2ClientDocs.java @@ -24,9 +24,7 @@ import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; -import java.util.Queue; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentLinkedQueue; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; @@ -199,11 +197,11 @@ public class HTTP2ClientDocs // Send the first DATA frame on the stream, with endStream=false // to signal that there are more frames in this stream. - CompletableFuture dataCF1 = stream.data(new DataFrame(stream.getId(), buffer1, false)); + CompletableFuture dataCF1 = stream.data(new DataFrame(stream.getId(), buffer1, false)); // Only when the first chunk has been sent we can send the second, // with endStream=true to signal that there are no more frames. - dataCF1.thenCompose(ignored -> stream.data(new DataFrame(stream.getId(), buffer2, true))); + dataCF1.thenCompose(s -> s.data(new DataFrame(s.getId(), buffer2, true))); // end::newStreamWithData[] } @@ -260,60 +258,6 @@ public class HTTP2ClientDocs // end::responseListener[] } - public void responseDataDemanded() throws Exception - { - HTTP2Client http2Client = new HTTP2Client(); - http2Client.start(); - SocketAddress serverAddress = new InetSocketAddress("localhost", 8080); - CompletableFuture sessionCF = http2Client.connect(serverAddress, new Session.Listener.Adapter()); - Session session = sessionCF.get(); - - HttpFields requestHeaders = new HttpFields(); - requestHeaders.put(HttpHeader.USER_AGENT, "Jetty HTTP2Client {version}"); - MetaData.Request request = new MetaData.Request("GET", new HttpURI("http://localhost:8080/path"), HttpVersion.HTTP_2, requestHeaders); - HeadersFrame headersFrame = new HeadersFrame(request, null, true); - - // tag::responseDataDemanded[] - class Chunk - { - private final ByteBuffer buffer; - private final Callback callback; - - Chunk(ByteBuffer buffer, Callback callback) - { - this.buffer = buffer; - this.callback = callback; - } - } - - // A queue that consumers poll to consume content asynchronously. - Queue dataQueue = new ConcurrentLinkedQueue<>(); - - // Open a Stream by sending the HEADERS frame. - session.newStream(headersFrame, new Stream.Listener.Adapter() - { - @Override - public void onDataDemanded(Stream stream, DataFrame frame, Callback callback) - { - // Get the content buffer. - ByteBuffer buffer = frame.getData(); - - // Store buffer to consume it asynchronously, and wrap the callback. - dataQueue.offer(new Chunk(buffer, Callback.from(() -> - { - // When the buffer has been consumed, then: - // A) succeed the nested callback. - callback.succeeded(); - // B) demand more DATA frames. - stream.demand(1); - }, callback::failed))); - - // Do not demand more content here, to avoid to overflow the queue. - } - }); - // end::responseDataDemanded[] - } - public void reset() throws Exception { HTTP2Client http2Client = new HTTP2Client(); diff --git a/jetty-documentation/src/main/java/embedded/server/http2/HTTP2ServerDocs.java b/jetty-documentation/src/main/java/embedded/server/http2/HTTP2ServerDocs.java new file mode 100644 index 00000000000..5b6658f76a3 --- /dev/null +++ b/jetty-documentation/src/main/java/embedded/server/http2/HTTP2ServerDocs.java @@ -0,0 +1,316 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package embedded.server.http2; + +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.http.HttpURI; +import org.eclipse.jetty.http.HttpVersion; +import org.eclipse.jetty.http.MetaData; +import org.eclipse.jetty.http2.ErrorCode; +import org.eclipse.jetty.http2.api.Session; +import org.eclipse.jetty.http2.api.Stream; +import org.eclipse.jetty.http2.api.server.ServerSessionListener; +import org.eclipse.jetty.http2.frames.DataFrame; +import org.eclipse.jetty.http2.frames.HeadersFrame; +import org.eclipse.jetty.http2.frames.PushPromiseFrame; +import org.eclipse.jetty.http2.frames.ResetFrame; +import org.eclipse.jetty.http2.frames.SettingsFrame; +import org.eclipse.jetty.http2.server.RawHTTP2ServerConnectionFactory; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.resource.Resource; + +import static java.lang.System.Logger.Level.INFO; + +@SuppressWarnings("unused") +public class HTTP2ServerDocs +{ + public void setup() throws Exception + { + // tag::setup[] + // Create a Server instance. + Server server = new Server(); + + ServerSessionListener sessionListener = new ServerSessionListener.Adapter(); + + // Create a ServerConnector with RawHTTP2ServerConnectionFactory. + RawHTTP2ServerConnectionFactory http2 = new RawHTTP2ServerConnectionFactory(sessionListener); + + // Configure RawHTTP2ServerConnectionFactory, for example: + + // Configure the max number of concurrent requests. + http2.setMaxConcurrentStreams(128); + // Enable support for CONNECT. + http2.setConnectProtocolEnabled(true); + + // Create the ServerConnector. + ServerConnector connector = new ServerConnector(server, http2); + + // Add the Connector to the Server + server.addConnector(connector); + + // Start the Server so it starts accepting connections from clients. + server.start(); + // end::setup[] + } + + public void accept() + { + // tag::accept[] + ServerSessionListener sessionListener = new ServerSessionListener.Adapter() + { + @Override + public void onAccept(Session session) + { + InetSocketAddress remoteAddress = session.getRemoteAddress(); + System.getLogger("http2").log(INFO, "Connection from {0}", remoteAddress); + } + }; + // end::accept[] + } + + public void preface() + { + // tag::preface[] + ServerSessionListener sessionListener = new ServerSessionListener.Adapter() + { + @Override + public Map onPreface(Session session) + { + // Customize the settings, for example: + Map settings = new HashMap<>(); + + // Tell the client that HTTP/2 push is disabled. + settings.put(SettingsFrame.ENABLE_PUSH, 0); + + return settings; + } + }; + // end::preface[] + } + + public void request() + { + // tag::request[] + ServerSessionListener sessionListener = new ServerSessionListener.Adapter() + { + @Override + public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) + { + // This is the "new stream" event, so it's guaranteed to be a request. + MetaData.Request request = (MetaData.Request)frame.getMetaData(); + + // Return a Stream.Listener to handle the request events, + // for example request content events or a request reset. + return new Stream.Listener.Adapter(); + } + }; + // end::request[] + } + + public void requestContent() + { + // tag::requestContent[] + ServerSessionListener sessionListener = new ServerSessionListener.Adapter() + { + @Override + public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) + { + MetaData.Request request = (MetaData.Request)frame.getMetaData(); + // Return a Stream.Listener to handle the request events. + return new Stream.Listener.Adapter() + { + @Override + public void onData(Stream stream, DataFrame frame, Callback callback) + { + // Get the content buffer. + ByteBuffer buffer = frame.getData(); + + // Consume the buffer, here - as an example - just log it. + System.getLogger("http2").log(INFO, "Consuming buffer {0}", buffer); + + // Tell the implementation that the buffer has been consumed. + callback.succeeded(); + + // By returning from the method, implicitly tell the implementation + // to deliver to this method more DATA frames when they are available. + } + }; + } + }; + // end::requestContent[] + } + + public void response() + { + // tag::response[] + ServerSessionListener sessionListener = new ServerSessionListener.Adapter() + { + @Override + public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) + { + // Send a response after reading the request. + MetaData.Request request = (MetaData.Request)frame.getMetaData(); + if (frame.isEndStream()) + { + respond(stream, request); + return null; + } + else + { + return new Stream.Listener.Adapter() + { + @Override + public void onData(Stream stream, DataFrame frame, Callback callback) + { + // Consume the request content. + callback.succeeded(); + if (frame.isEndStream()) + respond(stream, request); + } + }; + } + } + + private void respond(Stream stream, MetaData.Request request) + { + // Prepare the response HEADERS frame. + + // The response HTTP status and HTTP headers. + MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, new HttpFields()); + + if (HttpMethod.GET.is(request.getMethod())) + { + // The response content. + ByteBuffer resourceBytes = getResourceBytes(request); + + // Send the HEADERS frame with the response status and headers, + // and a DATA frame with the response content bytes. + stream.headers(new HeadersFrame(stream.getId(), response, null, false)) + .thenCompose(s -> s.data(new DataFrame(s.getId(), resourceBytes, true))); + } + else + { + // Send just the HEADERS frame with the response status and headers. + stream.headers(new HeadersFrame(stream.getId(), response, null, true)); + } + } + // tag::exclude[] + + private ByteBuffer getResourceBytes(MetaData.Request request) + { + return ByteBuffer.allocate(1024); + } + // end::exclude[] + }; + // end::response[] + } + + public void reset() + { + float maxRequestRate = 0F; + // tag::reset[] + ServerSessionListener sessionListener = new ServerSessionListener.Adapter() + { + @Override + public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) + { + float requestRate = calculateRequestRate(); + + if (requestRate > maxRequestRate) + { + stream.reset(new ResetFrame(stream.getId(), ErrorCode.REFUSED_STREAM_ERROR.code), Callback.NOOP); + return null; + } + else + { + // The request is accepted. + MetaData.Request request = (MetaData.Request)frame.getMetaData(); + // Return a Stream.Listener to handle the request events. + return new Stream.Listener.Adapter(); + } + } + // tag::exclude[] + + private float calculateRequestRate() + { + return 0F; + } + // end::exclude[] + }; + // end::reset[] + } + + public void push() throws Exception + { + // tag::push[] + // The favicon bytes. + ByteBuffer faviconBuffer = BufferUtil.toBuffer(Resource.newResource("/path/to/favicon.ico"), true); + + ServerSessionListener sessionListener = new ServerSessionListener.Adapter() + { + // By default, push is enabled. + private boolean pushEnabled = true; + + @Override + public void onSettings(Session session, SettingsFrame frame) + { + // Check whether the client sent an ENABLE_PUSH setting. + Map settings = frame.getSettings(); + Integer enablePush = settings.get(SettingsFrame.ENABLE_PUSH); + if (enablePush != null) + pushEnabled = enablePush == 1; + } + + @Override + public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) + { + MetaData.Request request = (MetaData.Request)frame.getMetaData(); + if (pushEnabled && request.getURIString().endsWith("/index.html")) + { + // Push the favicon. + HttpURI pushedURI = new HttpURI(request.getURI()); + pushedURI.setPath("/favicon.ico"); + MetaData.Request pushedRequest = new MetaData.Request("GET", pushedURI, HttpVersion.HTTP_2, new HttpFields()); + PushPromiseFrame promiseFrame = new PushPromiseFrame(stream.getId(), 0, pushedRequest); + stream.push(promiseFrame, new Stream.Listener.Adapter()) + .thenCompose(pushedStream -> + { + // Send the favicon "response". + MetaData.Response pushedResponse = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, new HttpFields()); + return pushedStream.headers(new HeadersFrame(pushedStream.getId(), pushedResponse, null, false)) + .thenCompose(pushed -> pushed.data(new DataFrame(pushed.getId(), faviconBuffer, true))); + }); + } + // Return a Stream.Listener to handle the request events. + return new Stream.Listener.Adapter(); + } + }; + // end::push[] + } +} diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/api/Stream.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/api/Stream.java index ae48a46159a..7cd70196c89 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/api/Stream.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/api/Stream.java @@ -52,6 +52,19 @@ public interface Stream */ public Session getSession(); + /** + *

      Sends the given HEADERS {@code frame} representing an HTTP response.

      + * + * @param frame the HEADERS frame to send + * @return the CompletableFuture that gets notified when the frame has been sent + */ + public default CompletableFuture headers(HeadersFrame frame) + { + Promise.Completable result = new Promise.Completable<>(); + headers(frame, Callback.from(() -> result.succeeded(this), result::failed)); + return result; + } + /** *

      Sends the given HEADERS {@code frame} representing an HTTP response.

      * @@ -60,6 +73,20 @@ public interface Stream */ public void headers(HeadersFrame frame, Callback callback); + /** + *

      Sends the given PUSH_PROMISE {@code frame}.

      + * + * @param frame the PUSH_PROMISE frame to send + * @param listener the listener that gets notified of stream events + * @return the CompletableFuture that gets notified of the pushed stream creation + */ + public default CompletableFuture push(PushPromiseFrame frame, Listener listener) + { + Promise.Completable result = new Promise.Completable<>(); + push(frame, result, listener); + return result; + } + /** *

      Sends the given PUSH_PROMISE {@code frame}.

      * @@ -75,10 +102,10 @@ public interface Stream * @param frame the DATA frame to send * @return the CompletableFuture that gets notified when the frame has been sent */ - public default CompletableFuture data(DataFrame frame) + public default CompletableFuture data(DataFrame frame) { - Callback.Completable result = new Callback.Completable(); - data(frame, result); + Promise.Completable result = new Promise.Completable<>(); + data(frame, Callback.from(() -> result.succeeded(this), result::failed)); return result; } diff --git a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/RawHTTP2ServerConnectionFactory.java b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/RawHTTP2ServerConnectionFactory.java index 6746c42516e..c4586d17ab9 100644 --- a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/RawHTTP2ServerConnectionFactory.java +++ b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/RawHTTP2ServerConnectionFactory.java @@ -37,6 +37,11 @@ public class RawHTTP2ServerConnectionFactory extends AbstractHTTP2ServerConnecti { private final ServerSessionListener listener; + public RawHTTP2ServerConnectionFactory(ServerSessionListener listener) + { + this(new HttpConfiguration(), listener); + } + public RawHTTP2ServerConnectionFactory(HttpConfiguration httpConfiguration, ServerSessionListener listener) { super(httpConfiguration); From 93774ae5648822262f906e1bf8954956190a46cf Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Mon, 13 Apr 2020 14:36:33 +0200 Subject: [PATCH 072/101] Fixed #4766 - SecuredRedirectHandler should extend HandlerWrapper. Updated the implementation to extend from HandlerWrapper. Updated the documentation. Signed-off-by: Simone Bordet --- .../server/http/server-http-handler-use.adoc | 20 +++++++- .../embedded/server/http/HTTPServerDocs.java | 51 +++++++++++++++++++ .../handler/SecuredRedirectHandler.java | 30 +++++------ 3 files changed, 84 insertions(+), 17 deletions(-) diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-handler-use.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-handler-use.adoc index 50c46854783..8a0c18903df 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-handler-use.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-handler-use.adoc @@ -418,8 +418,24 @@ Server [[eg-server-http-handler-use-util-secure-handler]] ===== SecuredRedirectHandler -- Redirect from HTTP to HTTPS -// TODO: wait for issue #4766 -TODO +`SecuredRedirectHandler` allows to redirect requests made with the `http` +scheme (and therefore to the clear-text port) to the `https` scheme (and +therefore to the encrypted port). + +For example a request to `+http://domain.com:8080/path?param=value+` is +redirected to `+https://domain.com:8443/path?param=value+`. + +Server applications must configure a `HttpConfiguration` object with the +secure scheme and secure port so that `SecuredRedirectHandler` can build +the redirect URI. + +`SecuredRedirectHandler` is typically configured at the server level, +although it can be configured on a per-context basis. + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=securedHandler] +---- [[eg-server-http-handler-use-util-default-handler]] ===== DefaultHandler diff --git a/jetty-documentation/src/main/java/embedded/server/http/HTTPServerDocs.java b/jetty-documentation/src/main/java/embedded/server/http/HTTPServerDocs.java index 263d94484f5..d9a8395a693 100644 --- a/jetty-documentation/src/main/java/embedded/server/http/HTTPServerDocs.java +++ b/jetty-documentation/src/main/java/embedded/server/http/HTTPServerDocs.java @@ -53,6 +53,7 @@ import org.eclipse.jetty.server.handler.HandlerCollection; import org.eclipse.jetty.server.handler.HandlerList; import org.eclipse.jetty.server.handler.HandlerWrapper; import org.eclipse.jetty.server.handler.ResourceHandler; +import org.eclipse.jetty.server.handler.SecuredRedirectHandler; import org.eclipse.jetty.server.handler.StatisticsHandler; import org.eclipse.jetty.server.handler.gzip.GzipHandler; import org.eclipse.jetty.servlet.DefaultServlet; @@ -761,6 +762,56 @@ public class HTTPServerDocs // end::statsHandler[] } + public void securedHandler() throws Exception + { + // tag::securedHandler[] + Server server = new Server(); + + // Configure the HttpConfiguration for the clear-text connector. + int securePort = 8443; + HttpConfiguration httpConfig = new HttpConfiguration(); + httpConfig.setSecurePort(securePort); + + // The clear-text connector. + ServerConnector connector = new ServerConnector(server, new HttpConnectionFactory(httpConfig)); + connector.setPort(8080); + server.addConnector(connector); + + // Configure the HttpConfiguration for the encrypted connector. + HttpConfiguration httpsConfig = new HttpConfiguration(httpConfig); + // Add the SecureRequestCustomizer because we are using TLS. + httpConfig.addCustomizer(new SecureRequestCustomizer()); + + // The HttpConnectionFactory for the encrypted connector. + HttpConnectionFactory http11 = new HttpConnectionFactory(httpsConfig); + + // Configure the SslContextFactory with the keyStore information. + SslContextFactory.Server sslContextFactory = new SslContextFactory.Server(); + sslContextFactory.setKeyStorePath("/path/to/keystore"); + sslContextFactory.setKeyStorePassword("secret"); + + // The ConnectionFactory for TLS. + SslConnectionFactory tls = new SslConnectionFactory(sslContextFactory, http11.getProtocol()); + + // The encrypted connector. + ServerConnector secureConnector = new ServerConnector(server, tls, http11); + secureConnector.setPort(8443); + server.addConnector(secureConnector); + + SecuredRedirectHandler securedHandler = new SecuredRedirectHandler(); + + // Link the SecuredRedirectHandler to the Server. + server.setHandler(securedHandler); + + // Create a ContextHandlerCollection to hold contexts. + ContextHandlerCollection contextCollection = new ContextHandlerCollection(); + // Link the ContextHandlerCollection to the StatisticsHandler. + securedHandler.setHandler(contextCollection); + + server.start(); + // end::securedHandler[] + } + public void defaultHandler() throws Exception { // tag::defaultHandler[] diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/SecuredRedirectHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/SecuredRedirectHandler.java index 450ced06987..28dc7a6653e 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/SecuredRedirectHandler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/SecuredRedirectHandler.java @@ -30,20 +30,22 @@ import org.eclipse.jetty.server.Request; import org.eclipse.jetty.util.URIUtil; /** - * Secured Redirect Handler - *

      - * Using information present in the {@link HttpConfiguration}, will attempt to redirect to the {@link HttpConfiguration#getSecureScheme()} and - * {@link HttpConfiguration#getSecurePort()} for any request that {@link HttpServletRequest#isSecure()} == false. + *

      SecuredRedirectHandler redirects from {@code http} to {@code https}.

      + *

      SecuredRedirectHandler uses the information present in {@link HttpConfiguration} + * attempting to redirect to the {@link HttpConfiguration#getSecureScheme()} and + * {@link HttpConfiguration#getSecurePort()} for any request that + * {@link HttpServletRequest#isSecure()} is false.

      */ -public class SecuredRedirectHandler extends AbstractHandler +public class SecuredRedirectHandler extends HandlerWrapper { @Override public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { HttpChannel channel = baseRequest.getHttpChannel(); - if (baseRequest.isSecure() || (channel == null)) + if (baseRequest.isSecure() || channel == null) { - // nothing to do + // Nothing to do here. + super.handle(target, baseRequest, request, response); return; } @@ -52,23 +54,21 @@ public class SecuredRedirectHandler extends AbstractHandler HttpConfiguration httpConfig = channel.getHttpConfiguration(); if (httpConfig == null) { - // no config, show error - response.sendError(HttpStatus.FORBIDDEN_403, "No http configuration available"); + response.sendError(HttpStatus.FORBIDDEN_403, "Missing HttpConfiguration"); return; } - if (httpConfig.getSecurePort() > 0) + int securePort = httpConfig.getSecurePort(); + if (securePort > 0) { - String scheme = httpConfig.getSecureScheme(); - int port = httpConfig.getSecurePort(); - - String url = URIUtil.newURI(scheme, baseRequest.getServerName(), port, baseRequest.getRequestURI(), baseRequest.getQueryString()); + String secureScheme = httpConfig.getSecureScheme(); + String url = URIUtil.newURI(secureScheme, baseRequest.getServerName(), securePort, baseRequest.getRequestURI(), baseRequest.getQueryString()); response.setContentLength(0); response.sendRedirect(url); } else { - response.sendError(HttpStatus.FORBIDDEN_403, "Not Secure"); + response.sendError(HttpStatus.FORBIDDEN_403, "HttpConfiguration.securePort not configured"); } } } From 1a234dcf4c47d56cdec8deab21110461efb06c55 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Mon, 13 Apr 2020 14:55:16 +0200 Subject: [PATCH 073/101] Issue #4765 - Review GzipHandler inside ServletContextHandler. Removed GzipHandler from ServletContextHandler. Updated ServletContextHandlerTest. Signed-off-by: Simone Bordet --- .../jetty/servlet/ServletContextHandler.java | 42 ------------------- .../servlet/ServletContextHandlerTest.java | 37 +--------------- 2 files changed, 2 insertions(+), 77 deletions(-) diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java index d86529440b0..7e611b7803c 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java @@ -95,7 +95,6 @@ public class ServletContextHandler extends ContextHandler public static final int SESSIONS = 1; public static final int SECURITY = 2; - public static final int GZIP = 4; public static final int NO_SESSIONS = 0; public static final int NO_SECURITY = 0; @@ -128,7 +127,6 @@ public class ServletContextHandler extends ContextHandler protected SessionHandler _sessionHandler; protected SecurityHandler _securityHandler; protected ServletHandler _servletHandler; - protected GzipHandler _gzipHandler; protected int _options; protected JspConfigDescriptor _jspConfig; @@ -281,21 +279,6 @@ public class ServletContextHandler extends ContextHandler handler = _securityHandler; } - // link gzip handler - if (getGzipHandler() != null) - { - while (!(handler.getHandler() instanceof GzipHandler) && - !(handler.getHandler() instanceof ServletHandler) && - handler.getHandler() instanceof HandlerWrapper) - { - handler = (HandlerWrapper)handler.getHandler(); - } - - if (handler.getHandler() != _gzipHandler) - doSetHandler(handler, _gzipHandler); - handler = _gzipHandler; - } - // link servlet handler if (getServletHandler() != null) { @@ -369,8 +352,6 @@ public class ServletContextHandler extends ContextHandler /** * Finish constructing handlers and link them together. - * - * @see org.eclipse.jetty.server.handler.ContextHandler#startContext() */ @Override protected void startContext() throws Exception @@ -440,17 +421,6 @@ public class ServletContextHandler extends ContextHandler return _sessionHandler; } - /** - * @return Returns the gzipHandler. - */ - @ManagedAttribute(value = "context gzip handler", readonly = true) - public GzipHandler getGzipHandler() - { - if (_gzipHandler == null && (_options & GZIP) != 0 && !isStarted()) - _gzipHandler = new GzipHandler(); - return _gzipHandler; - } - /** * Convenience method to add a servlet. * @@ -652,16 +622,6 @@ public class ServletContextHandler extends ContextHandler relinkHandlers(); } - /** - * @param gzipHandler The {@link GzipHandler} to set on this context. - */ - public void setGzipHandler(GzipHandler gzipHandler) - { - replaceHandler(_gzipHandler, gzipHandler); - _gzipHandler = gzipHandler; - relinkHandlers(); - } - /** * @param servletHandler The servletHandler to set. */ @@ -683,8 +643,6 @@ public class ServletContextHandler extends ContextHandler setSessionHandler((SessionHandler)handler); else if (handler instanceof SecurityHandler) setSecurityHandler((SecurityHandler)handler); - else if (handler instanceof GzipHandler) - setGzipHandler((GzipHandler)handler); else if (handler instanceof ServletHandler) setServletHandler((ServletHandler)handler); else diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletContextHandlerTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletContextHandlerTest.java index 5df8d48658b..bef8ceef9a4 100644 --- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletContextHandlerTest.java +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletContextHandlerTest.java @@ -79,7 +79,6 @@ import org.eclipse.jetty.server.handler.ContextHandlerCollection; import org.eclipse.jetty.server.handler.HandlerList; import org.eclipse.jetty.server.handler.HandlerWrapper; import org.eclipse.jetty.server.handler.ResourceHandler; -import org.eclipse.jetty.server.handler.gzip.GzipHandler; import org.eclipse.jetty.server.session.SessionHandler; import org.eclipse.jetty.util.DecoratedObjectFactory; import org.eclipse.jetty.util.Decorator; @@ -1630,30 +1629,6 @@ public class ServletContextHandlerTest assertEquals(extra, context.getSessionHandler().getHandler()); } - @Test - public void testGzipHandlerOption() throws Exception - { - ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS | ServletContextHandler.GZIP); - GzipHandler gzip = context.getGzipHandler(); - _server.start(); - assertEquals(context.getSessionHandler(), context.getHandler()); - assertEquals(gzip, context.getSessionHandler().getHandler()); - assertEquals(context.getServletHandler(), gzip.getHandler()); - } - - @Test - public void testGzipHandlerSet() throws Exception - { - ServletContextHandler context = new ServletContextHandler(); - context.setSessionHandler(new SessionHandler()); - context.setGzipHandler(new GzipHandler()); - GzipHandler gzip = context.getGzipHandler(); - _server.start(); - assertEquals(context.getSessionHandler(), context.getHandler()); - assertEquals(gzip, context.getSessionHandler().getHandler()); - assertEquals(context.getServletHandler(), gzip.getHandler()); - } - @Test public void testReplaceServletHandlerWithServlet() throws Exception { @@ -1689,14 +1664,12 @@ public class ServletContextHandlerTest @Test public void testSetSecurityHandler() throws Exception { - ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS | ServletContextHandler.SECURITY | ServletContextHandler.GZIP); + ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS | ServletContextHandler.SECURITY); assertNotNull(context.getSessionHandler()); SessionHandler sessionHandler = context.getSessionHandler(); assertNotNull(context.getSecurityHandler()); SecurityHandler securityHandler = context.getSecurityHandler(); - assertNotNull(context.getGzipHandler()); - GzipHandler gzipHandler = context.getGzipHandler(); - + //check the handler linking order HandlerWrapper h = (HandlerWrapper)context.getHandler(); assertSame(h, sessionHandler); @@ -1704,9 +1677,6 @@ public class ServletContextHandlerTest h = (HandlerWrapper)h.getHandler(); assertSame(h, securityHandler); - h = (HandlerWrapper)h.getHandler(); - assertSame(h, gzipHandler); - //replace the security handler SecurityHandler myHandler = new SecurityHandler() { @@ -1747,9 +1717,6 @@ public class ServletContextHandlerTest h = (HandlerWrapper)h.getHandler(); assertSame(h, myHandler); - - h = (HandlerWrapper)h.getHandler(); - assertSame(h, gzipHandler); } @Test From dc89f7f264c1503d7a4a70fbdcf23024fe2bd504 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Mon, 13 Apr 2020 17:57:38 +0200 Subject: [PATCH 074/101] Fixes #4764 - HTTP2 Jetty Server does not send back content-length. Sending Content-Length header if known at the time of sending the response headers. Signed-off-by: Simone Bordet --- .../http2/client/http/ContentLengthTest.java | 105 ++++++++++++++++++ .../http2/server/HttpTransportOverHTTP2.java | 8 ++ 2 files changed, 113 insertions(+) create mode 100644 jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/ContentLengthTest.java diff --git a/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/ContentLengthTest.java b/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/ContentLengthTest.java new file mode 100644 index 00000000000..6d0bb511513 --- /dev/null +++ b/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/ContentLengthTest.java @@ -0,0 +1,105 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.http2.client.http; + +import java.io.IOException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.gzip.GzipHandler; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class ContentLengthTest extends AbstractTest +{ + @ParameterizedTest + @ValueSource(strings = {"GET", "HEAD", "POST", "PUT"}) + public void testZeroContentLengthAddedByServer(String method) throws Exception + { + start(new EmptyServerHandler()); + + ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) + .method(method) + .send(); + + HttpFields responseHeaders = response.getHeaders(); + long contentLength = responseHeaders.getLongField(HttpHeader.CONTENT_LENGTH.asString()); + assertEquals(0, contentLength); + } + + @ParameterizedTest + @ValueSource(strings = {"GET", "HEAD", "POST", "PUT"}) + public void testContentLengthAddedByServer(String method) throws Exception + { + byte[] data = new byte[512]; + start(new EmptyServerHandler() + { + @Override + protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + { + response.getOutputStream().write(data); + } + }); + + ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) + .method(method) + .send(); + + HttpFields responseHeaders = response.getHeaders(); + long contentLength = responseHeaders.getLongField(HttpHeader.CONTENT_LENGTH.asString()); + assertEquals(data.length, contentLength); + } + + @ParameterizedTest + @ValueSource(strings = {"GET", "HEAD", "POST", "PUT"}) + public void testGzippedContentLengthAddedByServer(String method) throws Exception + { + byte[] data = new byte[4096]; + + GzipHandler gzipHandler = new GzipHandler(); + gzipHandler.addIncludedMethods(method); + gzipHandler.setMinGzipSize(data.length / 2); + gzipHandler.setHandler(new EmptyServerHandler() + { + @Override + protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + { + response.setContentLength(data.length); + response.getOutputStream().write(data); + } + }); + + start(gzipHandler); + + ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) + .method(method) + .send(); + + HttpFields responseHeaders = response.getHeaders(); + long contentLength = responseHeaders.getLongField(HttpHeader.CONTENT_LENGTH.asString()); + assertTrue(0 < contentLength && contentLength < data.length); + } +} diff --git a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpTransportOverHTTP2.java b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpTransportOverHTTP2.java index 6504977d5d9..a19a8fa071e 100644 --- a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpTransportOverHTTP2.java +++ b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpTransportOverHTTP2.java @@ -23,6 +23,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Supplier; import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; @@ -110,6 +111,13 @@ public class HttpTransportOverHTTP2 implements HttpTransport { if (commit.compareAndSet(false, true)) { + if (lastContent) + { + HttpFields responseHeaders = info.getFields(); + if (!responseHeaders.contains(HttpHeader.CONTENT_LENGTH)) + responseHeaders.put(HttpHeader.CONTENT_LENGTH, String.valueOf(BufferUtil.length(content))); + } + if (hasContent) { Callback commitCallback = new Callback.Nested(callback) From e17d0f4e98e3ef7a4149936691e9807662f55344 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Mon, 13 Apr 2020 23:54:31 +0200 Subject: [PATCH 075/101] Issue #4765 - Review GzipHandler inside ServletContextHandler. Fixed test failures. Signed-off-by: Simone Bordet --- .../jetty/servlets/GzipDefaultTest.java | 2 +- .../org/eclipse/jetty/servlets/GzipTester.java | 18 +++++++++++------- .../jetty/servlets/IncludedGzipTest.java | 2 +- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipDefaultTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipDefaultTest.java index 19475c61f29..a30150100c7 100644 --- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipDefaultTest.java +++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipDefaultTest.java @@ -673,7 +673,7 @@ public class GzipDefaultTest GzipTester tester = new GzipTester(testingdir.getEmptyPathDir(), compressionType); // Configure Gzip Handler - tester.getGzipHandler().setExcludedPaths("/bad.txt"); + tester.getGzipHandler().setExcludedPaths(tester.getContextPath() + "/bad.txt"); tester.getGzipHandler().setIncludedPaths("*.txt"); // Prepare server file diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipTester.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipTester.java index 28959a4dc42..61bb4fab8be 100644 --- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipTester.java +++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipTester.java @@ -44,7 +44,6 @@ import org.eclipse.jetty.http.tools.HttpTester; import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.server.handler.gzip.GzipHandler; import org.eclipse.jetty.servlet.FilterHolder; -import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.servlet.ServletTester; import org.eclipse.jetty.toolchain.test.FS; @@ -82,28 +81,33 @@ public class GzipTester private String encoding = "ISO8859_1"; private String userAgent = null; - private final ServletTester tester = new ServletTester("/context", ServletContextHandler.GZIP); + private final GzipHandler gzipHandler = new GzipHandler(); + private final ServletTester tester = new ServletTester("/context"); private Path testdir; private String accept; private String compressionType; + public GzipTester(Path testingdir, String compressionType) + { + this(testingdir, compressionType, compressionType); + } + public GzipTester(Path testingdir, String compressionType, String accept) { this.testdir = testingdir; this.compressionType = compressionType; this.accept = accept; + this.tester.getServer().insertHandler(gzipHandler); } - public GzipTester(Path testingdir, String compressionType) + public String getContextPath() { - this.testdir = testingdir; - this.compressionType = compressionType; - this.accept = compressionType; + return tester.getContextPath(); } public GzipHandler getGzipHandler() { - return tester.getContext().getGzipHandler(); + return gzipHandler; } public int getOutputBufferSize() diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/IncludedGzipTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/IncludedGzipTest.java index 50d7526bf67..df942bb0d60 100644 --- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/IncludedGzipTest.java +++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/IncludedGzipTest.java @@ -90,7 +90,7 @@ public class IncludedGzipTest GzipHandler gzipHandler = new GzipHandler(); gzipHandler.setMinGzipSize(16); - tester.getContext().insertHandler(gzipHandler); + tester.getServer().insertHandler(gzipHandler); tester.start(); } From 2e85b3e169e30d604b1af11a7518086e5545b0ce Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Tue, 14 Apr 2020 11:44:58 +0200 Subject: [PATCH 076/101] Fixes #4764 - HTTP2 Jetty Server does not send back content-length. Updates after review. Now the Content-Length header is generated by HpackEncoder based on MetaData.contentLength, so that the MetaData.HttpFields are not modified. Signed-off-by: Simone Bordet --- .../java/org/eclipse/jetty/http/MetaData.java | 5 +++++ .../eclipse/jetty/http2/hpack/HpackEncoder.java | 14 +++++++++++++- .../http2/server/HttpTransportOverHTTP2.java | 16 ++++++++++++---- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/MetaData.java b/jetty-http/src/main/java/org/eclipse/jetty/http/MetaData.java index 7591f62e291..8035b086b72 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/MetaData.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/MetaData.java @@ -119,6 +119,11 @@ public class MetaData implements Iterable return _contentLength; } + public void setContentLength(long contentLength) + { + _contentLength = contentLength; + } + /** * @return an iterator over the HTTP fields * @see #getFields() diff --git a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackEncoder.java b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackEncoder.java index d71df43f044..0788806b34b 100644 --- a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackEncoder.java +++ b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackEncoder.java @@ -217,7 +217,8 @@ public class HpackEncoder // Remove fields as specified in RFC 7540, 8.1.2.2. if (fields != null) { - // For example: Connection: Close, TE, Upgrade, Custom. + // Remove the headers specified in the Connection header, + // for example: Connection: Close, TE, Upgrade, Custom. Set hopHeaders = null; for (String value : fields.getCSV(HttpHeader.CONNECTION, false)) { @@ -225,6 +226,8 @@ public class HpackEncoder hopHeaders = new HashSet<>(); hopHeaders.add(StringUtil.asciiToLowerCase(value)); } + + boolean contentLengthEncoded = false; for (HttpField field : fields) { HttpHeader header = field.getHeader(); @@ -239,8 +242,17 @@ public class HpackEncoder String name = field.getLowerCaseName(); if (hopHeaders != null && hopHeaders.contains(name)) continue; + if (header == HttpHeader.CONTENT_LENGTH) + contentLengthEncoded = true; encode(buffer, field); } + + if (!contentLengthEncoded) + { + long contentLength = metadata.getContentLength(); + if (contentLength >= 0) + encode(buffer, new HttpField(HttpHeader.CONTENT_LENGTH, String.valueOf(contentLength))); + } } // Check size diff --git a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpTransportOverHTTP2.java b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpTransportOverHTTP2.java index a19a8fa071e..03a9812c588 100644 --- a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpTransportOverHTTP2.java +++ b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpTransportOverHTTP2.java @@ -22,8 +22,8 @@ import java.nio.ByteBuffer; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Supplier; +import org.eclipse.jetty.http.BadMessageException; import org.eclipse.jetty.http.HttpFields; -import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; @@ -113,9 +113,17 @@ public class HttpTransportOverHTTP2 implements HttpTransport { if (lastContent) { - HttpFields responseHeaders = info.getFields(); - if (!responseHeaders.contains(HttpHeader.CONTENT_LENGTH)) - responseHeaders.put(HttpHeader.CONTENT_LENGTH, String.valueOf(BufferUtil.length(content))); + long realContentLength = BufferUtil.length(content); + long contentLength = info.getContentLength(); + if (contentLength < 0) + { + info.setContentLength(realContentLength); + } + else if (contentLength != realContentLength) + { + callback.failed(new BadMessageException(HttpStatus.INTERNAL_SERVER_ERROR_500, String.format("Incorrect Content-Length %d!=%d", contentLength, realContentLength))); + return; + } } if (hasContent) From 013acca012db0a40cfecfbec33580c3be9739b1b Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Tue, 14 Apr 2020 12:19:52 +0200 Subject: [PATCH 077/101] Issue #4765 - Review GzipHandler inside ServletContextHandler. Fixed XML files. Restored ServletContextHandler.setGzipHandler() and deprecated it in case it's used from user's XML files. Signed-off-by: Simone Bordet --- .../jetty/servlet/ServletContextHandler.java | 13 ++++++++++++- .../src/test/resources/add-jetty-test-webapp.xml | 12 +++++++----- .../webapp-contexts/RFC2616/rfc2616-webapp.xml | 12 +++++++----- .../src/main/config/demo-base/webapps/test.xml | 12 +++++++----- 4 files changed, 33 insertions(+), 16 deletions(-) diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java index 7e611b7803c..a4bcc5c039d 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java @@ -633,7 +633,18 @@ public class ServletContextHandler extends ContextHandler } /** - * Insert a HandlerWrapper before the first Session,Security or ServletHandler + * @param gzipHandler the GzipHandler for this ServletContextHandler + * @deprecated use {@link #insertHandler(HandlerWrapper)} instead + */ + @Deprecated + public void setGzipHandler(GzipHandler gzipHandler) + { + insertHandler(gzipHandler); + LOG.warn("ServletContextHandler.setGzipHandler(GzipHandler) is deprecated, use insertHandler(HandlerWrapper) instead."); + } + + /** + * Insert a HandlerWrapper before the first Session, Security or ServletHandler * but after any other HandlerWrappers. */ @Override diff --git a/tests/test-integration/src/test/resources/add-jetty-test-webapp.xml b/tests/test-integration/src/test/resources/add-jetty-test-webapp.xml index 07d1b2b74f0..96e6c271d45 100644 --- a/tests/test-integration/src/test/resources/add-jetty-test-webapp.xml +++ b/tests/test-integration/src/test/resources/add-jetty-test-webapp.xml @@ -6,11 +6,13 @@ /test-jetty-webapp /test-jetty-webapp.war - - - 1024 - - + + + + 1024 + + + diff --git a/tests/test-integration/src/test/resources/webapp-contexts/RFC2616/rfc2616-webapp.xml b/tests/test-integration/src/test/resources/webapp-contexts/RFC2616/rfc2616-webapp.xml index f7c83e419f1..55cd5a5c847 100644 --- a/tests/test-integration/src/test/resources/webapp-contexts/RFC2616/rfc2616-webapp.xml +++ b/tests/test-integration/src/test/resources/webapp-contexts/RFC2616/rfc2616-webapp.xml @@ -3,9 +3,11 @@ /rfc2616-webapp /test-webapp-rfc2616.war - - - 1024 - - + + + + 1024 + + + diff --git a/tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/webapps/test.xml b/tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/webapps/test.xml index 0e49d729146..b98b427615f 100644 --- a/tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/webapps/test.xml +++ b/tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/webapps/test.xml @@ -35,11 +35,13 @@ detected. true - - - 2048 - - + + + + 2048 + + + Connection +Connection -> Parser : parse() +Parser -> HttpChannel : events +Connection -> HttpChannel : handle() +HttpChannel -> Server : handle() +Server -> Handlers : handle() +---- + +[[eg-server-http-channel-events]] +===== HttpChannel Events + +The central component processing HTTP requests is `HttpChannel`. +There is a 1-to-1 relationship between an HTTP request/response and an +`HttpChannel`, no matter what is the specific protocol that carries the +HTTP request over the network (HTTP/1.1, HTTP/2 or FastCGI). + +Advanced server applications may be interested in the progress of the +processing of an HTTP request/response by `HttpChannel`. +A typical case is to know exactly _when_ the HTTP request/response +processing is complete, for example to monitor processing times. + +NOTE: A `Handler` or a Servlet `Filter` may not report precisely when +an HTTP request/response processing is finished. +A server application may write a small enough content that is aggregated +by Jetty for efficiency reasons; the write returns immediately, but +nothing has been written to the network yet. + +`HttpChannel` notifies ``HttpChannel.Listener``s of the progress of the +HTTP request/response handling. +Currently, the following events are available: + +* `requestBegin` +* `beforeDispatch` +* `dispatchFailure` +* `afterDispatch` +* `requestContent` +* `requestContentEnd` +* `requestTrailers` +* `requestEnd` +* `responseBegin` +* `responseCommit` +* `responseContent` +* `responseFailure` +* `responseEnd` +* `complete` + +Please refer to the `HttpChannel.Listener` +link:{JDURL}/org/eclipse/jetty/server/HttpChannel.Listener.html[javadocs] +for the complete list of events. + +Server applications can register `HttpChannel.Listener` by adding them as +beans to the `Connector`: + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=httpChannelListener] +---- include::server-http-connector.adoc[] include::server-http-handler.adoc[] diff --git a/jetty-documentation/src/main/java/embedded/server/http/HTTPServerDocs.java b/jetty-documentation/src/main/java/embedded/server/http/HTTPServerDocs.java index d9a8395a693..f3e5f786764 100644 --- a/jetty-documentation/src/main/java/embedded/server/http/HTTPServerDocs.java +++ b/jetty-documentation/src/main/java/embedded/server/http/HTTPServerDocs.java @@ -20,6 +20,8 @@ package embedded.server.http; import java.io.IOException; import java.util.EnumSet; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import javax.servlet.DispatcherType; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; @@ -37,6 +39,7 @@ import org.eclipse.jetty.rewrite.handler.RedirectRegexRule; import org.eclipse.jetty.rewrite.handler.RewriteHandler; import org.eclipse.jetty.rewrite.handler.RewriteRegexRule; import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.HttpChannel; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.server.ProxyConnectionFactory; @@ -68,6 +71,8 @@ import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.eclipse.jetty.webapp.WebAppContext; +import static java.lang.System.Logger.Level.INFO; + @SuppressWarnings("unused") public class HTTPServerDocs { @@ -104,6 +109,50 @@ public class HTTPServerDocs // end::simple[] } + public void httpChannelListener() throws Exception + { + // tag::httpChannelListener[] + class TimingHttpChannelListener implements HttpChannel.Listener + { + private final ConcurrentMap times = new ConcurrentHashMap<>(); + + @Override + public void onRequestBegin(Request request) + { + times.put(request, System.nanoTime()); + } + + @Override + public void onComplete(Request request) + { + long begin = times.remove(request); + long elapsed = System.nanoTime() - begin; + System.getLogger("timing").log(INFO, "Request {0} took {1} ns", request, elapsed); + } + } + + Server server = new Server(); + + Connector connector = new ServerConnector(server); + server.addConnector(connector); + + // Add the HttpChannel.Listener as bean to the connector. + connector.addBean(new TimingHttpChannelListener()); + + // Set a simple Handler to handle requests/responses. + server.setHandler(new AbstractHandler() + { + @Override + public void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) + { + jettyRequest.setHandled(true); + } + }); + + server.start(); + // end::httpChannelListener[] + } + public void configureConnector() throws Exception { // tag::configureConnector[] From b388b53f794c760fffee358e44a8996a0f9ae5a3 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Tue, 14 Apr 2020 22:52:20 +0200 Subject: [PATCH 083/101] Improvements to the Jetty server documentation. Revised and moved documentation for 1xx status codes. Signed-off-by: Simone Bordet --- .../old_docs/architecture/1xx-responses.adoc | 41 ------------- .../old_docs/architecture/chapter.adoc | 1 - .../server/http/server-http-application.adoc | 57 +++++++++++++++++++ .../server/http/server-http.adoc | 1 + .../embedded/server/http/HTTPServerDocs.java | 43 ++++++++++++++ 5 files changed, 101 insertions(+), 42 deletions(-) delete mode 100644 jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/architecture/1xx-responses.adoc create mode 100644 jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-application.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/architecture/1xx-responses.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/architecture/1xx-responses.adoc deleted file mode 100644 index e558ab780d6..00000000000 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/architecture/1xx-responses.adoc +++ /dev/null @@ -1,41 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under -// the terms of the Eclipse Public License 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0 -// -// This Source Code may also be made available under the following -// Secondary Licenses when the conditions for such availability set -// forth in the Eclipse Public License, v. 2.0 are satisfied: -// the Apache License v2.0 which is available at -// https://www.apache.org/licenses/LICENSE-2.0 -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -[[jetty-1xx-responses]] -=== Managing 1xx Responses - -The http://www.ietf.org/rfc/rfc2616.txt[HTTP RFC] allows for 1xx informational responses to be sent before a real content response. -Unfortunately the servlet specification does not provide a way for these to be sent, so Jetty has had to provide non-standard handling of these headers. - -[[jetty-100-continue]] -==== 100 Continue - -The 100 Continue response should be sent by the server when a client sends a request with a Expect: 100-continue header, as the client will not send the body of the request until the 100 continue response has been sent. - -The intent of this feature is to allow a server to inspect the headers and to tell the client to not send a request body that might be too large or insufficiently private or otherwise unable to be handled. - -Jetty achieves this by waiting until the input stream or reader is obtained by the filter/servlet, before sending the 100 continues response. -Thus a filter/servlet may inspect the headers of a request before getting the input stream and send an error response (or redirect etc.) rather than the 100 continues. - -[[jetty-102-processing]] -==== 102 Processing - -http://www.ietf.org/rfc/rfc2518.txt[RFC 2518] defines the 102 processing response that can be sent "when the server has a reasonable expectation that the request will take significant time to complete. -As guidance, if a method is taking longer than 20 seconds (a reasonable, but arbitrary value) to process the server SHOULD return a 102 (Processing) response". - -So if a request is received with the Expect: 102-processing header, then a filter/servlet may send a 102 response (without terminating further processing) by calling `servletResponse.sendError(102);`. diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/architecture/chapter.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/architecture/chapter.adoc index 4c08ba13d69..833ba3f384b 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/architecture/chapter.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/architecture/chapter.adoc @@ -23,5 +23,4 @@ General items related to the architecture of jetty and how it deals with certain include::basic-architecture.adoc[] include::jetty-classloading.adoc[] -include::1xx-responses.adoc[] include::server-side-architecture.adoc[] diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-application.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-application.adoc new file mode 100644 index 00000000000..c872ac8e57d --- /dev/null +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-application.adoc @@ -0,0 +1,57 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +[[eg-server-http-application]] +=== Writing HTTP Server Applications + +Writing HTTP applications is typically simple, especially when using blocking APIs. +However, there are more subtle cases where it is worth clarifying what a server application should do to obtain the desired results when deployed in Jetty. + +[[eg-server-http-application-1xx]] +==== Managing 1xx Responses + +The link:https://tools.ietf.org/html/rfc7231#section-5.1.1[HTTP/1.1 RFC] allows for `1xx` informational responses to be sent before a real content response. +Unfortunately the servlet specification does not provide a way for these to be sent, so Jetty has had to provide non-standard handling of these headers. + +[[eg-server-http-application-100]] +===== 100 Continue + +The `100 Continue` response should be sent by the server when a client sends a request with an `Expect: 100-continue` header, as the client will not send the body of the request until the `100 Continue` response has been sent. + +The intent of this feature is to allow a server to inspect the headers and to tell the client to not send a request body that might be too large or insufficiently private or otherwise unable to be handled. + +Jetty achieves this by waiting until the input stream or reader is obtained by the filter/servlet, before sending the `100 Continue` response. +Thus a filter/servlet may inspect the headers of a request before getting the input stream and send an error response (or redirect etc.) rather than the 100 continues. + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=continue100] +---- + +[[jetty-102-processing]] +==== 102 Processing + +link:https://tools.ietf.org/html/rfc2518#section-10.1[RFC 2518] defined the `102 Processing` status code that can be sent "when the server has a reasonable expectation that the request will take significant time to complete. +As guidance, if a method is taking longer than 20 seconds (a reasonable, but arbitrary value) to process the server SHOULD return a `102 Processing` response". + +However a later update of RFC 2518, link:https://tools.ietf.org/html/rfc4918[RFC 4918], removed the `102 Processing` status code for link:https://tools.ietf.org/html/rfc4918#appendix-F.3["lack of implementation"]. + +Jetty supports the `102 Processing` status code. +If a request is received with the `Expect: 102-processing` header, then a filter/servlet may send a `102 Processing` response (without terminating further processing) by calling `response.sendError(102)`. + +// TODO: add section to always read request content until -1 is reached. diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http.adoc index 982ed874200..cde36774086 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http.adoc @@ -181,3 +181,4 @@ include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=httpChan include::server-http-connector.adoc[] include::server-http-handler.adoc[] +include::server-http-application.adoc[] diff --git a/jetty-documentation/src/main/java/embedded/server/http/HTTPServerDocs.java b/jetty-documentation/src/main/java/embedded/server/http/HTTPServerDocs.java index f3e5f786764..3b2cedb7fbd 100644 --- a/jetty-documentation/src/main/java/embedded/server/http/HTTPServerDocs.java +++ b/jetty-documentation/src/main/java/embedded/server/http/HTTPServerDocs.java @@ -24,12 +24,15 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import javax.servlet.DispatcherType; import javax.servlet.ServletException; +import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory; import org.eclipse.jetty.http.HttpCompliance; +import org.eclipse.jetty.http.HttpHeaderValue; +import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpURI; import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory; @@ -885,4 +888,44 @@ public class HTTPServerDocs server.start(); // end::defaultHandler[] } + + public void continue100() + { + // tag::continue100[] + class Continue100HttpServlet extends HttpServlet + { + @Override + protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException + { + // Inspect the method and headers. + boolean isPost = HttpMethod.POST.is(request.getMethod()); + boolean expects100 = HttpHeaderValue.CONTINUE.is(request.getHeader("Expect")); + long contentLength = request.getContentLengthLong(); + + if (isPost && expects100) + { + if (contentLength > 1024 * 1024) + { + // Rejects uploads that are too large. + response.sendError(HttpStatus.PAYLOAD_TOO_LARGE_413); + } + else + { + // Getting the request InputStream indicates that + // the application wants to read the request content. + // Jetty will send the 100 Continue response at this + // point, and the client will send the request content. + ServletInputStream input = request.getInputStream(); + + // Read and process the request input. + } + } + else + { + // Process normal requests. + } + } + } + // end::continue100[] + } } From fc7fb9e5d71f11a85a6ae3c9a0152dfdb8ccba79 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Tue, 14 Apr 2020 23:28:43 +0200 Subject: [PATCH 084/101] Improvements to the Jetty server documentation. Removed the old Handler documentation, already replaced by the new one. Signed-off-by: Simone Bordet --- .../old_docs/handlers/chapter.adoc | 22 --- .../handlers/writing-custom-handlers.adoc | 176 ------------------ .../embedded-guide/old_docs/server.adoc | 2 - .../server/http/server-http-application.adoc | 13 +- .../http/server-http-handler-implement.adoc | 2 +- .../embedded-guide/server/server.adoc | 2 + 6 files changed, 11 insertions(+), 206 deletions(-) delete mode 100644 jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/handlers/chapter.adoc delete mode 100644 jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/handlers/writing-custom-handlers.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/handlers/chapter.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/handlers/chapter.adoc deleted file mode 100644 index ec0b8bba510..00000000000 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/handlers/chapter.adoc +++ /dev/null @@ -1,22 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under -// the terms of the Eclipse Public License 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0 -// -// This Source Code may also be made available under the following -// Secondary Licenses when the conditions for such availability set -// forth in the Eclipse Public License, v. 2.0 are satisfied: -// the Apache License v2.0 which is available at -// https://www.apache.org/licenses/LICENSE-2.0 -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -[[jetty-handlers]] -== Handlers - -include::writing-custom-handlers.adoc[] diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/handlers/writing-custom-handlers.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/handlers/writing-custom-handlers.adoc deleted file mode 100644 index 026007fe3ee..00000000000 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/handlers/writing-custom-handlers.adoc +++ /dev/null @@ -1,176 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under -// the terms of the Eclipse Public License 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0 -// -// This Source Code may also be made available under the following -// Secondary Licenses when the conditions for such availability set -// forth in the Eclipse Public License, v. 2.0 are satisfied: -// the Apache License v2.0 which is available at -// https://www.apache.org/licenses/LICENSE-2.0 -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -[[writing-custom-handlers]] -=== Writing Custom Handlers - -The Handler is the Jetty component that deals with received requests. - -Many users of Jetty never need to write a Jetty Handler, but instead use the link:{JDURL}/org/eclipse/jetty/servlet/package-summary.html[Servlet API.] -You can reuse the existing Jetty handlers for context, security, sessions and servlets without the need for extension. -However, some users might have special requirements or footprint concerns that prohibit the use of the full servlet API. -For them implementing a Jetty handler is a straight forward way to provide dynamic web content with a minimum of fuss. - -See the section on xref:basic-architecture[] to understand more about Handlers vs. Servlets. - -[[handler-api]] -==== The Handler API - -The link:{JDURL}/org/eclipse/jetty/server/Handler.html[Handler] interface provides Jetty's core of content generation or manipulation. -Classes that implement this interface are used to coordinate requests, filter requests and generate content. - -The core API of the Handler interface is: - -[source, java, subs="{sub-order}"] ----- -public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) - throws IOException, ServletException ----- - -An implementation of this method can handle a request and pass the request onto another handler (or servlet), or it can modify and/or wrap the request before passing it on. -This gives three styles of handler: - -* Coordinating Handlers - Handlers that route requests to other handlers (`HandlerCollection`, `ContextHandlerCollection`) -* Filtering Handlers - Handlers that augment a request and pass it on to other handlers (`HandlerWrapper`, `ContextHandler`, `SessionHandler`) -* Generating Handlers - Handlers that produce content (`ResourceHandler` and `ServletHandler`) - -[[target]] -===== The Target - -The target of a handler is an identifier for the resource that should handle the passed request. -This is normally the URI that is parsed from an HTTP Request. -However, in two key circumstances the target may differ from the URI of the passed request: - -* If the request has been dispatched to a named resource, such as a named servlet, the target is the name of that resource. -* If the request is being made by a call to link:http://docs.oracle.com/javaee/7/api/javax/servlet/RequestDispatcher.html[`RequestDispatcher`], the target is the URI of the included resource and is different to the URI of the actual request. - -[[request-and-response]] -===== The Request and Response - -The request and response objects used in the signature of the handle method are -link:http://docs.oracle.com/javaee/7/api/javax/servlet/ServletRequest.html[`ServletRequest`] and link:http://docs.oracle.com/javaee/7/api/javax/servlet/ServletResponse.html[`ServletResponse`]. -These are the standard APIs and are moderately restricted in what they can do to the request and response. -More often than not, access to the Jetty implementations of these classes is required: link:{JDURL}/org/eclipse/jetty/server/Request.html[`Request`] and link:{JDURL}/org/eclipse/jetty/server/Response.html[`Response`]. -However, as the request and response may be wrapped by handlers, filters and servlets, it is not possible to pass the implementation directly. -The following mantra retrieves the core implementation objects from under any wrappers: - -[source, java, subs="{sub-order}"] ----- -Request base_request = request instanceof Request ? (Request)request : HttpConnection.getCurrentConnection().getHttpChannel().getRequest(); -Response base_response = response instanceof Response ? (Response)response : HttpConnection.getCurrentConnection().getHttpChannel().getResponse(); ----- - -Notice that if the handler passes the request on to another handler, it should use the Request/Response objects passed in, and not the base objects. -This is to preserve any wrapping done by up stream handlers. - -[[dispatch]] -===== The Dispatch - -The dispatch argument indicates the state of the handling of the call and may be: - -* `REQUEST == 1` - An original request received from a connector. -* `FORWARD == 2` - A request being forwarded by a RequestDispatcher. -* `INCLUDE == 4` - A request being included by a RequestDispatcher. -* `ERROR == 8` - A request being forwarded to a error handler by the container. - -These mostly have significance for servlet and related handlers. -For example, the security handler only applies authentication and authorization to REQUEST dispatches. - -[[handling-requests]] -==== Handling Requests - -A Handler may handle a request by: - -* xref:generating-response[] -* xref:filtering-request-or-response[] -* xref:passing-request-and-response[] - -[[generating-response]] -===== Generating a Response - -The link:{JDURL}/org/eclipse/jetty/embedded/OneHandler.html[`OneHandler`] embedded example shows how a simple handler can generate a response. - -You can use the standard servlet response API, which will typically set some status, content headers and then write out the content: - -[source, java, subs="{sub-order}"] ----- - response.setContentType("text/html"); - response.setStatus(HttpServletResponse.SC_OK); - response.getWriter().println("

      Hello OneHandler

      "); ----- - -It is also very important that a handler indicate that it has completed handling the request and that the request should not be passed to other handlers: - -[source, java, subs="{sub-order}"] ----- - Request base_request = (request instanceof Request) ? (Request)request:HttpConnection.getCurrentConnection().getHttpChannel().getRequest(); - base_request.setHandled(true); ----- - -[[filtering-request-or-response]] -===== Filtering the Request and/or Response - -Once the base request or response object is obtained, you can modify it. -Typically you would make modifications to accomplish: - -* Breaking the URI into contextPath, servletPath and pathInfo components. -* Associating a resource base with a request for static content. -* Associating a session with a request. -* Associating a security principal with a request. -* Changing the URI and paths during a request dispatch forward to another resource. - -You can also update the context of the request: - -* Setting the current threads context classloader. -* Setting thread locals to identify the current `ServletContext`. - -Typically Jetty passes a modified request to another handler and undoes modifications in a finally block afterwards: - -[source, java, subs="{sub-order}"] ----- - try - { - base_request.setSession(a_session); - next_handler.handle(target,request,response,dispatch); - } - finally - { - base_request.setSession(old_session); - } ----- - -The classes that implement the link:{JDURL}/org/eclipse/jetty/server/handler/HandlerWrapper.html[`HandlerWrapper`] class are typically handler filters of this style. - -[[passing-request-and-response]] -===== Passing the Request and Response to Another Handler - -A handler might simply inspect the request and use the target, request URI or other information to select another handler to pass the request to. -These handlers typically implement the link:{JDURL}/org/eclipse/jetty/server/HandlerContainer.html[`HandlerContainer`] interface. - -Examples include: - -* link:{JDURL}/org/eclipse/jetty/server/handler/HandlerCollection.html[Class `HandlerCollection`] - -A collection of handlers, where each handler is called regardless of the state of the request. -This is typically used to pass a request to a link:{JDURL}/org/eclipse/jetty/server/handler/ContextHandlerCollection.html[`ContextHandlerCollection`,] and then the link:{JDURL}/org/eclipse/jetty/server/handler/RequestLogHandler.html[`RequestLogHandler`.] -* link:{JDURL}/org/eclipse/jetty/server/handler/HandlerList.html[`HandlerList`] - A list of handlers that are called in turn until the request state is set as handled. -* link:{JDURL}/org/eclipse/jetty/server/handler/ContextHandlerCollection.html[`ContextHandlerCollection`] - A collection of Handlers, of which one is selected by best match for the context path. - -[[more-about-handlers]] -==== More About Handlers - -See the link:{JDURL}/[latest Jetty JavaDoc] for detailed information on each Jetty handler. diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/server.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/server.adoc index bfd8444bab5..d8ade48b4b4 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/server.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/server.adoc @@ -21,8 +21,6 @@ include::embedding/chapter.adoc[] include::maven/chapter.adoc[] -include::clients/http/chapter.adoc[] -include::handlers/chapter.adoc[] include::websockets/intro/chapter.adoc[] include::websockets/jetty/chapter.adoc[] include::ant/chapter.adoc[] diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-application.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-application.adoc index c872ac8e57d..dcd6600898e 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-application.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-application.adoc @@ -20,7 +20,7 @@ === Writing HTTP Server Applications Writing HTTP applications is typically simple, especially when using blocking APIs. -However, there are more subtle cases where it is worth clarifying what a server application should do to obtain the desired results when deployed in Jetty. +However, there are subtle cases where it is worth clarifying what a server application should do to obtain the desired results when run by Jetty. [[eg-server-http-application-1xx]] ==== Managing 1xx Responses @@ -44,12 +44,15 @@ include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=continue ---- [[jetty-102-processing]] -==== 102 Processing +===== 102 Processing -link:https://tools.ietf.org/html/rfc2518#section-10.1[RFC 2518] defined the `102 Processing` status code that can be sent "when the server has a reasonable expectation that the request will take significant time to complete. -As guidance, if a method is taking longer than 20 seconds (a reasonable, but arbitrary value) to process the server SHOULD return a `102 Processing` response". +link:https://tools.ietf.org/html/rfc2518[RFC 2518] defined the `102 Processing` status code that can be sent: -However a later update of RFC 2518, link:https://tools.ietf.org/html/rfc4918[RFC 4918], removed the `102 Processing` status code for link:https://tools.ietf.org/html/rfc4918#appendix-F.3["lack of implementation"]. +[quote,RFC 2518 section 10.1] +when the server has a reasonable expectation that the request will take significant time to complete. +As guidance, if a method is taking longer than 20 seconds (a reasonable, but arbitrary value) to process the server SHOULD return a `102 Processing` response. + +However, a later update of RFC 2518, link:https://tools.ietf.org/html/rfc4918[RFC 4918], removed the `102 Processing` status code for link:https://tools.ietf.org/html/rfc4918#appendix-F.3["lack of implementation"]. Jetty supports the `102 Processing` status code. If a request is received with the `Expect: 102-processing` header, then a filter/servlet may send a `102 Processing` response (without terminating further processing) by calling `response.sendError(102)`. diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-handler-implement.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-handler-implement.adoc index 625d206f2e0..36dc9df088d 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-handler-implement.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-handler-implement.adoc @@ -67,5 +67,5 @@ include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=handlerF Note how a filtering `Handler` extends from `HandlerWrapper` and as such needs another handler to forward the request processing to, and how the -two``Handler``s needs to be linked together to work properly. +two ``Handler``s needs to be linked together to work properly. diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/server.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/server/server.adoc index a8cc9ceffd2..79feeefb8da 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/server/server.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/server/server.adoc @@ -45,6 +45,8 @@ xref:eg-server-http2[HTTP/2 libraries] * WebSocket support, for applications that want to embed a WebSocket server, via the xref:eg-server-websocket[WebSocket libraries] +// TODO: add a section on lifecycle and the component tree. + include::http/server-http.adoc[] include::http2/server-http2.adoc[] include::websocket/server-websocket.adoc[] From 4047e31ce76568e3f37e706c1f455cc70505b3b9 Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Tue, 14 Apr 2020 21:15:50 +1000 Subject: [PATCH 085/101] Issue #4771 - cleanup and add tests for the different ws message handlers Signed-off-by: Lachlan Roberts --- .../client/SessionAddMessageHandlerTest.java | 31 ++-- .../handlers/AbstractAnnotatedHandler.java | 69 +++++++ .../javax/tests/handlers/AbstractHandler.java | 68 +++++++ .../tests/handlers/BaseMessageHandler.java | 4 +- .../javax/tests/handlers/BinaryHandlers.java | 169 ++++++++++++++++++ .../handlers/ByteArrayPartialHandler.java | 30 ---- .../tests/handlers/ByteArrayWholeHandler.java | 30 ---- .../handlers/ByteBufferPartialHandler.java | 30 ---- .../handlers/ByteBufferWholeHandler.java | 31 ---- .../tests/handlers/ComboMessageHandler.java | 6 +- .../handlers/ExtendedMessageHandler.java | 2 +- .../handlers/InputStreamWholeHandler.java | 31 ---- .../tests/handlers/LongMessageHandler.java | 3 +- .../tests/handlers/MessageHandlerTest.java | 167 +++++++++++++++++ .../tests/handlers/ReaderWholeHandler.java | 31 ---- .../tests/handlers/StringPartialHandler.java | 30 ---- .../tests/handlers/StringWholeHandler.java | 30 ---- .../javax/tests/handlers/TextHandlers.java | 125 +++++++++++++ .../api/WebSocketConnectionListener.java | 12 +- .../websocket/api/WebSocketListener.java | 8 +- .../api/WebSocketPartialListener.java | 8 +- .../api/WebSocketPingPongListener.java | 8 +- .../api/annotations/OnWebSocketMessage.java | 10 +- .../JettyWebSocketFrameHandlerFactory.java | 19 +- .../tests/ConcurrentConnectTest.java | 6 +- .../jetty/websocket/tests/EventSocket.java | 16 +- .../websocket/tests/JettyOnCloseTest.java | 16 +- .../JettyWebSocketExtensionConfigTest.java | 2 +- .../tests/JettyWebSocketFilterTest.java | 2 +- .../tests/JettyWebSocketServletTest.java | 2 +- .../websocket/tests/SuspendResumeTest.java | 22 +-- .../tests/WebSocketOverHTTP2Test.java | 6 +- .../tests/WebSocketServletExamplesTest.java | 6 +- .../websocket/tests/WebSocketStopTest.java | 10 +- .../tests/autobahn/JettyAutobahnClient.java | 2 +- .../tests/client/ClientConfigTest.java | 8 +- .../listeners/AbstractAnnotatedListener.java | 69 +++++++ .../tests/listeners/AbstractListener.java | 66 +++++++ .../tests/listeners/BinaryListeners.java | 129 +++++++++++++ .../tests/listeners/TextListeners.java | 105 +++++++++++ .../listeners/WebSocketListenerTest.java | 146 +++++++++++++++ .../tests/server/ServerConfigTest.java | 10 +- .../util/messages/ByteArrayMessageSink.java | 3 +- .../messages/PartialByteArrayMessageSink.java | 11 +- .../PartialByteBufferMessageSink.java | 8 - 45 files changed, 1231 insertions(+), 366 deletions(-) create mode 100644 jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/AbstractAnnotatedHandler.java create mode 100644 jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/AbstractHandler.java create mode 100644 jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/BinaryHandlers.java delete mode 100644 jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/ByteArrayPartialHandler.java delete mode 100644 jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/ByteArrayWholeHandler.java delete mode 100644 jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/ByteBufferPartialHandler.java delete mode 100644 jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/ByteBufferWholeHandler.java delete mode 100644 jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/InputStreamWholeHandler.java create mode 100644 jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/MessageHandlerTest.java delete mode 100644 jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/ReaderWholeHandler.java delete mode 100644 jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/StringPartialHandler.java delete mode 100644 jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/StringWholeHandler.java create mode 100644 jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/TextHandlers.java create mode 100644 jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/listeners/AbstractAnnotatedListener.java create mode 100644 jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/listeners/AbstractListener.java create mode 100644 jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/listeners/BinaryListeners.java create mode 100644 jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/listeners/TextListeners.java create mode 100644 jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/listeners/WebSocketListenerTest.java diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/client/SessionAddMessageHandlerTest.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/client/SessionAddMessageHandlerTest.java index f980f1cd3e6..bc097cd5084 100644 --- a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/client/SessionAddMessageHandlerTest.java +++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/client/SessionAddMessageHandlerTest.java @@ -41,10 +41,9 @@ import org.eclipse.jetty.websocket.javax.common.UpgradeRequest; import org.eclipse.jetty.websocket.javax.common.UpgradeRequestAdapter; import org.eclipse.jetty.websocket.javax.tests.MessageType; import org.eclipse.jetty.websocket.javax.tests.SessionMatchers; -import org.eclipse.jetty.websocket.javax.tests.handlers.ByteArrayWholeHandler; -import org.eclipse.jetty.websocket.javax.tests.handlers.ByteBufferPartialHandler; +import org.eclipse.jetty.websocket.javax.tests.handlers.BinaryHandlers; import org.eclipse.jetty.websocket.javax.tests.handlers.LongMessageHandler; -import org.eclipse.jetty.websocket.javax.tests.handlers.StringWholeHandler; +import org.eclipse.jetty.websocket.javax.tests.handlers.TextHandlers; import org.hamcrest.Matchers; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -93,7 +92,7 @@ public class SessionAddMessageHandlerTest @Test public void testMessageHandlerBinary() { - session.addMessageHandler(new ByteBufferPartialHandler()); + session.addMessageHandler(new BinaryHandlers.ByteBufferPartialHandler()); assertThat("session", session, SessionMatchers.isMessageHandlerTypeRegistered(MessageType.BINARY)); assertThat("session", session, Matchers.not(SessionMatchers.isMessageHandlerTypeRegistered(MessageType.TEXT))); assertThat("session", session, Matchers.not(SessionMatchers.isMessageHandlerTypeRegistered(MessageType.PONG))); @@ -102,7 +101,7 @@ public class SessionAddMessageHandlerTest Matchers.hasItem( Matchers.allOf( SessionMatchers.isMessageHandlerType(session, MessageType.BINARY), - instanceOf(ByteBufferPartialHandler.class) + instanceOf(BinaryHandlers.ByteBufferPartialHandler.class) ) ) ); @@ -111,8 +110,8 @@ public class SessionAddMessageHandlerTest @Test public void testMessageHandlerBoth() { - session.addMessageHandler(new StringWholeHandler()); - session.addMessageHandler(new ByteArrayWholeHandler()); + session.addMessageHandler(new TextHandlers.StringWholeHandler()); + session.addMessageHandler(new BinaryHandlers.ByteArrayWholeHandler()); assertThat("session", session, SessionMatchers.isMessageHandlerTypeRegistered(MessageType.BINARY)); assertThat("session", session, SessionMatchers.isMessageHandlerTypeRegistered(MessageType.TEXT)); assertThat("session", session, Matchers.not(SessionMatchers.isMessageHandlerTypeRegistered(MessageType.PONG))); @@ -121,7 +120,7 @@ public class SessionAddMessageHandlerTest Matchers.hasItem( Matchers.allOf( SessionMatchers.isMessageHandlerType(session, MessageType.BINARY), - instanceOf(ByteArrayWholeHandler.class) + instanceOf(BinaryHandlers.ByteArrayWholeHandler.class) ) ) ); @@ -130,7 +129,7 @@ public class SessionAddMessageHandlerTest Matchers.hasItem( Matchers.allOf( SessionMatchers.isMessageHandlerType(session, MessageType.TEXT), - instanceOf(StringWholeHandler.class) + instanceOf(TextHandlers.StringWholeHandler.class) ) ) ); @@ -139,9 +138,9 @@ public class SessionAddMessageHandlerTest @Test public void testMessageHandlerReplaceTextHandler() { - MessageHandler strHandler = new StringWholeHandler(); + MessageHandler strHandler = new TextHandlers.StringWholeHandler(); session.addMessageHandler(strHandler); // add a TEXT handler - session.addMessageHandler(new ByteArrayWholeHandler()); // add BINARY handler + session.addMessageHandler(new BinaryHandlers.ByteArrayWholeHandler()); // add BINARY handler session.removeMessageHandler(strHandler); // remove original TEXT handler session.addMessageHandler(new LongMessageHandler()); // add new TEXT handler @@ -154,7 +153,7 @@ public class SessionAddMessageHandlerTest Matchers.hasItem( Matchers.allOf( SessionMatchers.isMessageHandlerType(session, MessageType.BINARY), - instanceOf(ByteArrayWholeHandler.class) + instanceOf(BinaryHandlers.ByteArrayWholeHandler.class) ) ) ); @@ -177,7 +176,7 @@ public class SessionAddMessageHandlerTest MessageHandler.Whole lamdaHandler = (msg) -> received.add(msg); session.addMessageHandler(String.class, lamdaHandler); // add a TEXT handler lambda - session.addMessageHandler(new ByteArrayWholeHandler()); // add BINARY handler + session.addMessageHandler(new BinaryHandlers.ByteArrayWholeHandler()); // add BINARY handler session.removeMessageHandler(lamdaHandler); // remove original TEXT handler assertThat("session", session, SessionMatchers.isMessageHandlerTypeRegistered(MessageType.BINARY)); @@ -189,7 +188,7 @@ public class SessionAddMessageHandlerTest Matchers.hasItem( Matchers.allOf( SessionMatchers.isMessageHandlerType(session, MessageType.BINARY), - instanceOf(ByteArrayWholeHandler.class) + instanceOf(BinaryHandlers.ByteArrayWholeHandler.class) ) ) ); @@ -198,7 +197,7 @@ public class SessionAddMessageHandlerTest @Test public void testMessageHandlerText() { - session.addMessageHandler(new StringWholeHandler()); + session.addMessageHandler(new TextHandlers.StringWholeHandler()); assertThat("session", session, Matchers.not(SessionMatchers.isMessageHandlerTypeRegistered(MessageType.BINARY))); assertThat("session", session, SessionMatchers.isMessageHandlerTypeRegistered(MessageType.TEXT)); @@ -209,7 +208,7 @@ public class SessionAddMessageHandlerTest Matchers.hasItem( Matchers.allOf( SessionMatchers.isMessageHandlerType(session, MessageType.TEXT), - instanceOf(StringWholeHandler.class) + instanceOf(TextHandlers.StringWholeHandler.class) ) ) ); diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/AbstractAnnotatedHandler.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/AbstractAnnotatedHandler.java new file mode 100644 index 00000000000..919461ccf22 --- /dev/null +++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/AbstractAnnotatedHandler.java @@ -0,0 +1,69 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.websocket.javax.tests.handlers; + +import java.io.IOException; +import java.nio.ByteBuffer; +import javax.websocket.EndpointConfig; +import javax.websocket.OnError; +import javax.websocket.OnOpen; +import javax.websocket.Session; +import javax.websocket.server.ServerEndpoint; + +@ServerEndpoint("/") +public class AbstractAnnotatedHandler +{ + protected Session _session; + + @OnOpen + public void onOpen(Session session, EndpointConfig config) + { + _session = session; + } + + @OnError + public void onError(Session session, Throwable thr) + { + thr.printStackTrace(); + } + + public void sendText(String message, boolean last) + { + try + { + _session.getBasicRemote().sendText(message, last); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + + public void sendBinary(ByteBuffer message, boolean last) + { + try + { + _session.getBasicRemote().sendBinary(message, last); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } +} diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/AbstractHandler.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/AbstractHandler.java new file mode 100644 index 00000000000..5b31c930106 --- /dev/null +++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/AbstractHandler.java @@ -0,0 +1,68 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.websocket.javax.tests.handlers; + +import java.io.IOException; +import java.nio.ByteBuffer; +import javax.websocket.Endpoint; +import javax.websocket.EndpointConfig; +import javax.websocket.MessageHandler; +import javax.websocket.Session; + +public class AbstractHandler extends Endpoint implements MessageHandler +{ + protected Session _session; + + @Override + public void onOpen(Session session, EndpointConfig config) + { + _session = session; + _session.addMessageHandler(this); + } + + @Override + public void onError(Session session, Throwable thr) + { + thr.printStackTrace(); + } + + public void sendText(String message, boolean last) + { + try + { + _session.getBasicRemote().sendText(message, last); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + + public void sendBinary(ByteBuffer message, boolean last) + { + try + { + _session.getBasicRemote().sendBinary(message, last); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } +} diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/BaseMessageHandler.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/BaseMessageHandler.java index 1df281c32f4..c4452ff38f0 100644 --- a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/BaseMessageHandler.java +++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/BaseMessageHandler.java @@ -20,11 +20,11 @@ package org.eclipse.jetty.websocket.javax.tests.handlers; import javax.websocket.MessageHandler; -public class BaseMessageHandler implements MessageHandler.Whole +public class BaseMessageHandler extends AbstractHandler implements MessageHandler.Whole { @Override public void onMessage(String message) { - // TODO Auto-generated method stub + sendText(message, true); } } diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/BinaryHandlers.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/BinaryHandlers.java new file mode 100644 index 00000000000..de3be71ced2 --- /dev/null +++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/BinaryHandlers.java @@ -0,0 +1,169 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.websocket.javax.tests.handlers; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.util.stream.Stream; +import javax.websocket.MessageHandler; +import javax.websocket.OnMessage; +import javax.websocket.Session; +import javax.websocket.server.ServerEndpoint; + +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.IO; +import org.junit.jupiter.params.provider.Arguments; + +public class BinaryHandlers +{ + public static Stream getBinaryHandlers() + { + return Stream.of( + ByteArrayWholeHandler.class, + ByteArrayPartialHandler.class, + ByteBufferWholeHandler.class, + ByteBufferPartialHandler.class, + InputStreamWholeHandler.class, + AnnotatedByteBufferWholeHandler.class, + AnnotatedByteBufferPartialHandler.class, + AnnotatedByteArrayWholeHandler.class, + AnnotatedByteArrayPartialHandler.class, + AnnotatedInputStreamWholeHandler.class, + AnnotatedReverseArgumentPartialHandler.class + ).map(Arguments::of); + } + + public static class ByteArrayWholeHandler extends AbstractHandler implements MessageHandler.Whole + { + @Override + public void onMessage(byte[] message) + { + sendBinary(BufferUtil.toBuffer(message), true); + } + } + + public static class ByteArrayPartialHandler extends AbstractHandler implements MessageHandler.Partial + { + @Override + public void onMessage(byte[] partialMessage, boolean last) + { + sendBinary(BufferUtil.toBuffer(partialMessage), last); + } + } + + public static class ByteBufferWholeHandler extends AbstractHandler implements MessageHandler.Whole + { + @Override + public void onMessage(ByteBuffer message) + { + sendBinary(message, true); + } + } + + public static class ByteBufferPartialHandler extends AbstractHandler implements MessageHandler.Partial + { + @Override + public void onMessage(ByteBuffer partialMessage, boolean last) + { + sendBinary(partialMessage, last); + } + } + + public static class InputStreamWholeHandler extends AbstractHandler implements MessageHandler.Whole + { + @Override + public void onMessage(InputStream stream) + { + sendBinary(readBytes(stream), true); + } + } + + @ServerEndpoint("/") + public static class AnnotatedByteBufferWholeHandler extends AbstractAnnotatedHandler + { + @OnMessage + public void onMessage(ByteBuffer message) + { + sendBinary(message, true); + } + } + + @ServerEndpoint("/") + public static class AnnotatedByteBufferPartialHandler extends AbstractAnnotatedHandler + { + @OnMessage + public void onMessage(ByteBuffer message, boolean last) + { + sendBinary(message, last); + } + } + + @ServerEndpoint("/") + public static class AnnotatedByteArrayWholeHandler extends AbstractAnnotatedHandler + { + @OnMessage + public void onMessage(byte[] message) + { + sendBinary(BufferUtil.toBuffer(message), true); + } + } + + @ServerEndpoint("/") + public static class AnnotatedByteArrayPartialHandler extends AbstractAnnotatedHandler + { + @OnMessage + public void onMessage(byte[] message, boolean last) + { + sendBinary(BufferUtil.toBuffer(message), last); + } + } + + @ServerEndpoint("/") + public static class AnnotatedInputStreamWholeHandler extends AbstractAnnotatedHandler + { + @OnMessage + public void onMessage(InputStream stream) + { + sendBinary(readBytes(stream), true); + } + } + + @ServerEndpoint("/") + public static class AnnotatedReverseArgumentPartialHandler extends AbstractAnnotatedHandler + { + @OnMessage + public void onMessage(boolean last, Session session, byte[] message) + { + sendBinary(BufferUtil.toBuffer(message), last); + } + } + + private static ByteBuffer readBytes(InputStream stream) + { + try + { + return BufferUtil.toBuffer(IO.readBytes(stream)); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } +} diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/ByteArrayPartialHandler.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/ByteArrayPartialHandler.java deleted file mode 100644 index 32f9bc222e9..00000000000 --- a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/ByteArrayPartialHandler.java +++ /dev/null @@ -1,30 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under -// the terms of the Eclipse Public License 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0 -// -// This Source Code may also be made available under the following -// Secondary Licenses when the conditions for such availability set -// forth in the Eclipse Public License, v. 2.0 are satisfied: -// the Apache License v2.0 which is available at -// https://www.apache.org/licenses/LICENSE-2.0 -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package org.eclipse.jetty.websocket.javax.tests.handlers; - -import javax.websocket.MessageHandler; - -public class ByteArrayPartialHandler implements MessageHandler.Partial -{ - @Override - public void onMessage(byte[] partialMessage, boolean last) - { - // TODO Auto-generated method stub - } -} diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/ByteArrayWholeHandler.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/ByteArrayWholeHandler.java deleted file mode 100644 index 9c93050a373..00000000000 --- a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/ByteArrayWholeHandler.java +++ /dev/null @@ -1,30 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under -// the terms of the Eclipse Public License 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0 -// -// This Source Code may also be made available under the following -// Secondary Licenses when the conditions for such availability set -// forth in the Eclipse Public License, v. 2.0 are satisfied: -// the Apache License v2.0 which is available at -// https://www.apache.org/licenses/LICENSE-2.0 -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package org.eclipse.jetty.websocket.javax.tests.handlers; - -import javax.websocket.MessageHandler; - -public class ByteArrayWholeHandler implements MessageHandler.Whole -{ - @Override - public void onMessage(byte[] message) - { - // TODO Auto-generated method stub - } -} diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/ByteBufferPartialHandler.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/ByteBufferPartialHandler.java deleted file mode 100644 index f203e9a0cd9..00000000000 --- a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/ByteBufferPartialHandler.java +++ /dev/null @@ -1,30 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under -// the terms of the Eclipse Public License 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0 -// -// This Source Code may also be made available under the following -// Secondary Licenses when the conditions for such availability set -// forth in the Eclipse Public License, v. 2.0 are satisfied: -// the Apache License v2.0 which is available at -// https://www.apache.org/licenses/LICENSE-2.0 -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package org.eclipse.jetty.websocket.javax.tests.handlers; - -import java.nio.ByteBuffer; -import javax.websocket.MessageHandler; - -public class ByteBufferPartialHandler implements MessageHandler.Partial -{ - @Override - public void onMessage(ByteBuffer partialMessage, boolean last) - { - } -} diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/ByteBufferWholeHandler.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/ByteBufferWholeHandler.java deleted file mode 100644 index 4e350776b28..00000000000 --- a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/ByteBufferWholeHandler.java +++ /dev/null @@ -1,31 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under -// the terms of the Eclipse Public License 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0 -// -// This Source Code may also be made available under the following -// Secondary Licenses when the conditions for such availability set -// forth in the Eclipse Public License, v. 2.0 are satisfied: -// the Apache License v2.0 which is available at -// https://www.apache.org/licenses/LICENSE-2.0 -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package org.eclipse.jetty.websocket.javax.tests.handlers; - -import java.nio.ByteBuffer; -import javax.websocket.MessageHandler; - -public class ByteBufferWholeHandler implements MessageHandler.Whole -{ - @Override - public void onMessage(ByteBuffer message) - { - // TODO Auto-generated method stub - } -} diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/ComboMessageHandler.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/ComboMessageHandler.java index a266e5a5c1d..9067fed437d 100644 --- a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/ComboMessageHandler.java +++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/ComboMessageHandler.java @@ -24,17 +24,17 @@ import javax.websocket.MessageHandler; /** * A particularly annoying type of MessageHandler. One defining 2 implementations. */ -public class ComboMessageHandler implements MessageHandler.Whole, MessageHandler.Partial +public class ComboMessageHandler extends AbstractHandler implements MessageHandler.Whole, MessageHandler.Partial { @Override public void onMessage(ByteBuffer partialMessage, boolean last) { - // TODO Auto-generated method stub + sendBinary(partialMessage, last); } @Override public void onMessage(String message) { - // TODO Auto-generated method stub + sendText(message, true); } } diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/ExtendedMessageHandler.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/ExtendedMessageHandler.java index 9cb1f749d85..76c10824fc0 100644 --- a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/ExtendedMessageHandler.java +++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/ExtendedMessageHandler.java @@ -26,6 +26,6 @@ public class ExtendedMessageHandler extends BaseMessageHandler implements Messag @Override public void onMessage(ByteBuffer partialMessage, boolean last) { - // TODO Auto-generated method stub + sendBinary(partialMessage, last); } } diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/InputStreamWholeHandler.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/InputStreamWholeHandler.java deleted file mode 100644 index 34c5af063b8..00000000000 --- a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/InputStreamWholeHandler.java +++ /dev/null @@ -1,31 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under -// the terms of the Eclipse Public License 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0 -// -// This Source Code may also be made available under the following -// Secondary Licenses when the conditions for such availability set -// forth in the Eclipse Public License, v. 2.0 are satisfied: -// the Apache License v2.0 which is available at -// https://www.apache.org/licenses/LICENSE-2.0 -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package org.eclipse.jetty.websocket.javax.tests.handlers; - -import java.io.InputStream; -import javax.websocket.MessageHandler; - -public class InputStreamWholeHandler implements MessageHandler.Whole -{ - @Override - public void onMessage(InputStream stream) - { - // TODO Auto-generated method stub - } -} diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/LongMessageHandler.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/LongMessageHandler.java index f1155847855..46d1500997c 100644 --- a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/LongMessageHandler.java +++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/LongMessageHandler.java @@ -20,10 +20,11 @@ package org.eclipse.jetty.websocket.javax.tests.handlers; import javax.websocket.MessageHandler; -public class LongMessageHandler implements MessageHandler.Whole +public class LongMessageHandler extends AbstractHandler implements MessageHandler.Whole { @Override public void onMessage(Long message) { + sendText(message.toString(), true); } } diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/MessageHandlerTest.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/MessageHandlerTest.java new file mode 100644 index 00000000000..92a4db28a11 --- /dev/null +++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/MessageHandlerTest.java @@ -0,0 +1,167 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.websocket.javax.tests.handlers; + +import java.net.URI; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.websocket.CloseReason; +import javax.websocket.ContainerProvider; +import javax.websocket.Session; +import javax.websocket.WebSocketContainer; +import javax.websocket.server.ServerEndpointConfig; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.component.LifeCycle; +import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer; +import org.eclipse.jetty.websocket.javax.tests.EventSocket; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import static javax.websocket.CloseReason.CloseCodes.NORMAL_CLOSURE; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class MessageHandlerTest +{ + private Server server; + private URI serverUri; + private WebSocketContainer client; + + private static Stream getBinaryHandlers() + { + return Stream.concat(BinaryHandlers.getBinaryHandlers(), + Stream.of(ComboMessageHandler.class, ExtendedMessageHandler.class).map(Arguments::of)); + } + + private static Stream getTextHandlers() + { + return Stream.concat(TextHandlers.getTextHandlers(), + Stream.of(ComboMessageHandler.class, ExtendedMessageHandler.class).map(Arguments::of)); + } + + @BeforeEach + public void before() throws Exception + { + server = new Server(); + ServerConnector connector = new ServerConnector(server); + server.addConnector(connector); + + ServletContextHandler contextHandler = new ServletContextHandler(); + contextHandler.setContextPath("/"); + JavaxWebSocketServletContainerInitializer.configure(contextHandler, (context, container) -> + { + Stream argumentsStream = Stream.concat(getBinaryHandlers(), getTextHandlers()); + for (Class c : getClassListFromArguments(argumentsStream)) + { + container.addEndpoint(ServerEndpointConfig.Builder.create(c, "/" + c.getSimpleName()).build()); + } + + container.addEndpoint(ServerEndpointConfig.Builder.create(LongMessageHandler.class, + "/" + LongMessageHandler.class.getSimpleName()).build()); + }); + + server.setHandler(contextHandler); + server.start(); + serverUri = URI.create("ws://localhost:" + connector.getLocalPort() + "/"); + client = ContainerProvider.getWebSocketContainer(); + } + + @AfterEach + public void after() throws Exception + { + LifeCycle.stop(client); + server.stop(); + } + + @ParameterizedTest + @MethodSource("getBinaryHandlers") + public void testBinaryHandlers(Class clazz) throws Exception + { + EventSocket clientEndpoint = new EventSocket(); + Session session = client.connectToServer(clientEndpoint, serverUri.resolve(clazz.getSimpleName())); + + // Send and receive echo on client. + ByteBuffer payload = BufferUtil.toBuffer("hello world"); + session.getBasicRemote().sendBinary(payload); + ByteBuffer echoMessage = clientEndpoint.binaryMessages.poll(5, TimeUnit.SECONDS); + assertThat(echoMessage, is(payload)); + + // Close normally. + session.close(new CloseReason(NORMAL_CLOSURE, "standard close")); + assertTrue(clientEndpoint.closeLatch.await(5, TimeUnit.SECONDS)); + assertThat(clientEndpoint.closeReason.getCloseCode(), is(NORMAL_CLOSURE)); + assertThat(clientEndpoint.closeReason.getReasonPhrase(), is("standard close")); + } + + @ParameterizedTest + @MethodSource("getTextHandlers") + public void testTextHandlers(Class clazz) throws Exception + { + EventSocket clientEndpoint = new EventSocket(); + Session session = client.connectToServer(clientEndpoint, serverUri.resolve(clazz.getSimpleName())); + + // Send and receive echo on client. + String payload = "hello world"; + session.getBasicRemote().sendText(payload); + String echoMessage = clientEndpoint.textMessages.poll(5, TimeUnit.SECONDS); + assertThat(echoMessage, is(payload)); + + // Close normally. + session.close(new CloseReason(NORMAL_CLOSURE, "standard close")); + assertTrue(clientEndpoint.closeLatch.await(5, TimeUnit.SECONDS)); + assertThat(clientEndpoint.closeReason.getCloseCode(), is(NORMAL_CLOSURE)); + assertThat(clientEndpoint.closeReason.getReasonPhrase(), is("standard close")); + } + + @Test + public void testLongDecoderHandler() throws Exception + { + EventSocket clientEndpoint = new EventSocket(); + Session session = client.connectToServer(clientEndpoint, serverUri.resolve(LongMessageHandler.class.getSimpleName())); + + // Send and receive echo on client. + String payload = Long.toString(Long.MAX_VALUE); + session.getBasicRemote().sendText(payload); + String echoMessage = clientEndpoint.textMessages.poll(5, TimeUnit.SECONDS); + assertThat(echoMessage, is(payload)); + + // Close normally. + session.close(new CloseReason(NORMAL_CLOSURE, "standard close")); + assertTrue(clientEndpoint.closeLatch.await(5, TimeUnit.SECONDS)); + assertThat(clientEndpoint.closeReason.getCloseCode(), is(NORMAL_CLOSURE)); + assertThat(clientEndpoint.closeReason.getReasonPhrase(), is("standard close")); + } + + private List> getClassListFromArguments(Stream stream) + { + return stream.map(arguments -> (Class)arguments.get()[0]).collect(Collectors.toList()); + } +} diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/ReaderWholeHandler.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/ReaderWholeHandler.java deleted file mode 100644 index d8637aa7080..00000000000 --- a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/ReaderWholeHandler.java +++ /dev/null @@ -1,31 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under -// the terms of the Eclipse Public License 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0 -// -// This Source Code may also be made available under the following -// Secondary Licenses when the conditions for such availability set -// forth in the Eclipse Public License, v. 2.0 are satisfied: -// the Apache License v2.0 which is available at -// https://www.apache.org/licenses/LICENSE-2.0 -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package org.eclipse.jetty.websocket.javax.tests.handlers; - -import java.io.Reader; -import javax.websocket.MessageHandler; - -public class ReaderWholeHandler implements MessageHandler.Whole -{ - @Override - public void onMessage(Reader reader) - { - // TODO Auto-generated method stub - } -} diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/StringPartialHandler.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/StringPartialHandler.java deleted file mode 100644 index d614d282a1f..00000000000 --- a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/StringPartialHandler.java +++ /dev/null @@ -1,30 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under -// the terms of the Eclipse Public License 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0 -// -// This Source Code may also be made available under the following -// Secondary Licenses when the conditions for such availability set -// forth in the Eclipse Public License, v. 2.0 are satisfied: -// the Apache License v2.0 which is available at -// https://www.apache.org/licenses/LICENSE-2.0 -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package org.eclipse.jetty.websocket.javax.tests.handlers; - -import javax.websocket.MessageHandler; - -public class StringPartialHandler implements MessageHandler.Partial -{ - @Override - public void onMessage(String partialMessage, boolean last) - { - // TODO Auto-generated method stub - } -} diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/StringWholeHandler.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/StringWholeHandler.java deleted file mode 100644 index 2f072ebc9bb..00000000000 --- a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/StringWholeHandler.java +++ /dev/null @@ -1,30 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under -// the terms of the Eclipse Public License 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0 -// -// This Source Code may also be made available under the following -// Secondary Licenses when the conditions for such availability set -// forth in the Eclipse Public License, v. 2.0 are satisfied: -// the Apache License v2.0 which is available at -// https://www.apache.org/licenses/LICENSE-2.0 -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package org.eclipse.jetty.websocket.javax.tests.handlers; - -import javax.websocket.MessageHandler; - -public class StringWholeHandler implements MessageHandler.Whole -{ - @Override - public void onMessage(String message) - { - // TODO Auto-generated method stub - } -} diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/TextHandlers.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/TextHandlers.java new file mode 100644 index 00000000000..b5badb5ec0a --- /dev/null +++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/handlers/TextHandlers.java @@ -0,0 +1,125 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.websocket.javax.tests.handlers; + +import java.io.IOException; +import java.io.Reader; +import java.util.stream.Stream; +import javax.websocket.MessageHandler; +import javax.websocket.OnMessage; +import javax.websocket.Session; +import javax.websocket.server.ServerEndpoint; + +import org.eclipse.jetty.util.IO; +import org.junit.jupiter.params.provider.Arguments; + +public class TextHandlers +{ + public static Stream getTextHandlers() + { + return Stream.of( + StringWholeHandler.class, + StringPartialHandler.class, + ReaderWholeHandler.class, + AnnotatedStringWholeHandler.class, + AnnotatedStringPartialHandler.class, + AnnotatedReaderHandler.class, + AnnotatedReverseArgumentsPartialHandler.class + ).map(Arguments::of); + } + + public static class StringWholeHandler extends AbstractHandler implements MessageHandler.Whole + { + @Override + public void onMessage(String message) + { + sendText(message, true); + } + } + + public static class StringPartialHandler extends AbstractHandler implements MessageHandler.Partial + { + @Override + public void onMessage(String partialMessage, boolean last) + { + sendText(partialMessage, last); + } + } + + public static class ReaderWholeHandler extends AbstractHandler implements MessageHandler.Whole + { + @Override + public void onMessage(Reader reader) + { + sendText(readString(reader), true); + } + } + + @ServerEndpoint("/") + public static class AnnotatedStringWholeHandler extends AbstractAnnotatedHandler + { + @OnMessage + public void onMessage(String message) + { + sendText(message, true); + } + } + + @ServerEndpoint("/") + public static class AnnotatedStringPartialHandler extends AbstractAnnotatedHandler + { + @OnMessage + public void onMessage(String message, boolean last) + { + sendText(message, last); + } + } + + @ServerEndpoint("/") + public static class AnnotatedReaderHandler extends AbstractAnnotatedHandler + { + @OnMessage + public void onMessage(Reader reader) + { + sendText(readString(reader), true); + } + } + + @ServerEndpoint("/") + public static class AnnotatedReverseArgumentsPartialHandler extends AbstractAnnotatedHandler + { + @OnMessage + public void onMessage(boolean last, String message, Session session) + { + sendText(message, last); + } + } + + private static String readString(Reader reader) + { + try + { + return IO.toString(reader); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } +} diff --git a/jetty-websocket/websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketConnectionListener.java b/jetty-websocket/websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketConnectionListener.java index 7dac7419859..fd98cae3cfe 100644 --- a/jetty-websocket/websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketConnectionListener.java +++ b/jetty-websocket/websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketConnectionListener.java @@ -31,7 +31,9 @@ public interface WebSocketConnectionListener * @param statusCode the close status code. (See {@link StatusCode}) * @param reason the optional reason for the close. */ - void onWebSocketClose(int statusCode, String reason); + default void onWebSocketClose(int statusCode, String reason) + { + } /** * A WebSocket {@link Session} has connected successfully and is ready to be used. @@ -40,7 +42,9 @@ public interface WebSocketConnectionListener * * @param session the websocket session. */ - void onWebSocketConnect(Session session); + default void onWebSocketConnect(Session session) + { + } /** * A WebSocket exception has occurred. @@ -53,5 +57,7 @@ public interface WebSocketConnectionListener * * @param cause the error that occurred. */ - void onWebSocketError(Throwable cause); + default void onWebSocketError(Throwable cause) + { + } } diff --git a/jetty-websocket/websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketListener.java b/jetty-websocket/websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketListener.java index 8f11b97c375..ec3b059ceac 100644 --- a/jetty-websocket/websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketListener.java +++ b/jetty-websocket/websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketListener.java @@ -30,12 +30,16 @@ public interface WebSocketListener extends WebSocketConnectionListener * @param offset the offset in the payload array where the data starts * @param len the length of bytes in the payload */ - void onWebSocketBinary(byte[] payload, int offset, int len); + default void onWebSocketBinary(byte[] payload, int offset, int len) + { + } /** * A WebSocket Text frame was received. * * @param message the message */ - void onWebSocketText(String message); + default void onWebSocketText(String message) + { + } } diff --git a/jetty-websocket/websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketPartialListener.java b/jetty-websocket/websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketPartialListener.java index 26b4cc0a830..ec676b7387f 100644 --- a/jetty-websocket/websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketPartialListener.java +++ b/jetty-websocket/websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketPartialListener.java @@ -35,7 +35,9 @@ public interface WebSocketPartialListener extends WebSocketConnectionListener * @param payload the binary message frame payload * @param fin true if this is the final frame, false otherwise */ - void onWebSocketPartialBinary(ByteBuffer payload, boolean fin); + default void onWebSocketPartialBinary(ByteBuffer payload, boolean fin) + { + } /** * A WebSocket TEXT (or associated CONTINUATION) frame has been received. @@ -50,5 +52,7 @@ public interface WebSocketPartialListener extends WebSocketConnectionListener * will be held over until the next frame is received. * @param fin true if this is the final frame, false otherwise */ - void onWebSocketPartialText(String payload, boolean fin); + default void onWebSocketPartialText(String payload, boolean fin) + { + } } diff --git a/jetty-websocket/websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketPingPongListener.java b/jetty-websocket/websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketPingPongListener.java index dde0f7fb0ea..968ac27d937 100644 --- a/jetty-websocket/websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketPingPongListener.java +++ b/jetty-websocket/websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketPingPongListener.java @@ -30,12 +30,16 @@ public interface WebSocketPingPongListener extends WebSocketConnectionListener * * @param payload the ping payload */ - void onWebSocketPing(ByteBuffer payload); + default void onWebSocketPing(ByteBuffer payload) + { + } /** * A WebSocket PONG has been received. * * @param payload the pong payload */ - void onWebSocketPong(ByteBuffer payload); + default void onWebSocketPong(ByteBuffer payload) + { + } } diff --git a/jetty-websocket/websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/OnWebSocketMessage.java b/jetty-websocket/websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/OnWebSocketMessage.java index acbee3fbd3b..dc58cf9973e 100644 --- a/jetty-websocket/websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/OnWebSocketMessage.java +++ b/jetty-websocket/websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/OnWebSocketMessage.java @@ -35,9 +35,9 @@ import org.eclipse.jetty.websocket.api.Session; *

      * Text Message Versions *

        - *
      1. {@code public void methodName(String text)}
      2. + *
      3. public void methodName(String text)
      4. *
      5. public void methodName({@link Session} session, String text)
      6. - *
      7. {@code public void methodName(Reader reader)}
      8. + *
      9. public void methodName(Reader reader)
      10. *
      11. public void methodName({@link Session} session, Reader reader)
      12. *
      * Note: that the {@link Reader} in this case will always use UTF-8 encoding/charset (this is dictated by the RFC 6455 spec for Text Messages. If you need to @@ -45,11 +45,11 @@ import org.eclipse.jetty.websocket.api.Session; *

      * Binary Message Versions *

        - *
      1. {@code public void methodName(ByteBuffer message)}
      2. + *
      3. public void methodName(ByteBuffer message)
      4. *
      5. public void methodName({@link Session} session, ByteBuffer message)
      6. - *
      7. {@code public void methodName(byte buf[], int offset, int length)}
      8. + *
      9. public void methodName(byte buf[], int offset, int length)
      10. *
      11. public void methodName({@link Session} session, byte buf[], int offset, int length)
      12. - *
      13. {@code public void methodName(InputStream stream)}
      14. + *
      15. public void methodName(InputStream stream)
      16. *
      17. public void methodName({@link Session} session, InputStream stream)
      18. *
      */ diff --git a/jetty-websocket/websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandlerFactory.java b/jetty-websocket/websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandlerFactory.java index 13a22ab6d0f..0d08b373c4f 100644 --- a/jetty-websocket/websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandlerFactory.java +++ b/jetty-websocket/websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandlerFactory.java @@ -296,15 +296,9 @@ public class JettyWebSocketFrameHandlerFactory extends ContainerLifeCycle final InvokerUtils.Arg STATUS_CODE = new InvokerUtils.Arg(int.class); final InvokerUtils.Arg REASON = new InvokerUtils.Arg(String.class); MethodHandle methodHandle = InvokerUtils.mutatedInvoker(lookup, endpointClass, onmethod, SESSION, STATUS_CODE, REASON); - // TODO: need mutation of args? ... - // Session + CloseInfo -> - // setOnClose((closeInfo) ->{ - // args[0] = getSession(); - // args[1] = closeInfo.getStatusCode(); - // args[2] = closeInfo.getReason(); - // invoker.apply(endpoint, args); metadata.setCloseHandler(methodHandle, onmethod); } + // OnWebSocketError [0..1] onmethod = ReflectUtils.findAnnotatedMethod(endpointClass, OnWebSocketError.class); if (onmethod != null) @@ -360,7 +354,6 @@ public class JettyWebSocketFrameHandlerFactory extends ContainerLifeCycle new InvokerUtils.Arg(Reader.class).required() }; - onmessageloop: for (Method onMsg : onMessages) { assertSignatureValid(endpointClass, onMsg, OnWebSocketMessage.class); @@ -371,7 +364,7 @@ public class JettyWebSocketFrameHandlerFactory extends ContainerLifeCycle // Normal Text Message assertSignatureValid(endpointClass, onMsg, OnWebSocketMessage.class); metadata.setTextHandler(StringMessageSink.class, methodHandle, onMsg); - continue onmessageloop; + continue; } methodHandle = InvokerUtils.optionalMutatedInvoker(lookup, endpointClass, onMsg, binaryBufferCallingArgs); @@ -380,7 +373,7 @@ public class JettyWebSocketFrameHandlerFactory extends ContainerLifeCycle // ByteBuffer Binary Message assertSignatureValid(endpointClass, onMsg, OnWebSocketMessage.class); metadata.setBinaryHandle(ByteBufferMessageSink.class, methodHandle, onMsg); - continue onmessageloop; + continue; } methodHandle = InvokerUtils.optionalMutatedInvoker(lookup, endpointClass, onMsg, binaryArrayCallingArgs); @@ -389,7 +382,7 @@ public class JettyWebSocketFrameHandlerFactory extends ContainerLifeCycle // byte[] Binary Message assertSignatureValid(endpointClass, onMsg, OnWebSocketMessage.class); metadata.setBinaryHandle(ByteArrayMessageSink.class, methodHandle, onMsg); - continue onmessageloop; + continue; } methodHandle = InvokerUtils.optionalMutatedInvoker(lookup, endpointClass, onMsg, inputStreamCallingArgs); @@ -398,7 +391,7 @@ public class JettyWebSocketFrameHandlerFactory extends ContainerLifeCycle // InputStream Binary Message assertSignatureValid(endpointClass, onMsg, OnWebSocketMessage.class); metadata.setBinaryHandle(InputStreamMessageSink.class, methodHandle, onMsg); - continue onmessageloop; + continue; } methodHandle = InvokerUtils.optionalMutatedInvoker(lookup, endpointClass, onMsg, readerCallingArgs); @@ -407,7 +400,7 @@ public class JettyWebSocketFrameHandlerFactory extends ContainerLifeCycle // Reader Text Message assertSignatureValid(endpointClass, onMsg, OnWebSocketMessage.class); metadata.setTextHandler(ReaderMessageSink.class, methodHandle, onMsg); - continue onmessageloop; + continue; } else { diff --git a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/ConcurrentConnectTest.java b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/ConcurrentConnectTest.java index d0cb78f7884..4eddfa4c677 100644 --- a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/ConcurrentConnectTest.java +++ b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/ConcurrentConnectTest.java @@ -122,15 +122,15 @@ public class ConcurrentConnectTest for (EventSocket l : listeners) { l.session.getRemote().sendString("ping"); - assertThat(l.messageQueue.poll(5, TimeUnit.SECONDS), is("ping")); + assertThat(l.textMessages.poll(5, TimeUnit.SECONDS), is("ping")); l.session.close(StatusCode.NORMAL, "close from client"); } for (EventSocket l : listeners) { assertTrue(l.closeLatch.await(5, TimeUnit.SECONDS)); - assertThat(l.statusCode, is(StatusCode.NORMAL)); - assertThat(l.reason, is("close from client")); + assertThat(l.closeCode, is(StatusCode.NORMAL)); + assertThat(l.closeReason, is("close from client")); assertNull(l.error); } diff --git a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/EventSocket.java b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/EventSocket.java index 69d3fcb9e71..bffbe3825e5 100644 --- a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/EventSocket.java +++ b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/EventSocket.java @@ -42,10 +42,10 @@ public class EventSocket public Session session; private String behavior; - public BlockingQueue messageQueue = new BlockingArrayQueue<>(); - public BlockingQueue binaryMessageQueue = new BlockingArrayQueue<>(); - public volatile int statusCode = StatusCode.UNDEFINED; - public volatile String reason; + public BlockingQueue textMessages = new BlockingArrayQueue<>(); + public BlockingQueue binaryMessages = new BlockingArrayQueue<>(); + public volatile int closeCode = StatusCode.UNDEFINED; + public volatile String closeReason; public volatile Throwable error = null; public CountDownLatch openLatch = new CountDownLatch(1); @@ -67,7 +67,7 @@ public class EventSocket { if (LOG.isDebugEnabled()) LOG.debug("{} onMessage(): {}", toString(), message); - messageQueue.offer(message); + textMessages.offer(message); } @OnWebSocketMessage @@ -76,7 +76,7 @@ public class EventSocket ByteBuffer message = ByteBuffer.wrap(buf, offset, len); if (LOG.isDebugEnabled()) LOG.debug("{} onMessage(): {}", toString(), message); - binaryMessageQueue.offer(message); + binaryMessages.offer(message); } @OnWebSocketClose @@ -84,8 +84,8 @@ public class EventSocket { if (LOG.isDebugEnabled()) LOG.debug("{} onClose(): {}:{}", toString(), statusCode, reason); - this.statusCode = statusCode; - this.reason = reason; + this.closeCode = statusCode; + this.closeReason = reason; closeLatch.countDown(); } diff --git a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/JettyOnCloseTest.java b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/JettyOnCloseTest.java index 77b15837219..fb70a93f8ee 100644 --- a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/JettyOnCloseTest.java +++ b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/JettyOnCloseTest.java @@ -130,8 +130,8 @@ public class JettyOnCloseTest clientEndpoint.session.close(); assertTrue(clientEndpoint.closeLatch.await(5, TimeUnit.SECONDS)); - assertThat(clientEndpoint.statusCode, is(StatusCode.SERVICE_RESTART)); - assertThat(clientEndpoint.reason, is("custom close reason")); + assertThat(clientEndpoint.closeCode, is(StatusCode.SERVICE_RESTART)); + assertThat(clientEndpoint.closeReason, is("custom close reason")); } @Test @@ -146,8 +146,8 @@ public class JettyOnCloseTest serverEndpoint.session.close(StatusCode.NORMAL, "first close"); assertTrue(clientEndpoint.closeLatch.await(5, TimeUnit.SECONDS)); - assertThat(clientEndpoint.statusCode, is(StatusCode.NORMAL)); - assertThat(clientEndpoint.reason, is("first close")); + assertThat(clientEndpoint.closeCode, is(StatusCode.NORMAL)); + assertThat(clientEndpoint.closeReason, is("first close")); } @Test @@ -166,8 +166,8 @@ public class JettyOnCloseTest serverEndpoint.session.close(StatusCode.PROTOCOL, "abnormal close 1"); assertTrue(clientEndpoint.closeLatch.await(5, TimeUnit.SECONDS)); - assertThat(clientEndpoint.statusCode, is(StatusCode.PROTOCOL)); - assertThat(clientEndpoint.reason, is("abnormal close 1")); + assertThat(clientEndpoint.closeCode, is(StatusCode.PROTOCOL)); + assertThat(clientEndpoint.closeReason, is("abnormal close 1")); } @Test @@ -185,8 +185,8 @@ public class JettyOnCloseTest clientEndpoint.session.close(); assertTrue(clientEndpoint.closeLatch.await(5, TimeUnit.SECONDS)); - assertThat(clientEndpoint.statusCode, is(StatusCode.SERVER_ERROR)); - assertThat(clientEndpoint.reason, containsString("trigger onError from onClose")); + assertThat(clientEndpoint.closeCode, is(StatusCode.SERVER_ERROR)); + assertThat(clientEndpoint.closeReason, containsString("trigger onError from onClose")); assertTrue(serverEndpoint.errorLatch.await(5, TimeUnit.SECONDS)); assertThat(serverEndpoint.error, instanceOf(RuntimeException.class)); diff --git a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/JettyWebSocketExtensionConfigTest.java b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/JettyWebSocketExtensionConfigTest.java index 620a59ee419..9b2a4cc74a0 100644 --- a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/JettyWebSocketExtensionConfigTest.java +++ b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/JettyWebSocketExtensionConfigTest.java @@ -130,7 +130,7 @@ public class JettyWebSocketExtensionConfigTest assertTrue(socket.closeLatch.await(5, TimeUnit.SECONDS)); assertTrue(correctResponseExtensions.await(5, TimeUnit.SECONDS)); - String msg = socket.messageQueue.poll(); + String msg = socket.textMessages.poll(); assertThat(msg, is("hello world")); } } diff --git a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/JettyWebSocketFilterTest.java b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/JettyWebSocketFilterTest.java index 2543e78f4c4..488eb796a9b 100644 --- a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/JettyWebSocketFilterTest.java +++ b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/JettyWebSocketFilterTest.java @@ -80,7 +80,7 @@ public class JettyWebSocketFilterTest } assertTrue(socket.closeLatch.await(10, TimeUnit.SECONDS)); - String msg = socket.messageQueue.poll(); + String msg = socket.textMessages.poll(); assertThat(msg, is("hello world")); } } diff --git a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/JettyWebSocketServletTest.java b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/JettyWebSocketServletTest.java index 2bf60c8dd27..926ecd0bdd3 100644 --- a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/JettyWebSocketServletTest.java +++ b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/JettyWebSocketServletTest.java @@ -92,7 +92,7 @@ public class JettyWebSocketServletTest } assertTrue(socket.closeLatch.await(10, TimeUnit.SECONDS)); - String msg = socket.messageQueue.poll(); + String msg = socket.textMessages.poll(); assertThat(msg, is("hello world")); } } diff --git a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/SuspendResumeTest.java b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/SuspendResumeTest.java index 90556e1823e..e919baada8f 100644 --- a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/SuspendResumeTest.java +++ b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/SuspendResumeTest.java @@ -110,16 +110,16 @@ public class SuspendResumeTest clientSocket.session.getRemote().sendString("suspend"); clientSocket.session.getRemote().sendString("hello world"); - assertThat(serverSocket.messageQueue.poll(5, TimeUnit.SECONDS), is("suspend")); - assertNull(serverSocket.messageQueue.poll(1, TimeUnit.SECONDS)); + assertThat(serverSocket.textMessages.poll(5, TimeUnit.SECONDS), is("suspend")); + assertNull(serverSocket.textMessages.poll(1, TimeUnit.SECONDS)); serverSocket.suspendToken.resume(); - assertThat(serverSocket.messageQueue.poll(5, TimeUnit.SECONDS), is("suspend")); - assertNull(serverSocket.messageQueue.poll(1, TimeUnit.SECONDS)); + assertThat(serverSocket.textMessages.poll(5, TimeUnit.SECONDS), is("suspend")); + assertNull(serverSocket.textMessages.poll(1, TimeUnit.SECONDS)); serverSocket.suspendToken.resume(); - assertThat(serverSocket.messageQueue.poll(5, TimeUnit.SECONDS), is("hello world")); - assertNull(serverSocket.messageQueue.poll(1, TimeUnit.SECONDS)); + assertThat(serverSocket.textMessages.poll(5, TimeUnit.SECONDS), is("hello world")); + assertNull(serverSocket.textMessages.poll(1, TimeUnit.SECONDS)); // make sure both sides are closed clientSocket.session.close(); @@ -142,22 +142,22 @@ public class SuspendResumeTest // verify connection by sending a message from server to client assertTrue(serverSocket.openLatch.await(5, TimeUnit.SECONDS)); serverSocket.session.getRemote().sendString("verification"); - assertThat(clientSocket.messageQueue.poll(5, TimeUnit.SECONDS), is("verification")); + assertThat(clientSocket.textMessages.poll(5, TimeUnit.SECONDS), is("verification")); // suspend the client so that no read events occur SuspendToken suspendToken = clientSocket.session.suspend(); // verify client can still send messages clientSocket.session.getRemote().sendString("message-from-client"); - assertThat(serverSocket.messageQueue.poll(5, TimeUnit.SECONDS), is("message-from-client")); + assertThat(serverSocket.textMessages.poll(5, TimeUnit.SECONDS), is("message-from-client")); // the message is not received as it is suspended serverSocket.session.getRemote().sendString("message-from-server"); - assertNull(clientSocket.messageQueue.poll(2, TimeUnit.SECONDS)); + assertNull(clientSocket.textMessages.poll(2, TimeUnit.SECONDS)); // client should receive message after it resumes suspendToken.resume(); - assertThat(clientSocket.messageQueue.poll(5, TimeUnit.SECONDS), is("message-from-server")); + assertThat(clientSocket.textMessages.poll(5, TimeUnit.SECONDS), is("message-from-server")); // make sure both sides are closed clientSocket.session.close(); @@ -180,7 +180,7 @@ public class SuspendResumeTest // verify connection by sending a message from server to client assertTrue(serverSocket.openLatch.await(5, TimeUnit.SECONDS)); serverSocket.session.getRemote().sendString("verification"); - assertThat(clientSocket.messageQueue.poll(5, TimeUnit.SECONDS), is("verification")); + assertThat(clientSocket.textMessages.poll(5, TimeUnit.SECONDS), is("verification")); // make sure both sides are closed clientSocket.session.close(); diff --git a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketOverHTTP2Test.java b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketOverHTTP2Test.java index cd590d1507f..aad53455047 100644 --- a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketOverHTTP2Test.java +++ b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketOverHTTP2Test.java @@ -167,13 +167,13 @@ public class WebSocketOverHTTP2Test String text = "websocket"; session.getRemote().sendString(text); - String message = wsEndPoint.messageQueue.poll(5, TimeUnit.SECONDS); + String message = wsEndPoint.textMessages.poll(5, TimeUnit.SECONDS); assertNotNull(message); assertEquals(text, message); session.close(StatusCode.NORMAL, null); assertTrue(wsEndPoint.closeLatch.await(5, TimeUnit.SECONDS)); - assertEquals(StatusCode.NORMAL, wsEndPoint.statusCode); + assertEquals(StatusCode.NORMAL, wsEndPoint.closeCode); assertNull(wsEndPoint.error); } @@ -230,7 +230,7 @@ public class WebSocketOverHTTP2Test String text = "websocket"; session.getRemote().sendString(text); - String message = wsEndPoint.messageQueue.poll(5, TimeUnit.SECONDS); + String message = wsEndPoint.textMessages.poll(5, TimeUnit.SECONDS); assertNotNull(message); assertEquals(text, message); diff --git a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketServletExamplesTest.java b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketServletExamplesTest.java index 4f309ab5cf0..547c5c396ae 100644 --- a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketServletExamplesTest.java +++ b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketServletExamplesTest.java @@ -123,7 +123,7 @@ public class WebSocketServletExamplesTest String message = "hello world"; session.getRemote().sendString(message); - String response = socket.messageQueue.poll(5, TimeUnit.SECONDS); + String response = socket.textMessages.poll(5, TimeUnit.SECONDS); assertThat(response, is(message)); } @@ -147,7 +147,7 @@ public class WebSocketServletExamplesTest String message = "hello world"; session.getRemote().sendString(message); - String response = socket.messageQueue.poll(5, TimeUnit.SECONDS); + String response = socket.textMessages.poll(5, TimeUnit.SECONDS); assertThat(response, is(message)); } @@ -173,7 +173,7 @@ public class WebSocketServletExamplesTest String message = "hello world"; session.getRemote().sendString(message); - String response = socket.messageQueue.poll(5, TimeUnit.SECONDS); + String response = socket.textMessages.poll(5, TimeUnit.SECONDS); assertThat(response, is(message)); } diff --git a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketStopTest.java b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketStopTest.java index 43ffc5e687f..914ddd1ae2b 100644 --- a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketStopTest.java +++ b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketStopTest.java @@ -102,8 +102,8 @@ public class WebSocketStopTest client.stop(); assertTrue(clientSocket1.closeLatch.await(5, TimeUnit.SECONDS)); assertTrue(clientSocket2.closeLatch.await(5, TimeUnit.SECONDS)); - assertThat(clientSocket1.statusCode, is(StatusCode.SHUTDOWN)); - assertThat(clientSocket2.statusCode, is(StatusCode.SHUTDOWN)); + assertThat(clientSocket1.closeCode, is(StatusCode.SHUTDOWN)); + assertThat(clientSocket2.closeCode, is(StatusCode.SHUTDOWN)); } @Test @@ -116,7 +116,7 @@ public class WebSocketStopTest upgradeRequest.addExtensions("permessage-deflate"); Session session = client.connect(clientSocket, uri, upgradeRequest).get(5, TimeUnit.SECONDS); clientSocket.session.getRemote().sendString("init deflater"); - assertThat(serverSocket.messageQueue.poll(5, TimeUnit.SECONDS), is("init deflater")); + assertThat(serverSocket.textMessages.poll(5, TimeUnit.SECONDS), is("init deflater")); session.close(StatusCode.NORMAL, null); // make sure both sides are closed @@ -125,8 +125,8 @@ public class WebSocketStopTest assertTrue(serverSocket.closeLatch.await(5, TimeUnit.SECONDS)); // check we closed normally - assertThat(clientSocket.statusCode, is(StatusCode.NORMAL)); - assertThat(serverSocket.statusCode, is(StatusCode.NORMAL)); + assertThat(clientSocket.closeCode, is(StatusCode.NORMAL)); + assertThat(serverSocket.closeCode, is(StatusCode.NORMAL)); IOException error = assertThrows(IOException.class, () -> session.getRemote().sendString("this should fail before ExtensionStack")); diff --git a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/autobahn/JettyAutobahnClient.java b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/autobahn/JettyAutobahnClient.java index 662bbaa7bf5..916ab848b81 100644 --- a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/autobahn/JettyAutobahnClient.java +++ b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/autobahn/JettyAutobahnClient.java @@ -161,7 +161,7 @@ public class JettyAutobahnClient if (waitForUpgrade(wsUri, response)) { - String msg = onCaseCount.messageQueue.poll(10, TimeUnit.SECONDS); + String msg = onCaseCount.textMessages.poll(10, TimeUnit.SECONDS); onCaseCount.session.close(StatusCode.SHUTDOWN, null); assertTrue(onCaseCount.closeLatch.await(2, TimeUnit.SECONDS)); assertNotNull(msg); diff --git a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientConfigTest.java b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientConfigTest.java index 96d1e240536..ba3446931d6 100644 --- a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientConfigTest.java +++ b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientConfigTest.java @@ -159,7 +159,7 @@ public class ClientConfigTest assertNull(clientEndpoint.error); assertTrue(serverSocket.closeLatch.await(5, TimeUnit.SECONDS)); - assertThat(serverSocket.statusCode, is(StatusCode.NO_CODE)); + assertThat(serverSocket.closeCode, is(StatusCode.NO_CODE)); } @ParameterizedTest @@ -177,7 +177,7 @@ public class ClientConfigTest assertThat(clientEndpoint.error, instanceOf(MessageTooLargeException.class)); assertTrue(serverSocket.closeLatch.await(5, TimeUnit.SECONDS)); - assertThat(serverSocket.statusCode, is(StatusCode.MESSAGE_TOO_LARGE)); + assertThat(serverSocket.closeCode, is(StatusCode.MESSAGE_TOO_LARGE)); } @ParameterizedTest @@ -196,7 +196,7 @@ public class ClientConfigTest assertThat(clientEndpoint.error, instanceOf(WebSocketTimeoutException.class)); assertTrue(serverSocket.closeLatch.await(5, TimeUnit.SECONDS)); - assertThat(serverSocket.statusCode, is(StatusCode.SHUTDOWN)); + assertThat(serverSocket.closeCode, is(StatusCode.SHUTDOWN)); } @ParameterizedTest @@ -214,6 +214,6 @@ public class ClientConfigTest assertThat(clientEndpoint.error, instanceOf(MessageTooLargeException.class)); assertTrue(serverSocket.closeLatch.await(5, TimeUnit.SECONDS)); - assertThat(serverSocket.statusCode, is(StatusCode.MESSAGE_TOO_LARGE)); + assertThat(serverSocket.closeCode, is(StatusCode.MESSAGE_TOO_LARGE)); } } diff --git a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/listeners/AbstractAnnotatedListener.java b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/listeners/AbstractAnnotatedListener.java new file mode 100644 index 00000000000..c28200ac88e --- /dev/null +++ b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/listeners/AbstractAnnotatedListener.java @@ -0,0 +1,69 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.websocket.tests.listeners; + +import java.io.IOException; +import java.nio.ByteBuffer; + +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError; +import org.eclipse.jetty.websocket.api.annotations.WebSocket; + +@WebSocket +public class AbstractAnnotatedListener +{ + protected Session _session; + + @OnWebSocketConnect + public void onWebSocketConnect(Session session) + { + _session = session; + } + + @OnWebSocketError + public void onWebSocketError(Throwable thr) + { + thr.printStackTrace(); + } + + public void sendText(String message, boolean last) + { + try + { + _session.getRemote().sendPartialString(message, last); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + + public void sendBinary(ByteBuffer message, boolean last) + { + try + { + _session.getRemote().sendPartialBytes(message, last); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } +} diff --git a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/listeners/AbstractListener.java b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/listeners/AbstractListener.java new file mode 100644 index 00000000000..d6b3784e970 --- /dev/null +++ b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/listeners/AbstractListener.java @@ -0,0 +1,66 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.websocket.tests.listeners; + +import java.io.IOException; +import java.nio.ByteBuffer; + +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.WebSocketConnectionListener; + +public class AbstractListener implements WebSocketConnectionListener +{ + protected Session _session; + + @Override + public void onWebSocketConnect(Session session) + { + _session = session; + } + + @Override + public void onWebSocketError(Throwable thr) + { + thr.printStackTrace(); + } + + public void sendText(String message, boolean last) + { + try + { + _session.getRemote().sendPartialString(message, last); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + + public void sendBinary(ByteBuffer message, boolean last) + { + try + { + _session.getRemote().sendPartialBytes(message, last); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } +} diff --git a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/listeners/BinaryListeners.java b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/listeners/BinaryListeners.java new file mode 100644 index 00000000000..7cc0a8850a7 --- /dev/null +++ b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/listeners/BinaryListeners.java @@ -0,0 +1,129 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.websocket.tests.listeners; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.util.stream.Stream; + +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.WebSocketListener; +import org.eclipse.jetty.websocket.api.WebSocketPartialListener; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; +import org.eclipse.jetty.websocket.api.annotations.WebSocket; +import org.junit.jupiter.params.provider.Arguments; + +public class BinaryListeners +{ + public static Stream getBinaryListeners() + { + return Stream.of( + OffsetByteArrayWholeListener.class, + OffsetByteBufferPartialListener.class, + AnnotatedByteBufferWholeListener.class, + AnnotatedByteArrayWholeListener.class, + AnnotatedOffsetByteArrayWholeListener.class, + AnnotatedInputStreamWholeListener.class, + AnnotatedReverseArgumentPartialListener.class + ).map(Arguments::of); + } + + public static class OffsetByteArrayWholeListener extends AbstractListener implements WebSocketListener + { + @Override + public void onWebSocketBinary(byte[] payload, int offset, int len) + { + sendBinary(BufferUtil.toBuffer(payload, offset, len), true); + } + } + + public static class OffsetByteBufferPartialListener extends AbstractListener implements WebSocketPartialListener + { + @Override + public void onWebSocketPartialBinary(ByteBuffer payload, boolean fin) + { + sendBinary(payload, fin); + } + } + + @WebSocket + public static class AnnotatedByteBufferWholeListener extends AbstractAnnotatedListener + { + @OnWebSocketMessage + public void onMessage(ByteBuffer message) + { + sendBinary(message, true); + } + } + + @WebSocket + public static class AnnotatedByteArrayWholeListener extends AbstractAnnotatedListener + { + @OnWebSocketMessage + public void onMessage(byte[] message) + { + sendBinary(BufferUtil.toBuffer(message), true); + } + } + + @WebSocket + public static class AnnotatedOffsetByteArrayWholeListener extends AbstractAnnotatedListener + { + @OnWebSocketMessage + public void onMessage(byte[] message, int offset, int length) + { + sendBinary(BufferUtil.toBuffer(message, offset, length), true); + } + } + + @WebSocket + public static class AnnotatedInputStreamWholeListener extends AbstractAnnotatedListener + { + @OnWebSocketMessage + public void onMessage(InputStream stream) + { + sendBinary(readBytes(stream), true); + } + } + + @WebSocket + public static class AnnotatedReverseArgumentPartialListener extends AbstractAnnotatedListener + { + @OnWebSocketMessage + public void onMessage(Session session, ByteBuffer message) + { + sendBinary(message, true); + } + } + + public static ByteBuffer readBytes(InputStream stream) + { + try + { + return BufferUtil.toBuffer(IO.readBytes(stream)); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } +} diff --git a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/listeners/TextListeners.java b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/listeners/TextListeners.java new file mode 100644 index 00000000000..e362c28aea0 --- /dev/null +++ b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/listeners/TextListeners.java @@ -0,0 +1,105 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.websocket.tests.listeners; + +import java.io.IOException; +import java.io.Reader; +import java.util.stream.Stream; + +import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.WebSocketListener; +import org.eclipse.jetty.websocket.api.WebSocketPartialListener; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; +import org.eclipse.jetty.websocket.api.annotations.WebSocket; +import org.junit.jupiter.params.provider.Arguments; + +public class TextListeners +{ + public static Stream getTextListeners() + { + return Stream.of( + StringWholeListener.class, + StringPartialListener.class, + AnnotatedStringWholeListener.class, + AnnotatedReaderWholeListener.class, + AnnotatedReverseArgumentPartialListener.class + ).map(Arguments::of); + } + + public static class StringWholeListener extends AbstractListener implements WebSocketListener + { + @Override + public void onWebSocketText(String message) + { + sendText(message, true); + } + } + + public static class StringPartialListener extends AbstractListener implements WebSocketPartialListener + { + @Override + public void onWebSocketPartialText(String message, boolean fin) + { + sendText(message, fin); + } + } + + @WebSocket + public static class AnnotatedStringWholeListener extends AbstractAnnotatedListener + { + @OnWebSocketMessage + public void onMessage(String message) + { + sendText(message, true); + } + } + + @WebSocket + public static class AnnotatedReaderWholeListener extends AbstractAnnotatedListener + { + @OnWebSocketMessage + public void onMessage(Reader reader) + { + sendText(readString(reader), true); + } + } + + @WebSocket + public static class AnnotatedReverseArgumentPartialListener extends AbstractAnnotatedListener + { + @OnWebSocketMessage + public void onMessage(Session session, String message) + { + sendText(message, true); + } + } + + public static String readString(Reader reader) + { + try + { + return IO.toString(reader); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } +} diff --git a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/listeners/WebSocketListenerTest.java b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/listeners/WebSocketListenerTest.java new file mode 100644 index 00000000000..fe290ccba82 --- /dev/null +++ b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/listeners/WebSocketListenerTest.java @@ -0,0 +1,146 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.websocket.tests.listeners; + +import java.net.URI; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.websocket.api.StatusCode; +import org.eclipse.jetty.websocket.client.WebSocketClient; +import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer; +import org.eclipse.jetty.websocket.tests.EventSocket; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class WebSocketListenerTest +{ + private Server server; + private URI serverUri; + private WebSocketClient client; + + @BeforeEach + public void before() throws Exception + { + server = new Server(); + ServerConnector connector = new ServerConnector(server); + server.addConnector(connector); + + ServletContextHandler contextHandler = new ServletContextHandler(); + contextHandler.setContextPath("/"); + JettyWebSocketServletContainerInitializer.configure(contextHandler, (context, container) -> + { + for (Class c : getClassListFromArguments(TextListeners.getTextListeners())) + { + container.addMapping("/text/" + c.getSimpleName(), (req, res) -> construct(c)); + } + + for (Class c : getClassListFromArguments(BinaryListeners.getBinaryListeners())) + { + container.addMapping("/binary/" + c.getSimpleName(), (req, res) -> construct(c)); + } + }); + + server.setHandler(contextHandler); + server.start(); + serverUri = URI.create("ws://localhost:" + connector.getLocalPort() + "/"); + client = new WebSocketClient(); + client.start(); + } + + @AfterEach + public void after() throws Exception + { + client.stop(); + server.stop(); + } + + @ParameterizedTest + @MethodSource("org.eclipse.jetty.websocket.tests.listeners.TextListeners#getTextListeners") + public void testTextListeners(Class clazz) throws Exception + { + EventSocket clientEndpoint = new EventSocket(); + client.connect(clientEndpoint, serverUri.resolve("/text/" + clazz.getSimpleName())).get(5, TimeUnit.SECONDS); + + // Send and receive echo on client. + String payload = "hello world"; + clientEndpoint.session.getRemote().sendString(payload); + String echoMessage = clientEndpoint.textMessages.poll(5, TimeUnit.SECONDS); + assertThat(echoMessage, is(payload)); + + // Close normally. + clientEndpoint.session.close(StatusCode.NORMAL, "standard close"); + assertTrue(clientEndpoint.closeLatch.await(5, TimeUnit.SECONDS)); + assertThat(clientEndpoint.closeCode, is(StatusCode.NORMAL)); + assertThat(clientEndpoint.closeReason, is("standard close")); + } + + @ParameterizedTest + @MethodSource("org.eclipse.jetty.websocket.tests.listeners.BinaryListeners#getBinaryListeners") + public void testBinaryListeners(Class clazz) throws Exception + { + EventSocket clientEndpoint = new EventSocket(); + client.connect(clientEndpoint, serverUri.resolve("/binary/" + clazz.getSimpleName())).get(5, TimeUnit.SECONDS); + + // Send and receive echo on client. + ByteBuffer payload = BufferUtil.toBuffer("hello world"); + clientEndpoint.session.getRemote().sendBytes(payload); + ByteBuffer echoMessage = clientEndpoint.binaryMessages.poll(5, TimeUnit.SECONDS); + assertThat(echoMessage, is(payload)); + + // Close normally. + clientEndpoint.session.close(StatusCode.NORMAL, "standard close"); + assertTrue(clientEndpoint.closeLatch.await(5, TimeUnit.SECONDS)); + assertThat(clientEndpoint.closeCode, is(StatusCode.NORMAL)); + assertThat(clientEndpoint.closeReason, is("standard close")); + } + + private List> getClassListFromArguments(Stream stream) + { + return stream.map(arguments -> (Class)arguments.get()[0]).collect(Collectors.toList()); + } + + private T construct(Class clazz) + { + try + { + @SuppressWarnings("unchecked") + T instance = (T)clazz.getConstructors()[0].newInstance(); + return instance; + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } +} diff --git a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/ServerConfigTest.java b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/ServerConfigTest.java index 510774ed602..739877c084e 100644 --- a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/ServerConfigTest.java +++ b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/ServerConfigTest.java @@ -230,7 +230,7 @@ public class ServerConfigTest assertNull(serverEndpoint.error); assertTrue(clientEndpoint.closeLatch.await(5, TimeUnit.SECONDS)); - assertThat(clientEndpoint.statusCode, is(StatusCode.NO_CODE)); + assertThat(clientEndpoint.closeCode, is(StatusCode.NO_CODE)); listener.assertClosed(); } @@ -251,7 +251,7 @@ public class ServerConfigTest assertThat(serverEndpoint.error, instanceOf(MessageTooLargeException.class)); assertTrue(clientEndpoint.closeLatch.await(5, TimeUnit.SECONDS)); - assertThat(clientEndpoint.statusCode, is(StatusCode.MESSAGE_TOO_LARGE)); + assertThat(clientEndpoint.closeCode, is(StatusCode.MESSAGE_TOO_LARGE)); listener.assertClosed(); } @@ -267,7 +267,7 @@ public class ServerConfigTest connect.get(5, TimeUnit.SECONDS); clientEndpoint.session.getRemote().sendString("hello world"); - String msg = serverEndpoint.messageQueue.poll(500, TimeUnit.MILLISECONDS); + String msg = serverEndpoint.textMessages.poll(500, TimeUnit.MILLISECONDS); assertThat(msg, is("hello world")); Thread.sleep(idleTimeout + 500); @@ -275,7 +275,7 @@ public class ServerConfigTest assertThat(serverEndpoint.error, instanceOf(WebSocketTimeoutException.class)); assertTrue(clientEndpoint.closeLatch.await(5, TimeUnit.SECONDS)); - assertThat(clientEndpoint.statusCode, is(StatusCode.SHUTDOWN)); + assertThat(clientEndpoint.closeCode, is(StatusCode.SHUTDOWN)); listener.assertClosed(); } @@ -296,7 +296,7 @@ public class ServerConfigTest assertThat(serverEndpoint.error, instanceOf(MessageTooLargeException.class)); assertTrue(clientEndpoint.closeLatch.await(5, TimeUnit.SECONDS)); - assertThat(clientEndpoint.statusCode, is(StatusCode.MESSAGE_TOO_LARGE)); + assertThat(clientEndpoint.closeCode, is(StatusCode.MESSAGE_TOO_LARGE)); listener.assertClosed(); } diff --git a/jetty-websocket/websocket-util/src/main/java/org/eclipse/jetty/websocket/util/messages/ByteArrayMessageSink.java b/jetty-websocket/websocket-util/src/main/java/org/eclipse/jetty/websocket/util/messages/ByteArrayMessageSink.java index 1921e36b704..4b84a54156d 100644 --- a/jetty-websocket/websocket-util/src/main/java/org/eclipse/jetty/websocket/util/messages/ByteArrayMessageSink.java +++ b/jetty-websocket/websocket-util/src/main/java/org/eclipse/jetty/websocket/util/messages/ByteArrayMessageSink.java @@ -42,7 +42,8 @@ public class ByteArrayMessageSink extends AbstractMessageSink { super(session, methodHandle); - // byte[] buf + // This uses the offset length byte array signature not supported by javax websocket. + // The javax layer instead uses decoders for whole byte array messages instead of this message sink. MethodType onMessageType = MethodType.methodType(Void.TYPE, byte[].class, int.class, int.class); if (methodHandle.type().changeReturnType(void.class) != onMessageType.changeReturnType(void.class)) { diff --git a/jetty-websocket/websocket-util/src/main/java/org/eclipse/jetty/websocket/util/messages/PartialByteArrayMessageSink.java b/jetty-websocket/websocket-util/src/main/java/org/eclipse/jetty/websocket/util/messages/PartialByteArrayMessageSink.java index a42c3577837..bf7058cbe0c 100644 --- a/jetty-websocket/websocket-util/src/main/java/org/eclipse/jetty/websocket/util/messages/PartialByteArrayMessageSink.java +++ b/jetty-websocket/websocket-util/src/main/java/org/eclipse/jetty/websocket/util/messages/PartialByteArrayMessageSink.java @@ -19,13 +19,11 @@ package org.eclipse.jetty.websocket.util.messages; import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodType; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.websocket.core.CoreSession; import org.eclipse.jetty.websocket.core.Frame; -import org.eclipse.jetty.websocket.util.InvalidSignatureException; public class PartialByteArrayMessageSink extends AbstractMessageSink { @@ -34,13 +32,6 @@ public class PartialByteArrayMessageSink extends AbstractMessageSink public PartialByteArrayMessageSink(CoreSession session, MethodHandle methodHandle) { super(session, methodHandle); - - // byte[] buf, int offset, int length - MethodType onMessageType = MethodType.methodType(Void.TYPE, byte[].class, int.class, int.class, boolean.class); - if (methodHandle.type() != onMessageType) - { - throw InvalidSignatureException.build(onMessageType, methodHandle.type()); - } } @Override @@ -51,7 +42,7 @@ public class PartialByteArrayMessageSink extends AbstractMessageSink if (frame.hasPayload() || frame.isFin()) { byte[] buffer = frame.hasPayload() ? BufferUtil.toArray(frame.getPayload()) : EMPTY_BUFFER; - methodHandle.invoke(buffer, 0, buffer.length, frame.isFin()); + methodHandle.invoke(buffer, frame.isFin()); } callback.succeeded(); diff --git a/jetty-websocket/websocket-util/src/main/java/org/eclipse/jetty/websocket/util/messages/PartialByteBufferMessageSink.java b/jetty-websocket/websocket-util/src/main/java/org/eclipse/jetty/websocket/util/messages/PartialByteBufferMessageSink.java index ce00ee7acef..9336e93aa52 100644 --- a/jetty-websocket/websocket-util/src/main/java/org/eclipse/jetty/websocket/util/messages/PartialByteBufferMessageSink.java +++ b/jetty-websocket/websocket-util/src/main/java/org/eclipse/jetty/websocket/util/messages/PartialByteBufferMessageSink.java @@ -29,14 +29,6 @@ public class PartialByteBufferMessageSink extends AbstractMessageSink public PartialByteBufferMessageSink(CoreSession session, MethodHandle methodHandle) { super(session, methodHandle); - - /* TODO: Review - MethodType onMessageType = MethodType.methodType(Void.TYPE, ByteBuffer.class, boolean.class); - if (methodHandle.type() != onMessageType) - { - throw InvalidSignatureException.build(onMessageType, methodHandle.type()); - } - */ } @Override From 2addb6a655ac40ac120be0e5cb33c59a91052024 Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Wed, 15 Apr 2020 18:42:28 +0200 Subject: [PATCH 086/101] Jetty 10.0.x use HandlerList instead of HandlerCollection (#4757) * Use HandlerList instead of HandlerCollection Signed-off-by: Greg Wilkins * Use HandlerList instead of HandlerCollection Signed-off-by: Greg Wilkins --- .../eclipse/jetty/embedded/ExampleServer.java | 7 +- .../jetty/embedded/FastFileServer.java | 8 +- .../eclipse/jetty/embedded/FileServer.java | 5 +- .../org/eclipse/jetty/embedded/JarServer.java | 5 +- .../eclipse/jetty/embedded/LikeJettyXml.java | 7 +- .../eclipse/jetty/embedded/ManyHandlers.java | 2 +- .../jetty/embedded/WebSocketJsrServer.java | 6 +- .../src/main/resources/exampleserver.xml | 2 +- .../src/main/resources/jetty-otherserver.xml | 2 +- .../eclipse/jetty/ant/ServerProxyImpl.java | 16 +- .../jetty/deploy/BadAppDeployTest.java | 21 +- jetty-deploy/src/test/resources/jetty.xml | 2 +- .../http/spi/JettyHttpServerProvider.java | 6 +- .../jetty/http/spi/util/SpiUtility.java | 110 ---- .../jetty/maven/plugin/ServerSupport.java | 17 +- .../src/test/resources/embedder-jetty.xml | 2 +- .../jetty-osgi-boot/jettyhome/etc/jetty.xml | 5 +- .../config/etc/jetty-with-custom-class.xml | 5 +- .../src/test/config/etc/jetty.xml | 5 +- .../src/main/config/etc/jetty-rewrite.xml | 37 +- .../config/modules/rewrite/rewrite-rules.xml | 40 +- .../jetty/rewrite/handler/RewriteHandler.java | 32 +- .../handler/CookiePatternRuleTest.java | 6 +- .../jetty-rewrite.xml | 499 +++++++++--------- .../java/org/eclipse/jetty/runner/Runner.java | 3 +- .../jetty/security/AliasedConstraintTest.java | 5 +- jetty-server/src/main/config/etc/jetty.xml | 12 +- .../jetty/server/PartialRFC2616Test.java | 8 +- .../jetty/server/ServerConnectorTest.java | 25 +- .../handler/ContextHandlerCollectionTest.java | 3 +- .../server/handler/ContextHandlerTest.java | 7 +- .../server/handler/DefaultHandlerTest.java | 5 +- .../jetty/server/handler/HandlerTest.java | 69 ++- .../server/handler/NcsaRequestLogTest.java | 6 +- .../handler/SecuredRedirectHandlerTest.java | 6 +- .../AsyncContextDispatchWithQueryStrings.java | 6 +- .../jetty/servlet/AsyncContextTest.java | 7 +- .../jetty/servlet/DefaultHandlerTest.java | 7 +- .../jetty/servlet/ServletRequestLogTest.java | 29 +- .../jetty/servlet/ServletWrapperTest.java | 6 +- .../src/main/config/etc/jetty-spring.xml | 2 +- .../org/eclipse/jetty/spring/jetty.xml | 2 +- .../jetty/webapp/HugeResourceTest.java | 6 +- .../websocket/javax/tests/CoreServer.java | 5 +- .../javax/tests/RestartContextTest.java | 11 +- .../server/browser/BrowserDebugTool.java | 6 +- .../tests/client/BadNetworkTest.java | 5 +- .../tests/client/ClientCloseTest.java | 5 +- .../tests/client/ClientSessionsTest.java | 5 +- .../tests/client/SlowClientTest.java | 6 +- .../tests/server/FrameAnnotationTest.java | 6 +- .../tests/server/FrameListenerTest.java | 6 +- .../tests/server/PartialListenerTest.java | 6 +- .../tests/server/ServerCloseTest.java | 6 +- .../tests/server/SlowServerTest.java | 6 +- .../jetty/test/DeploymentErrorTest.java | 8 +- .../eclipse/jetty/test/DigestPostTest.java | 8 +- .../jetty/test/FailedSelectorTest.java | 6 +- .../src/test/resources/DefaultHandler.xml | 4 +- .../src/test/resources/RFC2616Base.xml | 6 +- .../src/test/resources/basic-server.xml | 2 +- .../jetty/DatabaseLoginServiceTestServer.java | 7 +- .../session/RequestDispatchedSessionTest.java | 6 +- .../java/org/eclipse/jetty/TestServer.java | 7 +- .../jetty/TestTransparentProxyServer.java | 9 +- 65 files changed, 444 insertions(+), 753 deletions(-) delete mode 100644 jetty-http-spi/src/test/java/org/eclipse/jetty/http/spi/util/SpiUtility.java diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ExampleServer.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ExampleServer.java index 1eed27836c6..538c06486fb 100644 --- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ExampleServer.java +++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ExampleServer.java @@ -19,11 +19,10 @@ package org.eclipse.jetty.embedded; import org.eclipse.jetty.server.Connector; -import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.handler.DefaultHandler; -import org.eclipse.jetty.server.handler.HandlerCollection; +import org.eclipse.jetty.server.handler.HandlerList; import org.eclipse.jetty.servlet.ServletContextHandler; public class ExampleServer @@ -41,9 +40,7 @@ public class ExampleServer context.addServlet(HelloServlet.class, "/hello"); context.addServlet(AsyncEchoServlet.class, "/echo/*"); - HandlerCollection handlers = new HandlerCollection(); - handlers.setHandlers(new Handler[]{context, new DefaultHandler()}); - server.setHandler(handlers); + server.setHandler(new HandlerList(context, new DefaultHandler())); return server; } diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/FastFileServer.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/FastFileServer.java index 2254cce0a69..745a551acd5 100644 --- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/FastFileServer.java +++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/FastFileServer.java @@ -31,7 +31,6 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.MimeTypes; -import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpOutput; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Server; @@ -62,12 +61,9 @@ public class FastFileServer { Server server = new Server(port); - HandlerList handlers = new HandlerList(); - handlers.setHandlers(new Handler[]{ + server.setHandler(new HandlerList( new FastFileHandler(resourceBase), - new DefaultHandler() - }); - server.setHandler(handlers); + new DefaultHandler())); return server; } diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/FileServer.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/FileServer.java index 58d32b93446..fbff826fdd8 100644 --- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/FileServer.java +++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/FileServer.java @@ -21,7 +21,6 @@ package org.eclipse.jetty.embedded; import java.nio.file.Path; import java.nio.file.Paths; -import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.DefaultHandler; import org.eclipse.jetty.server.handler.HandlerList; @@ -53,9 +52,7 @@ public class FileServer resourceHandler.setBaseResource(baseResource); // Add the ResourceHandler to the server. - HandlerList handlers = new HandlerList(); - handlers.setHandlers(new Handler[]{resourceHandler, new DefaultHandler()}); - server.setHandler(handlers); + server.setHandler(new HandlerList(resourceHandler, new DefaultHandler())); return server; } diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/JarServer.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/JarServer.java index ca285e010dd..b5b41164869 100644 --- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/JarServer.java +++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/JarServer.java @@ -51,10 +51,7 @@ public class JarServer context.setBaseResource(base); context.addServlet(new ServletHolder(new DefaultServlet()), "/"); - HandlerList handlers = new HandlerList(); - handlers.addHandler(context); - handlers.addHandler(new DefaultHandler()); - server.setHandler(handlers); + server.setHandler(new HandlerList(context, new DefaultHandler())); return server; } diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/LikeJettyXml.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/LikeJettyXml.java index ea16f69c68b..b094964df53 100644 --- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/LikeJettyXml.java +++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/LikeJettyXml.java @@ -40,7 +40,6 @@ import org.eclipse.jetty.security.HashLoginService; import org.eclipse.jetty.server.AsyncRequestLogWriter; import org.eclipse.jetty.server.CustomRequestLog; import org.eclipse.jetty.server.DebugListener; -import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.server.LowResourceMonitor; @@ -51,7 +50,7 @@ import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.SslConnectionFactory; import org.eclipse.jetty.server.handler.ContextHandlerCollection; import org.eclipse.jetty.server.handler.DefaultHandler; -import org.eclipse.jetty.server.handler.HandlerCollection; +import org.eclipse.jetty.server.handler.HandlerList; import org.eclipse.jetty.server.handler.StatisticsHandler; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.QueuedThreadPool; @@ -105,10 +104,8 @@ public class LikeJettyXml // httpConfig.addCustomizer(new ForwardedRequestCustomizer()); // Handler Structure - HandlerCollection handlers = new HandlerCollection(); ContextHandlerCollection contexts = new ContextHandlerCollection(); - handlers.setHandlers(new Handler[]{contexts, new DefaultHandler()}); - server.setHandler(handlers); + server.setHandler(new HandlerList(contexts, new DefaultHandler())); // === jetty-jmx.xml === MBeanContainer mbContainer = new MBeanContainer( diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ManyHandlers.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ManyHandlers.java index 77dd973eafe..58df808147d 100644 --- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ManyHandlers.java +++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ManyHandlers.java @@ -124,7 +124,7 @@ public class ManyHandlers CustomRequestLog ncsaLog = new CustomRequestLog(requestLogFile.getAbsolutePath()); server.setRequestLog(ncsaLog); - // create the handler collections + // create the handlers list HandlerList handlers = new HandlerList(); // wrap contexts around specific handlers diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/WebSocketJsrServer.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/WebSocketJsrServer.java index 86c88c703e3..26fa623cdbe 100644 --- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/WebSocketJsrServer.java +++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/WebSocketJsrServer.java @@ -50,11 +50,8 @@ public class WebSocketJsrServer { Server server = new Server(port); - HandlerList handlers = new HandlerList(); - ServletContextHandler context = new ServletContextHandler(); context.setContextPath("/"); - handlers.addHandler(context); // Enable javax.websocket configuration for the context JavaxWebSocketServletContainerInitializer.configure(context, @@ -65,8 +62,7 @@ public class WebSocketJsrServer } ); - handlers.addHandler(new DefaultHandler()); - server.setHandler(handlers); + server.setHandler(new HandlerList(context, new DefaultHandler())); return server; } diff --git a/examples/embedded/src/main/resources/exampleserver.xml b/examples/embedded/src/main/resources/exampleserver.xml index 138bdfd53b0..b3940b8f20a 100644 --- a/examples/embedded/src/main/resources/exampleserver.xml +++ b/examples/embedded/src/main/resources/exampleserver.xml @@ -25,7 +25,7 @@ - + diff --git a/examples/embedded/src/main/resources/jetty-otherserver.xml b/examples/embedded/src/main/resources/jetty-otherserver.xml index 89596307069..2a4c4d361c4 100644 --- a/examples/embedded/src/main/resources/jetty-otherserver.xml +++ b/examples/embedded/src/main/resources/jetty-otherserver.xml @@ -3,7 +3,7 @@ - + diff --git a/jetty-ant/src/main/java/org/eclipse/jetty/ant/ServerProxyImpl.java b/jetty-ant/src/main/java/org/eclipse/jetty/ant/ServerProxyImpl.java index 16460fe7c18..e71da97c657 100644 --- a/jetty-ant/src/main/java/org/eclipse/jetty/ant/ServerProxyImpl.java +++ b/jetty-ant/src/main/java/org/eclipse/jetty/ant/ServerProxyImpl.java @@ -30,7 +30,6 @@ import org.eclipse.jetty.ant.types.ContextHandlers; import org.eclipse.jetty.ant.utils.ServerProxy; import org.eclipse.jetty.ant.utils.TaskLog; import org.eclipse.jetty.security.LoginService; -import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.RequestLog; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; @@ -39,6 +38,7 @@ import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.server.handler.ContextHandlerCollection; import org.eclipse.jetty.server.handler.DefaultHandler; import org.eclipse.jetty.server.handler.HandlerCollection; +import org.eclipse.jetty.server.handler.HandlerList; import org.eclipse.jetty.util.Scanner; import org.eclipse.jetty.util.resource.PathResource; import org.eclipse.jetty.util.resource.Resource; @@ -406,23 +406,15 @@ public class ServerProxyImpl implements ServerProxy if (requestLog != null) server.setRequestLog(requestLog); - contexts = (ContextHandlerCollection)server - .getChildHandlerByClass(ContextHandlerCollection.class); + contexts = server.getChildHandlerByClass(ContextHandlerCollection.class); if (contexts == null) { contexts = new ContextHandlerCollection(); - HandlerCollection handlers = (HandlerCollection)server - .getChildHandlerByClass(HandlerCollection.class); + HandlerCollection handlers = server.getChildHandlerByClass(HandlerCollection.class); if (handlers == null) - { - handlers = new HandlerCollection(); - server.setHandler(handlers); - handlers.setHandlers(new Handler[]{contexts, new DefaultHandler()}); - } + server.setHandler(new HandlerList(contexts, new DefaultHandler())); else - { handlers.addHandler(contexts); - } } //if there are any extra contexts to deploy diff --git a/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/BadAppDeployTest.java b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/BadAppDeployTest.java index 0c99e06d673..6a6ee4fb9d8 100644 --- a/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/BadAppDeployTest.java +++ b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/BadAppDeployTest.java @@ -29,7 +29,7 @@ import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.handler.ContextHandlerCollection; import org.eclipse.jetty.server.handler.DefaultHandler; -import org.eclipse.jetty.server.handler.HandlerCollection; +import org.eclipse.jetty.server.handler.HandlerList; import org.eclipse.jetty.toolchain.test.FS; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.toolchain.test.jupiter.WorkDir; @@ -69,7 +69,7 @@ public class BadAppDeployTest It is important that this Order be maintained for an accurate test case. ### BEAN: QueuedThreadPool[qtp1327763628]@4f2410ac{STOPPED,8<=0<=200,i=0,r=-1,q=0}[NO_TRY] ### BEAN: ServerConnector@16f65612{HTTP/1.1,[http/1.1]}{0.0.0.0:8080} - ### BEAN: HandlerCollection@5f150435{STOPPED} + ### BEAN: HandlerList@5f150435{STOPPED} ### BEAN: DeploymentManager@1c53fd30{STOPPED} */ @@ -79,10 +79,8 @@ public class BadAppDeployTest server.addConnector(connector); ContextHandlerCollection contexts = new ContextHandlerCollection(); - HandlerCollection handlers = new HandlerCollection(); - handlers.addHandler(contexts); - handlers.addHandler(new DefaultHandler()); - server.setHandler(handlers); // this should be done before addBean(deploymentManager) + // this should be done before addBean(deploymentManager) + server.setHandler(new HandlerList(contexts, new DefaultHandler())); DeploymentManager deploymentManager = new DeploymentManager(); deploymentManager.setContexts(contexts); @@ -121,7 +119,7 @@ public class BadAppDeployTest ### BEAN: QueuedThreadPool[qtp1530388690]@5b37e0d2{STOPPED,8<=0<=200,i=0,r=-1,q=0}[NO_TRY] ### BEAN: ServerConnector@5e265ba4{HTTP/1.1,[http/1.1]}{0.0.0.0:8080} ### BEAN: DeploymentManager@3419866c{STOPPED} - ### BEAN: HandlerCollection@63e31ee{STOPPED} + ### BEAN: HandlerList@63e31ee{STOPPED} */ server = new Server(); @@ -146,12 +144,11 @@ public class BadAppDeployTest webAppProvider.setMonitoredDirName(webappsDir.toString()); webAppProvider.setScanInterval(1); - server.addBean(deploymentManager); // this should be done before setHandler(handlers) + // this must be done before setHandler(handlers) + server.addBean(deploymentManager); - HandlerCollection handlers = new HandlerCollection(); - handlers.addHandler(contexts); - handlers.addHandler(new DefaultHandler()); - server.setHandler(handlers); // this should be done after addBean(deploymentManager) + // this must be done after addBean(deploymentManager) + server.setHandler(new HandlerList(contexts, new DefaultHandler())); assertTimeoutPreemptively(ofSeconds(10), () -> { diff --git a/jetty-deploy/src/test/resources/jetty.xml b/jetty-deploy/src/test/resources/jetty.xml index 7901921d362..f3ada0bf854 100644 --- a/jetty-deploy/src/test/resources/jetty.xml +++ b/jetty-deploy/src/test/resources/jetty.xml @@ -106,7 +106,7 @@ - + diff --git a/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpServerProvider.java b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpServerProvider.java index 43ab5f8242e..a9a875c8402 100644 --- a/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpServerProvider.java +++ b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpServerProvider.java @@ -24,11 +24,10 @@ import java.net.InetSocketAddress; import com.sun.net.httpserver.HttpServer; import com.sun.net.httpserver.HttpsServer; import com.sun.net.httpserver.spi.HttpServerProvider; -import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.ContextHandlerCollection; import org.eclipse.jetty.server.handler.DefaultHandler; -import org.eclipse.jetty.server.handler.HandlerCollection; +import org.eclipse.jetty.server.handler.HandlerList; import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.eclipse.jetty.util.thread.ThreadPool; @@ -57,8 +56,7 @@ public class JettyHttpServerProvider extends HttpServerProvider ThreadPool threadPool = new DelegatingThreadPool(new QueuedThreadPool()); server = new Server(threadPool); - HandlerCollection handlerCollection = new HandlerCollection(); - handlerCollection.setHandlers(new Handler[]{new ContextHandlerCollection(), new DefaultHandler()}); + HandlerList handlerCollection = new HandlerList(new ContextHandlerCollection(), new DefaultHandler()); server.setHandler(handlerCollection); shared = false; diff --git a/jetty-http-spi/src/test/java/org/eclipse/jetty/http/spi/util/SpiUtility.java b/jetty-http-spi/src/test/java/org/eclipse/jetty/http/spi/util/SpiUtility.java deleted file mode 100644 index 264a880cc25..00000000000 --- a/jetty-http-spi/src/test/java/org/eclipse/jetty/http/spi/util/SpiUtility.java +++ /dev/null @@ -1,110 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under -// the terms of the Eclipse Public License 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0 -// -// This Source Code may also be made available under the following -// Secondary Licenses when the conditions for such availability set -// forth in the Eclipse Public License, v. 2.0 are satisfied: -// the Apache License v2.0 which is available at -// https://www.apache.org/licenses/LICENSE-2.0 -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package org.eclipse.jetty.http.spi.util; - -import java.net.InetSocketAddress; -import java.util.ArrayList; -import java.util.Hashtable; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; - -import org.eclipse.jetty.http.spi.DelegatingThreadPool; -import org.eclipse.jetty.http.spi.JettyHttpServer; -import org.eclipse.jetty.server.Handler; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.handler.ContextHandler; -import org.eclipse.jetty.server.handler.ContextHandlerCollection; -import org.eclipse.jetty.server.handler.HandlerCollection; - -/** - * This is a utility class. Test cases uses this utility class - */ -public class SpiUtility -{ - - public static ThreadPoolExecutor getThreadPoolExecutor(int poolSize, int[] poolInfo) - { - return new ThreadPoolExecutor(poolSize, poolInfo[0], poolInfo[1], TimeUnit.SECONDS, new ArrayBlockingQueue(poolInfo[2])); - } - - public static DelegatingThreadPool getDelegatingThreadPool() - { - ThreadPoolExecutor threadPoolExecutor = SpiUtility.getThreadPoolExecutor(Pool.CORE_POOL_SIZE.getValue(), SpiConstants.poolInfo); - DelegatingThreadPool delegatingThreadPool = new DelegatingThreadPool(threadPoolExecutor); - return delegatingThreadPool; - } - - public static InetSocketAddress getInetSocketAddress() - { - return new InetSocketAddress(SpiConstants.LOCAL_HOST, SpiConstants.DEFAULT_PORT); - } - - public static void callBind(JettyHttpServer jettyHttpServer) throws Exception - { - InetSocketAddress inetSocketAddress = SpiUtility.getInetSocketAddress(); - jettyHttpServer.bind(inetSocketAddress, SpiConstants.BACK_LOG); - } - - public static Server getServerForContextHandler() - { - Handler handler = new ContextHandler(); - Server server = new Server(); - server.setHandler(handler); - return server; - } - - public static Server getServerForContextHandlerCollection() - { - Handler handler = new ContextHandlerCollection(); - Server server = new Server(); - server.setHandler(handler); - return server; - } - - public static Server getServerForHandlerCollection() - { - ContextHandler handler = new ContextHandler(); - Handler[] handles = - {handler}; - HandlerCollection contextHandler = new HandlerCollection(); - contextHandler.setHandlers(handles); - Server server = new Server(); - server.setHandler(contextHandler); - return server; - } - - public static Map> getAcceptCharsetHeader() - { - ArrayList valueSet = new ArrayList(); - valueSet.add(SpiConstants.UTF_8); - Map> headers = new Hashtable<>(); - headers.put(SpiConstants.ACCEPT_CHARSET, valueSet); - return headers; - } - - public static List getReqHeaderValues() - { - List reqHeaderValues = new ArrayList<>(); - reqHeaderValues.add("en-US"); - return reqHeaderValues; - } -} diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/ServerSupport.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/ServerSupport.java index b1001c744b4..95024bb85ea 100644 --- a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/ServerSupport.java +++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/ServerSupport.java @@ -26,14 +26,13 @@ import java.util.Map; import org.eclipse.jetty.security.LoginService; import org.eclipse.jetty.server.Connector; -import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.RequestLog; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.server.handler.ContextHandlerCollection; import org.eclipse.jetty.server.handler.DefaultHandler; import org.eclipse.jetty.server.handler.HandlerCollection; -import org.eclipse.jetty.server.handler.RequestLogHandler; +import org.eclipse.jetty.server.handler.HandlerList; import org.eclipse.jetty.util.resource.PathResource; import org.eclipse.jetty.webapp.Configurations; import org.eclipse.jetty.webapp.WebAppContext; @@ -68,26 +67,18 @@ public class ServerSupport if (server == null) throw new IllegalArgumentException("Server is null"); - DefaultHandler defaultHandler = new DefaultHandler(); - RequestLogHandler requestLogHandler = new RequestLogHandler(); if (requestLog != null) - requestLogHandler.setRequestLog(requestLog); + server.setRequestLog(requestLog); ContextHandlerCollection contexts = findContextHandlerCollection(server); if (contexts == null) { contexts = new ContextHandlerCollection(); - HandlerCollection handlers = (HandlerCollection)server.getChildHandlerByClass(HandlerCollection.class); + HandlerCollection handlers = server.getChildHandlerByClass(HandlerCollection.class); if (handlers == null) - { - handlers = new HandlerCollection(); - server.setHandler(handlers); - handlers.setHandlers(new Handler[]{contexts, defaultHandler, requestLogHandler}); - } + server.setHandler(new HandlerList(contexts, new DefaultHandler())); else - { handlers.addHandler(contexts); - } } if (contextHandlers != null) diff --git a/jetty-maven-plugin/src/test/resources/embedder-jetty.xml b/jetty-maven-plugin/src/test/resources/embedder-jetty.xml index 85ae556bdda..a7ad2021f67 100644 --- a/jetty-maven-plugin/src/test/resources/embedder-jetty.xml +++ b/jetty-maven-plugin/src/test/resources/embedder-jetty.xml @@ -3,7 +3,7 @@ - + diff --git a/jetty-osgi/jetty-osgi-boot/jettyhome/etc/jetty.xml b/jetty-osgi/jetty-osgi-boot/jettyhome/etc/jetty.xml index d4bac27248f..b73820e42e4 100644 --- a/jetty-osgi/jetty-osgi-boot/jettyhome/etc/jetty.xml +++ b/jetty-osgi/jetty-osgi-boot/jettyhome/etc/jetty.xml @@ -24,7 +24,7 @@ - + @@ -33,9 +33,6 @@ - - - diff --git a/jetty-osgi/test-jetty-osgi/src/test/config/etc/jetty-with-custom-class.xml b/jetty-osgi/test-jetty-osgi/src/test/config/etc/jetty-with-custom-class.xml index 6c2b9d68082..4561574d208 100644 --- a/jetty-osgi/test-jetty-osgi/src/test/config/etc/jetty-with-custom-class.xml +++ b/jetty-osgi/test-jetty-osgi/src/test/config/etc/jetty-with-custom-class.xml @@ -26,7 +26,7 @@ - + @@ -35,9 +35,6 @@ - - - diff --git a/jetty-osgi/test-jetty-osgi/src/test/config/etc/jetty.xml b/jetty-osgi/test-jetty-osgi/src/test/config/etc/jetty.xml index 25f831c57c5..1d10384859c 100644 --- a/jetty-osgi/test-jetty-osgi/src/test/config/etc/jetty.xml +++ b/jetty-osgi/test-jetty-osgi/src/test/config/etc/jetty.xml @@ -24,7 +24,7 @@ - + @@ -33,9 +33,6 @@ - - - diff --git a/jetty-rewrite/src/main/config/etc/jetty-rewrite.xml b/jetty-rewrite/src/main/config/etc/jetty-rewrite.xml index 1dc494ce93f..e123113c5c2 100644 --- a/jetty-rewrite/src/main/config/etc/jetty-rewrite.xml +++ b/jetty-rewrite/src/main/config/etc/jetty-rewrite.xml @@ -1,5 +1,4 @@ - - + @@ -9,22 +8,28 @@ - - - - - - - - REQUEST - ASYNC - - + + + - - - + + + + + + REQUEST + + + + + ASYNC + + + + + + diff --git a/jetty-rewrite/src/main/config/modules/rewrite/rewrite-rules.xml b/jetty-rewrite/src/main/config/modules/rewrite/rewrite-rules.xml index 90e983f7b14..41be9bb7180 100644 --- a/jetty-rewrite/src/main/config/modules/rewrite/rewrite-rules.xml +++ b/jetty-rewrite/src/main/config/modules/rewrite/rewrite-rules.xml @@ -14,10 +14,10 @@ - /favicon.ico - Cache-Control - Max-Age=3600,public - true + /favicon.ico + Cache-Control + Max-Age=3600,public + true @@ -27,8 +27,8 @@ - /test/rewrite/ - /test/rewrite/info.html + /test/rewrite/ + /test/rewrite/info.html @@ -38,8 +38,8 @@ - /test/some/old/context - /test/rewritten/newcontext + /test/some/old/context + /test/rewritten/newcontext @@ -49,8 +49,8 @@ - /test/rewrite/for/* - /test/rewritten/ + /test/rewrite/for/* + /test/rewritten/ @@ -60,8 +60,8 @@ - (.*?)/reverse/([^/]*)/(.*) - $1/reverse/$3/$2 + (.*?)/reverse/([^/]*)/(.*) + $1/reverse/$3/$2 @@ -71,9 +71,9 @@ - /* - visited - yes + /* + visited + yes @@ -83,8 +83,8 @@ - /test/redirect/* - /test/redirected + /test/redirect/* + /test/redirected @@ -94,9 +94,9 @@ - /400Error - 400 - ResponsePatternRule Demo + /400Error + 400 + ResponsePatternRule Demo diff --git a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewriteHandler.java b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewriteHandler.java index 81db59e9457..a3e0f8bf7ac 100644 --- a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewriteHandler.java +++ b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewriteHandler.java @@ -146,24 +146,22 @@ import org.eclipse.jetty.server.handler.HandlerWrapper; * </New> * * <Set name="handler"> - * <New id="Handlers" class="org.eclipse.jetty.server.handler.HandlerCollection"> - * <Set name="handlers"> - * <Array type="org.eclipse.jetty.server.Handler"> - * <Item> - * <Ref id="RewriteHandler"/> - * </Item> - * <Item> - * <New id="Contexts" class="org.eclipse.jetty.server.handler.ContextHandlerCollection"/> - * </Item> - * <Item> - * <New id="DefaultHandler" class="org.eclipse.jetty.server.handler.DefaultHandler"/> - * </Item> - * <Item> - * <New id="RequestLog" class="org.eclipse.jetty.server.handler.RequestLogHandler"/> - * </Item> - * </Array> + * <Ref id="RewriteHandler"/> + * <Set name="handler"> + * <New id="Handlers" class="org.eclipse.jetty.server.handler.HandlerCollection"> + * <Set name="handlers"> + * <Array type="org.eclipse.jetty.server.Handler"> + * <Item> + * <New id="Contexts" class="org.eclipse.jetty.server.handler.ContextHandlerCollection"/> + * </Item> + * <Item> + * <New id="DefaultHandler" class="org.eclipse.jetty.server.handler.DefaultHandler"/> + * </Item> + * </Array> + * </Set> + * </New> * </Set> - * </New> + * </Ref> * </Set> * */ diff --git a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/CookiePatternRuleTest.java b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/CookiePatternRuleTest.java index 08253d96c50..9fd364b8627 100644 --- a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/CookiePatternRuleTest.java +++ b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/CookiePatternRuleTest.java @@ -76,11 +76,7 @@ public class CookiePatternRuleTest } }; - HandlerList handlers = new HandlerList(); - handlers.addHandler(rewriteHandler); - handlers.addHandler(dummyHandler); - - server.setHandler(handlers); + server.setHandler(new HandlerList(rewriteHandler, dummyHandler)); server.start(); } diff --git a/jetty-rewrite/src/test/resources/org.mortbay.jetty.rewrite.handler/jetty-rewrite.xml b/jetty-rewrite/src/test/resources/org.mortbay.jetty.rewrite.handler/jetty-rewrite.xml index dedac35bc32..2688e21964c 100644 --- a/jetty-rewrite/src/test/resources/org.mortbay.jetty.rewrite.handler/jetty-rewrite.xml +++ b/jetty-rewrite/src/test/resources/org.mortbay.jetty.rewrite.handler/jetty-rewrite.xml @@ -1,5 +1,4 @@ - - + @@ -9,263 +8,269 @@ - - - - - - - - 10 - 50 - 5 - 2 - - + + + + + + + 10 + 50 + 5 + 2 + + - - - - - - - - - - - - - 30000 - - - - - - - - - - - - - - - - - - - - - - - - - - requestedPath - true - - - - - - - /* - /test - - - - - - /session/ - 401 - Setting error code 401 - - - - - - *.jsp - Server - dexter webserver - - - - - - *.jsp - title - driven header purpose - - - - - - /test/dispatch - http://jetty.eclipse.org - - - - - - /test-jaas/$ - /demo - - - - - - X-Forwarded-Scheme - https - https - - - - - - - - - eclipse.com - www.eclipse.com - eclipse.org - www.eclipse.org - - - - - - - /* - CookiePatternRule - 1 - - - - - - - - + + + + + + + + + + - - - - - - - - - - - - - - - - - + + + 30000 - + + - - - - - - - - - - - - - - - - /contexts - 1 - - - + + + + + - - - - - - - - - - - - - - - - - - /webapps - false - true - false - /etc/webdefault.xml - - - + + + + - - - - - - - - - - - - Test Realm - /etc/realm.properties - - - - + + + + + + + + - - - - - - - - - - - /yyyy_mm_dd.request.log - yyyy_MM_dd - 90 - true - true - false - GMT + requestedPath + true + + + + + + + /* + /test + + + + + + /session/ + 401 + Setting error code 401 + + + + + + *.jsp + Server + dexter webserver + + + + + + *.jsp + title + driven header purpose + + + + + + /test/dispatch + http://jetty.eclipse.org + + + + + + /test-jaas/$ + /demo + + + + + + X-Forwarded-Scheme + https + https + + + + + + + + + eclipse.com + www.eclipse.com + eclipse.org + www.eclipse.org + + + + + + + /* + CookiePatternRule + 1 + + + + + + + + + + + + + + + + + + + + + + - + + - - - - true - 1000 + + + + + + + + + + + + + + + + + + /contexts + + 1 + + + + + + + + + + + + + + + + + + + + + + + + /webapps + + false + true + false + /etc/webdefault.xml + + + + + + + + + + + + + + + + + Test Realm + /etc/realm.properties + + + + + + + + + + + + + + + + + /yyyy_mm_dd.request.log + + yyyy_MM_dd + 90 + true + true + false + GMT + + + + + + + + true + 1000 diff --git a/jetty-runner/src/main/java/org/eclipse/jetty/runner/Runner.java b/jetty-runner/src/main/java/org/eclipse/jetty/runner/Runner.java index 0e67904d8f4..9b3ab625ca2 100644 --- a/jetty-runner/src/main/java/org/eclipse/jetty/runner/Runner.java +++ b/jetty-runner/src/main/java/org/eclipse/jetty/runner/Runner.java @@ -46,6 +46,7 @@ import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.server.handler.ContextHandlerCollection; import org.eclipse.jetty.server.handler.DefaultHandler; import org.eclipse.jetty.server.handler.HandlerCollection; +import org.eclipse.jetty.server.handler.HandlerList; import org.eclipse.jetty.server.handler.StatisticsHandler; import org.eclipse.jetty.server.session.SessionHandler; import org.eclipse.jetty.servlet.ServletContextHandler; @@ -341,7 +342,7 @@ public class Runner HandlerCollection handlers = (HandlerCollection)_server.getChildHandlerByClass(HandlerCollection.class); if (handlers == null) { - handlers = new HandlerCollection(); + handlers = new HandlerList(); _server.setHandler(handlers); } diff --git a/jetty-security/src/test/java/org/eclipse/jetty/security/AliasedConstraintTest.java b/jetty-security/src/test/java/org/eclipse/jetty/security/AliasedConstraintTest.java index 413bebee3ec..92d93d366d1 100644 --- a/jetty-security/src/test/java/org/eclipse/jetty/security/AliasedConstraintTest.java +++ b/jetty-security/src/test/java/org/eclipse/jetty/security/AliasedConstraintTest.java @@ -26,7 +26,6 @@ import java.util.stream.Stream; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.server.Connector; -import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.LocalConnector; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.ContextHandler; @@ -81,9 +80,7 @@ public class AliasedConstraintTest context.setContextPath("/ctx"); context.setResourceBase(MavenTestingUtils.getTestResourceDir("docroot").getAbsolutePath()); - HandlerList handlers = new HandlerList(); - handlers.setHandlers(new Handler[]{context, new DefaultHandler()}); - server.setHandler(handlers); + server.setHandler(new HandlerList(context, new DefaultHandler())); context.setHandler(session); // context.addAliasCheck(new AllowSymLinkAliasChecker()); diff --git a/jetty-server/src/main/config/etc/jetty.xml b/jetty-server/src/main/config/etc/jetty.xml index 0c6f3b4d948..cf4e989eee5 100644 --- a/jetty-server/src/main/config/etc/jetty.xml +++ b/jetty-server/src/main/config/etc/jetty.xml @@ -78,17 +78,13 @@ - + - - - - - - + + - + diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/PartialRFC2616Test.java b/jetty-server/src/test/java/org/eclipse/jetty/server/PartialRFC2616Test.java index a45a8d91be2..52dd7d94c6b 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/PartialRFC2616Test.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/PartialRFC2616Test.java @@ -27,7 +27,7 @@ import org.eclipse.jetty.http.HttpParser; import org.eclipse.jetty.logging.StacklessLogging; import org.eclipse.jetty.server.LocalConnector.LocalEndPoint; import org.eclipse.jetty.server.handler.ContextHandler; -import org.eclipse.jetty.server.handler.HandlerCollection; +import org.eclipse.jetty.server.handler.HandlerList; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -64,11 +64,7 @@ public class PartialRFC2616Test context.setContextPath("/"); context.setHandler(new DumpHandler()); - HandlerCollection collection = new HandlerCollection(); - collection.setHandlers(new Handler[] - {vcontext, context}); - - server.setHandler(collection); + server.setHandler(new HandlerList(vcontext, context)); server.start(); } diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ServerConnectorTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ServerConnectorTest.java index 11ce1879cc7..3fe424c2072 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/ServerConnectorTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ServerConnectorTest.java @@ -126,11 +126,7 @@ public class ServerConnectorTest connector.setPort(0); server.addConnector(connector); - HandlerList handlers = new HandlerList(); - handlers.addHandler(new ReuseInfoHandler()); - handlers.addHandler(new DefaultHandler()); - - server.setHandler(handlers); + server.setHandler(new HandlerList(new ReuseInfoHandler(), new DefaultHandler())); try { @@ -162,11 +158,7 @@ public class ServerConnectorTest connector.setReuseAddress(true); server.addConnector(connector); - HandlerList handlers = new HandlerList(); - handlers.addHandler(new ReuseInfoHandler()); - handlers.addHandler(new DefaultHandler()); - - server.setHandler(handlers); + server.setHandler(new HandlerList(new ReuseInfoHandler(), new DefaultHandler())); try { @@ -198,11 +190,7 @@ public class ServerConnectorTest connector.setReuseAddress(false); server.addConnector(connector); - HandlerList handlers = new HandlerList(); - handlers.addHandler(new ReuseInfoHandler()); - handlers.addHandler(new DefaultHandler()); - - server.setHandler(handlers); + server.setHandler(new HandlerList(new ReuseInfoHandler(), new DefaultHandler())); try { @@ -312,12 +300,9 @@ public class ServerConnectorTest connector.setPort(port); server.addConnector(connector); - HandlerList handlers = new HandlerList(); - handlers.addHandler(new DefaultHandler()); + server.setHandler(new HandlerList(new DefaultHandler())); - server.setHandler(handlers); - - IOException x = assertThrows(IOException.class, () -> server.start()); + IOException x = assertThrows(IOException.class, server::start); assertThat(x.getCause(), instanceOf(BindException.class)); assertThat(x.getMessage(), containsString("0.0.0.0:" + port)); } diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerCollectionTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerCollectionTest.java index f9c05bed48a..dd4ee094d0d 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerCollectionTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerCollectionTest.java @@ -352,12 +352,11 @@ public class ContextHandlerCollectionTest ContextHandler left = new ContextHandler("/left"); left.setHandler(new AsyncHandler("left")); - HandlerList centre = new HandlerList(); ContextHandler centreLeft = new ContextHandler("/leftcentre"); centreLeft.setHandler(new AsyncHandler("left of centre")); ContextHandler centreRight = new ContextHandler("/rightcentre"); centreRight.setHandler(new AsyncHandler("right of centre")); - centre.setHandlers(new Handler[]{centreLeft, new WrappedHandler(centreRight)}); + HandlerList centre = new HandlerList(centreLeft, new WrappedHandler(centreRight)); ContextHandler right = new ContextHandler("/right"); right.setHandler(new AsyncHandler("right")); diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerTest.java index dbbc0d6e274..598ceb86801 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerTest.java @@ -27,7 +27,6 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; import java.util.List; - import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import javax.servlet.http.HttpServletRequest; @@ -89,7 +88,7 @@ public class ContextHandlerTest IsHandledHandler handlerC = new IsHandledHandler(); contextC.setHandler(handlerC); - HandlerCollection c = new HandlerCollection(); + HandlerList c = new HandlerList(); c.addHandler(contextA); c.addHandler(contextB); @@ -178,7 +177,7 @@ public class ContextHandlerTest contextH.setHandler(handlerH); contextH.setVirtualHosts(new String[]{"*.com"}); - HandlerCollection c = new HandlerCollection(); + HandlerList c = new HandlerList(); c.addHandler(contextA); c.addHandler(contextB); c.addHandler(contextC); @@ -271,7 +270,7 @@ public class ContextHandlerTest } // Reversed order to check priority when multiple matches - HandlerCollection d = new HandlerCollection(); + HandlerList d = new HandlerList(); d.addHandler(contextH); d.addHandler(contextG); d.addHandler(contextF); diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/DefaultHandlerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/DefaultHandlerTest.java index bd88dee2952..1def256c28b 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/DefaultHandlerTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/DefaultHandlerTest.java @@ -25,7 +25,6 @@ import java.nio.charset.StandardCharsets; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.tools.HttpTester; -import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.junit.jupiter.api.AfterEach; @@ -52,9 +51,7 @@ public class DefaultHandlerTest ContextHandlerCollection contexts = new ContextHandlerCollection(); handler = new DefaultHandler(); - HandlerCollection handlers = new HandlerCollection(); - handlers.setHandlers(new Handler[]{contexts, handler}); - server.setHandler(handlers); + server.setHandler(new HandlerList(contexts, handler)); handler.setServeIcon(true); handler.setShowContexts(true); diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/HandlerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/HandlerTest.java index c3738b09663..99eb1264ef6 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/HandlerTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/HandlerTest.java @@ -36,7 +36,6 @@ import static org.junit.jupiter.api.Assertions.assertThrows; public class HandlerTest { - @Test public void testWrapperSetServer() { @@ -117,16 +116,16 @@ public class HandlerTest } @Test - public void testCollectionSetServer() + public void testHandlerListSetServer() { Server s = new Server(); - HandlerCollection a = new HandlerCollection(); - HandlerCollection b = new HandlerCollection(); - HandlerCollection b1 = new HandlerCollection(); - HandlerCollection b2 = new HandlerCollection(); - HandlerCollection c = new HandlerCollection(); - HandlerCollection c1 = new HandlerCollection(); - HandlerCollection c2 = new HandlerCollection(); + HandlerList a = new HandlerList(); + HandlerList b = new HandlerList(); + HandlerList b1 = new HandlerList(); + HandlerList b2 = new HandlerList(); + HandlerList c = new HandlerList(); + HandlerList c1 = new HandlerList(); + HandlerList c2 = new HandlerList(); a.addHandler(b); a.addHandler(c); @@ -143,16 +142,16 @@ public class HandlerTest } @Test - public void testCollectionServerSet() + public void testHandlerListServerSet() { Server s = new Server(); - HandlerCollection a = new HandlerCollection(); - HandlerCollection b = new HandlerCollection(); - HandlerCollection b1 = new HandlerCollection(); - HandlerCollection b2 = new HandlerCollection(); - HandlerCollection c = new HandlerCollection(); - HandlerCollection c1 = new HandlerCollection(); - HandlerCollection c2 = new HandlerCollection(); + HandlerList a = new HandlerList(); + HandlerList b = new HandlerList(); + HandlerList b1 = new HandlerList(); + HandlerList b2 = new HandlerList(); + HandlerList c = new HandlerList(); + HandlerList c1 = new HandlerList(); + HandlerList c2 = new HandlerList(); a.setServer(s); a.addHandler(b); @@ -169,24 +168,24 @@ public class HandlerTest } @Test - public void testCollectionThisLoop() + public void testHandlerListThisLoop() { - HandlerCollection a = new HandlerCollection(); + HandlerList a = new HandlerList(); IllegalStateException e = assertThrows(IllegalStateException.class, () -> a.addHandler(a)); assertThat(e.getMessage(), containsString("loop")); } @Test - public void testCollectionDeepLoop() + public void testHandlerListDeepLoop() { - HandlerCollection a = new HandlerCollection(); - HandlerCollection b = new HandlerCollection(); - HandlerCollection b1 = new HandlerCollection(); - HandlerCollection b2 = new HandlerCollection(); - HandlerCollection c = new HandlerCollection(); - HandlerCollection c1 = new HandlerCollection(); - HandlerCollection c2 = new HandlerCollection(); + HandlerList a = new HandlerList(); + HandlerList b = new HandlerList(); + HandlerList b1 = new HandlerList(); + HandlerList b2 = new HandlerList(); + HandlerList c = new HandlerList(); + HandlerList c1 = new HandlerList(); + HandlerList c2 = new HandlerList(); a.addHandler(b); a.addHandler(c); @@ -198,15 +197,15 @@ public class HandlerTest } @Test - public void testCollectionChainLoop() + public void testHandlerListChainLoop() { - HandlerCollection a = new HandlerCollection(); - HandlerCollection b = new HandlerCollection(); - HandlerCollection b1 = new HandlerCollection(); - HandlerCollection b2 = new HandlerCollection(); - HandlerCollection c = new HandlerCollection(); - HandlerCollection c1 = new HandlerCollection(); - HandlerCollection c2 = new HandlerCollection(); + HandlerList a = new HandlerList(); + HandlerList b = new HandlerList(); + HandlerList b1 = new HandlerList(); + HandlerList b2 = new HandlerList(); + HandlerList c = new HandlerList(); + HandlerList c1 = new HandlerList(); + HandlerList c2 = new HandlerList(); a.addHandler(c); b.setHandlers(new Handler[]{b1, b2}); diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/NcsaRequestLogTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/NcsaRequestLogTest.java index 4ee0fb69ddf..1053b71ec47 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/NcsaRequestLogTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/NcsaRequestLogTest.java @@ -432,8 +432,7 @@ public class NcsaRequestLogTest setup(logType); RequestLogHandler handler = new RequestLogHandler(); handler.setRequestLog(_log); - HandlerCollection handlers = new HandlerCollection(); - handlers.setHandlers(new Handler[]{handler, testHandler}); + HandlerList handlers = new HandlerList(handler, testHandler); _server.setHandler(handlers); startServer(); makeRequest(requestPath); @@ -454,8 +453,7 @@ public class NcsaRequestLogTest testHandler instanceof ResponseSendErrorHandler ); - HandlerCollection handlers = new HandlerCollection(); - handlers.setHandlers(new Handler[]{testHandler, handler}); + HandlerCollection handlers = new HandlerCollection(testHandler, handler); _server.setHandler(handlers); startServer(); makeRequest(requestPath); diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/SecuredRedirectHandlerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/SecuredRedirectHandlerTest.java index 37726292fcd..4e3dbadc0bd 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/SecuredRedirectHandlerTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/SecuredRedirectHandlerTest.java @@ -126,11 +126,7 @@ public class SecuredRedirectHandlerTest contextHandlers.setHandlers(new Handler[]{redirectHandler, rootContext, test1Context, test2Context}); // Create server level handler tree - HandlerList handlers = new HandlerList(); - handlers.addHandler(contextHandlers); - handlers.addHandler(new DefaultHandler()); // round things out - - server.setHandler(handlers); + server.setHandler(new HandlerList(contextHandlers, new DefaultHandler())); server.start(); diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncContextDispatchWithQueryStrings.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncContextDispatchWithQueryStrings.java index b174c80a9e5..ae01a30d649 100644 --- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncContextDispatchWithQueryStrings.java +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncContextDispatchWithQueryStrings.java @@ -26,7 +26,6 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.server.Connector; -import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.LocalConnector; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.DefaultHandler; @@ -60,10 +59,7 @@ public class AsyncContextDispatchWithQueryStrings _contextHandler.addServlet(new ServletHolder(new TestServlet()), "/firstDispatchWithNewQueryString"); _contextHandler.addServlet(new ServletHolder(new TestServlet()), "/secondDispatchNewValueForExistingQueryString"); - HandlerList handlers = new HandlerList(); - handlers.setHandlers(new Handler[]{_contextHandler, new DefaultHandler()}); - - _server.setHandler(handlers); + _server.setHandler(new HandlerList(_contextHandler, new DefaultHandler())); _server.start(); } diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncContextTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncContextTest.java index 23b3ad97101..48cf1380151 100644 --- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncContextTest.java +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncContextTest.java @@ -34,7 +34,6 @@ import javax.servlet.http.HttpServletResponseWrapper; import org.eclipse.jetty.http.tools.HttpTester; import org.eclipse.jetty.logging.StacklessLogging; -import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpChannel; import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.server.LocalConnector; @@ -99,11 +98,7 @@ public class AsyncContextTest errorHandler.addErrorPage(500, "/error/500"); errorHandler.addErrorPage(IOException.class.getName(), "/error/IOE"); - HandlerList handlers = new HandlerList(); - handlers.setHandlers(new Handler[] - {_contextHandler, new DefaultHandler()}); - - _server.setHandler(handlers); + _server.setHandler(new HandlerList(_contextHandler, new DefaultHandler())); _server.start(); } diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DefaultHandlerTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DefaultHandlerTest.java index d3d6bbf816d..d7cd7aebf7f 100644 --- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DefaultHandlerTest.java +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DefaultHandlerTest.java @@ -70,12 +70,7 @@ public class DefaultHandlerTest contextFoo.setContextPath("/foo"); contextFoo.setBaseResource(new PathResource(baseFoo)); - HandlerList handlers = new HandlerList(); - handlers.addHandler(contextA); - handlers.addHandler(contextFoo); - handlers.addHandler(new DefaultHandler()); - - server.setHandler(handlers); + server.setHandler(new HandlerList(contextA, contextFoo, new DefaultHandler())); server.start(); } diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletRequestLogTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletRequestLogTest.java index 0965236b8a4..6c916b53ed8 100644 --- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletRequestLogTest.java +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletRequestLogTest.java @@ -47,7 +47,7 @@ import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.handler.ContextHandlerCollection; import org.eclipse.jetty.server.handler.DefaultHandler; import org.eclipse.jetty.server.handler.ErrorHandler; -import org.eclipse.jetty.server.handler.HandlerCollection; +import org.eclipse.jetty.server.handler.HandlerList; import org.eclipse.jetty.toolchain.test.IO; import org.eclipse.jetty.util.component.AbstractLifeCycle; import org.junit.jupiter.api.Assertions; @@ -301,7 +301,7 @@ public class ServletRequestLogTest // First the behavior as defined in etc/jetty.xml // id="Handlers" - HandlerCollection handlers = new HandlerCollection(); + HandlerList handlers = new HandlerList(); // id="Contexts" ContextHandlerCollection contexts = new ContextHandlerCollection(); // id="DefaultHandler" @@ -389,16 +389,9 @@ public class ServletRequestLogTest ErrorHandler errorHandler = new ErrorHandler(); server.addBean(errorHandler); - // First the behavior as defined in etc/jetty.xml - // id="Handlers" - HandlerCollection handlers = new HandlerCollection(); - // id="Contexts" ContextHandlerCollection contexts = new ContextHandlerCollection(); - // id="DefaultHandler" DefaultHandler defaultHandler = new DefaultHandler(); - - handlers.setHandlers(new Handler[]{contexts, defaultHandler}); - server.setHandler(handlers); + server.setHandler(new HandlerList(contexts, defaultHandler)); // Next the behavior as defined by etc/jetty-requestlog.xml // the id="RequestLog" @@ -477,16 +470,8 @@ public class ServletRequestLogTest connector.setPort(0); server.setConnectors(new Connector[]{connector}); - // First the behavior as defined in etc/jetty.xml - // id="Handlers" - HandlerCollection handlers = new HandlerCollection(); - // id="Contexts" ContextHandlerCollection contexts = new ContextHandlerCollection(); - // id="DefaultHandler" - DefaultHandler defaultHandler = new DefaultHandler(); - - handlers.setHandlers(new Handler[]{contexts, defaultHandler}); - server.setHandler(handlers); + server.setHandler(new HandlerList(contexts, new DefaultHandler())); // Next the behavior as defined by etc/jetty-requestlog.xml // the id="RequestLog" @@ -571,15 +556,11 @@ public class ServletRequestLogTest server.setConnectors(new Connector[]{connector}); // First the behavior as defined in etc/jetty.xml (as is) - // id="Handlers" - HandlerCollection handlers = new HandlerCollection(); // id="Contexts" ContextHandlerCollection contexts = new ContextHandlerCollection(); // id="DefaultHandler" DefaultHandler defaultHandler = new DefaultHandler(); - - handlers.setHandlers(new Handler[]{contexts, defaultHandler}); - server.setHandler(handlers); + server.setHandler(new HandlerList(contexts, defaultHandler)); // Next the proposed behavioral change to etc/jetty-requestlog.xml // the id="RequestLog" diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletWrapperTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletWrapperTest.java index d9ca26b31d6..3352096bbc8 100644 --- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletWrapperTest.java +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletWrapperTest.java @@ -71,11 +71,7 @@ public class ServletWrapperTest FilterHolder filterHolder = context.addFilter(WrapFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST)); filterHolder.setAsyncSupported(true); - HandlerList handlers = new HandlerList(); - handlers.addHandler(context); - handlers.addHandler(new DefaultHandler()); - - server.setHandler(handlers); + server.setHandler(new HandlerList(context, new DefaultHandler())); server.start(); } diff --git a/jetty-spring/src/main/config/etc/jetty-spring.xml b/jetty-spring/src/main/config/etc/jetty-spring.xml index 637450d51a7..40a5e7cf1bb 100644 --- a/jetty-spring/src/main/config/etc/jetty-spring.xml +++ b/jetty-spring/src/main/config/etc/jetty-spring.xml @@ -29,7 +29,7 @@ - + diff --git a/jetty-spring/src/test/resources/org/eclipse/jetty/spring/jetty.xml b/jetty-spring/src/test/resources/org/eclipse/jetty/spring/jetty.xml index 28915187ad8..57b87cc407b 100644 --- a/jetty-spring/src/test/resources/org/eclipse/jetty/spring/jetty.xml +++ b/jetty-spring/src/test/resources/org/eclipse/jetty/spring/jetty.xml @@ -21,7 +21,7 @@ - + diff --git a/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/HugeResourceTest.java b/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/HugeResourceTest.java index 8bd2d5c1916..7bb613c79c5 100644 --- a/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/HugeResourceTest.java +++ b/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/HugeResourceTest.java @@ -188,11 +188,7 @@ public class HugeResourceTest ServletHolder holder = context.addServlet(MultipartServlet.class, "/multipart"); holder.getRegistration().setMultipartConfig(multipartConfig); - HandlerList handlers = new HandlerList(); - handlers.addHandler(context); - handlers.addHandler(new DefaultHandler()); - - server.setHandler(handlers); + server.setHandler(new HandlerList(context, new DefaultHandler())); server.start(); } diff --git a/jetty-websocket/websocket-javax-tests/src/main/java/org/eclipse/jetty/websocket/javax/tests/CoreServer.java b/jetty-websocket/websocket-javax-tests/src/main/java/org/eclipse/jetty/websocket/javax/tests/CoreServer.java index f9704788735..d3a3314fd6e 100644 --- a/jetty-websocket/websocket-javax-tests/src/main/java/org/eclipse/jetty/websocket/javax/tests/CoreServer.java +++ b/jetty-websocket/websocket-javax-tests/src/main/java/org/eclipse/jetty/websocket/javax/tests/CoreServer.java @@ -81,10 +81,7 @@ public class CoreServer extends ContainerLifeCycle server.addConnector(connector); // Add Handler - HandlerList handlers = new HandlerList(); - handlers.addHandler(new WebSocketUpgradeHandler(negotiator)); - handlers.addHandler(new DefaultHandler()); - server.setHandler(handlers); + server.setHandler(new HandlerList(new WebSocketUpgradeHandler(negotiator), new DefaultHandler())); // Start Server addBean(server); diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/RestartContextTest.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/RestartContextTest.java index dc845795664..4fcbf33318c 100644 --- a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/RestartContextTest.java +++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/RestartContextTest.java @@ -72,12 +72,7 @@ public class RestartContextTest context.addEventListener(new AddEndpointListener()); // Setup handler tree - HandlerList handlers = new HandlerList(); - handlers.addHandler(context); - handlers.addHandler(new DefaultHandler()); - - // Add handler tree to server - server.setHandler(handlers); + server.setHandler(new HandlerList(context, new DefaultHandler())); // Start server server.start(); @@ -114,9 +109,7 @@ public class RestartContextTest }); // Setup handler tree - HandlerList handlers = new HandlerList(); - handlers.addHandler(context); - handlers.addHandler(new DefaultHandler()); + HandlerList handlers = new HandlerList(context, new DefaultHandler()); // Add handler tree to server server.setHandler(handlers); diff --git a/jetty-websocket/websocket-jetty-server/src/test/java/org/eclipse/jetty/websocket/server/browser/BrowserDebugTool.java b/jetty-websocket/websocket-jetty-server/src/test/java/org/eclipse/jetty/websocket/server/browser/BrowserDebugTool.java index 88ea1275982..2ddb8dfb14c 100644 --- a/jetty-websocket/websocket-jetty-server/src/test/java/org/eclipse/jetty/websocket/server/browser/BrowserDebugTool.java +++ b/jetty-websocket/websocket-jetty-server/src/test/java/org/eclipse/jetty/websocket/server/browser/BrowserDebugTool.java @@ -114,11 +114,7 @@ public class BrowserDebugTool ServletHolder defHolder = new ServletHolder("default", DefaultServlet.class); context.addServlet(defHolder, "/"); - HandlerList handlers = new HandlerList(); - handlers.addHandler(context); - handlers.addHandler(new DefaultHandler()); - - server.setHandler(handlers); + server.setHandler(new HandlerList(context, new DefaultHandler())); LOG.info("{} setup on port {}", this.getClass().getName(), port); } diff --git a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/BadNetworkTest.java b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/BadNetworkTest.java index 4efcc4ab8ca..d3d5b08783a 100644 --- a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/BadNetworkTest.java +++ b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/BadNetworkTest.java @@ -86,10 +86,7 @@ public class BadNetworkTest }); context.addServlet(holder, "/ws"); - HandlerList handlers = new HandlerList(); - handlers.addHandler(context); - handlers.addHandler(new DefaultHandler()); - server.setHandler(handlers); + server.setHandler(new HandlerList(context, new DefaultHandler())); JettyWebSocketServletContainerInitializer.configure(context, null); server.start(); diff --git a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientCloseTest.java b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientCloseTest.java index 5d3534fc585..2561ef27162 100644 --- a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientCloseTest.java +++ b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientCloseTest.java @@ -136,10 +136,7 @@ public class ClientCloseTest }); context.addServlet(holder, "/ws"); - HandlerList handlers = new HandlerList(); - handlers.addHandler(context); - handlers.addHandler(new DefaultHandler()); - server.setHandler(handlers); + server.setHandler(new HandlerList(context, new DefaultHandler())); JettyWebSocketServletContainerInitializer.configure(context, null); server.start(); diff --git a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientSessionsTest.java b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientSessionsTest.java index d612814d06d..4ce370a41a8 100644 --- a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientSessionsTest.java +++ b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientSessionsTest.java @@ -79,10 +79,7 @@ public class ClientSessionsTest }); context.addServlet(holder, "/ws"); - HandlerList handlers = new HandlerList(); - handlers.addHandler(context); - handlers.addHandler(new DefaultHandler()); - server.setHandler(handlers); + server.setHandler(new HandlerList(context, new DefaultHandler())); JettyWebSocketServletContainerInitializer.configure(context, null); server.start(); diff --git a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/SlowClientTest.java b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/SlowClientTest.java index 978d1fda795..97c21eb9993 100644 --- a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/SlowClientTest.java +++ b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/SlowClientTest.java @@ -81,11 +81,7 @@ public class SlowClientTest }); context.addServlet(websocket, "/ws"); - HandlerList handlers = new HandlerList(); - handlers.addHandler(context); - handlers.addHandler(new DefaultHandler()); - - server.setHandler(handlers); + server.setHandler(new HandlerList(context, new DefaultHandler())); JettyWebSocketServletContainerInitializer.configure(context, null); server.start(); diff --git a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/FrameAnnotationTest.java b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/FrameAnnotationTest.java index c79c67582f5..bbb3f8060ea 100644 --- a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/FrameAnnotationTest.java +++ b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/FrameAnnotationTest.java @@ -90,11 +90,7 @@ public class FrameAnnotationTest context.addServlet(closeEndpoint, "/ws"); JettyWebSocketServletContainerInitializer.configure(context, null); - HandlerList handlers = new HandlerList(); - handlers.addHandler(context); - handlers.addHandler(new DefaultHandler()); - - server.setHandler(handlers); + server.setHandler(new HandlerList(context, new DefaultHandler())); server.start(); } diff --git a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/FrameListenerTest.java b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/FrameListenerTest.java index dc2107f2727..7306732bf22 100644 --- a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/FrameListenerTest.java +++ b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/FrameListenerTest.java @@ -83,11 +83,7 @@ public class FrameListenerTest context.addServlet(closeEndpoint, "/ws"); JettyWebSocketServletContainerInitializer.configure(context, null); - HandlerList handlers = new HandlerList(); - handlers.addHandler(context); - handlers.addHandler(new DefaultHandler()); - - server.setHandler(handlers); + server.setHandler(new HandlerList(context, new DefaultHandler())); server.start(); } diff --git a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/PartialListenerTest.java b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/PartialListenerTest.java index 1c2804d59f4..01ee6ed0346 100644 --- a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/PartialListenerTest.java +++ b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/PartialListenerTest.java @@ -86,11 +86,7 @@ public class PartialListenerTest context.addServlet(closeEndpoint, "/ws"); JettyWebSocketServletContainerInitializer.configure(context, null); - HandlerList handlers = new HandlerList(); - handlers.addHandler(context); - handlers.addHandler(new DefaultHandler()); - - server.setHandler(handlers); + server.setHandler(new HandlerList(context, new DefaultHandler())); server.start(); } diff --git a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/ServerCloseTest.java b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/ServerCloseTest.java index 3277da8b00c..2229e931bd4 100644 --- a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/ServerCloseTest.java +++ b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/ServerCloseTest.java @@ -85,11 +85,7 @@ public class ServerCloseTest }); context.addServlet(closeEndpoint, "/ws"); - HandlerList handlers = new HandlerList(); - handlers.addHandler(context); - handlers.addHandler(new DefaultHandler()); - - server.setHandler(handlers); + server.setHandler(new HandlerList(context, new DefaultHandler())); JettyWebSocketServletContainerInitializer.configure(context, null); server.start(); diff --git a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/SlowServerTest.java b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/SlowServerTest.java index f48f3846bc7..c27e8251807 100644 --- a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/SlowServerTest.java +++ b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/SlowServerTest.java @@ -81,11 +81,7 @@ public class SlowServerTest }); context.addServlet(websocket, "/ws"); - HandlerList handlers = new HandlerList(); - handlers.addHandler(context); - handlers.addHandler(new DefaultHandler()); - - server.setHandler(handlers); + server.setHandler(new HandlerList(context, new DefaultHandler())); JettyWebSocketServletContainerInitializer.configure(context, null); server.start(); diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/DeploymentErrorTest.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/DeploymentErrorTest.java index 8d823d125c9..f4776e79653 100644 --- a/tests/test-integration/src/test/java/org/eclipse/jetty/test/DeploymentErrorTest.java +++ b/tests/test-integration/src/test/java/org/eclipse/jetty/test/DeploymentErrorTest.java @@ -41,13 +41,12 @@ import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.io.RuntimeIOException; import org.eclipse.jetty.logging.StacklessLogging; -import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.server.handler.ContextHandlerCollection; import org.eclipse.jetty.server.handler.DefaultHandler; -import org.eclipse.jetty.server.handler.HandlerCollection; +import org.eclipse.jetty.server.handler.HandlerList; import org.eclipse.jetty.toolchain.test.FS; import org.eclipse.jetty.toolchain.test.IO; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; @@ -113,10 +112,7 @@ public class DeploymentErrorTest server.addBean(deploymentManager); // Server handlers - HandlerCollection handlers = new HandlerCollection(); - handlers.setHandlers(new Handler[] - {contexts, new DefaultHandler()}); - server.setHandler(handlers); + server.setHandler(new HandlerList(contexts, new DefaultHandler())); // Setup Configurations Configurations.setServerDefault(server) diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/DigestPostTest.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/DigestPostTest.java index 059303e8c24..57004f83234 100644 --- a/tests/test-integration/src/test/java/org/eclipse/jetty/test/DigestPostTest.java +++ b/tests/test-integration/src/test/java/org/eclipse/jetty/test/DigestPostTest.java @@ -45,12 +45,11 @@ import org.eclipse.jetty.security.ConstraintMapping; import org.eclipse.jetty.security.ConstraintSecurityHandler; import org.eclipse.jetty.security.authentication.DigestAuthenticator; import org.eclipse.jetty.server.Connector; -import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.NetworkConnector; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.handler.DefaultHandler; -import org.eclipse.jetty.server.handler.HandlerCollection; +import org.eclipse.jetty.server.handler.HandlerList; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.TypeUtil; @@ -141,10 +140,7 @@ public class DigestPostTest security.setConstraintMappings(Collections.singletonList(mapping)); - HandlerCollection handlers = new HandlerCollection(); - handlers.setHandlers(new Handler[] - {context, new DefaultHandler()}); - _server.setHandler(handlers); + _server.setHandler(new HandlerList(context, new DefaultHandler())); _server.start(); } diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/FailedSelectorTest.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/FailedSelectorTest.java index f31db9e212f..a1aa817edd3 100644 --- a/tests/test-integration/src/test/java/org/eclipse/jetty/test/FailedSelectorTest.java +++ b/tests/test-integration/src/test/java/org/eclipse/jetty/test/FailedSelectorTest.java @@ -116,11 +116,7 @@ public class FailedSelectorTest ServletHolder closeHolder = new ServletHolder(new CloseSelectorServlet(connector)); context.addServlet(closeHolder, "/selector/close"); - HandlerList handlers = new HandlerList(); - handlers.addHandler(context); - handlers.addHandler(new DefaultHandler()); - - server.setHandler(handlers); + server.setHandler(new HandlerList(context, new DefaultHandler())); server.start(); } diff --git a/tests/test-integration/src/test/resources/DefaultHandler.xml b/tests/test-integration/src/test/resources/DefaultHandler.xml index 992bea97225..e23d89e6d0e 100644 --- a/tests/test-integration/src/test/resources/DefaultHandler.xml +++ b/tests/test-integration/src/test/resources/DefaultHandler.xml @@ -20,11 +20,11 @@ - + - + diff --git a/tests/test-integration/src/test/resources/RFC2616Base.xml b/tests/test-integration/src/test/resources/RFC2616Base.xml index ed55b90a72d..aad3e838d43 100644 --- a/tests/test-integration/src/test/resources/RFC2616Base.xml +++ b/tests/test-integration/src/test/resources/RFC2616Base.xml @@ -33,7 +33,7 @@ - + @@ -48,7 +48,7 @@ /virtualhost - + virtual @@ -56,7 +56,7 @@ /tests /default - + default diff --git a/tests/test-integration/src/test/resources/basic-server.xml b/tests/test-integration/src/test/resources/basic-server.xml index fefca7e39e8..74283e41641 100644 --- a/tests/test-integration/src/test/resources/basic-server.xml +++ b/tests/test-integration/src/test/resources/basic-server.xml @@ -19,7 +19,7 @@ - + diff --git a/tests/test-loginservice/src/test/java/org/eclipse/jetty/DatabaseLoginServiceTestServer.java b/tests/test-loginservice/src/test/java/org/eclipse/jetty/DatabaseLoginServiceTestServer.java index 994a8607144..3ac0a52602d 100644 --- a/tests/test-loginservice/src/test/java/org/eclipse/jetty/DatabaseLoginServiceTestServer.java +++ b/tests/test-loginservice/src/test/java/org/eclipse/jetty/DatabaseLoginServiceTestServer.java @@ -41,10 +41,9 @@ import org.eclipse.jetty.security.ConstraintMapping; import org.eclipse.jetty.security.ConstraintSecurityHandler; import org.eclipse.jetty.security.LoginService; import org.eclipse.jetty.security.authentication.BasicAuthenticator; -import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.AbstractHandler; -import org.eclipse.jetty.server.handler.HandlerCollection; +import org.eclipse.jetty.server.handler.HandlerList; import org.eclipse.jetty.servlet.DefaultServlet; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; @@ -236,8 +235,6 @@ public class DatabaseLoginServiceTestServer _handler = new TestHandler(_resourceBase); - HandlerCollection handlers = new HandlerCollection(); - handlers.setHandlers(new Handler[]{_handler, root}); - security.setHandler(handlers); + security.setHandler(new HandlerList(_handler, root)); } } diff --git a/tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/RequestDispatchedSessionTest.java b/tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/RequestDispatchedSessionTest.java index 935577a4f14..7a7b6cae5ee 100644 --- a/tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/RequestDispatchedSessionTest.java +++ b/tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/RequestDispatchedSessionTest.java @@ -65,11 +65,7 @@ public class RequestDispatchedSessionTest contextHandler.addServlet(ShowUserServlet.class, "/user"); contextHandler.addServlet(DefaultServlet.class, "/"); - HandlerList handlers = new HandlerList(); - handlers.addHandler(contextHandler); - handlers.addHandler(new DefaultHandler()); - - server.setHandler(handlers); + server.setHandler(new HandlerList(contextHandler, new DefaultHandler())); server.start(); } diff --git a/tests/test-webapps/test-jetty-webapp/src/test/java/org/eclipse/jetty/TestServer.java b/tests/test-webapps/test-jetty-webapp/src/test/java/org/eclipse/jetty/TestServer.java index 31b249b6340..cba5f2233b5 100644 --- a/tests/test-webapps/test-jetty-webapp/src/test/java/org/eclipse/jetty/TestServer.java +++ b/tests/test-webapps/test-jetty-webapp/src/test/java/org/eclipse/jetty/TestServer.java @@ -32,7 +32,6 @@ import org.eclipse.jetty.jmx.MBeanContainer; import org.eclipse.jetty.security.HashLoginService; import org.eclipse.jetty.server.CustomRequestLog; import org.eclipse.jetty.server.ForwardedRequestCustomizer; -import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.server.Request; @@ -42,7 +41,7 @@ import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.server.handler.ContextHandlerCollection; import org.eclipse.jetty.server.handler.DefaultHandler; -import org.eclipse.jetty.server.handler.HandlerCollection; +import org.eclipse.jetty.server.handler.HandlerList; import org.eclipse.jetty.server.handler.HandlerWrapper; import org.eclipse.jetty.server.handler.ResourceHandler; import org.eclipse.jetty.server.session.DefaultSessionCache; @@ -98,10 +97,8 @@ public class TestServer server.addConnector(httpConnector); // Handlers - HandlerCollection handlers = new HandlerCollection(); ContextHandlerCollection contexts = new ContextHandlerCollection(); - handlers.setHandlers(new Handler[] - {contexts, new DefaultHandler()}); + HandlerList handlers = new HandlerList(contexts, new DefaultHandler()); // Add restart handler to test the ability to save sessions and restart RestartHandler restart = new RestartHandler(); diff --git a/tests/test-webapps/test-proxy-webapp/src/test/java/org/eclipse/jetty/TestTransparentProxyServer.java b/tests/test-webapps/test-proxy-webapp/src/test/java/org/eclipse/jetty/TestTransparentProxyServer.java index 1bd261c1111..96a88bb834b 100644 --- a/tests/test-webapps/test-proxy-webapp/src/test/java/org/eclipse/jetty/TestTransparentProxyServer.java +++ b/tests/test-webapps/test-proxy-webapp/src/test/java/org/eclipse/jetty/TestTransparentProxyServer.java @@ -25,7 +25,6 @@ import org.eclipse.jetty.http2.HTTP2Cipher; import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory; import org.eclipse.jetty.jmx.MBeanContainer; import org.eclipse.jetty.server.ForwardedRequestCustomizer; -import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.server.SecureRequestCustomizer; @@ -34,7 +33,7 @@ import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.SslConnectionFactory; import org.eclipse.jetty.server.handler.ContextHandlerCollection; import org.eclipse.jetty.server.handler.DefaultHandler; -import org.eclipse.jetty.server.handler.HandlerCollection; +import org.eclipse.jetty.server.handler.HandlerList; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.eclipse.jetty.webapp.WebAppContext; @@ -107,12 +106,8 @@ public class TestTransparentProxyServer server.addConnector(http2Connector); // Handlers - HandlerCollection handlers = new HandlerCollection(); ContextHandlerCollection contexts = new ContextHandlerCollection(); - handlers.setHandlers(new Handler[] - {contexts, new DefaultHandler()}); - - server.setHandler(handlers); + server.setHandler(new HandlerList(contexts, new DefaultHandler())); // Setup proxy webapp WebAppContext webapp = new WebAppContext(); From d681f108536442f29b6bde54042e56220ad2bc82 Mon Sep 17 00:00:00 2001 From: Olivier Lamy Date: Thu, 16 Apr 2020 11:19:47 +1000 Subject: [PATCH 087/101] upgrade spifly to 1.2.4 and replace jdk13 build with jdk14 (#4780) Signed-off-by: olivier lamy --- Jenkinsfile | 4 ++-- jetty-osgi/test-jetty-osgi/pom.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 4477293c51e..2f39121af28 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -40,11 +40,11 @@ pipeline { } } - stage("Build / Test - JDK13") { + stage("Build / Test - JDK14") { agent { node { label 'linux' } } steps { timeout(time: 120, unit: 'MINUTES') { - mavenBuild("jdk13", "-T3 -Pmongodb clean install", "maven3", true) + mavenBuild("jdk14", "-T3 -Pmongodb clean install", "maven3", true) warnings consoleParsers: [[parserName: 'Maven'], [parserName: 'Java']] junit testResults: '**/target/surefire-reports/*.xml,**/target/invoker-reports/TEST*.xml' } diff --git a/jetty-osgi/test-jetty-osgi/pom.xml b/jetty-osgi/test-jetty-osgi/pom.xml index 01be0b6dfdb..18f4c1d8c9b 100644 --- a/jetty-osgi/test-jetty-osgi/pom.xml +++ b/jetty-osgi/test-jetty-osgi/pom.xml @@ -182,7 +182,7 @@ org.apache.aries.spifly org.apache.aries.spifly.dynamic.bundle - 1.2.3 + 1.2.4 test From fb8c8270b323092838ec6a617b6f576896f6109b Mon Sep 17 00:00:00 2001 From: Olivier Lamy Date: Thu, 16 Apr 2020 11:19:47 +1000 Subject: [PATCH 088/101] upgrade spifly to 1.2.4 and replace jdk13 build with jdk14 (#4780) Signed-off-by: olivier lamy --- Jenkinsfile | 4 ++-- jetty-osgi/test-jetty-osgi/pom.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 2e5ab191b26..270223b4297 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -52,11 +52,11 @@ pipeline { } } - stage("Build / Test - JDK13") { + stage("Build / Test - JDK14") { agent { node { label 'linux' } } steps { timeout(time: 120, unit: 'MINUTES') { - mavenBuild("jdk13", "-T3 -Pmongodb clean install", "maven3", true) + mavenBuild("jdk14", "-T3 -Pmongodb clean install", "maven3", true) warnings consoleParsers: [[parserName: 'Maven'], [parserName: 'Java']] junit testResults: '**/target/surefire-reports/*.xml,**/target/invoker-reports/TEST*.xml' } diff --git a/jetty-osgi/test-jetty-osgi/pom.xml b/jetty-osgi/test-jetty-osgi/pom.xml index 48d65d94c39..4bfcd4ea4f8 100644 --- a/jetty-osgi/test-jetty-osgi/pom.xml +++ b/jetty-osgi/test-jetty-osgi/pom.xml @@ -159,7 +159,7 @@ org.apache.aries.spifly org.apache.aries.spifly.dynamic.bundle - 1.2.3 + 1.2.4 test From bea09425c8517483324e69c288c6f2326439c9a8 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Fri, 17 Apr 2020 16:16:53 +0200 Subject: [PATCH 089/101] Improvements to the Jetty server documentation. Using one-line-per-sentence. Signed-off-by: Simone Bordet --- .../embedded-guide/client/client-io-arch.adoc | 133 +++------- .../embedded-guide/client/client.adoc | 19 +- .../client/http/client-http-api.adoc | 73 ++---- .../http/client-http-authentication.adoc | 46 +--- .../http/client-http-configuration.adoc | 49 ++-- .../client/http/client-http-cookie.adoc | 6 +- .../client/http/client-http-intro.adoc | 128 +++------ .../client/http/client-http-proxy.adoc | 28 +- .../client/http/client-http-transport.adoc | 109 +++----- .../client/http2/client-http2.adoc | 116 +++------ .../main/asciidoc/embedded-guide/http2.adoc | 100 ++------ .../main/asciidoc/embedded-guide/io-arch.adoc | 190 ++++---------- .../server/http/server-http-connector.adoc | 137 +++------- .../http/server-http-handler-implement.adoc | 24 +- .../server/http/server-http-handler-use.adoc | 242 +++++------------- .../server/http/server-http-handler.adoc | 56 +--- .../server/http/server-http.adoc | 71 ++--- .../server/http2/server-http2.adoc | 82 ++---- .../embedded-guide/server/server-io-arch.adoc | 6 +- .../embedded-guide/server/server.adoc | 26 +- 20 files changed, 455 insertions(+), 1186 deletions(-) diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/client/client-io-arch.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/client-io-arch.adoc index 8b11d289e06..57686e6f4a4 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/client/client-io-arch.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/client-io-arch.adoc @@ -19,44 +19,32 @@ [[eg-client-io-arch]] === Client Libraries I/O Architecture -The Jetty client libraries provide the basic components and APIs to implement -a network client. +The Jetty client libraries provide the basic components and APIs to implement a network client. -They build on the common xref:eg-io-arch[Jetty I/O Architecture] and provide client -specific concepts (such as establishing a connection to a server). +They build on the common xref:eg-io-arch[Jetty I/O Architecture] and provide client specific concepts (such as establishing a connection to a server). There are conceptually two layers that compose the Jetty client libraries: -. xref:eg-client-io-arch-network[The network layer], that handles the low level -I/O and deals with buffers, threads, etc. -. xref:eg-client-io-arch-protocol[The protocol layer], that handles the parsing -of bytes read from the network and the generation of bytes to write to the -network. +. xref:eg-client-io-arch-network[The network layer], that handles the low level I/O and deals with buffers, threads, etc. +. xref:eg-client-io-arch-protocol[The protocol layer], that handles the parsing of bytes read from the network and the generation of bytes to write to the network. [[eg-client-io-arch-network]] ==== Client Libraries Network Layer -The Jetty client libraries use the common I/O design described in -link:#eg-io-arch[this section]. -The main client-side component is the -link:{JDURL}/org/eclipse/jetty/io/ClientConnector.html[`ClientConnector`]. +The Jetty client libraries use the common I/O design described in link:#eg-io-arch[this section]. +The main client-side component is the link:{JDURL}/org/eclipse/jetty/io/ClientConnector.html[`ClientConnector`]. -The `ClientConnector` primarily wraps the -link:{JDURL}/org/eclipse/jetty/io/SelectorManager.html[`SelectorManager`] -and aggregates other four components: +The `ClientConnector` primarily wraps the link:{JDURL}/org/eclipse/jetty/io/SelectorManager.html[`SelectorManager`] and aggregates other four components: * a thread pool (in form of an `java.util.concurrent.Executor`) * a scheduler (in form of `org.eclipse.jetty.util.thread.Scheduler`) * a byte buffer pool (in form of `org.eclipse.jetty.io.ByteBufferPool`) * a TLS factory (in form of `org.eclipse.jetty.util.ssl.SslContextFactory.Client`) -The `ClientConnector` is where you want to set those components after you -have configured them. -If you don't explicitly set those components on the `ClientConnector`, then -appropriate defaults will be chosen when the `ClientConnector` starts. +The `ClientConnector` is where you want to set those components after you have configured them. +If you don't explicitly set those components on the `ClientConnector`, then appropriate defaults will be chosen when the `ClientConnector` starts. -The simplest example that creates and starts a `ClientConnector` is the -following: +The simplest example that creates and starts a `ClientConnector` is the following: [source,java,indent=0] ---- @@ -70,119 +58,72 @@ A more typical example: include::../{doc_code}/embedded/client/ClientConnectorDocs.java[tags=typical] ---- -A more advanced example that customizes the `ClientConnector` by overriding -some of its methods: +A more advanced example that customizes the `ClientConnector` by overriding some of its methods: [source,java,indent=0] ---- include::../{doc_code}/embedded/client/ClientConnectorDocs.java[tags=advanced] ---- -Since `ClientConnector` is the component that handles the low-level network, it -is also the component where you want to configure the low-level network configuration. +Since `ClientConnector` is the component that handles the low-level network, it is also the component where you want to configure the low-level network configuration. The most common parameters are: -* `ClientConnector.selectors`: the number of ``java.nio.Selector``s components -(defaults to `1`) that are present to handle the ``SocketChannel``s opened by -the `ClientConnector`. You typically want to increase the number of selectors -only for those use cases where each selector should handle more than few hundreds -_concurrent_ socket events. -For example, one selector typically runs well for `250` _concurrent_ socket -events; as a rule of thumb, you can multiply that number by `10` to obtain the -number of opened sockets a selector can handle (`2500`), based on the assumption -that not all the `2500` sockets will be active _at the same time_. -* `ClientConnector.idleTimeout`: the duration of time after which -`ClientConnector` closes a socket due to inactivity (defaults to `30` seconds). -This is an important parameter to configure, and you typically want the client -idle timeout to be shorter than the server idle timeout, to avoid race -conditions where the client attempts to use a socket just before the client-side -idle timeout expires, but the server-side idle timeout has already expired and -the is already closing the socket. -* `ClientConnector.connectBlocking`: whether the operation of connecting a -socket to the server (i.e. `SocketChannel.connect(SocketAddress)`) must be a -blocking or a non-blocking operation (defaults to `false`). +* `ClientConnector.selectors`: the number of ``java.nio.Selector``s components (defaults to `1`) that are present to handle the ``SocketChannel``s opened by the `ClientConnector`. +You typically want to increase the number of selectors only for those use cases where each selector should handle more than few hundreds _concurrent_ socket events. +For example, one selector typically runs well for `250` _concurrent_ socket events; as a rule of thumb, you can multiply that number by `10` to obtain the number of opened sockets a selector can handle (`2500`), based on the assumption that not all the `2500` sockets will be active _at the same time_. +* `ClientConnector.idleTimeout`: the duration of time after which `ClientConnector` closes a socket due to inactivity (defaults to `30` seconds). +This is an important parameter to configure, and you typically want the client idle timeout to be shorter than the server idle timeout, to avoid race conditions where the client attempts to use a socket just before the client-side idle timeout expires, but the server-side idle timeout has already expired and the is already closing the socket. +* `ClientConnector.connectBlocking`: whether the operation of connecting a socket to the server (i.e. `SocketChannel.connect(SocketAddress)`) must be a blocking or a non-blocking operation (defaults to `false`). For `localhost` or same datacenter hosts you want to set this parameter to `true` because DNS resolution will be immediate (and likely never fail). -For generic Internet hosts (e.g. when you are implementing a web spider) you -want to set this parameter to `false`. -* `ClientConnector.connectTimeout`: the duration of time after which -`ClientConnector` aborts a connection attempt to the server (defaults to `5` -seconds). +For generic Internet hosts (e.g. when you are implementing a web spider) you want to set this parameter to `false`. +* `ClientConnector.connectTimeout`: the duration of time after which `ClientConnector` aborts a connection attempt to the server (defaults to `5` seconds). This time includes the DNS lookup time _and_ the TCP connect time. -Please refer to the `ClientConnector` -link:{JDURL}/org/eclipse/jetty/io/ClientConnector.html[javadocs] -for the complete list of configurable parameters. +Please refer to the `ClientConnector` link:{JDURL}/org/eclipse/jetty/io/ClientConnector.html[javadocs] for the complete list of configurable parameters. [[eg-client-io-arch-protocol]] ==== Client Libraries Protocol Layer -The protocol layer builds on top of the network layer to generate the -bytes to be written to the network and to parse the bytes read from the -network. +The protocol layer builds on top of the network layer to generate the bytes to be written to the network and to parse the bytes read from the network. -Recall from link:#eg-io-arch-connection[this section] that Jetty uses the -`Connection` abstraction to produce and interpret the network bytes. +Recall from link:#eg-io-arch-connection[this section] that Jetty uses the `Connection` abstraction to produce and interpret the network bytes. -On the client side, a `ClientConnectionFactory` implementation is the -component that creates `Connection` instances based on the protocol that -the client wants to "speak" with the server. +On the client side, a `ClientConnectionFactory` implementation is the component that creates `Connection` instances based on the protocol that the client wants to "speak" with the server. -Applications use `ClientConnector.connect(SocketAddress, Map)` -to establish a TCP connection to the server, and must tell -`ClientConnector` how to create the `Connection` for that particular -TCP connection, and how to notify back the application when the connection -creation succeeds or fails. +Applications use `ClientConnector.connect(SocketAddress, Map)` to establish a TCP connection to the server, and must tell `ClientConnector` how to create the `Connection` for that particular TCP connection, and how to notify back the application when the connection creation succeeds or fails. -This is done by passing a -link:{JDURL}/org/eclipse/jetty/io/ClientConnectionFactory.html[`ClientConnectionFactory`] -(that creates `Connection` instances) and a -link:{JDURL}/org/eclipse/jetty/util/Promise.html[`Promise`] (that is notified -of connection creation success or failure) in the context `Map` as follows: +This is done by passing a link:{JDURL}/org/eclipse/jetty/io/ClientConnectionFactory.html[`ClientConnectionFactory`] (that creates `Connection` instances) and a link:{JDURL}/org/eclipse/jetty/util/Promise.html[`Promise`] (that is notified of connection creation success or failure) in the context `Map` as follows: [source,java,indent=0] ---- include::../{doc_code}/embedded/client/ClientConnectorDocs.java[tags=connect] ---- -When a `Connection` is created successfully, its `onOpen()` method is invoked, -and then the promise is completed successfully. +When a `Connection` is created successfully, its `onOpen()` method is invoked, and then the promise is completed successfully. -It is now possible to write a super-simple `telnet` client that reads and writes -string lines: +It is now possible to write a super-simple `telnet` client that reads and writes string lines: [source,java,indent=0] ---- include::../{doc_code}/embedded/client/ClientConnectorDocs.java[tags=telnet] ---- -Note how a very basic "telnet" API that applications could use is implemented -in the form of the `onLine(Consumer)` for the non-blocking receiving -side and `writeLine(String, Callback)` for the non-blocking sending side. -Note also how the `onFillable()` method implements some basic "parsing" -by looking up the `\n` character in the buffer. +Note how a very basic "telnet" API that applications could use is implemented in the form of the `onLine(Consumer)` for the non-blocking receiving side and `writeLine(String, Callback)` for the non-blocking sending side. +Note also how the `onFillable()` method implements some basic "parsing" by looking up the `\n` character in the buffer. -NOTE: The "telnet" client above looks like a super-simple HTTP client because -HTTP/1.0 can be seen as a line-based protocol. HTTP/1.0 was used just as an -example, but we could have used any other line-based protocol such as -link:https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol[SMTP], -provided that the server was able to understand it. +NOTE: The "telnet" client above looks like a super-simple HTTP client because HTTP/1.0 can be seen as a line-based protocol. +HTTP/1.0 was used just as an example, but we could have used any other line-based protocol such as link:https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol[SMTP], provided that the server was able to understand it. -This is very similar to what the Jetty client implementation does for real -network protocols. -Real network protocols are of course more complicated and so is the implementation -code that handles them, but the general ideas are similar. +This is very similar to what the Jetty client implementation does for real network protocols. +Real network protocols are of course more complicated and so is the implementation code that handles them, but the general ideas are similar. -The Jetty client implementation provides a number of `ClientConnectionFactory` -implementations that can be composed to produce and interpret the network bytes. +The Jetty client implementation provides a number of `ClientConnectionFactory` implementations that can be composed to produce and interpret the network bytes. -For example, it is simple to modify the above example to use the TLS protocol -so that you will be able to connect to the server on port `443`, typically -reserved for the encrypted HTTP protocol. +For example, it is simple to modify the above example to use the TLS protocol so that you will be able to connect to the server on port `443`, typically reserved for the encrypted HTTP protocol. -The differences between the clear-text version and the TLS encrypted version -are minimal: +The differences between the clear-text version and the TLS encrypted version are minimal: [source,java,indent=0] ---- diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/client/client.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/client.adoc index 28a17421373..3b6145f9ab8 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/client/client.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/client.adoc @@ -19,18 +19,11 @@ [[eg-client]] == Client Libraries -The Eclipse Jetty Project provides also provides client-side libraries -that allow you to embed a client in your applications. -A typical example is a client application that needs to contact a third party -service via HTTP (for example a REST service). -Another example is a proxy application that receives HTTP requests and -forwards them as FCGI requests to a PHP application such as WordPress, -or receives HTTP/1.1 requests and converts them to HTTP/2. -Yet another example is a client application that needs to received events -from a WebSocket server. +The Eclipse Jetty Project provides also provides client-side libraries that allow you to embed a client in your applications. +A typical example is a client application that needs to contact a third party service via HTTP (for example a REST service). +Another example is a proxy application that receives HTTP requests and forwards them as FCGI requests to a PHP application such as WordPress, or receives HTTP/1.1 requests and converts them to HTTP/2. Yet another example is a client application that needs to received events from a WebSocket server. -The client libraries are designed to be non-blocking and offer both synchronous -and asynchronous APIs and come with a large number of configuration options. +The client libraries are designed to be non-blocking and offer both synchronous and asynchronous APIs and come with a large number of configuration options. These are the available client libraries: @@ -38,9 +31,7 @@ These are the available client libraries: * xref:eg-client-http2[The HTTP/2 Client Library] * xref:eg-client-websocket[The WebSocket client library] -If you are interested in the low-level details of how the Eclipse Jetty -client libraries work, or are interested in writing a custom protocol, -look at the xref:eg-client-io-arch[Client I/O Architecture]. +If you are interested in the low-level details of how the Eclipse Jetty client libraries work, or are interested in writing a custom protocol, look at the xref:eg-client-io-arch[Client I/O Architecture]. include::http/client-http.adoc[] include::http2/client-http2.adoc[] diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-api.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-api.adoc index e2aa147bb42..e8a6e19c000 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-api.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-api.adoc @@ -182,8 +182,7 @@ While the request content is awaited and consequently uploaded by the client app In this case, `Response.Listener` callbacks will be invoked before the request is fully sent. This allows fine-grained control of the request/response conversation: for example the server may reject contents that are too big, send a response to the client, which in turn may stop the content upload. -Another way to provide request content is by using an `OutputStreamRequestContent`, -which allows applications to write request content when it is available to the `OutputStream` provided by `OutputStreamRequestContent`: +Another way to provide request content is by using an `OutputStreamRequestContent`, which allows applications to write request content when it is available to the `OutputStream` provided by `OutputStreamRequestContent`: [source,java,indent=0] ---- @@ -222,74 +221,42 @@ include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tags=inputStr Finally, let's look at the advanced usage of the response content handling. -The response content is provided by the `HttpClient` implementation to application -listeners following a reactive model similar to that of `java.util.concurrent.Flow`. +The response content is provided by the `HttpClient` implementation to application listeners following a reactive model similar to that of `java.util.concurrent.Flow`. The listener that follows this model is `Response.DemandedContentListener`. -After the response headers have been processed by the `HttpClient` implementation, -`Response.DemandedContentListener.onBeforeContent(response, demand)` is -invoked. This allows the application to control whether to demand the first -content or not. The default implementation of this method calls `demand.accept(1)`, -which demands one chunk of content to the implementation. +After the response headers have been processed by the `HttpClient` implementation, `Response.DemandedContentListener.onBeforeContent(response, demand)` is invoked. +This allows the application to control whether to demand the first content or not. +The default implementation of this method calls `demand.accept(1)`, which demands one chunk of content to the implementation. The implementation will deliver the chunk of content as soon as it is available. -The chunks of content are delivered to the application by invoking -`Response.DemandedContentListener.onContent(response, demand, buffer, callback)`. +The chunks of content are delivered to the application by invoking `Response.DemandedContentListener.onContent(response, demand, buffer, callback)`. Applications implement this method to process the content bytes in the `buffer`. -Succeeding the `callback` signals to the implementation that the application -has consumed the `buffer` so that the implementation can dispose/recycle the -`buffer`. Failing the `callback` signals to the implementation to fail the -response (no more content will be delivered, and the _response failed_ event -will be emitted). +Succeeding the `callback` signals to the implementation that the application has consumed the `buffer` so that the implementation can dispose/recycle the `buffer`. +Failing the `callback` signals to the implementation to fail the response (no more content will be delivered, and the _response failed_ event will be emitted). -IMPORTANT: Succeeding the `callback` must be done only after the `buffer` -bytes have been consumed. When the `callback` is succeeded, the `HttpClient` -implementation may reuse the `buffer` and overwrite the bytes with different -bytes; if the application looks at the `buffer` _after_ having succeeded -the `callback` is may see other, unrelated, bytes. +IMPORTANT: Succeeding the `callback` must be done only after the `buffer` bytes have been consumed. +When the `callback` is succeeded, the `HttpClient` implementation may reuse the `buffer` and overwrite the bytes with different bytes; if the application looks at the `buffer` _after_ having succeeded the `callback` is may see other, unrelated, bytes. The application uses the `demand` object to demand more content chunks. -Applications will typically demand for just one more content via -`demand.accept(1)`, but may decide to demand for more via `demand.accept(2)` -or demand "infinitely" once via `demand.accept(Long.MAX_VALUE)`. -Applications that demand for more than 1 chunk of content must be prepared -to receive all the content that they have demanded. +Applications will typically demand for just one more content via `demand.accept(1)`, but may decide to demand for more via `demand.accept(2)` or demand "infinitely" once via `demand.accept(Long.MAX_VALUE)`. +Applications that demand for more than 1 chunk of content must be prepared to receive all the content that they have demanded. Demanding for content and consuming the content are orthogonal activities. -An application can demand "infinitely" and store aside the pairs -`(buffer, callback)` to consume them later. -If not done carefully, this may lead to excessive memory consumption, since -the ``buffer``s are not consumed. -Succeeding the ``callback``s will result in the ``buffer``s to be -disposed/recycled and may be performed at any time. +An application can demand "infinitely" and store aside the pairs `(buffer, callback)` to consume them later. +If not done carefully, this may lead to excessive memory consumption, since the ``buffer``s are not consumed. +Succeeding the ``callback``s will result in the ``buffer``s to be disposed/recycled and may be performed at any time. -An application can also demand one chunk of content, consume it (by -succeeding the associated `callback`) and then _not_ demand for more content -until a later time. +An application can also demand one chunk of content, consume it (by succeeding the associated `callback`) and then _not_ demand for more content until a later time. -Subclass `Response.AsyncContentListener` overrides the behavior of -`Response.DemandedContentListener`; when an application implementing its -`onContent(response, buffer, callback)` succeeds the `callback`, it -will have _both_ the effect of disposing/recycling the `buffer` _and_ the -effect of demanding one more chunk of content. +Subclass `Response.AsyncContentListener` overrides the behavior of `Response.DemandedContentListener`; when an application implementing its `onContent(response, buffer, callback)` succeeds the `callback`, it will have _both_ the effect of disposing/recycling the `buffer` _and_ the effect of demanding one more chunk of content. -Subclass `Response.ContentListener` overrides the behavior of -`Response.AsyncContentListener`; when an application implementing its -`onContent(response, buffer)` returns from the method itself, it will -_both_ the effect of disposing/recycling the `buffer` _and_ the effect -of demanding one more chunk of content. +Subclass `Response.ContentListener` overrides the behavior of `Response.AsyncContentListener`; when an application implementing its `onContent(response, buffer)` returns from the method itself, it will _both_ the effect of disposing/recycling the `buffer` _and_ the effect of demanding one more chunk of content. -Previous examples of response content handling were inefficient because they -involved copying the `buffer` bytes, either to accumulate them aside so that -the application could use them when the request was completed, or because -they were provided to an API such as `InputStream` that made use of `byte[]` -(and therefore a copy from `ByteBuffer` to `byte[]` is necessary). +Previous examples of response content handling were inefficient because they involved copying the `buffer` bytes, either to accumulate them aside so that the application could use them when the request was completed, or because they were provided to an API such as `InputStream` that made use of `byte[]` (and therefore a copy from `ByteBuffer` to `byte[]` is necessary). -An application that implements a forwarder between two servers can be -implemented efficiently by handling the response content without copying -the `buffer` bytes as in the following example: +An application that implements a forwarder between two servers can be implemented efficiently by handling the response content without copying the `buffer` bytes as in the following example: [source,java,indent=0] ---- diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-authentication.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-authentication.adoc index 0f9a0134a52..ba31f99ce4e 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-authentication.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-authentication.adoc @@ -19,13 +19,9 @@ [[eg-client-http-authentication]] === HttpClient Authentication Support -Jetty's `HttpClient` supports the `BASIC` and `DIGEST` authentication -mechanisms defined by link:https://tools.ietf.org/html/rfc7235[RFC 7235], -as well as the SPNEGO authentication mechanism defined in -link:https://tools.ietf.org/html/rfc4559[RFC 4559]. +Jetty's `HttpClient` supports the `BASIC` and `DIGEST` authentication mechanisms defined by link:https://tools.ietf.org/html/rfc7235[RFC 7235], as well as the SPNEGO authentication mechanism defined in link:https://tools.ietf.org/html/rfc4559[RFC 4559]. -The HTTP _conversation_ - the sequence of related HTTP requests - for a -request that needs authentication is the following: +The HTTP _conversation_ - the sequence of related HTTP requests - for a request that needs authentication is the following: [plantuml] ---- @@ -43,59 +39,43 @@ HttpClient -> Server : GET + Authentication Server -> Application : 200 OK ---- -Upon receiving a HTTP 401 response code, `HttpClient` looks at the -`WWW-Authenticate` response header (the server _challenge_) and then tries to -match configured authentication credentials to produce an `Authentication` -header that contains the authentication credentials to access the resource. +Upon receiving a HTTP 401 response code, `HttpClient` looks at the `WWW-Authenticate` response header (the server _challenge_) and then tries to match configured authentication credentials to produce an `Authentication` header that contains the authentication credentials to access the resource. -You can configure authentication credentials in the `HttpClient` instance as -follows: +You can configure authentication credentials in the `HttpClient` instance as follows: [source,java,indent=0] ---- include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=addAuthentication] ---- -``Authentication``s are matched against the server challenge first by -mechanism (e.g. `BASIC` or `DIGEST`), then by realm and then by URI. +``Authentication``s are matched against the server challenge first by mechanism (e.g. `BASIC` or `DIGEST`), then by realm and then by URI. -If an `Authentication` match is found, the application does not receive events -related to the HTTP 401 response. These events are handled internally by -`HttpClient` which produces another (internal) request similar to the original -request but with an additional `Authorization` header. +If an `Authentication` match is found, the application does not receive events related to the HTTP 401 response. +These events are handled internally by `HttpClient` which produces another (internal) request similar to the original request but with an additional `Authorization` header. -If the authentication is successful, the server responds with a HTTP 200 and -`HttpClient` caches the `Authentication.Result` so that subsequent requests -for a matching URI will not incur in the additional rountrip caused by the -HTTP 401 response. +If the authentication is successful, the server responds with a HTTP 200 and `HttpClient` caches the `Authentication.Result` so that subsequent requests for a matching URI will not incur in the additional rountrip caused by the HTTP 401 response. -It is possible to clear ``Authentication.Result``s in order to force -authentication again: +It is possible to clear ``Authentication.Result``s in order to force authentication again: [source,java,indent=0] ---- include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=clearResults] ---- -Authentication results may be preempted to avoid the additional roundtrip -due to the server challenge in this way: +Authentication results may be preempted to avoid the additional roundtrip due to the server challenge in this way: [source,java,indent=0] ---- include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=preemptedResult] ---- -In this way, requests for the given URI are enriched immediately with the -`Authorization` header, and the server should respond with HTTP 200 (and the -resource content) rather than with the 401 and the challenge. +In this way, requests for the given URI are enriched immediately with the `Authorization` header, and the server should respond with HTTP 200 (and the resource content) rather than with the 401 and the challenge. -It is also possible to preempt the authentication for a single request only, -in this way: +It is also possible to preempt the authentication for a single request only, in this way: [source,java,indent=0] ---- include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=requestPreemptedResult] ---- -See also the xref:eg-client-http-proxy-authentication[proxy authentication section] -for further information about how authentication works with HTTP proxies. +See also the xref:eg-client-http-proxy-authentication[proxy authentication section] for further information about how authentication works with HTTP proxies. diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-configuration.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-configuration.adoc index 800d2c24adb..f20c0bf6160 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-configuration.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-configuration.adoc @@ -20,54 +20,39 @@ === HttpClient Configuration `HttpClient` has a quite large number of configuration parameters. -Please refer to the `HttpClient` -link:{JDURL}/org/eclipse/jetty/client/HttpClient.html[javadocs] -for the complete list of configurable parameters. +Please refer to the `HttpClient` link:{JDURL}/org/eclipse/jetty/client/HttpClient.html[javadocs] for the complete list of configurable parameters. + The most common parameters are: -* `HttpClient.idleTimeout`: same as `ClientConnector.idleTimeout` -described in xref:eg-client-io-arch-network[this section]. -* `HttpClient.connectBlocking`: same as `ClientConnector.connectBlocking` -described in xref:eg-client-io-arch-network[this section]. -* `HttpClient.connectTimeout`: same as `ClientConnector.connectTimeout` -described in xref:eg-client-io-arch-network[this section]. -* `HttpClient.maxConnectionsPerDestination`: the max number of TCP -connections that are opened for a particular destination (defaults to 64). -* `HttpClient.maxRequestsQueuedPerDestination`: the max number of requests -queued (defaults to 1024). +* `HttpClient.idleTimeout`: same as `ClientConnector.idleTimeout` described in xref:eg-client-io-arch-network[this section]. +* `HttpClient.connectBlocking`: same as `ClientConnector.connectBlocking` described in xref:eg-client-io-arch-network[this section]. +* `HttpClient.connectTimeout`: same as `ClientConnector.connectTimeout` described in xref:eg-client-io-arch-network[this section]. +* `HttpClient.maxConnectionsPerDestination`: the max number of TCP connections that are opened for a particular destination (defaults to 64). +* `HttpClient.maxRequestsQueuedPerDestination`: the max number of requests queued (defaults to 1024). [[eg-client-http-configuration-tls]] ==== HttpClient TLS Configuration `HttpClient` supports HTTPS requests out-of-the-box like a browser does. -The support for HTTPS request is provided by a `SslContextFactory.Client`, -typically configured in the `ClientConnector`. -If not explicitly configured, the `ClientConnector` will allocate a default -one when started. +The support for HTTPS request is provided by a `SslContextFactory.Client`, typically configured in the `ClientConnector`. +If not explicitly configured, the `ClientConnector` will allocate a default one when started. [source,java,indent=0] ---- include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tags=tlsExplicit] ---- -The default `SslContextFactory.Client` verifies the certificate sent by the -server by verifying the certificate chain. -This means that requests to public websites that have a valid certificates -(such as ``https://google.com``) will work out-of-the-box. +The default `SslContextFactory.Client` verifies the certificate sent by the server by verifying the certificate chain. +This means that requests to public websites that have a valid certificate (such as ``https://google.com``) will work out-of-the-box. -However, requests made to sites (typically ``localhost``) that have invalid -(for example, expired or with a wrong host) or self-signed certificates will -fail (like they will in a browser). +However, requests made to sites (typically ``localhost``) that have an invalid (for example, expired or with a wrong host) or self-signed certificate will fail (like they will in a browser). -Certificate validation is performed at two levels: at the TLS implementation -level (in the JDK) and - optionally - at the application level. +Certificate validation is performed at two levels: at the TLS implementation level (in the JDK) and - optionally - at the application level. -By default, certificate validation at the TLS level is enabled, while -certificate validation at the application level is disabled. +By default, certificate validation at the TLS level is enabled, while certificate validation at the application level is disabled. -You can configure the `SslContextFactory.Client` to skip certificate validation -at the TLS level: +You can configure the `SslContextFactory.Client` to skip certificate validation at the TLS level: [source,java,indent=0] ---- @@ -81,6 +66,4 @@ You can enable certificate validation at the application level: include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tags=tlsAppValidation] ---- -Please refer to the `SslContextFactory.Client` -link:{JDURL}/org/eclipse/jetty/util/ssl/SslContextFactory.Client.html[javadocs] -for the complete list of configurable parameters. +Please refer to the `SslContextFactory.Client` link:{JDURL}/org/eclipse/jetty/util/ssl/SslContextFactory.Client.html[javadocs] for the complete list of configurable parameters. diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-cookie.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-cookie.adoc index c5b92f7c723..8a42bf0b3e9 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-cookie.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-cookie.adoc @@ -51,9 +51,8 @@ You can remove cookies that you do not want to be sent in future HTTP requests: include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=removeCookie] ---- -If you want to totally disable cookie handling, you can install a -`HttpCookieStore.Empty`. This must be done when `HttpClient` is used in a -proxy application, in this way: +If you want to totally disable cookie handling, you can install a `HttpCookieStore.Empty`. +This must be done when `HttpClient` is used in a proxy application, in this way: [source,java,indent=0] ---- @@ -71,6 +70,7 @@ The example above will retain only cookies that come from the `google.com` domai // TODO: move this section to server-side ==== Special Characters in Cookies + Jetty is compliant with link:https://tools.ietf.org/html/rfc6265[RFC6265], and as such care must be taken when setting a cookie value that includes special characters such as `;`. Previously, Version=1 cookies defined in link:https://tools.ietf.org/html/rfc2109[RFC2109] (and continued in link:https://tools.ietf.org/html/rfc2965[RFC2965]) allowed for special/reserved characters to be enclosed within double quotes when declared in a `Set-Cookie` response header: diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-intro.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-intro.adoc index ab78e59dedc..4fc9670d698 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-intro.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-intro.adoc @@ -24,13 +24,10 @@ The Jetty HTTP client module provides easy-to-use APIs and utility classes to pe Jetty's HTTP client is non-blocking and asynchronous. It offers an asynchronous API that never blocks for I/O, making it very efficient in thread utilization and well suited for high performance scenarios such as load testing or parallel computation. -However, when all you need to do is to perform a `GET` request to a resource, Jetty's HTTP client offers also a synchronous API; a programming interface -where the thread that issued the request blocks until the request/response conversation is complete. +However, when all you need to do is to perform a `GET` request to a resource, Jetty's HTTP client offers also a synchronous API; a programming interface where the thread that issued the request blocks until the request/response conversation is complete. -Jetty's HTTP client supports xref:#eg-client-http-transport[different transports]: HTTP/1.1, FastCGI and HTTP/2. -This means that the semantic of a HTTP request (that is, " `GET` me the resource `/index.html` ") can be carried over the network in different formats. -The most common and default format is HTTP/1.1. -That said, Jetty's HTTP client can carry the same request using the FastCGI format or the HTTP/2 format. +Jetty's HTTP client supports xref:#eg-client-http-transport[different transports]: HTTP/1.1, FastCGI and HTTP/2. This means that the semantic of a HTTP request (that is, " `GET` me the resource `/index.html` ") can be carried over the network in different formats. +The most common and default format is HTTP/1.1. That said, Jetty's HTTP client can carry the same request using the FastCGI format or the HTTP/2 format. The FastCGI transport is heavily used in Jetty's link:#fastcgi[FastCGI support] that allows Jetty to work as a reverse proxy to PHP (exactly like Apache or Nginx do) and therefore be able to serve - for example - WordPress websites. @@ -61,8 +58,7 @@ The Maven artifact coordinates are the following: The main class is named `org.eclipse.jetty.client.HttpClient`. You can think of a `HttpClient` instance as a browser instance. -Like a browser it can make requests to different domains, it manages redirects, cookies and authentication, you can configure it with a proxy, and -it provides you with the responses to the requests you make. +Like a browser it can make requests to different domains, it manages redirects, cookies and authentication, you can configure it with a proxy, and it provides you with the responses to the requests you make. In order to use `HttpClient`, you must instantiate it, configure it, and then start it: @@ -78,11 +74,8 @@ There are several reasons for having multiple `HttpClient` instances including, * You want the two instances to behave like two different browsers and hence have different cookies, different authentication credentials, etc. * You want to use link:#eg-client-http-transport[different transports]. -Like browsers, HTTPS requests are supported out-of-the-box, as long as the server -provides a valid certificate. -In case the server does not provide a valid certificate (or in case it is self-signed) -you want to customize ``HttpClient``'s TLS configuration as described in -xref:eg-client-http-configuration-tls[this section]. +Like browsers, HTTPS requests are supported out-of-the-box, as long as the server provides a valid certificate. +In case the server does not provide a valid certificate (or in case it is self-signed) you want to customize ``HttpClient``'s TLS configuration as described in xref:eg-client-http-configuration-tls[this section]. [[eg-client-http-stop]] ==== Stopping HttpClient @@ -99,64 +92,39 @@ Stopping `HttpClient` makes sure that the memory it holds (for example, authenti [[eg-client-http-arch]] ==== HttpClient Architecture -A `HttpClient` instance can be thought as a browser instance, and it manages the -following components: +A `HttpClient` instance can be thought as a browser instance, and it manages the following components: * a `CookieStore` (see xref:eg-client-http-cookie[this section]). * a `AuthenticationStore` (see xref:eg-client-http-authentication[this section]). * a `ProxyConfiguration` (see xref:eg-client-http-proxy[this section]). * a set of _destinations_. -A _destination_ is the client-side component that represent an _origin_ on -a server, and manages a queue of requests for that origin, and a -xref:eg-client-http-connection-pool[pool of connections] to that origin. +A _destination_ is the client-side component that represent an _origin_ on a server, and manages a queue of requests for that origin, and a xref:eg-client-http-connection-pool[pool of connections] to that origin. -An _origin_ may be simply thought as the tuple `(scheme, host, port)` and it -is where the client connects to in order to communicate with the server. +An _origin_ may be simply thought as the tuple `(scheme, host, port)` and it is where the client connects to in order to communicate with the server. However, this is not enough. -If you use `HttpClient` to write a proxy you may have different clients that -want to contact the same server. -In this case, you may not want to use the same proxy-to-server connection to -proxy requests for both clients, for example for authentication reasons: the -server may associate the connection with authentication credentials and you -do not want to use the same connection for two different users that have -different credentials. -Instead, you want to use different connections for different clients and -this can be achieved by "tagging" a destination with a tag object that -represents the remote client (for example, it could be the remote client IP -address). +If you use `HttpClient` to write a proxy you may have different clients that want to contact the same server. +In this case, you may not want to use the same proxy-to-server connection to proxy requests for both clients, for example for authentication reasons: the server may associate the connection with authentication credentials and you do not want to use the same connection for two different users that have different credentials. +Instead, you want to use different connections for different clients and this can be achieved by "tagging" a destination with a tag object that represents the remote client (for example, it could be the remote client IP address). -Two origins with the same `(scheme, host, port)` but different `tag` -create two different destinations and therefore two different connection pools. +Two origins with the same `(scheme, host, port)` but different `tag` create two different destinations and therefore two different connection pools. However, also this is not enough. It is possible that a server speaks different protocols on the same `port`. -A connection may start by speaking one protocol, for example HTTP/1.1, but -then be upgraded to speak a different protocol, for example HTTP/2. -After a connection has been upgraded to a second protocol, it cannot speak -the first protocol anymore, so it can only be used to communicate using -the second protocol. +A connection may start by speaking one protocol, for example HTTP/1.1, but then be upgraded to speak a different protocol, for example HTTP/2. After a connection has been upgraded to a second protocol, it cannot speak the first protocol anymore, so it can only be used to communicate using the second protocol. -Two origins with the same `(scheme, host, port)` but different -`protocol` create two different destinations and therefore two different -connection pools. +Two origins with the same `(scheme, host, port)` but different `protocol` create two different destinations and therefore two different connection pools. -Therefore an origin is identified by the tuple -`(scheme, host, port, tag, protocol)`. +Therefore an origin is identified by the tuple `(scheme, host, port, tag, protocol)`. [[eg-client-http-connection-pool]] ==== HttpClient Connection Pooling -A destination manages a `org.eclipse.jetty.client.ConnectionPool`, where -connections to a particular origin are pooled for performance reasons: -opening a connection is a costly operation and it's better to reuse them -for multiple requests. +A destination manages a `org.eclipse.jetty.client.ConnectionPool`, where connections to a particular origin are pooled for performance reasons: +opening a connection is a costly operation and it's better to reuse them for multiple requests. -NOTE: Remember that to select a specific destination you must select a -specific origin, and that an origin is identified by the tuple -`(scheme, host, port, tag, protocol)`, so you can have multiple destinations -for the same `host` and `port`. +NOTE: Remember that to select a specific destination you must select a specific origin, and that an origin is identified by the tuple `(scheme, host, port, tag, protocol)`, so you can have multiple destinations for the same `host` and `port`. You can access the `ConnectionPool` in this way: @@ -167,17 +135,11 @@ include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tags=getConne Jetty's client library provides the following `ConnectionPool` implementations: -* `DuplexConnectionPool`, historically the first implementation, only used by -the HTTP/1.1 transport. -* `MultiplexConnectionPool`, the generic implementation valid for any transport -where connections are reused with a MRU (most recently used) algorithm (that is, -the connections most recently returned to the connection pool are the more -likely to be used again). -* `RoundRobinConnectionPool`, similar to `MultiplexConnectionPool` but where -connections are reused with a round-robin algorithm. +* `DuplexConnectionPool`, historically the first implementation, only used by the HTTP/1.1 transport. +* `MultiplexConnectionPool`, the generic implementation valid for any transport where connections are reused with a MRU (most recently used) algorithm (that is, the connections most recently returned to the connection pool are the more likely to be used again). +* `RoundRobinConnectionPool`, similar to `MultiplexConnectionPool` but where connections are reused with a round-robin algorithm. -The `ConnectionPool` implementation can be customized for each destination in -by setting a `ConnectionPool.Factory` on the `HttpClientTransport`: +The `ConnectionPool` implementation can be customized for each destination in by setting a `ConnectionPool.Factory` on the `HttpClientTransport`: [source,java,indent=0] ---- @@ -214,40 +176,22 @@ Destination -> Destination : dequeue(Request) Destination -> Connection : send(Request) ---- -When a request is sent, an origin is computed from the request; `HttpClient` -uses that origin to find (or create if it does not exist) the correspondent -destination. -The request is then queued onto the destination, and this causes the -destination to ask its connection pool for a free connection. -If a connection is available, it is returned, otherwise a new connection is -created. -Once the destination has obtained the connection, it dequeues the request -and sends it over the connection. +When a request is sent, an origin is computed from the request; `HttpClient` uses that origin to find (or create if it does not exist) the correspondent destination. +The request is then queued onto the destination, and this causes the destination to ask its connection pool for a free connection. +If a connection is available, it is returned, otherwise a new connection is created. +Once the destination has obtained the connection, it dequeues the request and sends it over the connection. -The first request to a destination triggers the opening of the first -connection. -A second request with the same origin sent _after_ the first request/response -cycle is completed will reuse the same connection. -A second request with the same origin sent _concurrently_ with the first -request will cause the opening of a second connection. -The configuration parameter `HttpClient.maxConnectionsPerDestination` -(see also the xref:eg-client-http-configuration[configuration section]) controls -the max number of connections that can be opened for a destination. +The first request to a destination triggers the opening of the first connection. +A second request with the same origin sent _after_ the first request/response cycle is completed will reuse the same connection. +A second request with the same origin sent _concurrently_ with the first request will cause the opening of a second connection. +The configuration parameter `HttpClient.maxConnectionsPerDestination` (see also the xref:eg-client-http-configuration[configuration section]) controls the max number of connections that can be opened for a destination. -NOTE: If opening connections to a given origin takes a long time, then -requests for that origin will queue up in the corresponding destination. +NOTE: If opening connections to a given origin takes a long time, then requests for that origin will queue up in the corresponding destination. Each connection can handle a limited number of concurrent requests. -For HTTP/1.1, this number is always `1`: there can only be one outstanding -request for each connection. -For HTTP/2 this number is determined by the server `max_concurrent_stream` -setting (typically around `100`, i.e. there can be up to `100` outstanding -requests for every connection). +For HTTP/1.1, this number is always `1`: there can only be one outstanding request for each connection. +For HTTP/2 this number is determined by the server `max_concurrent_stream` setting (typically around `100`, i.e. there can be up to `100` outstanding requests for every connection). -When a destination has maxed out its number of connections, and all -connections have maxed out their number of outstanding requests, more requests -sent to that destination will be queued. +When a destination has maxed out its number of connections, and all connections have maxed out their number of outstanding requests, more requests sent to that destination will be queued. When the request queue is full, the request will be failed. -The configuration parameter `HttpClient.maxRequestsQueuedPerDestination` -(see also the xref:eg-client-http-configuration[configuration section]) controls -the max number of requests that can be queued for a destination. +The configuration parameter `HttpClient.maxRequestsQueuedPerDestination` (see also the xref:eg-client-http-configuration[configuration section]) controls the max number of requests that can be queued for a destination. diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-proxy.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-proxy.adoc index a4b8fa9cc26..6231cee2a77 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-proxy.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-proxy.adoc @@ -21,9 +21,7 @@ Jetty's `HttpClient` can be configured to use proxies to connect to destinations. -Two types of proxies are available out of the box: a HTTP proxy (provided by -class `org.eclipse.jetty.client.HttpProxy`) and a SOCKS 4 proxy (provided by -class `org.eclipse.jetty.client.Socks4Proxy`). +Two types of proxies are available out of the box: a HTTP proxy (provided by class `org.eclipse.jetty.client.HttpProxy`) and a SOCKS 4 proxy (provided by class `org.eclipse.jetty.client.Socks4Proxy`). Other implementations may be written by subclassing `ProxyConfiguration.Proxy`. The following is a typical configuration: @@ -33,32 +31,25 @@ The following is a typical configuration: include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=proxy] ---- -You specify the proxy host and proxy port, and optionally also the addresses -that you do not want to be proxied, and then add the proxy configuration on -the `ProxyConfiguration` instance. +You specify the proxy host and proxy port, and optionally also the addresses that you do not want to be proxied, and then add the proxy configuration on the `ProxyConfiguration` instance. -Configured in this way, `HttpClient` makes requests to the HTTP proxy (for -plain-text HTTP requests) or establishes a tunnel via HTTP `CONNECT` (for -encrypted HTTPS requests). +Configured in this way, `HttpClient` makes requests to the HTTP proxy (for plain-text HTTP requests) or establishes a tunnel via HTTP `CONNECT` (for encrypted HTTPS requests). Proxying is supported for both HTTP/1.1 and HTTP/2. [[eg-client-http-proxy-authentication]] ==== Proxy Authentication Support -Jetty's `HttpClient` supports proxy authentication in the same way it supports -xref:eg-client-http-authentication[server authentication]. +Jetty's `HttpClient` supports proxy authentication in the same way it supports xref:eg-client-http-authentication[server authentication]. -In the example below, the proxy requires `BASIC` authentication, but the server -requires `DIGEST` authentication, and therefore: +In the example below, the proxy requires `BASIC` authentication, but the server requires `DIGEST` authentication, and therefore: [source,java,indent=0] ---- include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=proxyAuthentication] ---- -The HTTP conversation for successful authentications on both the proxy and the -server is the following: +The HTTP conversation for successful authentications on both the proxy and the server is the following: [plantuml] ---- @@ -84,9 +75,6 @@ Proxy -> HttpClient : 200 OK HttpClient -> Application : 200 OK ---- -The application does not receive events related to the responses with code 407 -and 401 since they are handled internally by `HttpClient`. +The application does not receive events related to the responses with code 407 and 401 since they are handled internally by `HttpClient`. -Similarly to the xref:eg-client-http-authentication[authentication section], the -proxy authentication result and the server authentication result can be -preempted to avoid, respectively, the 407 and 401 roundtrips. +Similarly to the xref:eg-client-http-authentication[authentication section], the proxy authentication result and the server authentication result can be preempted to avoid, respectively, the 407 and 401 roundtrips. diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-transport.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-transport.adoc index 5c59f5f8920..3f5fa985b52 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-transport.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http/client-http-transport.adoc @@ -19,20 +19,13 @@ [[eg-client-http-transport]] === HttpClient Pluggable Transports -Jetty's `HttpClient` can be configured to use different transports to carry the -semantic of HTTP requests and responses. +Jetty's `HttpClient` can be configured to use different transports to carry the semantic of HTTP requests and responses. -This means that the intention of a client to request resource `/index.html` -using the `GET` method can be carried over the network in different formats. +This means that the intention of a client to request resource `/index.html` using the `GET` method can be carried over the network in different formats. -A `HttpClient` transport is the component that is in charge of converting a -high-level, semantic, HTTP requests such as "`GET` resource ``/index.html``" -into the specific format understood by the server (for example, HTTP/2), and to -convert the server response from the specific format (HTTP/2) into high-level, -semantic objects that can be used by applications. +A `HttpClient` transport is the component that is in charge of converting a high-level, semantic, HTTP requests such as "`GET` resource ``/index.html``" into the specific format understood by the server (for example, HTTP/2), and to convert the server response from the specific format (HTTP/2) into high-level, semantic objects that can be used by applications. -The most common protocol format is HTTP/1.1, a textual protocol with lines -separated by `\r\n`: +The most common protocol format is HTTP/1.1, a textual protocol with lines separated by `\r\n`: [source,screen] ---- @@ -56,27 +49,17 @@ x0C x0B D O C U M E ... ---- -Similarly, HTTP/2 is a binary protocol that transports the same information -in a yet different format. +Similarly, HTTP/2 is a binary protocol that transports the same information in a yet different format. -A protocol may be _negotiated_ between client and server. A request for a -resource may be sent using one protocol (for example, HTTP/1.1), but the -response may arrive in a different protocol (for example, HTTP/2). +A protocol may be _negotiated_ between client and server. +A request for a resource may be sent using one protocol (for example, HTTP/1.1), but the response may arrive in a different protocol (for example, HTTP/2). -`HttpClient` supports 3 static transports, each speaking only one protocol: -xref:eg-client-http-transport-http11[HTTP/1.1], -xref:eg-client-http-transport-http2[HTTP/2] and -xref:eg-client-http-transport-fcgi[FastCGI], -all of them with 2 variants: clear-text and TLS encrypted. +`HttpClient` supports 3 static transports, each speaking only one protocol: xref:eg-client-http-transport-http11[HTTP/1.1], xref:eg-client-http-transport-http2[HTTP/2] and xref:eg-client-http-transport-fcgi[FastCGI], all of them with 2 variants: clear-text and TLS encrypted. -`HttpClient` also supports one -xref:eg-client-http-transport-dynamic[dynamic transport], -that can speak different protocols and can select the right protocol by -negotiating it with the server or by explicit indication from applications. +`HttpClient` also supports one xref:eg-client-http-transport-dynamic[dynamic transport], that can speak different protocols and can select the right protocol by negotiating it with the server or by explicit indication from applications. Applications are typically not aware of the actual protocol being used. -This allows them to write their logic against a high-level API that hides the -details of the specific protocol being used over the network. +This allows them to write their logic against a high-level API that hides the details of the specific protocol being used over the network. [[eg-client-http-transport-http11]] ==== HTTP/1.1 Transport @@ -88,8 +71,7 @@ HTTP/1.1 is the default transport. include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=defaultTransport] ---- -If you want to customize the HTTP/1.1 transport, you can explicitly configure -it in this way: +If you want to customize the HTTP/1.1 transport, you can explicitly configure it in this way: [source,java,indent=0] ---- @@ -106,12 +88,9 @@ The HTTP/2 transport can be configured in this way: include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=http2Transport] ---- -`HTTP2Client` is the lower-level client that provides an API based on HTTP/2 -concepts such as _sessions_, _streams_ and _frames_ that are specific to HTTP/2. -See xref:eg-client-http2[the HTTP/2 client section] for more information. +`HTTP2Client` is the lower-level client that provides an API based on HTTP/2 concepts such as _sessions_, _streams_ and _frames_ that are specific to HTTP/2. See xref:eg-client-http2[the HTTP/2 client section] for more information. -`HttpClientTransportOverHTTP2` uses `HTTP2Client` to format high-level semantic -HTTP requests (like "GET resource /index.html") into the HTTP/2 specific format. +`HttpClientTransportOverHTTP2` uses `HTTP2Client` to format high-level semantic HTTP requests (like "GET resource /index.html") into the HTTP/2 specific format. [[eg-client-http-transport-fcgi]] ==== FastCGI Transport @@ -123,33 +102,21 @@ The FastCGI transport can be configured in this way: include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=fcgiTransport] ---- -In order to make requests using the FastCGI transport, you need to have a -FastCGI server such as https://en.wikipedia.org/wiki/PHP#PHPFPM[PHP-FPM] -(see also http://php.net/manual/en/install.fpm.php). +In order to make requests using the FastCGI transport, you need to have a FastCGI server such as https://en.wikipedia.org/wiki/PHP#PHPFPM[PHP-FPM] (see also http://php.net/manual/en/install.fpm.php). -The FastCGI transport is primarily used by Jetty's link:#fastcgi[FastCGI support] -to serve PHP pages (WordPress for example). +The FastCGI transport is primarily used by Jetty's link:#fastcgi[FastCGI support] to serve PHP pages (WordPress for example). [[eg-client-http-transport-dynamic]] ==== Dynamic Transport -The static transports work well if you know in advance the protocol you want -to speak with the server, or if the server only supports one protocol (such -as FastCGI). +The static transports work well if you know in advance the protocol you want to speak with the server, or if the server only supports one protocol (such as FastCGI). -With the advent of HTTP/2, however, servers are now able to support multiple -protocols, at least both HTTP/1.1 and HTTP/2. +With the advent of HTTP/2, however, servers are now able to support multiple protocols, at least both HTTP/1.1 and HTTP/2. The HTTP/2 protocol is typically negotiated between client and server. -This negotiation can happen via ALPN, a TLS extension that allows the client -to tell the server the list of protocol that the client supports, so that the -server can pick one of the client supported protocols that also the server -supports; or via HTTP/1.1 upgrade by means of the `Upgrade` header. +This negotiation can happen via ALPN, a TLS extension that allows the client to tell the server the list of protocol that the client supports, so that the server can pick one of the client supported protocols that also the server supports; or via HTTP/1.1 upgrade by means of the `Upgrade` header. -Applications can configure the dynamic transport with one or more -_application_ protocols such as HTTP/1.1 or HTTP/2. The implementation will -take care of using TLS for HTTPS URIs, using ALPN, negotiating protocols, -upgrading from one protocol to another, etc. +Applications can configure the dynamic transport with one or more _application_ protocols such as HTTP/1.1 or HTTP/2. The implementation will take care of using TLS for HTTPS URIs, using ALPN, negotiating protocols, upgrading from one protocol to another, etc. By default, the dynamic transport only speaks HTTP/1.1: @@ -158,51 +125,37 @@ By default, the dynamic transport only speaks HTTP/1.1: include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=dynamicDefault] ---- -The dynamic transport can be configured with just one protocol, making it -equivalent to the corresponding static transport: +The dynamic transport can be configured with just one protocol, making it equivalent to the corresponding static transport: [source,java,indent=0] ---- include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=dynamicOneProtocol] ---- -The dynamic transport, however, has been implemented to support multiple -transports, in particular both HTTP/1.1 and HTTP/2: +The dynamic transport, however, has been implemented to support multiple transports, in particular both HTTP/1.1 and HTTP/2: [source,java,indent=0] ---- include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=dynamicH1H2] ---- -IMPORTANT: The order in which the protocols are specified to -`HttpClientTransportDynamic` indicates what is the client preference. -If the protocol is negotiated via ALPN, it is the server that decides what is -the protocol to use for the communication, regardless of the client preference. +IMPORTANT: The order in which the protocols are specified to `HttpClientTransportDynamic` indicates what is the client preference. +If the protocol is negotiated via ALPN, it is the server that decides what is the protocol to use for the communication, regardless of the client preference. If the protocol is not negotiated, the client preference is honored. -Provided that the server supports both HTTP/1.1 and HTTP/2 clear-text, client -applications can explicitly hint the version they want to use: +Provided that the server supports both HTTP/1.1 and HTTP/2 clear-text, client applications can explicitly hint the version they want to use: [source,java,indent=0] ---- include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=dynamicClearText] ---- -In case of TLS encrypted communication using the HTTPS scheme, things are a -little more complicated. +In case of TLS encrypted communication using the HTTPS scheme, things are a little more complicated. -If the client application explicitly specifies the HTTP version, then ALPN -is not used on the client. By specifying the HTTP version explicitly, the -client application has prior-knowledge of what HTTP version the server -supports, and therefore ALPN is not needed. -If the server does not support the HTTP version chosen by the client, then -the communication will fail. +If the client application explicitly specifies the HTTP version, then ALPN is not used on the client. +By specifying the HTTP version explicitly, the client application has prior-knowledge of what HTTP version the server supports, and therefore ALPN is not needed. +If the server does not support the HTTP version chosen by the client, then the communication will fail. -If the client application does not explicitly specify the HTTP version, -then ALPN will be used on the client. -If the server also supports ALPN, then the protocol will be negotiated via -ALPN and the server will choose the protocol to use. -If the server does not support ALPN, the client will try to use the first -protocol configured in `HttpClientTransportDynamic`, and the communication -may succeed or fail depending on whether the server supports the protocol -chosen by the client. +If the client application does not explicitly specify the HTTP version, then ALPN will be used on the client. +If the server also supports ALPN, then the protocol will be negotiated via ALPN and the server will choose the protocol to use. +If the server does not support ALPN, the client will try to use the first protocol configured in `HttpClientTransportDynamic`, and the communication may succeed or fail depending on whether the server supports the protocol chosen by the client. diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http2/client-http2.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http2/client-http2.adoc index 8cdc48187e6..900d45ff295 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/client/http2/client-http2.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http2/client-http2.adoc @@ -19,19 +19,11 @@ [[eg-client-http2]] === HTTP/2 Client Library -In the vast majority of cases, client applications should use the generic, -high-level, xref:eg-client-http[HTTP client library] that also provides -HTTP/2 support via the pluggable -xref:eg-client-http-transport-http2[HTTP/2 transport] or the -xref:eg-client-http-transport-dynamic[dynamic transport]. +In the vast majority of cases, client applications should use the generic, high-level, xref:eg-client-http[HTTP client library] that also provides HTTP/2 support via the pluggable xref:eg-client-http-transport-http2[HTTP/2 transport] or the xref:eg-client-http-transport-dynamic[dynamic transport]. -The high-level HTTP library supports cookies, authentication, redirection, -connection pooling and a number of other features that are absent in the -low-level HTTP/2 library. +The high-level HTTP library supports cookies, authentication, redirection, connection pooling and a number of other features that are absent in the low-level HTTP/2 library. -The HTTP/2 client library has been designed for those applications that need -low-level access to HTTP/2 features such as _sessions_, _streams_ and -_frames_, and this is quite a rare use case. +The HTTP/2 client library has been designed for those applications that need low-level access to HTTP/2 features such as _sessions_, _streams_ and _frames_, and this is quite a rare use case. See also the correspondent xref:eg-server-http2[HTTP/2 server library]. @@ -49,16 +41,14 @@ The Maven artifact coordinates for the HTTP/2 client library are the following: ---- -The main class is named `org.eclipse.jetty.http2.client.HTTP2Client`, and -must be created, configured and started before use: +The main class is named `org.eclipse.jetty.http2.client.HTTP2Client`, and must be created, configured and started before use: [source,java,indent=0] ---- include::../../{doc_code}/embedded/client/http2/HTTP2ClientDocs.java[tags=start] ---- -When your application stops, or otherwise does not need `HTTP2Client` anymore, -it should stop the `HTTP2Client` instance (or instances) that were started: +When your application stops, or otherwise does not need `HTTP2Client` anymore, it should stop the `HTTP2Client` instance (or instances) that were started: [source,java,indent=0] ---- @@ -66,13 +56,8 @@ include::../../{doc_code}/embedded/client/http2/HTTP2ClientDocs.java[tags=stop] ---- `HTTP2Client` allows client applications to connect to an HTTP/2 server. -A _session_ represents a single TCP connection to an HTTP/2 server and is -defined by class `org.eclipse.jetty.http2.api.Session`. -A _session_ typically has a long life - once the TCP connection is established, -it remains open until it is not used anymore (and therefore it is closed by -the idle timeout mechanism), until a fatal error occurs (for example, a network -failure), or if one of the peers decides unilaterally to close the TCP -connection. +A _session_ represents a single TCP connection to an HTTP/2 server and is defined by class `org.eclipse.jetty.http2.api.Session`. +A _session_ typically has a long life - once the TCP connection is established, it remains open until it is not used anymore (and therefore it is closed by the idle timeout mechanism), until a fatal error occurs (for example, a network failure), or if one of the peers decides unilaterally to close the TCP connection. include::../../http2.adoc[tag=multiplex] @@ -81,14 +66,12 @@ include::../../http2.adoc[tag=multiplex] include::../../http2.adoc[tag=flowControl] -How a client application should handle HTTP/2 flow control is discussed in -details in xref:eg-client-http2-response[this section]. +How a client application should handle HTTP/2 flow control is discussed in details in xref:eg-client-http2-response[this section]. [[eg-client-http2-connect]] ==== Connecting to the Server -The first thing an application should do is to connect to the server and -obtain a `Session`. +The first thing an application should do is to connect to the server and obtain a `Session`. The following example connects to the server on a clear-text port: [source,java,indent=0] @@ -103,46 +86,32 @@ The following example connects to the server on an encrypted port: include::../../{doc_code}/embedded/client/http2/HTTP2ClientDocs.java[tags=encryptedConnect] ---- -IMPORTANT: Applications must know in advance whether they want to connect to a -clear-text or encrypted port, and pass the `SslContextFactory` parameter -accordingly to the `connect(...)` method. +IMPORTANT: Applications must know in advance whether they want to connect to a clear-text or encrypted port, and pass the `SslContextFactory` parameter accordingly to the `connect(...)` method. [[eg-client-http2-configure]] ===== Configuring the Session The `connect(...)` method takes a `Session.Listener` parameter. -This listener's `onPreface(...)` method is invoked just before establishing the -connection to the server to gather the client configuration to send to the -server. Client applications can override this method to change the default -configuration: +This listener's `onPreface(...)` method is invoked just before establishing the connection to the server to gather the client configuration to send to the server. +Client applications can override this method to change the default configuration: [source,java,indent=0] ---- include::../../{doc_code}/embedded/client/http2/HTTP2ClientDocs.java[tags=configure] ---- -The `Session.Listener` is notified of session events originated by the server -such as receiving a `SETTINGS` frame from the server, or the server closing -the connection, or the client timing out the connection due to idleness. -Please refer to the `Session.Listener` -link:{JDURL}/org/eclipse/jetty/http2/api/Session.Listener.html[javadocs] for -the complete list of events. +The `Session.Listener` is notified of session events originated by the server such as receiving a `SETTINGS` frame from the server, or the server closing the connection, or the client timing out the connection due to idleness. +Please refer to the `Session.Listener` link:{JDURL}/org/eclipse/jetty/http2/api/Session.Listener.html[javadocs] for the complete list of events. -Once a `Session` has been established, the communication with the server happens -by exchanging _frames_, as specified in the -link:https://tools.ietf.org/html/rfc7540#section-4[HTTP/2 specification]. +Once a `Session` has been established, the communication with the server happens by exchanging _frames_, as specified in the link:https://tools.ietf.org/html/rfc7540#section-4[HTTP/2 specification]. [[eg-client-http2-request]] ==== Sending a Request -Sending an HTTP request to the server, and receiving a response, creates a -_stream_ that encapsulates the exchange of HTTP/2 frames that compose the -request and the response. +Sending an HTTP request to the server, and receiving a response, creates a _stream_ that encapsulates the exchange of HTTP/2 frames that compose the request and the response. -In order to send an HTTP request to the server, the client must send a -`HEADERS` frame. -`HEADERS` frames carry the request method, the request URI and the request -headers. +In order to send an HTTP request to the server, the client must send a `HEADERS` frame. +`HEADERS` frames carry the request method, the request URI and the request headers. Sending the `HEADERS` frame opens the `Stream`: [source,java,indent=0,subs=normal] @@ -151,12 +120,8 @@ include::../../{doc_code}/embedded/client/http2/HTTP2ClientDocs.java[tags=newStr ---- Note how `Session.newStream(...)` takes a `Stream.Listener` parameter. -This listener is notified of stream events originated by the server such as -receiving `HEADERS` or `DATA` frames that are part of the response, discussed -in more details in the xref:eg-client-http2-response[section below]. -Please refer to the `Stream.Listener` -link:{JDURL}/org/eclipse/jetty/http2/api/Stream.Listener.html[javadocs] for -the complete list of events. +This listener is notified of stream events originated by the server such as receiving `HEADERS` or `DATA` frames that are part of the response, discussed in more details in the xref:eg-client-http2-response[section below]. +Please refer to the `Stream.Listener` link:{JDURL}/org/eclipse/jetty/http2/api/Stream.Listener.html[javadocs] for the complete list of events. HTTP requests may have content, which is sent using the `Stream` APIs: @@ -165,27 +130,19 @@ HTTP requests may have content, which is sent using the `Stream` APIs: include::../../{doc_code}/embedded/client/http2/HTTP2ClientDocs.java[tags=newStreamWithData] ---- -IMPORTANT: When sending two `DATA` frames consecutively, the second call to -`Stream.data(...)` must be done only when the first is completed, or a -`WritePendingException` will be thrown. -Use the `Callback` APIs or `CompletableFuture` APIs to ensure that the second -`Stream.data(...)` call is performed when the first completed successfully. +IMPORTANT: When sending two `DATA` frames consecutively, the second call to `Stream.data(...)` must be done only when the first is completed, or a `WritePendingException` will be thrown. +Use the `Callback` APIs or `CompletableFuture` APIs to ensure that the second `Stream.data(...)` call is performed when the first completed successfully. [[eg-client-http2-response]] ==== Receiving a Response -Response events are delivered to the `Stream.Listener` passed to -`Session.newStream(...)`. +Response events are delivered to the `Stream.Listener` passed to `Session.newStream(...)`. -An HTTP response is typically composed of a `HEADERS` frame containing the -HTTP status code and the response headers, and optionally one or more `DATA` -frames containing the response content bytes. +An HTTP response is typically composed of a `HEADERS` frame containing the HTTP status code and the response headers, and optionally one or more `DATA` frames containing the response content bytes. -The HTTP/2 protocol also supports response trailers (that is, headers that are -sent after the response content) that also are sent using a `HEADERS` frame. +The HTTP/2 protocol also supports response trailers (that is, headers that are sent after the response content) that also are sent using a `HEADERS` frame. -A client application can therefore receive the HTTP/2 frames sent by the server -by implementing the relevant methods in `Stream.Listener`: +A client application can therefore receive the HTTP/2 frames sent by the server by implementing the relevant methods in `Stream.Listener`: [source,java,indent=0] ---- @@ -197,12 +154,9 @@ include::../../http2.adoc[tag=apiFlowControl] [[eg-client-http2-reset]] ==== Resetting a Request or Response -In HTTP/2, clients and servers have the ability to tell to the other peer that -they are not interested anymore in either the request or the response, using a -`RST_STREAM` frame. +In HTTP/2, clients and servers have the ability to tell to the other peer that they are not interested anymore in either the request or the response, using a `RST_STREAM` frame. -The `HTTP2Client` APIs allow client applications to send and receive this -"reset" frame: +The `HTTP2Client` APIs allow client applications to send and receive this "reset" frame: [source,java,indent=0] ---- @@ -212,14 +166,10 @@ include::../../{doc_code}/embedded/client/http2/HTTP2ClientDocs.java[tags=reset] [[eg-client-http2-push]] ==== Receiving HTTP/2 Pushes -HTTP/2 servers have the ability to push resources related to a primary -resource. -When an HTTP/2 server pushes a resource, it sends to the client a -`PUSH_PROMISE` frame that contains the request URI and headers that a client -would use to request explicitly that resource. +HTTP/2 servers have the ability to push resources related to a primary resource. +When an HTTP/2 server pushes a resource, it sends to the client a `PUSH_PROMISE` frame that contains the request URI and headers that a client would use to request explicitly that resource. -Client applications can be configured to tell the server to never push -resources, see xref:eg-client-http2-configure[this section]. +Client applications can be configured to tell the server to never push resources, see xref:eg-client-http2-configure[this section]. Client applications can listen to the push events, and act accordingly: @@ -228,9 +178,7 @@ Client applications can listen to the push events, and act accordingly: include::../../{doc_code}/embedded/client/http2/HTTP2ClientDocs.java[tags=push] ---- -If a client application does not want to handle a particular HTTP/2 push, it -can just reset the pushed stream to tell the server to stop sending bytes for -the pushed stream: +If a client application does not want to handle a particular HTTP/2 push, it can just reset the pushed stream to tell the server to stop sending bytes for the pushed stream: [source,java,indent=0] ---- diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/http2.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/http2.adoc index b77a1768235..c3649af932b 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/http2.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/http2.adoc @@ -19,100 +19,56 @@ // Snippets of HTTP/2 documentation that are common between client and server. tag::multiplex[] -HTTP/2 is a multiplexed protocol: it allows multiple HTTP/2 requests to be sent -on the same TCP connection. +HTTP/2 is a multiplexed protocol: it allows multiple HTTP/2 requests to be sent on the same TCP connection. Each request/response cycle is represented by a _stream_. Therefore, a single _session_ manages multiple concurrent _streams_. -A _stream_ has typically a very short life compared to the _session_: a -_stream_ only exists for the duration of the request/response cycle and then -disappears. +A _stream_ has typically a very short life compared to the _session_: a _stream_ only exists for the duration of the request/response cycle and then disappears. end::multiplex[] tag::flowControl[] -The HTTP/2 protocol is _flow controlled_ (see -link:https://tools.ietf.org/html/rfc7540#section-5.2[the specification]). -This means that a sender and a receiver maintain a _flow control window_ that -tracks the number of data bytes sent and received, respectively. -When a sender sends data bytes, it reduces its flow control window. When a -receiver receives data bytes, it also reduces its flow control window, and -then passes the received data bytes to the application. -The application consumes the data bytes and tells back the receiver that it -has consumed the data bytes. -The receiver then enlarges the flow control window, and arranges to send a -message to the sender with the number of bytes consumed, so that the sender -can enlarge its flow control window. +The HTTP/2 protocol is _flow controlled_ (see link:https://tools.ietf.org/html/rfc7540#section-5.2[the specification]). +This means that a sender and a receiver maintain a _flow control window_ that tracks the number of data bytes sent and received, respectively. +When a sender sends data bytes, it reduces its flow control window. +When a receiver receives data bytes, it also reduces its flow control window, and then passes the received data bytes to the application. +The application consumes the data bytes and tells back the receiver that it has consumed the data bytes. +The receiver then enlarges the flow control window, and arranges to send a message to the sender with the number of bytes consumed, so that the sender can enlarge its flow control window. -A sender can send data bytes up to its whole flow control window, then it must -stop sending until it receives a message from the receiver that the data bytes -have been consumed, which enlarges the flow control window, which allows the -sender to send more data bytes. +A sender can send data bytes up to its whole flow control window, then it must stop sending until it receives a message from the receiver that the data bytes have been consumed, which enlarges the flow control window, which allows the sender to send more data bytes. -HTTP/2 defines _two_ flow control windows: one for each _session_, and one -for each _stream_. Let's see with an example how they interact, assuming that -in this example the session flow control window is 120 bytes and the stream -flow control window is 100 bytes. +HTTP/2 defines _two_ flow control windows: one for each _session_, and one for each _stream_. +Let's see with an example how they interact, assuming that in this example the session flow control window is 120 bytes and the stream flow control window is 100 bytes. -The sender opens a session, and then opens `stream_1` on that session, and -sends `80` data bytes. -At this point the session flow control window is `40` bytes (`120 - 80`), and -``stream_1``'s flow control window is `20` bytes (`100 - 80`). +The sender opens a session, and then opens `stream_1` on that session, and sends `80` data bytes. +At this point the session flow control window is `40` bytes (`120 - 80`), and ``stream_1``'s flow control window is `20` bytes (`100 - 80`). The sender now opens `stream_2` on the same session and sends `40` data bytes. -At this point, the session flow control window is `0` bytes (`40 - 40`), -while ``stream_2``'s flow control window is `60` (`100 - 40`). -Since now the session flow control window is `0`, the sender cannot send more -data bytes, neither on `stream_1` nor on `stream_2` despite both have their -stream flow control windows greater than `0`. +At this point, the session flow control window is `0` bytes (`40 - 40`), while ``stream_2``'s flow control window is `60` (`100 - 40`). +Since now the session flow control window is `0`, the sender cannot send more data bytes, neither on `stream_1` nor on `stream_2` despite both have their stream flow control windows greater than `0`. -The receiver consumes ``stream_2``'s `40` data bytes and sends a message to -the sender with this information. -At this point, the session flow control window is `40` (`0 + 40`), -``stream_1``'s flow control window is still `20` and ``stream_2``'s flow -control window is `100` (`60 + 40`). -If the sender opens `stream_3` and would like to send 50 data bytes, it would -only be able to send `40` because that is the maximum allowed by the session -flow control window at this point. +The receiver consumes ``stream_2``'s `40` data bytes and sends a message to the sender with this information. +At this point, the session flow control window is `40` (`0 40`), ``stream_1``'s flow control window is still `20` and ``stream_2``'s flow control window is `100` (`60 40`). +If the sender opens `stream_3` and would like to send 50 data bytes, it would only be able to send `40` because that is the maximum allowed by the session flow control window at this point. -It is therefore very important that applications notify the fact that they -have consumed data bytes as soon as possible, so that the implementation -(the receiver) can send a message to the sender (in the form of a -`WINDOW_UPDATE` frame) with the information to enlarge the flow control -window, therefore reducing the possibility that sender stalls due to the flow -control windows being reduced to `0`. +It is therefore very important that applications notify the fact that they have consumed data bytes as soon as possible, so that the implementation (the receiver) can send a message to the sender (in the form of a `WINDOW_UPDATE` frame) with the information to enlarge the flow control window, therefore reducing the possibility that sender stalls due to the flow control windows being reduced to `0`. end::flowControl[] tag::apiFlowControl[] -NOTE: Returning from the `onData(...)` method implicitly demands for -more `DATA` frames (unless the one just delivered was the last). -Additional `DATA` frames may be delivered immediately if they are available -or later, asynchronously, when they arrive. +NOTE: Returning from the `onData(...)` method implicitly demands for more `DATA` frames (unless the one just delivered was the last). +Additional `DATA` frames may be delivered immediately if they are available or later, asynchronously, when they arrive. -Applications that consume the content buffer within `onData(...)` -(for example, writing it to a file, or copying the bytes to another storage) -should succeed the callback as soon as they have consumed the content buffer. -This allows the implementation to reuse the buffer, reducing the memory -requirements needed to handle the content buffers. +Applications that consume the content buffer within `onData(...)` (for example, writing it to a file, or copying the bytes to another storage) should succeed the callback as soon as they have consumed the content buffer. +This allows the implementation to reuse the buffer, reducing the memory requirements needed to handle the content buffers. -Alternatively, a client application may store away _both_ the buffer and the -callback to consume the buffer bytes later, or pass _both_ the buffer and -the callback to another asynchronous API (this is typical in proxy -applications). +Alternatively, a client application may store away _both_ the buffer and the callback to consume the buffer bytes later, or pass _both_ the buffer and the callback to another asynchronous API (this is typical in proxy applications). -IMPORTANT: Completing the `Callback` is very important not only to allow the -implementation to reuse the buffer, but also tells the implementation to -enlarge the stream and session flow control windows so that the sender will -be able to send more `DATA` frames without stalling. +IMPORTANT: Completing the `Callback` is very important not only to allow the implementation to reuse the buffer, but also tells the implementation to enlarge the stream and session flow control windows so that the sender will be able to send more `DATA` frames without stalling. -Applications can also precisely control _when_ to demand more `DATA` -frames, by implementing the `onDataDemanded(...)` method instead of -`onData(...)`: +Applications can also precisely control _when_ to demand more `DATA` frames, by implementing the `onDataDemanded(...)` method instead of `onData(...)`: [source,java,indent=0] ---- include::{doc_code}/embedded/HTTP2Docs.java[tags=dataDemanded] ---- -IMPORTANT: Applications that implement `onDataDemanded(...)` must remember -to call `Stream.demand(...)`. If they don't, the implementation will not -deliver `DATA` frames and the application will stall threadlessly until an -idle timeout fires to close the stream or the session. +IMPORTANT: Applications that implement `onDataDemanded(...)` must remember to call `Stream.demand(...)`. +If they don't, the implementation will not deliver `DATA` frames and the application will stall threadlessly until an idle timeout fires to close the stream or the session. end::apiFlowControl[] diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/io-arch.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/io-arch.adoc index 3d11dc73008..10af0da8104 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/io-arch.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/io-arch.adoc @@ -20,33 +20,22 @@ [[eg-io-arch]] == Jetty I/O Architecture -Jetty libraries (both client and server) use Java NIO to handle I/O, so that -at its core Jetty I/O is completely non-blocking. +Jetty libraries (both client and server) use Java NIO to handle I/O, so that at its core Jetty I/O is completely non-blocking. [[eg-io-arch-selector-manager]] === Jetty I/O: `SelectorManager` -The core class of Jetty I/O is -link:{JDURL}/org/eclipse/jetty/io/SelectorManager.html[`SelectorManager`]. +The core class of Jetty I/O is link:{JDURL}/org/eclipse/jetty/io/SelectorManager.html[`SelectorManager`]. -`SelectorManager` manages internally a configurable number of -link:{JDURL}/org/eclipse/jetty/io/ManagedSelector.html[`ManagedSelector`]s. -Each `ManagedSelector` wraps an instance of `java.nio.channels.Selector` that -in turn manages a number of `java.nio.channels.SocketChannel` instances. +`SelectorManager` manages internally a configurable number of link:{JDURL}/org/eclipse/jetty/io/ManagedSelector.html[`ManagedSelector`]s. +Each `ManagedSelector` wraps an instance of `java.nio.channels.Selector` that in turn manages a number of `java.nio.channels.SocketChannel` instances. NOTE: TODO: add image -`SocketChannel` instances can be created by network clients when connecting -to a server and by a network server when accepting connections from network -clients. -In both cases the `SocketChannel` instance is passed to `SelectorManager` -(which passes it to `ManagedSelector` and eventually to -`java.nio.channels.Selector`) to be registered for use within Jetty. +`SocketChannel` instances can be created by network clients when connecting to a server and by a network server when accepting connections from network clients. +In both cases the `SocketChannel` instance is passed to `SelectorManager` (which passes it to `ManagedSelector` and eventually to `java.nio.channels.Selector`) to be registered for use within Jetty. -It is possible for an application to create the `SocketChannel` -instances outside Jetty, even perform some initial network traffic also -outside Jetty (for example for authentication purposes), and then pass the -`SocketChannel` instance to `SelectorManager` for use within Jetty. +It is possible for an application to create the `SocketChannel` instances outside Jetty, even perform some initial network traffic also outside Jetty (for example for authentication purposes), and then pass the `SocketChannel` instance to `SelectorManager` for use within Jetty. This example shows how a client can connect to a server: @@ -65,143 +54,81 @@ include::{doc_code}/embedded/SelectorManagerDocs.java[tags=accept] [[eg-io-arch-endpoint-connection]] === Jetty I/O: `EndPoint` and `Connection` -``SocketChannel``s that are passed to `SelectorManager` are wrapped into two -related components: -an link:{JDURL}/org/eclipse/jetty/io/EndPoint.html[`EndPoint`] and a -link:{JDURL}/org/eclipse/jetty/io/Connection.html[`Connection`]. +``SocketChannel``s that are passed to `SelectorManager` are wrapped into two related components: an link:{JDURL}/org/eclipse/jetty/io/EndPoint.html[`EndPoint`] and a link:{JDURL}/org/eclipse/jetty/io/Connection.html[`Connection`]. -`EndPoint` is the Jetty abstraction for a `SocketChannel`: you can read bytes -from an `EndPoint` via `EndPoint.fill(ByteBuffer)`, you can write bytes to an -`EndPoint` via `EndPoint.flush(ByteBuffer...)` and -`EndPoint.write(Callback, ByteBuffer...)`, you can close an `EndPoint` via -`EndPoint.close()`, etc. +`EndPoint` is the Jetty abstraction for a `SocketChannel`: you can read bytes from an `EndPoint` via `EndPoint.fill(ByteBuffer)`, you can write bytes to an `EndPoint` via `EndPoint.flush(ByteBuffer...)` and `EndPoint.write(Callback, ByteBuffer...)`, you can close an `EndPoint` via `EndPoint.close()`, etc. -`Connection` is the Jetty abstraction that is responsible to read bytes from -the `EndPoint` and to deserialize the read bytes into objects. -For example, a HTTP/1.1 server-side `Connection` implementation is responsible -to deserialize HTTP/1.1 request bytes into a HTTP request object. -Conversely, a HTTP/1.1 client-side `Connection` implementation is responsible -to deserialize HTTP/1.1 response bytes into a HTTP response object. +`Connection` is the Jetty abstraction that is responsible to read bytes from the `EndPoint` and to deserialize the read bytes into objects. +For example, a HTTP/1.1 server-side `Connection` implementation is responsible to deserialize HTTP/1.1 request bytes into a HTTP request object. +Conversely, a HTTP/1.1 client-side `Connection` implementation is responsible to deserialize HTTP/1.1 response bytes into a HTTP response object. -`Connection` is the abstraction that implements the reading side of a specific -protocol such as HTTP/1.1, or HTTP/2, or WebSocket: it is able to read incoming -communication in that protocol. +`Connection` is the abstraction that implements the reading side of a specific protocol such as HTTP/1.1, or HTTP/2, or WebSocket: it is able to read incoming communication in that protocol. -The writing side for a specific protocol _may_ be implemented in the `Connection` -but may also be implemented in other components, although eventually the bytes -to be written will be written through the `EndPoint`. +The writing side for a specific protocol _may_ be implemented in the `Connection` but may also be implemented in other components, although eventually the bytes to be written will be written through the `EndPoint`. -While there is primarily just one implementation of `EndPoint`, -link:{JDURL}/org/eclipse/jetty/io/SocketChannelEndPoint.html[`SocketChannelEndPoint`] -(used both on the client-side and on the server-side), there are many -implementations of `Connection`, typically two for each protocol (one for the -client-side and one for the server-side). +While there is primarily just one implementation of `EndPoint`,link:{JDURL}/org/eclipse/jetty/io/SocketChannelEndPoint.html[`SocketChannelEndPoint`] (used both on the client-side and on the server-side), there are many implementations of `Connection`, typically two for each protocol (one for the client-side and one for the server-side). -The `EndPoint` and `Connection` pairs can be chained, for example in case of -encrypted communication using the TLS protocol. -There is an `EndPoint` and `Connection` TLS pair where the `EndPoint` reads the -encrypted bytes from the network and the `Connection` decrypts them; next in the -chain there is an `EndPoint` and `Connection` pair where the `EndPoint` "reads" -decrypted bytes (provided by the previous `Connection`) and the `Connection` -deserializes them into specific protocol objects (for example HTTP/2 frame -objects). +The `EndPoint` and `Connection` pairs can be chained, for example in case of encrypted communication using the TLS protocol. +There is an `EndPoint` and `Connection` TLS pair where the `EndPoint` reads the encrypted bytes from the network and the `Connection` decrypts them; next in the chain there is an `EndPoint` and `Connection` pair where the `EndPoint` "reads" decrypted bytes (provided by the previous `Connection`) and the `Connection` deserializes them into specific protocol objects (for example HTTP/2 frame objects). -Certain protocols, such as WebSocket, start the communication with the server -using one protocol (e.g. HTTP/1.1), but then change the communication to use -another protocol (e.g. WebSocket). -`EndPoint` supports changing the `Connection` object on-the-fly via -`EndPoint.upgrade(Connection)`. -This allows to use the HTTP/1.1 `Connection` during the initial communication -and later to replace it with a WebSocket `Connection`. +Certain protocols, such as WebSocket, start the communication with the server using one protocol (e.g. HTTP/1.1), but then change the communication to use another protocol (e.g. WebSocket). +`EndPoint` supports changing the `Connection` object on-the-fly via `EndPoint.upgrade(Connection)`. +This allows to use the HTTP/1.1 `Connection` during the initial communication and later to replace it with a WebSocket `Connection`. NOTE: TODO: add a section on `UpgradeFrom` and `UpgradeTo`? -`SelectorManager` is an abstract class because while it knows how to create -concrete `EndPoint` instances, it does not know how to create protocol -specific `Connection` instances. +`SelectorManager` is an abstract class because while it knows how to create concrete `EndPoint` instances, it does not know how to create protocol specific `Connection` instances. -Creating `Connection` instances is performed on the server-side by -link:{JDURL}/org/eclipse/jetty/server/ConnectionFactory.html[`ConnectionFactory`]s. -and on the client-side by -link:{JDURL}/org/eclipse/jetty/io/ClientConnectionFactory.html[`ClientConnectionFactory`]s +Creating `Connection` instances is performed on the server-side by link:{JDURL}/org/eclipse/jetty/server/ConnectionFactory.html[`ConnectionFactory`]s and on the client-side by link:{JDURL}/org/eclipse/jetty/io/ClientConnectionFactory.html[`ClientConnectionFactory`]s -On the server-side, the component that aggregates a `SelectorManager` with a -set of ``ConnectionFactory``s is -link:{JDURL}/org/eclipse/jetty/server/ServerConnector.html[`ServerConnector`]s. +On the server-side, the component that aggregates a `SelectorManager` with a set of ``ConnectionFactory``s is link:{JDURL}/org/eclipse/jetty/server/ServerConnector.html[`ServerConnector`]s, see xref:eg-server-io-arch[]. -NOTE: TODO: add a link to a server-side specific architecture section - -On the client-side, the components that aggregates a `SelectorManager` with a -set of ``ClientConnectionFactory``s are -link:{JDURL}/org/eclipse/jetty/client/HttpClientTransport.html[`HttpClientTransport`] -subclasses. - -NOTE: TODO: add a link to a client-side specific architecture section +On the client-side, the components that aggregates a `SelectorManager` with a set of ``ClientConnectionFactory``s are link:{JDURL}/org/eclipse/jetty/client/HttpClientTransport.html[`HttpClientTransport`] subclasses, see xref:eg-client-io-arch[]. [[eg-io-arch-endpoint]] === Jetty I/O: `EndPoint` The Jetty I/O library use Java NIO to handle I/O, so that I/O is non-blocking. -At the Java NIO level, in order to be notified when a `SocketChannel` has data -to be read, the `SelectionKey.OP_READ` flag must be set. +At the Java NIO level, in order to be notified when a `SocketChannel` has data to be read, the `SelectionKey.OP_READ` flag must be set. -In the Jetty I/O library, you can call `EndPoint.fillInterested(Callback)` -to declare interest in the "read" (or "fill") event, and the `Callback` parameter -is the object that is notified when such event occurs. +In the Jetty I/O library, you can call `EndPoint.fillInterested(Callback)` to declare interest in the "read" (or "fill") event, and the `Callback` parameter is the object that is notified when such event occurs. -At the Java NIO level, a `SocketChannel` is always writable, unless it becomes -TCP congested. In order to be notified when a `SocketChannel` uncongests and it -is therefore writable again, the `SelectionKey.OP_WRITE` flag must be set. +At the Java NIO level, a `SocketChannel` is always writable, unless it becomes TCP congested. +In order to be notified when a `SocketChannel` uncongests and it is therefore writable again, the `SelectionKey.OP_WRITE` flag must be set. -In the Jetty I/O library, you can call `EndPoint.write(Callback, ByteBuffer...)` -to write the ``ByteBuffer``s and the `Callback` parameter is the object that is -notified when the whole write is finished (i.e. _all_ ``ByteBuffer``s have been -fully written, even if they are delayed by TCP congestion/uncongestion). +In the Jetty I/O library, you can call `EndPoint.write(Callback, ByteBuffer...)` to write the ``ByteBuffer``s and the `Callback` parameter is the object that is notified when the whole write is finished (i.e. _all_ ``ByteBuffer``s have been fully written, even if they are delayed by TCP congestion/uncongestion). -The `EndPoint` APIs abstract out the Java NIO details by providing non-blocking -APIs based on `Callback` objects for I/O operations. -The `EndPoint` APIs are typically called by `Connection` implementations, see -xref:eg-io-arch-connection[this section]. +The `EndPoint` APIs abstract out the Java NIO details by providing non-blocking APIs based on `Callback` objects for I/O operations. +The `EndPoint` APIs are typically called by `Connection` implementations, see xref:eg-io-arch-connection[this section]. [[eg-io-arch-connection]] === Jetty I/O: `Connection` -`Connection` is the abstraction that deserializes incoming bytes into objects, -for example a HTTP request object or a WebSocket frame object, that can be used -by more abstract layers. +`Connection` is the abstraction that deserializes incoming bytes into objects, for example a HTTP request object or a WebSocket frame object, that can be used by more abstract layers. `Connection` instances have two lifecycle methods: -* `Connection.onOpen()`, invoked when the `Connection` is associated with the -`EndPoint` -* `Connection.onClose(Throwable)`, invoked when the `Connection` is disassociated -from the `EndPoint`, where the `Throwable` parameter indicates whether the -disassociation was normal (when the parameter is `null`) or was due to an error -(when the parameter is not `null`) +* `Connection.onOpen()`, invoked when the `Connection` is associated with the `EndPoint` +* `Connection.onClose(Throwable)`, invoked when the `Connection` is disassociated from the `EndPoint`, where the `Throwable` parameter indicates whether the disassociation was normal (when the parameter is `null`) or was due to an error (when the parameter is not `null`) -When a `Connection` is first created, it is not registered for any Java NIO -event. -It is therefore typical to implement `onOpen()` to call -`EndPoint.fillInterested(Callback)` so that the `Connection` declares interest -for read events and it is invoked (via the `Callback`) when the read event -happens. +When a `Connection` is first created, it is not registered for any Java NIO event. +It is therefore typical to implement `onOpen()` to call `EndPoint.fillInterested(Callback)` so that the `Connection` declares interest for read events and it is invoked (via the `Callback`) when the read event happens. -Abstract class `AbstractConnection` partially implements `Connection` and -provides simpler APIs. The example below shows a typical implementation that -extends `AbstractConnection`: +Abstract class `AbstractConnection` partially implements `Connection` and provides simpler APIs. +The example below shows a typical implementation that extends `AbstractConnection`: [source,java,indent=0] ---- include::{doc_code}/embedded/SelectorManagerDocs.java[tags=connection] ---- +// TODO: Introduce Connection.Listener + [[eg-io-arch-echo]] === Jetty I/O: Network Echo -With the concepts above it is now possible to write a simple, fully non-blocking, -`Connection` implementation that simply echoes the bytes that it reads back -to the other peer. +With the concepts above it is now possible to write a simple, fully non-blocking, `Connection` implementation that simply echoes the bytes that it reads back to the other peer. A naive, but wrong, implementation may be the following: @@ -212,9 +139,7 @@ include::{doc_code}/embedded/SelectorManagerDocs.java[tags=echo-wrong] WARNING: The implementation above is wrong and leads to `StackOverflowError`. -The problem with this implementation is that if the writes always complete -synchronously (i.e. without being delayed by TCP congestion), you end up with -this sequence of calls: +The problem with this implementation is that if the writes always complete synchronously (i.e. without being delayed by TCP congestion), you end up with this sequence of calls: ---- Connection.onFillable() @@ -228,13 +153,10 @@ Connection.onFillable() which leads to `StackOverflowError`. -This is a typical side effect of asynchronous programming using non-blocking -APIs, and happens in the Jetty I/O library as well. +This is a typical side effect of asynchronous programming using non-blocking APIs, and happens in the Jetty I/O library as well. NOTE: The callback is invoked synchronously for efficiency reasons. -Submitting the invocation of the callback to an `Executor` to be invoked in -a different thread would cause a context switch and make simple writes -extremely inefficient. +Submitting the invocation of the callback to an `Executor` to be invoked in a different thread would cause a context switch and make simple writes extremely inefficient. A correct implementation is the following: @@ -243,23 +165,15 @@ A correct implementation is the following: include::{doc_code}/embedded/SelectorManagerDocs.java[tags=echo-correct] ---- -The correct implementation performs consecutive reads in a loop (rather than -recursively), but _only_ if the correspondent write is completed successfully. +The correct implementation performs consecutive reads in a loop (rather than recursively), but _only_ if the correspondent write is completed successfully. -In order to detect whether the write is completed, a concurrent state machine -is used. This is necessary because the notification of the completion of the -write may happen in a different thread, while the original writing thread -may still be changing the state. +In order to detect whether the write is completed, a concurrent state machine is used. +This is necessary because the notification of the completion of the write may happen in a different thread, while the original writing thread may still be changing the state. -The original writing thread starts moves the state from `IDLE` to `WRITING`, -then issues the actual `write()` call. -The original writing thread then assumes that the `write()` did not complete -and tries to move to the `PENDING` state just after the `write()`. -If it fails to move from the `WRITING` state to the `PENDING` state, it means -that the write was completed. -Otherwise, the write is now `PENDING` and waiting for the callback to be -notified of the completion at a later time. -When the callback is notified of the `write()` completion, it checks whether -the `write()` was `PENDING`, and if it was it resumes reading. +The original writing thread starts moves the state from `IDLE` to `WRITING`, then issues the actual `write()` call. +The original writing thread then assumes that the `write()` did not complete and tries to move to the `PENDING` state just after the `write()`. +If it fails to move from the `WRITING` state to the `PENDING` state, it means that the write was completed. +Otherwise, the write is now `PENDING` and waiting for the callback to be notified of the completion at a later time. +When the callback is notified of the `write()` completion, it checks whether the `write()` was `PENDING`, and if it was it resumes reading. NOTE: TODO: Introduce IteratingCallback? diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-connector.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-connector.adoc index 7af8e7dc4df..1c13d365b76 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-connector.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-connector.adoc @@ -19,48 +19,34 @@ [[eg-server-http-connector]] === Server Connectors -A `Connector` is the component that handles incoming requests from clients, -and works in conjunction with `ConnectionFactory` instances. +A `Connector` is the component that handles incoming requests from clients, and works in conjunction with `ConnectionFactory` instances. -The primary implementation is `org.eclipse.jetty.server.ServerConnector`. -`ServerConnector` uses a `java.nio.channels.ServerSocketChannel` to listen -to a TCP port and to accept TCP connections. +The primary implementation is `org.eclipse.jetty.server.ServerConnector`.`ServerConnector` uses a `java.nio.channels.ServerSocketChannel` to listen to a TCP port and to accept TCP connections. -Since `ServerConnector` wraps a `ServerSocketChannel`, it can be configured -in a similar way, for example the port to listen to, the network address -to bind to, etc.: +Since `ServerConnector` wraps a `ServerSocketChannel`, it can be configured in a similar way, for example the port to listen to, the network address to bind to, etc.: [source,java,indent=0] ---- include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=configureConnector] ---- -The _acceptors_ are threads (typically only one) that compete to accept TCP -connections on the listening port. -When a connection is accepted, `ServerConnector` wraps the accepted -`SocketChannel` and passes it to the -xref:eg-io-arch-selector-manager[`SelectorManager`]. -Therefore, there is a little moment where the acceptor thread is not accepting -new connections because it is busy wrapping the just accepted one to pass it -to the `SelectorManager`. -Connections that are ready to be accepted but are not accepted yet are queued -in a bounded queue (at the OS level) whose capacity can be configured with the -`ServerConnector.acceptQueueSize` parameter. +The _acceptors_ are threads (typically only one) that compete to accept TCP connections on the listening port. +When a connection is accepted, `ServerConnector` wraps the accepted `SocketChannel` and passes it to the xref:eg-io-arch-selector-manager[`SelectorManager`]. +Therefore, there is a little moment where the acceptor thread is not accepting new connections because it is busy wrapping the just accepted one to pass it to the `SelectorManager`. +Connections that are ready to be accepted but are not accepted yet are queued in a bounded queue (at the OS level) whose capacity can be configured with the `ServerConnector.acceptQueueSize` parameter. -If your application must withstand a very high rate of connections opened, -configuring more than one acceptor thread may be beneficial: when one acceptor -thread accepts one connection, another acceptor thread can take over accepting -connections. +If your application must withstand a very high rate of connections opened, configuring more than one acceptor thread may be beneficial: when one acceptor thread accepts one connection, another acceptor thread can take over accepting connections. -The _selectors_ are components that manage a set of connected sockets, -implemented by xref:eg-io-arch-selector-manager[`ManagedSelector`]. -Each selector requires one thread and uses the Java NIO mechanism to -efficiently handle the set of connected sockets. -As a rule of thumb, a single selector can easily manage up to 1000-5000 -sockets, although the number may vary greatly depending on the application. +The _selectors_ are components that manage a set of connected sockets, implemented by xref:eg-io-arch-selector-manager[`ManagedSelector`]. +Each selector requires one thread and uses the Java NIO mechanism to efficiently handle the set of connected sockets. +As a rule of thumb, a single selector can easily manage up to 1000-5000 sockets, although the number may vary greatly depending on the application. -It is possible to configure more than one `ServerConnector`, each listening -on a different port: +For example, web site applications tend to use sockets for one or more HTTP requests to retrieve resources and then the socket is idle for most of the time. +In this case a single selector may be able to manage many sockets because chances are that they will be idle most of the time. +On the contrary, web messaging applications tend to send many small messages at a very high frequency so that the socket is rarely idle. +In this case a single selector may be able to manage less sockets because chances are that many of them will be active at the same time. + +It is possible to configure more than one `ServerConnector`, each listening on a different port: [source,java,indent=0] ---- @@ -70,20 +56,15 @@ include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=configur [[eg-server-http-connector-protocol]] ==== Configuring Protocols -For each accepted TCP connection, `ServerConnector` asks a `ConnectionFactory` -to create a `Connection` object that handles the network traffic on that TCP -connection, parsing and generating bytes for a specific protocol (see -xref:eg-io-arch[this section] for more details about `Connection` objects). +For each accepted TCP connection, `ServerConnector` asks a `ConnectionFactory` to create a `Connection` object that handles the network traffic on that TCP connection, parsing and generating bytes for a specific protocol (see xref:eg-io-arch[this section] for more details about `Connection` objects). A `ServerConnector` can be configured with one or more ``ConnectionFactory``s. -If no `ConnectionFactory` is specified then `HttpConnectionFactory` is -implicitly configured. +If no `ConnectionFactory` is specified then `HttpConnectionFactory` is implicitly configured. [[eg-server-http-connector-protocol-http11]] ===== Configuring HTTP/1.1 -`HttpConnectionFactory` creates `HttpConnection` objects that parse bytes -and generate bytes for the HTTP/1.1 protocol. +`HttpConnectionFactory` creates `HttpConnection` objects that parse bytes and generate bytes for the HTTP/1.1 protocol. This is how you configure Jetty to support clear-text HTTP/1.1: @@ -92,10 +73,7 @@ This is how you configure Jetty to support clear-text HTTP/1.1: include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=http11] ---- -Supporting encrypted HTTP/1.1 (that is, requests with the HTTPS scheme) -is supported by configuring an `SslContextFactory` that has access to the -keyStore containing the private server key and public server certificate, -in this way: +Supporting encrypted HTTP/1.1 (that is, requests with the HTTPS scheme)is supported by configuring an `SslContextFactory` that has access to the keyStore containing the private server key and public server certificate, in this way: [source,java,indent=0] ---- @@ -105,20 +83,11 @@ include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=tlsHttp1 [[eg-server-http-connector-protocol-proxy-http11]] ===== Configuring Jetty behind a Load Balancer -It is often the case that Jetty receives connections from a load balancer -configured to distribute the load among many Jetty backend servers. +It is often the case that Jetty receives connections from a load balancer configured to distribute the load among many Jetty backend servers. -From the Jetty point of view, all the connections arrive from the load -balancer, rather than the real clients, but is possible to configure the load -balancer to forward the real client IP address and port to the backend Jetty -server using the -link:https://www.haproxy.org/download/2.1/doc/proxy-protocol.txt[PROXY protocol]. +From the Jetty point of view, all the connections arrive from the load balancer, rather than the real clients, but is possible to configure the load balancer to forward the real client IP address and port to the backend Jetty server using the link:https://www.haproxy.org/download/2.1/doc/proxy-protocol.txt[PROXY protocol]. -NOTE: The PROXY protocol is widely supported by load balancers such as -link:http://cbonte.github.io/haproxy-dconv/2.2/configuration.html#5.2-send-proxy[HAProxy] -(via its `send-proxy` directive), -link:https://docs.nginx.com/nginx/admin-guide/load-balancer/using-proxy-protocol[Nginx] -(via its `proxy_protocol on` directive) and others. +NOTE: The PROXY protocol is widely supported by load balancers such as link:http://cbonte.github.io/haproxy-dconv/2.2/configuration.html#5.2-send-proxy[HAProxy] (via its `send-proxy` directive), link:https://docs.nginx.com/nginx/admin-guide/load-balancer/using-proxy-protocol[Nginx](via its `proxy_protocol on` directive) and others. To support this case, Jetty can be configured in this way: @@ -127,57 +96,36 @@ To support this case, Jetty can be configured in this way: include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=proxyHTTP] ---- -Note how the ``ConnectionFactory``s passed to `ServerConnector` are in order: -first PROXY, then HTTP/1.1. -Note also how the PROXY `ConnectionFactory` needs to know its _next_ protocol -(in this example, HTTP/1.1). +Note how the ``ConnectionFactory``s passed to `ServerConnector` are in order: first PROXY, then HTTP/1.1. +Note also how the PROXY `ConnectionFactory` needs to know its _next_ protocol (in this example, HTTP/1.1). -Each `ConnectionFactory` is asked to create a `Connection` object for each -accepted TCP connection; the `Connection` objects will be chained together -to handle the bytes, each for its own protocol. -Therefore the `ProxyConnection` will handle the PROXY protocol bytes and -`HttpConnection` will handle the HTTP/1.1 bytes producing a request object -and response object that will be processed by ``Handler``s. +Each `ConnectionFactory` is asked to create a `Connection` object for each accepted TCP connection; the `Connection` objects will be chained together to handle the bytes, each for its own protocol. +Therefore the `ProxyConnection` will handle the PROXY protocol bytes and `HttpConnection` will handle the HTTP/1.1 bytes producing a request object and response object that will be processed by ``Handler``s. [[eg-server-http-connector-protocol-http2]] ===== Configuring HTTP/2 -It is well known that the HTTP ports are `80` (for clear-text HTTP) and `443` -for encrypted HTTP. -By using those ports, a client had _prior knowledge_ that the server would -speak, respectively, the HTTP/1.x protocol and the TLS protocol (and, after -decryption, the HTTP/1.x protocol). +It is well known that the HTTP ports are `80` (for clear-text HTTP) and `443` for encrypted HTTP. +By using those ports, a client had _prior knowledge_ that the server would speak, respectively, the HTTP/1.x protocol and the TLS protocol (and, after decryption, the HTTP/1.x protocol). -HTTP/2 was designed to be a smooth transition from HTTP/1.1 for users and -as such the HTTP ports were not changed. -However the HTTP/2 protocol is, on the wire, a binary protocol, completely -different from HTTP/1.1. -Therefore, with HTTP/2, clients that connect to port `80` may speak either -HTTP/1.1 or HTTP/2, and the server must figure out which version of the HTTP -protocol the client is speaking. +HTTP/2 was designed to be a smooth transition from HTTP/1.1 for users and as such the HTTP ports were not changed. +However the HTTP/2 protocol is, on the wire, a binary protocol, completely different from HTTP/1.1. +Therefore, with HTTP/2, clients that connect to port `80` may speak either HTTP/1.1 or HTTP/2, and the server must figure out which version of the HTTP protocol the client is speaking. -Jetty can support both HTTP/1.1 and HTTP/2 on the same clear-text port by -configuring both the HTTP/1.1 and the HTTP/2 ``ConnectionFactory``s: +Jetty can support both HTTP/1.1 and HTTP/2 on the same clear-text port by configuring both the HTTP/1.1 and the HTTP/2 ``ConnectionFactory``s: [source,java,indent=0] ---- include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=http11H2C] ---- -Note how the ``ConnectionFactory``s passed to `ServerConnector` are in order: -first HTTP/1.1, then HTTP/2. -This is necessary to support both protocols on the same port: Jetty will -start parsing the incoming bytes as HTTP/1.1, but then realize that they -are HTTP/2 bytes and will therefore _upgrade_ from HTTP/1.1 to HTTP/2. +Note how the ``ConnectionFactory``s passed to `ServerConnector` are in order: first HTTP/1.1, then HTTP/2. +This is necessary to support both protocols on the same port: Jetty will start parsing the incoming bytes as HTTP/1.1, but then realize that they are HTTP/2 bytes and will therefore _upgrade_ from HTTP/1.1 to HTTP/2. -This configuration is also typical when Jetty is installed in backend servers -behind a load balancer that also takes care of offloading TLS. -When Jetty is behind a load balancer, you can always prepend the PROXY -protocol as described in -xref:eg-server-http-connector-protocol-proxy-http11[this section]. +This configuration is also typical when Jetty is installed in backend servers behind a load balancer that also takes care of offloading TLS. +When Jetty is behind a load balancer, you can always prepend the PROXY protocol as described in xref:eg-server-http-connector-protocol-proxy-http11[this section]. -When using encrypted HTTP/2, the unencrypted protocol is negotiated by client -and server using an extension to the TLS protocol called ALPN. +When using encrypted HTTP/2, the unencrypted protocol is negotiated by client and server using an extension to the TLS protocol called ALPN. Jetty supports ALPN and encrypted HTTP/2 with this configuration: @@ -186,10 +134,7 @@ Jetty supports ALPN and encrypted HTTP/2 with this configuration: include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=tlsALPNHTTP] ---- -Note how the ``ConnectionFactory``s passed to `ServerConnector` are in order: -TLS, ALPN, HTTP/1.1, HTTP/2. +Note how the ``ConnectionFactory``s passed to `ServerConnector` are in order: TLS, ALPN, HTTP/1.1, HTTP/2. Jetty starts parsing TLS bytes so that it can obtain the ALPN extension. -With the ALPN extension information, Jetty can negotiate a protocol and -pick, among the ``ConnectionFactory``s supported by the `ServerConnector`, -the `ConnectionFactory` correspondent to the negotiated protocol. +With the ALPN extension information, Jetty can negotiate a protocol and pick, among the ``ConnectionFactory``s supported by the `ServerConnector`, the `ConnectionFactory` correspondent to the negotiated protocol. diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-handler-implement.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-handler-implement.adoc index 36dc9df088d..f65127edc12 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-handler-implement.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-handler-implement.adoc @@ -28,16 +28,10 @@ include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=handlerA The `target` parameter is an identifier for the resource. This is normally the URI that is parsed from an HTTP request. -However, a request could be forwarded to either a named resource, in which case -`target` will be the name of the resource, or to a different URI, in which case -`target` will be the new URI. +However, a request could be forwarded to either a named resource, in which case `target` will be the name of the resource, or to a different URI, in which case `target` will be the new URI. -Applications may wrap the request or response (or both) and forward the wrapped -request or response to a different URI (which may be possibly handled by a -different `Handler`). -This is the reason why there are two request parameters in the `Handler` APIs: -the first is the unwrapped, original, request while the second is the -application-wrapped request. +Applications may wrap the request or response (or both) and forward the wrapped request or response to a different URI (which may be possibly handled by a different `Handler`). +This is the reason why there are two request parameters in the `Handler` APIs: the first is the unwrapped, original, request while the second is the application-wrapped request. [[eg-server-http-handler-impl-hello]] ===== Hello World Handler @@ -49,23 +43,17 @@ A simple "Hello World" `Handler` is the following: include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=handlerHello] ---- -Such a simple `Handler` extends from `AbstractHandler` and can access the -request and response main features, such as reading request headers and -content, or writing response headers and content. +Such a simple `Handler` extends from `AbstractHandler` and can access the request and response main features, such as reading request headers and content, or writing response headers and content. [[eg-server-http-handler-impl-filter]] ===== Filtering Handler -A filtering `Handler` is a handler that perform some modification to the -request or response, and then either forwards the request to another -`Handler` or produces an error response: +A filtering `Handler` is a handler that perform some modification to the request or response, and then either forwards the request to another `Handler` or produces an error response: [source,java,indent=0] ---- include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=handlerFilter] ---- -Note how a filtering `Handler` extends from `HandlerWrapper` and as such -needs another handler to forward the request processing to, and how the -two ``Handler``s needs to be linked together to work properly. +Note how a filtering `Handler` extends from `HandlerWrapper` and as such needs another handler to forward the request processing to, and how the two ``Handler``s needs to be linked together to work properly. diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-handler-use.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-handler-use.adoc index 8a0c18903df..9837d2736ae 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-handler-use.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-handler-use.adoc @@ -19,52 +19,31 @@ [[eg-server-http-handler-use]] ==== Using Provided Handlers -Web applications are the unit of deployment in an HTTP server or Servlet -container such as Jetty. +Web applications are the unit of deployment in an HTTP server or Servlet container such as Jetty. -Two different web applications are typically deployed on different -__context path__s, where a _context path_ is the initial segment of the URI -path. -For example, web application `webappA` that implements a web user interface -for an e-commerce site may be deployed to context path `/shop`, while web -application `webappB` that implements a REST API for the e-commerce business -may be deployed to `/api`. +Two different web applications are typically deployed on different __context path__s, where a _context path_ is the initial segment of the URI path. +For example, web application `webappA` that implements a web user interface for an e-commerce site may be deployed to context path `/shop`, while web application `webappB` that implements a REST API for the e-commerce business may be deployed to `/api`. -A client making a request to URI `/shop/cart` is directed by Jetty to -`webappA`, while a request to URI `/api/products` is directed to `webappB`. +A client making a request to URI `/shop/cart` is directed by Jetty to `webappA`, while a request to URI `/api/products` is directed to `webappB`. -An alternative way to deploy the two web applications of the example above -is to use _virtual hosts_. -A _virtual host_ is a subdomain of the primary domain that shares the same -IP address with the primary domain. -If the e-commerce business primary domain is `domain.com`, then a virtual -host for `webappA` could be `shop.domain.com`, while a virtual host for -`webappB` could be `api.domain.com`. +An alternative way to deploy the two web applications of the example above is to use _virtual hosts_. +A _virtual host_ is a subdomain of the primary domain that shares the same IP address with the primary domain. +If the e-commerce business primary domain is `domain.com`, then a virtual host for `webappA` could be `shop.domain.com`, while a virtual host for `webappB` could be `api.domain.com`. -Web application `webappA` can now be deployed to virtual host -`shop.domain.com` and context path `/`, while web application `webappB` -can be deployed to virtual host `api.domain.com` and context path `/`. -Both applications have the same context path `/`, but they can be -distinguished by the subdomain. +Web application `webappA` can now be deployed to virtual host `shop.domain.com` and context path `/`, while web application `webappB` can be deployed to virtual host `api.domain.com` and context path `/`. +Both applications have the same context path `/`, but they can be distinguished by the subdomain. -A client making a request to `+https://shop.domain.com/cart+` is -directed by Jetty to `webappA`, while a request to -`+https://api.domain.com/products+` is directed to `webappB`. +A client making a request to `+https://shop.domain.com/cart+` is directed by Jetty to `webappA`, while a request to `+https://api.domain.com/products+` is directed to `webappB`. -Therefore, in general, a web application is deployed to a _context_ -which can be seen as the pair `(virtual_host, context_path)`. -In the first case the contexts were `(domain.com, /shop)` and -`(domain.com, /api)`, while in the second case the contexts were -`(shop.domain.com, /)` and `(api.domain.com, /)`. -Server applications using the Jetty Server Libraries create and -configure a _context_ for each web application. +Therefore, in general, a web application is deployed to a _context_ which can be seen as the pair `(virtual_host, context_path)`. +In the first case the contexts were `(domain.com, /shop)` and `(domain.com, /api)`, while in the second case the contexts were `(shop.domain.com, /)` and `(api.domain.com, /)`. +Server applications using the Jetty Server Libraries create and configure a _context_ for each web application. [[eg-server-http-handler-use-context]] ===== ContextHandler -`ContextHandler` is a `Handler` that represents a _context_ for a web -application. It is a `HandlerWrapper` that performs some action before -and after delegating to the nested `Handler`. +`ContextHandler` is a `Handler` that represents a _context_ for a web application. +It is a `HandlerWrapper` that performs some action before and after delegating to the nested `Handler`. // TODO: expand on what the ContextHandler does, e.g. ServletContext. The simplest use of `ContextHandler` is the following: @@ -88,22 +67,14 @@ Server Server applications may need to deploy to Jetty more than one web application. -Recall from the xref:eg-server-http-handler[introduction] that Jetty offers -`HandlerCollection` and `HandlerList` that may contain a sequence of children -``Handler``s. -However, both of these have no knowledge of the concept of _context_ and just -iterate through the sequence of ``Handler``s. +Recall from the xref:eg-server-http-handler[introduction] that Jetty offers `HandlerCollection` and `HandlerList` that may contain a sequence of children ``Handler``s. +However, both of these have no knowledge of the concept of _context_ and just iterate through the sequence of ``Handler``s. -A better choice for multiple web application is `ContextHandlerCollection`, -that matches a _context_ from either its _context path_ or _virtual host_, -without iterating through the ``Handler``s. +A better choice for multiple web application is `ContextHandlerCollection`, that matches a _context_ from either its _context path_ or _virtual host_, without iterating through the ``Handler``s. If `ContextHandlerCollection` does not find a match, it just returns. -What happens next depends on the `Handler` tree structure: other ``Handler``s -may be invoked after `ContextHandlerCollection`, for example `DefaultHandler` -(see xref:eg-server-http-handler-use-util-default-handler[this section]). -Eventually, if `Request.setHandled(true)` is not called, Jetty returns a HTTP -`404` response to the client. +What happens next depends on the `Handler` tree structure: other ``Handler``s may be invoked after `ContextHandlerCollection`, for example `DefaultHandler` (see xref:eg-server-http-handler-use-util-default-handler[this section]). +Eventually, if `Request.setHandled(true)` is not called, Jetty returns an HTTP `404` response to the client. [source,java,indent=0] ---- @@ -125,11 +96,9 @@ Server [[eg-server-http-handler-use-servlet-context]] ===== ServletContextHandler -``Handler``s are easy to write, but often web applications have already been -written using the Servlet APIs, using ``Servlet``s and ``Filter``s. +``Handler``s are easy to write, but often web applications have already been written using the Servlet APIs, using ``Servlet``s and ``Filter``s. -`ServletContextHandler` is a `ContextHandler` that provides support for the -Servlet APIs and implements the behaviors required by the Servlet specification. +`ServletContextHandler` is a `ContextHandler` that provides support for the Servlet APIs and implements the behaviors required by the Servlet specification. The Maven artifact coordinates are: @@ -157,36 +126,23 @@ Server └── _CrossOriginFilter /*_ ---- -Note how the Servlet components (they are not ``Handler``s) are represented in -_italic_. +Note how the Servlet components (they are not ``Handler``s) are represented in _italic_. -Note also how adding a `Servlet` or a `Filter` returns a _holder_ object that -can be used to specify additional configuration for that particular `Servlet` -or `Filter`. +Note also how adding a `Servlet` or a `Filter` returns a _holder_ object that can be used to specify additional configuration for that particular `Servlet` or `Filter`. -When a request arrives to `ServletContextHandler` the request URI will be -matched against the ``Filter``s and ``Servlet`` mappings and only those that -match will process the request, as dictated by the Servlet specification. +When a request arrives to `ServletContextHandler` the request URI will be matched against the ``Filter``s and ``Servlet`` mappings and only those that match will process the request, as dictated by the Servlet specification. -IMPORTANT: `ServletContextHandler` is a terminal `Handler`, that is it always -calls `Request.setHandled(true)` when invoked. -Server applications must be careful when creating the `Handler` -tree to put ``ServletContextHandler``s as last ``Handler``s in a `HandlerList` -or as children of `ContextHandlerCollection`. +IMPORTANT: `ServletContextHandler` is a terminal `Handler`, that is it always calls `Request.setHandled(true)` when invoked. +Server applications must be careful when creating the `Handler` tree to put ``ServletContextHandler``s as last ``Handler``s in a `HandlerList` or as children of `ContextHandlerCollection`. [[eg-server-http-handler-use-webapp-context]] ===== WebAppContext -`WebAppContext` is a `ServletContextHandler` that auto configures itself by -reading a `web.xml` Servlet configuration file. +`WebAppContext` is a `ServletContextHandler` that auto configures itself by reading a `web.xml` Servlet configuration file. -Server applications can specify a `+*.war+` file or a directory with the -structure of a `+*.war+` file to `WebAppContext` to deploy a standard Servlet -web application packaged as a `war` (as defined by the Servlet specification). +Server applications can specify a `+*.war+` file or a directory with the structure of a `+*.war+` file to `WebAppContext` to deploy a standard Servlet web application packaged as a `war` (as defined by the Servlet specification). -Where server applications using `ServletContextHandler` must manually invoke -methods to add ``Servlet``s and ``Filter``s, `WebAppContext` reads -`WEB-INF/web.xml` to add ``Servlet``s and ``Filter``s. +Where server applications using `ServletContextHandler` must manually invoke methods to add ``Servlet``s and ``Filter``s, `WebAppContext` reads `WEB-INF/web.xml` to add ``Servlet``s and ``Filter``s. [source,java,indent=0] ---- @@ -196,45 +152,24 @@ include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=webAppCo [[eg-server-http-handler-use-resource-handler]] ===== ResourceHandler -- Static Content -Static content such as images or files (HTML, JavaScript, CSS) can be sent -by Jetty very efficiently because Jetty can write the content asynchronously, -using direct ``ByteBuffer``s to minimize data copy, and using a memory cache -for faster access to the data to send. +Static content such as images or files (HTML, JavaScript, CSS) can be sent by Jetty very efficiently because Jetty can write the content asynchronously, using direct ``ByteBuffer``s to minimize data copy, and using a memory cache for faster access to the data to send. -Being able to write content asynchronously means that if the network gets -congested (for example, the client reads the content very slowly) and the -server stalls the send of the requested data, then Jetty will wait to resume -the send _without_ blocking a thread to finish the send. +Being able to write content asynchronously means that if the network gets congested (for example, the client reads the content very slowly) and the server stalls the send of the requested data, then Jetty will wait to resume the send _without_ blocking a thread to finish the send. `ResourceHandler` supports the following features: * Welcome files, for example serving `/index.html` for request URI `/` -* Precompressed resources, serving a precompressed `/document.txt.gz` for -request URI `/document.txt` -* link:https://tools.ietf.org/html/rfc7233[Range requests], for requests -containing the `Range` header, which allows clients to pause and resume -downloads of large files -* Directory listing, serving a HTML page with the file list of the requested -directory -* Conditional headers, for requests containing the `If-Match`, `If-None-Match`, -`If-Modified-Since`, `If-Unmodified-Since` headers. +* Precompressed resources, serving a precompressed `/document.txt.gz` for request URI `/document.txt` +* link:https://tools.ietf.org/html/rfc7233[Range requests], for requests containing the `Range` header, which allows clients to pause and resume downloads of large files +* Directory listing, serving a HTML page with the file list of the requested directory +* Conditional headers, for requests containing the `If-Match`, `If-None-Match`, `If-Modified-Since`, `If-Unmodified-Since` headers. -The number of features supported and the efficiency in sending static content -are on the same level as those of common front-end servers used to serve -static content such as Nginx or Apache. -Therefore, the traditional architecture where Nginx/Apache was the front-end -server used only to send static content and Jetty was the back-end server used -only to send dynamic content is somehow obsolete as Jetty can perform -efficiently both tasks. -This leads to simpler systems (less components to configure and manage) and -more performance (no need to proxy dynamic requests from front-end servers -to back-end servers). +The number of features supported and the efficiency in sending static content are on the same level as those of common front-end servers used to serve static content such as Nginx or Apache. +Therefore, the traditional architecture where Nginx/Apache was the front-end server used only to send static content and Jetty was the back-end server used only to send dynamic content is somehow obsolete as Jetty can perform efficiently both tasks. +This leads to simpler systems (less components to configure and manage) and more performance (no need to proxy dynamic requests from front-end servers to back-end servers). -NOTE: It is common to use Nginx/Apache as load balancers, or as rewrite/redirect -servers. We typically recommend link:https://haproxy.org[HAProxy] as load -balancer, and Jetty has -xref:eg-server-http-handler-use-util-rewrite-handler[rewrite/redirect features] -as well. +NOTE: It is common to use Nginx/Apache as load balancers, or as rewrite/redirect servers. +We typically recommend link:https://haproxy.org[HAProxy] as load balancer, and Jetty has xref:eg-server-http-handler-use-util-rewrite-handler[rewrite/redirect features] as well. This is how you configure a `ResourceHandler` to create a simple file server: @@ -250,19 +185,14 @@ If you need to serve static resources from multiple directories: include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=multipleResourcesHandler] ---- -If the resource is not found, `ResourceHandler` will not call -`Request.setHandled(true)` so what happens next depends on the `Handler` -tree structure. See also -xref:eg-server-http-handler-use-util-default-handler[how to use] `DefaultHandler`. +If the resource is not found, `ResourceHandler` will not call `Request.setHandled(true)` so what happens next depends on the `Handler` tree structure. +See also xref:eg-server-http-handler-use-util-default-handler[how to use] `DefaultHandler`. [[eg-server-http-handler-use-default-servlet]] ===== DefaultServlet -- Static Content for Servlets -If you have a -xref:eg-server-http-handler-use-servlet-context[Servlet web application], -you may want to use a `DefaultServlet` instead of `ResourceHandler`. -The features are similar, but `DefaultServlet` is more commonly used to -serve static files for Servlet web applications. +If you have a xref:eg-server-http-handler-use-servlet-context[Servlet web application], you may want to use a `DefaultServlet` instead of `ResourceHandler`. +The features are similar, but `DefaultServlet` is more commonly used to serve static files for Servlet web applications. [source,java,indent=0] ---- @@ -272,15 +202,10 @@ include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=defaultS [[eg-server-http-handler-use-util-gzip-handler]] ===== GzipHandler -`GzipHandler` provides supports for automatic decompression of compressed -request content and automatic compression of response content. +`GzipHandler` provides supports for automatic decompression of compressed request content and automatic compression of response content. -`GzipHandler` is a `HandlerWrapper` that inspects the request and, if the -request matches the `GzipHandler` configuration, just installs the required -components to eventually perform decompression of the request content or -compression of the response content. -The decompression/compression is not performed until the web application -reads request content or writes response content. +`GzipHandler` is a `HandlerWrapper` that inspects the request and, if the request matches the `GzipHandler` configuration, just installs the required components to eventually perform decompression of the request content or compression of the response content. +The decompression/compression is not performed until the web application reads request content or writes response content. `GzipHandler` can be configured at the server level in this way: @@ -301,10 +226,7 @@ Server └── ContextHandler N ---- -However, in less common cases, you can configure `GzipHandler` on a -per-context basis, for example because you want to configure `GzipHandler` -with different parameters for each context, or because you want only some -contexts to have compression support: +However, in less common cases, you can configure `GzipHandler` on a per-context basis, for example because you want to configure `GzipHandler` with different parameters for each context, or because you want only some contexts to have compression support: [source,java,indent=0] ---- @@ -330,10 +252,7 @@ Server [[eg-server-http-handler-use-util-rewrite-handler]] ===== RewriteHandler -`RewriteHandler` provides support for URL rewriting, very similarly to -link:https://httpd.apache.org/docs/current/mod/mod_rewrite.html[Apache's mod_rewrite] -or -link:https://nginx.org/en/docs/http/ngx_http_rewrite_module.html[Nginx rewrite module]. +`RewriteHandler` provides support for URL rewriting, very similarly to link:https://httpd.apache.org/docs/current/mod/mod_rewrite.html[Apache's mod_rewrite] or link:https://nginx.org/en/docs/http/ngx_http_rewrite_module.html[Nginx rewrite module]. The Maven artifact coordinates are: @@ -346,20 +265,13 @@ The Maven artifact coordinates are: ---- -`RewriteHandler` can be configured with a set of __rule__s; a _rule_ inspects -the request and when it matches it performs some change to the request (for -example, changes the URI path, adds/removes headers, etc.). +`RewriteHandler` can be configured with a set of __rule__s; a _rule_ inspects the request and when it matches it performs some change to the request (for example, changes the URI path, adds/removes headers, etc.). -The Jetty Server Libraries provide rules for the most common usages, but you -can write your own rules by extending the -`org.eclipse.jetty.rewrite.handler.Rule` class. +The Jetty Server Libraries provide rules for the most common usages, but you can write your own rules by extending the `org.eclipse.jetty.rewrite.handler.Rule` class. -Please refer to the `jetty-rewrite` module -link:{JDURL}/org/eclipse/jetty/rewrite/handler/package-summary.html[javadocs] -for the complete list of available rules. +Please refer to the `jetty-rewrite` module link:{JDURL}/org/eclipse/jetty/rewrite/handler/package-summary.html[javadocs] for the complete list of available rules. -You typically want to configure `RewriteHandler` at the server level, although -it is possible to configure it on a per-context basis. +You typically want to configure `RewriteHandler` at the server level, although it is possible to configure it on a per-context basis. [source,java,indent=0] ---- @@ -381,22 +293,18 @@ Server [[eg-server-http-handler-use-util-stats-handler]] ===== StatisticsHandler -`StatisticsHandler` gathers and exposes a number of statistic values related -to request processing such as: +`StatisticsHandler` gathers and exposes a number of statistic values related to request processing such as: * Total number of requests * Current number of concurrent requests * Minimum, maximum, average and standard deviation of request processing times -* Number of responses grouped by HTTP code (i.e. how many `2xx` responses, how -many `3xx` responses, etc.) +* Number of responses grouped by HTTP code (i.e. how many `2xx` responses, how many `3xx` responses, etc.) * Total response content bytes -Server applications can read these values and use them internally, or expose -them via some service, or export them via JMX. +Server applications can read these values and use them internally, or expose them via some service, or export them via JMX. // TODO: xref to the JMX section. -`StatisticsHandler` can be configured at the server level or at the context -level. +`StatisticsHandler` can be configured at the server level or at the context level. [source,java,indent=0] ---- @@ -418,19 +326,13 @@ Server [[eg-server-http-handler-use-util-secure-handler]] ===== SecuredRedirectHandler -- Redirect from HTTP to HTTPS -`SecuredRedirectHandler` allows to redirect requests made with the `http` -scheme (and therefore to the clear-text port) to the `https` scheme (and -therefore to the encrypted port). +`SecuredRedirectHandler` allows to redirect requests made with the `http` scheme (and therefore to the clear-text port) to the `https` scheme (and therefore to the encrypted port). -For example a request to `+http://domain.com:8080/path?param=value+` is -redirected to `+https://domain.com:8443/path?param=value+`. +For example a request to `+http://domain.com:8080/path?param=value+` is redirected to `+https://domain.com:8443/path?param=value+`. -Server applications must configure a `HttpConfiguration` object with the -secure scheme and secure port so that `SecuredRedirectHandler` can build -the redirect URI. +Server applications must configure a `HttpConfiguration` object with the secure scheme and secure port so that `SecuredRedirectHandler` can build the redirect URI. -`SecuredRedirectHandler` is typically configured at the server level, -although it can be configured on a per-context basis. +`SecuredRedirectHandler` is typically configured at the server level, although it can be configured on a per-context basis. [source,java,indent=0] ---- @@ -440,16 +342,13 @@ include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=securedH [[eg-server-http-handler-use-util-default-handler]] ===== DefaultHandler -`DefaultHandler` is a terminal `Handler` that always calls -`Request.setHandled(true)` and performs the following: +`DefaultHandler` is a terminal `Handler` that always calls `Request.setHandled(true)` and performs the following: * Serves the `favicon.ico` Jetty icon when it is requested * Sends a HTTP `404` response for any other request -* The HTTP `404` response content nicely shows a HTML table with all the -contexts deployed on the `Server` instance +* The HTTP `404` response content nicely shows a HTML table with all the contexts deployed on the `Server` instance -`DefaultHandler` is best used as the last `Handler` of a `HandlerList`, -for example: +`DefaultHandler` is best used as the last `Handler` of a `HandlerList`, for example: [source,java,indent=0] ---- @@ -469,12 +368,7 @@ Server └── DefaultHandler ---- -In the example above, `ContextHandlerCollection` will try to match a request -to one of the contexts; if the match fails, `HandlerList` will call the next -`Handler` which is `DefaultHandler` that will return a HTTP `404` with an -HTML page showing the existing contexts deployed on the `Server`. +In the example above, `ContextHandlerCollection` will try to match a request to one of the contexts; if the match fails, `HandlerList` will call the next `Handler` which is `DefaultHandler` that will return a HTTP `404` with an HTML page showing the existing contexts deployed on the `Server`. -NOTE: `DefaultHandler` just sends a nicer HTTP `404` response in case of -wrong requests from clients. -Jetty will send an HTTP `404` response anyway if `DefaultHandler` is not -used. +NOTE: `DefaultHandler` just sends a nicer HTTP `404` response in case of wrong requests from clients. +Jetty will send an HTTP `404` response anyway if `DefaultHandler` is not used. diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-handler.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-handler.adoc index 104f2c50ece..f12483e57d7 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-handler.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-handler.adoc @@ -19,31 +19,21 @@ [[eg-server-http-handler]] === Server Handlers -An `org.eclipse.jetty.server.Handler` is the component that processes -incoming HTTP requests and eventually produces HTTP responses. +An `org.eclipse.jetty.server.Handler` is the component that processes incoming HTTP requests and eventually produces HTTP responses. ``Handler``s can be organized in different ways: * in a sequence, where ``Handler``s are invoked one after the other ** `HandlerCollection` invokes _all_ ``Handler``s one after the other -** `HandlerList` invokes ``Handlers``s until one calls `Request.setHandled(true)` -to indicate that the request has been handled and no further `Handler` should -be invoked +** `HandlerList` invokes ``Handlers``s until one calls `Request.setHandled(true)` to indicate that the request has been handled and no further `Handler` should be invoked * nested, where one `Handler` invokes the next, nested, `Handler` ** `HandlerWrapper` implements this behavior -The `HandlerCollection` behavior (invoking _all_ handlers) is useful when -for example the last `Handler` is a logging `Handler` that logs the request -(that may have been modified by previous handlers). +The `HandlerCollection` behavior (invoking _all_ handlers) is useful when for example the last `Handler` is a logging `Handler` that logs the request(that may have been modified by previous handlers). -The `HandlerList` behavior (invoking handlers up to the first that calls -`Request.setHandled(true)`) is useful when each handler processes a different -URIs or a different virtual hosts: ``Handler``s are invoked one after the -other until one matches the URI or virtual host. +The `HandlerList` behavior (invoking handlers up to the first that calls `Request.setHandled(true)`) is useful when each handler processes a different URIs or a different virtual hosts: ``Handler``s are invoked one after the other until one matches the URI or virtual host. -The nested behavior is useful to enrich the request with additional services -such as HTTP session support (`SessionHandler`), or with specific behaviors -dictated by the Servlet specification (`ServletHandler`). +The nested behavior is useful to enrich the request with additional services such as HTTP session support (`SessionHandler`), or with specific behaviors dictated by the Servlet specification (`ServletHandler`). ``Handler``s can be organized in a tree by composing them together: @@ -64,40 +54,12 @@ HandlerCollection └── LoggingHandler ---- -//// -PlantUML cannot render a tree left-aligned :( -[plantuml] ----- -skinparam backgroundColor transparent -skinparam monochrome true -skinparam shadowing false -skinparam padding 5 +Server applications should rarely write custom ``Handler``s, preferring instead to use existing ``Handler``s provided by the Jetty Server Libraries for managing web application contexts, security, HTTP sessions and Servlet support. +Refer to xref:eg-server-http-handler-use[this section] for more information about how to use the ``Handler``s provided by the Jetty Server Libraries. -scale 1.5 - -hide members -hide circle - -HandlerCollection -- HandlerList -HandlerCollection -- LoggingHandler -HandlerList -- App1Handler -HandlerList -- App2Handler -App2Handler -- ServletHandler ----- -//// - -Server applications should rarely write custom ``Handler``s, preferring -instead to use existing ``Handler``s provided by the Jetty Server Libraries -for managing web application contexts, security, HTTP sessions and Servlet -support. -Refer to xref:eg-server-http-handler-use[this section] for more information about -how to use the ``Handler``s provided by the Jetty Server Libraries. - -However, in some cases the additional features are not required, or additional -constraints on memory footprint, or performance, or just simplicity must be met. +However, in some cases the additional features are not required, or additional constraints on memory footprint, or performance, or just simplicity must be met. In these cases, implementing your own `Handler` may be a better solution. -Refer to xref:eg-server-http-handler-implement[this section] for more information -about how to write your own ``Handler``s. +Refer to xref:eg-server-http-handler-implement[this section] for more information about how to write your own ``Handler``s. include::server-http-handler-use.adoc[] include::server-http-handler-implement.adoc[] diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http.adoc index cde36774086..32ff53d3aaa 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http.adoc @@ -19,8 +19,7 @@ [[eg-server-http]] === HTTP Server Libraries -The Eclipse Jetty Project has historically provided libraries to embed an HTTP -server and a Servlet Container. +The Eclipse Jetty Project has historically provided libraries to embed an HTTP server and a Servlet Container. The Maven artifact coordinates are: @@ -33,9 +32,7 @@ The Maven artifact coordinates are:
      ---- -An `org.eclipse.jetty.server.Server` instance is the central component that -links together a collection of ``Connector``s and a collection of -``Handler``s, with threads from a `ThreadPool` doing the work. +An `org.eclipse.jetty.server.Server` instance is the central component that links together a collection of ``Connector``s and a collection of ``Handler``s, with threads from a `ThreadPool` doing the work. [plantuml] ---- @@ -54,12 +51,9 @@ Connectors - Server Server -- Handlers ---- -The components that accept connections from clients are -`org.eclipse.jetty.server.Connector` implementations. +The components that accept connections from clients are `org.eclipse.jetty.server.Connector` implementations. -When a Jetty server interprets the HTTP protocol (both HTTP/1.1 and HTTP/2), -it uses `org.eclipse.jetty.server.Handler` instances to process incoming -requests and eventually produce responses. +When a Jetty server interprets the HTTP protocol (both HTTP/1.1 and HTTP/2), it uses `org.eclipse.jetty.server.Handler` instances to process incoming requests and eventually produce responses. A `Server` must be created, configured and started: @@ -68,41 +62,27 @@ A `Server` must be created, configured and started: include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=simple] ---- -The example above shows the simplest HTTP/1.1 server; it has no support for -HTTP sessions, for HTTP authentication, or for any of the features required -by the Servlet specification. +The example above shows the simplest HTTP/1.1 server; it has no support for HTTP sessions, for HTTP authentication, or for any of the features required by the Servlet specification. -All these features are provided by the Jetty Server Libraries and server -applications only need to put the required components together to provide -all the required features, and it is discussed in details in -xref:eg-server-http-handler-use[this section]. +All these features are provided by the Jetty Server Libraries and server applications only need to put the required components together to provide all the required features, and it is discussed in details in xref:eg-server-http-handler-use[this section]. [[eg-server-http-request-processing]] ==== Server Request Processing The Jetty HTTP request processing is outlined below in the diagram below. -Request handing is slightly different for each protocol; in HTTP/2 Jetty -takes into account multiplexing, something that is not present in HTTP/1.1. +Request handing is slightly different for each protocol; in HTTP/2 Jetty takes into account multiplexing, something that is not present in HTTP/1.1. -However, the diagram below captures the essence of request handling that -is common among all protocols that carry HTTP requests. +However, the diagram below captures the essence of request handling that is common among all protocols that carry HTTP requests. First, the Jetty I/O layer emits an event that a socket has data to read. -This event is converted to a call to `AbstractConnection.onFillable()`, -where the `Connection` first reads from the `EndPoint` into a `ByteBuffer`, -and then calls a protocol specific parser to parse the bytes in the -`ByteBuffer`. +This event is converted to a call to `AbstractConnection.onFillable()`, where the `Connection` first reads from the `EndPoint` into a `ByteBuffer`, and then calls a protocol specific parser to parse the bytes in the `ByteBuffer`. -The parser emit events such that are protocol specific; the HTTP/2 parser, -for example, emits events for each HTTP/2 frame that has been parsed. -The parser events are then converted to protocol independent events such -as _"request start"_, _"request headers"_, _"request content chunk"_, etc. +The parser emit events such that are protocol specific; the HTTP/2 parser, for example, emits events for each HTTP/2 frame that has been parsed. +The parser events are then converted to protocol independent events such as _"request start"_, _"request headers"_, _"request content chunk"_, etc. that in turn are converted into method calls to `HttpChannel`. -When enough of the HTTP request is arrived, the `Connection` calls -`HttpChannel.handle()` that calls the `Handler` chain, that eventually -calls the server application code. +When enough of the HTTP request is arrived, the `Connection` calls `HttpChannel.handle()` that calls the `Handler` chain, that eventually calls the server application code. [plantuml] ---- @@ -133,23 +113,15 @@ Server -> Handlers : handle() ===== HttpChannel Events The central component processing HTTP requests is `HttpChannel`. -There is a 1-to-1 relationship between an HTTP request/response and an -`HttpChannel`, no matter what is the specific protocol that carries the -HTTP request over the network (HTTP/1.1, HTTP/2 or FastCGI). +There is a 1-to-1 relationship between an HTTP request/response and an `HttpChannel`, no matter what is the specific protocol that carries the HTTP request over the network (HTTP/1.1, HTTP/2 or FastCGI). -Advanced server applications may be interested in the progress of the -processing of an HTTP request/response by `HttpChannel`. -A typical case is to know exactly _when_ the HTTP request/response -processing is complete, for example to monitor processing times. +Advanced server applications may be interested in the progress of the processing of an HTTP request/response by `HttpChannel`. +A typical case is to know exactly _when_ the HTTP request/response processing is complete, for example to monitor processing times. -NOTE: A `Handler` or a Servlet `Filter` may not report precisely when -an HTTP request/response processing is finished. -A server application may write a small enough content that is aggregated -by Jetty for efficiency reasons; the write returns immediately, but -nothing has been written to the network yet. +NOTE: A `Handler` or a Servlet `Filter` may not report precisely when an HTTP request/response processing is finished. +A server application may write a small enough content that is aggregated by Jetty for efficiency reasons; the write returns immediately, but nothing has been written to the network yet. -`HttpChannel` notifies ``HttpChannel.Listener``s of the progress of the -HTTP request/response handling. +`HttpChannel` notifies ``HttpChannel.Listener``s of the progress of the HTTP request/response handling. Currently, the following events are available: * `requestBegin` @@ -167,12 +139,9 @@ Currently, the following events are available: * `responseEnd` * `complete` -Please refer to the `HttpChannel.Listener` -link:{JDURL}/org/eclipse/jetty/server/HttpChannel.Listener.html[javadocs] -for the complete list of events. +Please refer to the `HttpChannel.Listener` link:{JDURL}/org/eclipse/jetty/server/HttpChannel.Listener.html[javadocs] for the complete list of events. -Server applications can register `HttpChannel.Listener` by adding them as -beans to the `Connector`: +Server applications can register `HttpChannel.Listener` by adding them as beans to the `Connector`: [source,java,indent=0] ---- diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/http2/server-http2.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/server/http2/server-http2.adoc index 470ac968793..e7977dbecf8 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/server/http2/server-http2.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/server/http2/server-http2.adoc @@ -19,14 +19,9 @@ [[eg-server-http2]] === HTTP/2 Server Library -In the vast majority of cases, server applications should use the generic, -high-level, xref:eg-server-http[HTTP server library] that also provides -HTTP/2 support via the HTTP/2 ``ConnectionFactory``s as described in details -xref:eg-server-http-connector-protocol-http2[here]. +In the vast majority of cases, server applications should use the generic, high-level, xref:eg-server-http[HTTP server library] that also provides HTTP/2 support via the HTTP/2 ``ConnectionFactory``s as described in details xref:eg-server-http-connector-protocol-http2[here]. -The low-level HTTP/2 server library has been designed for those applications -that need low-level access to HTTP/2 features such as _sessions_, _streams_ -and _frames_, and this is quite a rare use case. +The low-level HTTP/2 server library has been designed for those applications that need low-level access to HTTP/2 features such as _sessions_, _streams_ and _frames_, and this is quite a rare use case. See also the correspondent xref:eg-client-http2[HTTP/2 client library]. @@ -51,50 +46,34 @@ include::../../http2.adoc[tag=multiplex] include::../../http2.adoc[tag=flowControl] -How a server application should handle HTTP/2 flow control is discussed in -details in xref:eg-server-http2-request[this section]. +How a server application should handle HTTP/2 flow control is discussed in details in xref:eg-server-http2-request[this section]. [[eg-server-http2-setup]] ==== Server Setup -The low-level HTTP/2 support is provided by -`org.eclipse.jetty.http2.server.RawHTTP2ServerConnectionFactory` and -`org.eclipse.jetty.http2.api.server.ServerSessionListener`: +The low-level HTTP/2 support is provided by `org.eclipse.jetty.http2.server.RawHTTP2ServerConnectionFactory` and `org.eclipse.jetty.http2.api.server.ServerSessionListener`: [source,java,indent=0] ---- include::../../{doc_code}/embedded/server/http2/HTTP2ServerDocs.java[tags=setup] ---- -Where server applications using the -xref:eg-server-http[high-level server library] deal with HTTP -requests and responses in ``Handler``s, server applications using the -low-level HTTP/2 server library deal directly with HTTP/2 __session__s, -__stream__s and __frame__s in a `ServerSessionListener` implementation. +Where server applications using the xref:eg-server-http[high-level server library] deal with HTTP requests and responses in ``Handler``s, server applications using the low-level HTTP/2 server library deal directly with HTTP/2 __session__s, __stream__s and __frame__s in a `ServerSessionListener` implementation. -The `ServerSessionListener` interface defines a number of methods that are -invoked by the implementation upon the occurrence of HTTP/2 events, and that -server applications can override to react to those events. +The `ServerSessionListener` interface defines a number of methods that are invoked by the implementation upon the occurrence of HTTP/2 events, and that server applications can override to react to those events. -Please refer to the `ServerSessionListener` -link:{JDURL}/org/eclipse/jetty/http2/api/server/ServerSessionListener.html[javadocs] -for the complete list of events. +Please refer to the `ServerSessionListener`link:{JDURL}/org/eclipse/jetty/http2/api/server/ServerSessionListener.html[javadocs] for the complete list of events. -The first event is the _accept_ event and happens when a client opens a new -TCP connection to the server and the server accepts the connection. -This is the first occasion where server applications have access to the HTTP/2 -`Session` object: +The first event is the _accept_ event and happens when a client opens a new TCP connection to the server and the server accepts the connection. +This is the first occasion where server applications have access to the HTTP/2 `Session` object: [source,java,indent=0] ---- include::../../{doc_code}/embedded/server/http2/HTTP2ServerDocs.java[tags=accept] ---- -After connecting to the server, a compliant HTTP/2 client must send the -link:https://tools.ietf.org/html/rfc7540#section-3.5[HTTP/2 client preface], -and when the server receives it, it generates the _preface_ event on the server. -This is where server applications can customize the connection settings by -returning a map of settings that the implementation will send to the client: +After connecting to the server, a compliant HTTP/2 client must send the link:https://tools.ietf.org/html/rfc7540#section-3.5[HTTP/2 client preface], and when the server receives it, it generates the _preface_ event on the server. +This is where server applications can customize the connection settings by returning a map of settings that the implementation will send to the client: [source,java,indent=0] ---- @@ -104,13 +83,9 @@ include::../../{doc_code}/embedded/server/http2/HTTP2ServerDocs.java[tags=prefac [[eg-server-http2-request]] ==== Receiving a Request -Receiving an HTTP request from the client, and sending a response, creates -a _stream_ that encapsulates the exchange of HTTP/2 frames that compose the -request and the response. +Receiving an HTTP request from the client, and sending a response, creates a _stream_ that encapsulates the exchange of HTTP/2 frames that compose the request and the response. -An HTTP request is made of a `HEADERS` frame, that carries the request method, -the request URI and the request headers, and optional `DATA` frames that carry -the request content. +An HTTP request is made of a `HEADERS` frame, that carries the request method, the request URI and the request headers, and optional `DATA` frames that carry the request content. Receiving the `HEADERS` frame opens the `Stream`: @@ -119,11 +94,7 @@ Receiving the `HEADERS` frame opens the `Stream`: include::../../{doc_code}/embedded/server/http2/HTTP2ServerDocs.java[tags=request] ---- -Server applications should return a `Stream.Listener` implementation from -`onNewStream(...)` to be notified of events generated by the client, such as -`DATA` frames carrying request content, or a `RST_STREAM` frame indicating -that the client wants to _reset_ the request, or an idle timeout event -indicating that the client was supposed to send more frames but it did not. +Server applications should return a `Stream.Listener` implementation from `onNewStream(...)` to be notified of events generated by the client, such as `DATA` frames carrying request content, or a `RST_STREAM` frame indicating that the client wants to _reset_ the request, or an idle timeout event indicating that the client was supposed to send more frames but it did not. The example below shows how to receive request content: @@ -137,16 +108,11 @@ include::../../http2.adoc[tag=apiFlowControl] [[eg-server-http2-response]] ==== Sending a Response -After receiving an HTTP request, a server application must send an HTTP -response. +After receiving an HTTP request, a server application must send an HTTP response. -An HTTP response is typically composed of a `HEADERS` frame containing the -HTTP status code and the response headers, and optionally one or more `DATA` -frames containing the response content bytes. +An HTTP response is typically composed of a `HEADERS` frame containing the HTTP status code and the response headers, and optionally one or more `DATA` frames containing the response content bytes. -The HTTP/2 protocol also supports response trailers (that is, headers that -are sent after the response content) that also are sent using a `HEADERS` -frame. +The HTTP/2 protocol also supports response trailers (that is, headers that are sent after the response content) that also are sent using a `HEADERS` frame. A server application can send a response in this way: @@ -159,9 +125,7 @@ include::../../{doc_code}/embedded/server/http2/HTTP2ServerDocs.java[tags=respon ==== Resetting a Request A server application may decide that it does not want to accept the request. -For example, it may throttle the client because it sent too many requests in -a time window, or the request is invalid (and does not deserve a proper HTTP -response), etc. +For example, it may throttle the client because it sent too many requests in a time window, or the request is invalid (and does not deserve a proper HTTP response), etc. A request can be reset in this way: @@ -173,14 +137,10 @@ include::../../{doc_code}/embedded/server/http2/HTTP2ServerDocs.java[tags=reset; [[eg-server-http2-push]] ==== HTTP/2 Push of Resources -A server application may _push_ secondary resources related to a primary -resource. +A server application may _push_ secondary resources related to a primary resource. -A client may inform the server that it does not accept pushed resources -(see link:https://tools.ietf.org/html/rfc7540#section-8.2[this section] -of the specification) via a `SETTINGS` frame. -Server applications must track `SETTINGS` frames and verify whether the -client supports HTTP/2 push, and only push if the client supports it: +A client may inform the server that it does not accept pushed resources(see link:https://tools.ietf.org/html/rfc7540#section-8.2[this section] of the specification) via a `SETTINGS` frame. +Server applications must track `SETTINGS` frames and verify whether the client supports HTTP/2 push, and only push if the client supports it: [source,java,indent=0] ---- diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/server-io-arch.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/server/server-io-arch.adoc index 2efff583192..d4d6ef5b671 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/server/server-io-arch.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/server/server-io-arch.adoc @@ -19,11 +19,9 @@ [[eg-server-io-arch]] === Server Libraries I/O Architecture -The Jetty server libraries provide the basic components and APIs to implement -a network server. +The Jetty server libraries provide the basic components and APIs to implement a network server. -They build on the common xref:eg-io-arch[Jetty I/O Architecture] and provide server -specific concepts. +They build on the common xref:eg-io-arch[Jetty I/O Architecture] and provide server specific concepts. The main I/O server-side class is `org.eclipse.jetty.server.ServerConnector`. diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/server.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/server/server.adoc index 79feeefb8da..21e0c4e7cdd 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/server/server.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/server/server.adoc @@ -19,31 +19,19 @@ [[eg-server]] == Server Libraries -The Eclipse Jetty Project provides server-side libraries -that allow you to embed an HTTP or WebSocket server in your applications. +The Eclipse Jetty Project provides server-side libraries that allow you to embed an HTTP or WebSocket server in your applications. A typical example is a HTTP server that needs to expose a REST endpoint. -Another example is a proxy application that receives HTTP requests and -forwards them to third party services possibly using also the Jetty -xref:eg-client[client libraries]. +Another example is a proxy application that receives HTTP requests and forwards them to third party services possibly using also the Jetty xref:eg-client[client libraries]. -While historically Jetty is an HTTP server, it is possible to use the Jetty -server-side libraries to write a generic network server that interprets -any network protocol (not only HTTP). +While historically Jetty is an HTTP server, it is possible to use the Jetty server-side libraries to write a generic network server that interprets any network protocol (not only HTTP). -If you are interested in the low-level details of how the Eclipse Jetty -server libraries work, or are interested in writing a custom protocol, -look at the xref:eg-server-io-arch[Server I/O Architecture]. +If you are interested in the low-level details of how the Eclipse Jetty server libraries work, or are interested in writing a custom protocol, look at the xref:eg-server-io-arch[Server I/O Architecture]. The Jetty server-side libraries provide: -* HTTP support for HTTP/1.0, HTTP/1.1, HTTP/2, clear-text or encrypted, for -applications that want to embed Jetty as a generic HTTP server or proxy, -via the xref:eg-server-http[HTTP libraries] -* HTTP/2 low-level support, for applications that want to explicitly handle -low-level HTTP/2 _sessions_, _streams_ and _frames_, via the -xref:eg-server-http2[HTTP/2 libraries] -* WebSocket support, for applications that want to embed a WebSocket server, -via the xref:eg-server-websocket[WebSocket libraries] +* HTTP support for HTTP/1.0, HTTP/1.1, HTTP/2, clear-text or encrypted, for applications that want to embed Jetty as a generic HTTP server or proxy, via the xref:eg-server-http[HTTP libraries] +* HTTP/2 low-level support, for applications that want to explicitly handle low-level HTTP/2 _sessions_, _streams_ and _frames_, via the xref:eg-server-http2[HTTP/2 libraries] +* WebSocket support, for applications that want to embed a WebSocket server, via the xref:eg-server-websocket[WebSocket libraries] // TODO: add a section on lifecycle and the component tree. From 56f92adcc63dfd756e2a18d8c8213e139169da2b Mon Sep 17 00:00:00 2001 From: Olivier Lamy Date: Sat, 18 Apr 2020 09:59:04 +1000 Subject: [PATCH 090/101] Jetty 10.0.x graphviz container (#4783) * configure build using image jettyproject/jetty-build:latest Signed-off-by: olivier lamy --- Jenkinsfile | 78 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 44 insertions(+), 34 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 2f39121af28..2dfc916f2d1 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -8,45 +8,51 @@ pipeline { stage("Parallel Stage") { parallel { stage("Build / Test - JDK11") { - agent { node { label 'linux' } } + agent { + node { label 'linux' } + } options { timeout(time: 120, unit: 'MINUTES') } steps { - mavenBuild("jdk11", "-T3 -Pmongodb clean install", "maven3", true) // -Pautobahn - // Collect up the jacoco execution results (only on main build) - jacoco inclusionPattern: '**/org/eclipse/jetty/**/*.class', - exclusionPattern: '' + - // build tools - '**/org/eclipse/jetty/ant/**' + - ',**/org/eclipse/jetty/maven/**' + - ',**/org/eclipse/jetty/jspc/**' + - // example code / documentation - ',**/org/eclipse/jetty/embedded/**' + - ',**/org/eclipse/jetty/asyncrest/**' + - ',**/org/eclipse/jetty/demo/**' + - // special environments / late integrations - ',**/org/eclipse/jetty/gcloud/**' + - ',**/org/eclipse/jetty/infinispan/**' + - ',**/org/eclipse/jetty/osgi/**' + - ',**/org/eclipse/jetty/spring/**' + - ',**/org/eclipse/jetty/http/spi/**' + - // test classes - ',**/org/eclipse/jetty/tests/**' + - ',**/org/eclipse/jetty/test/**', - execPattern: '**/target/jacoco.exec', - classPattern: '**/target/classes', - sourcePattern: '**/src/main/java' - warnings consoleParsers: [[parserName: 'Maven'], [parserName: 'Java']] - junit testResults: '**/target/surefire-reports/*.xml,**/target/invoker-reports/TEST*.xml,**/target/autobahntestsuite-reports/*.xml' + container('jetty-build') { + mavenBuild("jdk11", "-T3 -Pmongodb clean install", "maven3", true) // -Pautobahn + // Collect up the jacoco execution results (only on main build) + jacoco inclusionPattern: '**/org/eclipse/jetty/**/*.class', + exclusionPattern: '' + + // build tools + '**/org/eclipse/jetty/ant/**' + + ',**/org/eclipse/jetty/maven/**' + + ',**/org/eclipse/jetty/jspc/**' + + // example code / documentation + ',**/org/eclipse/jetty/embedded/**' + + ',**/org/eclipse/jetty/asyncrest/**' + + ',**/org/eclipse/jetty/demo/**' + + // special environments / late integrations + ',**/org/eclipse/jetty/gcloud/**' + + ',**/org/eclipse/jetty/infinispan/**' + + ',**/org/eclipse/jetty/osgi/**' + + ',**/org/eclipse/jetty/spring/**' + + ',**/org/eclipse/jetty/http/spi/**' + + // test classes + ',**/org/eclipse/jetty/tests/**' + + ',**/org/eclipse/jetty/test/**', + execPattern: '**/target/jacoco.exec', + classPattern: '**/target/classes', + sourcePattern: '**/src/main/java' + warnings consoleParsers: [[parserName: 'Maven'], [parserName: 'Java']] + junit testResults: '**/target/surefire-reports/*.xml,**/target/invoker-reports/TEST*.xml,**/target/autobahntestsuite-reports/*.xml' + } } } stage("Build / Test - JDK14") { agent { node { label 'linux' } } steps { - timeout(time: 120, unit: 'MINUTES') { - mavenBuild("jdk14", "-T3 -Pmongodb clean install", "maven3", true) - warnings consoleParsers: [[parserName: 'Maven'], [parserName: 'Java']] - junit testResults: '**/target/surefire-reports/*.xml,**/target/invoker-reports/TEST*.xml' + container('jetty-build') { + timeout( time: 120, unit: 'MINUTES' ) { + mavenBuild( "jdk14", "-T3 -Pmongodb clean install", "maven3", true ) + warnings consoleParsers: [[parserName: 'Maven'], [parserName: 'Java']] + junit testResults: '**/target/surefire-reports/*.xml,**/target/invoker-reports/TEST*.xml' + } } } } @@ -54,9 +60,13 @@ pipeline { stage("Build Javadoc") { agent { node { label 'linux' } } steps { - timeout(time: 30, unit: 'MINUTES') { - mavenBuild("jdk11", "package source:jar javadoc:jar javadoc:aggregate-jar -Peclipse-release -DskipTests -Dpmd.skip=true -Dcheckstyle.skip=true", "maven3", true) - warnings consoleParsers: [[parserName: 'Maven'], [parserName: 'JavaDoc'], [parserName: 'Java']] + container('jetty-build') { + timeout( time: 30, unit: 'MINUTES' ) { + mavenBuild( "jdk11", + "package source:jar javadoc:jar javadoc:aggregate-jar -Peclipse-release -DskipTests -Dpmd.skip=true -Dcheckstyle.skip=true", + "maven3", true ) + warnings consoleParsers: [[parserName: 'Maven'], [parserName: 'JavaDoc'], [parserName: 'Java']] + } } } } From 15f7e0671bdbb01a7d9c6716b41f9c60e5ab3f9f Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Sat, 18 Apr 2020 16:21:36 +0200 Subject: [PATCH 091/101] Issue #4443 - Track backport of ALPN APIs to Java 8. Removed Java 8 profiles, not needed anymore. Jetty 9.4.x requires Java 9+ to be built, in order to build Java 9 specific modules. The ALPN boot jar used in tests was either replaced by the ALPN agent or the tests only run in Java 9+. Signed-off-by: Simone Bordet --- pom.xml | 552 -------------------------------------------------------- 1 file changed, 552 deletions(-) diff --git a/pom.xml b/pom.xml index 87af7001236..85c3d29e715 100644 --- a/pom.xml +++ b/pom.xml @@ -1494,558 +1494,6 @@
      - - 8u00 - - - java.version - 1.8.0 - - - - 8.1.0.v20141016 - - - - 8u05 - - - java.version - 1.8.0_05 - - - - 8.1.0.v20141016 - - - - 8u11 - - - java.version - 1.8.0_11 - - - - 8.1.0.v20141016 - - - - 8u20 - - - java.version - 1.8.0_20 - - - - 8.1.0.v20141016 - - - - 8u25 - - - java.version - 1.8.0_25 - - - - 8.1.2.v20141202 - - - - 8u31 - - - java.version - 1.8.0_31 - - - - 8.1.3.v20150130 - - - - 8u40 - - - java.version - 1.8.0_40 - - - - 8.1.3.v20150130 - - - - 8u45 - - - java.version - 1.8.0_45 - - - - 8.1.3.v20150130 - - - - 8u51 - - - java.version - 1.8.0_51 - - - - 8.1.4.v20150727 - - - - 8u60 - - - java.version - 1.8.0_60 - - - - 8.1.5.v20150921 - - - - 8u65 - - - java.version - 1.8.0_65 - - - - 8.1.6.v20151105 - - - - 8u66 - - - java.version - 1.8.0_66 - - - - 8.1.6.v20151105 - - - - 8u71 - - - java.version - 1.8.0_71 - - - - 8.1.7.v20160121 - - - - 8u72 - - - java.version - 1.8.0_72 - - - - 8.1.7.v20160121 - - - - 8u73 - - - java.version - 1.8.0_73 - - - - 8.1.7.v20160121 - - - - 8u74 - - - java.version - 1.8.0_74 - - - - 8.1.7.v20160121 - - - - 8u77 - - - java.version - 1.8.0_77 - - - - 8.1.7.v20160121 - - - - 8u91 - - - java.version - 1.8.0_91 - - - - 8.1.7.v20160121 - - - - 8u92 - - - java.version - 1.8.0_92 - - - - 8.1.8.v20160420 - - - - 8u101 - - - java.version - 1.8.0_101 - - - - 8.1.9.v20160720 - - - - 8u102 - - - java.version - 1.8.0_102 - - - - 8.1.9.v20160720 - - - - 8u111 - - - java.version - 1.8.0_111 - - - - 8.1.9.v20160720 - - - - 8u112 - - - java.version - 1.8.0_112 - - - - 8.1.10.v20161026 - - - - 8u121 - - - java.version - 1.8.0_121 - - - - 8.1.11.v20170118 - - - - 8u131 - - - java.version - 1.8.0_131 - - - - 8.1.11.v20170118 - - - - 8u141 - - - java.version - 1.8.0_141 - - - - 8.1.11.v20170118 - - - - 8u144 - - - java.version - 1.8.0_144 - - - - 8.1.11.v20170118 - - - - 8u151 - - - java.version - 1.8.0_151 - - - - 8.1.11.v20170118 - - - - 8u152 - - - java.version - 1.8.0_152 - - - - 8.1.11.v20170118 - - - - 8u161 - - - java.version - 1.8.0_161 - - - - 8.1.12.v20180117 - - - - 8u162 - - - java.version - 1.8.0_162 - - - - 8.1.12.v20180117 - - - - 8u171 - - - java.version - 1.8.0_171 - - - - 8.1.12.v20180117 - - - - 8u172 - - - java.version - 1.8.0_172 - - - - 8.1.12.v20180117 - - - - 8u181 - - - java.version - 1.8.0_181 - - - - 8.1.12.v20180117 - - - - 8u191 - - - java.version - 1.8.0_191 - - - - 8.1.13.v20181017 - - - - 8u192 - - - java.version - 1.8.0_192 - - - - 8.1.13.v20181017 - - - - 8u201 - - - java.version - 1.8.0_201 - - - - 8.1.13.v20181017 - - - - 8u202 - - - java.version - 1.8.0_202 - - - - 8.1.13.v20181017 - - - - 8u211 - - - java.version - 1.8.0_211 - - - - 8.1.13.v20181017 - - - - 8u212 - - - java.version - 1.8.0_212 - - - - 8.1.13.v20181017 - - - - 8u221 - - - java.version - 1.8.0_221 - - - - 8.1.13.v20181017 - - - - 8u222 - - - java.version - 1.8.0_222 - - - - 8.1.13.v20181017 - - - - 8u231 - - - java.version - 1.8.0_231 - - - - 8.1.13.v20181017 - - - - 8u232 - - - java.version - 1.8.0_232 - - - - 8.1.13.v20181017 - - - - 8u241 - - - java.version - 1.8.0_241 - - - - 8.1.13.v20181017 - - - - 8u242 - - - java.version - 1.8.0_242 - - - - 8.1.13.v20181017 - - jdk9 From fe7f71fda1d7bb881b396e0b1a63d170a853c481 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Sun, 19 Apr 2020 16:47:49 +0200 Subject: [PATCH 092/101] Excluding the "external" tests to make builds in Jenkins more reliable. Signed-off-by: Simone Bordet --- pom.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pom.xml b/pom.xml index 85c3d29e715..c01c5e4a462 100644 --- a/pom.xml +++ b/pom.xml @@ -1388,6 +1388,9 @@ org.apache.maven.plugins maven-surefire-plugin ${maven.surefire.version} + + external + From 74b6694075880d0e74676edbbc026946135420bc Mon Sep 17 00:00:00 2001 From: Jan Bartel Date: Tue, 21 Apr 2020 15:24:40 +0200 Subject: [PATCH 093/101] Jetty 10.0.x 4794 http input set read listener (#4796) * Issue #4794 HttpInput.setReadListener should check async state Signed-off-by: Jan Bartel --- .../org/eclipse/jetty/server/HttpInput.java | 4 + .../eclipse/jetty/server/HttpInputTest.java | 188 +++++++++++++----- 2 files changed, 139 insertions(+), 53 deletions(-) diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java index 4d5e142b15b..9486f1d9fbd 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java @@ -732,6 +732,10 @@ public class HttpInput extends ServletInputStream implements Runnable _listener = Objects.requireNonNull(readListener); + //illegal if async not started + if (!_channelState.isAsync()) + throw new IllegalStateException("Async not started"); + if (isError()) { woken = _channelState.onReadReady(); diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpInputTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpInputTest.java index 5802a952697..bc6969de6a7 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpInputTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpInputTest.java @@ -88,11 +88,96 @@ public class HttpInputTest super.failed(x); } } + + public class TestHttpInput extends HttpInput + { + public TestHttpInput(HttpChannelState state) + { + super(state); + } + + @Override + protected void produceContent() throws IOException + { + _history.add("produceContent " + _fillAndParseSimulate.size()); + + for (String s = _fillAndParseSimulate.poll(); s != null; s = _fillAndParseSimulate.poll()) + { + if ("_EOF_".equals(s)) + _in.eof(); + else + _in.addContent(new TContent(s)); + } + } + + @Override + protected void blockForContent() throws IOException + { + _history.add("blockForContent"); + super.blockForContent(); + } + } + + public class TestHttpChannelState extends HttpChannelState + { + private boolean _fakeAsyncState; + + public TestHttpChannelState(HttpChannel channel) + { + super(channel); + } + + public boolean isFakeAsyncState() + { + return _fakeAsyncState; + } + + public void setFakeAsyncState(boolean fakeAsyncState) + { + _fakeAsyncState = fakeAsyncState; + } + + @Override + public boolean isAsync() + { + if (isFakeAsyncState()) + return true; + return super.isAsync(); + } + + @Override + public void onReadUnready() + { + _history.add("s.onReadUnready"); + super.onReadUnready(); + } + + @Override + public boolean onReadPossible() + { + _history.add("s.onReadPossible"); + return super.onReadPossible(); + } + + @Override + public boolean onContentAdded() + { + _history.add("s.onDataAvailable"); + return super.onContentAdded(); + } + + @Override + public boolean onReadReady() + { + _history.add("s.onReadReady"); + return super.onReadReady(); + } + } @BeforeEach public void before() { - _in = new HttpInput(new HttpChannelState(new HttpChannel(new MockConnector(), new HttpConfiguration(), null, null) + _in = new TestHttpInput(new TestHttpChannelState(new HttpChannel(new MockConnector(), new HttpConfiguration(), null, null) { @Override public void onAsyncWaitForContent() @@ -100,57 +185,7 @@ public class HttpInputTest _history.add("asyncReadInterested"); } }) - { - @Override - public void onReadUnready() - { - _history.add("s.onReadUnready"); - super.onReadUnready(); - } - - @Override - public boolean onReadPossible() - { - _history.add("s.onReadPossible"); - return super.onReadPossible(); - } - - @Override - public boolean onContentAdded() - { - _history.add("s.onDataAvailable"); - return super.onContentAdded(); - } - - @Override - public boolean onReadReady() - { - _history.add("s.onReadReady"); - return super.onReadReady(); - } - }) - { - @Override - protected void produceContent() throws IOException - { - _history.add("produceContent " + _fillAndParseSimulate.size()); - - for (String s = _fillAndParseSimulate.poll(); s != null; s = _fillAndParseSimulate.poll()) - { - if ("_EOF_".equals(s)) - _in.eof(); - else - _in.addContent(new TContent(s)); - } - } - - @Override - protected void blockForContent() throws IOException - { - _history.add("blockForContent"); - super.blockForContent(); - } - }; + ); } @AfterEach @@ -326,7 +361,9 @@ public class HttpInputTest @Test public void testAsyncEmpty() throws Exception { + ((TestHttpChannelState)_in.getHttpChannelState()).setFakeAsyncState(true); _in.setReadListener(_listener); + ((TestHttpChannelState)_in.getHttpChannelState()).setFakeAsyncState(false); assertThat(_history.poll(), equalTo("produceContent 0")); assertThat(_history.poll(), equalTo("s.onReadUnready")); assertThat(_history.poll(), nullValue()); @@ -341,7 +378,10 @@ public class HttpInputTest @Test public void testAsyncRead() throws Exception { + ((TestHttpChannelState)_in.getHttpChannelState()).setFakeAsyncState(true); _in.setReadListener(_listener); + ((TestHttpChannelState)_in.getHttpChannelState()).setFakeAsyncState(false); + assertThat(_history.poll(), equalTo("produceContent 0")); assertThat(_history.poll(), equalTo("s.onReadUnready")); assertThat(_history.poll(), nullValue()); @@ -388,7 +428,9 @@ public class HttpInputTest @Test public void testAsyncEOF() throws Exception { + ((TestHttpChannelState)_in.getHttpChannelState()).setFakeAsyncState(true); _in.setReadListener(_listener); + ((TestHttpChannelState)_in.getHttpChannelState()).setFakeAsyncState(false); assertThat(_history.poll(), equalTo("produceContent 0")); assertThat(_history.poll(), equalTo("s.onReadUnready")); assertThat(_history.poll(), nullValue()); @@ -406,8 +448,10 @@ public class HttpInputTest @Test public void testAsyncReadEOF() throws Exception - { + { + ((TestHttpChannelState)_in.getHttpChannelState()).setFakeAsyncState(true); _in.setReadListener(_listener); + ((TestHttpChannelState)_in.getHttpChannelState()).setFakeAsyncState(false); assertThat(_history.poll(), equalTo("produceContent 0")); assertThat(_history.poll(), equalTo("s.onReadUnready")); assertThat(_history.poll(), nullValue()); @@ -452,7 +496,9 @@ public class HttpInputTest @Test public void testAsyncError() throws Exception { + ((TestHttpChannelState)_in.getHttpChannelState()).setFakeAsyncState(true); _in.setReadListener(_listener); + ((TestHttpChannelState)_in.getHttpChannelState()).setFakeAsyncState(false); assertThat(_history.poll(), equalTo("produceContent 0")); assertThat(_history.poll(), equalTo("s.onReadUnready")); assertThat(_history.poll(), nullValue()); @@ -477,6 +523,42 @@ public class HttpInputTest assertThat(_history.poll(), nullValue()); } + + @Test + public void testSetListenerWithNull() throws Exception + { + //test can't be null + assertThrows(NullPointerException.class, () -> + { + _in.setReadListener(null); + }); + } + + @Test + public void testSetListenerNotAsync() throws Exception + { + //test not async + assertThrows(IllegalStateException.class, () -> + { + _in.setReadListener(_listener); + }); + } + + @Test + public void testSetListenerAlreadySet() throws Exception + { + //set up a listener + ((TestHttpChannelState)_in.getHttpChannelState()).setFakeAsyncState(true); + _in.setReadListener(_listener); + //throw away any events generated by setting the listener + _history.clear(); + ((TestHttpChannelState)_in.getHttpChannelState()).setFakeAsyncState(false); + //now test that you can't set another listener + assertThrows(IllegalStateException.class, () -> + { + _in.setReadListener(_listener); + }); + } @Test public void testRecycle() throws Exception From 8516240c03ac6e2ff752236966afb20a394961bb Mon Sep 17 00:00:00 2001 From: Jan Bartel Date: Tue, 21 Apr 2020 16:51:58 +0200 Subject: [PATCH 094/101] Tidy up osgi test code, add more to README.txt Signed-off-by: Jan Bartel --- jetty-osgi/test-jetty-osgi/README.txt | 20 ++- .../TestJettyOSGiBootContextAsService.java | 2 +- .../test/TestJettyOSGiBootHTTP2Conscrypt.java | 17 +- .../osgi/test/TestJettyOSGiBootHTTP2JDK9.java | 22 ++- .../TestJettyOSGiBootWebAppAsService.java | 8 +- .../TestJettyOSGiBootWithAnnotations.java | 8 +- .../test/TestJettyOSGiBootWithBundle.java | 11 +- .../TestJettyOSGiBootWithJavaxWebSocket.java | 8 +- .../osgi/test/TestJettyOSGiBootWithJsp.java | 11 +- .../test/TestJettyOSGiBootWithWebSocket.java | 7 +- .../eclipse/jetty/osgi/test/TestOSGiUtil.java | 151 ++++-------------- 11 files changed, 78 insertions(+), 187 deletions(-) diff --git a/jetty-osgi/test-jetty-osgi/README.txt b/jetty-osgi/test-jetty-osgi/README.txt index 30e029fc6ce..9811ae5018a 100644 --- a/jetty-osgi/test-jetty-osgi/README.txt +++ b/jetty-osgi/test-jetty-osgi/README.txt @@ -124,11 +124,15 @@ INSTALLED 2 60 org.eclipse.jetty.tests.webapp file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.tests.webapp_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 32 61 PAXEXAM-PROBE-d9c5a341-5c98-4084-b814-8303880cb447 local 0.0.0 32 -If one of them isn't active (32) and you think it should be, you can edit the src of the test and call a method to generate more information next time you run the test: -TestOSGiUtil.getBundle(BundleContext, String) +If things didn't go so well, and some bundle that should be in state ACTIVE (32) isn't, then you'll see diagnosis like this: -Where BundleContext is the field called bundleContext in the unit test class, and the String is the symbolic name of the jar. For example, for jetty-util, the symbolic name is org.eclipse.jetty.util. You can find it on the list above. You can also look into the pom.xml for the relevant jetty module and find it. If it's a 3rd party jar, you'll have to look in the META-INF/MANIFEST.MF. +Trying to start the bundle org.glassfish.web.javax.servlet.jsp.jstl that was supposed to be active or resolved. +org.glassfish.web.javax.servlet.jsp.jstl failed to start +org.osgi.framework.BundleException: Could not resolve module: org.glassfish.web.javax.servlet.jsp.jstl [57] + Unresolved requirement: Import-Package: javax.servlet.jsp.jstl.core + +The "Unresolved requirement" means either that some bundle that exports that package has not been deployed, or that it is deployed, but it's manifest is screwed up, and didn't expose that package. Check the test code for the mavenBundle() statements to ascertain if it has been deployed, and then check the manifest inside the jar for the Export-Package statements to verify the correct packages are exported. @@ -204,3 +208,13 @@ at org.eclipse.osgi.internal.serviceregistry.ServiceRegistry.notifyHooksPrivileg at org.eclipse.osgi.internal.weaving.WovenClassImpl.callHooks(WovenClassImpl.java:270) at org.eclipse.osgi.internal.weaving.WeavingHookConfigurator.processClass(WeavingHookConfigurator.java:71) ... 35 more + + + + +Other Useful Things +------------------- + +If you have an ordinary jar with no osgi manifest headers in it, or one with incorrect/incomplete osgi manifest headers in it, you can use the Pax Wrapped Bundle facility to add in/replace the headers so that the test will work. + +See https://ops4j1.jira.com/wiki/spaces/PAXEXAM4/pages/54263890/Configuration+Options#ConfigurationOptions-wrappedBundle for more information. The wrappedBundle() itself uses an underlying Wrap Protocol mechanism, which has more details on configuration options, so also look at https://ops4j1.jira.com/wiki/spaces/paxurl/pages/3833898/Wrap+Protocol. For an example of it in use in the tests, look at TestOSGiUtil.java line 179. diff --git a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootContextAsService.java b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootContextAsService.java index 2e9f7393b82..4f04a0dd69d 100644 --- a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootContextAsService.java +++ b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootContextAsService.java @@ -80,7 +80,7 @@ public class TestJettyOSGiBootContextAsService public void testContextHandlerAsOSGiService() throws Exception { if (Boolean.getBoolean(TestOSGiUtil.BUNDLE_DEBUG)) - TestOSGiUtil.assertAllBundlesActiveOrResolved(bundleContext); + TestOSGiUtil.diagnoseBundles(bundleContext); // now test the context HttpClient client = new HttpClient(); diff --git a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootHTTP2Conscrypt.java b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootHTTP2Conscrypt.java index 850d1507b1f..3139fdd0231 100644 --- a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootHTTP2Conscrypt.java +++ b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootHTTP2Conscrypt.java @@ -111,20 +111,21 @@ public class TestJettyOSGiBootHTTP2Conscrypt public void assertAllBundlesActiveOrResolved() { - TestOSGiUtil.debugBundles(bundleContext); - Bundle conscrypt = TestOSGiUtil.getBundle(bundleContext, "org.eclipse.jetty.alpn.conscrypt.server"); - TestOSGiUtil.diagnoseNonActiveOrNonResolvedBundle(conscrypt); - assertNotNull(conscrypt); - ServiceReference[] services = conscrypt.getRegisteredServices(); - assertNotNull(services); - assertTrue(services.length > 0); + } @Test public void testHTTP2() throws Exception { if (Boolean.getBoolean(TestOSGiUtil.BUNDLE_DEBUG)) - assertAllBundlesActiveOrResolved(); + { + TestOSGiUtil.diagnoseBundles(bundleContext); + Bundle conscrypt = TestOSGiUtil.getBundle(bundleContext, "org.eclipse.jetty.alpn.conscrypt.server"); + assertNotNull(conscrypt); + ServiceReference[] services = conscrypt.getRegisteredServices(); + assertNotNull(services); + assertTrue(services.length > 0); + } HTTP2Client client = new HTTP2Client(); try diff --git a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootHTTP2JDK9.java b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootHTTP2JDK9.java index f9af861bff2..d46e20d6011 100644 --- a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootHTTP2JDK9.java +++ b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootHTTP2JDK9.java @@ -100,23 +100,19 @@ public class TestJettyOSGiBootHTTP2JDK9 return res; } - public void assertAllBundlesActiveOrResolved() - { - TestOSGiUtil.debugBundles(bundleContext); - TestOSGiUtil.assertAllBundlesActiveOrResolved(bundleContext); - Bundle javaAlpn = TestOSGiUtil.getBundle(bundleContext, "org.eclipse.jetty.alpn.java.server"); - assertNotNull(javaAlpn); - ServiceReference[] services = javaAlpn.getRegisteredServices(); - assertNotNull(services); - Bundle server = TestOSGiUtil.getBundle(bundleContext, "org.eclipse.jetty.alpn.server"); - assertNotNull(server); - } - @Test public void testHTTP2() throws Exception { if (Boolean.getBoolean(TestOSGiUtil.BUNDLE_DEBUG)) - assertAllBundlesActiveOrResolved(); + { + TestOSGiUtil.diagnoseBundles(bundleContext); + Bundle javaAlpn = TestOSGiUtil.getBundle(bundleContext, "org.eclipse.jetty.alpn.java.server"); + assertNotNull(javaAlpn); + ServiceReference[] services = javaAlpn.getRegisteredServices(); + assertNotNull(services); + Bundle server = TestOSGiUtil.getBundle(bundleContext, "org.eclipse.jetty.alpn.server"); + assertNotNull(server); + } HttpClient httpClient = null; HTTP2Client http2Client = null; diff --git a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWebAppAsService.java b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWebAppAsService.java index 94843e83d0b..94777b8a4b0 100644 --- a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWebAppAsService.java +++ b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWebAppAsService.java @@ -94,17 +94,11 @@ public class TestJettyOSGiBootWebAppAsService return res; } - public void assertAllBundlesActiveOrResolved() - { - TestOSGiUtil.debugBundles(bundleContext); - TestOSGiUtil.assertAllBundlesActiveOrResolved(bundleContext); - } - @Test public void testBundle() throws Exception { if (Boolean.getBoolean(TestOSGiUtil.BUNDLE_DEBUG)) - assertAllBundlesActiveOrResolved(); + TestOSGiUtil.diagnoseBundles(bundleContext); ServiceReference[] refs = bundleContext.getServiceReferences(WebAppContext.class.getName(), null); assertNotNull(refs); diff --git a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWithAnnotations.java b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWithAnnotations.java index 7cef2e897d1..73ce1ddd1b8 100644 --- a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWithAnnotations.java +++ b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWithAnnotations.java @@ -93,17 +93,11 @@ public class TestJettyOSGiBootWithAnnotations return res; } - public void assertAllBundlesActiveOrResolved() - { - TestOSGiUtil.debugBundles(bundleContext); - TestOSGiUtil.assertAllBundlesActiveOrResolved(bundleContext); - } - @Test public void testIndex() throws Exception { if (Boolean.getBoolean(TestOSGiUtil.BUNDLE_DEBUG)) - assertAllBundlesActiveOrResolved(); + TestOSGiUtil.diagnoseBundles(bundleContext); HttpClient client = new HttpClient(); try diff --git a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWithBundle.java b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWithBundle.java index 95626805127..6cb76380732 100644 --- a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWithBundle.java +++ b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWithBundle.java @@ -29,7 +29,6 @@ import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.osgi.boot.OSGiServerConstants; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.ops4j.pax.exam.Configuration; @@ -95,16 +94,12 @@ public class TestJettyOSGiBootWithBundle return options; } - @Test - public void assertAllBundlesActiveOrResolved() - { - TestOSGiUtil.assertAllBundlesActiveOrResolved(bundleContext); - } - - @Ignore @Test public void testContextHandlerAsOSGiService() throws Exception { + if (Boolean.getBoolean(TestOSGiUtil.BUNDLE_DEBUG)) + TestOSGiUtil.diagnoseBundles(bundleContext); + // now test the context HttpClient client = new HttpClient(); try diff --git a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWithJavaxWebSocket.java b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWithJavaxWebSocket.java index a5de33403c3..9ce67297315 100644 --- a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWithJavaxWebSocket.java +++ b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWithJavaxWebSocket.java @@ -96,12 +96,6 @@ public class TestJettyOSGiBootWithJavaxWebSocket return res; } - public void assertAllBundlesActiveOrResolved() - { - TestOSGiUtil.assertAllBundlesActiveOrResolved(bundleContext); - TestOSGiUtil.debugBundles(bundleContext); - } - @Test public void testWebsocket() throws Exception { @@ -111,7 +105,7 @@ public class TestJettyOSGiBootWithJavaxWebSocket startBundle(bundleContext, "org.eclipse.jetty.tests.webapp"); if (Boolean.getBoolean(TestOSGiUtil.BUNDLE_DEBUG)) - assertAllBundlesActiveOrResolved(); + TestOSGiUtil.diagnoseBundles(bundleContext); String port = System.getProperty("boot.javax.websocket.port"); assertNotNull(port); diff --git a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWithJsp.java b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWithJsp.java index 46a1099c7f4..602560a4e89 100644 --- a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWithJsp.java +++ b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWithJsp.java @@ -20,6 +20,7 @@ package org.eclipse.jetty.osgi.test; import java.util.ArrayList; import java.util.List; + import javax.inject.Inject; import org.eclipse.jetty.client.HttpClient; @@ -37,7 +38,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.ops4j.pax.exam.CoreOptions.mavenBundle; -import static org.ops4j.pax.exam.CoreOptions.systemProperty; /** * Pax-Exam to make sure the jetty-osgi-boot can be started along with the @@ -63,7 +63,6 @@ public class TestJettyOSGiBootWithJsp options.add(CoreOptions.systemPackages("com.sun.org.apache.xalan.internal.res", "com.sun.org.apache.xml.internal.utils", "com.sun.org.apache.xml.internal.utils", "com.sun.org.apache.xpath.internal", "com.sun.org.apache.xpath.internal.jaxp", "com.sun.org.apache.xpath.internal.objects")); - options.addAll(TestOSGiUtil.coreJettyDependencies()); options.add(mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-alpn-java-client").versionAsInProject().start()); options.add(mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-alpn-client").versionAsInProject().start()); @@ -80,17 +79,11 @@ public class TestJettyOSGiBootWithJsp return res; } - public void assertAllBundlesActiveOrResolved() - { - TestOSGiUtil.debugBundles(bundleContext); - TestOSGiUtil.assertAllBundlesActiveOrResolved(bundleContext); - } - @Test public void testJspDump() throws Exception { if (Boolean.getBoolean(TestOSGiUtil.BUNDLE_DEBUG)) - assertAllBundlesActiveOrResolved(); + TestOSGiUtil.diagnoseBundles(bundleContext); HttpClient client = new HttpClient(); try diff --git a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWithWebSocket.java b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWithWebSocket.java index 3289fee817e..46aabf84930 100644 --- a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWithWebSocket.java +++ b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWithWebSocket.java @@ -83,17 +83,16 @@ public class TestJettyOSGiBootWithWebSocket return res; } - public void assertAllBundlesActiveOrResolved() + public void debugBundles() { - TestOSGiUtil.assertAllBundlesActiveOrResolved(bundleContext); - TestOSGiUtil.debugBundles(bundleContext); + } @Test public void testWebsocket() throws Exception { if (Boolean.getBoolean(TestOSGiUtil.BUNDLE_DEBUG)) - assertAllBundlesActiveOrResolved(); + TestOSGiUtil.diagnoseBundles(bundleContext); String port = System.getProperty("boot.websocket.port"); assertNotNull(port); diff --git a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestOSGiUtil.java b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestOSGiUtil.java index 15b7729d2b8..caebf813d7d 100644 --- a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestOSGiUtil.java +++ b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestOSGiUtil.java @@ -211,14 +211,6 @@ public class TestOSGiUtil return res; } - public static List + + 1.1.7 + @@ -1496,6 +1498,15 @@ + + + cbi-releases + https://repo.eclipse.org/content/repositories/cbi-releases/ + + false + + + jdk9 @@ -1550,21 +1561,6 @@ - - cbi-repository - - true - - - - cbi-releases - https://repo.eclipse.org/content/repositories/cbi-releases/ - - false - - - - From 13094fa912a4660a921cc8538ca3ed880b1d30ed Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Mon, 20 Apr 2020 15:47:56 +0200 Subject: [PATCH 099/101] Improvements to the Jetty server documentation. Added section on the components architecture. Signed-off-by: Simone Bordet --- .../asciidoc/embedded-guide/arch-bean.adoc | 101 +++++++++++++ .../{io-arch.adoc => arch-io.adoc} | 30 ++-- .../embedded-guide/arch-listener.adoc | 33 ++++ .../main/asciidoc/embedded-guide/arch.adoc | 25 +++ .../embedded-guide/client/client-io-arch.adoc | 6 +- .../main/asciidoc/embedded-guide/index.adoc | 2 +- .../server/http/server-http-connector.adoc | 6 +- .../embedded-guide/server/server-io-arch.adoc | 2 +- .../embedded-guide/server/server.adoc | 2 - .../src/main/java/embedded/ComponentDocs.java | 143 ++++++++++++++++++ 10 files changed, 326 insertions(+), 24 deletions(-) create mode 100644 jetty-documentation/src/main/asciidoc/embedded-guide/arch-bean.adoc rename jetty-documentation/src/main/asciidoc/embedded-guide/{io-arch.adoc => arch-io.adoc} (95%) create mode 100644 jetty-documentation/src/main/asciidoc/embedded-guide/arch-listener.adoc create mode 100644 jetty-documentation/src/main/asciidoc/embedded-guide/arch.adoc create mode 100644 jetty-documentation/src/main/java/embedded/ComponentDocs.java diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/arch-bean.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/arch-bean.adoc new file mode 100644 index 00000000000..0d48fc0e776 --- /dev/null +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/arch-bean.adoc @@ -0,0 +1,101 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +[[eg-arch-bean]] +=== Jetty Component Architecture + +Applications that use the Jetty libraries (both client and server) create objects from Jetty classes and compose them together to obtain the desired functionalities. + +A client application creates a `ClientConnector` instance, a `HttpClientTransport` instance and an `HttpClient` instance and compose them to have a working HTTP client that uses to call third party services. + +A server application creates a `ThreadPool` instance, a `Server` instance, a `ServerConnector` instance, a `Handler` instance and compose them together to expose an HTTP service. + +Internally, the Jetty libraries create even more instances of other components that also are composed together with the main ones created by applications. + +The end result is that an application based on the Jetty libraries is a _tree_ of components. +In server application the root of the component tree is a `Server` instance, while in client applications the root of the component tree is an `HttpClient` instance. + +Having all the Jetty components in a tree is beneficial in a number of use cases. +It makes possible to register the components in the tree as JMX MBeans (TODO: xref the JMX section) so that a JMX console can look at the internal state of the components. +It also makes possible to dump the component tree (and therefore each component's internal state) to a log file or to the console for troubleshooting purposes (TODO: xref troubleshooting section). + +[[eg-arch-bean-lifecycle]] +==== Jetty Component Lifecycle + +Jetty components typically have a life cycle: they can be started and stopped. +The Jetty components that have a life cycle implement the `org.eclipse.jetty.util.component.LifeCycle` interface. + +Jetty components that contain other components extend the `org.eclipse.jetty.util.component.ContainerLifeCycle` class. +`ContainerLifeCycle` can contain these type of components, also called __bean__s: + +* _managed_ beans, `LifeCycle` instances whose life cycle is tied to the life cycle of their container +* _unmanaged_ beans, `LifeCycle` instances whose life cycle is _not_ tied to the life cycle of their container +* _POJO_ (Plain Old Java Object) beans, instances that do not implement `LifeCycle` + +`ContainerLifeCycle` uses the following logic to determine if a bean should be _managed_, _unmanaged_ or _POJO_: + +* the bean implements `LifeCycle` +** the bean is not started, add it as _managed_ +** the bean is started, add it as _unmanaged_ +* the bean does not implement `LifeCycle`, add it as _POJO_ + +When a `ContainerLifeCycle` is started, it also starts recursively all its managed beans (if they implement `LifeCycle`); unmanaged beans are not started during the `ContainerLifeCycle` start cycle. +Likewise, stopping a `ContainerLifeCycle` stops recursively and in reverse order all its managed beans; unmanaged beans are not stopped during the `ContainerLifeCycle` stop cycle. + +Components can also be started and stopped individually, therefore activating or deactivating the functionalities that they offer. + +Applications should first compose components in the desired structure, and then start the root component: + +[source,java,indent=0] +---- +include::{doc_code}/embedded/ComponentDocs.java[tags=start] +---- + +The component tree is the following: + +[source,screen] +---- +Root +├── Monitor (MANAGED) +└── Service (MANAGED) + └── ScheduledExecutorService (POJO) +---- + +When the `Root` instance is created, also the `Monitor` instance is created and added as bean, so `Monitor` is the first bean of `Root`. +`Monitor` is a _managed_ bean, because it has been explicitly added to `Root` via `ContainerLifeCycle.addManaged(...)`. + +Then, the application creates a `Service` instance and adds it to `Root` via `ContainerLifeCycle.addBean(...)`, so `Service` is the second bean of `Root`. +`Service` is a _managed_ bean too, because it is a `LifeCycle` and at the time it was added to `Root` is was not started. + +The `ScheduledExecutorService` within `Service` does not implement `LifeCycle` so it is added as a _POJO_ to `Service`. + +It is possible to stop and re-start any component in a tree, for example: + +[source,java,indent=0] +---- +include::{doc_code}/embedded/ComponentDocs.java[tags=restart] +---- + +`Service` can be stopped independently of `Root`, and re-started. +Starting and stopping a non-root component does not alter the structure of the component tree, just the state of the subtree starting from the component that has been stopped and re-started. + +[[eg-arch-bean-listener]] +==== Jetty Component Listeners + +// TODO: LifeCycle.Listener +// TODO: Container.Listener + InheritedListener diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/io-arch.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/arch-io.adoc similarity index 95% rename from jetty-documentation/src/main/asciidoc/embedded-guide/io-arch.adoc rename to jetty-documentation/src/main/asciidoc/embedded-guide/arch-io.adoc index 10af0da8104..f5a342afbab 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/io-arch.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/arch-io.adoc @@ -16,14 +16,13 @@ // ======================================================================== // -[appendix] -[[eg-io-arch]] -== Jetty I/O Architecture +[[eg-arch-io]] +=== Jetty I/O Architecture Jetty libraries (both client and server) use Java NIO to handle I/O, so that at its core Jetty I/O is completely non-blocking. -[[eg-io-arch-selector-manager]] -=== Jetty I/O: `SelectorManager` +[[eg-arch-io-selector-manager]] +==== Jetty I/O: `SelectorManager` The core class of Jetty I/O is link:{JDURL}/org/eclipse/jetty/io/SelectorManager.html[`SelectorManager`]. @@ -51,8 +50,8 @@ This example shows how a server accepts a client connection: include::{doc_code}/embedded/SelectorManagerDocs.java[tags=accept] ---- -[[eg-io-arch-endpoint-connection]] -=== Jetty I/O: `EndPoint` and `Connection` +[[eg-arch-io-endpoint-connection]] +==== Jetty I/O: `EndPoint` and `Connection` ``SocketChannel``s that are passed to `SelectorManager` are wrapped into two related components: an link:{JDURL}/org/eclipse/jetty/io/EndPoint.html[`EndPoint`] and a link:{JDURL}/org/eclipse/jetty/io/Connection.html[`Connection`]. @@ -85,8 +84,8 @@ On the server-side, the component that aggregates a `SelectorManager` with a set On the client-side, the components that aggregates a `SelectorManager` with a set of ``ClientConnectionFactory``s are link:{JDURL}/org/eclipse/jetty/client/HttpClientTransport.html[`HttpClientTransport`] subclasses, see xref:eg-client-io-arch[]. -[[eg-io-arch-endpoint]] -=== Jetty I/O: `EndPoint` +[[eg-arch-io-endpoint]] +==== Jetty I/O: `EndPoint` The Jetty I/O library use Java NIO to handle I/O, so that I/O is non-blocking. @@ -100,10 +99,10 @@ In order to be notified when a `SocketChannel` uncongests and it is therefore wr In the Jetty I/O library, you can call `EndPoint.write(Callback, ByteBuffer...)` to write the ``ByteBuffer``s and the `Callback` parameter is the object that is notified when the whole write is finished (i.e. _all_ ``ByteBuffer``s have been fully written, even if they are delayed by TCP congestion/uncongestion). The `EndPoint` APIs abstract out the Java NIO details by providing non-blocking APIs based on `Callback` objects for I/O operations. -The `EndPoint` APIs are typically called by `Connection` implementations, see xref:eg-io-arch-connection[this section]. +The `EndPoint` APIs are typically called by `Connection` implementations, see xref:eg-arch-io-connection[this section]. -[[eg-io-arch-connection]] -=== Jetty I/O: `Connection` +[[eg-arch-io-connection]] +==== Jetty I/O: `Connection` `Connection` is the abstraction that deserializes incoming bytes into objects, for example a HTTP request object or a WebSocket frame object, that can be used by more abstract layers. @@ -123,10 +122,13 @@ The example below shows a typical implementation that extends `AbstractConnectio include::{doc_code}/embedded/SelectorManagerDocs.java[tags=connection] ---- +[[eg-arch-io-connection-listener]] +===== Jetty I/O: `Connection.Listener` + // TODO: Introduce Connection.Listener -[[eg-io-arch-echo]] -=== Jetty I/O: Network Echo +[[eg-arch-io-echo]] +==== Jetty I/O: Network Echo With the concepts above it is now possible to write a simple, fully non-blocking, `Connection` implementation that simply echoes the bytes that it reads back to the other peer. diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/arch-listener.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/arch-listener.adoc new file mode 100644 index 00000000000..43b63bbbc08 --- /dev/null +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/arch-listener.adoc @@ -0,0 +1,33 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +[[eg-arch-listener]] +=== Jetty Listeners + +The Jetty architecture is based on xref:eg-arch-bean[components], typically organized in a component tree. +These components have an internal state that varies with the component life cycle (that is, whether the component is started or stopped), as well as with the component use at runtime. +The typical example is a thread pool, whose internal state -- such as the number of pooled threads or the job queue size -- changes as the thread pool is used by the running client or server. + +In many cases, the component state change produces an event that is broadcast to listeners. +Applications can register listeners to these components to be notified of the events they produce. + +This section lists the listeners available in the Jetty components, but the events and listener APIs are discussed in the component specific sections. + +* xref:eg-arch-bean-listener[] +* xref:eg-arch-io-connection-listener[] +* xref:eg-server-http-channel-events[] diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/arch.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/arch.adoc new file mode 100644 index 00000000000..6ca0cbde4d5 --- /dev/null +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/arch.adoc @@ -0,0 +1,25 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +[appendix] +[[eg-arch]] +== Jetty Architecture + +include::arch-bean.adoc[] +include::arch-listener.adoc[] +include::arch-io.adoc[] diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/client/client-io-arch.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/client/client-io-arch.adoc index 57686e6f4a4..8d0c1039920 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/client/client-io-arch.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/client-io-arch.adoc @@ -21,7 +21,7 @@ The Jetty client libraries provide the basic components and APIs to implement a network client. -They build on the common xref:eg-io-arch[Jetty I/O Architecture] and provide client specific concepts (such as establishing a connection to a server). +They build on the common xref:eg-arch-io[Jetty I/O Architecture] and provide client specific concepts (such as establishing a connection to a server). There are conceptually two layers that compose the Jetty client libraries: @@ -31,7 +31,7 @@ There are conceptually two layers that compose the Jetty client libraries: [[eg-client-io-arch-network]] ==== Client Libraries Network Layer -The Jetty client libraries use the common I/O design described in link:#eg-io-arch[this section]. +The Jetty client libraries use the common I/O design described in link:#eg-arch-io[this section]. The main client-side component is the link:{JDURL}/org/eclipse/jetty/io/ClientConnector.html[`ClientConnector`]. The `ClientConnector` primarily wraps the link:{JDURL}/org/eclipse/jetty/io/SelectorManager.html[`SelectorManager`] and aggregates other four components: @@ -88,7 +88,7 @@ Please refer to the `ClientConnector` link:{JDURL}/org/eclipse/jetty/io/ClientCo The protocol layer builds on top of the network layer to generate the bytes to be written to the network and to parse the bytes read from the network. -Recall from link:#eg-io-arch-connection[this section] that Jetty uses the `Connection` abstraction to produce and interpret the network bytes. +Recall from link:#eg-arch-io-connection[this section] that Jetty uses the `Connection` abstraction to produce and interpret the network bytes. On the client side, a `ClientConnectionFactory` implementation is the component that creates `Connection` instances based on the protocol that the client wants to "speak" with the server. diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/index.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/index.adoc index 7039fc933ec..112ba2a5552 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/index.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/index.adoc @@ -59,4 +59,4 @@ endif::[] include::.asciidoctorconfig[] include::client/client.adoc[] include::server/server.adoc[] -include::io-arch.adoc[] +include::arch.adoc[] diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-connector.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-connector.adoc index 1c13d365b76..d4fb580cb2d 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-connector.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-connector.adoc @@ -31,13 +31,13 @@ include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=configur ---- The _acceptors_ are threads (typically only one) that compete to accept TCP connections on the listening port. -When a connection is accepted, `ServerConnector` wraps the accepted `SocketChannel` and passes it to the xref:eg-io-arch-selector-manager[`SelectorManager`]. +When a connection is accepted, `ServerConnector` wraps the accepted `SocketChannel` and passes it to the xref:eg-arch-io-selector-manager[`SelectorManager`]. Therefore, there is a little moment where the acceptor thread is not accepting new connections because it is busy wrapping the just accepted one to pass it to the `SelectorManager`. Connections that are ready to be accepted but are not accepted yet are queued in a bounded queue (at the OS level) whose capacity can be configured with the `ServerConnector.acceptQueueSize` parameter. If your application must withstand a very high rate of connections opened, configuring more than one acceptor thread may be beneficial: when one acceptor thread accepts one connection, another acceptor thread can take over accepting connections. -The _selectors_ are components that manage a set of connected sockets, implemented by xref:eg-io-arch-selector-manager[`ManagedSelector`]. +The _selectors_ are components that manage a set of connected sockets, implemented by xref:eg-arch-io-selector-manager[`ManagedSelector`]. Each selector requires one thread and uses the Java NIO mechanism to efficiently handle the set of connected sockets. As a rule of thumb, a single selector can easily manage up to 1000-5000 sockets, although the number may vary greatly depending on the application. @@ -56,7 +56,7 @@ include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=configur [[eg-server-http-connector-protocol]] ==== Configuring Protocols -For each accepted TCP connection, `ServerConnector` asks a `ConnectionFactory` to create a `Connection` object that handles the network traffic on that TCP connection, parsing and generating bytes for a specific protocol (see xref:eg-io-arch[this section] for more details about `Connection` objects). +For each accepted TCP connection, `ServerConnector` asks a `ConnectionFactory` to create a `Connection` object that handles the network traffic on that TCP connection, parsing and generating bytes for a specific protocol (see xref:eg-arch-io[this section] for more details about `Connection` objects). A `ServerConnector` can be configured with one or more ``ConnectionFactory``s. If no `ConnectionFactory` is specified then `HttpConnectionFactory` is implicitly configured. diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/server-io-arch.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/server/server-io-arch.adoc index d4d6ef5b671..c04763c51aa 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/server/server-io-arch.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/server/server-io-arch.adoc @@ -21,7 +21,7 @@ The Jetty server libraries provide the basic components and APIs to implement a network server. -They build on the common xref:eg-io-arch[Jetty I/O Architecture] and provide server specific concepts. +They build on the common xref:eg-arch-io[Jetty I/O Architecture] and provide server specific concepts. The main I/O server-side class is `org.eclipse.jetty.server.ServerConnector`. diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/server.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/server/server.adoc index 21e0c4e7cdd..04321e604f9 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/server/server.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/server/server.adoc @@ -33,8 +33,6 @@ The Jetty server-side libraries provide: * HTTP/2 low-level support, for applications that want to explicitly handle low-level HTTP/2 _sessions_, _streams_ and _frames_, via the xref:eg-server-http2[HTTP/2 libraries] * WebSocket support, for applications that want to embed a WebSocket server, via the xref:eg-server-websocket[WebSocket libraries] -// TODO: add a section on lifecycle and the component tree. - include::http/server-http.adoc[] include::http2/server-http2.adoc[] include::websocket/server-websocket.adoc[] diff --git a/jetty-documentation/src/main/java/embedded/ComponentDocs.java b/jetty-documentation/src/main/java/embedded/ComponentDocs.java new file mode 100644 index 00000000000..d08f3df83c2 --- /dev/null +++ b/jetty-documentation/src/main/java/embedded/ComponentDocs.java @@ -0,0 +1,143 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package embedded; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; + +import org.eclipse.jetty.util.component.AbstractLifeCycle; +import org.eclipse.jetty.util.component.ContainerLifeCycle; + +@SuppressWarnings("unused") +public class ComponentDocs +{ + public void start() throws Exception + { + // tag::start[] + class Monitor extends AbstractLifeCycle + { + } + + class Root extends ContainerLifeCycle + { + // Monitor is an internal component. + private final Monitor monitor = new Monitor(); + + public Root() + { + // The Monitor life cycle is managed by Root. + addManaged(monitor); + } + } + + class Service extends ContainerLifeCycle + { + // An instance of the Java scheduler service. + private ScheduledExecutorService scheduler; + + @Override + protected void doStart() throws Exception + { + // Java's schedulers cannot be restarted, so they must + // be created anew every time their container is started. + scheduler = Executors.newSingleThreadScheduledExecutor(); + // Even if Java scheduler does not implement + // LifeCycle, make it part of the component tree. + addBean(scheduler); + // Start all the children beans. + super.doStart(); + } + + @Override + protected void doStop() throws Exception + { + // Perform the opposite operations that were + // performed in doStart(), in reverse order. + super.doStop(); + removeBean(scheduler); + scheduler.shutdown(); + } + } + + // Create a Root instance. + Root root = new Root(); + + // Create a Service instance. + Service service = new Service(); + + // Link the components. + root.addBean(service); + + // Start the root component to + // start the whole component tree. + root.start(); + // end::start[] + } + + public void restart() throws Exception + { + // tag::restart[] + class Root extends ContainerLifeCycle + { + } + + class Service extends ContainerLifeCycle + { + // An instance of the Java scheduler service. + private ScheduledExecutorService scheduler; + + @Override + protected void doStart() throws Exception + { + // Java's schedulers cannot be restarted, so they must + // be created anew every time their container is started. + scheduler = Executors.newSingleThreadScheduledExecutor(); + // Even if Java scheduler does not implement + // LifeCycle, make it part of the component tree. + addBean(scheduler); + // Start all the children beans. + super.doStart(); + } + + @Override + protected void doStop() throws Exception + { + // Perform the opposite operations that were + // performed in doStart(), in reverse order. + super.doStop(); + removeBean(scheduler); + scheduler.shutdown(); + } + } + + Root root = new Root(); + Service service = new Service(); + root.addBean(service); + + // Start the Root component. + root.start(); + + // Stop temporarily Service without stopping the Root. + service.stop(); + + // Restart Service. + service.start(); + // end::restart[] + } +} From c6e58e693b10f09621e1c7721b01389e6021d2e6 Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Wed, 22 Apr 2020 18:10:49 +1000 Subject: [PATCH 100/101] Issue #4747 - changes from review Signed-off-by: Lachlan Roberts --- .../jetty/websocket/core/CloseStatus.java | 7 +------ .../core/internal/WebSocketSessionState.java | 18 +++++++++++++++--- .../test/resources/jetty-logging.properties | 2 +- .../test/resources/jetty-logging.properties | 2 +- 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/CloseStatus.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/CloseStatus.java index 4025c632aa1..cdbed64f7a6 100644 --- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/CloseStatus.java +++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/CloseStatus.java @@ -55,7 +55,7 @@ public class CloseStatus private final int code; private final String reason; - private Throwable cause; + private final Throwable cause; /** * Creates a reason for closing a web socket connection with the no given status code. @@ -211,11 +211,6 @@ public class CloseStatus return !isOrdinary(code); } - public void initCause(Throwable cause) - { - this.cause = cause; - } - public Throwable getCause() { return cause; diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/WebSocketSessionState.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/WebSocketSessionState.java index 574341dcbda..6054ab8068f 100644 --- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/WebSocketSessionState.java +++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/WebSocketSessionState.java @@ -20,6 +20,7 @@ package org.eclipse.jetty.websocket.core.internal; import java.nio.channels.ClosedChannelException; +import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.websocket.core.CloseStatus; import org.eclipse.jetty.websocket.core.Frame; import org.eclipse.jetty.websocket.core.OpCode; @@ -124,8 +125,19 @@ public class WebSocketSessionState } /** - * This should only be called directly before closing the connection when we are in {@link State#CLOSED} state. - * This ensures an abnormal close status and if we have no error in the CloseStatus we will set one. + *

      + * If no error is set in the CloseStatus this will either, replace the current close status with + * a {@link CloseStatus#SERVER_ERROR} status if we had a NORMAL close code, or, it will set the cause + * of the CloseStatus if the previous cause was null, this allows onError to be notified after the connection is closed. + *

      + *

      + * This should only be called if there is an error directly before the call to + * {@link WebSocketCoreSession#closeConnection(CloseStatus, Callback)}. + *

      + *

      + * This could occur if the FrameHandler throws an exception in onFrame after receiving a close frame reply, in this + * case to notify onError we must set the cause in the closeStatus. + *

      * @param t the error which occurred. */ public void onError(Throwable t) @@ -141,7 +153,7 @@ public class WebSocketSessionState // Otherwise set the error if it wasn't already set to notify onError as well as onClose. if (_closeStatus.getCause() == null) - _closeStatus.initCause(t); + _closeStatus = new CloseStatus(_closeStatus.getCode(), _closeStatus.getReason(), t); } } diff --git a/jetty-websocket/websocket-core/src/test/resources/jetty-logging.properties b/jetty-websocket/websocket-core/src/test/resources/jetty-logging.properties index 2f149811ed4..c08968b68e5 100644 --- a/jetty-websocket/websocket-core/src/test/resources/jetty-logging.properties +++ b/jetty-websocket/websocket-core/src/test/resources/jetty-logging.properties @@ -1,5 +1,5 @@ # Jetty Logging using jetty-slf4j-impl -org.eclipse.jetty.LEVEL=INFO +# org.eclipse.jetty.LEVEL=DEBUG # org.eclipse.jetty.io.LEVEL=DEBUG # org.eclipse.jetty.websocket.core.LEVEL=DEBUG # org.eclipse.jetty.websocket.core.TestFrameHandler.LEVEL=DEBUG diff --git a/jetty-websocket/websocket-javax-tests/src/test/resources/jetty-logging.properties b/jetty-websocket/websocket-javax-tests/src/test/resources/jetty-logging.properties index 83afb9061fe..7485b6d2a83 100644 --- a/jetty-websocket/websocket-javax-tests/src/test/resources/jetty-logging.properties +++ b/jetty-websocket/websocket-javax-tests/src/test/resources/jetty-logging.properties @@ -1,5 +1,5 @@ # Jetty Logging using jetty-slf4j-impl -org.eclipse.jetty.LEVEL=INFO +# org.eclipse.jetty.LEVEL=DEBUG # org.eclipse.jetty.util.log.stderr.LONG=true # org.eclipse.jetty.server.AbstractConnector.LEVEL=DEBUG # org.eclipse.jetty.io.WriteFlusher.LEVEL=DEBUG From 1d96dda43f28696adaeb2230dfc664f85dbc972d Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Wed, 22 Apr 2020 13:38:20 +0200 Subject: [PATCH 101/101] Improvements to the Jetty server documentation. Added section on troubleshooting. Signed-off-by: Simone Bordet --- .../main/asciidoc/embedded-guide/index.adoc | 1 + .../server/http/server-http-application.adoc | 2 +- .../server/http/server-http-connector.adoc | 9 +- .../server/http/server-http-handler-use.adoc | 158 ++++++++++-------- .../server/http/server-http-handler.adoc | 2 + .../server/http/server-http-security.adoc | 24 +++ .../server/http/server-http.adoc | 28 ++-- .../embedded-guide/troubleshooting.adoc | 85 ++++++++++ 8 files changed, 223 insertions(+), 86 deletions(-) create mode 100644 jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-security.adoc create mode 100644 jetty-documentation/src/main/asciidoc/embedded-guide/troubleshooting.adoc diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/index.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/index.adoc index 112ba2a5552..51809cb9d3b 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/index.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/index.adoc @@ -60,3 +60,4 @@ include::.asciidoctorconfig[] include::client/client.adoc[] include::server/server.adoc[] include::arch.adoc[] +include::troubleshooting.adoc[] diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-application.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-application.adoc index dcd6600898e..c1bbb90189b 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-application.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-application.adoc @@ -23,7 +23,7 @@ Writing HTTP applications is typically simple, especially when using blocking AP However, there are subtle cases where it is worth clarifying what a server application should do to obtain the desired results when run by Jetty. [[eg-server-http-application-1xx]] -==== Managing 1xx Responses +==== Sending 1xx Responses The link:https://tools.ietf.org/html/rfc7231#section-5.1.1[HTTP/1.1 RFC] allows for `1xx` informational responses to be sent before a real content response. Unfortunately the servlet specification does not provide a way for these to be sent, so Jetty has had to provide non-standard handling of these headers. diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-connector.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-connector.adoc index d4fb580cb2d..73aa19ae680 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-connector.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-connector.adoc @@ -21,7 +21,8 @@ A `Connector` is the component that handles incoming requests from clients, and works in conjunction with `ConnectionFactory` instances. -The primary implementation is `org.eclipse.jetty.server.ServerConnector`.`ServerConnector` uses a `java.nio.channels.ServerSocketChannel` to listen to a TCP port and to accept TCP connections. +The primary implementation is `org.eclipse.jetty.server.ServerConnector`. +`ServerConnector` uses a `java.nio.channels.ServerSocketChannel` to listen to a TCP port and to accept TCP connections. Since `ServerConnector` wraps a `ServerSocketChannel`, it can be configured in a similar way, for example the port to listen to, the network address to bind to, etc.: @@ -32,18 +33,18 @@ include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=configur The _acceptors_ are threads (typically only one) that compete to accept TCP connections on the listening port. When a connection is accepted, `ServerConnector` wraps the accepted `SocketChannel` and passes it to the xref:eg-arch-io-selector-manager[`SelectorManager`]. -Therefore, there is a little moment where the acceptor thread is not accepting new connections because it is busy wrapping the just accepted one to pass it to the `SelectorManager`. +Therefore, there is a little moment where the acceptor thread is not accepting new connections because it is busy wrapping the just accepted connection to pass it to the `SelectorManager`. Connections that are ready to be accepted but are not accepted yet are queued in a bounded queue (at the OS level) whose capacity can be configured with the `ServerConnector.acceptQueueSize` parameter. If your application must withstand a very high rate of connections opened, configuring more than one acceptor thread may be beneficial: when one acceptor thread accepts one connection, another acceptor thread can take over accepting connections. The _selectors_ are components that manage a set of connected sockets, implemented by xref:eg-arch-io-selector-manager[`ManagedSelector`]. -Each selector requires one thread and uses the Java NIO mechanism to efficiently handle the set of connected sockets. +Each selector requires one thread and uses the Java NIO mechanism to efficiently handle a set of connected sockets. As a rule of thumb, a single selector can easily manage up to 1000-5000 sockets, although the number may vary greatly depending on the application. For example, web site applications tend to use sockets for one or more HTTP requests to retrieve resources and then the socket is idle for most of the time. In this case a single selector may be able to manage many sockets because chances are that they will be idle most of the time. -On the contrary, web messaging applications tend to send many small messages at a very high frequency so that the socket is rarely idle. +On the contrary, web messaging applications tend to send many small messages at a very high frequency so that sockets are rarely idle. In this case a single selector may be able to manage less sockets because chances are that many of them will be active at the same time. It is possible to configure more than one `ServerConnector`, each listening on a different port: diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-handler-use.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-handler-use.adoc index 9837d2736ae..8dc92bc8b62 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-handler-use.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-handler-use.adoc @@ -38,8 +38,19 @@ A client making a request to `+https://shop.domain.com/cart+` is directed by Jet Therefore, in general, a web application is deployed to a _context_ which can be seen as the pair `(virtual_host, context_path)`. In the first case the contexts were `(domain.com, /shop)` and `(domain.com, /api)`, while in the second case the contexts were `(shop.domain.com, /)` and `(api.domain.com, /)`. Server applications using the Jetty Server Libraries create and configure a _context_ for each web application. +Many __context__s can be deployed together to enrich the web application offering -- for example a catalog context, a shop context, an API context, an administration context, etc. -[[eg-server-http-handler-use-context]] +Web applications can be written using exclusively the Servlet APIs, since developers know well the Servlet API and because they guarantee better portability across Servlet container implementations. + +Embedded web applications based on the Servlet APIs are described in xref:eg-server-http-handler-use-servlet[this section]. + +Embedded web applications may also require additional features such as access to Jetty specific APIs, or utility features such as redirection from HTTP to HTTPS, support for `gzip` content compression, etc. +The Jetty Server Libraries provides a number of out-of-the-box __Handler__s that implement the most common functionalities and are described in xref:eg-server-http-handler-use-util[this section]. + +[[eg-server-http-handler-use-util]] +==== Custom and Utility Handlers + +[[eg-server-http-handler-use-util-context]] ===== ContextHandler `ContextHandler` is a `Handler` that represents a _context_ for a web application. @@ -62,7 +73,7 @@ Server └── ShopHandler ---- -[[eg-server-http-handler-use-context-collection]] +[[eg-server-http-handler-use-util-context-collection]] ===== ContextHandlerCollection Server applications may need to deploy to Jetty more than one web application. @@ -93,63 +104,7 @@ Server └── RESTHandler ---- -[[eg-server-http-handler-use-servlet-context]] -===== ServletContextHandler - -``Handler``s are easy to write, but often web applications have already been written using the Servlet APIs, using ``Servlet``s and ``Filter``s. - -`ServletContextHandler` is a `ContextHandler` that provides support for the Servlet APIs and implements the behaviors required by the Servlet specification. - -The Maven artifact coordinates are: - -[source,xml,subs=normal] ----- - - org.eclipse.jetty - jetty-servlet - {version} - ----- - -[source,java,indent=0] ----- -include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=servletContextHandler] ----- - -The `Handler` and Servlet components tree structure looks like the following: - -[source,screen,subs=normal] ----- -Server -└── ServletContextHandler /shop - ├── _ShopCartServlet /cart/*_ - └── _CrossOriginFilter /*_ ----- - -Note how the Servlet components (they are not ``Handler``s) are represented in _italic_. - -Note also how adding a `Servlet` or a `Filter` returns a _holder_ object that can be used to specify additional configuration for that particular `Servlet` or `Filter`. - -When a request arrives to `ServletContextHandler` the request URI will be matched against the ``Filter``s and ``Servlet`` mappings and only those that match will process the request, as dictated by the Servlet specification. - -IMPORTANT: `ServletContextHandler` is a terminal `Handler`, that is it always calls `Request.setHandled(true)` when invoked. -Server applications must be careful when creating the `Handler` tree to put ``ServletContextHandler``s as last ``Handler``s in a `HandlerList` or as children of `ContextHandlerCollection`. - -[[eg-server-http-handler-use-webapp-context]] -===== WebAppContext - -`WebAppContext` is a `ServletContextHandler` that auto configures itself by reading a `web.xml` Servlet configuration file. - -Server applications can specify a `+*.war+` file or a directory with the structure of a `+*.war+` file to `WebAppContext` to deploy a standard Servlet web application packaged as a `war` (as defined by the Servlet specification). - -Where server applications using `ServletContextHandler` must manually invoke methods to add ``Servlet``s and ``Filter``s, `WebAppContext` reads `WEB-INF/web.xml` to add ``Servlet``s and ``Filter``s. - -[source,java,indent=0] ----- -include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=webAppContextHandler] ----- - -[[eg-server-http-handler-use-resource-handler]] +[[eg-server-http-handler-use-util-resource-handler]] ===== ResourceHandler -- Static Content Static content such as images or files (HTML, JavaScript, CSS) can be sent by Jetty very efficiently because Jetty can write the content asynchronously, using direct ``ByteBuffer``s to minimize data copy, and using a memory cache for faster access to the data to send. @@ -188,17 +143,6 @@ include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=multiple If the resource is not found, `ResourceHandler` will not call `Request.setHandled(true)` so what happens next depends on the `Handler` tree structure. See also xref:eg-server-http-handler-use-util-default-handler[how to use] `DefaultHandler`. -[[eg-server-http-handler-use-default-servlet]] -===== DefaultServlet -- Static Content for Servlets - -If you have a xref:eg-server-http-handler-use-servlet-context[Servlet web application], you may want to use a `DefaultServlet` instead of `ResourceHandler`. -The features are similar, but `DefaultServlet` is more commonly used to serve static files for Servlet web applications. - -[source,java,indent=0] ----- -include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=defaultServlet] ----- - [[eg-server-http-handler-use-util-gzip-handler]] ===== GzipHandler @@ -372,3 +316,77 @@ In the example above, `ContextHandlerCollection` will try to match a request to NOTE: `DefaultHandler` just sends a nicer HTTP `404` response in case of wrong requests from clients. Jetty will send an HTTP `404` response anyway if `DefaultHandler` is not used. + +[[eg-server-http-handler-use-servlet]] +==== Servlet API Handlers + +[[eg-server-http-handler-use-servlet-context]] +===== ServletContextHandler + +``Handler``s are easy to write, but often web applications have already been written using the Servlet APIs, using ``Servlet``s and ``Filter``s. + +`ServletContextHandler` is a `ContextHandler` that provides support for the Servlet APIs and implements the behaviors required by the Servlet specification. + +The Maven artifact coordinates are: + +[source,xml,subs=normal] +---- + + org.eclipse.jetty + jetty-servlet + {version} + +---- + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=servletContextHandler] +---- + +The `Handler` and Servlet components tree structure looks like the following: + +[source,screen,subs=normal] +---- +Server +└── ServletContextHandler /shop + ├── _ShopCartServlet /cart/*_ + └── _CrossOriginFilter /*_ +---- + +Note how the Servlet components (they are not ``Handler``s) are represented in _italic_. + +Note also how adding a `Servlet` or a `Filter` returns a _holder_ object that can be used to specify additional configuration for that particular `Servlet` or `Filter`. + +When a request arrives to `ServletContextHandler` the request URI will be matched against the ``Filter``s and ``Servlet`` mappings and only those that match will process the request, as dictated by the Servlet specification. + +IMPORTANT: `ServletContextHandler` is a terminal `Handler`, that is it always calls `Request.setHandled(true)` when invoked. +Server applications must be careful when creating the `Handler` tree to put ``ServletContextHandler``s as last ``Handler``s in a `HandlerList` or as children of `ContextHandlerCollection`. + +[[eg-server-http-handler-use-webapp-context]] +===== WebAppContext + +`WebAppContext` is a `ServletContextHandler` that auto configures itself by reading a `web.xml` Servlet configuration file. + +Server applications can specify a `+*.war+` file or a directory with the structure of a `+*.war+` file to `WebAppContext` to deploy a standard Servlet web application packaged as a `war` (as defined by the Servlet specification). + +Where server applications using `ServletContextHandler` must manually invoke methods to add ``Servlet``s and ``Filter``s, `WebAppContext` reads `WEB-INF/web.xml` to add ``Servlet``s and ``Filter``s. + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=webAppContextHandler] +---- + +// TODO: add a section on Configuration (system/server classes) +// TODO: add a section about how to setup JSP support +// TODO: add a section on ClassLoading (see old docs) + +[[eg-server-http-handler-use-default-servlet]] +===== DefaultServlet -- Static Content for Servlets + +If you have a xref:eg-server-http-handler-use-servlet-context[Servlet web application], you may want to use a `DefaultServlet` instead of `ResourceHandler`. +The features are similar, but `DefaultServlet` is more commonly used to serve static files for Servlet web applications. + +[source,java,indent=0] +---- +include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=defaultServlet] +---- diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-handler.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-handler.adoc index f12483e57d7..61f17cf3ec0 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-handler.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-handler.adoc @@ -61,5 +61,7 @@ However, in some cases the additional features are not required, or additional c In these cases, implementing your own `Handler` may be a better solution. Refer to xref:eg-server-http-handler-implement[this section] for more information about how to write your own ``Handler``s. +// TODO: document ScopedHandler? Is this really necessary or just an implementation detail that application will never worry about? + include::server-http-handler-use.adoc[] include::server-http-handler-implement.adoc[] diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-security.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-security.adoc new file mode 100644 index 00000000000..2aa0f00cd5c --- /dev/null +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http-security.adoc @@ -0,0 +1,24 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +[[eg-server-http-security]] +==== Securing HTTP Server Applications + +// TODO: ConstraintSecurityHandler and Authenticators and LoginServices +TODO + diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http.adoc index 32ff53d3aaa..fe9a8d27066 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/server/http/server-http.adoc @@ -64,7 +64,12 @@ include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=simple] The example above shows the simplest HTTP/1.1 server; it has no support for HTTP sessions, for HTTP authentication, or for any of the features required by the Servlet specification. -All these features are provided by the Jetty Server Libraries and server applications only need to put the required components together to provide all the required features, and it is discussed in details in xref:eg-server-http-handler-use[this section]. +All these features are provided by the Jetty Server Libraries, and server applications only need to put the required components together to provide all the required features. + +The ``Handler``s provided by the Jetty Server Libraries allow writing server applications that have functionalities similar to Apache HTTPD or Nginx (for example: URL redirection, URL rewriting, serving static content, reverse proxying, etc.), as well as generating content dynamically by processing incoming requests. +Read xref:eg-server-http-handler[this section] for further details. + +If you are interested in writing your server application based on the Servlet APIs, jump to xref:eg-server-http-handler-use-servlet[this section]. [[eg-server-http-request-processing]] ==== Server Request Processing @@ -75,15 +80,6 @@ Request handing is slightly different for each protocol; in HTTP/2 Jetty takes i However, the diagram below captures the essence of request handling that is common among all protocols that carry HTTP requests. -First, the Jetty I/O layer emits an event that a socket has data to read. -This event is converted to a call to `AbstractConnection.onFillable()`, where the `Connection` first reads from the `EndPoint` into a `ByteBuffer`, and then calls a protocol specific parser to parse the bytes in the `ByteBuffer`. - -The parser emit events such that are protocol specific; the HTTP/2 parser, for example, emits events for each HTTP/2 frame that has been parsed. -The parser events are then converted to protocol independent events such as _"request start"_, _"request headers"_, _"request content chunk"_, etc. -that in turn are converted into method calls to `HttpChannel`. - -When enough of the HTTP request is arrived, the `Connection` calls `HttpChannel.handle()` that calls the `Handler` chain, that eventually calls the server application code. - [plantuml] ---- skinparam backgroundColor transparent @@ -109,6 +105,15 @@ HttpChannel -> Server : handle() Server -> Handlers : handle() ---- +First, the Jetty I/O layer emits an event that a socket has data to read. +This event is converted to a call to `AbstractConnection.onFillable()`, where the `Connection` first reads from the `EndPoint` into a `ByteBuffer`, and then calls a protocol specific parser to parse the bytes in the `ByteBuffer`. + +The parser emit events that are protocol specific; the HTTP/2 parser, for example, emits events for each HTTP/2 frame that has been parsed. +The parser events are then converted to protocol independent events such as _"request start"_, _"request headers"_, _"request content chunk"_, etc. +that in turn are converted into method calls to `HttpChannel`. + +When enough of the HTTP request is arrived, the `Connection` calls `HttpChannel.handle()` that calls the `Handler` chain, that eventually calls the server application code. + [[eg-server-http-channel-events]] ===== HttpChannel Events @@ -141,7 +146,7 @@ Currently, the following events are available: Please refer to the `HttpChannel.Listener` link:{JDURL}/org/eclipse/jetty/server/HttpChannel.Listener.html[javadocs] for the complete list of events. -Server applications can register `HttpChannel.Listener` by adding them as beans to the `Connector`: +Server applications can register `HttpChannel.Listener` by adding them as xref:eg-arch-bean[beans] to the `Connector`: [source,java,indent=0] ---- @@ -150,4 +155,5 @@ include::../../{doc_code}/embedded/server/http/HTTPServerDocs.java[tags=httpChan include::server-http-connector.adoc[] include::server-http-handler.adoc[] +include::server-http-security.adoc[] include::server-http-application.adoc[] diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/troubleshooting.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/troubleshooting.adoc new file mode 100644 index 00000000000..0bfc95b68a7 --- /dev/null +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/troubleshooting.adoc @@ -0,0 +1,85 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +[appendix] +[[eg-troubleshooting]] +== Troubleshooting Jetty + +[[eg-troubleshooting-logging]] +=== Logging + +The Jetty libraries (both client and server) use link:http://slf4j.org/[SLF4J] as logging APIs. +You can therefore plug in any SLF4J logging implementation, and configure the logging category `org.eclipse.jetty` at the desired level. + +When you have problems with Jetty, the first thing that you want to do is to enable DEBUG logging. +This is helpful because by reading the DEBUG logs you get a better understanding of what is going on in the system (and that alone may give you the answers you need to fix the problem), and because Jetty developers will probably need the DEBUG logs to help you. + +==== Jetty SLF4J Binding + +The Jetty artifact `jetty-slf4j-impl` is a SLF4J binding, that is the Jetty implementation of the SLF4J APIs, and provides a number of easy-to-use features to configure logging. + +The Jetty SLF4J binding only provides an appender that writes to `System.err`. +For more advanced configurations (for example, logging to a file), use link:http://logback.qos.ch[LogBack], or link:https://logging.apache.org/log4j/2.x/[Log4J2], or your preferred SLF4J binding. + +CAUTION: Only one binding can be present in the class-path or module-path. If you use the LogBack SLF4J binding or the Log4J2 SLF4J binding, remember to remove the Jetty SLF4J binding. + +The Jetty SLF4J binding reads a file in the class-path (or module-path) called `jetty-logging.properties` that can be configured with the logging levels for various logger categories: + +.jetty-logging.properties +[source,screen] +---- +# By default, log at INFO level all Jetty classes. +org.eclipse.jetty.LEVEL=INFO + +# However, the Jetty client classes are logged at DEBUG level. +org.eclipse.jetty.client.LEVEL=DEBUG +---- + +Similarly to how you configure the `jetty-logging.properties` file, you can set the system property `org.eclipse.jetty[.].LEVEL=DEBUG` to quickly change the logging level to DEBUG without editing any file. +The system property can be set on the command line, or in your IDE when you run your tests or your Jetty-based application and will override the `jetty-logging.properties` file configuration. +For example to enable DEBUG logging for all the Jetty classes (_very_ verbose): + +[source,screen] +---- +java -Dorg.eclipse.jetty.LEVEL=DEBUG --class-path ... +---- + +If you want to enable DEBUG logging but only for the HTTP/2 classes: + +[source,screen] +---- +java -Dorg.eclipse.jetty.http2.LEVEL=DEBUG --class-path ... +---- + +[[eg-troubleshooting-debugging]] +=== Debugging + +Sometimes, in order to figure out a problem, enabling xref:eg-troubleshooting-logging[DEBUG logging] is not enough and you really need to debug the code with a debugger. + +Debugging an embedded Jetty application is most easily done from your preferred IDE, so refer to your IDE instruction for how to debug Java applications. + +Remote debugging can be enabled in a Jetty application via command line options: + +[source,screen] +---- +java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:8000 --class-path ... +---- + +The example above enables remote debugging so that debuggers (for example, your preferred IDE) can connect to port `8000` on the host running the Jetty application to receive debugging events. + +NOTE: More technically, remote debugging exchanges JVM Tools Interface (JVMTI) events and commands via the Java Debug Wire Protocol (JDWP).