Merge branch 'jetty-10.0.x' into jetty-11.0.x

This commit is contained in:
olivier lamy 2020-04-07 11:39:31 +10:00
commit 3483e0c2f7
71 changed files with 2892 additions and 594 deletions

View File

@ -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);
}
}

View File

@ -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<String> protocols)
{
super(List.of("alpn"), new ALPNClientConnectionFactory(executor, factory, protocols));
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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());
}
}

View File

@ -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<String> 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<String> 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<String, Object> 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<String> 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<String> 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<Info> findClientConnectionFactoryInfo(List<String> protocols)
private Optional<Info> findClientConnectionFactoryInfo(List<String> protocols, boolean secure)
{
return factoryInfos.stream()
.filter(info -> info.matches(protocols))
.filter(info -> info.matches(protocols, secure))
.findFirst();
}
}

View File

@ -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());
/**
* <p>Representation of the {@code HTTP/1.1} application protocol used by {@link HttpClientTransportDynamic}.</p>
*/
public static final Info HTTP11 = new HTTP11(new HttpClientConnectionFactory());
@Override
public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> 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<String> protocols = List.of("http/1.1");
private HTTP11(ClientConnectionFactory factory)
{
super(factory);
}
@Override
public List<String> getProtocols(boolean secure)
{
return protocols;
}
@Override
public String toString()
{
return String.format("%s@%x%s", getClass().getSimpleName(), hashCode(), protocols);
}
}
}

View File

@ -49,14 +49,23 @@
</properties>
<dependencies>
<dependency>
<groupId>org.eclipse.jetty.toolchain</groupId>
<artifactId>jetty-servlet-api</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-client</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-io</artifactId>
<groupId>org.eclipse.jetty.http2</groupId>
<artifactId>http2-http-client-transport</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.fcgi</groupId>
<artifactId>fcgi-client</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>

View File

@ -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}"]
----
<Configure class="org.eclipse.jetty.webapp.WebAppContext">
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<!-- Max Form Size -->
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<Set name="maxFormContentSize">200000</Set>
<Set name="maxFormContentSize">400000</Set>
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<!-- Max Form Keys -->
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<Set name="maxFormKeys">400</Set>
</Configure>
----
==== 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}"]
----
<Configure class="org.eclipse.jetty.server.Server">
<Call name="setAttribute">
<Arg>org.eclipse.jetty.server.Request.maxFormContentSize</Arg>
<Arg>200000</Arg>
</Call>
</Configure>
----
____
[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
----

View File

@ -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]).

View File

@ -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

View File

@ -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[]

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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<HttpCookie> 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<HttpCookie> 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.

View File

@ -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}"]
----
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-client</artifactId>
<version>{version}</version>
</dependency>
----
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.

View File

@ -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.

View File

@ -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.

View File

@ -16,7 +16,7 @@
// ========================================================================
//
[[client-http]]
[[eg-client-http]]
=== HTTP Client
include::client-http-intro.adoc[]

View File

@ -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}"]
----
<dependency>
<groupId>org.eclipse.jetty.http2</groupId>
<artifactId>http2-client</artifactId>
<version>{version}</version>
</dependency>
----
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]
----

View File

@ -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}"]
----

View File

@ -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}"]
----

View File

@ -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<HttpCookie> 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<HttpCookie> 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[]
}
}

View File

@ -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<Session> 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<Session> 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<Integer, Integer> onPreface(Session session)
{
Map<Integer, Integer> 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<Session> 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<Session> 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<Stream> 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<Void> 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<Session> 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<Session> 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<Chunk> 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<Session> 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<Stream> 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<Session> 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<Stream> 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<Session> 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<Stream> 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[]
}
}

View File

@ -39,6 +39,7 @@
<artifactId>jetty-server</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-slf4j-impl</artifactId>
@ -58,7 +59,7 @@
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-alpn-server</artifactId>
<artifactId>jetty-alpn-java-server</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>

View File

@ -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.
* <p>
* This servlet accepts two additional init-params:
* This servlet accepts these additional {@code init-param}s:
* <ul>
* <li>{@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;
* </ul></li>
* <li>{@code fastCGI.HTTPS}, optional, defaults to false, that specifies whether
* to force the FastCGI {@code HTTPS} parameter to the value {@code on}</li>
* <li>{@code fastCGI.envNames}, optional, a comma separated list of environment variable
* names read via {@link System#getenv(String)} that are forwarded as FastCGI parameters.</li>
* </ul>
*
* @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<String> 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));

View File

@ -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<Session> promise)
public CompletableFuture<Session> connect(SocketAddress address, Session.Listener listener)
{
return connect(null, address, listener);
}
public void connect(SocketAddress address, Session.Listener listener, Promise<Session> promise)
{
// Prior-knowledge clear-text HTTP/2 (h2c).
connect(null, address, listener, promise);
}
public void connect(SslContextFactory sslContextFactory, InetSocketAddress address, Session.Listener listener, Promise<Session> promise)
public CompletableFuture<Session> connect(SslContextFactory sslContextFactory, SocketAddress address, Session.Listener listener)
{
Promise.Completable<Session> result = new Promise.Completable<>();
connect(sslContextFactory, address, listener, result);
return result;
}
public void connect(SslContextFactory sslContextFactory, SocketAddress address, Session.Listener listener, Promise<Session> promise)
{
connect(sslContextFactory, address, listener, promise, null);
}
public void connect(SslContextFactory sslContextFactory, InetSocketAddress address, Session.Listener listener, Promise<Session> promise, Map<String, Object> context)
public void connect(SslContextFactory sslContextFactory, SocketAddress address, Session.Listener listener, Promise<Session> promise, Map<String, Object> context)
{
ClientConnectionFactory factory = newClientConnectionFactory(sslContextFactory);
connect(address, factory, listener, promise, context);

View File

@ -38,7 +38,8 @@ import org.eclipse.jetty.util.annotation.ManagedObject;
* consumed. Only the smaller bucket can refill the bigger bucket.</p>
* <p>The smaller bucket is defined as a fraction of the bigger bucket.</p>
* <p>For a more visual representation, see the
* <a href="http://en.wikipedia.org/wiki/Shishi-odoshi">rocking bamboo fountain</a>.</p>
* <a href="http://en.wikipedia.org/wiki/Shishi-odoshi">rocking bamboo fountain</a>,
* where the bamboo is the smaller bucket and the pool is the bigger bucket.</p>
* <p>The algorithm works in this way.</p>
* <p>The initial bigger bucket (BB) capacity is 100, and let's imagine the smaller
* bucket (SB) being 40% of the bigger bucket: 40.</p>
@ -50,6 +51,16 @@ import org.eclipse.jetty.util.annotation.ManagedObject;
* with delta=45.</p>
* <p>The application consumes the remaining 15, so now SB=15, and no window
* control frame is emitted.</p>
* <p>The {@code bufferRatio} controls how often the window control frame is
* emitted.</p>
* <p>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.</p>
* <p>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.</p>
* <p>The default value is {@code bufferRatio=0.5}.</p>
*/
@ManagedObject
public class BufferingFlowControlStrategy extends AbstractFlowControlStrategy

View File

@ -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();

View File

@ -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
{
/**
* <p>Sends the given HEADERS {@code frame} to create a new {@link Stream}.</p>
*
* @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<Stream> newStream(HeadersFrame frame, Stream.Listener listener)
{
Promise.Completable<Stream> result = new Promise.Completable<>();
newStream(frame, result, listener);
return result;
}
/**
* <p>Sends the given HEADERS {@code frame} to create a new {@link Stream}.</p>
*

View File

@ -18,6 +18,8 @@
package org.eclipse.jetty.http2.api;
import java.util.concurrent.CompletableFuture;
import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.http2.frames.PushPromiseFrame;
@ -67,6 +69,19 @@ public interface Stream
*/
public void push(PushPromiseFrame frame, Promise<Stream> promise, Listener listener);
/**
* <p>Sends the given DATA {@code frame}.</p>
*
* @param frame the DATA frame to send
* @return the CompletableFuture that gets notified when the frame has been sent
*/
public default CompletableFuture<Void> data(DataFrame frame)
{
Callback.Completable result = new Callback.Completable();
data(frame, result);
return result;
}
/**
* <p>Sends the given DATA {@code frame}.</p>
*
@ -174,7 +189,7 @@ public interface Stream
/**
* <p>Callback method invoked when a PUSH_PROMISE frame has been received.</p>
*
* @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
*/

View File

@ -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
/**
* <p>Representation of the {@code HTTP/2} application protocol used by {@link HttpClientTransportDynamic}.</p>
*
* @see HttpClientConnectionFactory#HTTP11
*/
public static class HTTP2 extends Info
{
public H2(HTTP2Client client)
{
super(List.of("h2"), new ClientConnectionFactoryOverHTTP2(client));
}
}
private static final List<String> protocols = List.of("h2", "h2c");
private static final List<String> 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<String> 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);
}
}
}

View File

@ -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();

View File

@ -66,26 +66,21 @@ public interface ClientConnectionFactory
}
/**
* <p>A holder for a list of protocol strings identifying a network protocol
* <p>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.</p>
*/
public static class Info extends ContainerLifeCycle
public abstract static class Info extends ContainerLifeCycle
{
private final List<String> protocols;
private final ClientConnectionFactory factory;
public Info(List<String> protocols, ClientConnectionFactory factory)
public Info(ClientConnectionFactory factory)
{
this.protocols = protocols;
this.factory = factory;
addBean(factory);
}
public List<String> getProtocols()
{
return protocols;
}
public abstract List<String> 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<String> candidates)
public boolean matches(List<String> 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<String, Object> 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);
}
}
}

View File

