Issue #6728 - QUIC and HTTP/3

- Improved configuration of client and server.
- Started implementation of HttpClientTransportOverHTTP3.

Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
This commit is contained in:
Simone Bordet 2021-10-15 14:53:51 +02:00
parent 336f39c73e
commit a803dfa44f
25 changed files with 735 additions and 86 deletions

View File

@ -172,6 +172,31 @@
<artifactId>http2-server</artifactId>
<version>10.0.8-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.http3</groupId>
<artifactId>http3-client</artifactId>
<version>10.0.8-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.http3</groupId>
<artifactId>http3-common</artifactId>
<version>10.0.8-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.http3</groupId>
<artifactId>http3-http-client-transport</artifactId>
<version>10.0.8-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.http3</groupId>
<artifactId>http3-qpack</artifactId>
<version>10.0.8-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.http3</groupId>
<artifactId>http3-server</artifactId>
<version>10.0.8-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-http-spi</artifactId>
@ -247,6 +272,26 @@
<artifactId>jetty-osgi-boot-warurl</artifactId>
<version>10.0.8-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.quic</groupId>
<artifactId>quic-client</artifactId>
<version>10.0.8-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.quic</groupId>
<artifactId>quic-common</artifactId>
<version>10.0.8-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.quic</groupId>
<artifactId>quic-quiche</artifactId>
<version>10.0.8-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.quic</groupId>
<artifactId>quic-server</artifactId>
<version>10.0.8-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.osgi</groupId>
<artifactId>jetty-httpservice</artifactId>

View File

@ -21,15 +21,13 @@ import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.jetty.http3.HTTP3Configuration;
import org.eclipse.jetty.http3.api.Session;
import org.eclipse.jetty.io.ClientConnectionFactory;
import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.quic.client.ClientQuicConnection;
import org.eclipse.jetty.quic.client.QuicClientConnectorConfigurator;
import org.eclipse.jetty.quic.common.QuicConfiguration;
import org.eclipse.jetty.quic.common.QuicConnection;
import org.eclipse.jetty.quic.common.QuicSessionContainer;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -48,17 +46,24 @@ public class HTTP3Client extends ContainerLifeCycle
public static final String SESSION_PROMISE_CONTEXT_KEY = CLIENT_CONTEXT_KEY + ".promise";
private static final Logger LOG = LoggerFactory.getLogger(HTTP3Client.class);
private final HTTP3Configuration http3Configuration = new HTTP3Configuration();
private final QuicSessionContainer container = new QuicSessionContainer();
private final HTTP3Configuration configuration = new HTTP3Configuration();
private final ClientConnector connector;
private List<String> protocols = List.of("h3");
private final QuicConfiguration quicConfiguration;
public HTTP3Client()
{
this.connector = new ClientConnector(new QuicClientConnectorConfigurator(this::configureConnection));
QuicClientConnectorConfigurator configurator = new QuicClientConnectorConfigurator(this::configureConnection);
this.connector = new ClientConnector(configurator);
this.quicConfiguration = configurator.getQuicConfiguration();
addBean(connector);
addBean(configuration);
addBean(quicConfiguration);
addBean(http3Configuration);
addBean(container);
// Allow the mandatory unidirectional streams, plus pushed streams.
quicConfiguration.setMaxUnidirectionalRemoteStreams(48);
quicConfiguration.setUnidirectionalStreamRecvWindow(4 * 1024 * 1024);
quicConfiguration.setProtocols(List.of("h3"));
}
public ClientConnector getClientConnector()
@ -66,32 +71,29 @@ public class HTTP3Client extends ContainerLifeCycle
return connector;
}
public HTTP3Configuration getConfiguration()
public QuicConfiguration getQuicConfiguration()
{
return configuration;
return quicConfiguration;
}
@ManagedAttribute("The ALPN protocol list")
public List<String> getProtocols()
public HTTP3Configuration getHTTP3Configuration()
{
return protocols;
}
public void setProtocols(List<String> protocols)
{
this.protocols = protocols;
return http3Configuration;
}
public CompletableFuture<Session.Client> connect(SocketAddress address, Session.Client.Listener listener)
{
Map<String, Object> context = new ConcurrentHashMap<>();
return connect(address, listener, context);
}
public CompletableFuture<Session.Client> connect(SocketAddress address, Session.Client.Listener listener, Map<String, Object> context)
{
Promise.Completable<Session.Client> completable = new Promise.Completable<>();
ClientConnectionFactory factory = new HTTP3ClientConnectionFactory();
context.put(CLIENT_CONTEXT_KEY, this);
context.put(SESSION_LISTENER_CONTEXT_KEY, listener);
context.put(SESSION_PROMISE_CONTEXT_KEY, completable);
context.put(ClientQuicConnection.APPLICATION_PROTOCOLS, getProtocols());
context.put(ClientConnector.CLIENT_CONNECTION_FACTORY_CONTEXT_KEY, factory);
context.computeIfAbsent(ClientConnector.CLIENT_CONNECTION_FACTORY_CONTEXT_KEY, key -> new HTTP3ClientConnectionFactory());
context.put(ClientConnector.CONNECTION_PROMISE_CONTEXT_KEY, Promise.from(ioConnection -> {}, completable::failed));
if (LOG.isDebugEnabled())
@ -107,10 +109,10 @@ public class HTTP3Client extends ContainerLifeCycle
{
QuicConnection quicConnection = (QuicConnection)connection;
quicConnection.addEventListener(container);
quicConnection.setInputBufferSize(getConfiguration().getInputBufferSize());
quicConnection.setOutputBufferSize(getConfiguration().getOutputBufferSize());
quicConnection.setUseInputDirectByteBuffers(getConfiguration().isUseInputDirectByteBuffers());
quicConnection.setUseOutputDirectByteBuffers(getConfiguration().isUseOutputDirectByteBuffers());
quicConnection.setInputBufferSize(getHTTP3Configuration().getInputBufferSize());
quicConnection.setOutputBufferSize(getHTTP3Configuration().getOutputBufferSize());
quicConnection.setUseInputDirectByteBuffers(getHTTP3Configuration().isUseInputDirectByteBuffers());
quicConnection.setUseOutputDirectByteBuffers(getHTTP3Configuration().isUseOutputDirectByteBuffers());
}
return connection;
}

