Improvements to the Jetty client documentation, protocols section.
Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
This commit is contained in:
parent
eaf9d43a0b
commit
51c42f2849
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -59,8 +59,13 @@
|
|||
<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>
|
||||
|
|
|
@ -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]).
|
||||
|
|
|
@ -34,7 +34,7 @@ include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tags=simpleBl
|
|||
The method `HttpClient.GET(...)` performs a HTTP `GET` request to the given URI and returns a `ContentResponse` when the request/response conversation completes successfully.
|
||||
|
||||
The `ContentResponse` object contains the HTTP response information: status code, headers and possibly content.
|
||||
The content length is limited by default to 2 MiB; for larger content see xref:http-client-response-content[].
|
||||
The content length is limited by default to 2 MiB; for larger content see xref:client-http-content-response[].
|
||||
|
||||
If you want to customize the request, for example by issuing a `HEAD` request instead of a `GET`, and simulating a browser user agent, you can do it in this way:
|
||||
|
||||
|
@ -190,7 +190,7 @@ which allows applications to write request content when it is available to the `
|
|||
include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tags=outputStreamRequestContent]
|
||||
----
|
||||
|
||||
[[http-client-response-content]]
|
||||
[[client-http-content-response]]
|
||||
===== Response Content Handling
|
||||
|
||||
Jetty's `HttpClient` allows applications to handle response content in different ways.
|
||||
|
|
|
@ -16,21 +16,25 @@
|
|||
// ========================================================================
|
||||
//
|
||||
|
||||
[[http-client-transport]]
|
||||
=== Pluggable Transports
|
||||
[[client-http-transport]]
|
||||
=== HttpClient Pluggable Transports
|
||||
|
||||
Jetty's HTTP client can be configured to use different transports to carry the semantic of HTTP requests and responses.
|
||||
Jetty's `HttpClient` can be configured to use different transports to carry the
|
||||
semantic of HTTP requests and responses.
|
||||
|
||||
This means that the intention of a client to request resource `/index.html` using the `GET` method can be carried over the network in different formats.
|
||||
This means that the intention of a client to request resource `/index.html`
|
||||
using the `GET` method can be carried over the network in different formats.
|
||||
|
||||
A HTTP client transport is the component that is in charge of converting a high-level, semantic, HTTP requests such as "GET resource /index.html" into the specific format understood by the server (for example, HTTP/2), and to convert the server response from the specific format (HTTP/2) into high-level, semantic objects that can be used by applications.
|
||||
A `HttpClient` transport is the component that is in charge of converting a
|
||||
high-level, semantic, HTTP requests such as "`GET` resource ``/index.html``"
|
||||
into the specific format understood by the server (for example, HTTP/2), and to
|
||||
convert the server response from the specific format (HTTP/2) into high-level,
|
||||
semantic objects that can be used by applications.
|
||||
|
||||
In this way, applications are not aware of the actual protocol being used.
|
||||
This allows them to write their logic against a high-level API that hides the details of the specific protocol being used over the network.
|
||||
The most common protocol format is HTTP/1.1, a textual protocol with lines
|
||||
separated by `\r\n`:
|
||||
|
||||
The most common protocol format is HTTP/1.1, a text-based protocol with lines separated by `\r\n`:
|
||||
|
||||
[source, screen, subs="{sub-order}"]
|
||||
[source,screen,subs="{sub-order}"]
|
||||
----
|
||||
GET /index.html HTTP/1.1\r\n
|
||||
Host: domain.com\r\n
|
||||
|
@ -40,7 +44,7 @@ Host: domain.com\r\n
|
|||
|
||||
However, the same request can be made using FastCGI, a binary protocol:
|
||||
|
||||
[source, screen, subs="{sub-order}"]
|
||||
[source,screen,subs="{sub-order}"]
|
||||
----
|
||||
x01 x01 x00 x01 x00 x08 x00 x00
|
||||
x00 x01 x01 x00 x00 x00 x00 x00
|
||||
|
@ -52,64 +56,153 @@ x0C x0B D O C U M E
|
|||
...
|
||||
----
|
||||
|
||||
Similarly, HTTP/2 is a binary protocol that transports the same information in a yet different format.
|
||||
Similarly, HTTP/2 is a binary protocol that transports the same information
|
||||
in a yet different format.
|
||||
|
||||
A protocol may be _negotiated_ between client and server. A request for a
|
||||
resource may be sent using one protocol (for example, HTTP/1.1), but the
|
||||
response may arrive in a different protocol (for example, HTTP/2).
|
||||
|
||||
`HttpClient` supports 3 static transports, each speaking only one protocol:
|
||||
link:#client-http-transport-http11[HTTP/1.1],
|
||||
link:#client-http-transport-http2[HTTP/2] and
|
||||
link:#client-http-transport-fcgi[FastCGI],
|
||||
all of them with 2 variants: clear-text and TLS encrypted.
|
||||
|
||||
`HttpClient` also supports one
|
||||
link:#client-http-transport-dynamic[dynamic transport],
|
||||
that can speak different protocols and can select the right protocol by
|
||||
negotiating it with the server or by explicit indication from applications.
|
||||
|
||||
Applications are typically not aware of the actual protocol being used.
|
||||
This allows them to write their logic against a high-level API that hides the
|
||||
details of the specific protocol being used over the network.
|
||||
|
||||
[[client-http-transport-http11]]
|
||||
==== HTTP/1.1 Transport
|
||||
|
||||
HTTP/1.1 is the default transport.
|
||||
|
||||
[source, java, subs="{sub-order}"]
|
||||
[source,java,indent=0]
|
||||
----
|
||||
// No transport specified, using default.
|
||||
HttpClient client = new HttpClient();
|
||||
client.start();
|
||||
include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=defaultTransport]
|
||||
----
|
||||
|
||||
If you want to customize the HTTP/1.1 transport, you can explicitly configure `HttpClient` in this way:
|
||||
If you want to customize the HTTP/1.1 transport, you can explicitly configure
|
||||
it in this way:
|
||||
|
||||
[source, java, subs="{sub-order}"]
|
||||
[source,java,indent=0]
|
||||
----
|
||||
int selectors = 1;
|
||||
HttpClientTransportOverHTTP transport = new HttpClientTransportOverHTTP(selectors);
|
||||
|
||||
HttpClient client = new HttpClient(transport, null);
|
||||
client.start();
|
||||
include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=http11Transport]
|
||||
----
|
||||
|
||||
The example above allows you to customize the number of NIO selectors that `HttpClient` will be using.
|
||||
|
||||
[[client-http-transport-http2]]
|
||||
==== HTTP/2 Transport
|
||||
|
||||
The HTTP/2 transport can be configured in this way:
|
||||
|
||||
[source, java, subs="{sub-order}"]
|
||||
[source,java,indent=0]
|
||||
----
|
||||
HTTP2Client h2Client = new HTTP2Client();
|
||||
h2Client.setSelectors(1);
|
||||
HttpClientTransportOverHTTP2 transport = new HttpClientTransportOverHTTP2(h2Client);
|
||||
|
||||
HttpClient client = new HttpClient(transport, null);
|
||||
client.start();
|
||||
include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=http2Transport]
|
||||
----
|
||||
|
||||
`HTTP2Client` is the lower-level client that provides an API based on HTTP/2 concepts such as _sessions_, _streams_ and _frames_ that are specific to HTTP/2.
|
||||
`HTTP2Client` is the lower-level client that provides an API based on HTTP/2
|
||||
concepts such as _sessions_, _streams_ and _frames_ that are specific to HTTP/2.
|
||||
See link:#client-http2[the HTTP/2 client section] for more information.
|
||||
|
||||
`HttpClientTransportOverHTTP2` uses `HTTP2Client` to format high-level semantic HTTP requests ("GET resource /index.html") into the HTTP/2 specific format.
|
||||
`HttpClientTransportOverHTTP2` uses `HTTP2Client` to format high-level semantic
|
||||
HTTP requests (like "GET resource /index.html") into the HTTP/2 specific format.
|
||||
|
||||
[[client-http-transport-fcgi]]
|
||||
==== FastCGI Transport
|
||||
|
||||
The FastCGI transport can be configured in this way:
|
||||
|
||||
[source, java, subs="{sub-order}"]
|
||||
[source,java,indent=0]
|
||||
----
|
||||
int selectors = 1;
|
||||
String scriptRoot = "/var/www/wordpress";
|
||||
HttpClientTransportOverFCGI transport = new HttpClientTransportOverFCGI(selectors, false, scriptRoot);
|
||||
|
||||
HttpClient client = new HttpClient(transport, null);
|
||||
client.start();
|
||||
include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=fcgiTransport]
|
||||
----
|
||||
|
||||
In order to make requests using the FastCGI transport, you need to have a FastCGI server such as https://en.wikipedia.org/wiki/PHP#PHPFPM[PHP-FPM] (see also http://php.net/manual/en/install.fpm.php).
|
||||
In order to make requests using the FastCGI transport, you need to have a
|
||||
FastCGI server such as https://en.wikipedia.org/wiki/PHP#PHPFPM[PHP-FPM]
|
||||
(see also http://php.net/manual/en/install.fpm.php).
|
||||
|
||||
The FastCGI transport is primarily used by Jetty's link:#fastcgi[FastCGI support] to serve PHP pages (WordPress for example).
|
||||
The FastCGI transport is primarily used by Jetty's link:#fastcgi[FastCGI support]
|
||||
to serve PHP pages (WordPress for example).
|
||||
|
||||
[[client-http-transport-dynamic]]
|
||||
==== Dynamic Transport
|
||||
|
||||
The static transports work well if you know in advance the protocol you want
|
||||
to speak with the server, or if the server only supports one protocol (such
|
||||
as FastCGI).
|
||||
|
||||
With the advent of HTTP/2, however, servers are now able to support multiple
|
||||
protocols, at least both HTTP/1.1 and HTTP/2.
|
||||
|
||||
The HTTP/2 protocol is typically negotiated between client and server.
|
||||
This negotiation can happen via ALPN, a TLS extension that allows the client
|
||||
to tell the server the list of protocol that the client supports, so that the
|
||||
server can pick one of the client supported protocols that also the server
|
||||
supports; or via HTTP/1.1 upgrade by means of the `Upgrade` header.
|
||||
|
||||
Applications can configure the dynamic transport with one or more
|
||||
_application_ protocols such as HTTP/1.1 or HTTP/2. The implementation will
|
||||
take care of using TLS for HTTPS URIs, using ALPN, negotiating protocols,
|
||||
upgrading from one protocol to another, etc.
|
||||
|
||||
By default, the dynamic transport only speaks HTTP/1.1:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=dynamicDefault]
|
||||
----
|
||||
|
||||
The dynamic transport can be configured with just one protocol, making it
|
||||
equivalent to the corresponding static transport:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=dynamicOneProtocol]
|
||||
----
|
||||
|
||||
The dynamic transport, however, has been implemented to support multiple
|
||||
transports, in particular both HTTP/1.1 and HTTP/2:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=dynamicH1H2]
|
||||
----
|
||||
|
||||
IMPORTANT: The order in which the protocols are specified to
|
||||
`HttpClientTransportDynamic` indicates what is the client preference.
|
||||
If the protocol is negotiated via ALPN, it is the server that decides what is
|
||||
the protocol to use for the communication, regardless of the client preference.
|
||||
If the protocol is not negotiated, the client preference is honored.
|
||||
|
||||
Provided that the server supports both HTTP/1.1 and HTTP/2 clear-text, client
|
||||
applications can explicitly hint the version they want to use:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=dynamicClearText]
|
||||
----
|
||||
|
||||
In case of TLS encrypted communication using the HTTPS scheme, things are a
|
||||
little more complicated.
|
||||
|
||||
If the client application explicitly specifies the HTTP version, then ALPN
|
||||
is not used on the client. By specifying the HTTP version explicitly, the
|
||||
client application has prior-knowledge of what HTTP version the server
|
||||
supports, and therefore ALPN is not needed.
|
||||
If the server does not support the HTTP version chosen by the client, then
|
||||
the communication will fail.
|
||||
|
||||
If the client application does not explicitly specify the HTTP version,
|
||||
then ALPN will be used on the client.
|
||||
If the server also supports ALPN, then the protocol will be negotiated via
|
||||
ALPN and the server will choose the protocol to use.
|
||||
If the server does not support ALPN, the client will try to use the first
|
||||
protocol configured in `HttpClientTransportDynamic`, and the communication
|
||||
may succeed or fail depending on whether the server supports the protocol
|
||||
chosen by the client.
|
||||
|
|
|
@ -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}"]
|
||||
----
|
||||
|
|
|
@ -41,6 +41,8 @@ import org.eclipse.jetty.client.api.Request;
|
|||
import org.eclipse.jetty.client.api.Response;
|
||||
import org.eclipse.jetty.client.api.Result;
|
||||
import org.eclipse.jetty.client.dynamic.HttpClientTransportDynamic;
|
||||
import org.eclipse.jetty.client.http.HttpClientConnectionFactory;
|
||||
import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
|
||||
import org.eclipse.jetty.client.util.AsyncRequestContent;
|
||||
import org.eclipse.jetty.client.util.BasicAuthentication;
|
||||
import org.eclipse.jetty.client.util.BufferingResponseListener;
|
||||
|
@ -52,9 +54,16 @@ import org.eclipse.jetty.client.util.InputStreamResponseListener;
|
|||
import org.eclipse.jetty.client.util.OutputStreamRequestContent;
|
||||
import org.eclipse.jetty.client.util.PathRequestContent;
|
||||
import org.eclipse.jetty.client.util.StringRequestContent;
|
||||
import org.eclipse.jetty.fcgi.client.http.HttpClientTransportOverFCGI;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http.HttpVersion;
|
||||
import org.eclipse.jetty.http2.client.HTTP2Client;
|
||||
import org.eclipse.jetty.http2.client.http.ClientConnectionFactoryOverHTTP2;
|
||||
import org.eclipse.jetty.http2.client.http.HttpClientTransportOverHTTP2;
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.io.ClientConnectionFactory;
|
||||
import org.eclipse.jetty.io.ClientConnector;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.HttpCookieStore;
|
||||
|
@ -666,4 +675,129 @@ public class HTTPClientDocs
|
|||
ContentResponse response = httpClient.newRequest(serverURI).send();
|
||||
// end::proxyAuthentication[]
|
||||
}
|
||||
|
||||
public void defaultTransport() throws Exception
|
||||
{
|
||||
// tag::defaultTransport[]
|
||||
// No transport specified, using default.
|
||||
HttpClient httpClient = new HttpClient();
|
||||
httpClient.start();
|
||||
// end::defaultTransport[]
|
||||
}
|
||||
|
||||
public void http11Transport() throws Exception
|
||||
{
|
||||
// tag::http11Transport[]
|
||||
// Configure HTTP/1.1 transport.
|
||||
HttpClientTransportOverHTTP transport = new HttpClientTransportOverHTTP();
|
||||
transport.setHeaderCacheSize(16384);
|
||||
|
||||
HttpClient client = new HttpClient(transport);
|
||||
client.start();
|
||||
// end::http11Transport[]
|
||||
}
|
||||
|
||||
public void http2Transport() throws Exception
|
||||
{
|
||||
// tag::http2Transport[]
|
||||
// The HTTP2Client powers the HTTP/2 transport.
|
||||
HTTP2Client h2Client = new HTTP2Client();
|
||||
h2Client.setInitialSessionRecvWindow(64 * 1024 * 1024);
|
||||
|
||||
// Create and configure the HTTP/2 transport.
|
||||
HttpClientTransportOverHTTP2 transport = new HttpClientTransportOverHTTP2(h2Client);
|
||||
transport.setUseALPN(true);
|
||||
|
||||
HttpClient client = new HttpClient(transport);
|
||||
client.start();
|
||||
// end::http2Transport[]
|
||||
}
|
||||
|
||||
public void fcgiTransport() throws Exception
|
||||
{
|
||||
// tag::fcgiTransport[]
|
||||
String scriptRoot = "/var/www/wordpress";
|
||||
HttpClientTransportOverFCGI transport = new HttpClientTransportOverFCGI(scriptRoot);
|
||||
|
||||
HttpClient client = new HttpClient(transport);
|
||||
client.start();
|
||||
// end::fcgiTransport[]
|
||||
}
|
||||
|
||||
public void dynamicDefault() throws Exception
|
||||
{
|
||||
// tag::dynamicDefault[]
|
||||
// Dynamic transport speaks HTTP/1.1 by default.
|
||||
HttpClientTransportDynamic transport = new HttpClientTransportDynamic();
|
||||
|
||||
HttpClient client = new HttpClient(transport);
|
||||
client.start();
|
||||
// end::dynamicDefault[]
|
||||
}
|
||||
|
||||
public void dynamicOneProtocol() throws Exception
|
||||
{
|
||||
// tag::dynamicOneProtocol[]
|
||||
ClientConnector connector = new ClientConnector();
|
||||
|
||||
// Equivalent to HttpClientTransportOverHTTP.
|
||||
HttpClientTransportDynamic http11Transport = new HttpClientTransportDynamic(connector, HttpClientConnectionFactory.HTTP11);
|
||||
|
||||
// Equivalent to HttpClientTransportOverHTTP2.
|
||||
HTTP2Client http2Client = new HTTP2Client(connector);
|
||||
HttpClientTransportDynamic http2Transport = new HttpClientTransportDynamic(connector, new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client));
|
||||
// end::dynamicOneProtocol[]
|
||||
}
|
||||
|
||||
public void dynamicH1H2() throws Exception
|
||||
{
|
||||
// tag::dynamicH1H2[]
|
||||
ClientConnector connector = new ClientConnector();
|
||||
|
||||
ClientConnectionFactory.Info http1 = HttpClientConnectionFactory.HTTP11;
|
||||
|
||||
HTTP2Client http2Client = new HTTP2Client(connector);
|
||||
ClientConnectionFactoryOverHTTP2.HTTP2 http2 = new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client);
|
||||
|
||||
HttpClientTransportDynamic transport = new HttpClientTransportDynamic(connector, http1, http2);
|
||||
|
||||
HttpClient client = new HttpClient(transport);
|
||||
client.start();
|
||||
// end::dynamicH1H2[]
|
||||
}
|
||||
|
||||
public void dynamicClearText() throws Exception
|
||||
{
|
||||
// tag::dynamicClearText[]
|
||||
ClientConnector connector = new ClientConnector();
|
||||
ClientConnectionFactory.Info http1 = HttpClientConnectionFactory.HTTP11;
|
||||
HTTP2Client http2Client = new HTTP2Client(connector);
|
||||
ClientConnectionFactoryOverHTTP2.HTTP2 http2 = new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client);
|
||||
HttpClientTransportDynamic transport = new HttpClientTransportDynamic(connector, http1, http2);
|
||||
HttpClient client = new HttpClient(transport);
|
||||
client.start();
|
||||
|
||||
// The server supports both HTTP/1.1 and HTTP/2 clear-text on port 8080.
|
||||
|
||||
// Make a clear-text request without explicit version.
|
||||
// The first protocol specified to HttpClientTransportDynamic
|
||||
// is picked, in this example will be HTTP/1.1.
|
||||
ContentResponse http1Response = client.newRequest("host", 8080).send();
|
||||
|
||||
// Make a clear-text request with explicit version.
|
||||
// Clear-text HTTP/2 is used for this request.
|
||||
ContentResponse http2Response = client.newRequest("host", 8080)
|
||||
// Specify the version explicitly.
|
||||
.version(HttpVersion.HTTP_2)
|
||||
.send();
|
||||
|
||||
// Make a clear-text upgrade request from HTTP/1.1 to HTTP/2.
|
||||
// The request will start as HTTP/1.1, but the response will be HTTP/2.
|
||||
ContentResponse upgradedResponse = client.newRequest("host", 8080)
|
||||
.header(HttpHeader.UPGRADE, "h2c")
|
||||
.header(HttpHeader.HTTP2_SETTINGS, "")
|
||||
.header(HttpHeader.CONNECTION, "Upgrade, HTTP2-Settings")
|
||||
.send();
|
||||
// end::dynamicClearText[]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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