@ -56,9 +56,77 @@
<Bundle-Classpath />
<Fragment-Host>org.eclipse.jetty.osgi.boot</Fragment-Host>
<Export-Package>!org.eclipse.jetty.osgi.boot.*</Export-Package>
<Import-Package>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
<Import-Package>
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
</Import-Package>
<DynamicImport-Package>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"</DynamicImport-Package>
<DynamicImport-Package>
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"
</DynamicImport-Package>
</instructions>
</configuration>
</plugin>

View File

@ -71,7 +71,7 @@
<Bundle-SymbolicName>org.eclipse.jetty.osgi.boot;singleton:=true</Bundle-SymbolicName>
<Bundle-Activator>org.eclipse.jetty.osgi.boot.JettyBootstrapActivator</Bundle-Activator>
<DynamicImport-Package>org.eclipse.jetty.*;version="[$(version;===;${parsedVersion.osgiVersion}),$(version;==+;${parsedVersion.osgiVersion}))"</DynamicImport-Package>
<Import-Package>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, *
<Import-Package>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, *
</Import-Package>
<Require-Capability>
osgi.extender; filter:="(osgi.extender=osgi.serviceloader.registrar)"

View File

@ -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

View File

@ -72,18 +72,31 @@
<dependency>
<groupId>org.ops4j.pax.tinybundles</groupId>
<artifactId>tinybundles</artifactId>
<version>2.1.1</version>
<version>3.0.0</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>biz.aQute.bnd</groupId>
<artifactId>bnd</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.ops4j.pax.url</groupId>
<artifactId>pax-url-wrap</artifactId>
<version>${pax.url.version}</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>biz.aQute.bnd</groupId>
<artifactId>bndlib</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>biz.aQute.bnd</groupId>
<artifactId>bndlib</artifactId>
<version>2.4.0</version>
<artifactId>biz.aQute.bndlib</artifactId>
<version>5.0.0</version>
<exclusions>
<exclusion>
<groupId>org.osgi</groupId>
@ -105,6 +118,12 @@
</dependency>
<!-- Jetty OSGi Deps -->
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-slf4j-impl</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.osgi</groupId>
<artifactId>jetty-osgi-boot</artifactId>
@ -417,18 +436,6 @@
<artifactId>jetty-test-helper</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>

View File

@ -1 +0,0 @@
org.eclipse.jetty.LEVEL=INFO

View File

@ -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<Option> options = new ArrayList<>();
options.addAll(TestOSGiUtil.configurePaxExamLogging());
options.add(CoreOptions.junitBundles());
options.addAll(TestOSGiUtil.configureJettyHomeAndPort(false, "jetty-http-boot-context-as-service.xml"));
options.add(CoreOptions.bootDelegationPackages("org.xml.sax", "org.xml.*", "org.w3c.*", "javax.xml.*"));
@ -69,8 +70,7 @@ public class TestJettyOSGiBootContextAsService
// a bundle that registers a webapp as a service for the jetty osgi core to pick up and deploy
options.add(mavenBundle().groupId("org.eclipse.jetty.osgi").artifactId("test-jetty-osgi-context").versionAsInProject().start());
options.add(systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value(LOG_LEVEL));
options.add(systemProperty("org.eclipse.jetty.LEVEL").value(LOG_LEVEL));
options.add(systemProperty("org.ops4j.pax.url.mvn.localRepository").value(System.getProperty("mavenRepoPath")));
return options.toArray(new Option[0]);

View File