View File

@ -41,8 +41,7 @@ public class HTTP3ClientConnectionFactory implements ClientConnectionFactory, Pr
Session.Client.Listener listener = (Session.Client.Listener)context.get(HTTP3Client.SESSION_LISTENER_CONTEXT_KEY);
@SuppressWarnings("unchecked")
Promise<Session.Client> promise = (Promise<Session.Client>)context.get(HTTP3Client.SESSION_PROMISE_CONTEXT_KEY);
ClientHTTP3Session session = new ClientHTTP3Session(client.getConfiguration(), (ClientQuicSession)quicSession, listener, promise);
session.setStreamIdleTimeout(client.getConfiguration().getStreamIdleTimeout());
ClientHTTP3Session session = new ClientHTTP3Session(client.getHTTP3Configuration(), (ClientQuicSession)quicSession, listener, promise);
if (LOG.isDebugEnabled())
LOG.debug("created protocol-specific {}", session);
return session;

View File

@ -54,6 +54,7 @@ public class ClientHTTP3Session extends ClientProtocolSession
super(quicSession);
this.session = new HTTP3SessionClient(this, listener, promise);
addBean(session);
session.setStreamIdleTimeout(configuration.getStreamIdleTimeout());
if (LOG.isDebugEnabled())
LOG.debug("initializing HTTP/3 streams");
@ -95,16 +96,6 @@ public class ClientHTTP3Session extends ClientProtocolSession
return session;
}
public long getStreamIdleTimeout()
{
return session.getStreamIdleTimeout();
}
public void setStreamIdleTimeout(long streamIdleTimeout)
{
session.setStreamIdleTimeout(streamIdleTimeout);
}
@Override
protected void doStart() throws Exception
{

View File

@ -27,7 +27,8 @@ module org.eclipse.jetty.http3.common
exports org.eclipse.jetty.http3.internal to
org.eclipse.jetty.http3.client,
org.eclipse.jetty.http3.server;
org.eclipse.jetty.http3.server,
org.eclipse.jetty.http3.http.client.transport;
exports org.eclipse.jetty.http3.internal.generator to
org.eclipse.jetty.http3.client,
org.eclipse.jetty.http3.server;

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.eclipse.jetty.http3</groupId>
<artifactId>http3-parent</artifactId>
<version>10.0.8-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>http3-http-client-transport</artifactId>
<dependencies>
<dependency>
<groupId>org.eclipse.jetty.http3</groupId>
<artifactId>http3-client</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-client</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,22 @@
//
// ========================================================================
// Copyright (c) 1995-2021 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
// ========================================================================
//
module org.eclipse.jetty.http3.http.client.transport
{
requires org.slf4j;
requires transitive org.eclipse.jetty.client;
requires transitive org.eclipse.jetty.http3.client;
exports org.eclipse.jetty.http3.client.http;
}

View File

@ -0,0 +1,74 @@
//
// ========================================================================
// Copyright (c) 1995-2021 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
// ========================================================================
//
package org.eclipse.jetty.http3.client.http;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import org.eclipse.jetty.client.dynamic.HttpClientTransportDynamic;
import org.eclipse.jetty.client.http.HttpClientConnectionFactory;
import org.eclipse.jetty.http3.client.HTTP3Client;
import org.eclipse.jetty.http3.client.HTTP3ClientConnectionFactory;
import org.eclipse.jetty.io.ClientConnectionFactory;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
public class ClientConnectionFactoryOverHTTP3 extends ContainerLifeCycle implements ClientConnectionFactory
{
private final ClientConnectionFactory factory = new HTTP3ClientConnectionFactory();
private final HTTP3Client client;
public ClientConnectionFactoryOverHTTP3(HTTP3Client client)
{
this.client = client;
addBean(client);
}
@Override
public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
{
// HTTPSessionListenerPromise listenerPromise = new HTTPSessionListenerPromise(context);
// context.put(HTTP2ClientConnectionFactory.CLIENT_CONTEXT_KEY, client);
// context.put(HTTP2ClientConnectionFactory.SESSION_LISTENER_CONTEXT_KEY, listenerPromise);
// context.put(HTTP2ClientConnectionFactory.SESSION_PROMISE_CONTEXT_KEY, listenerPromise);
// return factory.newConnection(endPoint, context);
return null;
}
/**
* <p>Representation of the {@code HTTP/3} application protocol used by {@link HttpClientTransportDynamic}.</p>
*
* @see HttpClientConnectionFactory#HTTP11
*/
public static class HTTP3 extends Info
{
public HTTP3(HTTP3Client client)
{
super(new ClientConnectionFactoryOverHTTP3(client));
}
@Override
public List<String> getProtocols(boolean secure)
{
return List.of("h3");
}
@Override
public String toString()
{
return String.format("%s@%x", getClass().getSimpleName(), hashCode());
}
}
}

View File

@ -0,0 +1,148 @@
//
// ========================================================================
// Copyright (c) 1995-2021 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
// ========================================================================
//
package org.eclipse.jetty.http3.client.http;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.eclipse.jetty.client.AbstractHttpClientTransport;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.HttpClientTransport;
import org.eclipse.jetty.client.HttpDestination;
import org.eclipse.jetty.client.HttpRequest;
import org.eclipse.jetty.client.MultiplexConnectionPool;
import org.eclipse.jetty.client.MultiplexHttpDestination;
import org.eclipse.jetty.client.Origin;
import org.eclipse.jetty.http3.HTTP3Configuration;
import org.eclipse.jetty.http3.api.Session;
import org.eclipse.jetty.http3.client.HTTP3Client;
import org.eclipse.jetty.http3.client.http.internal.HttpConnectionOverHTTP3;
import org.eclipse.jetty.http3.frames.SettingsFrame;
import org.eclipse.jetty.http3.internal.HTTP3Session;
import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.Promise;
public class HttpClientTransportOverHTTP3 extends AbstractHttpClientTransport
{
private final HTTP3Client client;
public HttpClientTransportOverHTTP3(HTTP3Client client)
{
this.client = Objects.requireNonNull(client);
addBean(client);
setConnectionPoolFactory(destination ->
{
HttpClient httpClient = getHttpClient();
return new MultiplexConnectionPool(destination, httpClient.getMaxConnectionsPerDestination(), destination, httpClient.getMaxRequestsQueuedPerDestination());
});
}
public HTTP3Client getHTTP3Client()
{
return client;
}
@Override
protected void doStart() throws Exception
{
if (!client.isStarted())
{
HttpClient httpClient = getHttpClient();
ClientConnector clientConnector = this.client.getClientConnector();
clientConnector.setExecutor(httpClient.getExecutor());
clientConnector.setScheduler(httpClient.getScheduler());
clientConnector.setByteBufferPool(httpClient.getByteBufferPool());
clientConnector.setConnectTimeout(Duration.ofMillis(httpClient.getConnectTimeout()));
clientConnector.setConnectBlocking(httpClient.isConnectBlocking());
clientConnector.setBindAddress(httpClient.getBindAddress());
clientConnector.setIdleTimeout(Duration.ofMillis(httpClient.getIdleTimeout()));
HTTP3Configuration configuration = client.getHTTP3Configuration();
configuration.setInputBufferSize(httpClient.getResponseBufferSize());
configuration.setUseInputDirectByteBuffers(httpClient.isUseInputDirectByteBuffers());
configuration.setUseOutputDirectByteBuffers(httpClient.isUseOutputDirectByteBuffers());
}
super.doStart();
}
@Override
public Origin newOrigin(HttpRequest request)
{
return getHttpClient().createOrigin(request, new Origin.Protocol(List.of("h3"), false));
}
@Override
public HttpDestination newHttpDestination(Origin origin)
{
SocketAddress address = origin.getAddress().getSocketAddress();
return new MultiplexHttpDestination(getHttpClient(), origin, getHTTP3Client().getClientConnector().isIntrinsicallySecure(address));
}
@Override
public void connect(InetSocketAddress address, Map<String, Object> context)
{
connect((SocketAddress)address, context);
}
@Override
public void connect(SocketAddress address, Map<String, Object> context)
{
HttpClient httpClient = getHttpClient();
ClientConnector clientConnector = client.getClientConnector();
clientConnector.setConnectTimeout(Duration.ofMillis(httpClient.getConnectTimeout()));
clientConnector.setConnectBlocking(httpClient.isConnectBlocking());
clientConnector.setBindAddress(httpClient.getBindAddress());
HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
context.put(ClientConnector.CLIENT_CONNECTION_FACTORY_CONTEXT_KEY, destination.getClientConnectionFactory());
getHTTP3Client().connect(address, new SessionClientListener(context), context);
}
@Override
public Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
{
return null;
}
private class SessionClientListener implements Session.Client.Listener
{
private final Map<String, Object> context;
private SessionClientListener(Map<String, Object> context)
{
this.context = context;
}
@SuppressWarnings("unchecked")
private Promise<org.eclipse.jetty.client.api.Connection> httpConnectionPromise()
{
return (Promise<org.eclipse.jetty.client.api.Connection>)context.get(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
}
@Override
public void onSettings(Session session, SettingsFrame frame)
{
HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
HttpConnectionOverHTTP3 connection = new HttpConnectionOverHTTP3(destination, (HTTP3Session)session);
httpConnectionPromise().succeeded(connection);
}
}
}

View File

@ -0,0 +1,52 @@
//
// ========================================================================
// Copyright (c) 1995-2021 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
// ========================================================================
//
package org.eclipse.jetty.http3.client.http.internal;
import org.eclipse.jetty.client.HttpChannel;
import org.eclipse.jetty.client.HttpDestination;
import org.eclipse.jetty.client.HttpExchange;
import org.eclipse.jetty.client.HttpReceiver;
import org.eclipse.jetty.client.HttpSender;
public class HttpChannelOverHTTP3 extends HttpChannel
{
public HttpChannelOverHTTP3(HttpDestination destination)
{
super(destination);
}
@Override
protected HttpSender getHttpSender()
{
return null;
}
@Override
protected HttpReceiver getHttpReceiver()
{
return null;
}
@Override
public void send(HttpExchange exchange)
{
}
@Override
public void release()
{
}
}

View File

@ -0,0 +1,104 @@
//
// ========================================================================
// Copyright (c) 1995-2021 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
// ========================================================================
//
package org.eclipse.jetty.http3.client.http.internal;
import java.nio.channels.AsynchronousCloseException;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.jetty.client.ConnectionPool;
import org.eclipse.jetty.client.HttpChannel;
import org.eclipse.jetty.client.HttpConnection;
import org.eclipse.jetty.client.HttpDestination;
import org.eclipse.jetty.client.HttpExchange;
import org.eclipse.jetty.client.HttpRequest;
import org.eclipse.jetty.client.SendFailure;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http3.internal.HTTP3Session;
public class HttpConnectionOverHTTP3 extends HttpConnection implements ConnectionPool.Multiplexable
{
private final Set<HttpChannel> activeChannels = ConcurrentHashMap.newKeySet();
private final AtomicBoolean closed = new AtomicBoolean();
private final HTTP3Session session;
public HttpConnectionOverHTTP3(HttpDestination destination, HTTP3Session session)
{
super(destination);
this.session = session;
}
@Override
public int getMaxMultiplex()
{
// TODO: need to retrieve this via stats, and it's a fixed value.
return 1;
}
@Override
protected Iterator<HttpChannel> getHttpChannels()
{
return activeChannels.iterator();
}
@Override
public SendFailure send(HttpExchange exchange)
{
HttpRequest request = exchange.getRequest();
request.version(HttpVersion.HTTP_3);
normalizeRequest(request);
// One connection maps to N channels, so one channel for each exchange.
HttpChannelOverHTTP3 channel = new HttpChannelOverHTTP3(getHttpDestination());
activeChannels.add(channel);
return send(channel, exchange);
}
@Override
public boolean isClosed()
{
return closed.get();
}
@Override
public void close()
{
close(new AsynchronousCloseException());
}
private void close(Throwable failure)
{
if (closed.compareAndSet(false, true))
{
getHttpDestination().remove(this);
abort(failure);
session.goAway(false);
destroy();
}
}
private void abort(Throwable failure)
{
for (HttpChannel channel : activeChannels)
{
HttpExchange exchange = channel.getHttpExchange();
if (exchange != null)
exchange.getRequest().abort(failure);
}
activeChannels.clear();
}
}

View File

@ -63,9 +63,7 @@ public abstract class AbstractHTTP3ServerConnectionFactory extends AbstractConne
@Override
public ProtocolSession newProtocolSession(QuicSession quicSession, Map<String, Object> context)
{
ServerHTTP3Session session = new ServerHTTP3Session(getConfiguration(), (ServerQuicSession)quicSession, listener);
session.setStreamIdleTimeout(getConfiguration().getStreamIdleTimeout());
return session;
return new ServerHTTP3Session(getConfiguration(), (ServerQuicSession)quicSession, listener);
}
@Override

View File

@ -0,0 +1,41 @@
//
// ========================================================================
// Copyright (c) 1995-2021 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
// ========================================================================
//
package org.eclipse.jetty.http3.server;
import java.util.concurrent.Executor;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.quic.server.QuicServerConnector;
import org.eclipse.jetty.server.ConnectionFactory;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.Scheduler;
public class HTTP3ServerConnector extends QuicServerConnector
{
public HTTP3ServerConnector(Server server, SslContextFactory.Server sslContextFactory, ConnectionFactory... factories)
{
this(server, null, null, null, sslContextFactory, factories);
}
public HTTP3ServerConnector(Server server, Executor executor, Scheduler scheduler, ByteBufferPool bufferPool, SslContextFactory.Server sslContextFactory, ConnectionFactory... factories)
{
super(server, executor, scheduler, bufferPool, sslContextFactory, factories);
// Max concurrent streams that a client can open.
getQuicConfiguration().setMaxBidirectionalRemoteStreams(128);
// HTTP/3 requires a few mandatory unidirectional streams.
getQuicConfiguration().setMaxUnidirectionalRemoteStreams(8);
getQuicConfiguration().setUnidirectionalStreamRecvWindow(1024 * 1024);
}
}

View File

@ -59,6 +59,7 @@ public class ServerHTTP3Session extends ServerProtocolSession
super(quicSession);
this.session = new HTTP3SessionServer(this, listener);
addBean(session);
session.setStreamIdleTimeout(configuration.getStreamIdleTimeout());
if (LOG.isDebugEnabled())
LOG.debug("initializing HTTP/3 streams");
@ -103,11 +104,6 @@ public class ServerHTTP3Session extends ServerProtocolSession
return session;
}
public long getStreamIdleTimeout()
{
return session.getStreamIdleTimeout();
}
public void offer(Runnable task)
{
producer.offer(task);
@ -126,11 +122,6 @@ public class ServerHTTP3Session extends ServerProtocolSession
return result;
}
public void setStreamIdleTimeout(long streamIdleTimeout)
{
session.setStreamIdleTimeout(streamIdleTimeout);
}
@Override
protected void doStart() throws Exception
{

View File

@ -24,8 +24,8 @@ import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http3.api.Session;
import org.eclipse.jetty.http3.client.HTTP3Client;
import org.eclipse.jetty.http3.server.HTTP3ServerConnectionFactory;
import org.eclipse.jetty.http3.server.HTTP3ServerConnector;
import org.eclipse.jetty.http3.server.RawHTTP3ServerConnectionFactory;
import org.eclipse.jetty.quic.server.QuicServerConnector;
import org.eclipse.jetty.server.ConnectionFactory;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
@ -41,7 +41,7 @@ public class AbstractClientServerTest
@RegisterExtension
final BeforeTestExecutionCallback printMethodName = context ->
System.err.printf("Running %s.%s() %s%n", context.getRequiredTestClass().getSimpleName(), context.getRequiredTestMethod().getName(), context.getDisplayName());
protected QuicServerConnector connector;
protected HTTP3ServerConnector connector;
protected HTTP3Client client;
protected Server server;
@ -73,7 +73,7 @@ public class AbstractClientServerTest
QueuedThreadPool serverThreads = new QueuedThreadPool();
serverThreads.setName("server");
server = new Server(serverThreads);
connector = new QuicServerConnector(server, sslContextFactory, serverConnectionFactory);
connector = new HTTP3ServerConnector(server, sslContextFactory, serverConnectionFactory);
server.addConnector(connector);
}

View File

@ -282,7 +282,7 @@ public class ClientServerTest extends AbstractClientServerTest
});
int maxRequestHeadersSize = 128;
client.getConfiguration().setMaxRequestHeadersSize(maxRequestHeadersSize);
client.getHTTP3Configuration().setMaxRequestHeadersSize(maxRequestHeadersSize);
Session.Client clientSession = newSession(new Session.Client.Listener() {});
CountDownLatch requestFailureLatch = new CountDownLatch(1);

