Issue #6728 - QUIC and HTTP/3

- Fixed max streams semantic for HTTP/3.
It's not *concurrent* streams but *cumulative* streams that are limited.
Fixed MultiplexConnectionPool to take that into account with the introduction
of ConnectionPool.MaxUsable.

Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
This commit is contained in:
Simone Bordet 2021-11-03 18:06:25 +01:00
parent a31c35e9ad
commit b19c135f46
7 changed files with 81 additions and 31 deletions

View File

@ -124,4 +124,15 @@ public interface ConnectionPool extends Closeable
{
}
}
/**
* Marks a connection as being usable for a maximum number of requests.
*/
interface MaxUsable
{
/**
* @return the max number of requests on a single connection
*/
int getMaxUsageCount();
}
}

View File

@ -34,8 +34,17 @@ public class MultiplexConnectionPool extends AbstractConnectionPool
public MultiplexConnectionPool(HttpDestination destination, Pool.StrategyType strategy, int maxConnections, boolean cache, Callback requester, int maxMultiplex)
{
super(destination, new Pool<Connection>(strategy, maxConnections, cache)
super(destination, new Pool<>(strategy, maxConnections, cache)
{
@Override
protected int getMaxUsageCount(Connection connection)
{
int maxUsage = (connection instanceof MaxUsable)
? ((MaxUsable)connection).getMaxUsageCount()
: super.getMaxUsageCount(connection);
return maxUsage > 0 ? maxUsage : -1;
}
@Override
protected int getMaxMultiplex(Connection connection)
{

View File

@ -52,7 +52,7 @@ public class HttpClientTransportOverHTTP3 extends AbstractHttpClientTransport im
setConnectionPoolFactory(destination ->
{
HttpClient httpClient = getHttpClient();
return new MultiplexConnectionPool(destination, httpClient.getMaxConnectionsPerDestination(), destination, httpClient.getMaxRequestsQueuedPerDestination());
return new MultiplexConnectionPool(destination, httpClient.getMaxConnectionsPerDestination(), destination, 1);
});
}

View File

@ -31,7 +31,7 @@ import org.eclipse.jetty.http3.client.internal.HTTP3SessionClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class HttpConnectionOverHTTP3 extends HttpConnection implements ConnectionPool.Multiplexable
public class HttpConnectionOverHTTP3 extends HttpConnection implements ConnectionPool.Multiplexable, ConnectionPool.MaxUsable
{
private static final Logger LOG = LoggerFactory.getLogger(HttpConnectionOverHTTP3.class);
@ -52,6 +52,14 @@ public class HttpConnectionOverHTTP3 extends HttpConnection implements Connectio
@Override
public int getMaxMultiplex()
{
// As weird as this is, RFC 9000 specifies a *cumulative* number
// for the number of streams that can be opened in a connection.
return getMaxUsageCount();
}
@Override
public int getMaxUsageCount()
{
return session.getMaxLocalStreams();
}

View File

@ -384,14 +384,14 @@ public abstract class QuicSession extends ContainerLifeCycle
if (quicStreamEndPoint == null)
{
if (LOG.isDebugEnabled())
LOG.debug("creating endpoint for stream {} for {}", id, this);
LOG.debug("creating endpoint for stream #{} for {}", id, this);
quicStreamEndPoint = newQuicStreamEndPoint(streamId);
consumer.accept(quicStreamEndPoint);
}
return quicStreamEndPoint;
});
if (LOG.isDebugEnabled())
LOG.debug("returning endpoint for stream {} for {}", streamId, this);
LOG.debug("returning {} for {}", endPoint, this);
return endPoint;
}

View File

@ -19,6 +19,7 @@ import java.net.InetSocketAddress;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.file.Files;
import java.util.EventListener;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@ -47,7 +48,8 @@ public class QuicServerConnector extends AbstractNetworkConnector
private final QuicSessionContainer container = new QuicSessionContainer();
private final ServerDatagramSelectorManager selectorManager;
private final SslContextFactory.Server sslContextFactory;
private final QuicheConfig quicheConfig = new QuicheConfig();
private File privateKeyFile;
private File certificateChainFile;
private volatile DatagramChannel datagramChannel;
private volatile int localPort = -1;
private int inputBufferSize = 2048;
@ -155,23 +157,8 @@ public class QuicServerConnector extends AbstractNetworkConnector
keyManagerPassword == null ? keyStorePassword : keyManagerPassword.toCharArray()
);
File[] pemFiles = keyPair.export(new File(System.getProperty("java.io.tmpdir")));
quicheConfig.setPrivKeyPemPath(pemFiles[0].getPath());
quicheConfig.setCertChainPemPath(pemFiles[1].getPath());
quicheConfig.setVerifyPeer(quicConfiguration.isVerifyPeerCertificates());
// Idle timeouts must not be managed by Quiche.
quicheConfig.setMaxIdleTimeout(0L);
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.
protocols.add(0, "http/0.9");
quicheConfig.setApplicationProtos(protocols.toArray(String[]::new));
privateKeyFile = pemFiles[0];
certificateChainFile = pemFiles[1];
}
@Override
@ -204,6 +191,28 @@ public class QuicServerConnector extends AbstractNetworkConnector
}
}
QuicheConfig newQuicheConfig()
{
QuicheConfig quicheConfig = new QuicheConfig();
quicheConfig.setPrivKeyPemPath(privateKeyFile.getPath());
quicheConfig.setCertChainPemPath(certificateChainFile.getPath());
quicheConfig.setVerifyPeer(quicConfiguration.isVerifyPeerCertificates());
// Idle timeouts must not be managed by Quiche.
quicheConfig.setMaxIdleTimeout(0L);
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.
protocols.add(0, "http/0.9");
quicheConfig.setApplicationProtos(protocols.toArray(String[]::new));
return quicheConfig;
}
@Override
public void setIdleTimeout(long idleTimeout)
{
@ -214,6 +223,9 @@ public class QuicServerConnector extends AbstractNetworkConnector
@Override
protected void doStop() throws Exception
{
deleteFile(privateKeyFile);
deleteFile(certificateChainFile);
// We want the DatagramChannel to be stopped by the SelectorManager.
super.doStop();
@ -225,6 +237,20 @@ public class QuicServerConnector extends AbstractNetworkConnector
selectorManager.removeEventListener(l);
}
private void deleteFile(File file)
{
try
{
if (file != null)
Files.delete(file.toPath());
}
catch (IOException x)
{
if (LOG.isDebugEnabled())
LOG.debug("could not delete {}", file, x);
}
}
@Override
public CompletableFuture<Void> shutdown()
{
@ -261,7 +287,7 @@ public class QuicServerConnector extends AbstractNetworkConnector
@Override
public Connection newConnection(SelectableChannel channel, EndPoint endpoint, Object attachment)
{
ServerQuicConnection connection = new ServerQuicConnection(QuicServerConnector.this, endpoint, quicheConfig);
ServerQuicConnection connection = new ServerQuicConnection(QuicServerConnector.this, endpoint);
connection.addEventListener(container);
connection.setInputBufferSize(getInputBufferSize());
connection.setOutputBufferSize(getOutputBufferSize());

View File

@ -24,11 +24,9 @@ import org.eclipse.jetty.io.CyclicTimeouts;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.quic.common.QuicConnection;
import org.eclipse.jetty.quic.common.QuicSession;
import org.eclipse.jetty.quic.quiche.QuicheConfig;
import org.eclipse.jetty.quic.quiche.QuicheConnection;
import org.eclipse.jetty.quic.server.internal.SimpleTokenMinter;
import org.eclipse.jetty.quic.server.internal.SimpleTokenValidator;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.thread.Scheduler;
@ -42,14 +40,12 @@ public class ServerQuicConnection extends QuicConnection
{
private static final Logger LOG = LoggerFactory.getLogger(ServerQuicConnection.class);
private final QuicheConfig quicheConfig;
private final Connector connector;
private final QuicServerConnector connector;
private final SessionTimeouts sessionTimeouts;
protected ServerQuicConnection(Connector connector, EndPoint endPoint, QuicheConfig quicheConfig)
protected ServerQuicConnection(QuicServerConnector connector, EndPoint endPoint)
{
super(connector.getExecutor(), connector.getScheduler(), connector.getByteBufferPool(), endPoint);
this.quicheConfig = quicheConfig;
this.connector = connector;
this.sessionTimeouts = new SessionTimeouts(connector.getScheduler());
}
@ -66,7 +62,7 @@ public class ServerQuicConnection extends QuicConnection
{
ByteBufferPool byteBufferPool = getByteBufferPool();
// TODO make the token validator configurable
QuicheConnection quicheConnection = QuicheConnection.tryAccept(quicheConfig, new SimpleTokenValidator((InetSocketAddress)remoteAddress), cipherBuffer, remoteAddress);
QuicheConnection quicheConnection = QuicheConnection.tryAccept(connector.newQuicheConfig(), new SimpleTokenValidator((InetSocketAddress)remoteAddress), cipherBuffer, remoteAddress);
if (quicheConnection == null)
{
ByteBuffer negotiationBuffer = byteBufferPool.acquire(getOutputBufferSize(), true);