@ -50,15 +50,12 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
import static org.ops4j.pax.exam.CoreOptions.systemProperty;
import static org.ops4j.pax.exam.CoreOptions.wrappedBundle;
@RunWith(PaxExam.class)
@ExamReactorStrategy(PerClass.class)
public class TestJettyOSGiBootHTTP2Conscrypt
{
private static final String LOG_LEVEL = "WARN";
@Inject
private BundleContext bundleContext;
@ -66,6 +63,9 @@ public class TestJettyOSGiBootHTTP2Conscrypt
public Option[] config()
{
ArrayList<Option> options = new ArrayList<>();
options.addAll(TestOSGiUtil.configurePaxExamLogging());
options.add(CoreOptions.junitBundles());
options.addAll(TestOSGiUtil.configureJettyHomeAndPort(true, "jetty-http2.xml"));
options.add(CoreOptions.bootDelegationPackages("org.xml.sax", "org.xml.*", "org.w3c.*", "javax.xml.*", "javax.activation.*"));
@ -84,8 +84,6 @@ public class TestJettyOSGiBootHTTP2Conscrypt
options.add(mavenBundle().groupId("org.eclipse.jetty.http2").artifactId("http2-client").versionAsInProject().start());
options.add(mavenBundle().groupId("org.eclipse.jetty.http2").artifactId("http2-http-client-transport").versionAsInProject().start());
options.add(systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value(LOG_LEVEL));
options.add(systemProperty("org.eclipse.jetty.LEVEL").value(LOG_LEVEL));
options.add(CoreOptions.cleanCaches(true));
return options.toArray(new Option[0]);
}

View File

@ -49,14 +49,11 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
import static org.ops4j.pax.exam.CoreOptions.systemProperty;
@RunWith(PaxExam.class)
@ExamReactorStrategy(PerClass.class)
public class TestJettyOSGiBootHTTP2JDK9
{
private static final String LOG_LEVEL = "WARN";
@Inject
private BundleContext bundleContext;
@ -64,6 +61,9 @@ public class TestJettyOSGiBootHTTP2JDK9
public Option[] config()
{
ArrayList<Option> options = new ArrayList<>();
options.addAll(TestOSGiUtil.configurePaxExamLogging());
options.add(CoreOptions.junitBundles());
options.addAll(TestOSGiUtil.configureJettyHomeAndPort(true, "jetty-http2-jdk9.xml"));
options.add(CoreOptions.bootDelegationPackages("org.xml.sax", "org.xml.*", "org.w3c.*", "javax.xml.*", "javax.activation.*"));
@ -81,8 +81,6 @@ public class TestJettyOSGiBootHTTP2JDK9
options.add(mavenBundle().groupId("org.eclipse.jetty.http2").artifactId("http2-client").versionAsInProject().start());
options.add(mavenBundle().groupId("org.eclipse.jetty.http2").artifactId("http2-http-client-transport").versionAsInProject().start());
options.add(systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value(LOG_LEVEL));
options.add(systemProperty("org.eclipse.jetty.LEVEL").value(LOG_LEVEL));
options.add(CoreOptions.cleanCaches(true));
return options.toArray(new Option[0]);
}

View File

@ -38,7 +38,6 @@ import org.osgi.framework.ServiceReference;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
import static org.ops4j.pax.exam.CoreOptions.systemProperty;
/**
* TestJettyOSGiBootWebAppAsService
@ -54,8 +53,6 @@ import static org.ops4j.pax.exam.CoreOptions.systemProperty;
@RunWith(PaxExam.class)
public class TestJettyOSGiBootWebAppAsService
{
private static final String LOG_LEVEL = "WARN";
@Inject
BundleContext bundleContext = null;
@ -63,6 +60,9 @@ public class TestJettyOSGiBootWebAppAsService
public static Option[] configure()
{
ArrayList<Option> options = new ArrayList<>();
options.addAll(TestOSGiUtil.configurePaxExamLogging());
options.add(TestOSGiUtil.optionalRemoteDebug());
options.add(CoreOptions.junitBundles());
options.addAll(TestOSGiUtil.configureJettyHomeAndPort(false, "jetty-http-boot-webapp-as-service.xml"));
@ -74,8 +74,6 @@ public class TestJettyOSGiBootWebAppAsService
options.addAll(TestOSGiUtil.coreJettyDependencies());
options.add(mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-alpn-java-client").versionAsInProject().start());
options.add(mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-alpn-client").versionAsInProject().start());
options.add(systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value(LOG_LEVEL));
options.add(systemProperty("org.eclipse.jetty.LEVEL").value(LOG_LEVEL));
options.addAll(TestOSGiUtil.jspDependencies());
options.addAll(testDependencies());

View File

@ -39,7 +39,6 @@ import org.osgi.framework.BundleContext;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
import static org.ops4j.pax.exam.CoreOptions.systemProperty;
/**
* Pax-Exam to make sure the jetty-osgi-boot can be started along with the
@ -49,8 +48,6 @@ import static org.ops4j.pax.exam.CoreOptions.systemProperty;
@RunWith(PaxExam.class)
public class TestJettyOSGiBootWithAnnotations
{
private static final String LOG_LEVEL = "WARN";
@Inject
BundleContext bundleContext = null;
@ -58,6 +55,9 @@ public class TestJettyOSGiBootWithAnnotations
public static Option[] configure()
{
ArrayList<Option> options = new ArrayList<>();
options.addAll(TestOSGiUtil.configurePaxExamLogging());
options.add(TestOSGiUtil.optionalRemoteDebug());
options.add(CoreOptions.junitBundles());
options.addAll(TestOSGiUtil.configureJettyHomeAndPort(false, "jetty-http-boot-with-annotations.xml"));
@ -67,12 +67,9 @@ public class TestJettyOSGiBootWithAnnotations
"com.sun.org.apache.xpath.internal.jaxp", "com.sun.org.apache.xpath.internal.objects"));
options.addAll(TestOSGiUtil.coreJettyDependencies());
// TODO uncomment and update the following once 9.4.19 is released with a fix for #3726
// options.add(mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-util").version("9.4.19.v????????").noStart());
options.add(mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-alpn-java-client").versionAsInProject().start());
options.add(mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-alpn-client").versionAsInProject().start());
options.add(systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value(LOG_LEVEL));
options.add(systemProperty("org.eclipse.jetty.LEVEL").value(LOG_LEVEL));
options.addAll(jspDependencies());
options.addAll(annotationDependencies());
options.add(mavenBundle().groupId("org.eclipse.jetty.osgi").artifactId("test-jetty-osgi-fragment").versionAsInProject().noStart());

View File

@ -22,7 +22,6 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import jakarta.inject.Inject;
@ -45,7 +44,6 @@ import org.osgi.framework.Constants;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
import static org.ops4j.pax.exam.CoreOptions.options;
import static org.ops4j.pax.exam.CoreOptions.systemProperty;
/**
@ -59,7 +57,6 @@ import static org.ops4j.pax.exam.CoreOptions.systemProperty;
public class TestJettyOSGiBootWithBundle
{
private static final String TEST_JETTY_HOME_BUNDLE = "test-jetty-xml-bundle";
private static final String LOG_LEVEL = "WARN";
@Inject
BundleContext bundleContext = null;
@ -68,6 +65,9 @@ public class TestJettyOSGiBootWithBundle
public static Option[] configure() throws IOException
{
ArrayList<Option> options = new ArrayList<>();
options.addAll(TestOSGiUtil.configurePaxExamLogging());
options.add(CoreOptions.junitBundles());
options.addAll(configureJettyHomeAndPort());
options.add(CoreOptions.bootDelegationPackages("org.xml.sax", "org.xml.*", "org.w3c.*", "javax.xml.*"));
@ -75,9 +75,6 @@ public class TestJettyOSGiBootWithBundle
options.add(mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-alpn-java-client").versionAsInProject().start());
options.add(mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-alpn-client").versionAsInProject().start());
options.addAll(Arrays.asList(options(systemProperty("pax.exam.logging").value("none"))));
options.addAll(Arrays.asList(options(systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value(LOG_LEVEL))));
options.addAll(Arrays.asList(options(systemProperty("org.eclipse.jetty.LEVEL").value(LOG_LEVEL))));
TinyBundle bundle = TinyBundles.bundle();
bundle.add(SomeCustomBean.class);
bundle.set(Constants.BUNDLE_SYMBOLICNAME, TEST_JETTY_HOME_BUNDLE);

View File

@ -41,7 +41,6 @@ import org.osgi.framework.BundleException;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
import static org.ops4j.pax.exam.CoreOptions.systemProperty;
/**
* Test using websocket in osgi
@ -49,8 +48,6 @@ import static org.ops4j.pax.exam.CoreOptions.systemProperty;
@RunWith(PaxExam.class)
public class TestJettyOSGiBootWithJakartaWebSocket
{
private static final String LOG_LEVEL = "WARN";
@Inject
BundleContext bundleContext = null;
@ -58,6 +55,9 @@ public class TestJettyOSGiBootWithJakartaWebSocket
public static Option[] configure()
{
ArrayList<Option> options = new ArrayList<>();
options.addAll(TestOSGiUtil.configurePaxExamLogging());
// options.add(TestOSGiUtil.optionalRemoteDebug());
options.add(CoreOptions.junitBundles());
options.addAll(TestOSGiUtil.configureJettyHomeAndPort(false, "jetty-http-boot-with-jakarta-websocket.xml"));
@ -69,8 +69,6 @@ public class TestJettyOSGiBootWithJakartaWebSocket
options.addAll(TestOSGiUtil.coreJettyDependencies());
options.add(mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-alpn-java-client").versionAsInProject().start());
options.add(mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-alpn-client").versionAsInProject().start());
options.add(systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value(LOG_LEVEL));
options.add(systemProperty("org.eclipse.jetty.LEVEL").value(LOG_LEVEL));
options.addAll(jspDependencies());
options.addAll(testJettyWebApp());
options.addAll(extraDependencies());
@ -93,8 +91,8 @@ public class TestJettyOSGiBootWithJakartaWebSocket
public static List<Option> extraDependencies()
{
List<Option> res = new ArrayList<>();
res.add(mavenBundle().groupId("biz.aQute.bnd").artifactId("bndlib").versionAsInProject().start());
res.add(mavenBundle().groupId("org.ops4j.pax.tinybundles").artifactId("tinybundles").version("2.1.1").start());
res.add(mavenBundle().groupId("biz.aQute.bnd").artifactId("biz.aQute.bndlib").versionAsInProject().start());
res.add(mavenBundle().groupId("org.ops4j.pax.tinybundles").artifactId("tinybundles").versionAsInProject().start());
return res;
}

View File

@ -47,8 +47,6 @@ import static org.ops4j.pax.exam.CoreOptions.systemProperty;
@RunWith(PaxExam.class)
public class TestJettyOSGiBootWithJsp
{
private static final String LOG_LEVEL = "WARN";
@Inject
BundleContext bundleContext = null;
@ -56,6 +54,9 @@ public class TestJettyOSGiBootWithJsp
public static Option[] configure()
{
ArrayList<Option> options = new ArrayList<>();
options.addAll(TestOSGiUtil.configurePaxExamLogging());
options.add(CoreOptions.junitBundles());
options.addAll(TestOSGiUtil.configureJettyHomeAndPort(false, "jetty-http-boot-with-jsp.xml"));
options.add(CoreOptions.bootDelegationPackages("org.xml.sax", "org.xml.*", "org.w3c.*", "javax.xml.*", "javax.activation.*"));
@ -66,8 +67,6 @@ public class TestJettyOSGiBootWithJsp
options.addAll(TestOSGiUtil.coreJettyDependencies());
options.add(mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-alpn-java-client").versionAsInProject().start());
options.add(mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-alpn-client").versionAsInProject().start());
options.add(systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value(LOG_LEVEL));
options.add(systemProperty("org.eclipse.jetty.LEVEL").value(LOG_LEVEL));
options.addAll(jspDependencies());
options.add(CoreOptions.cleanCaches(true));
return options.toArray(new Option[0]);

View File

@ -37,7 +37,6 @@ import org.osgi.framework.BundleContext;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
import static org.ops4j.pax.exam.CoreOptions.systemProperty;
/**
*
@ -46,8 +45,6 @@ import static org.ops4j.pax.exam.CoreOptions.systemProperty;
public class TestJettyOSGiBootWithWebSocket
{
private static final String LOG_LEVEL = "WARN";
@Inject
BundleContext bundleContext = null;
@ -55,6 +52,9 @@ public class TestJettyOSGiBootWithWebSocket
public static Option[] configure()
{
ArrayList<Option> options = new ArrayList<>();
options.addAll(TestOSGiUtil.configurePaxExamLogging());
options.add(CoreOptions.junitBundles());
options.addAll(TestOSGiUtil.configureJettyHomeAndPort(false, "jetty-http-boot-with-websocket.xml"));
options.add(CoreOptions.bootDelegationPackages("org.xml.sax", "org.xml.*", "org.w3c.*", "javax.sql.*", "javax.xml.*", "javax.activation.*"));
@ -65,8 +65,6 @@ public class TestJettyOSGiBootWithWebSocket
options.addAll(TestOSGiUtil.coreJettyDependencies());
options.add(mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-alpn-java-client").versionAsInProject().start());
options.add(mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-alpn-client").versionAsInProject().start());
options.add(systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value(LOG_LEVEL));
options.add(systemProperty("org.eclipse.jetty.LEVEL").value(LOG_LEVEL));
options.addAll(jspDependencies());
options.addAll(testJettyWebApp());
return options.toArray(new Option[0]);

View File

@ -40,9 +40,14 @@ import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.ops4j.pax.exam.CoreOptions;
import org.ops4j.pax.exam.Option;
import org.ops4j.pax.exam.options.WrappedUrlProvisionOption.OverwriteMode;
import org.ops4j.pax.tinybundles.core.TinyBundle;
import org.ops4j.pax.tinybundles.core.TinyBundles;
import org.ops4j.pax.url.mvn.internal.AetherBasedResolver;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceReference;
import org.osgi.service.http.HttpService;
@ -52,6 +57,7 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
import static org.ops4j.pax.exam.CoreOptions.systemProperty;
import static org.ops4j.pax.exam.CoreOptions.wrappedBundle;
/**
* Helper methods for pax-exam tests
@ -59,6 +65,24 @@ import static org.ops4j.pax.exam.CoreOptions.systemProperty;
public class TestOSGiUtil
{
public static final String BUNDLE_DEBUG = "bundle.debug";
/**
* Null FragmentActivator for the fake bundle
* that exposes src/test/resources/jetty-logging.properties in
* the osgi container
*/
public static class FragmentActivator implements BundleActivator
{
@Override
public void start(BundleContext context) throws Exception
{
}
@Override
public void stop(BundleContext context) throws Exception
{
}
}
public static List<Option> configureJettyHomeAndPort(boolean ssl, String jettySelectorFileName)
{
@ -90,6 +114,16 @@ public class TestOSGiUtil
options.add(systemProperty("jetty.base").value(etc.getParentFile().getAbsolutePath()));
return options;
}
public static List<Option> configurePaxExamLogging()
{
//sort out logging from the pax-exam environment
List<Option> options = new ArrayList<>();
options.add(systemProperty("pax.exam.logging").value("none"));
String paxExamLogLevel = System.getProperty("pax.exam.LEVEL", "WARN");
options.add(systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value(paxExamLogLevel));
return options;
}
public static List<Option> provisionCoreJetty()
{
@ -108,9 +142,11 @@ public class TestOSGiUtil
public static List<Option> coreJettyDependencies()
{
AetherBasedResolver l;
List<Option> res = new ArrayList<>();
//enables a dump of the status of all deployed bundles
res.add(systemProperty("bundle.debug").value(Boolean.toString(Boolean.getBoolean(TestOSGiUtil.BUNDLE_DEBUG))));
//add locations to look for jars to deploy
String mavenRepoPath = System.getProperty("mavenRepoPath");
if (!StringUtil.isBlank(mavenRepoPath))
{
@ -124,15 +160,28 @@ public class TestOSGiUtil
{
res.add(systemProperty("org.ops4j.pax.url.mvn.settings").value(System.getProperty("settingsFilePath")));
}
//make src/test/resources/jetty-logging.properties visible to jetty in the osgi container
TinyBundle loggingPropertiesBundle = TinyBundles.bundle();
loggingPropertiesBundle.add("jetty-logging.properties", ClassLoader.getSystemResource("jetty-logging.properties"));
loggingPropertiesBundle.set(Constants.BUNDLE_SYMBOLICNAME, "jetty-logging-properties");
loggingPropertiesBundle.set(Constants.FRAGMENT_HOST, "org.eclipse.jetty.logging");
loggingPropertiesBundle.add(FragmentActivator.class);
res.add(CoreOptions.streamBundle(loggingPropertiesBundle.build()).noStart());
res.add(mavenBundle().groupId("org.eclipse.jetty.toolchain").artifactId("jetty-jakarta-servlet-api").versionAsInProject().start());
res.add(mavenBundle().groupId("org.ow2.asm").artifactId("asm").versionAsInProject().start());
res.add(mavenBundle().groupId("org.ow2.asm").artifactId("asm-commons").versionAsInProject().start());
res.add(mavenBundle().groupId("org.ow2.asm").artifactId("asm-tree").versionAsInProject().start());
res.add(mavenBundle().groupId("org.apache.aries.spifly").artifactId("org.apache.aries.spifly.dynamic.bundle").versionAsInProject().start());
res.add(mavenBundle().groupId("jakarta.annotation").artifactId("jakarta.annotation-api").versionAsInProject().start());
res.add(mavenBundle().groupId("org.apache.geronimo.specs").artifactId("geronimo-jta_1.1_spec").version("1.1.1").start());
res.add(mavenBundle().groupId("org.slf4j").artifactId("slf4j-api").versionAsInProject().noStart());
res.add(mavenBundle().groupId("org.slf4j").artifactId("slf4j-log4j12").versionAsInProject().noStart());
//the slf4j-api jar does not have support for ServiceLoader in osgi in its manifest, so add it now
res.add(wrappedBundle(mavenBundle().groupId("org.slf4j").artifactId("slf4j-api").versionAsInProject())
.instructions("Require-Capability=osgi.serviceloader;filter:=\"(osgi.serviceloader=org.slf4j.spi.SLF4JServiceProvider)\",osgi.extender;filter:=\"(osgi.extender=osgi.serviceloader.processor)\"")
.overwriteManifest(OverwriteMode.MERGE)
.start());
res.add(mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-slf4j-impl").versionAsInProject().start());
res.add(mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-util").versionAsInProject().start());
res.add(mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-deploy").versionAsInProject().start());
res.add(mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-server").versionAsInProject().start());

View File

@ -0,0 +1 @@
org.eclipse.jetty.LEVEL=INFO

View File

@ -1,28 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration debug="false" xmlns:log4j='http://jakarta.apache.org/log4j/'>
<appender name="console" class="org.apache.log4j.ConsoleAppender">
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} [%p] :%t: (%c) - %m%n" />
</layout>
</appender>
<logger name="shaded.org.eclipse.aether">
<level value="WARN" />
</logger>
<logger name="shaded.org.apache.http">
<level value="WARN" />
</logger>
<logger name="shaded.org.ops4j">
<level value="WARN" />
</logger>
<logger name="org.ops4j">
<level value="INFO" />
</logger>
<root>
<level value="DEBUG" />
<appender-ref ref="console" />
</root>
</log4j:configuration>

View File

@ -26,7 +26,9 @@ import org.eclipse.jetty.servlet.ServletHolder;
* RunAs
* <p>
* Represents a <code>&lt;run-as&gt;</code> element in web.xml, or a <code>&#064;RunAs</code> annotation.
* @deprecated unused as of 9.4.28 due for removal in 10.0.0
*/
@Deprecated
public class RunAs
{
private String _className;

View File

@ -27,7 +27,9 @@ import org.slf4j.LoggerFactory;
/**
* RunAsCollection
* @deprecated class unused as of 9.4.28 due for removal in 10.0.0
*/
@Deprecated
public class RunAsCollection
{
private static final Logger LOG = LoggerFactory.getLogger(RunAsCollection.class);

View File

@ -20,7 +20,6 @@ package org.eclipse.jetty.plus.webapp;
import org.eclipse.jetty.plus.annotation.InjectionCollection;
import org.eclipse.jetty.plus.annotation.LifeCycleCallbackCollection;
import org.eclipse.jetty.plus.annotation.RunAsCollection;
import org.eclipse.jetty.util.Decorator;
import org.eclipse.jetty.webapp.WebAppContext;
import org.slf4j.Logger;
@ -43,11 +42,6 @@ public class PlusDecorator implements Decorator
@Override
public Object decorate(Object o)
{
RunAsCollection runAses = (RunAsCollection)_context.getAttribute(RunAsCollection.RUNAS_COLLECTION);
if (runAses != null)
runAses.setRunAs(o);
InjectionCollection injections = (InjectionCollection)_context.getAttribute(InjectionCollection.INJECTION_COLLECTION);
if (injections != null)
injections.inject(o);

View File

@ -20,7 +20,6 @@ package org.eclipse.jetty.plus.webapp;
import java.util.Iterator;
import java.util.Objects;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NameNotFoundException;
@ -33,7 +32,6 @@ import org.eclipse.jetty.plus.annotation.LifeCycleCallback;
import org.eclipse.jetty.plus.annotation.LifeCycleCallbackCollection;
import org.eclipse.jetty.plus.annotation.PostConstructCallback;
import org.eclipse.jetty.plus.annotation.PreDestroyCallback;
import org.eclipse.jetty.plus.annotation.RunAsCollection;
import org.eclipse.jetty.plus.jndi.EnvEntry;
import org.eclipse.jetty.plus.jndi.Link;
import org.eclipse.jetty.plus.jndi.NamingEntry;
@ -90,13 +88,6 @@ public class PlusDescriptorProcessor extends IterativeDescriptorProcessor
callbacks = new LifeCycleCallbackCollection();
context.setAttribute(LifeCycleCallbackCollection.LIFECYCLE_CALLBACK_COLLECTION, callbacks);
}
RunAsCollection runAsCollection = (RunAsCollection)context.getAttribute(RunAsCollection.RUNAS_COLLECTION);
if (runAsCollection == null)
{
runAsCollection = new RunAsCollection();
context.setAttribute(RunAsCollection.RUNAS_COLLECTION, runAsCollection);
}
}
@Override

View File

@ -20,15 +20,38 @@ package org.eclipse.jetty.plus.annotation;
import java.lang.reflect.Method;
import javax.servlet.http.HttpServlet;
import org.eclipse.jetty.plus.webapp.PlusDecorator;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.webapp.WebAppContext;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasSize;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
public class LifeCycleCallbackCollectionTest
{
public static class TestServlet extends HttpServlet
{
public static int postConstructCount = 0;
public static int preDestroyCount = 0;
public void postconstruct()
{
++postConstructCount;
}
public void predestroy()
{
++preDestroyCount;
}
}
/**
* An unsupported lifecycle callback type
@ -154,6 +177,80 @@ public class LifeCycleCallbackCollectionTest
//expected
}
}
@Test
public void testServletPostConstructPreDestroy() throws Exception
{
Server server = new Server();
WebAppContext context = new WebAppContext();
context.setResourceBase(MavenTestingUtils.getTargetTestingDir("predestroy-test").toURI().toURL().toString());
context.setContextPath("/");
server.setHandler(context);
//add a non-async servlet
ServletHolder notAsync = new ServletHolder();
notAsync.setHeldClass(TestServlet.class);
notAsync.setName("notAsync");
notAsync.setAsyncSupported(false);
notAsync.setInitOrder(1);
context.getServletHandler().addServletWithMapping(notAsync, "/notasync/*");
//add an async servlet
ServletHolder async = new ServletHolder();
async.setHeldClass(TestServlet.class);
async.setName("async");
async.setAsyncSupported(true);
async.setInitOrder(1);
context.getServletHandler().addServletWithMapping(async, "/async/*");
//add a run-as servlet
ServletHolder runas = new ServletHolder();
runas.setHeldClass(TestServlet.class);
runas.setName("runas");
runas.setRunAsRole("admin");
runas.setInitOrder(1);
context.getServletHandler().addServletWithMapping(runas, "/runas/*");
//add both run-as and non async servlet
ServletHolder both = new ServletHolder();
both.setHeldClass(TestServlet.class);
both.setName("both");
both.setRunAsRole("admin");
both.setAsyncSupported(false);
both.setInitOrder(1);
context.getServletHandler().addServletWithMapping(both, "/both/*");
//Make fake lifecycle callbacks for all servlets
LifeCycleCallbackCollection collection = new LifeCycleCallbackCollection();
context.setAttribute(LifeCycleCallbackCollection.LIFECYCLE_CALLBACK_COLLECTION, collection);
PostConstructCallback pcNotAsync = new PostConstructCallback(TestServlet.class, "postconstruct");
collection.add(pcNotAsync);
PreDestroyCallback pdNotAsync = new PreDestroyCallback(TestServlet.class, "predestroy");
collection.add(pdNotAsync);
PostConstructCallback pcAsync = new PostConstructCallback(TestServlet.class, "postconstruct");
collection.add(pcAsync);
PreDestroyCallback pdAsync = new PreDestroyCallback(TestServlet.class, "predestroy");
collection.add(pdAsync);
PostConstructCallback pcRunAs = new PostConstructCallback(TestServlet.class, "postconstruct");
collection.add(pcRunAs);
PreDestroyCallback pdRunAs = new PreDestroyCallback(TestServlet.class, "predestroy");
collection.add(pdRunAs);
PostConstructCallback pcBoth = new PostConstructCallback(TestServlet.class, "postconstruct");
collection.add(pcBoth);
PreDestroyCallback pdBoth = new PreDestroyCallback(TestServlet.class, "predestroy");
collection.add(pdBoth);
server.start();
assertEquals(4, TestServlet.postConstructCount);
server.stop();
assertEquals(4, TestServlet.preDestroyCount);
}
@Test
public void testAddForPreDestroy() throws Exception

View File

@ -92,19 +92,24 @@ import org.slf4j.LoggerFactory;
/**
* ContextHandler.
*
* <p>
* This handler wraps a call to handle by setting the context and servlet path, plus setting the context classloader.
*
* </p>
* <p>
* If the context init parameter "org.eclipse.jetty.server.context.ManagedAttributes" is set to a comma separated list of names, then they are treated as
* If the context init parameter {@code org.eclipse.jetty.server.context.ManagedAttributes} is set to a comma separated list of names, then they are treated as
* context attribute names, which if set as attributes are passed to the servers Container so that they may be managed with JMX.
* </p>
* <p>
* The maximum size of a form that can be processed by this context is controlled by the system properties org.eclipse.jetty.server.Request.maxFormKeys and
* org.eclipse.jetty.server.Request.maxFormContentSize. These can also be configured with {@link #setMaxFormContentSize(int)} and {@link #setMaxFormKeys(int)}
* The maximum size of a form that can be processed by this context is controlled by the system properties {@code org.eclipse.jetty.server.Request.maxFormKeys} and
* {@code org.eclipse.jetty.server.Request.maxFormContentSize}. These can also be configured with {@link #setMaxFormContentSize(int)} and {@link #setMaxFormKeys(int)}
* </p>
* <p>
* This servers executor is made available via a context attributed "org.eclipse.jetty.server.Executor".
* The executor is made available via a context attributed {@code org.eclipse.jetty.server.Executor}.
* </p>
* <p>
* By default, the context is created with alias checkers for {@link AllowSymLinkAliasChecker} (unix only) and {@link ApproveNonExistentDirectoryAliases}. If
* these alias checkers are not required, then {@link #clearAliasChecks()} or {@link #setAliasChecks(List)} should be called.
* </p>
*/
@ManagedObject("URI Context")
public class ContextHandler extends ScopedHandler implements Attributes, Graceful
@ -171,6 +176,14 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
__serverInfo = serverInfo;
}
public enum ContextStatus
{
NOTSET,
INITIALIZED,
DESTROYED
}
protected ContextStatus _contextStatus = ContextStatus.NOTSET;
protected Context _scontext;
private final AttributesMap _attributes;
private final Map<String, String> _initParams;
@ -782,6 +795,8 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
// defers the calling of super.doStart()
startContext();
contextInitialized();
_availability = Availability.AVAILABLE;
LOG.info("Started {}", this);
@ -840,49 +855,97 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
addEventListener(new ManagedAttributeListener(this, StringUtil.csvSplit(managedAttributes)));
super.doStart();
}
/**
* Call the ServletContextListeners contextInitialized methods.
* This can be called from a ServletHandler during the proper sequence
* of initializing filters, servlets and listeners. However, if there is
* no ServletHandler, the ContextHandler will call this method during
* doStart().
*
* @throws Exception
*/
public void contextInitialized() throws Exception
{
// Call context listeners
_destroyServletContextListeners.clear();
if (!_servletContextListeners.isEmpty())
switch (_contextStatus)
{
ServletContextEvent event = new ServletContextEvent(_scontext);
for (ServletContextListener listener : _servletContextListeners)
case NOTSET:
{
callContextInitialized(listener, event);
_destroyServletContextListeners.add(listener);
try
{
_destroyServletContextListeners.clear();
if (!_servletContextListeners.isEmpty())
{
ServletContextEvent event = new ServletContextEvent(_scontext);
for (ServletContextListener listener : _servletContextListeners)
{
callContextInitialized(listener, event);
_destroyServletContextListeners.add(listener);
}
}
}
finally
{
_contextStatus = ContextStatus.INITIALIZED;
}
break;
}
default:
break;
}
}
/**
* Call the ServletContextListeners with contextDestroyed.
* This method can be called from a ServletHandler in the
* proper sequence of destroying filters, servlets and listeners.
* If there is no ServletHandler, the ContextHandler must ensure
* these listeners are called instead.
*
* @throws Exception
*/
public void contextDestroyed() throws Exception
{
switch (_contextStatus)
{
case INITIALIZED:
{
try
{
//Call context listeners
MultiException ex = new MultiException();
ServletContextEvent event = new ServletContextEvent(_scontext);
Collections.reverse(_destroyServletContextListeners);
for (ServletContextListener listener : _destroyServletContextListeners)
{
try
{
callContextDestroyed(listener, event);
}
catch (Exception x)
{
ex.add(x);
}
}
ex.ifExceptionThrow();
}
finally
{
_contextStatus = ContextStatus.DESTROYED;
}
break;
}
default:
break;
}
}
protected void stopContext() throws Exception
{
// Call the context listeners
ServletContextEvent event = new ServletContextEvent(_scontext);
Collections.reverse(_destroyServletContextListeners);
MultiException ex = new MultiException();
for (ServletContextListener listener : _destroyServletContextListeners)
{
try
{
callContextDestroyed(listener, event);
}
catch (Exception x)
{
ex.add(x);
}
}
// stop all the handler hierarchy
try
{
super.doStop();
}
catch (Exception x)
{
ex.add(x);
}
ex.ifExceptionThrow();
super.doStop();
}
protected void callContextInitialized(ServletContextListener l, ServletContextEvent e)
@ -926,6 +989,8 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
stopContext();
contextDestroyed();
// retain only durable listeners
setEventListeners(_durableListeners);
_durableListeners.clear();
@ -958,6 +1023,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
}
finally
{
_contextStatus = ContextStatus.NOTSET;
__context.set(oldContext);
exitScope(null);
LOG.info("Stopped {}", this);

View File

@ -410,7 +410,10 @@ public class ErrorHandler extends AbstractHandler
htmlRow(writer, "URI", uri);
htmlRow(writer, "STATUS", status);
htmlRow(writer, "MESSAGE", message);
htmlRow(writer, "SERVLET", request.getAttribute(Dispatcher.ERROR_SERVLET_NAME));
if (isShowServlet())
{
htmlRow(writer, "SERVLET", request.getAttribute(Dispatcher.ERROR_SERVLET_NAME));
}
Throwable cause = (Throwable)request.getAttribute(Dispatcher.ERROR_EXCEPTION);
while (cause != null)
{
@ -451,7 +454,7 @@ public class ErrorHandler extends AbstractHandler
while (cause != null)
{
writer.printf("CAUSED BY %s%n", cause);
if (_showStacks && !_disableStacks)
if (isShowStacks() && !_disableStacks)
{
cause.printStackTrace(writer);
}

View File

@ -28,8 +28,11 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import jakarta.servlet.ServletContextEvent;
import jakarta.servlet.ServletContextListener;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.LocalConnector;
import org.eclipse.jetty.server.Request;
@ -445,6 +448,22 @@ public class ContextHandlerTest
assertThat(connector.getResponse("GET /foo/xxx HTTP/1.0\n\n"), Matchers.containsString("ctx='/foo'"));
assertThat(connector.getResponse("GET /foo/bar/xxx HTTP/1.0\n\n"), Matchers.containsString("ctx='/foo/bar'"));
}
@Test
public void testContextInitializationDestruction() throws Exception
{
Server server = new Server();
ContextHandlerCollection contexts = new ContextHandlerCollection();
server.setHandler(contexts);
ContextHandler noServlets = new ContextHandler(contexts, "/noservlets");
TestServletContextListener listener = new TestServletContextListener();
noServlets.addEventListener(listener);
server.start();
assertEquals(1, listener.initialized);
server.stop();
assertEquals(1, listener.destroyed);
}
@Test
public void testContextVirtualGetContext() throws Exception
@ -840,4 +859,22 @@ public class ContextHandlerTest
writer.println("ctx='" + request.getContextPath() + "'");
}
}
private static class TestServletContextListener implements ServletContextListener
{
public int initialized = 0;
public int destroyed = 0;
@Override
public void contextInitialized( ServletContextEvent sce)
{
initialized++;
}
@Override
public void contextDestroyed(ServletContextEvent sce)
{
destroyed++;
}
}
}

View File

@ -33,6 +33,7 @@ import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Consumer;
import java.util.stream.Stream;
import jakarta.servlet.DispatcherType;
@ -308,6 +309,9 @@ public class ServletHandler extends ScopedHandler
_servlets = shs;
ServletMapping[] sms = (ServletMapping[])LazyList.toArray(servletMappings, ServletMapping.class);
_servletMappings = sms;
if (_contextHandler != null)
_contextHandler.contextDestroyed();
//Retain only Listeners added via jetty apis (is Source.EMBEDDED)
List<ListenerHolder> listenerHolders = new ArrayList<>();
@ -724,30 +728,40 @@ public class ServletHandler extends ScopedHandler
public void initialize()
throws Exception
{
_initialized = true;
MultiException mx = new MultiException();
Stream.concat(Stream.concat(
Arrays.stream(_filters),
Arrays.stream(_servlets).sorted()),
Arrays.stream(_listeners))
.forEach(h ->
Consumer<BaseHolder<?>> c = h ->
{
try
{
try
if (!h.isStarted())
{
if (!h.isStarted())
{
h.start();
h.initialize();
}
h.start();
h.initialize();
}
catch (Throwable e)
{
LOG.debug("Unable to start {}", h, e);
mx.add(e);
}
});
}
catch (Throwable e)
{
LOG.debug("Unable to start {}", h, e);
mx.add(e);
}
};
//Start the listeners so we can call them
Arrays.stream(_listeners).forEach(c);
//call listeners contextInitialized
if (_contextHandler != null)
_contextHandler.contextInitialized();
//Only set initialized true AFTER the listeners have been called
_initialized = true;
//Start the filters then the servlets
Stream.concat(
Arrays.stream(_filters),
Arrays.stream(_servlets).sorted())
.forEach(c);
mx.ifExceptionThrow();
}

View File

@ -392,18 +392,6 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
//check if we need to forcibly set load-on-startup
checkInitOnStartup();
if (_runAsRole == null)
{
_identityService = null;
_runAsToken = null;
}
else
{
_identityService = getServletHandler().getIdentityService();
if (_identityService != null)
_runAsToken = _identityService.newRunAsToken(_runAsRole);
}
_config = new Config();
synchronized (this)
@ -457,7 +445,14 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
if (o == null)
return;
Servlet servlet = ((Servlet)o);
getServletHandler().destroyServlet(servlet);
//need to use the unwrapped servlet because lifecycle callbacks such as
//postconstruct and predestroy are based off the classname and the wrapper
//classes are unknown outside the ServletHolder
Servlet unwrapped = servlet;
while (WrapperServlet.class.isAssignableFrom(unwrapped.getClass()))
unwrapped = ((WrapperServlet)unwrapped).getWrappedServlet();
getServletHandler().destroyServlet(unwrapped);
//destroy the wrapped servlet, in case there is special behaviour
servlet.destroy();
}
@ -570,10 +565,23 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
_servlet = newInstance();
if (_config == null)
_config = new Config();
//check run-as rolename and convert to token from IdentityService
if (_runAsRole == null)
{
_identityService = null;
_runAsToken = null;
}
else
{
_identityService = getServletHandler().getIdentityService();
if (_identityService != null)
{
// Handle run as
if (_identityService != null && _runAsToken != null)
_servlet = new RunAsServlet(_servlet, _identityService, _runAsToken);
_runAsToken = _identityService.newRunAsToken(_runAsRole);
_servlet = new RunAsServlet(_servlet, _identityService, _runAsToken);
}
}
if (!isAsyncSupported())
_servlet = new NotAsyncServlet(_servlet);
@ -1257,6 +1265,14 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
{
_servlet.destroy();
}
/**
* @return the original servlet
*/
public Servlet getWrappedServlet()
{
return _servlet;
}
@Override
public String toString()

View File

@ -36,6 +36,7 @@ import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.FilterRegistration;
import jakarta.servlet.GenericServlet;
import jakarta.servlet.Servlet;
import jakarta.servlet.ServletContainerInitializer;
import jakarta.servlet.ServletContext;
@ -111,6 +112,75 @@ public class ServletContextHandlerTest
private LocalConnector _connector;
private static final AtomicInteger __testServlets = new AtomicInteger();
private static int __initIndex = 0;
private static int __destroyIndex = 0;
public class StopTestFilter implements Filter
{
int _initIndex;
int _destroyIndex;
@Override
public void init(FilterConfig filterConfig) throws ServletException
{
_initIndex = __initIndex++;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
ServletException
{
}
@Override
public void destroy()
{
_destroyIndex = __destroyIndex++;
}
}
public class StopTestServlet extends GenericServlet
{
int _initIndex;
int _destroyIndex;
@Override
public void destroy()
{
_destroyIndex = __destroyIndex++;
super.destroy();
}
@Override
public void init() throws ServletException
{
_initIndex = __initIndex++;
super.init();
}
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException
{
}
}
public class StopTestListener implements ServletContextListener
{
int _initIndex;
int _destroyIndex;
@Override
public void contextInitialized(ServletContextEvent sce)
{
_initIndex = __initIndex++;
}
@Override
public void contextDestroyed(ServletContextEvent sce)
{
_destroyIndex = __destroyIndex++;
}
}
public static class MySCI implements ServletContainerInitializer
{
@ -654,6 +724,37 @@ public class ServletContextHandlerTest
assertTrue((Boolean)root.getServletContext().getAttribute("MyContextListener.setSessionTimeout"));
}
@Test
public void testDestroyOrder() throws Exception
{
ContextHandlerCollection contexts = new ContextHandlerCollection();
_server.setHandler(contexts);
ServletContextHandler root = new ServletContextHandler(contexts, "/", ServletContextHandler.SESSIONS);
ListenerHolder listenerHolder = new ListenerHolder();
StopTestListener stopTestListener = new StopTestListener();
listenerHolder.setListener(stopTestListener);
root.getServletHandler().addListener(listenerHolder);
ServletHolder servletHolder = new ServletHolder();
StopTestServlet stopTestServlet = new StopTestServlet();
servletHolder.setServlet(stopTestServlet);
root.addServlet(servletHolder, "/test");
FilterHolder filterHolder = new FilterHolder();
StopTestFilter stopTestFilter = new StopTestFilter();
filterHolder.setFilter(stopTestFilter);
root.addFilter(filterHolder, "/*", EnumSet.of(DispatcherType.REQUEST));
_server.start();
_server.stop();
assertEquals(0, stopTestListener._initIndex); //listeners contextInitialized called first
assertEquals(1, stopTestFilter._initIndex); //filters init
assertEquals(2, stopTestServlet._initIndex); //servlets init
assertEquals(0, stopTestFilter._destroyIndex); //filters destroyed first
assertEquals(1, stopTestServlet._destroyIndex); //servlets destroyed next
assertEquals(2, stopTestListener._destroyIndex); //listener contextDestroyed last
}
@Test
public void testAddSessionListener() throws Exception
{
@ -691,6 +792,41 @@ public class ServletContextHandlerTest
assertTrue((Boolean)root.getServletContext().getAttribute("MyContextListener.setSessionTrackingModes"));
}
@Test
public void testContextInitializationDestruction() throws Exception
{
Server server = new Server();
ContextHandlerCollection contexts = new ContextHandlerCollection();
server.setHandler(contexts);
ServletContextHandler root = new ServletContextHandler(contexts, "/");
class TestServletContextListener implements ServletContextListener
{
public int initialized = 0;
public int destroyed = 0;
@Override
public void contextInitialized(ServletContextEvent sce)
{
initialized++;
}
@Override
public void contextDestroyed(ServletContextEvent sce)
{
destroyed++;
}
}
TestServletContextListener listener = new TestServletContextListener();
root.addEventListener(listener);
server.start();
server.stop();
assertEquals(1, listener.initialized);
server.stop();
assertEquals(1, listener.destroyed);
}
@Test
public void testListenersFromContextListener() throws Exception
{

View File

@ -61,8 +61,8 @@ public class ServletLifeCycleTest
context.getObjectFactory().addDecorator(new TestDecorator());
ServletHandler sh = context.getServletHandler();
sh.addListener(new ListenerHolder(TestListener.class));
context.addEventListener(context.getServletContext().createListener(TestListener2.class));
sh.addListener(new ListenerHolder(TestListener.class)); //added directly to ServletHandler
context.addEventListener(context.getServletContext().createListener(TestListener2.class));//create,decorate and add listener to context - no holder!
sh.addFilterWithMapping(TestFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
sh.addFilterWithMapping(new FilterHolder(context.getServletContext().createFilter(TestFilter2.class)), "/*", EnumSet.of(DispatcherType.REQUEST));
@ -108,8 +108,6 @@ public class ServletLifeCycleTest
server.stop();
assertThat(events, Matchers.contains(
"contextDestroyed class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestListener",
"contextDestroyed class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestListener2",
"destroy class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestFilter2",
"Destroy class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestFilter2",
"destroy class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestFilter",
@ -120,7 +118,10 @@ public class ServletLifeCycleTest
"destroy class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestServlet2",
"Destroy class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestServlet",
"destroy class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestServlet",
"Destroy class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestListener"));
"contextDestroyed class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestListener",
"contextDestroyed class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestListener2",
"Destroy class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestListener"
));
// Listener added before start is not destroyed
List<EventListener> listeners = context.getEventListeners();

View File

@ -19,7 +19,10 @@
<extensions>true</extensions>
<configuration>
<instructions>
<Require-Capability>osgi.serviceloader; filter:="(osgi.serviceloader=org.eclipse.jetty.util.security.CredentialProvider)";resolution:=optional;cardinality:=multiple, osgi.extender; filter:="(osgi.extender=osgi.serviceloader.processor)";resolution:=optional</Require-Capability>
<Require-Capability>osgi.extender; filter:="(osgi.extender=osgi.serviceloader.registrar)"</Require-Capability>
<Provide-Capability>
osgi.serviceloader;osgi.serviceloader=org.slf4j.spi.SLF4JServiceProvider
</Provide-Capability>
</instructions>
</configuration>
</plugin>

View File

@ -152,7 +152,7 @@ public class WebSocketOverHTTP2Test
@Test
public void testWebSocketOverDynamicHTTP2() throws Exception
{
testWebSocketOverDynamicTransport(clientConnector -> new ClientConnectionFactoryOverHTTP2.H2C(new HTTP2Client(clientConnector)));
testWebSocketOverDynamicTransport(clientConnector -> new ClientConnectionFactoryOverHTTP2.HTTP2(new HTTP2Client(clientConnector)));
}
private void testWebSocketOverDynamicTransport(Function<ClientConnector, ClientConnectionFactory.Info> protocolFn) throws Exception
@ -184,7 +184,7 @@ public class WebSocketOverHTTP2Test
AbstractHTTP2ServerConnectionFactory h2c = connector.getBean(AbstractHTTP2ServerConnectionFactory.class);
h2c.setConnectProtocolEnabled(false);
startClient(clientConnector -> new ClientConnectionFactoryOverHTTP2.H2C(new HTTP2Client(clientConnector)));
startClient(clientConnector -> new ClientConnectionFactoryOverHTTP2.HTTP2(new HTTP2Client(clientConnector)));
EventSocket wsEndPoint = new EventSocket();
URI uri = URI.create("ws://localhost:" + connector.getLocalPort() + "/ws/echo");
@ -220,7 +220,7 @@ public class WebSocketOverHTTP2Test
}
});
startClient(clientConnector -> new ClientConnectionFactoryOverHTTP2.H2(new HTTP2Client(clientConnector)));
startClient(clientConnector -> new ClientConnectionFactoryOverHTTP2.HTTP2(new HTTP2Client(clientConnector)));
// Connect and send immediately a message, so the message
// arrives to the server while the server is still upgrading.
@ -242,7 +242,7 @@ public class WebSocketOverHTTP2Test
public void testWebSocketConnectPortDoesNotExist() throws Exception
{
startServer();
startClient(clientConnector -> new ClientConnectionFactoryOverHTTP2.H2(new HTTP2Client(clientConnector)));
startClient(clientConnector -> new ClientConnectionFactoryOverHTTP2.HTTP2(new HTTP2Client(clientConnector)));
EventSocket wsEndPoint = new EventSocket();
URI uri = URI.create("ws://localhost:" + (connector.getLocalPort() + 1) + "/ws/echo");
@ -259,7 +259,7 @@ public class WebSocketOverHTTP2Test
public void testWebSocketNotFound() throws Exception
{
startServer();
startClient(clientConnector -> new ClientConnectionFactoryOverHTTP2.H2(new HTTP2Client(clientConnector)));
startClient(clientConnector -> new ClientConnectionFactoryOverHTTP2.HTTP2(new HTTP2Client(clientConnector)));
EventSocket wsEndPoint = new EventSocket();
URI uri = URI.create("ws://localhost:" + connector.getLocalPort() + "/nothing");
@ -276,7 +276,7 @@ public class WebSocketOverHTTP2Test
public void testNotNegotiated() throws Exception
{
startServer();
startClient(clientConnector -> new ClientConnectionFactoryOverHTTP2.H2(new HTTP2Client(clientConnector)));
startClient(clientConnector -> new ClientConnectionFactoryOverHTTP2.HTTP2(new HTTP2Client(clientConnector)));
EventSocket wsEndPoint = new EventSocket();
URI uri = URI.create("ws://localhost:" + connector.getLocalPort() + "/ws/null");
@ -293,7 +293,7 @@ public class WebSocketOverHTTP2Test
public void testThrowFromCreator() throws Exception
{
startServer();
startClient(clientConnector -> new ClientConnectionFactoryOverHTTP2.H2(new HTTP2Client(clientConnector)));
startClient(clientConnector -> new ClientConnectionFactoryOverHTTP2.HTTP2(new HTTP2Client(clientConnector)));
CountDownLatch latch = new CountDownLatch(1);
connector.addBean(new HttpChannel.Listener()
@ -327,7 +327,7 @@ public class WebSocketOverHTTP2Test
public void testServerConnectionClose() throws Exception
{
startServer();
startClient(clientConnector -> new ClientConnectionFactoryOverHTTP2.H2(new HTTP2Client(clientConnector)));
startClient(clientConnector -> new ClientConnectionFactoryOverHTTP2.HTTP2(new HTTP2Client(clientConnector)));
EventSocket wsEndPoint = new EventSocket();
URI uri = URI.create("ws://localhost:" + connector.getLocalPort() + "/ws/connectionClose");

View File

@ -25,6 +25,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.file.Files;
@ -370,6 +371,7 @@ public class DistributionTester
private String jettyVersion;
private String mavenLocalRepository = System.getProperty("user.home") + "/.m2/repository";
private Map<String, String> mavenRemoteRepositories = new HashMap<>();
private Path logFile;
@Override
public String toString()
@ -507,6 +509,38 @@ public class DistributionTester
return false;
}
/**
* Awaits the logs file to contain the given text, for the given amount of time.
*
* @param logFile the log file to test
* @param txt the text that must be present in the console logs
* @param time the time to wait
* @param unit the unit of time
* @return true if the text was found, false if the timeout elapsed
* @throws InterruptedException if the wait is interrupted
*/
public boolean awaitLogsFileFor(Path logFile, String txt, long time, TimeUnit unit) throws InterruptedException
{
LogFileStreamer logFileStreamer = new LogFileStreamer(logFile);
Thread thread = new Thread(logFileStreamer, "LogFileStreamer/" + logFile);
thread.start();
try
{
long end = System.nanoTime() + unit.toNanos(time);
while (System.nanoTime() < end)
{
boolean result = logs.stream().anyMatch(s -> s.contains(txt));
if (result) return true;
Thread.sleep(250);
}
return false;
}
finally
{
logFileStreamer.stop();
}
}
/**
* Simple streamer for the console output from a Process
*/
@ -549,6 +583,52 @@ public class DistributionTester
}
}
private class LogFileStreamer implements Runnable
{
private RandomAccessFile inputFile;
private volatile boolean stop;
private final Path logFile;
public LogFileStreamer(Path logFile)
{
this.logFile = logFile;
}
@Override
public void run()
{
String currentLine;
long pos = 0;
while (!stop)
{
try
{
inputFile = new RandomAccessFile(logFile.toFile(), "r");
inputFile.seek(pos);
if ((currentLine = inputFile.readLine()) != null)
{
logs.add(currentLine);
}
pos = inputFile.getFilePointer();
}
catch (IOException e)
{
//ignore
}
finally
{
IO.close(inputFile);
}
}
}
public void stop()
{
stop = true;
IO.close(inputFile);
}
}
public Queue<String> getLogs()
{
return logs;

View File

@ -21,9 +21,11 @@ package org.eclipse.jetty.tests.distribution;
import java.io.BufferedWriter;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.nio.file.CopyOption;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.util.concurrent.TimeUnit;
@ -453,4 +455,49 @@ public class DistributionTests extends AbstractDistributionTest
}
}
}
@Test
public void testStartStopLog4j2Modules() throws Exception
{
Path jettyBase = Files.createTempDirectory("jetty_base");
String jettyVersion = System.getProperty("jettyVersion");
DistributionTester distribution = DistributionTester.Builder.newInstance() //
.jettyVersion(jettyVersion) //
.jettyBase(jettyBase) //
.mavenLocalRepository(System.getProperty("mavenRepoPath")) //
.build();
String[] args = {
"--create-startd",
"--approve-all-licenses",
"--add-to-start=http,logging-log4j2"
};
try (DistributionTester.Run run1 = distribution.start(args))
{
assertTrue(run1.awaitFor(5, TimeUnit.SECONDS));
assertEquals(0, run1.getExitValue());
Files.copy(Paths.get("src/test/resources/log4j2.xml"), //
Paths.get(jettyBase.toString(),"resources").resolve("log4j2.xml"), //
StandardCopyOption.REPLACE_EXISTING);
int port = distribution.freePort();
try (DistributionTester.Run run2 = distribution.start("jetty.http.port=" + port))
{
assertTrue(run2.awaitLogsFileFor(
jettyBase.resolve("logs").resolve("jetty.log"), //
"Started Server@", 10, TimeUnit.SECONDS));
startHttpClient();
ContentResponse response = client.GET("http://localhost:" + port);
assertEquals(HttpStatus.NOT_FOUND_404, response.getStatus());
run2.stop();
assertTrue(run2.awaitFor(5, TimeUnit.SECONDS));
}
}
}
}

View File

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" name="Jetty" >
<properties>
<property name="logging.dir">${sys:jetty.logging.dir:-logs}</property>
</properties>
<Appenders>
<Console name="console" target="SYSTEM_ERR">
<PatternLayout>
<Pattern>%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n</Pattern>
</PatternLayout>
</Console>
<RollingRandomAccessFile name="file"
fileName="${logging.dir}/jetty.log"
filePattern="${logging.dir}/jetty-%d{MM-dd-yyyy}.log.gz"
ignoreExceptions="false">
<PatternLayout>
<Pattern>%d [%t] %-5p %c %x - %m%n</Pattern>
</PatternLayout>
<Policies>
<TimeBasedTriggeringPolicy />
<SizeBasedTriggeringPolicy size="10 MB"/>
</Policies>
</RollingRandomAccessFile>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="file"/>
</Root>
</Loggers>
</Configuration>

View File

@ -42,7 +42,7 @@ import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.client.dynamic.HttpClientTransportDynamic;
import org.eclipse.jetty.client.http.HttpClientConnectionFactory;
import org.eclipse.jetty.client.util.BufferingResponseListener;
import org.eclipse.jetty.client.util.BytesContentProvider;
import org.eclipse.jetty.client.util.BytesRequestContent;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpScheme;
@ -244,7 +244,7 @@ public class HttpClientTransportDynamicTest
startServer(this::h1H2C, new EmptyServerHandler());
ClientConnector clientConnector = new ClientConnector();
HTTP2Client http2Client = new HTTP2Client(clientConnector);
ClientConnectionFactory.Info h2c = new ClientConnectionFactoryOverHTTP2.H2C(http2Client);
ClientConnectionFactory.Info h2c = new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client);
startClient(clientConnector, h2c);
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
// .version(HttpVersion.HTTP_2)
@ -273,14 +273,15 @@ public class HttpClientTransportDynamicTest
clientConnector.setSslContextFactory(newClientSslContextFactory());
HttpClientConnectionFactory.Info h1 = HttpClientConnectionFactory.HTTP11;
HTTP2Client http2Client = new HTTP2Client(clientConnector);
ClientConnectionFactory.Info h2c = new ClientConnectionFactoryOverHTTP2.H2C(http2Client);
HttpClientTransportDynamic transport = new HttpClientTransportDynamic(clientConnector, h1, h2c)
ClientConnectionFactory.Info http2 = new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client);
HttpClientTransportDynamic transport = new HttpClientTransportDynamic(clientConnector, h1, http2)
{
@Override
public Origin newOrigin(HttpRequest request)
{
// Use prior-knowledge, i.e. negotiate==false.
List<String> protocols = HttpVersion.HTTP_2 == request.getVersion() ? h2c.getProtocols() : h1.getProtocols();
boolean secure = HttpClient.isSchemeSecure(request.getScheme());
List<String> protocols = HttpVersion.HTTP_2 == request.getVersion() ? http2.getProtocols(secure) : h1.getProtocols(secure);
return new Origin(request.getScheme(), request.getHost(), request.getPort(), request.getTag(), new Origin.Protocol(protocols, false));
}
};
@ -320,8 +321,8 @@ public class HttpClientTransportDynamicTest
startServer(this::sslAlpnH1H2, new EmptyServerHandler());
ClientConnector clientConnector = new ClientConnector();
HTTP2Client http2Client = new HTTP2Client(clientConnector);
ClientConnectionFactory.Info h2 = new ClientConnectionFactoryOverHTTP2.H2(http2Client);
startClient(clientConnector, HttpClientConnectionFactory.HTTP11, h2);
ClientConnectionFactory.Info http2 = new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client);
startClient(clientConnector, HttpClientConnectionFactory.HTTP11, http2);
// Make a request, should be HTTP/1.1 because of the order of protocols on server.
ContentResponse h1cResponse = client.newRequest("localhost", connector.getLocalPort())
@ -355,8 +356,8 @@ public class HttpClientTransportDynamicTest
startServer(this::sslAlpnH1, new EmptyServerHandler());
ClientConnector clientConnector = new ClientConnector();
HTTP2Client http2Client = new HTTP2Client(clientConnector);
ClientConnectionFactory.Info h2 = new ClientConnectionFactoryOverHTTP2.H2(http2Client);
startClient(clientConnector, h2, HttpClientConnectionFactory.HTTP11);
ClientConnectionFactory.Info http2 = new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client);
startClient(clientConnector, http2, HttpClientConnectionFactory.HTTP11);
// The client prefers h2 over h1, and use of TLS and ALPN will allow the fallback to h1.
ContentResponse h1cResponse = client.newRequest("localhost", connector.getLocalPort())
@ -372,8 +373,8 @@ public class HttpClientTransportDynamicTest
startServer(this::h1, new EmptyServerHandler());
ClientConnector clientConnector = new ClientConnector();
HTTP2Client http2Client = new HTTP2Client(clientConnector);
ClientConnectionFactory.Info h2c = new ClientConnectionFactoryOverHTTP2.H2C(http2Client);
startClient(clientConnector, HttpClientConnectionFactory.HTTP11, h2c);
ClientConnectionFactory.Info http2 = new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client);
startClient(clientConnector, HttpClientConnectionFactory.HTTP11, http2);
// The client forces HTTP/2, but the server cannot speak it, so the request fails.
// There is no fallback to HTTP/1 because the protocol version is set explicitly.
@ -450,9 +451,8 @@ public class HttpClientTransportDynamicTest
server.start();
ClientConnector clientConnector = new ClientConnector();
HTTP2Client http2Client = new HTTP2Client(clientConnector);
ClientConnectionFactory.Info h2c = new ClientConnectionFactoryOverHTTP2.H2C(http2Client);
ClientConnectionFactory.Info h2 = new ClientConnectionFactoryOverHTTP2.H2(http2Client);
startClient(clientConnector, h2, HttpClientConnectionFactory.HTTP11, h2c);
ClientConnectionFactory.Info http2 = new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client);
startClient(clientConnector, HttpClientConnectionFactory.HTTP11, http2);
// Make a clear-text request using HTTP/1.1.
ContentResponse h1cResponse = client.newRequest("localhost", clearConnector.getLocalPort())
@ -510,8 +510,8 @@ public class HttpClientTransportDynamicTest
});
ClientConnector clientConnector = new ClientConnector();
HTTP2Client http2Client = new HTTP2Client(clientConnector);
ClientConnectionFactory.Info h2c = new ClientConnectionFactoryOverHTTP2.H2C(http2Client);
startClient(clientConnector, HttpClientConnectionFactory.HTTP11, h2c);
ClientConnectionFactory.Info http2 = new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client);
startClient(clientConnector, HttpClientConnectionFactory.HTTP11, http2);
// Make an upgrade request from HTTP/1.1 to H2C.
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
@ -584,8 +584,8 @@ public class HttpClientTransportDynamicTest
});
ClientConnector clientConnector = new ClientConnector();
HTTP2Client http2Client = new HTTP2Client(clientConnector);
ClientConnectionFactory.Info h2c = new ClientConnectionFactoryOverHTTP2.H2C(http2Client);
startClient(clientConnector, HttpClientConnectionFactory.HTTP11, h2c);
ClientConnectionFactory.Info http2 = new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client);
startClient(clientConnector, HttpClientConnectionFactory.HTTP11, http2);
int proxyPort = connector.getLocalPort();
// The proxy speaks both http/1.1 and h2c.
@ -622,8 +622,8 @@ public class HttpClientTransportDynamicTest
});
ClientConnector clientConnector = new ClientConnector();
HTTP2Client http2Client = new HTTP2Client(clientConnector);
ClientConnectionFactory.Info h2c = new ClientConnectionFactoryOverHTTP2.H2C(http2Client);
startClient(clientConnector, HttpClientConnectionFactory.HTTP11, h2c);
ClientConnectionFactory.Info http2 = new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client);
startClient(clientConnector, HttpClientConnectionFactory.HTTP11, http2);
// Make an upgrade request from HTTP/1.1 to H2C.
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
@ -653,8 +653,8 @@ public class HttpClientTransportDynamicTest
});
ClientConnector clientConnector = new ClientConnector();
HTTP2Client http2Client = new HTTP2Client(clientConnector);
ClientConnectionFactory.Info h2c = new ClientConnectionFactoryOverHTTP2.H2C(http2Client);
startClient(clientConnector, HttpClientConnectionFactory.HTTP11, h2c);
ClientConnectionFactory.Info http2 = new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client);
startClient(clientConnector, HttpClientConnectionFactory.HTTP11, http2);
// Make a POST upgrade request from HTTP/1.1 to H2C.
// We don't support upgrades with request content because
@ -668,7 +668,7 @@ public class HttpClientTransportDynamicTest
.header(HttpHeader.UPGRADE, "h2c")
.header(HttpHeader.HTTP2_SETTINGS, "")
.header(HttpHeader.CONNECTION, "Upgrade, HTTP2-Settings")
.content(new BytesContentProvider(bytes))
.body(new BytesRequestContent(bytes))
.timeout(5, TimeUnit.SECONDS)
.send(new BufferingResponseListener(bytes.length)
{
@ -693,8 +693,8 @@ public class HttpClientTransportDynamicTest
startServer(this::h1H2C, new EmptyServerHandler());
ClientConnector clientConnector = new ClientConnector();
HTTP2Client http2Client = new HTTP2Client(clientConnector);
ClientConnectionFactory.Info h2c = new ClientConnectionFactoryOverHTTP2.H2C(http2Client);
startClient(clientConnector, HttpClientConnectionFactory.HTTP11, h2c);
ClientConnectionFactory.Info http2 = new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client);
startClient(clientConnector, HttpClientConnectionFactory.HTTP11, http2);
// The upgrade request is missing the required HTTP2-Settings header.
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
@ -719,8 +719,8 @@ public class HttpClientTransportDynamicTest
});
ClientConnector clientConnector = new ClientConnector();
HTTP2Client http2Client = new HTTP2Client(clientConnector);
ClientConnectionFactory.Info h2c = new ClientConnectionFactoryOverHTTP2.H2C(http2Client);
startClient(clientConnector, HttpClientConnectionFactory.HTTP11, h2c);
ClientConnectionFactory.Info http2 = new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client);
startClient(clientConnector, HttpClientConnectionFactory.HTTP11, http2);
// Make an upgrade request from HTTP/1.1 to H2C.
CountDownLatch latch = new CountDownLatch(1);
@ -736,4 +736,23 @@ public class HttpClientTransportDynamicTest
assertTrue(latch.await(5, TimeUnit.SECONDS));
}
@Test
public void testClientWithALPNServerWithoutALPN() throws Exception
{
startServer(this::sslH1H2C, new EmptyServerHandler());
ClientConnector clientConnector = new ClientConnector();
HTTP2Client http2Client = new HTTP2Client(clientConnector);
ClientConnectionFactory.Info http2 = new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client);
startClient(clientConnector, HttpClientConnectionFactory.HTTP11, http2);
// Make a request without explicit version, so ALPN is used on the client.
// Since the server does not support ALPN, the first protocol is used.
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
.scheme(HttpScheme.HTTPS.asString())
.timeout(5, TimeUnit.SECONDS)
.send();
assertEquals(HttpStatus.OK_200, response.getStatus());
}
}

