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-annotations/src/main/java/org/eclipse/jetty/annotations/RunAsAnnotationHandler.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/RunAsAnnotationHandler.java
index b9eed98a9b8..04fd759f78b 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
@@ -20,7 +20,6 @@ package org.eclipse.jetty.annotations;
import jakarta.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.webapp.Descriptor;
import org.eclipse.jetty.webapp.MetaData;
@@ -60,14 +59,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..bac5020a164
--- /dev/null
+++ b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestRunAsAnnotation.java
@@ -0,0 +1,63 @@
+//
+// ========================================================================
+// 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.annotations;
+
+import java.io.File;
+
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.util.resource.Resource;
+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/*");
+ Resource fakeXml = Resource.newResource(new File(MavenTestingUtils.getTargetTestingDir("run-as"), "fake.xml"));
+ wac.getMetaData().setOrigin(holder2.getName() + ".servlet.run-as", new WebDescriptor(fakeXml));
+
+ AnnotationIntrospector parser = new AnnotationIntrospector(wac);
+ RunAsAnnotationHandler handler = new RunAsAnnotationHandler(wac);
+ parser.registerHandler(handler);
+ parser.introspect(new ServletC(), null);
+
+ assertEquals("admin", holder.getRunAsRole());
+ assertEquals(null, holder2.getRunAsRole());
+ }
+}
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 d7aadabe49e..49b33a32c4e 100644
--- a/jetty-documentation/pom.xml
+++ b/jetty-documentation/pom.xml
@@ -49,14 +49,23 @@
+
+ org.eclipse.jetty.toolchain
+ jetty-servlet-api
+ org.eclipse.jettyjetty-client${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/contexts/setting-form-size.adoc b/jetty-documentation/src/main/asciidoc/distribution-guide/contexts/setting-form-size.adoc
index d988e0d695e..4bd37d5d976 100644
--- a/jetty-documentation/src/main/asciidoc/distribution-guide/contexts/setting-form-size.adoc
+++ b/jetty-documentation/src/main/asciidoc/distribution-guide/contexts/setting-form-size.adoc
@@ -19,57 +19,51 @@
[[setting-form-size]]
=== Setting Max Form Size
-Jetty limits the amount of data that can post back from a browser or other client to the server.
-This helps protect the server against denial of service attacks by malicious clients sending huge amounts of data.
-The default maximum size Jetty permits is 200000 bytes.
-You can change this default for a particular webapp, for all webapps on a particular Server instance, or all webapps within the same JVM.
+When a client issues a POST request with `Content-Type: application/x-www-form-urlencoded` there are 2 configurable limits imposed to help protect the server against denial of service attacks by malicious clients sending huge amounts of data.
+
+There is a maximum form size limit, and a maximum form keys limit.
+
+The default maximum form size Jetty permits is 200000 bytes.
+The default maximum form keys Jetty permits is 1000 keys.
+
+You can change these defaults for a particular webapp, or all webapps within the same JVM.
==== For a Single Webapp
-The method to invoke is: `ContextHandler.setMaxFormContentSize(int maxSize);`
+The methods to invoke are:
+
+* `ContextHandler.setMaxFormContentSize(int maxSize);`
+* `ContextHandler.setMaxFormKeys(int max);`
This can be done either in a context XML deployment descriptor external to the webapp, or in a `jetty-web.xml` file in the webapp's `WEB-INF` directory.
In either case the syntax of the XML file is the same:
-[source, xml, subs="{sub-order}"]
+[source,xml,subs="{sub-order}"]
----
- 200000
+ 400000
+
+
+
+ 400
----
-==== For All Apps on a Server
-
-Set an attribute in `jetty.xml` on the Server instance for which you want to modify the maximum form content size:
-
-[source, xml, subs="{sub-order}"]
-----
-
-
- org.eclipse.jetty.server.Request.maxFormContentSize
- 200000
-
-
-----
-
-____
-[IMPORTANT]
-It is important to remember that you should *not* modify the XML files in your `$JETTY_HOME`.
-If you do for some reason feel you want to change the way an XML file operates, it is best to make a copy of it in your `$JETTY_BASE` in an `/etc` directory.
-Jetty will always look first to the `$JETTY_BASE` for configuration.
-____
-
==== For All Apps in the JVM
-Use the system property `org.eclipse.jetty.server.Request.maxFormContentSize`.
+Use the system properties:
+
+* `org.eclipse.jetty.server.Request.maxFormContentSize` - the maximum size of Form Content allowed
+* `org.eclipse.jetty.server.Request.maxFormKeys` - the maximum number of Form Keys allowed
+
This can be set on the command line or in the `$JETTY_BASE\start.ini` or any `$JETTY_BASE\start.d\*.ini` link:#startup-modules[module ini file.]
Using `$JETTY_BASE\start.d\server.ini` as an example:
-[source, console, subs="{sub-order}"]
+[source,console,subs="{sub-order}"]
----
# ---------------------------------------
# Module: server
@@ -83,5 +77,6 @@ Using `$JETTY_BASE\start.d\server.ini` as an example:
...
--Dorg.eclipse.jetty.server.Request.maxFormContentSize=200000
+-Dorg.eclipse.jetty.server.Request.maxFormContentSize=400000
+-Dorg.eclipse.jetty.server.Request.maxFormKeys=400
----
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/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 ac1bd6e9170..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,28 +16,32 @@
// ========================================================================
//
-[[client]]
+[[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 an application that needs to contact a third party
+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.
-There are primarily two client libraries:
+These are the available client libraries:
-* link:#client-http[The HTTP 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[]
include::client-io-arch.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 4765fb47a18..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:
@@ -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:
@@ -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]
----
-[[http-client-response-content]]
+[[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 4e3e48704ae..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,76 +16,86 @@
// ========================================================================
//
-[[client-http-authentication]]
-=== Authentication Support
+[[eg-client-http-authentication]]
+=== 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 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 62770598726..68a6feca9f4 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,104 +16,88 @@
// ========================================================================
//
-[[client-http-cookie]]
-=== Cookies Support
+[[eg-client-http-cookie]]
+=== 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 `jakarta.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..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,9 +43,21 @@ 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`.
+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.
@@ -64,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.
@@ -84,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 pool of
-connections to that origin.
+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.
@@ -133,9 +145,75 @@ connection pools.
Therefore an origin is identified by the tuple
`(scheme, host, port, tag, protocol)`.
-[[client-http-request-processing]]
+[[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.
+
+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]
+----
+
+[[eg-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.
@@ -148,18 +226,18 @@ 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`
-(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
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`
@@ -171,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 13f53584e93..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,88 +16,77 @@
// ========================================================================
//
-[[client-http-proxy]]
-=== Proxy Support
+[[eg-client-http-proxy]]
+=== 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).
-[[client-http-proxy-authentication]]
+Proxying is supported for both HTTP/1.1 and HTTP/2.
+
+[[eg-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
+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, 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 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 21870cc9460..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,21 +16,25 @@
// ========================================================================
//
-[[http-client-transport]]
-=== Pluggable Transports
+[[eg-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:
+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.
+
+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.
+
+[[eg-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.
-
+[[eg-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 xref:eg-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.
+[[eg-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).
+
+[[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).
+
+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/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
new file mode 100644
index 00000000000..546b61fa9fc
--- /dev/null
+++ b/jetty-documentation/src/main/asciidoc/embedded-guide/client/http2/client-http2.adoc
@@ -0,0 +1,322 @@
+//
+// ========================================================================
+// 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-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].
+
+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.
+
+[[eg-client-http2-intro]]
+==== Introducing HTTP2Client
+
+The Maven artifact coordinates for the HTTP/2 client library are the following:
+
+[source,xml,subs="{sub-order}"]
+----
+
+ org.eclipse.jetty.http2
+ http2-client
+ {version}
+
+----
+
+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:
+
+[source,java,indent=0]
+----
+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`.
+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.
+
+[[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].
+
+
+
+
+[[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 following example connects to the server on a clear-text port:
+
+[source,java,indent=0]
+----
+include::../../{doc_code}/embedded/client/http2/HTTP2ClientDocs.java[tags=clearTextConnect]
+----
+
+The following example connects to the server on an encrypted port:
+
+[source,java,indent=0]
+----
+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.
+
+[[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:
+
+[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.
+
+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.
+
+In order to send a 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={sub-order}]
+----
+include::../../{doc_code}/embedded/client/http2/HTTP2ClientDocs.java[tags=newStream]
+----
+
+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.
+
+HTTP requests may have content, which is sent using the `Stream` APIs:
+
+[source,java,indent=0]
+----
+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.
+
+[[eg-client-http2-response]]
+==== Receiving a Response
+
+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.
+
+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`:
+
+[source,java,indent=0]
+----
+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.
+
+[[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.
+
+The `HTTP2Client` APIs allow client applications to send and receive this
+"reset" frame:
+
+[source,java,indent=0]
+----
+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 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.
+
+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:
+
+[source,java,indent=0]
+----
+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:
+
+[source,java,indent=0]
+----
+include::../../{doc_code}/embedded/client/http2/HTTP2ClientDocs.java[tags=pushReset]
+----
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 8549f712e64..60161cf3c21 100644
--- a/jetty-documentation/src/main/java/embedded/client/http/HTTPClientDocs.java
+++ b/jetty-documentation/src/main/java/embedded/client/http/HTTPClientDocs.java
@@ -22,31 +22,55 @@ 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;
+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;
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;
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;
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;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import static java.lang.System.Logger.Level.INFO;
@@ -474,4 +498,353 @@ 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[]
+ }
+
+ 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[]
+ }
+
+ 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[]
+ }
+
+ 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[]
+ }
+
+ 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[]
+ }
}
diff --git a/jetty-documentation/src/main/java/embedded/client/http2/HTTP2ClientDocs.java b/jetty-documentation/src/main/java/embedded/client/http2/HTTP2ClientDocs.java
new file mode 100644
index 00000000000..c5abbf7eacb
--- /dev/null
+++ b/jetty-documentation/src/main/java/embedded/client/http2/HTTP2ClientDocs.java
@@ -0,0 +1,438 @@
+//
+// ========================================================================
+// 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.http2;
+
+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.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.ErrorCode;
+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.http2.frames.PushPromiseFrame;
+import org.eclipse.jetty.http2.frames.ResetFrame;
+import org.eclipse.jetty.http2.frames.SettingsFrame;
+import org.eclipse.jetty.io.ClientConnector;
+import org.eclipse.jetty.util.Callback;
+
+import static java.lang.System.Logger.Level.INFO;
+
+@SuppressWarnings("unused")
+public class HTTP2ClientDocs
+{
+ public void start() throws Exception
+ {
+ // tag::start[]
+ // Instantiate HTTP2Client.
+ HTTP2Client http2Client = new HTTP2Client();
+
+ // Configure HTTP2Client, for example:
+ http2Client.setStreamIdleTimeout(15000);
+
+ // Start HTTP2Client.
+ http2Client.start();
+ // end::start[]
+ }
+
+ public void stop() throws Exception
+ {
+ HTTP2Client http2Client = new HTTP2Client();
+ http2Client.start();
+ // tag::stop[]
+ // Stop HTTP2Client.
+ http2Client.stop();
+ // end::stop[]
+ }
+
+ public void clearTextConnect() throws Exception
+ {
+ HTTP2Client http2Client = new HTTP2Client();
+ http2Client.start();
+ // tag::clearTextConnect[]
+ // Address of the server's clear-text port.
+ SocketAddress serverAddress = new InetSocketAddress("localhost", 8080);
+
+ // Connect to the server, the CompletableFuture will be
+ // notified when the connection is succeeded (or failed).
+ CompletableFuture sessionCF = http2Client.connect(serverAddress, new Session.Listener.Adapter());
+
+ // Block to obtain the Session.
+ // Alternatively you can use the CompletableFuture APIs to avoid blocking.
+ Session session = sessionCF.get();
+ // end::clearTextConnect[]
+ }
+
+ public void encryptedConnect() throws Exception
+ {
+ // tag::encryptedConnect[]
+ HTTP2Client http2Client = new HTTP2Client();
+ http2Client.start();
+
+ ClientConnector connector = http2Client.getClientConnector();
+
+ // Address of the server's encrypted port.
+ SocketAddress serverAddress = new InetSocketAddress("localhost", 8443);
+
+ // Connect to the server, the CompletableFuture will be
+ // notified when the connection is succeeded (or failed).
+ CompletableFuture sessionCF = http2Client.connect(connector.getSslContextFactory(), serverAddress, new Session.Listener.Adapter());
+
+ // Block to obtain the Session.
+ // Alternatively you can use the CompletableFuture APIs to avoid blocking.
+ Session session = sessionCF.get();
+ // end::encryptedConnect[]
+ }
+
+ public void configure() throws Exception
+ {
+ HTTP2Client http2Client = new HTTP2Client();
+ http2Client.start();
+
+ // tag::configure[]
+ SocketAddress serverAddress = new InetSocketAddress("localhost", 8080);
+ http2Client.connect(serverAddress, new Session.Listener.Adapter()
+ {
+ @Override
+ public Map onPreface(Session session)
+ {
+ Map configuration = new HashMap<>();
+
+ // Disable push from the server.
+ configuration.put(SettingsFrame.ENABLE_PUSH, 0);
+
+ // Override HTTP2Client.initialStreamRecvWindow for this session.
+ configuration.put(SettingsFrame.INITIAL_WINDOW_SIZE, 1024 * 1024);
+
+ return configuration;
+ }
+ });
+ // end::configure[]
+ }
+
+ public void newStream() throws Exception
+ {
+ HTTP2Client http2Client = new HTTP2Client();
+ http2Client.start();
+ // tag::newStream[]
+ SocketAddress serverAddress = new InetSocketAddress("localhost", 8080);
+ CompletableFuture sessionCF = http2Client.connect(serverAddress, new Session.Listener.Adapter());
+ Session session = sessionCF.get();
+
+ // Configure the request headers.
+ HttpFields requestHeaders = new HttpFields();
+ requestHeaders.put(HttpHeader.USER_AGENT, "Jetty HTTP2Client {version}");
+
+ // The request metadata with method, URI and headers.
+ MetaData.Request request = new MetaData.Request("GET", new HttpURI("http://localhost:8080/path"), HttpVersion.HTTP_2, requestHeaders);
+
+ // The HTTP/2 HEADERS frame, with endStream=true
+ // to signal that this request has no content.
+ HeadersFrame headersFrame = new HeadersFrame(request, null, true);
+
+ // Open a Stream by sending the HEADERS frame.
+ session.newStream(headersFrame, new Stream.Listener.Adapter());
+ // end::newStream[]
+ }
+
+ public void newStreamWithData() throws Exception
+ {
+ HTTP2Client http2Client = new HTTP2Client();
+ http2Client.start();
+ // tag::newStreamWithData[]
+ SocketAddress serverAddress = new InetSocketAddress("localhost", 8080);
+ CompletableFuture sessionCF = http2Client.connect(serverAddress, new Session.Listener.Adapter());
+ Session session = sessionCF.get();
+
+ // Configure the request headers.
+ HttpFields requestHeaders = new HttpFields();
+ requestHeaders.put(HttpHeader.CONTENT_TYPE, "application/json");
+
+ // The request metadata with method, URI and headers.
+ MetaData.Request request = new MetaData.Request("POST", new HttpURI("http://localhost:8080/path"), HttpVersion.HTTP_2, requestHeaders);
+
+ // The HTTP/2 HEADERS frame, with endStream=false to
+ // signal that there will be more frames in this stream.
+ HeadersFrame headersFrame = new HeadersFrame(request, null, false);
+
+ // Open a Stream by sending the HEADERS frame.
+ CompletableFuture streamCF = session.newStream(headersFrame, new Stream.Listener.Adapter());
+
+ // Block to obtain the Stream.
+ // Alternatively you can use the CompletableFuture APIs to avoid blocking.
+ Stream stream = streamCF.get();
+
+ // The request content, in two chunks.
+ String content1 = "{\"greet\": \"hello world\"}";
+ ByteBuffer buffer1 = StandardCharsets.UTF_8.encode(content1);
+ String content2 = "{\"user\": \"jetty\"}";
+ ByteBuffer buffer2 = StandardCharsets.UTF_8.encode(content2);
+
+ // 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));
+
+ // 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)));
+ // end::newStreamWithData[]
+ }
+
+ public void responseListener() 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::responseListener[]
+ // Open a Stream by sending the HEADERS frame.
+ session.newStream(headersFrame, new Stream.Listener.Adapter()
+ {
+ @Override
+ public void onHeaders(Stream stream, HeadersFrame frame)
+ {
+ MetaData metaData = frame.getMetaData();
+
+ // Is this HEADERS frame the response or the trailers?
+ if (metaData.isResponse())
+ {
+ MetaData.Response response = (MetaData.Response)metaData;
+ System.getLogger("http2").log(INFO, "Received response {0}", response);
+ }
+ else
+ {
+ System.getLogger("http2").log(INFO, "Received trailers {0}", metaData.getFields());
+ }
+ }
+
+ @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::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();
+ 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::reset[]
+ // Open a Stream by sending the HEADERS frame.
+ CompletableFuture streamCF = session.newStream(headersFrame, new Stream.Listener.Adapter()
+ {
+ @Override
+ public void onReset(Stream stream, ResetFrame frame)
+ {
+ // The server reset this stream.
+ }
+ });
+ Stream stream = streamCF.get();
+
+ // Reset this stream (for example, the user closed the application).
+ stream.reset(new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP);
+ // end::reset[]
+ }
+
+ public void push() 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::push[]
+ // Open a Stream by sending the HEADERS frame.
+ CompletableFuture streamCF = session.newStream(headersFrame, new Stream.Listener.Adapter()
+ {
+ @Override
+ public Stream.Listener onPush(Stream pushedStream, PushPromiseFrame frame)
+ {
+ // The "request" the client would make for the pushed resource.
+ MetaData.Request pushedRequest = frame.getMetaData();
+ // The pushed "request" URI.
+ HttpURI pushedURI = pushedRequest.getURI();
+ // The pushed "request" headers.
+ HttpFields pushedRequestHeaders = pushedRequest.getFields();
+
+ // If needed, retrieve the primary stream that triggered the push.
+ Stream primaryStream = pushedStream.getSession().getStream(frame.getStreamId());
+
+ // Return a Stream.Listener to listen for the pushed "response" events.
+ return new Stream.Listener.Adapter()
+ {
+ @Override
+ public void onHeaders(Stream stream, HeadersFrame frame)
+ {
+ // Handle the pushed stream "response".
+
+ MetaData metaData = frame.getMetaData();
+ if (metaData.isResponse())
+ {
+ // The pushed "response" headers.
+ HttpFields pushedResponseHeaders = metaData.getFields();
+ }
+ }
+
+ @Override
+ public void onData(Stream stream, DataFrame frame, Callback callback)
+ {
+ // Handle the pushed stream "response" content.
+
+ // The pushed stream "response" content bytes.
+ ByteBuffer buffer = frame.getData();
+ // Consume the buffer and complete the callback.
+ callback.succeeded();
+ }
+ };
+ }
+ });
+ // end::push[]
+ }
+
+ public void pushReset() 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::pushReset[]
+ // Open a Stream by sending the HEADERS frame.
+ CompletableFuture streamCF = session.newStream(headersFrame, new Stream.Listener.Adapter()
+ {
+ @Override
+ public Stream.Listener onPush(Stream pushedStream, PushPromiseFrame frame)
+ {
+ // Reset the pushed stream to tell the server we are not interested.
+ pushedStream.reset(new ResetFrame(pushedStream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP);
+
+ // Not interested in listening to pushed response events.
+ return null;
+ }
+ });
+ // end::pushReset[]
+ }
+}
diff --git a/jetty-fcgi/fcgi-server/pom.xml b/jetty-fcgi/fcgi-server/pom.xml
index 1ff2f7cd94b..5afbaa581e0 100644
--- a/jetty-fcgi/fcgi-server/pom.xml
+++ b/jetty-fcgi/fcgi-server/pom.xml
@@ -39,6 +39,7 @@
jetty-server${project.version}
+
org.eclipse.jettyjetty-slf4j-impl
@@ -58,7 +59,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 dddbf15e6f6..600d5926451 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 jakarta.servlet.RequestDispatcher;
import jakarta.servlet.ServletConfig;
@@ -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));
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 fd45d8c934f..314932134ff 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
@@ -18,13 +18,13 @@
package org.eclipse.jetty.http2.client;
-import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.channels.SocketChannel;
import java.time.Duration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import org.eclipse.jetty.alpn.client.ALPNClientConnectionFactory;
@@ -339,18 +339,30 @@ public class HTTP2Client extends ContainerLifeCycle
this.useOutputDirectByteBuffers = useOutputDirectByteBuffers;
}
- public void connect(InetSocketAddress address, Session.Listener listener, Promise promise)
+ public CompletableFuture connect(SocketAddress address, Session.Listener listener)
+ {
+ return connect(null, address, listener);
+ }
+
+ public void connect(SocketAddress address, Session.Listener listener, Promise promise)
{
// Prior-knowledge clear-text HTTP/2 (h2c).
connect(null, address, listener, promise);
}
- public void connect(SslContextFactory sslContextFactory, InetSocketAddress address, Session.Listener listener, Promise promise)
+ public CompletableFuture connect(SslContextFactory sslContextFactory, SocketAddress address, Session.Listener listener)
+ {
+ Promise.Completable result = new Promise.Completable<>();
+ connect(sslContextFactory, address, listener, result);
+ return result;
+ }
+
+ public void connect(SslContextFactory sslContextFactory, SocketAddress address, Session.Listener listener, Promise promise)
{
connect(sslContextFactory, address, listener, promise, null);
}
- public void connect(SslContextFactory sslContextFactory, InetSocketAddress address, Session.Listener listener, Promise promise, Map context)
+ public void connect(SslContextFactory sslContextFactory, SocketAddress address, Session.Listener listener, Promise promise, Map context)
{
ClientConnectionFactory factory = newClientConnectionFactory(sslContextFactory);
connect(address, factory, listener, promise, context);
diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/BufferingFlowControlStrategy.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/BufferingFlowControlStrategy.java
index 20db018a96f..941dda4fcba 100644
--- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/BufferingFlowControlStrategy.java
+++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/BufferingFlowControlStrategy.java
@@ -38,7 +38,8 @@ import org.eclipse.jetty.util.annotation.ManagedObject;
* consumed. Only the smaller bucket can refill the bigger bucket.
*
The smaller bucket is defined as a fraction of the bigger bucket.
+ * 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}.
+ *
+ * @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/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-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();
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-osgi/jetty-osgi-boot-jsp/pom.xml b/jetty-osgi/jetty-osgi-boot-jsp/pom.xml
index af5237d6be1..ab3f1f59d71 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)", jakarta.servlet;version="[3.1,4.1)", jakarta.servlet.resources;version="[3.1,4.1)", jakarta.servlet.jsp.resources;version="[2.3,4.1)", jakarta.servlet.jsp;version="[2.3,2.4.1)", jakarta.servlet.jsp.el;version="[2.3,2.4.1)", jakarta.servlet.jsp.tagext;version="[2.3,2.4.1)", jakarta.servlet.jsp.jstl.core;version="1.2";resolution:=optional, jakarta.servlet.jsp.jstl.fmt;version="1.2";resolution:=optional, jakarta.servlet.jsp.jstl.sql;version="1.2";resolution:=optional, jakarta.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)",
+ jakarta.servlet;version="[3.1,4.1)",
+ jakarta.servlet.resources;version="[3.1,4.1)",
+ jakarta.servlet.jsp.resources;version="[2.3,4.1)",
+ jakarta.servlet.jsp;version="[2.3,2.4.1)",
+ jakarta.servlet.jsp.el;version="[2.3,2.4.1)",
+ jakarta.servlet.jsp.tagext;version="[2.3,2.4.1)",
+ jakarta.servlet.jsp.jstl.core;version="1.2";resolution:=optional,
+ jakarta.servlet.jsp.jstl.fmt;version="1.2";resolution:=optional,
+ jakarta.servlet.jsp.jstl.sql;version="1.2";resolution:=optional,
+ jakarta.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 5ecffdb698c..6a73998469b 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:=trueorg.eclipse.jetty.osgi.boot.JettyBootstrapActivatororg.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, jakarta.servlet;version="[3.1,4.1)", jakarta.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, jakarta.servlet;version="[3.1,4.1)", jakarta.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..30e029fc6ce
--- /dev/null
+++ b/jetty-osgi/test-jetty-osgi/README.txt
@@ -0,0 +1,206 @@
+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.
+
+
+
+General Test Diagnostics
+------------------------
+
+There are generally only ever 2 things wrong with the jetty/osgi interworking:
+
+1. you've added or changed an existing jetty-whatever subproject to use the java ServiceLoader, but you haven't put the right entries into the jar's manifest to allow ServiceLoader to work in osgi
+
+2. you've upgraded the jvm version and the version of Aries SpiFly that we use to provide ServiceLoader functionality in osgi does not support parsing java classes compiled with the new version
+
+
+* Diagnosing problem 1:
+
+Can be an obvious failure, because the osgi test that exercises your feature fails. Worst case is that your code substitutes the missing service with some default that isn't your new feature and it's never detected because the test still works. That's a problem with the design of the test, c'est la vie.
+
+Other problems with misconfigured manifests are usually to do with missing or incorrect Import-Package/Export-Package statements. This usually isn't a problem because we mostly automatically generate these using the mvn bnd tool during assembly of the jar, but can become a problem if the manifest has been manually cobbled together in the pom.xml. You'll notice these failures because an osgi test will fail with messages something like the following:
+
+Bundle [id:24, url:mvn:org.eclipse.jetty/jetty-util/10.0.0-SNAPSHOT] is not resolved
+
+To diagnose that further, you can rerun the test, and ask it to spit out a list of the status of every jetty jar that is deployed. To do that, supply the following system property at the command line:
+mvn -Dbundle.debug=true
+
+You'll see several lines of output like the following. All of the bundles should be state 32 (active) or state 4 (resolved):
+
+ACTIVE 32
+RESOLVED 4
+INSTALLED 2
+0 org.eclipse.osgi System Bundle 3.15.100.v20191114-1701 32
+1 org.ops4j.pax.exam file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.ops4j.pax.exam_4.13.1.jar 4.13.1 32
+2 org.ops4j.pax.exam.inject file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.ops4j.pax.exam.inject_4.13.1.jar 4.13.1 32
+3 org.ops4j.pax.exam.extender.service file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.ops4j.pax.exam.extender.service_4.13.1.jar 4.13.1 32
+4 osgi.cmpn file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/osgi.cmpn_4.3.1.201210102024.jar 4.3.1.201210102024 32
+5 org.ops4j.pax.logging.pax-logging-api file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.ops4j.pax.logging.pax-logging-api_1.10.1.jar 1.10.1 32
+6 org.ops4j.base file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.ops4j.base_1.5.0.jar 1.5.0 32
+7 org.ops4j.pax.swissbox.core file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.ops4j.pax.swissbox.core_1.8.2.jar 1.8.2 32
+8 org.ops4j.pax.swissbox.extender file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.ops4j.pax.swissbox.extender_1.8.2.jar 1.8.2 32
+9 org.ops4j.pax.swissbox.framework file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.ops4j.pax.swissbox.framework_1.8.2.jar 1.8.2 32
+10 org.ops4j.pax.swissbox.lifecycle file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.ops4j.pax.swissbox.lifecycle_1.8.2.jar 1.8.2 32
+11 org.ops4j.pax.swissbox.tracker file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.ops4j.pax.swissbox.tracker_1.8.2.jar 1.8.2 32
+12 org.apache.geronimo.specs.geronimo-atinject_1.0_spec file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.apache.geronimo.specs.geronimo-atinject_1.0_spec_1.0.jar 1.0.0 32
+13 org.ops4j.pax.tipi.junit file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.ops4j.pax.tipi.junit_4.12.0.1.jar 4.12.0.1 32
+14 org.ops4j.pax.tipi.hamcrest.core file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.ops4j.pax.tipi.hamcrest.core_1.3.0.1.jar 1.3.0.1 32
+15 org.ops4j.pax.exam.invoker.junit file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.ops4j.pax.exam.invoker.junit_4.13.1.jar 4.13.1 32
+16 org.eclipse.jetty.servlet-api file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.servlet-api_4.0.3.jar 4.0.3 32
+17 org.objectweb.asm file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.objectweb.asm_7.2.0.jar 7.2.0 32
+18 org.objectweb.asm.commons file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.objectweb.asm.commons_7.2.0.jar 7.2.0 32
+19 org.objectweb.asm.tree file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.objectweb.asm.tree_7.2.0.jar 7.2.0 32
+20 org.apache.aries.spifly.dynamic.bundle file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.apache.aries.spifly.dynamic.bundle_1.2.3.jar 1.2.3 32
+21 jakarta.annotation-api file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/jakarta.annotation-api_1.3.4.jar 1.3.4 32
+22 org.apache.geronimo.specs.geronimo-jta_1.1_spec file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.apache.geronimo.specs.geronimo-jta_1.1_spec_1.1.1.jar 1.1.1 32
+23 slf4j.api file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/slf4j.api_2.0.0.alpha1.jar 2.0.0.alpha1 4
+24 slf4j.log4j12 file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/slf4j.log4j12_2.0.0.alpha1.jar 2.0.0.alpha1 4
+25 org.eclipse.jetty.util file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.util_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 32
+26 org.eclipse.jetty.deploy file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.deploy_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 32
+27 org.eclipse.jetty.server file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.server_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 32
+28 org.eclipse.jetty.servlet file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.servlet_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 32
+29 org.eclipse.jetty.http file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.http_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 32
+30 org.eclipse.jetty.xml file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.xml_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 32
+31 org.eclipse.jetty.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.webapp_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 32
+32 org.eclipse.jetty.io file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.io_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 32
+33 org.eclipse.jetty.security file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.security_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 32
+34 org.eclipse.jetty.servlets file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.servlets_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 32
+35 org.eclipse.jetty.client file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.client_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 32
+36 org.eclipse.jetty.jndi file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.jndi_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 32
+37 org.eclipse.jetty.plus file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.plus_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 32
+38 org.eclipse.jetty.annotations file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.annotations_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 32
+39 org.eclipse.jetty.websocket.core file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.websocket.core_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 32
+40 org.eclipse.jetty.websocket.servlet file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.websocket.servlet_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 32
+41 org.eclipse.jetty.websocket.util file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.websocket.util_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 32
+42 org.eclipse.jetty.websocket.api file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.websocket.api_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 32
+43 org.eclipse.jetty.websocket.server file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.websocket.server_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 32
+44 org.eclipse.jetty.websocket.client file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.websocket.client_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 32
+45 org.eclipse.jetty.websocket.common file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.websocket.common_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 32
+46 org.eclipse.jetty.websocket-api file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.websocket-api_1.1.2.jar 1.1.2 4
+47 org.eclipse.jetty.websocket.javax.server file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.websocket.javax.server_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 4
+48 org.eclipse.jetty.websocket.javax.client file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.websocket.javax.client_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 4
+49 org.eclipse.jetty.websocket.javax.common file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.websocket.javax.common_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 4
+50 org.eclipse.jetty.osgi.boot file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.osgi.boot_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 32
+51 org.eclipse.jetty.alpn.java.client file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.alpn.java.client_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 32
+52 org.eclipse.jetty.alpn.client file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.alpn.client_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 32
+53 javax.servlet.jsp.jstl file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/javax.servlet.jsp.jstl_1.2.0.v201105211821.jar 1.2.0.v201105211821 32
+54 org.mortbay.jasper.apache-el file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.mortbay.jasper.apache-el_9.0.29.jar 9.0.29 32
+55 org.mortbay.jasper.apache-jsp file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.mortbay.jasper.apache-jsp_9.0.29.jar 9.0.29 32
+56 org.eclipse.jetty.apache-jsp file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.apache-jsp_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 32
+57 org.glassfish.web.javax.servlet.jsp.jstl file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.glassfish.web.javax.servlet.jsp.jstl_1.2.2.jar 1.2.2 32
+58 org.eclipse.jdt.core.compiler.batch file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jdt.core.compiler.batch_3.19.0.v20190903-0936.jar 3.19.0.v20190903-0936 32
+59 org.eclipse.jetty.osgi.boot.jsp file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.osgi.boot.jsp_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 4
+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)
+
+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.
+
+
+
+* Diagnosing Problem 2
+
+If you've upgraded the jetty jar and that's all you've changed, then more than likely it's a SpiFly versioning problem. SpiFly internally uses asm to parse classes to find services for the ServiceLoader, so it can be that the version of asm used by SpiFly doesn't support the higher jvm version. At the time of writing SpiFly is shipping an older version of asm baked into their jars, so very likely it won't support anything above jdk13.
+
+Also, if you don't see any test failures with unresolved jar messages, then it's also a good indicator that the problem is with SpiFly versioning. Unfortunately, when the problem is a jvm/SpiFly versioning mismatch, the osgi paxexam environment doesn't output any good log messages. There is a java.lang.Error that is thrown from inside asm that the environment doesn't pass on in any useful fashion.
+
+To try and catch this error, you can modify the ServletInstanceWrapper at line 163 to catch Throwable instead of Exception:
+https://github.com/eclipse/jetty.project/blob/jetty-10.0.x/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/ServerInstanceWrapper.java#L163
+
+When you do this, you get output like the following:
+
+java.lang.ClassFormatError: Unexpected error from weaving hook.
+at org.eclipse.osgi.internal.weaving.WeavingHookConfigurator.processClass(WeavingHookConfigurator.java:77)
+at org.eclipse.osgi.internal.loader.classpath.ClasspathManager.processClass(ClasspathManager.java:736)
+at org.eclipse.osgi.internal.loader.classpath.ClasspathManager.defineClass(ClasspathManager.java:707)
+at org.eclipse.osgi.internal.loader.classpath.ClasspathManager.findClassImpl(ClasspathManager.java:640)
+at org.eclipse.osgi.internal.loader.classpath.ClasspathManager.findLocalClassImpl(ClasspathManager.java:608)
+at org.eclipse.osgi.internal.loader.classpath.ClasspathManager.findLocalClassImpl(ClasspathManager.java:588)
+at org.eclipse.osgi.internal.loader.classpath.ClasspathManager.findLocalClass(ClasspathManager.java:567)
+at org.eclipse.osgi.internal.loader.ModuleClassLoader.findLocalClass(ModuleClassLoader.java:346)
+at org.eclipse.osgi.internal.loader.BundleLoader.findLocalClass(BundleLoader.java:398)
+at org.eclipse.osgi.internal.loader.sources.SingleSourcePackage.loadClass(SingleSourcePackage.java:41)
+at org.eclipse.osgi.internal.loader.BundleLoader.findClassInternal(BundleLoader.java:460)
+at org.eclipse.osgi.internal.loader.BundleLoader.findClass(BundleLoader.java:425)
+at org.eclipse.osgi.internal.loader.BundleLoader.findClass(BundleLoader.java:417)
+at org.eclipse.osgi.internal.loader.ModuleClassLoader.loadClass(ModuleClassLoader.java:171)
+at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522)
+at org.eclipse.jetty.osgi.boot.internal.serverfactory.ServerInstanceWrapper.configure(ServerInstanceWrapper.java:143)
+at org.eclipse.jetty.osgi.boot.internal.serverfactory.DefaultJettyAtJettyHomeHelper.startJettyAtJettyHome(DefaultJettyAtJettyHomeHelper.java:211)
+at org.eclipse.jetty.osgi.boot.JettyBootstrapActivator.start(JettyBootstrapActivator.java:98)
+at org.eclipse.osgi.internal.framework.BundleContextImpl$3.run(BundleContextImpl.java:842)
+at org.eclipse.osgi.internal.framework.BundleContextImpl$3.run(BundleContextImpl.java:1)
+at java.base/java.security.AccessController.doPrivileged(AccessController.java:554)
+at org.eclipse.osgi.internal.framework.BundleContextImpl.startActivator(BundleContextImpl.java:834)
+at org.eclipse.osgi.internal.framework.BundleContextImpl.start(BundleContextImpl.java:791)
+at org.eclipse.osgi.internal.framework.EquinoxBundle.startWorker0(EquinoxBundle.java:1015)
+at org.eclipse.osgi.internal.framework.EquinoxBundle$EquinoxModule.startWorker(EquinoxBundle.java:365)
+at org.eclipse.osgi.container.Module.doStart(Module.java:603)
+at org.eclipse.osgi.container.Module.start(Module.java:467)
+at org.eclipse.osgi.container.ModuleContainer$ContainerStartLevel$2.run(ModuleContainer.java:1844)
+at org.eclipse.osgi.internal.framework.EquinoxContainerAdaptor$1$1.execute(EquinoxContainerAdaptor.java:136)
+at org.eclipse.osgi.container.ModuleContainer$ContainerStartLevel.incStartLevel(ModuleContainer.java:1837)
+at org.eclipse.osgi.container.ModuleContainer$ContainerStartLevel.incStartLevel(ModuleContainer.java:1780)
+at org.eclipse.osgi.container.ModuleContainer$ContainerStartLevel.doContainerStartLevel(ModuleContainer.java:1742)
+at org.eclipse.osgi.container.ModuleContainer$ContainerStartLevel.dispatchEvent(ModuleContainer.java:1664)
+at org.eclipse.osgi.container.ModuleContainer$ContainerStartLevel.dispatchEvent(ModuleContainer.java:1)
+at org.eclipse.osgi.framework.eventmgr.EventManager.dispatchEvent(EventManager.java:234)
+at org.eclipse.osgi.framework.eventmgr.EventManager$EventThread.run(EventManager.java:345)
+Caused by: java.lang.IllegalArgumentException: Unsupported class file major version 58
+at org.objectweb.asm.ClassReader.(ClassReader.java:195)
+at org.objectweb.asm.ClassReader.(ClassReader.java:176)
+at org.objectweb.asm.ClassReader.(ClassReader.java:162)
+at org.objectweb.asm.ClassReader.(ClassReader.java:283)
+at org.apache.aries.spifly.dynamic.OSGiFriendlyClassWriter.getCommonSuperClass(OSGiFriendlyClassWriter.java:81)
+at org.objectweb.asm.SymbolTable.addMergedType(SymbolTable.java:1200)
+at org.objectweb.asm.Frame.merge(Frame.java:1299)
+at org.objectweb.asm.Frame.merge(Frame.java:1207)
+at org.objectweb.asm.MethodWriter.computeAllFrames(MethodWriter.java:1607)
+at org.objectweb.asm.MethodWriter.visitMaxs(MethodWriter.java:1543)
+at org.objectweb.asm.MethodVisitor.visitMaxs(MethodVisitor.java:762)
+at org.objectweb.asm.commons.LocalVariablesSorter.visitMaxs(LocalVariablesSorter.java:147)
+at org.objectweb.asm.ClassReader.readCode(ClassReader.java:2431)
+at org.objectweb.asm.ClassReader.readMethod(ClassReader.java:1283)
+at org.objectweb.asm.ClassReader.accept(ClassReader.java:688)
+at org.objectweb.asm.ClassReader.accept(ClassReader.java:400)
+at org.apache.aries.spifly.dynamic.ClientWeavingHook.weave(ClientWeavingHook.java:60)
+at org.eclipse.osgi.internal.weaving.WovenClassImpl.call(WovenClassImpl.java:175)
+at org.eclipse.osgi.internal.serviceregistry.ServiceRegistry.notifyHookPrivileged(ServiceRegistry.java:1343)
+at org.eclipse.osgi.internal.serviceregistry.ServiceRegistry.notifyHooksPrivileged(ServiceRegistry.java:1323)
+at org.eclipse.osgi.internal.weaving.WovenClassImpl.callHooks(WovenClassImpl.java:270)
+at org.eclipse.osgi.internal.weaving.WeavingHookConfigurator.processClass(WeavingHookConfigurator.java:71)
+... 35 more
diff --git a/jetty-osgi/test-jetty-osgi/pom.xml b/jetty-osgi/test-jetty-osgi/pom.xml
index ea5f0a7803d..e7f010608dd 100644
--- a/jetty-osgi/test-jetty-osgi/pom.xml
+++ b/jetty-osgi/test-jetty-osgi/pom.xml
@@ -72,18 +72,31 @@
org.ops4j.pax.tinybundlestinybundles
- 2.1.1
+ 3.0.0
+ test
+
+
+ biz.aQute.bnd
+ bnd
+
+ org.ops4j.pax.urlpax-url-wrap${pax.url.version}test
+
+
+ biz.aQute.bnd
+ bndlib
+
+ biz.aQute.bnd
- bndlib
- 2.4.0
+ biz.aQute.bndlib
+ 5.0.0org.osgi
@@ -105,6 +118,12 @@
+
+ org.eclipse.jetty
+ jetty-slf4j-impl
+ ${project.version}
+ test
+ org.eclipse.jetty.osgijetty-osgi-boot
@@ -417,18 +436,6 @@
jetty-test-helpertest
-
- org.slf4j
- slf4j-api
- ${slf4j.version}
- test
-
-
- org.slf4j
- slf4j-log4j12
- ${slf4j.version}
- test
- org.ow2.asmasm
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 65c9940c34f..9b3174256fa 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