Merge branch 'jetty-10.0.x' into jetty-11.0.x
This commit is contained in:
commit
3483e0c2f7
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
----
|
||||
|
|
|
@ -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]).
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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[]
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
// ========================================================================
|
||||
//
|
||||
|
||||
[[client-http]]
|
||||
[[eg-client-http]]
|
||||
=== HTTP Client
|
||||
|
||||
include::client-http-intro.adoc[]
|
||||
|
|
|
@ -0,0 +1,322 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under
|
||||
// the terms of the Eclipse Public License 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// This Source Code may also be made available under the following
|
||||
// Secondary Licenses when the conditions for such availability set
|
||||
// forth in the Eclipse Public License, v. 2.0 are satisfied:
|
||||
// the Apache License v2.0 which is available at
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
[[eg-client-http2]]
|
||||
=== HTTP/2 Client Library
|
||||
|
||||
In the vast majority of cases, client applications should use the generic,
|
||||
high-level, xref:eg-client-http[HTTP client library] that also provides
|
||||
HTTP/2 support via the pluggable
|
||||
xref:eg-client-http-transport-http2[HTTP/2 transport] or the
|
||||
xref:eg-client-http-transport-dynamic[dynamic transport].
|
||||
|
||||
The high-level HTTP library supports cookies, authentication, redirection,
|
||||
connection pooling and a number of other features that are absent in the
|
||||
low-level HTTP/2 library.
|
||||
|
||||
The HTTP/2 client library has been designed for those applications that need
|
||||
low-level access to HTTP/2 features such as _sessions_, _streams_ and
|
||||
_frames_, and this is quite a rare use case.
|
||||
|
||||
[[eg-client-http2-intro]]
|
||||
==== Introducing HTTP2Client
|
||||
|
||||
The Maven artifact coordinates for the HTTP/2 client library are the following:
|
||||
|
||||
[source,xml,subs="{sub-order}"]
|
||||
----
|
||||
<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]
|
||||
----
|
|
@ -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}"]
|
||||
----
|
||||
|
|
|
@ -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}"]
|
||||
----
|
||||
|
|
|
@ -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[]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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[]
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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>
|
||||
*
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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)"
|
||||
|
|
|
@ -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
|
|
@ -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>
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
org.eclipse.jetty.LEVEL=INFO
|
|
@ -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]);
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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]);
|
||||
|
|
|
@ -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]);
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
org.eclipse.jetty.LEVEL=INFO
|
|
@ -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>
|
|
@ -26,7 +26,9 @@ import org.eclipse.jetty.servlet.ServletHolder;
|
|||
* RunAs
|
||||
* <p>
|
||||
* Represents a <code><run-as></code> element in web.xml, or a <code>@RunAs</code> annotation.
|
||||
* @deprecated unused as of 9.4.28 due for removal in 10.0.0
|
||||
*/
|
||||
@Deprecated
|
||||
public class RunAs
|
||||
{
|
||||
private String _className;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue