Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/jetty-12.1.x' into fix/12.1.x/dy…
Browse files Browse the repository at this point in the history
…namic-compression-handler
  • Loading branch information
joakime committed Aug 21, 2024
2 parents 3bf5979 + a72ae70 commit 55fa414
Show file tree
Hide file tree
Showing 34 changed files with 711 additions and 154 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1049,6 +1049,31 @@ public void setConnectionPool() throws Exception
// end::setConnectionPool[]
}

public void preCreateConnections() throws Exception
{
// tag::preCreateConnections[]
HttpClient httpClient = new HttpClient();
httpClient.start();

// For HTTP/1.1, you need to explicitly configure to initialize connections.
if (httpClient.getTransport() instanceof HttpClientTransportOverHTTP http1)
http1.setInitializeConnections(true);

// Create a dummy request to the server you want to pre-create connections to.
Request request = httpClient.newRequest("https://host/");

// Resolve the destination for that request.
Destination destination = httpClient.resolveDestination(request);

// Pre-create, for example, half of the connections.
int preCreate = httpClient.getMaxConnectionsPerDestination() / 2;
CompletableFuture<Void> completable = destination.getConnectionPool().preCreateConnections(preCreate);

// Wait for the connections to be created.
completable.get(5, TimeUnit.SECONDS);
// end::preCreateConnections[]
}

public void unixDomain() throws Exception
{
// tag::unixDomain[]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ public void coreSessionHandler()
org.eclipse.jetty.session.SessionHandler sessionHandler = new org.eclipse.jetty.session.SessionHandler();
sessionHandler.setSessionCookie("SIMPLE");
sessionHandler.setUsingCookies(true);
sessionHandler.setUsingURLs(false);
sessionHandler.setUsingUriParameters(false);
sessionHandler.setSessionPath("/");
server.setHandler(sessionHandler);
sessionHandler.setHandler(new Handler.Abstract()
Expand Down
1 change: 1 addition & 0 deletions documentation/jetty/modules/programming-guide/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,4 @@
* Migration Guides
** xref:migration/94-to-10.adoc[]
** xref:migration/11-to-12.adoc[]
** xref:migration/12.0-to-12.1.adoc[]
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ 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 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.
* `RandomRobinConnectionPool`, similar to `MultiplexConnectionPool` but where connections are reused with an algorithm that chooses them randomly.
* `RandomConnectionPool`, similar to `MultiplexConnectionPool` but where connections are reused with an algorithm that chooses them randomly.

The `ConnectionPool` implementation can be customized for each destination in by setting a `ConnectionPool.Factory` on the `HttpClientTransport`:

Expand All @@ -167,6 +167,34 @@ The `ConnectionPool` implementation can be customized for each destination in by
include::code:example$src/main/java/org/eclipse/jetty/docs/programming/client/http/HTTPClientDocs.java[tags=setConnectionPool]
----

[[connection-pool-precreate-connections]]
=== Pre-Creating Connections

`ConnectionPool` offers the ability to pre-create connections by calling `ConnectionPool.preCreateConnections(int)`.

Pre-creating the connections saves the time and processing spent to establish the TCP connection, performing the TLS handshake (if necessary) and, for HTTP/2 and HTTP/3, perform the initial protocol setup.
This is particularly important for HTTP/2 because in the initial protocol setup the server informs the client of the maximum number of concurrent requests per connection (otherwise assumed to be just `1` by the client).

The scenarios where pre-creating connections is useful are, for example:

* Load testing, where you want to prepare the system with connections already created to avoid paying of cost of connection setup.
* Proxying scenarios, often in conjunction with the use of `RoundRobinConnectionPool` or `RandomConnectionPool`, where the proxy creates early the connections to the backend servers.

This is an example of how to pre-create connections:

[,java,indent=0]
----
include::code:example$src/main/java/org/eclipse/jetty/docs/programming/client/http/HTTPClientDocs.java[tags=preCreateConnections]
----

[NOTE]
====
Pre-creating connections for secure HTTP/1.1 requires you to call `HttpClientTransportOverHTTP.setInitializeConnections(true)`, otherwise only the TCP connection is established, but the TLS handshake is not initiated.
To initialize connections for secure HTTP/1.1, the client sends an initial `OPTIONS * HTTP/1.1` request to the server.
The server must be able to handle this request without closing the connection (in particular it must not add the `Connection: close` header in the response).
====

[[request-processing]]
== HttpClient Request Processing

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//

= Migrating from Jetty 12.0.x to Jetty 12.1.x

[[api-changes]]
== APIs Changes

=== `IteratingCallback`

Class `IteratingCallback` underwent refinements that changed the behavior of the `onCompleteFailure(Throwable)` method.

In Jetty 12.0.x, `IteratingCallback.onCompleteFailure(Throwable)` was called as soon as a failure was reported, without waiting the completion of the asynchronous operation (despite its name containing the word "complete").

For example, if a write operation performed with `IteratingCallback` was pending due to TCP congestion, and a timeout happened, `onCompleteFailure(Throwable)` was called as soon as the timeout happened, without waiting for the TCP congestion to resolve.

In Jetty 12.1.x, the same behavior is achieved by `IteratingCallback.onFailure(Throwable)`, so applications should review their usage of `IteratingCallback` and change the overrides of `onCompleteFailure(Throwable)` to override `onFailure(Throwable)` instead.
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,49 @@ public class HttpClientConnectionFactory implements ClientConnectionFactory
/**
* <p>Representation of the {@code HTTP/1.1} application protocol used by {@link HttpClientTransportDynamic}.</p>
*/
public static final Info HTTP11 = new HTTP11(new HttpClientConnectionFactory());
public static final Info HTTP11 = new HTTP11();

private boolean initializeConnections;

/**
* @return whether newly created connections should be initialized with an {@code OPTIONS * HTTP/1.1} request
*/
public boolean isInitializeConnections()
{
return initializeConnections;
}

/**
* @param initialize whether newly created connections should be initialized with an {@code OPTIONS * HTTP/1.1} request
*/
public void setInitializeConnections(boolean initialize)
{
this.initializeConnections = initialize;
}

@Override
public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context)
{
HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, context);
connection.setInitialize(isInitializeConnections());
return customize(connection, context);
}