View File

@ -97,7 +97,7 @@ public class StreamIdleTimeoutTest extends AbstractClientServerTest
});
long streamIdleTimeout = 1000;
client.getConfiguration().setStreamIdleTimeout(streamIdleTimeout);
client.getHTTP3Configuration().setStreamIdleTimeout(streamIdleTimeout);
Session.Client clientSession = newSession(new Session.Client.Listener() {});

View File

@ -17,6 +17,7 @@
<module>http3-common</module>
<module>http3-server</module>
<module>http3-client</module>
<module>http3-http-client-transport</module>
<module>http3-tests</module>
</modules>

View File

@ -30,6 +30,7 @@ import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.io.DatagramChannelEndPoint;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.RuntimeIOException;
import org.eclipse.jetty.quic.common.QuicConfiguration;
import org.eclipse.jetty.quic.common.QuicConnection;
import org.eclipse.jetty.quic.common.QuicSession;
import org.eclipse.jetty.quic.quiche.QuicheConfig;
@ -47,7 +48,6 @@ import org.slf4j.LoggerFactory;
*/
public class ClientQuicConnection extends QuicConnection
{
public static final String APPLICATION_PROTOCOLS = "org.eclipse.jetty.quic.application.protocols";
private static final Logger LOG = LoggerFactory.getLogger(ClientQuicConnection.class);
private final Map<SocketAddress, ClientQuicSession> pendingSessions = new ConcurrentHashMap<>();
@ -66,9 +66,10 @@ public class ClientQuicConnection extends QuicConnection
{
super.onOpen();
@SuppressWarnings("unchecked")
List<String> protocols = (List<String>)context.get(APPLICATION_PROTOCOLS);
if (protocols == null)
QuicConfiguration quicConfiguration = (QuicConfiguration)context.get(QuicConfiguration.CONTEXT_KEY);
List<String> protocols = quicConfiguration.getProtocols();
if (protocols == null || protocols.isEmpty())
{
HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
if (destination != null)
@ -77,19 +78,18 @@ public class ClientQuicConnection extends QuicConnection
throw new IllegalStateException("Missing ALPN protocols");
}
// TODO: pull the config settings from somewhere else TBD (context?)
QuicheConfig quicheConfig = new QuicheConfig();
quicheConfig.setApplicationProtos(protocols.toArray(String[]::new));
quicheConfig.setDisableActiveMigration(true);
quicheConfig.setVerifyPeer(false);
// Idle timeouts must not be managed by Quiche.
quicheConfig.setMaxIdleTimeout(0L);
quicheConfig.setInitialMaxData(10_000_000L);
quicheConfig.setInitialMaxStreamDataBidiLocal(10_000_000L);
quicheConfig.setInitialMaxStreamDataBidiRemote(10000000L);
quicheConfig.setInitialMaxStreamDataUni(10_000_000L);
quicheConfig.setInitialMaxStreamsUni(100L);
quicheConfig.setInitialMaxStreamsBidi(100L);
quicheConfig.setInitialMaxData((long)quicConfiguration.getSessionRecvWindow());
quicheConfig.setInitialMaxStreamDataBidiLocal((long)quicConfiguration.getBidirectionalStreamRecvWindow());
quicheConfig.setInitialMaxStreamDataBidiRemote((long)quicConfiguration.getBidirectionalStreamRecvWindow());
quicheConfig.setInitialMaxStreamDataUni((long)quicConfiguration.getUnidirectionalStreamRecvWindow());
quicheConfig.setInitialMaxStreamsUni((long)quicConfiguration.getMaxUnidirectionalRemoteStreams());
quicheConfig.setInitialMaxStreamsBidi((long)quicConfiguration.getMaxBidirectionalRemoteStreams());
quicheConfig.setCongestionControl(QuicheConfig.CongestionControl.CUBIC);
InetSocketAddress remoteAddress = (InetSocketAddress)context.get(ClientConnector.REMOTE_SOCKET_ADDRESS_CONTEXT_KEY);

View File

@ -27,21 +27,29 @@ import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.DatagramChannelEndPoint;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.ManagedSelector;
import org.eclipse.jetty.quic.common.QuicConfiguration;
public class QuicClientConnectorConfigurator extends ClientConnector.Configurator
{
public static final String CONNECTION_CONFIGURATOR_CONTEXT_KEY = QuicClientConnectorConfigurator.class.getSimpleName() + ".connectionConfigurator";
private final UnaryOperator<Connection> connectionConfigurator;
private final QuicConfiguration configuration = new QuicConfiguration();
private final UnaryOperator<Connection> configurator;
public QuicClientConnectorConfigurator()
{
this(UnaryOperator.identity());
}
public QuicClientConnectorConfigurator(UnaryOperator<Connection> connectionConfigurator)
public QuicClientConnectorConfigurator(UnaryOperator<Connection> configurator)
{
this.connectionConfigurator = Objects.requireNonNull(connectionConfigurator);
this.configurator = Objects.requireNonNull(configurator);
// Initialize to sane defaults for a client.
configuration.setSessionRecvWindow(16 * 1024 * 1024);
configuration.setBidirectionalStreamRecvWindow(8 * 1024 * 1024);
}
public QuicConfiguration getQuicConfiguration()
{
return configuration;
}
@Override
@ -53,7 +61,7 @@ public class QuicClientConnectorConfigurator extends ClientConnector.Configurato
@Override
public ChannelWithAddress newChannelWithAddress(ClientConnector clientConnector, SocketAddress address, Map<String, Object> context) throws IOException
{
context.putIfAbsent(CONNECTION_CONFIGURATOR_CONTEXT_KEY, connectionConfigurator);
context.put(QuicConfiguration.CONTEXT_KEY, configuration);
DatagramChannel channel = DatagramChannel.open();
return new ChannelWithAddress(channel, address);
}
@ -67,10 +75,6 @@ public class QuicClientConnectorConfigurator extends ClientConnector.Configurato
@Override
public Connection newConnection(ClientConnector clientConnector, SocketAddress address, EndPoint endPoint, Map<String, Object> context)
{
@SuppressWarnings("unchecked")
UnaryOperator<Connection> configurator = (UnaryOperator<Connection>)context.get(CONNECTION_CONFIGURATOR_CONTEXT_KEY);
if (configurator == null)
configurator = UnaryOperator.identity();
return configurator.apply(new ClientQuicConnection(clientConnector.getExecutor(), clientConnector.getScheduler(), clientConnector.getByteBufferPool(), endPoint, context));
}
}

View File

@ -86,7 +86,7 @@ public abstract class ProtocolSession extends ContainerLifeCycle
return session.getOrCreateStreamEndPoint(streamId, consumer);
}
private void processWritableStreams()
protected void processWritableStreams()
{
List<Long> writableStreamIds = session.getWritableStreamIds();
if (LOG.isDebugEnabled())

View File

@ -0,0 +1,88 @@
//
// ========================================================================
// Copyright (c) 1995-2021 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
// ========================================================================
//
package org.eclipse.jetty.quic.common;
import java.util.List;
public class QuicConfiguration
{
public static final String CONTEXT_KEY = QuicConfiguration.class.getName();
private List<String> protocols = List.of();
private int maxBidirectionalRemoteStreams;
private int maxUnidirectionalRemoteStreams;
private int sessionRecvWindow;
private int bidirectionalStreamRecvWindow;
private int unidirectionalStreamRecvWindow;
public List<String> getProtocols()
{
return protocols;
}
public void setProtocols(List<String> protocols)
{
this.protocols = protocols;
}
public int getMaxBidirectionalRemoteStreams()
{
return maxBidirectionalRemoteStreams;
}
public void setMaxBidirectionalRemoteStreams(int maxBidirectionalRemoteStreams)
{
this.maxBidirectionalRemoteStreams = maxBidirectionalRemoteStreams;
}
public int getMaxUnidirectionalRemoteStreams()
{
return maxUnidirectionalRemoteStreams;
}
public void setMaxUnidirectionalRemoteStreams(int maxUnidirectionalRemoteStreams)
{
this.maxUnidirectionalRemoteStreams = maxUnidirectionalRemoteStreams;
}
public int getSessionRecvWindow()
{
return sessionRecvWindow;
}
public void setSessionRecvWindow(int sessionRecvWindow)
{
this.sessionRecvWindow = sessionRecvWindow;
}
public int getBidirectionalStreamRecvWindow()
{
return bidirectionalStreamRecvWindow;
}
public void setBidirectionalStreamRecvWindow(int bidirectionalStreamRecvWindow)
{
this.bidirectionalStreamRecvWindow = bidirectionalStreamRecvWindow;
}
public int getUnidirectionalStreamRecvWindow()
{
return unidirectionalStreamRecvWindow;
}
public void setUnidirectionalStreamRecvWindow(int unidirectionalStreamRecvWindow)
{
this.unidirectionalStreamRecvWindow = unidirectionalStreamRecvWindow;
}
}

View File

@ -30,6 +30,7 @@ import org.eclipse.jetty.io.DatagramChannelEndPoint;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.ManagedSelector;
import org.eclipse.jetty.io.SelectorManager;
import org.eclipse.jetty.quic.common.QuicConfiguration;
import org.eclipse.jetty.quic.common.QuicSessionContainer;
import org.eclipse.jetty.quic.quiche.QuicheConfig;
import org.eclipse.jetty.quic.quiche.SSLKeyPair;
@ -42,6 +43,7 @@ import org.eclipse.jetty.util.thread.Scheduler;
public class QuicServerConnector extends AbstractNetworkConnector
{
private final QuicConfiguration quicConfiguration = new QuicConfiguration();
private final QuicSessionContainer container = new QuicSessionContainer();
private final ServerDatagramSelectorManager selectorManager;
private final SslContextFactory.Server sslContextFactory;
@ -65,7 +67,19 @@ public class QuicServerConnector extends AbstractNetworkConnector
addBean(this.selectorManager);
this.sslContextFactory = sslContextFactory;
addBean(this.sslContextFactory);
addBean(quicConfiguration);
addBean(container);
// Initialize to sane defaults for a server.
quicConfiguration.setSessionRecvWindow(4 * 1024 * 1024);
quicConfiguration.setBidirectionalStreamRecvWindow(2 * 1024 * 1024);
// One bidirectional stream to simulate the TCP stream, and no unidirectional streams.
quicConfiguration.setMaxBidirectionalRemoteStreams(1);
quicConfiguration.setMaxUnidirectionalRemoteStreams(0);
}
public QuicConfiguration getQuicConfiguration()
{
return quicConfiguration;
}
@Override
@ -141,18 +155,17 @@ public class QuicServerConnector extends AbstractNetworkConnector
);
File[] pemFiles = keyPair.export(new File(System.getProperty("java.io.tmpdir")));
// TODO: make the QuicheConfig configurable.
quicheConfig.setPrivKeyPemPath(pemFiles[0].getPath());
quicheConfig.setCertChainPemPath(pemFiles[1].getPath());
quicheConfig.setVerifyPeer(false);
// Idle timeouts must not be managed by Quiche.
quicheConfig.setMaxIdleTimeout(0L);
quicheConfig.setInitialMaxData(10000000L);
quicheConfig.setInitialMaxStreamDataBidiLocal(10000000L);
quicheConfig.setInitialMaxStreamDataBidiRemote(10000000L);
quicheConfig.setInitialMaxStreamDataUni(10000000L);
quicheConfig.setInitialMaxStreamsUni(100L);
quicheConfig.setInitialMaxStreamsBidi(100L);
quicheConfig.setInitialMaxData((long)quicConfiguration.getSessionRecvWindow());
quicheConfig.setInitialMaxStreamDataBidiLocal((long)quicConfiguration.getBidirectionalStreamRecvWindow());
quicheConfig.setInitialMaxStreamDataBidiRemote((long)quicConfiguration.getBidirectionalStreamRecvWindow());
quicheConfig.setInitialMaxStreamDataUni((long)quicConfiguration.getUnidirectionalStreamRecvWindow());
quicheConfig.setInitialMaxStreamsUni((long)quicConfiguration.getMaxUnidirectionalRemoteStreams());
quicheConfig.setInitialMaxStreamsBidi((long)quicConfiguration.getMaxBidirectionalRemoteStreams());
quicheConfig.setCongestionControl(QuicheConfig.CongestionControl.CUBIC);
List<String> protocols = getProtocols();
// This is only needed for Quiche example clients.

View File

@ -39,9 +39,16 @@ public interface Callback extends Invocable
}
};
default void completeWith(CompletableFuture<?> cf)
/**
* <p>Completes this callback with the given {@link CompletableFuture}.</p>
* <p>When the CompletableFuture completes normally, this callback is succeeded;
* when the CompletableFuture completes exceptionally, this callback is failed.</p>
*
* @param completable the CompletableFuture that completes this callback
*/
default void completeWith(CompletableFuture<?> completable)
{
cf.whenComplete((o, x) ->
completable.whenComplete((o, x) ->
{
if (x == null)
succeeded();

45
pom.xml
View File

@ -1634,6 +1634,31 @@
<artifactId>http2-server</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.http3</groupId>
<artifactId>http3-client</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.http3</groupId>
<artifactId>http3-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.http3</groupId>
<artifactId>http3-qpack</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.http3</groupId>
<artifactId>http3-http-client-transport</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.http3</groupId>
<artifactId>http3-server</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.memcached</groupId>
<artifactId>jetty-memcached-sessions</artifactId>
@ -1644,6 +1669,26 @@
<artifactId>jetty-osgi-boot</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.quic</groupId>
<artifactId>quic-client</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.quic</groupId>
<artifactId>quic-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.quic</groupId>
<artifactId>quic-quiche</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.quic</groupId>
<artifactId>quic-server</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.tests</groupId>
<artifactId>jetty-http-tools</artifactId>