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:
parent
a31c35e9ad
commit
b19c135f46
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue