From a0399a2e30f4a3bf3cd5d92d8e140349d571f2a8 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Wed, 13 Oct 2021 22:43:57 +0200 Subject: [PATCH] Issue #6728 - QUIC and HTTP/3 - Improved configuration of client and server. - Implemented handling of QPACK exceptions. Signed-off-by: Simone Bordet --- .../jetty/http3/client/HTTP3Client.java | 66 ++-------- .../client/HTTP3ClientConnectionFactory.java | 27 +--- .../client/internal/ClientHTTP3Session.java | 10 +- .../src/main/java/module-info.java | 1 + .../jetty/http3/HTTP3Configuration.java | 118 +++++++++++++++++ .../jetty/http3/internal/ControlFlusher.java | 2 +- .../jetty/http3/internal/HTTP3Flusher.java | 15 ++- .../jetty/http3/internal/HTTP3Session.java | 1 + .../generator/CancelPushGenerator.java | 4 +- .../internal/generator/ControlGenerator.java | 6 +- .../internal/generator/DataGenerator.java | 3 +- .../internal/generator/FrameGenerator.java | 4 +- .../internal/generator/GoAwayGenerator.java | 3 +- .../internal/generator/HeadersGenerator.java | 17 ++- .../generator/MaxPushIdGenerator.java | 4 +- .../internal/generator/MessageGenerator.java | 6 +- .../generator/PushPromiseGenerator.java | 4 +- .../internal/generator/SettingsGenerator.java | 3 +- .../http3/internal/DataGenerateParseTest.java | 2 +- .../internal/GoAwayGenerateParseTest.java | 2 +- .../internal/HeadersGenerateParseTest.java | 2 +- .../internal/SettingsGenerateParseTest.java | 2 +- .../AbstractHTTP3ServerConnectionFactory.java | 64 ++-------- .../server/internal/ServerHTTP3Session.java | 12 +- .../jetty/http3/tests/ClientServerTest.java | 120 ++++++++++++++++++ .../http3/tests/StreamIdleTimeoutTest.java | 6 +- 26 files changed, 331 insertions(+), 173 deletions(-) create mode 100644 jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/HTTP3Configuration.java diff --git a/jetty-http3/http3-client/src/main/java/org/eclipse/jetty/http3/client/HTTP3Client.java b/jetty-http3/http3-client/src/main/java/org/eclipse/jetty/http3/client/HTTP3Client.java index 5d586f3dc05..f169cfffad7 100644 --- a/jetty-http3/http3-client/src/main/java/org/eclipse/jetty/http3/client/HTTP3Client.java +++ b/jetty-http3/http3-client/src/main/java/org/eclipse/jetty/http3/client/HTTP3Client.java @@ -19,6 +19,7 @@ import java.util.Map; import java.util.concurrent.CompletableFuture; 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; @@ -48,18 +49,15 @@ public class HTTP3Client extends ContainerLifeCycle private static final Logger LOG = LoggerFactory.getLogger(HTTP3Client.class); private final QuicSessionContainer container = new QuicSessionContainer(); + private final HTTP3Configuration configuration = new HTTP3Configuration(); private final ClientConnector connector; private List protocols = List.of("h3"); - private long streamIdleTimeout = 30000; - private int inputBufferSize = 2048; - private int outputBufferSize = 2048; - private boolean useInputDirectByteBuffers = true; - private boolean useOutputDirectByteBuffers = true; public HTTP3Client() { this.connector = new ClientConnector(new QuicClientConnectorConfigurator(this::configureConnection)); addBean(connector); + addBean(configuration); addBean(container); } @@ -68,44 +66,9 @@ public class HTTP3Client extends ContainerLifeCycle return connector; } - public int getInputBufferSize() + public HTTP3Configuration getConfiguration() { - return inputBufferSize; - } - - public void setInputBufferSize(int inputBufferSize) - { - this.inputBufferSize = inputBufferSize; - } - - public int getOutputBufferSize() - { - return outputBufferSize; - } - - public void setOutputBufferSize(int outputBufferSize) - { - this.outputBufferSize = outputBufferSize; - } - - public boolean isUseInputDirectByteBuffers() - { - return useInputDirectByteBuffers; - } - - public void setUseInputDirectByteBuffers(boolean useInputDirectByteBuffers) - { - this.useInputDirectByteBuffers = useInputDirectByteBuffers; - } - - public boolean isUseOutputDirectByteBuffers() - { - return useOutputDirectByteBuffers; - } - - public void setUseOutputDirectByteBuffers(boolean useOutputDirectByteBuffers) - { - this.useOutputDirectByteBuffers = useOutputDirectByteBuffers; + return configuration; } @ManagedAttribute("The ALPN protocol list") @@ -119,17 +82,6 @@ public class HTTP3Client extends ContainerLifeCycle this.protocols = protocols; } - @ManagedAttribute("The stream idle timeout in milliseconds") - public long getStreamIdleTimeout() - { - return streamIdleTimeout; - } - - public void setStreamIdleTimeout(long streamIdleTimeout) - { - this.streamIdleTimeout = streamIdleTimeout; - } - public CompletableFuture connect(SocketAddress address, Session.Client.Listener listener) { Map context = new ConcurrentHashMap<>(); @@ -155,10 +107,10 @@ public class HTTP3Client extends ContainerLifeCycle { QuicConnection quicConnection = (QuicConnection)connection; quicConnection.addEventListener(container); - quicConnection.setInputBufferSize(getInputBufferSize()); - quicConnection.setOutputBufferSize(getOutputBufferSize()); - quicConnection.setUseInputDirectByteBuffers(isUseInputDirectByteBuffers()); - quicConnection.setUseOutputDirectByteBuffers(isUseOutputDirectByteBuffers()); + quicConnection.setInputBufferSize(getConfiguration().getInputBufferSize()); + quicConnection.setOutputBufferSize(getConfiguration().getOutputBufferSize()); + quicConnection.setUseInputDirectByteBuffers(getConfiguration().isUseInputDirectByteBuffers()); + quicConnection.setUseOutputDirectByteBuffers(getConfiguration().isUseOutputDirectByteBuffers()); } return connection; } diff --git a/jetty-http3/http3-client/src/main/java/org/eclipse/jetty/http3/client/HTTP3ClientConnectionFactory.java b/jetty-http3/http3-client/src/main/java/org/eclipse/jetty/http3/client/HTTP3ClientConnectionFactory.java index c2b103faa94..33a9d5ccf5b 100644 --- a/jetty-http3/http3-client/src/main/java/org/eclipse/jetty/http3/client/HTTP3ClientConnectionFactory.java +++ b/jetty-http3/http3-client/src/main/java/org/eclipse/jetty/http3/client/HTTP3ClientConnectionFactory.java @@ -34,29 +34,6 @@ public class HTTP3ClientConnectionFactory implements ClientConnectionFactory, Pr { private static final Logger LOG = LoggerFactory.getLogger(HTTP3ClientConnectionFactory.class); - private int maxBlockedStreams; - private int maxResponseHeadersSize = 8192; - - public int getMaxBlockedStreams() - { - return maxBlockedStreams; - } - - public void setMaxBlockedStreams(int maxBlockedStreams) - { - this.maxBlockedStreams = maxBlockedStreams; - } - - public int getMaxResponseHeadersSize() - { - return maxResponseHeadersSize; - } - - public void setMaxResponseHeadersSize(int maxResponseHeadersSize) - { - this.maxResponseHeadersSize = maxResponseHeadersSize; - } - @Override public ProtocolSession newProtocolSession(QuicSession quicSession, Map context) { @@ -64,8 +41,8 @@ public class HTTP3ClientConnectionFactory implements ClientConnectionFactory, Pr Session.Client.Listener listener = (Session.Client.Listener)context.get(HTTP3Client.SESSION_LISTENER_CONTEXT_KEY); @SuppressWarnings("unchecked") Promise promise = (Promise)context.get(HTTP3Client.SESSION_PROMISE_CONTEXT_KEY); - ClientHTTP3Session session = new ClientHTTP3Session((ClientQuicSession)quicSession, listener, promise, getMaxBlockedStreams(), getMaxResponseHeadersSize()); - session.setStreamIdleTimeout(client.getStreamIdleTimeout()); + ClientHTTP3Session session = new ClientHTTP3Session(client.getConfiguration(), (ClientQuicSession)quicSession, listener, promise); + session.setStreamIdleTimeout(client.getConfiguration().getStreamIdleTimeout()); if (LOG.isDebugEnabled()) LOG.debug("created protocol-specific {}", session); return session; diff --git a/jetty-http3/http3-client/src/main/java/org/eclipse/jetty/http3/client/internal/ClientHTTP3Session.java b/jetty-http3/http3-client/src/main/java/org/eclipse/jetty/http3/client/internal/ClientHTTP3Session.java index abef376673c..ca22a797361 100644 --- a/jetty-http3/http3-client/src/main/java/org/eclipse/jetty/http3/client/internal/ClientHTTP3Session.java +++ b/jetty-http3/http3-client/src/main/java/org/eclipse/jetty/http3/client/internal/ClientHTTP3Session.java @@ -16,6 +16,7 @@ package org.eclipse.jetty.http3.client.internal; import java.util.Map; import java.util.concurrent.CompletableFuture; +import org.eclipse.jetty.http3.HTTP3Configuration; import org.eclipse.jetty.http3.api.Session; import org.eclipse.jetty.http3.frames.Frame; import org.eclipse.jetty.http3.frames.SettingsFrame; @@ -48,7 +49,7 @@ public class ClientHTTP3Session extends ClientProtocolSession private final ControlFlusher controlFlusher; private final HTTP3Flusher messageFlusher; - public ClientHTTP3Session(ClientQuicSession quicSession, Session.Client.Listener listener, Promise promise, int maxBlockedStreams, int maxResponseHeadersSize) + public ClientHTTP3Session(HTTP3Configuration configuration, ClientQuicSession quicSession, Session.Client.Listener listener, Promise promise) { super(quicSession); this.session = new HTTP3SessionClient(this, listener, promise); @@ -60,7 +61,7 @@ public class ClientHTTP3Session extends ClientProtocolSession long encoderStreamId = getQuicSession().newStreamId(StreamType.CLIENT_UNIDIRECTIONAL); QuicStreamEndPoint encoderEndPoint = configureInstructionEndPoint(encoderStreamId); InstructionFlusher encoderInstructionFlusher = new InstructionFlusher(quicSession, encoderEndPoint, EncoderStreamConnection.STREAM_TYPE); - this.encoder = new QpackEncoder(new InstructionHandler(encoderInstructionFlusher), maxBlockedStreams); + this.encoder = new QpackEncoder(new InstructionHandler(encoderInstructionFlusher), configuration.getMaxBlockedStreams()); addBean(encoder); if (LOG.isDebugEnabled()) LOG.debug("created encoder stream #{} on {}", encoderStreamId, encoderEndPoint); @@ -68,7 +69,7 @@ public class ClientHTTP3Session extends ClientProtocolSession long decoderStreamId = getQuicSession().newStreamId(StreamType.CLIENT_UNIDIRECTIONAL); QuicStreamEndPoint decoderEndPoint = configureInstructionEndPoint(decoderStreamId); InstructionFlusher decoderInstructionFlusher = new InstructionFlusher(quicSession, decoderEndPoint, DecoderStreamConnection.STREAM_TYPE); - this.decoder = new QpackDecoder(new InstructionHandler(decoderInstructionFlusher), maxResponseHeadersSize); + this.decoder = new QpackDecoder(new InstructionHandler(decoderInstructionFlusher), configuration.getMaxResponseHeadersSize()); addBean(decoder); if (LOG.isDebugEnabled()) LOG.debug("created decoder stream #{} on {}", decoderStreamId, decoderEndPoint); @@ -80,8 +81,7 @@ public class ClientHTTP3Session extends ClientProtocolSession if (LOG.isDebugEnabled()) LOG.debug("created control stream #{} on {}", controlStreamId, controlEndPoint); - // TODO: make parameters configurable. - this.messageFlusher = new HTTP3Flusher(quicSession.getByteBufferPool(), encoder, 4096, true); + this.messageFlusher = new HTTP3Flusher(quicSession.getByteBufferPool(), encoder, configuration.getMaxRequestHeadersSize(), configuration.isUseOutputDirectByteBuffers()); addBean(messageFlusher); } diff --git a/jetty-http3/http3-common/src/main/java/module-info.java b/jetty-http3/http3-common/src/main/java/module-info.java index 4046d60ffdd..859b09f78ed 100644 --- a/jetty-http3/http3-common/src/main/java/module-info.java +++ b/jetty-http3/http3-common/src/main/java/module-info.java @@ -21,6 +21,7 @@ module org.eclipse.jetty.http3.common requires transitive org.eclipse.jetty.http3.qpack; requires transitive org.eclipse.jetty.quic.common; + exports org.eclipse.jetty.http3; exports org.eclipse.jetty.http3.api; exports org.eclipse.jetty.http3.frames; diff --git a/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/HTTP3Configuration.java b/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/HTTP3Configuration.java new file mode 100644 index 00000000000..241f7cd9e85 --- /dev/null +++ b/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/HTTP3Configuration.java @@ -0,0 +1,118 @@ +// +// ======================================================================== +// 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; + +import org.eclipse.jetty.util.annotation.ManagedAttribute; +import org.eclipse.jetty.util.annotation.ManagedObject; + +@ManagedObject +public class HTTP3Configuration +{ + private long streamIdleTimeout = 30000; + private int inputBufferSize = 2048; + private int outputBufferSize = 2048; + private boolean useInputDirectByteBuffers = true; + private boolean useOutputDirectByteBuffers = true; + private int maxBlockedStreams = 0; + private int maxRequestHeadersSize = 8192; + private int maxResponseHeadersSize = 8192; + + @ManagedAttribute("The stream idle timeout in milliseconds") + public long getStreamIdleTimeout() + { + return streamIdleTimeout; + } + + public void setStreamIdleTimeout(long streamIdleTimeout) + { + this.streamIdleTimeout = streamIdleTimeout; + } + + @ManagedAttribute("The size of the network input buffer") + public int getInputBufferSize() + { + return inputBufferSize; + } + + public void setInputBufferSize(int inputBufferSize) + { + this.inputBufferSize = inputBufferSize; + } + + @ManagedAttribute("The size of the network output buffer") + public int getOutputBufferSize() + { + return outputBufferSize; + } + + public void setOutputBufferSize(int outputBufferSize) + { + this.outputBufferSize = outputBufferSize; + } + + @ManagedAttribute("Whether to use direct buffers for input") + public boolean isUseInputDirectByteBuffers() + { + return useInputDirectByteBuffers; + } + + public void setUseInputDirectByteBuffers(boolean useInputDirectByteBuffers) + { + this.useInputDirectByteBuffers = useInputDirectByteBuffers; + } + + @ManagedAttribute("Whether to use direct buffers for output") + public boolean isUseOutputDirectByteBuffers() + { + return useOutputDirectByteBuffers; + } + + public void setUseOutputDirectByteBuffers(boolean useOutputDirectByteBuffers) + { + this.useOutputDirectByteBuffers = useOutputDirectByteBuffers; + } + + @ManagedAttribute("The max number of QPACK blocked streams") + public int getMaxBlockedStreams() + { + return maxBlockedStreams; + } + + public void setMaxBlockedStreams(int maxBlockedStreams) + { + this.maxBlockedStreams = maxBlockedStreams; + } + + @ManagedAttribute("The max size of the request headers") + public int getMaxRequestHeadersSize() + { + return maxRequestHeadersSize; + } + + public void setMaxRequestHeadersSize(int maxRequestHeadersSize) + { + this.maxRequestHeadersSize = maxRequestHeadersSize; + } + + @ManagedAttribute("The max size of the response headers") + public int getMaxResponseHeadersSize() + { + return maxResponseHeadersSize; + } + + public void setMaxResponseHeadersSize(int maxResponseHeadersSize) + { + this.maxResponseHeadersSize = maxResponseHeadersSize; + } +} diff --git a/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/ControlFlusher.java b/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/ControlFlusher.java index 067ee55f344..bd23f21a578 100644 --- a/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/ControlFlusher.java +++ b/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/ControlFlusher.java @@ -75,7 +75,7 @@ public class ControlFlusher extends IteratingCallback for (Entry entry : entries) { - generator.generate(lease, endPoint.getStreamId(), entry.frame); + generator.generate(lease, endPoint.getStreamId(), entry.frame, null); invocationType = Invocable.combine(invocationType, entry.callback.getInvocationType()); } diff --git a/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/HTTP3Flusher.java b/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/HTTP3Flusher.java index fdbb33b9e6d..6e3134ca1ef 100644 --- a/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/HTTP3Flusher.java +++ b/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/HTTP3Flusher.java @@ -74,7 +74,9 @@ public class HTTP3Flusher extends IteratingCallback return Action.SCHEDULED; } - generator.generate(lease, entry.endPoint.getStreamId(), frame); + int generated = generator.generate(lease, entry.endPoint.getStreamId(), frame, this::fail); + if (generated < 0) + return Action.SCHEDULED; QuicStreamEndPoint endPoint = entry.endPoint; List buffers = lease.getByteBuffers(); @@ -96,6 +98,17 @@ public class HTTP3Flusher extends IteratingCallback super.succeeded(); } + private void fail(Throwable x) + { + if (LOG.isDebugEnabled()) + LOG.debug("failed to flush {} on {}", entry, this); + lease.recycle(); + entry.callback.failed(x); + entry = null; + // Continue the iteration. + super.succeeded(); + } + @Override protected void onCompleteFailure(Throwable failure) { diff --git a/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/HTTP3Session.java b/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/HTTP3Session.java index 3eedad0a2f2..9fbf8b1812b 100644 --- a/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/HTTP3Session.java +++ b/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/HTTP3Session.java @@ -287,6 +287,7 @@ public abstract class HTTP3Session extends ContainerLifeCycle implements Session } else { + removeStream(stream); promise.failed(x); } }); diff --git a/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/generator/CancelPushGenerator.java b/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/generator/CancelPushGenerator.java index 50d4db9b6fa..2ab111e395c 100644 --- a/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/generator/CancelPushGenerator.java +++ b/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/generator/CancelPushGenerator.java @@ -13,13 +13,15 @@ package org.eclipse.jetty.http3.internal.generator; +import java.util.function.Consumer; + import org.eclipse.jetty.http3.frames.Frame; import org.eclipse.jetty.io.ByteBufferPool; public class CancelPushGenerator extends FrameGenerator { @Override - public int generate(ByteBufferPool.Lease lease, long streamId, Frame frame) + public int generate(ByteBufferPool.Lease lease, long streamId, Frame frame, Consumer fail) { return 0; } diff --git a/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/generator/ControlGenerator.java b/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/generator/ControlGenerator.java index 1f68510541c..7b9e7e255b5 100644 --- a/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/generator/ControlGenerator.java +++ b/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/generator/ControlGenerator.java @@ -13,6 +13,8 @@ package org.eclipse.jetty.http3.internal.generator; +import java.util.function.Consumer; + import org.eclipse.jetty.http3.frames.Frame; import org.eclipse.jetty.http3.frames.FrameType; import org.eclipse.jetty.io.ByteBufferPool; @@ -29,8 +31,8 @@ public class ControlGenerator generators[FrameType.MAX_PUSH_ID.type()] = new MaxPushIdGenerator(); } - public int generate(ByteBufferPool.Lease lease, long streamId, Frame frame) + public int generate(ByteBufferPool.Lease lease, long streamId, Frame frame, Consumer fail) { - return generators[frame.getFrameType().type()].generate(lease, streamId, frame); + return generators[frame.getFrameType().type()].generate(lease, streamId, frame, fail); } } diff --git a/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/generator/DataGenerator.java b/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/generator/DataGenerator.java index 9ffd0825bf7..d5c60794806 100644 --- a/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/generator/DataGenerator.java +++ b/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/generator/DataGenerator.java @@ -14,6 +14,7 @@ package org.eclipse.jetty.http3.internal.generator; import java.nio.ByteBuffer; +import java.util.function.Consumer; import org.eclipse.jetty.http3.frames.DataFrame; import org.eclipse.jetty.http3.frames.Frame; @@ -31,7 +32,7 @@ public class DataGenerator extends FrameGenerator } @Override - public int generate(ByteBufferPool.Lease lease, long streamId, Frame frame) + public int generate(ByteBufferPool.Lease lease, long streamId, Frame frame, Consumer fail) { DataFrame dataFrame = (DataFrame)frame; return generateDataFrame(lease, dataFrame); diff --git a/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/generator/FrameGenerator.java b/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/generator/FrameGenerator.java index 81e716f5d4e..83fa7d70aae 100644 --- a/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/generator/FrameGenerator.java +++ b/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/generator/FrameGenerator.java @@ -13,10 +13,12 @@ package org.eclipse.jetty.http3.internal.generator; +import java.util.function.Consumer; + import org.eclipse.jetty.http3.frames.Frame; import org.eclipse.jetty.io.ByteBufferPool; public abstract class FrameGenerator { - public abstract int generate(ByteBufferPool.Lease lease, long streamId, Frame frame); + public abstract int generate(ByteBufferPool.Lease lease, long streamId, Frame frame, Consumer fail); } diff --git a/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/generator/GoAwayGenerator.java b/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/generator/GoAwayGenerator.java index a53a9613cdd..8df23466b86 100644 --- a/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/generator/GoAwayGenerator.java +++ b/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/generator/GoAwayGenerator.java @@ -14,6 +14,7 @@ package org.eclipse.jetty.http3.internal.generator; import java.nio.ByteBuffer; +import java.util.function.Consumer; import org.eclipse.jetty.http3.frames.Frame; import org.eclipse.jetty.http3.frames.FrameType; @@ -31,7 +32,7 @@ public class GoAwayGenerator extends FrameGenerator } @Override - public int generate(ByteBufferPool.Lease lease, long streamId, Frame frame) + public int generate(ByteBufferPool.Lease lease, long streamId, Frame frame, Consumer fail) { GoAwayFrame goAwayFrame = (GoAwayFrame)frame; return generateGoAwayFrame(lease, goAwayFrame); diff --git a/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/generator/HeadersGenerator.java b/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/generator/HeadersGenerator.java index 8aaca988ecb..6eba4890b04 100644 --- a/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/generator/HeadersGenerator.java +++ b/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/generator/HeadersGenerator.java @@ -14,6 +14,7 @@ package org.eclipse.jetty.http3.internal.generator; import java.nio.ByteBuffer; +import java.util.function.Consumer; import org.eclipse.jetty.http3.frames.Frame; import org.eclipse.jetty.http3.frames.FrameType; @@ -37,21 +38,23 @@ public class HeadersGenerator extends FrameGenerator } @Override - public int generate(ByteBufferPool.Lease lease, long streamId, Frame frame) + public int generate(ByteBufferPool.Lease lease, long streamId, Frame frame, Consumer fail) { HeadersFrame headersFrame = (HeadersFrame)frame; - return generateHeadersFrame(lease, streamId, headersFrame); + return generateHeadersFrame(lease, streamId, headersFrame, fail); } - private int generateHeadersFrame(ByteBufferPool.Lease lease, long streamId, HeadersFrame frame) + private int generateHeadersFrame(ByteBufferPool.Lease lease, long streamId, HeadersFrame frame, Consumer fail) { try { // Reserve initial bytes for the frame header bytes. int frameTypeLength = VarLenInt.length(FrameType.HEADERS.type()); int maxHeaderLength = frameTypeLength + VarLenInt.MAX_LENGTH; + // The capacity of the buffer is larger than maxLength, but we need to enforce at most maxLength. ByteBuffer buffer = lease.acquire(maxHeaderLength + maxLength, useDirectByteBuffers); buffer.position(maxHeaderLength); + buffer.limit(buffer.position() + maxLength); // Encode after the maxHeaderLength. encoder.encode(buffer, streamId, frame.getMetaData()); buffer.flip(); @@ -66,11 +69,11 @@ public class HeadersGenerator extends FrameGenerator lease.append(buffer, true); return headerLength + dataLength; } - catch (QpackException e) + catch (QpackException x) { - // TODO - e.printStackTrace(); - return 0; + if (fail != null) + fail.accept(x); + return -1; } } } diff --git a/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/generator/MaxPushIdGenerator.java b/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/generator/MaxPushIdGenerator.java index 5274fe51f96..ab5772f5254 100644 --- a/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/generator/MaxPushIdGenerator.java +++ b/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/generator/MaxPushIdGenerator.java @@ -13,13 +13,15 @@ package org.eclipse.jetty.http3.internal.generator; +import java.util.function.Consumer; + import org.eclipse.jetty.http3.frames.Frame; import org.eclipse.jetty.io.ByteBufferPool; public class MaxPushIdGenerator extends FrameGenerator { @Override - public int generate(ByteBufferPool.Lease lease, long streamId, Frame frame) + public int generate(ByteBufferPool.Lease lease, long streamId, Frame frame, Consumer fail) { return 0; } diff --git a/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/generator/MessageGenerator.java b/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/generator/MessageGenerator.java index 5bd1dd04c2a..b911929c020 100644 --- a/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/generator/MessageGenerator.java +++ b/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/generator/MessageGenerator.java @@ -13,6 +13,8 @@ package org.eclipse.jetty.http3.internal.generator; +import java.util.function.Consumer; + import org.eclipse.jetty.http3.frames.Frame; import org.eclipse.jetty.http3.frames.FrameType; import org.eclipse.jetty.http3.qpack.QpackEncoder; @@ -29,8 +31,8 @@ public class MessageGenerator generators[FrameType.PUSH_PROMISE.type()] = new PushPromiseGenerator(); } - public int generate(ByteBufferPool.Lease lease, long streamId, Frame frame) + public int generate(ByteBufferPool.Lease lease, long streamId, Frame frame, Consumer fail) { - return generators[frame.getFrameType().type()].generate(lease, streamId, frame); + return generators[frame.getFrameType().type()].generate(lease, streamId, frame, fail); } } diff --git a/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/generator/PushPromiseGenerator.java b/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/generator/PushPromiseGenerator.java index 23e9da99c5a..6d3e0d00fa7 100644 --- a/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/generator/PushPromiseGenerator.java +++ b/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/generator/PushPromiseGenerator.java @@ -13,13 +13,15 @@ package org.eclipse.jetty.http3.internal.generator; +import java.util.function.Consumer; + import org.eclipse.jetty.http3.frames.Frame; import org.eclipse.jetty.io.ByteBufferPool; public class PushPromiseGenerator extends FrameGenerator { @Override - public int generate(ByteBufferPool.Lease lease, long streamId, Frame frame) + public int generate(ByteBufferPool.Lease lease, long streamId, Frame frame, Consumer fail) { return 0; } diff --git a/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/generator/SettingsGenerator.java b/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/generator/SettingsGenerator.java index af4d14b4936..9dabdb3cdb0 100644 --- a/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/generator/SettingsGenerator.java +++ b/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/generator/SettingsGenerator.java @@ -15,6 +15,7 @@ package org.eclipse.jetty.http3.internal.generator; import java.nio.ByteBuffer; import java.util.Map; +import java.util.function.Consumer; import org.eclipse.jetty.http3.frames.Frame; import org.eclipse.jetty.http3.frames.SettingsFrame; @@ -32,7 +33,7 @@ public class SettingsGenerator extends FrameGenerator } @Override - public int generate(ByteBufferPool.Lease lease, long streamId, Frame frame) + public int generate(ByteBufferPool.Lease lease, long streamId, Frame frame, Consumer fail) { SettingsFrame settingsFrame = (SettingsFrame)frame; return generateSettings(lease, settingsFrame); diff --git a/jetty-http3/http3-common/src/test/java/org/eclipse/jetty/http3/internal/DataGenerateParseTest.java b/jetty-http3/http3-common/src/test/java/org/eclipse/jetty/http3/internal/DataGenerateParseTest.java index d0a28ccbec3..ad8621f7d3c 100644 --- a/jetty-http3/http3-common/src/test/java/org/eclipse/jetty/http3/internal/DataGenerateParseTest.java +++ b/jetty-http3/http3-common/src/test/java/org/eclipse/jetty/http3/internal/DataGenerateParseTest.java @@ -55,7 +55,7 @@ public class DataGenerateParseTest DataFrame input = new DataFrame(ByteBuffer.wrap(inputBytes), true); ByteBufferPool.Lease lease = new ByteBufferPool.Lease(new NullByteBufferPool()); - new MessageGenerator(null, 8192, true).generate(lease, 0, input); + new MessageGenerator(null, 8192, true).generate(lease, 0, input, null); List frames = new ArrayList<>(); MessageParser parser = new MessageParser(new ParserListener() diff --git a/jetty-http3/http3-common/src/test/java/org/eclipse/jetty/http3/internal/GoAwayGenerateParseTest.java b/jetty-http3/http3-common/src/test/java/org/eclipse/jetty/http3/internal/GoAwayGenerateParseTest.java index dd8a716c151..1f2f0a12923 100644 --- a/jetty-http3/http3-common/src/test/java/org/eclipse/jetty/http3/internal/GoAwayGenerateParseTest.java +++ b/jetty-http3/http3-common/src/test/java/org/eclipse/jetty/http3/internal/GoAwayGenerateParseTest.java @@ -36,7 +36,7 @@ public class GoAwayGenerateParseTest GoAwayFrame input = GoAwayFrame.CLIENT_GRACEFUL; ByteBufferPool.Lease lease = new ByteBufferPool.Lease(new NullByteBufferPool()); - new ControlGenerator(true).generate(lease, 0, input); + new ControlGenerator(true).generate(lease, 0, input, null); List frames = new ArrayList<>(); ControlParser parser = new ControlParser(new ParserListener() diff --git a/jetty-http3/http3-common/src/test/java/org/eclipse/jetty/http3/internal/HeadersGenerateParseTest.java b/jetty-http3/http3-common/src/test/java/org/eclipse/jetty/http3/internal/HeadersGenerateParseTest.java index bf666ce3451..ada6f2b4e04 100644 --- a/jetty-http3/http3-common/src/test/java/org/eclipse/jetty/http3/internal/HeadersGenerateParseTest.java +++ b/jetty-http3/http3-common/src/test/java/org/eclipse/jetty/http3/internal/HeadersGenerateParseTest.java @@ -49,7 +49,7 @@ public class HeadersGenerateParseTest QpackEncoder encoder = new QpackEncoder(instructions -> {}, 100); ByteBufferPool.Lease lease = new ByteBufferPool.Lease(new NullByteBufferPool()); - new MessageGenerator(encoder, 8192, true).generate(lease, 0, input); + new MessageGenerator(encoder, 8192, true).generate(lease, 0, input, null); QpackDecoder decoder = new QpackDecoder(instructions -> {}, 8192); List frames = new ArrayList<>(); diff --git a/jetty-http3/http3-common/src/test/java/org/eclipse/jetty/http3/internal/SettingsGenerateParseTest.java b/jetty-http3/http3-common/src/test/java/org/eclipse/jetty/http3/internal/SettingsGenerateParseTest.java index d6f401c2460..54e806c1981 100644 --- a/jetty-http3/http3-common/src/test/java/org/eclipse/jetty/http3/internal/SettingsGenerateParseTest.java +++ b/jetty-http3/http3-common/src/test/java/org/eclipse/jetty/http3/internal/SettingsGenerateParseTest.java @@ -48,7 +48,7 @@ public class SettingsGenerateParseTest SettingsFrame input = new SettingsFrame(settings); ByteBufferPool.Lease lease = new ByteBufferPool.Lease(new NullByteBufferPool()); - new ControlGenerator(true).generate(lease, 0, input); + new ControlGenerator(true).generate(lease, 0, input, null); List frames = new ArrayList<>(); ControlParser parser = new ControlParser(new ParserListener() diff --git a/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/AbstractHTTP3ServerConnectionFactory.java b/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/AbstractHTTP3ServerConnectionFactory.java index 3ad235be805..560326569de 100644 --- a/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/AbstractHTTP3ServerConnectionFactory.java +++ b/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/AbstractHTTP3ServerConnectionFactory.java @@ -16,6 +16,7 @@ package org.eclipse.jetty.http3.server; import java.util.Map; import java.util.Objects; +import org.eclipse.jetty.http3.HTTP3Configuration; import org.eclipse.jetty.http3.api.Session; import org.eclipse.jetty.http3.internal.parser.MessageParser; import org.eclipse.jetty.http3.server.internal.ServerHTTP3Session; @@ -29,50 +30,24 @@ import org.eclipse.jetty.quic.server.ServerQuicSession; import org.eclipse.jetty.server.AbstractConnectionFactory; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.HttpConfiguration; -import org.eclipse.jetty.util.annotation.ManagedAttribute; public abstract class AbstractHTTP3ServerConnectionFactory extends AbstractConnectionFactory implements ProtocolSession.Factory { + private final HTTP3Configuration configuration = new HTTP3Configuration(); private final HttpConfiguration httpConfiguration; private final Session.Server.Listener listener; - private boolean useInputDirectByteBuffers = true; - private boolean useOutputDirectByteBuffers = true; - private int maxBlockedStreams = 0; - private long streamIdleTimeout = 30000; public AbstractHTTP3ServerConnectionFactory(HttpConfiguration httpConfiguration, Session.Server.Listener listener) { super("h3"); + addBean(configuration); this.httpConfiguration = Objects.requireNonNull(httpConfiguration); addBean(httpConfiguration); this.listener = listener; - } - - protected Session.Server.Listener getListener() - { - return listener; - } - - @ManagedAttribute("Whether to use direct ByteBuffers for reading") - public boolean isUseInputDirectByteBuffers() - { - return useInputDirectByteBuffers; - } - - public void setUseInputDirectByteBuffers(boolean useInputDirectByteBuffers) - { - this.useInputDirectByteBuffers = useInputDirectByteBuffers; - } - - @ManagedAttribute("Whether to use direct ByteBuffers for writing") - public boolean isUseOutputDirectByteBuffers() - { - return useOutputDirectByteBuffers; - } - - public void setUseOutputDirectByteBuffers(boolean useOutputDirectByteBuffers) - { - this.useOutputDirectByteBuffers = useOutputDirectByteBuffers; + configuration.setUseInputDirectByteBuffers(httpConfiguration.isUseInputDirectByteBuffers()); + configuration.setUseOutputDirectByteBuffers(httpConfiguration.isUseOutputDirectByteBuffers()); + configuration.setMaxRequestHeadersSize(httpConfiguration.getRequestHeaderSize()); + configuration.setMaxResponseHeadersSize(httpConfiguration.getResponseHeaderSize()); } public HttpConfiguration getHttpConfiguration() @@ -80,33 +55,16 @@ public abstract class AbstractHTTP3ServerConnectionFactory extends AbstractConne return httpConfiguration; } - @ManagedAttribute("The max number of streams blocked in QPACK encoding") - public int getMaxBlockedStreams() + public HTTP3Configuration getConfiguration() { - return maxBlockedStreams; - } - - public void setMaxBlockedStreams(int maxBlockedStreams) - { - this.maxBlockedStreams = maxBlockedStreams; - } - - @ManagedAttribute("The stream idle timeout in milliseconds") - public long getStreamIdleTimeout() - { - return streamIdleTimeout; - } - - public void setStreamIdleTimeout(long streamIdleTimeout) - { - this.streamIdleTimeout = streamIdleTimeout; + return configuration; } @Override public ProtocolSession newProtocolSession(QuicSession quicSession, Map context) { - ServerHTTP3Session session = new ServerHTTP3Session((ServerQuicSession)quicSession, listener, getMaxBlockedStreams(), getHttpConfiguration().getRequestHeaderSize()); - session.setStreamIdleTimeout(getStreamIdleTimeout()); + ServerHTTP3Session session = new ServerHTTP3Session(getConfiguration(), (ServerQuicSession)quicSession, listener); + session.setStreamIdleTimeout(getConfiguration().getStreamIdleTimeout()); return session; } diff --git a/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/ServerHTTP3Session.java b/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/ServerHTTP3Session.java index 0b7791b4e93..0a0bf41bf96 100644 --- a/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/ServerHTTP3Session.java +++ b/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/ServerHTTP3Session.java @@ -18,6 +18,7 @@ import java.util.Map; import java.util.Queue; import java.util.concurrent.CompletableFuture; +import org.eclipse.jetty.http3.HTTP3Configuration; import org.eclipse.jetty.http3.api.Session; import org.eclipse.jetty.http3.frames.Frame; import org.eclipse.jetty.http3.frames.SettingsFrame; @@ -53,7 +54,7 @@ public class ServerHTTP3Session extends ServerProtocolSession private final AdaptiveExecutionStrategy strategy; private final HTTP3Producer producer = new HTTP3Producer(); - public ServerHTTP3Session(ServerQuicSession quicSession, Session.Server.Listener listener, int maxBlockedStreams, int maxRequestHeadersSize) + public ServerHTTP3Session(HTTP3Configuration configuration, ServerQuicSession quicSession, Session.Server.Listener listener) { super(quicSession); this.session = new HTTP3SessionServer(this, listener); @@ -65,7 +66,7 @@ public class ServerHTTP3Session extends ServerProtocolSession long encoderStreamId = getQuicSession().newStreamId(StreamType.SERVER_UNIDIRECTIONAL); QuicStreamEndPoint encoderEndPoint = configureInstructionEndPoint(encoderStreamId); InstructionFlusher encoderInstructionFlusher = new InstructionFlusher(quicSession, encoderEndPoint, EncoderStreamConnection.STREAM_TYPE); - this.encoder = new QpackEncoder(new InstructionHandler(encoderInstructionFlusher), maxBlockedStreams); + this.encoder = new QpackEncoder(new InstructionHandler(encoderInstructionFlusher), configuration.getMaxBlockedStreams()); addBean(encoder); if (LOG.isDebugEnabled()) LOG.debug("created encoder stream #{} on {}", encoderStreamId, encoderEndPoint); @@ -73,20 +74,19 @@ public class ServerHTTP3Session extends ServerProtocolSession long decoderStreamId = getQuicSession().newStreamId(StreamType.SERVER_UNIDIRECTIONAL); QuicStreamEndPoint decoderEndPoint = configureInstructionEndPoint(decoderStreamId); InstructionFlusher decoderInstructionFlusher = new InstructionFlusher(quicSession, decoderEndPoint, DecoderStreamConnection.STREAM_TYPE); - this.decoder = new QpackDecoder(new InstructionHandler(decoderInstructionFlusher), maxRequestHeadersSize); + this.decoder = new QpackDecoder(new InstructionHandler(decoderInstructionFlusher), configuration.getMaxRequestHeadersSize()); addBean(decoder); if (LOG.isDebugEnabled()) LOG.debug("created decoder stream #{} on {}", decoderStreamId, decoderEndPoint); long controlStreamId = getQuicSession().newStreamId(StreamType.SERVER_UNIDIRECTIONAL); QuicStreamEndPoint controlEndPoint = configureControlEndPoint(controlStreamId); - this.controlFlusher = new ControlFlusher(quicSession, controlEndPoint, true); + this.controlFlusher = new ControlFlusher(quicSession, controlEndPoint, configuration.isUseOutputDirectByteBuffers()); addBean(controlFlusher); if (LOG.isDebugEnabled()) LOG.debug("created control stream #{} on {}", controlStreamId, controlEndPoint); - // TODO: make parameters configurable. - this.messageFlusher = new HTTP3Flusher(quicSession.getByteBufferPool(), encoder, 4096, true); + this.messageFlusher = new HTTP3Flusher(quicSession.getByteBufferPool(), encoder, configuration.getMaxResponseHeadersSize(), configuration.isUseOutputDirectByteBuffers()); addBean(messageFlusher); this.strategy = new AdaptiveExecutionStrategy(producer, getQuicSession().getExecutor()); diff --git a/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/ClientServerTest.java b/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/ClientServerTest.java index 8d42f95c188..c7004b32ef3 100644 --- a/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/ClientServerTest.java +++ b/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/ClientServerTest.java @@ -30,6 +30,8 @@ import org.eclipse.jetty.http3.api.Stream; import org.eclipse.jetty.http3.frames.DataFrame; import org.eclipse.jetty.http3.frames.HeadersFrame; import org.eclipse.jetty.http3.frames.SettingsFrame; +import org.eclipse.jetty.http3.internal.HTTP3ErrorCode; +import org.eclipse.jetty.http3.server.AbstractHTTP3ServerConnectionFactory; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; @@ -265,4 +267,122 @@ public class ClientServerTest extends AbstractClientServerTest assertTrue(clientDataLatch.await(5, TimeUnit.SECONDS)); assertArrayEquals(bytesSent, bytesReceived); } + + @Test + public void testRequestHeadersTooLarge() throws Exception + { + start(new Session.Server.Listener() + { + @Override + public Stream.Listener onRequest(Stream stream, HeadersFrame frame) + { + stream.respond(new HeadersFrame(new MetaData.Response(HttpVersion.HTTP_3, HttpStatus.OK_200, HttpFields.EMPTY), true)); + return null; + } + }); + + int maxRequestHeadersSize = 128; + client.getConfiguration().setMaxRequestHeadersSize(maxRequestHeadersSize); + Session.Client clientSession = newSession(new Session.Client.Listener() {}); + + CountDownLatch requestFailureLatch = new CountDownLatch(1); + HttpFields largeHeaders = HttpFields.build().put("too-large", "x".repeat(2 * maxRequestHeadersSize)); + clientSession.newRequest(new HeadersFrame(newRequest(HttpMethod.GET, "/", largeHeaders), true), new Stream.Listener() {}) + .whenComplete((s, x) -> + { + // The HTTP3Stream was created, but the application cannot access + // it, so the implementation must remove it from the HTTP3Session. + // See below the difference with the server. + if (x != null) + requestFailureLatch.countDown(); + }); + + assertTrue(requestFailureLatch.await(5, TimeUnit.SECONDS)); + assertTrue(clientSession.getStreams().isEmpty()); + + // Verify that the connection is still good. + CountDownLatch responseLatch = new CountDownLatch(1); + clientSession.newRequest(new HeadersFrame(newRequest("/"), true), new Stream.Listener() + { + @Override + public void onResponse(Stream stream, HeadersFrame frame) + { + responseLatch.countDown(); + } + }); + + assertTrue(responseLatch.await(5, TimeUnit.SECONDS)); + } + + @Test + public void testResponseHeadersTooLarge() throws Exception + { + int maxResponseHeadersSize = 128; + AtomicReference serverSessionRef = new AtomicReference<>(); + CountDownLatch responseFailureLatch = new CountDownLatch(1); + start(new Session.Server.Listener() + { + @Override + public Stream.Listener onRequest(Stream stream, HeadersFrame frame) + { + serverSessionRef.set(stream.getSession()); + MetaData.Request request = (MetaData.Request)frame.getMetaData(); + if ("/large".equals(request.getURI().getPath())) + { + HttpFields largeHeaders = HttpFields.build().put("too-large", "x".repeat(2 * maxResponseHeadersSize)); + stream.respond(new HeadersFrame(new MetaData.Response(HttpVersion.HTTP_3, HttpStatus.OK_200, largeHeaders), true)) + .whenComplete((s, x) -> + { + // The response could not be generated, but the stream is still valid. + // Applications may try to send a smaller response here, + // so the implementation must not remove the stream. + if (x != null) + { + // In this test, we give up if there is an error. + stream.reset(HTTP3ErrorCode.REQUEST_CANCELLED_ERROR.code(), x); + responseFailureLatch.countDown(); + } + }); + } + else + { + stream.respond(new HeadersFrame(new MetaData.Response(HttpVersion.HTTP_3, HttpStatus.OK_200, HttpFields.EMPTY), true)); + } + return null; + } + }); + AbstractHTTP3ServerConnectionFactory h3 = connector.getConnectionFactory(AbstractHTTP3ServerConnectionFactory.class); + assertNotNull(h3); + h3.getConfiguration().setMaxResponseHeadersSize(maxResponseHeadersSize); + + Session.Client clientSession = newSession(new Session.Client.Listener() {}); + + CountDownLatch streamFailureLatch = new CountDownLatch(1); + clientSession.newRequest(new HeadersFrame(newRequest("/large"), true), new Stream.Listener() + { + @Override + public void onFailure(Stream stream, Throwable failure) + { + streamFailureLatch.countDown(); + } + }); + + assertTrue(responseFailureLatch.await(5, TimeUnit.SECONDS)); + assertTrue(streamFailureLatch.await(5, TimeUnit.SECONDS)); + assertTrue(serverSessionRef.get().getStreams().isEmpty()); + assertTrue(clientSession.getStreams().isEmpty()); + + // Verify that the connection is still good. + CountDownLatch responseLatch = new CountDownLatch(1); + clientSession.newRequest(new HeadersFrame(newRequest("/"), true), new Stream.Listener() + { + @Override + public void onResponse(Stream stream, HeadersFrame frame) + { + responseLatch.countDown(); + } + }); + + assertTrue(responseLatch.await(5, TimeUnit.SECONDS)); + } } diff --git a/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/StreamIdleTimeoutTest.java b/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/StreamIdleTimeoutTest.java index b0f29a519e9..633880de9b0 100644 --- a/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/StreamIdleTimeoutTest.java +++ b/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/StreamIdleTimeoutTest.java @@ -97,7 +97,7 @@ public class StreamIdleTimeoutTest extends AbstractClientServerTest }); long streamIdleTimeout = 1000; - client.setStreamIdleTimeout(streamIdleTimeout); + client.getConfiguration().setStreamIdleTimeout(streamIdleTimeout); Session.Client clientSession = newSession(new Session.Client.Listener() {}); @@ -175,9 +175,9 @@ public class StreamIdleTimeoutTest extends AbstractClientServerTest } } }); - AbstractHTTP3ServerConnectionFactory h3 = server.getConnectors()[0].getConnectionFactory(AbstractHTTP3ServerConnectionFactory.class); + AbstractHTTP3ServerConnectionFactory h3 = connector.getConnectionFactory(AbstractHTTP3ServerConnectionFactory.class); assertNotNull(h3); - h3.setStreamIdleTimeout(idleTimeout); + h3.getConfiguration().setStreamIdleTimeout(idleTimeout); Session.Client clientSession = client.connect(new InetSocketAddress("localhost", connector.getLocalPort()), new Session.Client.Listener() {}) .get(5, TimeUnit.SECONDS);