View File

@ -180,9 +180,8 @@ public class ProxyWithDynamicTransportTest
{
ClientConnectionFactory.Info h1 = HttpClientConnectionFactory.HTTP11;
HTTP2Client http2Client = new HTTP2Client(clientConnector);
ClientConnectionFactory.Info h2c = new ClientConnectionFactoryOverHTTP2.H2C(http2Client);
ClientConnectionFactory.Info h2 = new ClientConnectionFactoryOverHTTP2.H2(http2Client);
return new HttpClient(new HttpClientTransportDynamic(clientConnector, h1, h2c, h2));
ClientConnectionFactory.Info http2 = new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client);
return new HttpClient(new HttpClientTransportDynamic(clientConnector, h1, http2));
}
});
context.addServlet(holder, "/*");
@ -200,9 +199,8 @@ public class ProxyWithDynamicTransportTest
clientConnector.setSslContextFactory(new SslContextFactory.Client(true));
http2Client = new HTTP2Client(clientConnector);
ClientConnectionFactory.Info h1 = HttpClientConnectionFactory.HTTP11;
ClientConnectionFactory.Info h2c = new ClientConnectionFactoryOverHTTP2.H2C(http2Client);
ClientConnectionFactory.Info h2 = new ClientConnectionFactoryOverHTTP2.H2(http2Client);
client = new HttpClient(new HttpClientTransportDynamic(clientConnector, h1, h2c, h2));
ClientConnectionFactory.Info http2 = new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client);
client = new HttpClient(new HttpClientTransportDynamic(clientConnector, h1, http2));
client.start();
}