private static class HTTP11 extends Info
/**
* <p>Representation of the {@code HTTP/1.1} application protocol used by {@link HttpClientTransportDynamic}.</p>
* <p>Applications should prefer using the constant {@link HttpClientConnectionFactory#HTTP11}, unless they
* need to customize the associated {@link HttpClientConnectionFactory}.</p>
*/
public static class HTTP11 extends Info
{
private static final List<String> protocols = List.of("http/1.1");

private HTTP11(ClientConnectionFactory factory)
public HTTP11()
{
this(new HttpClientConnectionFactory());
}

public HTTP11(ClientConnectionFactory factory)
{
super(factory);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
import org.eclipse.jetty.client.DuplexConnectionPool;
import org.eclipse.jetty.client.Origin;
import org.eclipse.jetty.client.Request;
import org.eclipse.jetty.io.ClientConnectionFactory;
import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.ProcessorUtils;
Expand All @@ -37,7 +36,7 @@ public class HttpClientTransportOverHTTP extends AbstractConnectorHttpClientTran
public static final Origin.Protocol HTTP11 = new Origin.Protocol(List.of("http/1.1"), false);
private static final Logger LOG = LoggerFactory.getLogger(HttpClientTransportOverHTTP.class);

private final ClientConnectionFactory factory = new HttpClientConnectionFactory();
private final HttpClientConnectionFactory factory = new HttpClientConnectionFactory();
private int headerCacheSize = 1024;
private boolean headerCacheCaseSensitive;

Expand Down Expand Up @@ -79,25 +78,54 @@ public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<Stri
return connection;
}

@ManagedAttribute("The maximum allowed size in bytes for an HTTP header field cache")
/**
* @return the max size in bytes for the HTTP header field cache
*/
@ManagedAttribute("The maximum allowed size in bytes for the HTTP header field cache")
public int getHeaderCacheSize()
{
return headerCacheSize;
}

/**
* @param headerCacheSize the max size in bytes for the HTTP header field cache
*/
public void setHeaderCacheSize(int headerCacheSize)
{
this.headerCacheSize = headerCacheSize;
}

@ManagedAttribute("Whether the header field cache is case sensitive")
/**
* @return whether the HTTP header field cache is case-sensitive
*/
@ManagedAttribute("Whether the HTTP header field cache is case-sensitive")
public boolean isHeaderCacheCaseSensitive()
{
return headerCacheCaseSensitive;
}

/**
* @param headerCacheCaseSensitive whether the HTTP header field cache is case-sensitive
*/
public void setHeaderCacheCaseSensitive(boolean headerCacheCaseSensitive)
{
this.headerCacheCaseSensitive = headerCacheCaseSensitive;
}

/**
* @return whether newly created connections should be initialized with an {@code OPTIONS * HTTP/1.1} request
*/
@ManagedAttribute("Whether newly created connections should be initialized with an OPTIONS * HTTP/1.1 request")
public boolean isInitializeConnections()
{
return factory.isInitializeConnections();
}

/**
* @param initialize whether newly created connections should be initialized with an {@code OPTIONS * HTTP/1.1} request
*/
public void setInitializeConnections(boolean initialize)
{
factory.setInitializeConnections(initialize);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import java.util.concurrent.atomic.LongAdder;

import org.eclipse.jetty.client.Connection;
import org.eclipse.jetty.client.Destination;
import org.eclipse.jetty.client.HttpClientTransport;
import org.eclipse.jetty.client.HttpUpgrader;
import org.eclipse.jetty.client.Request;
Expand All @@ -40,6 +41,7 @@
import org.eclipse.jetty.client.transport.IConnection;
import org.eclipse.jetty.client.transport.SendFailure;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.io.AbstractConnection;
import org.eclipse.jetty.io.EndPoint;
Expand All @@ -61,6 +63,7 @@ public class HttpConnectionOverHTTP extends AbstractConnection implements IConne
private final LongAdder bytesIn = new LongAdder();
private final LongAdder bytesOut = new LongAdder();
private long idleTimeout;
private boolean initialize;

public HttpConnectionOverHTTP(EndPoint endPoint, Map<String, Object> context)
{
Expand Down Expand Up @@ -159,12 +162,46 @@ public SendFailure send(HttpExchange exchange)
return delegate.send(exchange);
}

/**
* @return whether to initialize the connection with an {@code OPTIONS * HTTP/1.1} request.
*/
public boolean isInitialize()
{
return initialize;
}

/**
* @param initialize whether to initialize the connection with an {@code OPTIONS * HTTP/1.1} request.
*/
public void setInitialize(boolean initialize)
{
this.initialize = initialize;
}

@Override
public void onOpen()
{
super.onOpen();
fillInterested();
promise.succeeded(this);
boolean initialize = isInitialize();
if (initialize)
{
Destination destination = getHttpDestination();
Request request = destination.getHttpClient().newRequest(destination.getOrigin().asString())
.method(HttpMethod.OPTIONS)
.path("*");
send(request, result ->
{
if (result.isSucceeded())
promise.succeeded(this);
else
promise.failed(result.getFailure());
});
}
else
{
promise.succeeded(this);
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -703,7 +703,7 @@ public void testCountersSweepToStringThroughLifecycle(ConnectionPoolFactory fact
assertThat(connectionPool.toString(), not(nullValue()));
}

private static class ConnectionPoolFactory
public static class ConnectionPoolFactory
{
private final String name;
private final ConnectionPool.Factory factory;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -444,14 +444,12 @@ public AuthenticationState validateRequest(Request request, Response response, C
return AuthenticationState.SEND_FAILURE;
}

// TODO: No session API to work this out?
/*
if (request.isRequestedSessionIdFromURL())
String sessionIdFrom = (String)request.getAttribute("org.eclipse.jetty.session.RequestedSession.sessionIdFrom");
if (sessionIdFrom != null && !sessionIdFrom.startsWith("cookie"))
{
sendError(req, res, cb, "Session ID must be a cookie to support OpenID authentication");
return Authentication.SEND_FAILURE;
sendError(request, response, cb, "Session ID must be a cookie to support OpenID authentication");
return AuthenticationState.SEND_FAILURE;
}
*/

// Handle a request for authentication.
if (isJSecurityCheck(uri))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -396,5 +396,4 @@ public Session getSession(boolean create)
return null;
}
}

}
Loading

0 comments on commit 55fa414

Please sign in to comment.