From 420ec7cc1db65b42693dc002aca0faa567f62f7e Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Sat, 27 May 2023 19:14:01 +0200 Subject: [PATCH] HTTP/2 improvements. (#9749) * Implemented a few required error handlings. * Changed `Parser.init()` to directly take the listener, rather than wrapping it. The reason for this change was to be able to reconfigure the Parser upon receiving a SETTINGS frame. * Initially setting the encoder and decoder max table capacity at the default of 4096, as per spec. Signed-off-by: Simone Bordet --- .../org/eclipse/jetty/client/HttpClient.java | 18 + .../client/http/HttpReceiverOverHTTP.java | 2 +- .../jetty/http2/client/HTTP2Client.java | 77 +++- .../client/HTTP2ClientConnectionFactory.java | 79 +++- .../http2/client/HTTP2ClientSession.java | 26 +- .../client/ConcurrentStreamCreationTest.java | 1 + .../http2/client/FlowControlStrategyTest.java | 31 +- .../jetty/http2/client/GoAwayTest.java | 67 ++++ .../eclipse/jetty/http2/client/HTTP2Test.java | 22 +- .../jetty/http2/client/PrefaceTest.java | 14 +- .../jetty/http2/client/SettingsTest.java | 357 ++++++++++++++++++ .../jetty/http2/client/TrailersTest.java | 4 +- .../eclipse/jetty/http2/HTTP2Connection.java | 120 ++++-- .../org/eclipse/jetty/http2/HTTP2Session.java | 88 +++-- .../jetty/http2/generator/Generator.java | 40 +- .../jetty/http2/parser/GoAwayBodyParser.java | 3 + .../eclipse/jetty/http2/parser/Parser.java | 62 ++- .../jetty/http2/parser/ServerParser.java | 27 +- .../http2/parser/SettingsBodyParser.java | 18 +- .../http2/frames/ContinuationParseTest.java | 7 +- .../http2/frames/DataGenerateParseTest.java | 13 +- .../jetty/http2/frames/FrameFloodTest.java | 7 +- .../http2/frames/GoAwayGenerateParseTest.java | 13 +- .../frames/HeadersGenerateParseTest.java | 13 +- .../frames/HeadersTooLargeParseTest.java | 7 +- .../http2/frames/MaxFrameSizeParseTest.java | 9 +- .../http2/frames/PingGenerateParseTest.java | 19 +- .../frames/PriorityGenerateParseTest.java | 13 +- .../frames/PushPromiseGenerateParseTest.java | 13 +- .../http2/frames/ResetGenerateParseTest.java | 13 +- .../frames/SettingsGenerateParseTest.java | 61 ++- .../jetty/http2/frames/UnknownParseTest.java | 13 +- .../frames/WindowUpdateGenerateParseTest.java | 13 +- .../jetty/http2/hpack/HpackContext.java | 47 +-- .../jetty/http2/hpack/HpackDecoder.java | 59 ++- .../jetty/http2/hpack/HpackEncoder.java | 102 ++--- .../jetty/http2/hpack/MetaDataBuilder.java | 14 +- .../jetty/http2/hpack/HpackDecoderTest.java | 90 ++--- .../jetty/http2/hpack/HpackEncoderTest.java | 30 +- .../jetty/http2/hpack/HpackPerfTest.java | 8 +- .../eclipse/jetty/http2/hpack/HpackTest.java | 17 +- .../HttpClientTransportOverHTTP2Test.java | 10 +- .../AbstractHTTP2ServerConnectionFactory.java | 112 ++++-- .../http2/server/HTTP2ServerConnection.java | 24 +- .../http2/server/HTTP2ServerSession.java | 20 +- .../eclipse/jetty/http2/server/CloseTest.java | 19 +- .../jetty/http2/server/HTTP2CServerTest.java | 13 +- .../jetty/http2/server/HTTP2ServerTest.java | 47 ++- .../client/internal/ClientHTTP3Session.java | 2 +- .../jetty/http3/HTTP3Configuration.java | 20 +- .../server/internal/ServerHTTP3Session.java | 2 +- .../jetty/http3/tests/ClientServerTest.java | 4 +- 52 files changed, 1388 insertions(+), 522 deletions(-) create mode 100644 jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/SettingsTest.java diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java index 1ee19a52629..6dcaa0bb0d6 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java @@ -149,6 +149,7 @@ public class HttpClient extends ContainerLifeCycle private String defaultRequestContentType = "application/octet-stream"; private boolean useInputDirectByteBuffers = true; private boolean useOutputDirectByteBuffers = true; + private int maxResponseHeadersSize = -1; private Sweeper destinationSweeper; /** @@ -1181,6 +1182,23 @@ public class HttpClient extends ContainerLifeCycle this.useOutputDirectByteBuffers = useOutputDirectByteBuffers; } + /** + * @return the max size in bytes of the response headers + */ + @ManagedAttribute("The max size in bytes of the response headers") + public int getMaxResponseHeadersSize() + { + return maxResponseHeadersSize; + } + + /** + * @param maxResponseHeadersSize the max size in bytes of the response headers + */ + public void setMaxResponseHeadersSize(int maxResponseHeadersSize) + { + this.maxResponseHeadersSize = maxResponseHeadersSize; + } + /** * @return the forward proxy configuration */ diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTP.java b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTP.java index 514c252c64e..afbc4a828f9 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTP.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTP.java @@ -55,7 +55,7 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res { super(channel); HttpClient httpClient = channel.getHttpDestination().getHttpClient(); - parser = new HttpParser(this, -1, httpClient.getHttpCompliance()); + parser = new HttpParser(this, httpClient.getMaxResponseHeadersSize(), httpClient.getHttpCompliance()); HttpClientTransport transport = httpClient.getTransport(); if (transport instanceof HttpClientTransportOverHTTP) { diff --git a/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2Client.java b/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2Client.java index 01f4839ac16..13646239c99 100644 --- a/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2Client.java +++ b/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2Client.java @@ -28,6 +28,7 @@ import org.eclipse.jetty.http2.FlowControlStrategy; import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.frames.Frame; import org.eclipse.jetty.http2.frames.SettingsFrame; +import org.eclipse.jetty.http2.hpack.HpackContext; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.ClientConnectionFactory; import org.eclipse.jetty.io.ClientConnector; @@ -106,11 +107,13 @@ public class HTTP2Client extends ContainerLifeCycle private List protocols = List.of("h2"); private int initialSessionRecvWindow = 16 * 1024 * 1024; private int initialStreamRecvWindow = 8 * 1024 * 1024; - private int maxFrameLength = Frame.DEFAULT_MAX_LENGTH; + private int maxFrameSize = Frame.DEFAULT_MAX_LENGTH; private int maxConcurrentPushedStreams = 32; private int maxSettingsKeys = SettingsFrame.DEFAULT_MAX_KEYS; - private int maxDynamicTableSize = 4096; + private int maxDecoderTableCapacity = HpackContext.DEFAULT_MAX_TABLE_CAPACITY; + private int maxEncoderTableCapacity = HpackContext.DEFAULT_MAX_TABLE_CAPACITY; private int maxHeaderBlockFragment = 0; + private int maxResponseHeadersSize = -1; private FlowControlStrategy.Factory flowControlStrategyFactory = () -> new BufferingFlowControlStrategy(0.5F); private long streamIdleTimeout; private boolean useInputDirectByteBuffers = true; @@ -282,15 +285,27 @@ public class HTTP2Client extends ContainerLifeCycle this.initialStreamRecvWindow = initialStreamRecvWindow; } - @ManagedAttribute("The max frame length in bytes") + @Deprecated public int getMaxFrameLength() { - return maxFrameLength; + return getMaxFrameSize(); } - public void setMaxFrameLength(int maxFrameLength) + @Deprecated + public void setMaxFrameLength(int maxFrameSize) { - this.maxFrameLength = maxFrameLength; + setMaxFrameSize(maxFrameSize); + } + + @ManagedAttribute("The max frame size in bytes") + public int getMaxFrameSize() + { + return maxFrameSize; + } + + public void setMaxFrameSize(int maxFrameSize) + { + this.maxFrameSize = maxFrameSize; } @ManagedAttribute("The max number of concurrent pushed streams") @@ -315,15 +330,44 @@ public class HTTP2Client extends ContainerLifeCycle this.maxSettingsKeys = maxSettingsKeys; } - @ManagedAttribute("The HPACK dynamic table maximum size") - public int getMaxDynamicTableSize() + @ManagedAttribute("The HPACK encoder dynamic table maximum capacity") + public int getMaxEncoderTableCapacity() { - return maxDynamicTableSize; + return maxEncoderTableCapacity; } - public void setMaxDynamicTableSize(int maxDynamicTableSize) + /** + *

Sets the limit for the encoder HPACK dynamic table capacity.

+ *

Setting this value to {@code 0} disables the use of the dynamic table.

+ * + * @param maxEncoderTableCapacity The HPACK encoder dynamic table maximum capacity + */ + public void setMaxEncoderTableCapacity(int maxEncoderTableCapacity) { - this.maxDynamicTableSize = maxDynamicTableSize; + this.maxEncoderTableCapacity = maxEncoderTableCapacity; + } + + @ManagedAttribute("The HPACK decoder dynamic table maximum capacity") + public int getMaxDecoderTableCapacity() + { + return maxDecoderTableCapacity; + } + + public void setMaxDecoderTableCapacity(int maxDecoderTableCapacity) + { + this.maxDecoderTableCapacity = maxDecoderTableCapacity; + } + + @Deprecated + public int getMaxDynamicTableSize() + { + return getMaxDecoderTableCapacity(); + } + + @Deprecated + public void setMaxDynamicTableSize(int maxTableSize) + { + setMaxDecoderTableCapacity(maxTableSize); } @ManagedAttribute("The max size of header block fragments") @@ -337,6 +381,17 @@ public class HTTP2Client extends ContainerLifeCycle this.maxHeaderBlockFragment = maxHeaderBlockFragment; } + @ManagedAttribute("The max size of response headers") + public int getMaxResponseHeadersSize() + { + return maxResponseHeadersSize; + } + + public void setMaxResponseHeadersSize(int maxResponseHeadersSize) + { + this.maxResponseHeadersSize = maxResponseHeadersSize; + } + @ManagedAttribute("Whether to use direct ByteBuffers for reading") public boolean isUseInputDirectByteBuffers() { diff --git a/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2ClientConnectionFactory.java b/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2ClientConnectionFactory.java index f728626b55d..5f5efe7bf31 100644 --- a/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2ClientConnectionFactory.java +++ b/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2ClientConnectionFactory.java @@ -22,10 +22,12 @@ import org.eclipse.jetty.http2.FlowControlStrategy; import org.eclipse.jetty.http2.HTTP2Connection; import org.eclipse.jetty.http2.ISession; import org.eclipse.jetty.http2.api.Session; +import org.eclipse.jetty.http2.frames.Frame; import org.eclipse.jetty.http2.frames.PrefaceFrame; import org.eclipse.jetty.http2.frames.SettingsFrame; import org.eclipse.jetty.http2.frames.WindowUpdateFrame; import org.eclipse.jetty.http2.generator.Generator; +import org.eclipse.jetty.http2.hpack.HpackContext; import org.eclipse.jetty.http2.parser.Parser; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.ClientConnectionFactory; @@ -54,27 +56,30 @@ public class HTTP2ClientConnectionFactory implements ClientConnectionFactory Scheduler scheduler = client.getScheduler(); Session.Listener listener = (Session.Listener)context.get(SESSION_LISTENER_CONTEXT_KEY); @SuppressWarnings("unchecked") - Promise promise = (Promise)context.get(SESSION_PROMISE_CONTEXT_KEY); + Promise sessionPromise = (Promise)context.get(SESSION_PROMISE_CONTEXT_KEY); - Generator generator = new Generator(byteBufferPool, client.getMaxDynamicTableSize(), client.getMaxHeaderBlockFragment()); + Generator generator = new Generator(byteBufferPool, client.isUseOutputDirectByteBuffers(), client.getMaxHeaderBlockFragment()); FlowControlStrategy flowControl = client.getFlowControlStrategyFactory().newFlowControlStrategy(); - HTTP2ClientSession session = new HTTP2ClientSession(scheduler, endPoint, generator, listener, flowControl); + + Parser parser = new Parser(byteBufferPool, client.getMaxResponseHeadersSize()); + parser.setMaxFrameSize(client.getMaxFrameSize()); + parser.setMaxSettingsKeys(client.getMaxSettingsKeys()); + + HTTP2ClientSession session = new HTTP2ClientSession(scheduler, endPoint, parser, generator, listener, flowControl); session.setMaxRemoteStreams(client.getMaxConcurrentPushedStreams()); + session.setMaxEncoderTableCapacity(client.getMaxEncoderTableCapacity()); long streamIdleTimeout = client.getStreamIdleTimeout(); if (streamIdleTimeout > 0) session.setStreamIdleTimeout(streamIdleTimeout); - Parser parser = new Parser(byteBufferPool, session, 4096, 8192); - parser.setMaxFrameLength(client.getMaxFrameLength()); - parser.setMaxSettingsKeys(client.getMaxSettingsKeys()); - RetainableByteBufferPool retainableByteBufferPool = byteBufferPool.asRetainableByteBufferPool(); - HTTP2ClientConnection connection = new HTTP2ClientConnection(client, retainableByteBufferPool, executor, endPoint, - parser, session, client.getInputBufferSize(), promise, listener); + session, client.getInputBufferSize(), sessionPromise, listener); connection.setUseInputDirectByteBuffers(client.isUseInputDirectByteBuffers()); connection.setUseOutputDirectByteBuffers(client.isUseOutputDirectByteBuffers()); connection.addEventListener(connectionListener); + parser.init(connection); + return customize(connection, context); } @@ -84,11 +89,11 @@ public class HTTP2ClientConnectionFactory implements ClientConnectionFactory private final Promise promise; private final Session.Listener listener; - private HTTP2ClientConnection(HTTP2Client client, RetainableByteBufferPool retainableByteBufferPool, Executor executor, EndPoint endpoint, Parser parser, ISession session, int bufferSize, Promise promise, Session.Listener listener) + private HTTP2ClientConnection(HTTP2Client client, RetainableByteBufferPool retainableByteBufferPool, Executor executor, EndPoint endpoint, HTTP2ClientSession session, int bufferSize, Promise sessionPromise, Session.Listener listener) { - super(retainableByteBufferPool, executor, endpoint, parser, session, bufferSize); + super(retainableByteBufferPool, executor, endpoint, session, bufferSize); this.client = client; - this.promise = promise; + this.promise = sessionPromise; this.listener = listener; } @@ -98,12 +103,52 @@ public class HTTP2ClientConnectionFactory implements ClientConnectionFactory Map settings = listener.onPreface(getSession()); if (settings == null) settings = new HashMap<>(); - settings.computeIfAbsent(SettingsFrame.INITIAL_WINDOW_SIZE, k -> client.getInitialStreamRecvWindow()); - settings.computeIfAbsent(SettingsFrame.MAX_CONCURRENT_STREAMS, k -> client.getMaxConcurrentPushedStreams()); - Integer maxFrameLength = settings.get(SettingsFrame.MAX_FRAME_SIZE); - if (maxFrameLength != null) - getParser().setMaxFrameLength(maxFrameLength); + // Below we want to populate any settings to send to the server + // that have a different default than what prescribed by the RFC. + // Changing the configuration is done when the SETTINGS is sent. + + settings.compute(SettingsFrame.HEADER_TABLE_SIZE, (k, v) -> + { + if (v == null) + { + v = client.getMaxDecoderTableCapacity(); + if (v == HpackContext.DEFAULT_MAX_TABLE_CAPACITY) + v = null; + } + return v; + }); + settings.computeIfAbsent(SettingsFrame.MAX_CONCURRENT_STREAMS, k -> client.getMaxConcurrentPushedStreams()); + settings.compute(SettingsFrame.INITIAL_WINDOW_SIZE, (k, v) -> + { + if (v == null) + { + v = client.getInitialStreamRecvWindow(); + if (v == FlowControlStrategy.DEFAULT_WINDOW_SIZE) + v = null; + } + return v; + }); + settings.compute(SettingsFrame.MAX_FRAME_SIZE, (k, v) -> + { + if (v == null) + { + v = client.getMaxFrameSize(); + if (v == Frame.DEFAULT_MAX_LENGTH) + v = null; + } + return v; + }); + settings.compute(SettingsFrame.MAX_HEADER_LIST_SIZE, (k, v) -> + { + if (v == null) + { + v = client.getMaxResponseHeadersSize(); + if (v <= 0) + v = null; + } + return v; + }); PrefaceFrame prefaceFrame = new PrefaceFrame(); SettingsFrame settingsFrame = new SettingsFrame(settings, false); diff --git a/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2ClientSession.java b/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2ClientSession.java index 546f1c2c31f..0b3f6ee24f9 100644 --- a/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2ClientSession.java +++ b/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2ClientSession.java @@ -13,6 +13,8 @@ package org.eclipse.jetty.http2.client; +import java.util.Map; + import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http2.CloseState; import org.eclipse.jetty.http2.ErrorCode; @@ -23,7 +25,9 @@ import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.http2.frames.PushPromiseFrame; +import org.eclipse.jetty.http2.frames.SettingsFrame; import org.eclipse.jetty.http2.generator.Generator; +import org.eclipse.jetty.http2.parser.Parser; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.thread.Scheduler; @@ -34,9 +38,9 @@ public class HTTP2ClientSession extends HTTP2Session { private static final Logger LOG = LoggerFactory.getLogger(HTTP2ClientSession.class); - public HTTP2ClientSession(Scheduler scheduler, EndPoint endPoint, Generator generator, Session.Listener listener, FlowControlStrategy flowControl) + public HTTP2ClientSession(Scheduler scheduler, EndPoint endPoint, Parser parser, Generator generator, Session.Listener listener, FlowControlStrategy flowControl) { - super(scheduler, endPoint, generator, listener, flowControl, 1); + super(scheduler, endPoint, parser, generator, listener, flowControl, 1); } @Override @@ -87,12 +91,30 @@ public class HTTP2ClientSession extends HTTP2Session } } + @Override + public void onSettings(SettingsFrame frame) + { + Map settings = frame.getSettings(); + Integer value = settings.get(SettingsFrame.ENABLE_PUSH); + // SPEC: servers can only send ENABLE_PUSH=0. + if (value != null && value != 0) + onConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "invalid_settings_frame"); + else + super.onSettings(frame); + } + @Override public void onPushPromise(PushPromiseFrame frame) { if (LOG.isDebugEnabled()) LOG.debug("Received {}", frame); + if (!isPushEnabled()) + { + onConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "unexpected_push_promise_frame"); + return; + } + int streamId = frame.getStreamId(); int pushStreamId = frame.getPromisedStreamId(); IStream stream = getStream(streamId); diff --git a/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/ConcurrentStreamCreationTest.java b/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/ConcurrentStreamCreationTest.java index 24a4d2ac463..34906439b55 100644 --- a/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/ConcurrentStreamCreationTest.java +++ b/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/ConcurrentStreamCreationTest.java @@ -98,6 +98,7 @@ public class ConcurrentStreamCreationTest extends AbstractTest x.printStackTrace(); } }).start()); + assertTrue(clientLatch.await(total, TimeUnit.MILLISECONDS), String.format("Missing streams on client: %d/%d", clientLatch.getCount(), total)); assertTrue(serverLatch.await(total, TimeUnit.MILLISECONDS), String.format("Missing streams on server: %d/%d", serverLatch.getCount(), total)); assertTrue(responseLatch.await(total, TimeUnit.MILLISECONDS), String.format("Missing response on client: %d/%d", clientLatch.getCount(), total)); diff --git a/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/FlowControlStrategyTest.java b/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/FlowControlStrategyTest.java index 7788d4dbbda..d1b4b51fdef 100644 --- a/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/FlowControlStrategyTest.java +++ b/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/FlowControlStrategyTest.java @@ -35,6 +35,7 @@ import org.eclipse.jetty.http.HttpScheme; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; +import org.eclipse.jetty.http2.AbstractFlowControlStrategy; import org.eclipse.jetty.http2.BufferingFlowControlStrategy; import org.eclipse.jetty.http2.ErrorCode; import org.eclipse.jetty.http2.FlowControlStrategy; @@ -64,6 +65,7 @@ import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; +import static org.awaitility.Awaitility.await; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -204,11 +206,9 @@ public abstract class FlowControlStrategyTest SettingsFrame frame = new SettingsFrame(settings, false); FutureCallback callback = new FutureCallback(); clientSession.settings(frame, callback); - callback.get(5, TimeUnit.SECONDS); + await().atMost(5, TimeUnit.SECONDS).until(() -> clientStream1.getRecvWindow() == 0); assertEquals(FlowControlStrategy.DEFAULT_WINDOW_SIZE, clientStream1.getSendWindow()); - assertEquals(0, clientStream1.getRecvWindow()); - settingsLatch.await(5, TimeUnit.SECONDS); // Now create a new stream, it must pick up the new value. MetaData.Request request2 = newRequest("POST", HttpFields.EMPTY); @@ -343,6 +343,11 @@ public abstract class FlowControlStrategyTest completable.thenRun(settingsLatch::countDown); assertTrue(settingsLatch.await(5, TimeUnit.SECONDS)); + await().atMost(5, TimeUnit.SECONDS).until(() -> + { + AbstractFlowControlStrategy flow = (AbstractFlowControlStrategy)((HTTP2Session)session).getFlowControlStrategy(); + return flow.getInitialStreamRecvWindow() == windowSize; + }); CountDownLatch dataLatch = new CountDownLatch(1); Exchanger exchanger = new Exchanger<>(); @@ -403,13 +408,14 @@ public abstract class FlowControlStrategyTest { int windowSize = 1536; Exchanger exchanger = new Exchanger<>(); - CountDownLatch settingsLatch = new CountDownLatch(1); CountDownLatch dataLatch = new CountDownLatch(1); + AtomicReference serverSessionRef = new AtomicReference<>(); start(new ServerSessionListener.Adapter() { @Override public Map onPreface(Session session) { + serverSessionRef.set((HTTP2Session)session); Map settings = new HashMap<>(); settings.put(SettingsFrame.INITIAL_WINDOW_SIZE, windowSize); return settings; @@ -458,21 +464,18 @@ public abstract class FlowControlStrategyTest } }); - Session session = newClient(new Session.Listener.Adapter() - { - @Override - public void onSettings(Session session, SettingsFrame frame) - { - settingsLatch.countDown(); - } - }); + Session clientSession = newClient(new Session.Listener.Adapter()); - assertTrue(settingsLatch.await(5, TimeUnit.SECONDS)); + await().atMost(5, TimeUnit.SECONDS).until(() -> + { + AbstractFlowControlStrategy flow = (AbstractFlowControlStrategy)serverSessionRef.get().getFlowControlStrategy(); + return flow.getInitialStreamRecvWindow() == windowSize; + }); MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); HeadersFrame requestFrame = new HeadersFrame(metaData, null, false); FuturePromise streamPromise = new FuturePromise<>(); - session.newStream(requestFrame, streamPromise, null); + clientSession.newStream(requestFrame, streamPromise, null); Stream stream = streamPromise.get(5, TimeUnit.SECONDS); int length = 5 * windowSize; diff --git a/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/GoAwayTest.java b/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/GoAwayTest.java index a5d0baa6c76..10c2415c5be 100644 --- a/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/GoAwayTest.java +++ b/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/GoAwayTest.java @@ -36,6 +36,7 @@ import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.api.server.ServerSessionListener; import org.eclipse.jetty.http2.frames.DataFrame; +import org.eclipse.jetty.http2.frames.FrameType; import org.eclipse.jetty.http2.frames.GoAwayFrame; import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.http2.frames.ResetFrame; @@ -1103,4 +1104,70 @@ public class GoAwayTest extends AbstractTest Assertions.assertFalse(((HTTP2Session)serverSessionRef.get()).getEndPoint().isOpen()); Assertions.assertFalse(((HTTP2Session)clientSession).getEndPoint().isOpen()); } + + @Test + public void testGoAwayNonZeroStreamId() throws Exception + { + CountDownLatch serverGoAwayLatch = new CountDownLatch(1); + CountDownLatch serverFailureLatch = new CountDownLatch(1); + CountDownLatch serverCloseLatch = new CountDownLatch(1); + start(new ServerSessionListener.Adapter() + { + @Override + public void onGoAway(Session session, GoAwayFrame frame) + { + serverGoAwayLatch.countDown(); + } + + @Override + public void onFailure(Session session, Throwable failure) + { + serverFailureLatch.countDown(); + } + + @Override + public void onClose(Session session, GoAwayFrame frame) + { + serverCloseLatch.countDown(); + } + }); + + CountDownLatch clientGoAwayLatch = new CountDownLatch(1); + CountDownLatch clientCloseLatch = new CountDownLatch(1); + Session clientSession = newClient(new Session.Listener.Adapter() + { + @Override + public void onGoAway(Session session, GoAwayFrame frame) + { + clientGoAwayLatch.countDown(); + } + + @Override + public void onClose(Session session, GoAwayFrame frame) + { + clientCloseLatch.countDown(); + } + }); + + // Wait until the client has finished the previous writes. + Thread.sleep(1000); + // Write an invalid GOAWAY frame. + ByteBuffer byteBuffer = ByteBuffer.allocate(17) + .put((byte)0) + .put((byte)0) + .put((byte)8) + .put((byte)FrameType.GO_AWAY.getType()) + .put((byte)0) + .putInt(1) // Non-Zero Stream ID + .putInt(0) + .putInt(ErrorCode.PROTOCOL_ERROR.code) + .flip(); + ((HTTP2Session)clientSession).getEndPoint().write(Callback.NOOP, byteBuffer); + + Assertions.assertFalse(serverGoAwayLatch.await(1, TimeUnit.SECONDS)); + Assertions.assertTrue(serverFailureLatch.await(5, TimeUnit.SECONDS)); + Assertions.assertTrue(serverCloseLatch.await(5, TimeUnit.SECONDS)); + Assertions.assertTrue(clientGoAwayLatch.await(5, TimeUnit.SECONDS)); + Assertions.assertTrue(clientCloseLatch.await(5, TimeUnit.SECONDS)); + } } diff --git a/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/HTTP2Test.java b/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/HTTP2Test.java index 966f45975e9..55df1599594 100644 --- a/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/HTTP2Test.java +++ b/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/HTTP2Test.java @@ -47,10 +47,7 @@ import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.http2.frames.ResetFrame; import org.eclipse.jetty.http2.frames.SettingsFrame; import org.eclipse.jetty.http2.hpack.HpackException; -import org.eclipse.jetty.http2.parser.RateControl; -import org.eclipse.jetty.http2.parser.ServerParser; import org.eclipse.jetty.http2.server.RawHTTP2ServerConnectionFactory; -import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; @@ -737,6 +734,7 @@ public class HTTP2Test extends AbstractTest @Test public void testGoAwayRespondedWithGoAway() throws Exception { + CountDownLatch goAwayLatch = new CountDownLatch(1); ServerSessionListener.Adapter serverListener = new ServerSessionListener.Adapter() { @Override @@ -748,24 +746,14 @@ public class HTTP2Test extends AbstractTest stream.getSession().close(ErrorCode.NO_ERROR.code, null, Callback.NOOP); return null; } - }; - CountDownLatch goAwayLatch = new CountDownLatch(1); - RawHTTP2ServerConnectionFactory connectionFactory = new RawHTTP2ServerConnectionFactory(new HttpConfiguration(), serverListener) - { + @Override - protected ServerParser newServerParser(Connector connector, ServerParser.Listener listener, RateControl rateControl) + public void onGoAway(Session session, GoAwayFrame frame) { - return super.newServerParser(connector, new ServerParser.Listener.Wrapper(listener) - { - @Override - public void onGoAway(GoAwayFrame frame) - { - super.onGoAway(frame); - goAwayLatch.countDown(); - } - }, rateControl); + goAwayLatch.countDown(); } }; + RawHTTP2ServerConnectionFactory connectionFactory = new RawHTTP2ServerConnectionFactory(new HttpConfiguration(), serverListener); prepareServer(connectionFactory); server.start(); diff --git a/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/PrefaceTest.java b/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/PrefaceTest.java index d73eca22fa5..f1d998130ac 100644 --- a/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/PrefaceTest.java +++ b/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/PrefaceTest.java @@ -30,7 +30,6 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; -import java.util.function.UnaryOperator; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpStatus; @@ -145,6 +144,7 @@ public class PrefaceTest extends AbstractTest session.close(ErrorCode.NO_ERROR.code, null, Callback.NOOP); } }); + connector.setIdleTimeout(1000); ByteBufferPool byteBufferPool = client.getByteBufferPool(); try (SocketChannel socket = SocketChannel.open()) @@ -164,15 +164,15 @@ public class PrefaceTest extends AbstractTest socket.write(buffers.toArray(new ByteBuffer[buffers.size()])); Queue settings = new ArrayDeque<>(); - Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter() + Parser parser = new Parser(byteBufferPool, 8192); + parser.init(new Parser.Listener.Adapter() { @Override public void onSettings(SettingsFrame frame) { settings.offer(frame); } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); + }); ByteBuffer buffer = byteBufferPool.acquire(1024, true); while (true) @@ -297,7 +297,8 @@ public class PrefaceTest extends AbstractTest CountDownLatch clientSettingsLatch = new CountDownLatch(1); AtomicBoolean responded = new AtomicBoolean(); - Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter() + Parser parser = new Parser(byteBufferPool, 8192); + parser.init(new Parser.Listener.Adapter() { @Override public void onSettings(SettingsFrame frame) @@ -314,8 +315,7 @@ public class PrefaceTest extends AbstractTest if (frame.isEndStream()) responded.set(true); } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); + }); // HTTP/2 parsing. while (true) diff --git a/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/SettingsTest.java b/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/SettingsTest.java new file mode 100644 index 00000000000..cb5f58caba5 --- /dev/null +++ b/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/SettingsTest.java @@ -0,0 +1,357 @@ +// +// ======================================================================== +// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.http2.client; + +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.http.HttpVersion; +import org.eclipse.jetty.http.MetaData; +import org.eclipse.jetty.http2.Flags; +import org.eclipse.jetty.http2.HTTP2Session; +import org.eclipse.jetty.http2.api.Session; +import org.eclipse.jetty.http2.api.Stream; +import org.eclipse.jetty.http2.api.server.ServerSessionListener; +import org.eclipse.jetty.http2.frames.FrameType; +import org.eclipse.jetty.http2.frames.GoAwayFrame; +import org.eclipse.jetty.http2.frames.HeadersFrame; +import org.eclipse.jetty.http2.frames.PushPromiseFrame; +import org.eclipse.jetty.http2.frames.SettingsFrame; +import org.eclipse.jetty.http2.hpack.HpackException; +import org.eclipse.jetty.io.ByteBufferPool; +import org.eclipse.jetty.util.Callback; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class SettingsTest extends AbstractTest +{ + @Test + public void testSettingsNonZeroStreamId() throws Exception + { + AtomicReference serverSettingsLatch = new AtomicReference<>(null); + CountDownLatch serverFailureLatch = new CountDownLatch(1); + CountDownLatch serverCloseLatch = new CountDownLatch(1); + start(new ServerSessionListener.Adapter() + { + @Override + public void onSettings(Session session, SettingsFrame frame) + { + CountDownLatch latch = serverSettingsLatch.get(); + if (latch != null) + latch.countDown(); + } + + @Override + public void onFailure(Session session, Throwable failure) + { + serverFailureLatch.countDown(); + } + + @Override + public void onClose(Session session, GoAwayFrame frame) + { + serverCloseLatch.countDown(); + } + }); + + CountDownLatch clientGoAwayLatch = new CountDownLatch(1); + CountDownLatch clientCloseLatch = new CountDownLatch(1); + Session clientSession = newClient(new Session.Listener.Adapter() + { + @Override + public void onGoAway(Session session, GoAwayFrame frame) + { + clientGoAwayLatch.countDown(); + } + + @Override + public void onClose(Session session, GoAwayFrame frame) + { + clientCloseLatch.countDown(); + } + }); + + // Wait until the client has finished the previous writes. + Thread.sleep(1000); + // Set the SETTINGS latch now, to avoid that it + // is counted down during connection establishment. + serverSettingsLatch.set(new CountDownLatch(1)); + // Write an invalid SETTINGS frame. + ByteBuffer byteBuffer = ByteBuffer.allocate(17) + .put((byte)0) + .put((byte)0) + .put((byte)0) + .put((byte)FrameType.SETTINGS.getType()) + .put((byte)0) + .putInt(1) // Non-Zero Stream ID + .flip(); + ((HTTP2Session)clientSession).getEndPoint().write(Callback.NOOP, byteBuffer); + + Assertions.assertFalse(serverSettingsLatch.get().await(1, TimeUnit.SECONDS)); + Assertions.assertTrue(serverFailureLatch.await(5, TimeUnit.SECONDS)); + Assertions.assertTrue(serverCloseLatch.await(5, TimeUnit.SECONDS)); + Assertions.assertTrue(clientGoAwayLatch.await(5, TimeUnit.SECONDS)); + Assertions.assertTrue(clientCloseLatch.await(5, TimeUnit.SECONDS)); + } + + @Test + public void testSettingsReplyWithPayload() throws Exception + { + AtomicReference serverSettingsLatch = new AtomicReference<>(null); + CountDownLatch serverFailureLatch = new CountDownLatch(1); + CountDownLatch serverCloseLatch = new CountDownLatch(1); + start(new ServerSessionListener.Adapter() + { + @Override + public void onSettings(Session session, SettingsFrame frame) + { + CountDownLatch latch = serverSettingsLatch.get(); + if (latch != null) + latch.countDown(); + } + + @Override + public void onFailure(Session session, Throwable failure) + { + serverFailureLatch.countDown(); + } + + @Override + public void onClose(Session session, GoAwayFrame frame) + { + serverCloseLatch.countDown(); + } + }); + + CountDownLatch clientGoAwayLatch = new CountDownLatch(1); + CountDownLatch clientCloseLatch = new CountDownLatch(1); + Session clientSession = newClient(new Session.Listener.Adapter() + { + @Override + public void onGoAway(Session session, GoAwayFrame frame) + { + clientGoAwayLatch.countDown(); + } + + @Override + public void onClose(Session session, GoAwayFrame frame) + { + clientCloseLatch.countDown(); + } + }); + + // Wait until the client has finished the previous writes. + Thread.sleep(1000); + // Set the SETTINGS latch now, to avoid that it + // is counted down during connection establishment. + serverSettingsLatch.set(new CountDownLatch(1)); + // Write an invalid SETTINGS frame. + ByteBuffer byteBuffer = ByteBuffer.allocate(17) + .put((byte)0) + .put((byte)0) + .put((byte)6) + .put((byte)FrameType.SETTINGS.getType()) + .put((byte)Flags.ACK) + .putInt(0) + .putShort((short)SettingsFrame.ENABLE_PUSH) + .putInt(1) + .flip(); + ((HTTP2Session)clientSession).getEndPoint().write(Callback.NOOP, byteBuffer); + + Assertions.assertFalse(serverSettingsLatch.get().await(1, TimeUnit.SECONDS)); + Assertions.assertTrue(serverFailureLatch.await(5, TimeUnit.SECONDS)); + Assertions.assertTrue(serverCloseLatch.await(5, TimeUnit.SECONDS)); + Assertions.assertTrue(clientGoAwayLatch.await(5, TimeUnit.SECONDS)); + Assertions.assertTrue(clientCloseLatch.await(5, TimeUnit.SECONDS)); + } + + @Test + public void testInvalidEnablePush() throws Exception + { + CountDownLatch serverFailureLatch = new CountDownLatch(1); + start(new ServerSessionListener.Adapter() + { + @Override + public void onFailure(Session session, Throwable failure) + { + serverFailureLatch.countDown(); + } + }); + + newClient(new Session.Listener.Adapter() + { + @Override + public Map onPreface(Session session) + { + Map settings = new HashMap<>(); + settings.put(SettingsFrame.ENABLE_PUSH, 2); // Invalid value. + return settings; + } + }); + + Assertions.assertTrue(serverFailureLatch.await(5, TimeUnit.SECONDS)); + } + + @Test + public void testServerSendsEnablePush() throws Exception + { + start(new ServerSessionListener.Adapter() + { + @Override + public Map onPreface(Session session) + { + Map settings = new HashMap<>(); + // Servers cannot send "enable_push==1". + settings.put(SettingsFrame.ENABLE_PUSH, 1); + return settings; + } + }); + + CountDownLatch clientFailureLatch = new CountDownLatch(1); + newClient(new Session.Listener.Adapter() + { + @Override + public void onFailure(Session session, Throwable failure) + { + clientFailureLatch.countDown(); + } + }); + + Assertions.assertTrue(clientFailureLatch.await(5, TimeUnit.SECONDS)); + } + + @Test + public void testServerCannotSendsPushPromiseWithPushDisabled() throws Exception + { + CountDownLatch serverPushFailureLatch = new CountDownLatch(1); + start(new ServerSessionListener.Adapter() + { + @Override + public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) + { + MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, HttpFields.EMPTY); + stream.headers(new HeadersFrame(stream.getId(), response, null, true)) + .thenAccept(s -> + { + MetaData.Request push = newRequest("GET", "/push", HttpFields.EMPTY); + try + { + s.push(new PushPromiseFrame(s.getId(), push), new Stream.Listener.Adapter()); + } + catch (IllegalStateException x) + { + serverPushFailureLatch.countDown(); + } + }); + return null; + } + }); + + Session clientSession = newClient(new Session.Listener.Adapter() + { + @Override + public Map onPreface(Session session) + { + Map settings = new HashMap<>(); + // Disable push. + settings.put(SettingsFrame.ENABLE_PUSH, 0); + return settings; + } + }); + + CountDownLatch clientResponseLatch = new CountDownLatch(1); + CountDownLatch clientPushLatch = new CountDownLatch(1); + MetaData.Request request = newRequest("GET", HttpFields.EMPTY); + HeadersFrame frame = new HeadersFrame(request, null, true); + clientSession.newStream(frame, new Stream.Listener.Adapter() + { + @Override + public void onHeaders(Stream stream, HeadersFrame frame) + { + clientResponseLatch.countDown(); + } + + @Override + public Stream.Listener onPush(Stream stream, PushPromiseFrame frame) + { + clientPushLatch.countDown(); + return null; + } + }); + + Assertions.assertTrue(serverPushFailureLatch.await(5, TimeUnit.SECONDS)); + Assertions.assertTrue(clientResponseLatch.await(5, TimeUnit.SECONDS)); + Assertions.assertFalse(clientPushLatch.await(1, TimeUnit.SECONDS)); + } + + @Test + public void testClientReceivesPushPromiseWhenPushDisabled() throws Exception + { + start(new ServerSessionListener.Adapter() + { + @Override + public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) + { + try + { + HTTP2Session session = (HTTP2Session)stream.getSession(); + ByteBufferPool.Lease lease = new ByteBufferPool.Lease(connector.getByteBufferPool()); + MetaData.Request push = newRequest("GET", "/push", HttpFields.EMPTY); + PushPromiseFrame pushFrame = new PushPromiseFrame(stream.getId(), 2, push); + session.getGenerator().control(lease, pushFrame); + session.getEndPoint().write(Callback.NOOP, lease.getByteBuffers().toArray(ByteBuffer[]::new)); + return null; + } + catch (HpackException x) + { + return null; + } + } + }); + + CountDownLatch clientFailureLatch = new CountDownLatch(1); + Session clientSession = newClient(new Session.Listener.Adapter() + { + @Override + public Map onPreface(Session session) + { + Map settings = new HashMap<>(); + // Disable push. + settings.put(SettingsFrame.ENABLE_PUSH, 0); + return settings; + } + + @Override + public void onFailure(Session session, Throwable failure) + { + clientFailureLatch.countDown(); + } + }); + + // Wait until the server has finished the previous writes. + Thread.sleep(1000); + + MetaData.Request request = newRequest("GET", HttpFields.EMPTY); + HeadersFrame frame = new HeadersFrame(request, null, true); + clientSession.newStream(frame, new Stream.Listener.Adapter()); + + Assertions.assertTrue(clientFailureLatch.await(5, TimeUnit.SECONDS)); + } +} diff --git a/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/TrailersTest.java b/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/TrailersTest.java index 4306447eaa9..4a86782654b 100644 --- a/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/TrailersTest.java +++ b/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/TrailersTest.java @@ -40,6 +40,7 @@ import org.eclipse.jetty.http2.frames.DataFrame; import org.eclipse.jetty.http2.frames.Frame; import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.http2.frames.ResetFrame; +import org.eclipse.jetty.http2.generator.Generator; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Response; import org.eclipse.jetty.util.Callback; @@ -368,7 +369,8 @@ public class TrailersTest extends AbstractTest completable.thenRun(() -> { // Disable checks for invalid headers. - ((HTTP2Session)session).getGenerator().setValidateHpackEncoding(false); + Generator generator = ((HTTP2Session)session).getGenerator(); + generator.getHpackEncoder().setValidateEncoding(false); // Invalid trailer: cannot contain pseudo headers. HttpFields.Mutable trailerFields = HttpFields.build(); trailerFields.put(HttpHeader.C_METHOD, "GET"); diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Connection.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Connection.java index 26a5feaf742..662cc530c6c 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Connection.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Connection.java @@ -21,6 +21,14 @@ import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicLong; import org.eclipse.jetty.http2.frames.DataFrame; +import org.eclipse.jetty.http2.frames.GoAwayFrame; +import org.eclipse.jetty.http2.frames.HeadersFrame; +import org.eclipse.jetty.http2.frames.PingFrame; +import org.eclipse.jetty.http2.frames.PriorityFrame; +import org.eclipse.jetty.http2.frames.PushPromiseFrame; +import org.eclipse.jetty.http2.frames.ResetFrame; +import org.eclipse.jetty.http2.frames.SettingsFrame; +import org.eclipse.jetty.http2.frames.WindowUpdateFrame; import org.eclipse.jetty.http2.parser.Parser; import org.eclipse.jetty.io.AbstractConnection; import org.eclipse.jetty.io.Connection; @@ -37,7 +45,7 @@ import org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class HTTP2Connection extends AbstractConnection implements WriteFlusher.Listener, Connection.UpgradeTo +public class HTTP2Connection extends AbstractConnection implements Parser.Listener, WriteFlusher.Listener, Connection.UpgradeTo { protected static final Logger LOG = LoggerFactory.getLogger(HTTP2Connection.class); @@ -46,23 +54,20 @@ public class HTTP2Connection extends AbstractConnection implements WriteFlusher. private final HTTP2Producer producer = new HTTP2Producer(); private final AtomicLong bytesIn = new AtomicLong(); private final RetainableByteBufferPool retainableByteBufferPool; - private final Parser parser; - private final ISession session; + private final HTTP2Session session; private final int bufferSize; private final ExecutionStrategy strategy; private boolean useInputDirectByteBuffers; private boolean useOutputDirectByteBuffers; - protected HTTP2Connection(RetainableByteBufferPool retainableByteBufferPool, Executor executor, EndPoint endPoint, Parser parser, ISession session, int bufferSize) + protected HTTP2Connection(RetainableByteBufferPool retainableByteBufferPool, Executor executor, EndPoint endPoint, HTTP2Session session, int bufferSize) { super(endPoint, executor); this.retainableByteBufferPool = retainableByteBufferPool; - this.parser = parser; this.session = session; this.bufferSize = bufferSize; this.strategy = new AdaptiveExecutionStrategy(producer, executor); LifeCycle.start(strategy); - parser.init(ParserListener::new); } @Override @@ -96,11 +101,6 @@ public class HTTP2Connection extends AbstractConnection implements WriteFlusher. return session; } - protected Parser getParser() - { - return parser; - } - @Override public void onUpgradeTo(ByteBuffer buffer) { @@ -231,6 +231,78 @@ public class HTTP2Connection extends AbstractConnection implements WriteFlusher. } } + @Override + public void onHeaders(HeadersFrame frame) + { + session.onHeaders(frame); + } + + @Override + public void onData(DataFrame frame) + { + NetworkBuffer networkBuffer = producer.networkBuffer; + // Retain the network buffer because the frame payload is a slice of it. + networkBuffer.retain(); + // The network buffer is also the callback used to release the frame payload. + Callback callback = networkBuffer; + session.onData(frame, callback); + } + + @Override + public void onPriority(PriorityFrame frame) + { + session.onPriority(frame); + } + + @Override + public void onReset(ResetFrame frame) + { + session.onReset(frame); + } + + @Override + public void onSettings(SettingsFrame frame) + { + session.onSettings(frame); + } + + @Override + public void onPushPromise(PushPromiseFrame frame) + { + session.onPushPromise(frame); + } + + @Override + public void onPing(PingFrame frame) + { + session.onPing(frame); + } + + @Override + public void onGoAway(GoAwayFrame frame) + { + session.onGoAway(frame); + } + + @Override + public void onWindowUpdate(WindowUpdateFrame frame) + { + session.onWindowUpdate(frame); + } + + @Override + public void onStreamFailure(int streamId, int error, String reason) + { + session.onStreamFailure(streamId, error, reason); + } + + @Override + public void onConnectionFailure(int error, String reason) + { + producer.failed = true; + session.onConnectionFailure(error, reason); + } + @Override public void onFlushed(long bytes) throws IOException { @@ -275,7 +347,7 @@ public class HTTP2Connection extends AbstractConnection implements WriteFlusher. { while (networkBuffer.hasRemaining()) { - parser.parse(networkBuffer.getBuffer()); + session.getParser().parse(networkBuffer.getBuffer()); if (failed) return null; } @@ -391,30 +463,6 @@ public class HTTP2Connection extends AbstractConnection implements WriteFlusher. } } - private class ParserListener extends Parser.Listener.Wrapper - { - private ParserListener(Parser.Listener listener) - { - super(listener); - } - - @Override - public void onData(DataFrame frame) - { - NetworkBuffer networkBuffer = producer.networkBuffer; - networkBuffer.retain(); - Callback callback = networkBuffer; - session.onData(frame, callback); - } - - @Override - public void onConnectionFailure(int error, String reason) - { - producer.failed = true; - super.onConnectionFailure(error, reason); - } - } - private class NetworkBuffer implements Callback { private final RetainableByteBuffer delegate; diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Session.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Session.java index a923bedc204..ba5078e7719 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Session.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Session.java @@ -54,6 +54,7 @@ import org.eclipse.jetty.http2.frames.SettingsFrame; import org.eclipse.jetty.http2.frames.StreamFrame; import org.eclipse.jetty.http2.frames.WindowUpdateFrame; import org.eclipse.jetty.http2.generator.Generator; +import org.eclipse.jetty.http2.hpack.HpackEncoder; import org.eclipse.jetty.http2.hpack.HpackException; import org.eclipse.jetty.http2.parser.Parser; import org.eclipse.jetty.io.ByteBufferPool; @@ -94,6 +95,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio private final AtomicInteger recvWindow = new AtomicInteger(); private final AtomicLong bytesWritten = new AtomicLong(); private final EndPoint endPoint; + private final Parser parser; private final Generator generator; private final Session.Listener listener; private final FlowControlStrategy flowControl; @@ -104,12 +106,14 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio private long streamIdleTimeout; private int initialSessionRecvWindow; private int writeThreshold; + private int maxEncoderTableCapacity; private boolean pushEnabled; private boolean connectProtocolEnabled; - public HTTP2Session(Scheduler scheduler, EndPoint endPoint, Generator generator, Session.Listener listener, FlowControlStrategy flowControl, int initialStreamId) + public HTTP2Session(Scheduler scheduler, EndPoint endPoint, Parser parser, Generator generator, Session.Listener listener, FlowControlStrategy flowControl, int initialStreamId) { this.endPoint = endPoint; + this.parser = parser; this.generator = generator; this.listener = listener; this.flowControl = flowControl; @@ -207,11 +211,27 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio this.writeThreshold = writeThreshold; } + @ManagedAttribute("The HPACK encoder dynamic table maximum capacity") + public int getMaxEncoderTableCapacity() + { + return maxEncoderTableCapacity; + } + + public void setMaxEncoderTableCapacity(int maxEncoderTableCapacity) + { + this.maxEncoderTableCapacity = maxEncoderTableCapacity; + } + public EndPoint getEndPoint() { return endPoint; } + public Parser getParser() + { + return parser; + } + public Generator getGenerator() { return generator; @@ -348,8 +368,20 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio if (frame.isReply()) return; - // Iterate over all settings - for (Map.Entry entry : frame.getSettings().entrySet()) + Map settings = frame.getSettings(); + configure(settings, false); + notifySettings(this, frame); + + if (reply) + { + SettingsFrame replyFrame = new SettingsFrame(Collections.emptyMap(), true); + settings(replyFrame, Callback.NOOP); + } + } + + private void configure(Map settings, boolean local) + { + for (Map.Entry entry : settings.entrySet()) { int key = entry.getKey(); int value = entry.getValue(); @@ -358,8 +390,17 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio case SettingsFrame.HEADER_TABLE_SIZE: { if (LOG.isDebugEnabled()) - LOG.debug("Updating HPACK header table size to {} for {}", value, this); - generator.setHeaderTableSize(value); + LOG.debug("Updating HPACK {} max table capacity to {} for {}", local ? "decoder" : "encoder", value, this); + if (local) + { + parser.getHpackDecoder().setMaxTableCapacity(value); + } + else + { + HpackEncoder hpackEncoder = generator.getHpackEncoder(); + hpackEncoder.setMaxTableCapacity(value); + hpackEncoder.setTableCapacity(Math.min(value, getMaxEncoderTableCapacity())); + } break; } case SettingsFrame.ENABLE_PUSH: @@ -373,29 +414,38 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio case SettingsFrame.MAX_CONCURRENT_STREAMS: { if (LOG.isDebugEnabled()) - LOG.debug("Updating max local concurrent streams to {} for {}", value, this); - maxLocalStreams = value; + LOG.debug("Updating max {} concurrent streams to {} for {}", local ? "remote" : "local", value, this); + if (local) + maxRemoteStreams = value; + else + maxLocalStreams = value; break; } case SettingsFrame.INITIAL_WINDOW_SIZE: { if (LOG.isDebugEnabled()) LOG.debug("Updating initial stream window size to {} for {}", value, this); - flowControl.updateInitialStreamWindow(this, value, false); + flowControl.updateInitialStreamWindow(this, value, local); break; } case SettingsFrame.MAX_FRAME_SIZE: { if (LOG.isDebugEnabled()) - LOG.debug("Updating max frame size to {} for {}", value, this); - generator.setMaxFrameSize(value); + LOG.debug("Updating {} max frame size to {} for {}", local ? "parser" : "generator", value, this); + if (local) + parser.setMaxFrameSize(value); + else + generator.setMaxFrameSize(value); break; } case SettingsFrame.MAX_HEADER_LIST_SIZE: { if (LOG.isDebugEnabled()) - LOG.debug("Updating max header list size to {} for {}", value, this); - generator.setMaxHeaderListSize(value); + LOG.debug("Updating {} max header list size to {} for {}", local ? "decoder" : "encoder", value, this); + if (local) + parser.getHpackDecoder().setMaxHeaderListSize(value); + else + generator.getHpackEncoder().setMaxHeaderListSize(value); break; } case SettingsFrame.ENABLE_CONNECT_PROTOCOL: @@ -414,13 +464,6 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio } } } - notifySettings(this, frame); - - if (reply) - { - SettingsFrame replyFrame = new SettingsFrame(Collections.emptyMap(), true); - settings(replyFrame, Callback.NOOP); - } } @Override @@ -629,6 +672,8 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio @Override public void push(IStream stream, Promise promise, PushPromiseFrame frame, Stream.Listener listener) { + if (!isPushEnabled()) + throw new IllegalStateException("Push is disabled"); streamsState.push(frame, new Promise.Wrapper<>(promise) { @Override @@ -1283,9 +1328,8 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio case SETTINGS: { SettingsFrame settingsFrame = (SettingsFrame)frame; - Integer initialWindow = settingsFrame.getSettings().get(SettingsFrame.INITIAL_WINDOW_SIZE); - if (initialWindow != null) - flowControl.updateInitialStreamWindow(HTTP2Session.this, initialWindow, true); + if (!settingsFrame.isReply()) + configure(settingsFrame.getSettings(), true); break; } default: diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/generator/Generator.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/generator/Generator.java index 7d86f783fbc..a0318fae9fc 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/generator/Generator.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/generator/Generator.java @@ -30,20 +30,32 @@ public class Generator public Generator(ByteBufferPool byteBufferPool) { - this(byteBufferPool, 4096, 0); + this(byteBufferPool, 0); } - public Generator(ByteBufferPool byteBufferPool, int maxDynamicTableSize, int maxHeaderBlockFragment) + @Deprecated + public Generator(ByteBufferPool byteBufferPool, int maxTableCapacity, int maxHeaderBlockFragment) { - this(byteBufferPool, true, maxDynamicTableSize, maxHeaderBlockFragment); + this(byteBufferPool, maxHeaderBlockFragment); } - public Generator(ByteBufferPool byteBufferPool, boolean useDirectByteBuffers, int maxDynamicTableSize, int maxHeaderBlockFragment) + @Deprecated + public Generator(ByteBufferPool byteBufferPool, boolean useDirectByteBuffers, int maxTableCapacity, int maxHeaderBlockFragment) + { + this(byteBufferPool, useDirectByteBuffers, maxHeaderBlockFragment); + } + + public Generator(ByteBufferPool byteBufferPool, int maxHeaderBlockFragment) + { + this(byteBufferPool, true, maxHeaderBlockFragment); + } + + public Generator(ByteBufferPool byteBufferPool, boolean useDirectByteBuffers, int maxHeaderBlockFragment) { this.byteBufferPool = byteBufferPool; headerGenerator = new HeaderGenerator(useDirectByteBuffers); - hpackEncoder = new HpackEncoder(maxDynamicTableSize); + hpackEncoder = new HpackEncoder(); this.generators = new FrameGenerator[FrameType.values().length]; this.generators[FrameType.HEADERS.getType()] = new HeadersGenerator(headerGenerator, hpackEncoder, maxHeaderBlockFragment); @@ -66,14 +78,21 @@ public class Generator return byteBufferPool; } - public void setValidateHpackEncoding(boolean validateEncoding) + public HpackEncoder getHpackEncoder() { - hpackEncoder.setValidateEncoding(validateEncoding); + return hpackEncoder; } - public void setHeaderTableSize(int headerTableSize) + @Deprecated + public void setValidateHpackEncoding(boolean validateEncoding) { - hpackEncoder.setRemoteMaxDynamicTableSize(headerTableSize); + getHpackEncoder().setValidateEncoding(validateEncoding); + } + + @Deprecated + public void setHeaderTableSize(int maxTableSize) + { + getHpackEncoder().setTableCapacity(maxTableSize); } public void setMaxFrameSize(int maxFrameSize) @@ -91,8 +110,9 @@ public class Generator return dataGenerator.generate(lease, frame, maxLength); } + @Deprecated public void setMaxHeaderListSize(int value) { - hpackEncoder.setMaxHeaderListSize(value); + getHpackEncoder().setMaxHeaderListSize(value); } } diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/GoAwayBodyParser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/GoAwayBodyParser.java index 2ffc848f78d..5bc6bc7999e 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/GoAwayBodyParser.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/GoAwayBodyParser.java @@ -51,6 +51,9 @@ public class GoAwayBodyParser extends BodyParser { case PREPARE: { + // SPEC: wrong streamId is treated as connection error. + if (getStreamId() != 0) + return connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_go_away_frame"); state = State.LAST_STREAM_ID; length = getBodyLength(); break; diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/Parser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/Parser.java index e308f696079..fcae69d7d14 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/Parser.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/Parser.java @@ -14,7 +14,6 @@ package org.eclipse.jetty.http2.parser; import java.nio.ByteBuffer; -import java.util.function.UnaryOperator; import org.eclipse.jetty.http2.ErrorCode; import org.eclipse.jetty.http2.Flags; @@ -44,33 +43,46 @@ public class Parser private static final Logger LOG = LoggerFactory.getLogger(Parser.class); private final ByteBufferPool byteBufferPool; - private final Listener listener; private final HeaderParser headerParser; private final HpackDecoder hpackDecoder; private final BodyParser[] bodyParsers; + private Listener listener; private UnknownBodyParser unknownBodyParser; - private int maxFrameLength = Frame.DEFAULT_MAX_LENGTH; + private int maxFrameSize = Frame.DEFAULT_MAX_LENGTH; private int maxSettingsKeys = SettingsFrame.DEFAULT_MAX_KEYS; private boolean continuation; private State state = State.HEADER; - public Parser(ByteBufferPool byteBufferPool, Listener listener, int maxDynamicTableSize, int maxHeaderSize) + @Deprecated + public Parser(ByteBufferPool byteBufferPool, int maxTableCapacity, int maxHeaderSize) { - this(byteBufferPool, listener, maxDynamicTableSize, maxHeaderSize, RateControl.NO_RATE_CONTROL); + this(byteBufferPool, maxHeaderSize); } - public Parser(ByteBufferPool byteBufferPool, Listener listener, int maxDynamicTableSize, int maxHeaderSize, RateControl rateControl) + public Parser(ByteBufferPool byteBufferPool, int maxHeaderSize) + { + this(byteBufferPool, maxHeaderSize, RateControl.NO_RATE_CONTROL); + } + + @Deprecated + public Parser(ByteBufferPool byteBufferPool, int maxTableSize, int maxHeaderSize, RateControl rateControl) + { + this(byteBufferPool, maxHeaderSize, rateControl); + } + + public Parser(ByteBufferPool byteBufferPool, int maxHeaderSize, RateControl rateControl) { this.byteBufferPool = byteBufferPool; - this.listener = listener; this.headerParser = new HeaderParser(rateControl == null ? RateControl.NO_RATE_CONTROL : rateControl); - this.hpackDecoder = new HpackDecoder(maxDynamicTableSize, maxHeaderSize); + this.hpackDecoder = new HpackDecoder(maxHeaderSize); this.bodyParsers = new BodyParser[FrameType.values().length]; } - public void init(UnaryOperator wrapper) + public void init(Listener listener) { - Listener listener = wrapper.apply(this.listener); + if (this.listener != null) + throw new IllegalStateException("Invalid parser initialization"); + this.listener = listener; unknownBodyParser = new UnknownBodyParser(headerParser, listener); HeaderBlockParser headerBlockParser = new HeaderBlockParser(headerParser, byteBufferPool, hpackDecoder, unknownBodyParser); HeaderBlockFragments headerBlockFragments = new HeaderBlockFragments(byteBufferPool); @@ -86,6 +98,16 @@ public class Parser bodyParsers[FrameType.CONTINUATION.getType()] = new ContinuationBodyParser(headerParser, listener, headerBlockParser, headerBlockFragments); } + protected Listener getListener() + { + return listener; + } + + public HpackDecoder getHpackDecoder() + { + return hpackDecoder; + } + private void reset() { headerParser.reset(); @@ -146,7 +168,7 @@ public class Parser if (LOG.isDebugEnabled()) LOG.debug("Parsed {} frame header from {}@{}", headerParser, buffer, Integer.toHexString(buffer.hashCode())); - if (headerParser.getLength() > getMaxFrameLength()) + if (headerParser.getLength() > getMaxFrameSize()) return connectionFailure(buffer, ErrorCode.FRAME_SIZE_ERROR, "invalid_frame_length"); FrameType frameType = FrameType.from(getFrameType()); @@ -214,14 +236,26 @@ public class Parser return headerParser.hasFlag(bit); } + @Deprecated public int getMaxFrameLength() { - return maxFrameLength; + return getMaxFrameSize(); } - public void setMaxFrameLength(int maxFrameLength) + @Deprecated + public void setMaxFrameLength(int maxFrameSize) { - this.maxFrameLength = maxFrameLength; + setMaxFrameSize(maxFrameSize); + } + + public int getMaxFrameSize() + { + return maxFrameSize; + } + + public void setMaxFrameSize(int maxFrameSize) + { + this.maxFrameSize = maxFrameSize; } public int getMaxSettingsKeys() diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/ServerParser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/ServerParser.java index fa1cb0f65a8..6a51b6fdc3a 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/ServerParser.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/ServerParser.java @@ -27,18 +27,34 @@ public class ServerParser extends Parser { private static final Logger LOG = LoggerFactory.getLogger(ServerParser.class); - private final Listener listener; - private final PrefaceParser prefaceParser; + private PrefaceParser prefaceParser; private State state = State.PREFACE; private boolean notifyPreface = true; - public ServerParser(ByteBufferPool byteBufferPool, Listener listener, int maxDynamicTableSize, int maxHeaderSize, RateControl rateControl) + @Deprecated + public ServerParser(ByteBufferPool byteBufferPool, int maxTableSize, int maxHeaderSize, RateControl rateControl) { - super(byteBufferPool, listener, maxDynamicTableSize, maxHeaderSize, rateControl); - this.listener = listener; + this(byteBufferPool, maxHeaderSize, rateControl); + } + + public ServerParser(ByteBufferPool byteBufferPool, int maxHeaderSize, RateControl rateControl) + { + super(byteBufferPool, maxHeaderSize, rateControl); + } + + @Override + public void init(Parser.Listener listener) + { + super.init(listener); this.prefaceParser = new PrefaceParser(listener); } + @Override + protected Listener getListener() + { + return (Listener)super.getListener(); + } + /** *

A direct upgrade is an unofficial upgrade from HTTP/1.1 to HTTP/2.0.

*

A direct upgrade is initiated when {@code org.eclipse.jetty.server.HttpConnection} @@ -132,6 +148,7 @@ public class ServerParser extends Parser private void notifyPreface() { + Listener listener = getListener(); try { listener.onPreface(); diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/SettingsBodyParser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/SettingsBodyParser.java index f6aa685925c..99d914a07d7 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/SettingsBodyParser.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/SettingsBodyParser.java @@ -68,6 +68,8 @@ public class SettingsBodyParser extends BodyParser @Override protected void emptyBody(ByteBuffer buffer) { + if (!validateFrame(buffer, getStreamId(), 0)) + return; boolean isReply = hasFlag(Flags.ACK); SettingsFrame frame = new SettingsFrame(Collections.emptyMap(), isReply); if (!isReply && !rateControlOnEvent(frame)) @@ -76,6 +78,17 @@ public class SettingsBodyParser extends BodyParser onSettings(frame); } + private boolean validateFrame(ByteBuffer buffer, int streamId, int bodyLength) + { + // SPEC: wrong streamId is treated as connection error. + if (streamId != 0) + return connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_settings_frame"); + // SPEC: reply with body is treated as connection error. + if (hasFlag(Flags.ACK) && bodyLength > 0) + return connectionFailure(buffer, ErrorCode.FRAME_SIZE_ERROR.code, "invalid_settings_frame"); + return true; + } + @Override public boolean parse(ByteBuffer buffer) { @@ -90,9 +103,8 @@ public class SettingsBodyParser extends BodyParser { case PREPARE: { - // SPEC: wrong streamId is treated as connection error. - if (streamId != 0) - return connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_settings_frame"); + if (!validateFrame(buffer, streamId, bodyLength)) + return false; length = bodyLength; settings = new HashMap<>(); state = State.SETTING_ID; diff --git a/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/ContinuationParseTest.java b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/ContinuationParseTest.java index 1086ea7bf9e..b4604bc4c8d 100644 --- a/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/ContinuationParseTest.java +++ b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/ContinuationParseTest.java @@ -16,7 +16,6 @@ package org.eclipse.jetty.http2.frames; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; -import java.util.function.UnaryOperator; import org.eclipse.jetty.http.HostPortHttpField; import org.eclipse.jetty.http.HttpField; @@ -46,7 +45,8 @@ public class ContinuationParseTest HeadersGenerator generator = new HeadersGenerator(new HeaderGenerator(), new HpackEncoder()); final List frames = new ArrayList<>(); - Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter() + Parser parser = new Parser(byteBufferPool, 8192); + parser.init(new Parser.Listener.Adapter() { @Override public void onHeaders(HeadersFrame frame) @@ -59,8 +59,7 @@ public class ContinuationParseTest { frames.add(new HeadersFrame(null, null, false)); } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); + }); // Iterate a few times to be sure the parser is properly reset. for (int i = 0; i < 2; ++i) diff --git a/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/DataGenerateParseTest.java b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/DataGenerateParseTest.java index 81fdaa8414d..6f6a0b02923 100644 --- a/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/DataGenerateParseTest.java +++ b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/DataGenerateParseTest.java @@ -17,7 +17,6 @@ import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import java.util.Random; -import java.util.function.UnaryOperator; import org.eclipse.jetty.http2.generator.DataGenerator; import org.eclipse.jetty.http2.generator.HeaderGenerator; @@ -88,15 +87,15 @@ public class DataGenerateParseTest DataGenerator generator = new DataGenerator(new HeaderGenerator()); final List frames = new ArrayList<>(); - Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter() + Parser parser = new Parser(byteBufferPool, 8192); + parser.init(new Parser.Listener.Adapter() { @Override public void onData(DataFrame frame) { frames.add(frame); } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); + }); // Iterate a few times to be sure generator and parser are properly reset. for (int i = 0; i < 2; ++i) @@ -128,15 +127,15 @@ public class DataGenerateParseTest DataGenerator generator = new DataGenerator(new HeaderGenerator()); final List frames = new ArrayList<>(); - Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter() + Parser parser = new Parser(byteBufferPool, 8192); + parser.init(new Parser.Listener.Adapter() { @Override public void onData(DataFrame frame) { frames.add(frame); } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); + }); // Iterate a few times to be sure generator and parser are properly reset. for (int i = 0; i < 2; ++i) diff --git a/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/FrameFloodTest.java b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/FrameFloodTest.java index da35a21a58b..a5d51fc05cb 100644 --- a/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/FrameFloodTest.java +++ b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/FrameFloodTest.java @@ -16,7 +16,6 @@ package org.eclipse.jetty.http2.frames; import java.nio.ByteBuffer; import java.time.Duration; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.UnaryOperator; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; @@ -123,15 +122,15 @@ public class FrameFloodTest private void testFrameFlood(byte[] preamble, byte[] bytes) { AtomicBoolean failed = new AtomicBoolean(); - Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter() + Parser parser = new Parser(byteBufferPool, 8192, new WindowRateControl(8, Duration.ofSeconds(1))); + parser.init(new Parser.Listener.Adapter() { @Override public void onConnectionFailure(int error, String reason) { failed.set(true); } - }, 4096, 8192, new WindowRateControl(8, Duration.ofSeconds(1))); - parser.init(UnaryOperator.identity()); + }); if (preamble != null) { diff --git a/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/GoAwayGenerateParseTest.java b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/GoAwayGenerateParseTest.java index 2263be92046..b6c8ac8e454 100644 --- a/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/GoAwayGenerateParseTest.java +++ b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/GoAwayGenerateParseTest.java @@ -17,7 +17,6 @@ import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import java.util.Random; -import java.util.function.UnaryOperator; import org.eclipse.jetty.http2.generator.GoAwayGenerator; import org.eclipse.jetty.http2.generator.HeaderGenerator; @@ -40,15 +39,15 @@ public class GoAwayGenerateParseTest GoAwayGenerator generator = new GoAwayGenerator(new HeaderGenerator()); final List frames = new ArrayList<>(); - Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter() + Parser parser = new Parser(byteBufferPool, 8192); + parser.init(new Parser.Listener.Adapter() { @Override public void onGoAway(GoAwayFrame frame) { frames.add(frame); } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); + }); int lastStreamId = 13; int error = 17; @@ -82,15 +81,15 @@ public class GoAwayGenerateParseTest GoAwayGenerator generator = new GoAwayGenerator(new HeaderGenerator()); final List frames = new ArrayList<>(); - Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter() + Parser parser = new Parser(byteBufferPool, 8192); + parser.init(new Parser.Listener.Adapter() { @Override public void onGoAway(GoAwayFrame frame) { frames.add(frame); } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); + }); int lastStreamId = 13; int error = 17; diff --git a/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/HeadersGenerateParseTest.java b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/HeadersGenerateParseTest.java index 0f7506203c0..9ce79fd5aee 100644 --- a/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/HeadersGenerateParseTest.java +++ b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/HeadersGenerateParseTest.java @@ -16,7 +16,6 @@ package org.eclipse.jetty.http2.frames; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; -import java.util.function.UnaryOperator; import org.eclipse.jetty.http.HostPortHttpField; import org.eclipse.jetty.http.HttpField; @@ -52,15 +51,15 @@ public class HeadersGenerateParseTest MetaData.Request metaData = new MetaData.Request("GET", HttpScheme.HTTP.asString(), new HostPortHttpField("localhost:8080"), "/path", HttpVersion.HTTP_2, fields, -1); final List frames = new ArrayList<>(); - Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter() + Parser parser = new Parser(byteBufferPool, 8192); + parser.init(new Parser.Listener.Adapter() { @Override public void onHeaders(HeadersFrame frame) { frames.add(frame); } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); + }); // Iterate a few times to be sure generator and parser are properly reset. for (int i = 0; i < 2; ++i) @@ -105,15 +104,15 @@ public class HeadersGenerateParseTest HeadersGenerator generator = new HeadersGenerator(new HeaderGenerator(), new HpackEncoder()); final List frames = new ArrayList<>(); - Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter() + Parser parser = new Parser(byteBufferPool, 8192); + parser.init(new Parser.Listener.Adapter() { @Override public void onHeaders(HeadersFrame frame) { frames.add(frame); } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); + }); // Iterate a few times to be sure generator and parser are properly reset. for (int i = 0; i < 2; ++i) diff --git a/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/HeadersTooLargeParseTest.java b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/HeadersTooLargeParseTest.java index fab3a5b2ad8..587c1c2958a 100644 --- a/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/HeadersTooLargeParseTest.java +++ b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/HeadersTooLargeParseTest.java @@ -15,7 +15,6 @@ package org.eclipse.jetty.http2.frames; import java.nio.ByteBuffer; import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.UnaryOperator; import org.eclipse.jetty.http.HostPortHttpField; import org.eclipse.jetty.http.HttpFields; @@ -67,15 +66,15 @@ public class HeadersTooLargeParseTest HeadersGenerator generator = new HeadersGenerator(new HeaderGenerator(), new HpackEncoder()); AtomicInteger failure = new AtomicInteger(); - Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter() + Parser parser = new Parser(byteBufferPool, maxHeaderSize); + parser.init(new Parser.Listener.Adapter() { @Override public void onConnectionFailure(int error, String reason) { failure.set(error); } - }, 4096, maxHeaderSize); - parser.init(UnaryOperator.identity()); + }); int streamId = 48; ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool); diff --git a/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/MaxFrameSizeParseTest.java b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/MaxFrameSizeParseTest.java index ec25b727b58..bd60711cad6 100644 --- a/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/MaxFrameSizeParseTest.java +++ b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/MaxFrameSizeParseTest.java @@ -15,7 +15,6 @@ package org.eclipse.jetty.http2.frames; import java.nio.ByteBuffer; import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.UnaryOperator; import org.eclipse.jetty.http2.ErrorCode; import org.eclipse.jetty.http2.parser.Parser; @@ -35,16 +34,16 @@ public class MaxFrameSizeParseTest int maxFrameLength = Frame.DEFAULT_MAX_LENGTH + 16; AtomicInteger failure = new AtomicInteger(); - Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter() + Parser parser = new Parser(byteBufferPool, 8192); + parser.setMaxFrameSize(maxFrameLength); + parser.init(new Parser.Listener.Adapter() { @Override public void onConnectionFailure(int error, String reason) { failure.set(error); } - }, 4096, 8192); - parser.setMaxFrameLength(maxFrameLength); - parser.init(UnaryOperator.identity()); + }); // Iterate a few times to be sure the parser is properly reset. for (int i = 0; i < 2; ++i) diff --git a/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/PingGenerateParseTest.java b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/PingGenerateParseTest.java index eee36832750..e73d114794a 100644 --- a/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/PingGenerateParseTest.java +++ b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/PingGenerateParseTest.java @@ -17,7 +17,6 @@ import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import java.util.Random; -import java.util.function.UnaryOperator; import org.eclipse.jetty.http2.generator.HeaderGenerator; import org.eclipse.jetty.http2.generator.PingGenerator; @@ -41,15 +40,15 @@ public class PingGenerateParseTest PingGenerator generator = new PingGenerator(new HeaderGenerator()); final List frames = new ArrayList<>(); - Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter() + Parser parser = new Parser(byteBufferPool, 8192); + parser.init(new Parser.Listener.Adapter() { @Override public void onPing(PingFrame frame) { frames.add(frame); } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); + }); byte[] payload = new byte[8]; new Random().nextBytes(payload); @@ -82,15 +81,15 @@ public class PingGenerateParseTest PingGenerator generator = new PingGenerator(new HeaderGenerator()); final List frames = new ArrayList<>(); - Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter() + Parser parser = new Parser(byteBufferPool, 8192); + parser.init(new Parser.Listener.Adapter() { @Override public void onPing(PingFrame frame) { frames.add(frame); } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); + }); byte[] payload = new byte[8]; new Random().nextBytes(payload); @@ -123,15 +122,15 @@ public class PingGenerateParseTest PingGenerator generator = new PingGenerator(new HeaderGenerator()); final List frames = new ArrayList<>(); - Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter() + Parser parser = new Parser(byteBufferPool, 8192); + parser.init(new Parser.Listener.Adapter() { @Override public void onPing(PingFrame frame) { frames.add(frame); } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); + }); ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool); PingFrame ping = new PingFrame(NanoTime.now(), true); diff --git a/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/PriorityGenerateParseTest.java b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/PriorityGenerateParseTest.java index 6861c2fff99..f83a3d4c9bf 100644 --- a/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/PriorityGenerateParseTest.java +++ b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/PriorityGenerateParseTest.java @@ -16,7 +16,6 @@ package org.eclipse.jetty.http2.frames; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; -import java.util.function.UnaryOperator; import org.eclipse.jetty.http2.generator.HeaderGenerator; import org.eclipse.jetty.http2.generator.PriorityGenerator; @@ -37,15 +36,15 @@ public class PriorityGenerateParseTest PriorityGenerator generator = new PriorityGenerator(new HeaderGenerator()); final List frames = new ArrayList<>(); - Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter() + Parser parser = new Parser(byteBufferPool, 8192); + parser.init(new Parser.Listener.Adapter() { @Override public void onPriority(PriorityFrame frame) { frames.add(frame); } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); + }); int streamId = 13; int parentStreamId = 17; @@ -82,15 +81,15 @@ public class PriorityGenerateParseTest PriorityGenerator generator = new PriorityGenerator(new HeaderGenerator()); final List frames = new ArrayList<>(); - Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter() + Parser parser = new Parser(byteBufferPool, 8192); + parser.init(new Parser.Listener.Adapter() { @Override public void onPriority(PriorityFrame frame) { frames.add(frame); } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); + }); int streamId = 13; int parentStreamId = 17; diff --git a/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/PushPromiseGenerateParseTest.java b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/PushPromiseGenerateParseTest.java index 1377131f446..7696d954f9b 100644 --- a/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/PushPromiseGenerateParseTest.java +++ b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/PushPromiseGenerateParseTest.java @@ -16,7 +16,6 @@ package org.eclipse.jetty.http2.frames; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; -import java.util.function.UnaryOperator; import org.eclipse.jetty.http.HostPortHttpField; import org.eclipse.jetty.http.HttpField; @@ -45,15 +44,15 @@ public class PushPromiseGenerateParseTest PushPromiseGenerator generator = new PushPromiseGenerator(new HeaderGenerator(), new HpackEncoder()); final List frames = new ArrayList<>(); - Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter() + Parser parser = new Parser(byteBufferPool, 8192); + parser.init(new Parser.Listener.Adapter() { @Override public void onPushPromise(PushPromiseFrame frame) { frames.add(frame); } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); + }); int streamId = 13; int promisedStreamId = 17; @@ -98,15 +97,15 @@ public class PushPromiseGenerateParseTest PushPromiseGenerator generator = new PushPromiseGenerator(new HeaderGenerator(), new HpackEncoder()); final List frames = new ArrayList<>(); - Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter() + Parser parser = new Parser(byteBufferPool, 8192); + parser.init(new Parser.Listener.Adapter() { @Override public void onPushPromise(PushPromiseFrame frame) { frames.add(frame); } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); + }); int streamId = 13; int promisedStreamId = 17; diff --git a/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/ResetGenerateParseTest.java b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/ResetGenerateParseTest.java index 067ad9e53fe..f37f52efa6e 100644 --- a/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/ResetGenerateParseTest.java +++ b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/ResetGenerateParseTest.java @@ -16,7 +16,6 @@ package org.eclipse.jetty.http2.frames; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; -import java.util.function.UnaryOperator; import org.eclipse.jetty.http2.generator.HeaderGenerator; import org.eclipse.jetty.http2.generator.ResetGenerator; @@ -37,15 +36,15 @@ public class ResetGenerateParseTest ResetGenerator generator = new ResetGenerator(new HeaderGenerator()); final List frames = new ArrayList<>(); - Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter() + Parser parser = new Parser(byteBufferPool, 8192); + parser.init(new Parser.Listener.Adapter() { @Override public void onReset(ResetFrame frame) { frames.add(frame); } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); + }); int streamId = 13; int error = 17; @@ -78,15 +77,15 @@ public class ResetGenerateParseTest ResetGenerator generator = new ResetGenerator(new HeaderGenerator()); final List frames = new ArrayList<>(); - Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter() + Parser parser = new Parser(byteBufferPool, 8192); + parser.init(new Parser.Listener.Adapter() { @Override public void onReset(ResetFrame frame) { frames.add(frame); } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); + }); int streamId = 13; int error = 17; diff --git a/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/SettingsGenerateParseTest.java b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/SettingsGenerateParseTest.java index d8c9d6d03ee..25bed58d1fd 100644 --- a/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/SettingsGenerateParseTest.java +++ b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/SettingsGenerateParseTest.java @@ -20,7 +20,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.UnaryOperator; import org.eclipse.jetty.http2.ErrorCode; import org.eclipse.jetty.http2.generator.HeaderGenerator; @@ -31,6 +30,7 @@ import org.eclipse.jetty.io.MappedByteBufferPool; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; public class SettingsGenerateParseTest @@ -40,8 +40,7 @@ public class SettingsGenerateParseTest @Test public void testGenerateParseNoSettings() { - - List frames = testGenerateParse(Collections.emptyMap()); + List frames = testGenerateParse(Collections.emptyMap(), true); assertEquals(1, frames.size()); SettingsFrame frame = frames.get(0); assertEquals(0, frame.getSettings().size()); @@ -58,7 +57,7 @@ public class SettingsGenerateParseTest int key2 = 19; Integer value2 = 23; settings1.put(key2, value2); - List frames = testGenerateParse(settings1); + List frames = testGenerateParse(settings1, false); assertEquals(1, frames.size()); SettingsFrame frame = frames.get(0); Map settings2 = frame.getSettings(); @@ -67,26 +66,26 @@ public class SettingsGenerateParseTest assertEquals(value2, settings2.get(key2)); } - private List testGenerateParse(Map settings) + private List testGenerateParse(Map settings, boolean reply) { SettingsGenerator generator = new SettingsGenerator(new HeaderGenerator()); List frames = new ArrayList<>(); - Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter() + Parser parser = new Parser(byteBufferPool, 8192); + parser.init(new Parser.Listener.Adapter() { @Override public void onSettings(SettingsFrame frame) { frames.add(frame); } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); + }); // Iterate a few times to be sure generator and parser are properly reset. for (int i = 0; i < 2; ++i) { ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool); - generator.generateSettings(lease, settings, true); + generator.generateSettings(lease, settings, reply); frames.clear(); for (ByteBuffer buffer : lease.getByteBuffers()) @@ -107,20 +106,20 @@ public class SettingsGenerateParseTest SettingsGenerator generator = new SettingsGenerator(new HeaderGenerator()); AtomicInteger errorRef = new AtomicInteger(); - Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter() + Parser parser = new Parser(byteBufferPool, 8192); + parser.init(new Parser.Listener.Adapter() { @Override public void onConnectionFailure(int error, String reason) { errorRef.set(error); } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); + }); Map settings1 = new HashMap<>(); settings1.put(13, 17); ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool); - generator.generateSettings(lease, settings1, true); + generator.generateSettings(lease, settings1, false); // Modify the length of the frame to make it invalid ByteBuffer bytes = lease.getByteBuffers().get(0); bytes.putShort(1, (short)(bytes.getShort(1) - 1)); @@ -142,15 +141,15 @@ public class SettingsGenerateParseTest SettingsGenerator generator = new SettingsGenerator(new HeaderGenerator()); List frames = new ArrayList<>(); - Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter() + Parser parser = new Parser(byteBufferPool, 8192); + parser.init(new Parser.Listener.Adapter() { @Override public void onSettings(SettingsFrame frame) { frames.add(frame); } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); + }); Map settings1 = new HashMap<>(); int key = 13; @@ -161,7 +160,7 @@ public class SettingsGenerateParseTest for (int i = 0; i < 2; ++i) { ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool); - generator.generateSettings(lease, settings1, true); + generator.generateSettings(lease, settings1, false); frames.clear(); for (ByteBuffer buffer : lease.getByteBuffers()) @@ -177,7 +176,7 @@ public class SettingsGenerateParseTest Map settings2 = frame.getSettings(); assertEquals(1, settings2.size()); assertEquals(value, settings2.get(key)); - assertTrue(frame.isReply()); + assertFalse(frame.isReply()); } } @@ -187,17 +186,17 @@ public class SettingsGenerateParseTest SettingsGenerator generator = new SettingsGenerator(new HeaderGenerator()); AtomicInteger errorRef = new AtomicInteger(); - Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter() + Parser parser = new Parser(byteBufferPool, 8192); + int maxSettingsKeys = 32; + parser.setMaxSettingsKeys(maxSettingsKeys); + parser.init(new Parser.Listener.Adapter() { @Override public void onConnectionFailure(int error, String reason) { errorRef.set(error); } - }, 4096, 8192); - int maxSettingsKeys = 32; - parser.setMaxSettingsKeys(maxSettingsKeys); - parser.init(UnaryOperator.identity()); + }); Map settings = new HashMap<>(); for (int i = 0; i < maxSettingsKeys + 1; ++i) @@ -227,10 +226,10 @@ public class SettingsGenerateParseTest int maxSettingsKeys = pairs / 2; AtomicInteger errorRef = new AtomicInteger(); - Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter(), 4096, 8192); + Parser parser = new Parser(byteBufferPool, 8192); parser.setMaxSettingsKeys(maxSettingsKeys); - parser.setMaxFrameLength(Frame.DEFAULT_MAX_LENGTH); - parser.init(listener -> new Parser.Listener.Wrapper(listener) + parser.setMaxFrameSize(Frame.DEFAULT_MAX_LENGTH); + parser.init(new Parser.Listener.Adapter() { @Override public void onConnectionFailure(int error, String reason) @@ -268,17 +267,17 @@ public class SettingsGenerateParseTest SettingsGenerator generator = new SettingsGenerator(new HeaderGenerator()); AtomicInteger errorRef = new AtomicInteger(); - Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter() + Parser parser = new Parser(byteBufferPool, 8192); + int maxSettingsKeys = 32; + parser.setMaxSettingsKeys(maxSettingsKeys); + parser.init(new Parser.Listener.Adapter() { @Override public void onConnectionFailure(int error, String reason) { errorRef.set(error); } - }, 4096, 8192); - int maxSettingsKeys = 32; - parser.setMaxSettingsKeys(maxSettingsKeys); - parser.init(UnaryOperator.identity()); + }); Map settings = new HashMap<>(); settings.put(13, 17); diff --git a/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/UnknownParseTest.java b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/UnknownParseTest.java index 9eed81e8ef4..d7f603f1d0c 100644 --- a/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/UnknownParseTest.java +++ b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/UnknownParseTest.java @@ -17,7 +17,6 @@ import java.nio.ByteBuffer; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; -import java.util.function.UnaryOperator; import org.eclipse.jetty.http2.ErrorCode; import org.eclipse.jetty.http2.parser.Parser; @@ -48,8 +47,8 @@ public class UnknownParseTest public void testInvalidFrameSize() { AtomicInteger failure = new AtomicInteger(); - Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter(), 4096, 8192); - parser.init(listener -> new Parser.Listener.Wrapper(listener) + Parser parser = new Parser(byteBufferPool, 8192); + parser.init(new Parser.Listener.Adapter() { @Override public void onConnectionFailure(int error, String reason) @@ -57,7 +56,7 @@ public class UnknownParseTest failure.set(error); } }); - parser.setMaxFrameLength(Frame.DEFAULT_MAX_LENGTH); + parser.setMaxFrameSize(Frame.DEFAULT_MAX_LENGTH); // 0x4001 == 16385 which is > Frame.DEFAULT_MAX_LENGTH. byte[] bytes = new byte[]{0, 0x40, 0x01, 64, 0, 0, 0, 0, 0}; @@ -73,15 +72,15 @@ public class UnknownParseTest private void testParse(Function fn) { AtomicBoolean failure = new AtomicBoolean(); - Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter() + Parser parser = new Parser(byteBufferPool, 8192); + parser.init(new Parser.Listener.Adapter() { @Override public void onConnectionFailure(int error, String reason) { failure.set(true); } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); + }); // Iterate a few times to be sure the parser is properly reset. for (int i = 0; i < 2; ++i) diff --git a/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/WindowUpdateGenerateParseTest.java b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/WindowUpdateGenerateParseTest.java index 13c63081da9..63edbb30ccd 100644 --- a/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/WindowUpdateGenerateParseTest.java +++ b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/WindowUpdateGenerateParseTest.java @@ -16,7 +16,6 @@ package org.eclipse.jetty.http2.frames; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; -import java.util.function.UnaryOperator; import org.eclipse.jetty.http2.generator.HeaderGenerator; import org.eclipse.jetty.http2.generator.WindowUpdateGenerator; @@ -37,15 +36,15 @@ public class WindowUpdateGenerateParseTest WindowUpdateGenerator generator = new WindowUpdateGenerator(new HeaderGenerator()); final List frames = new ArrayList<>(); - Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter() + Parser parser = new Parser(byteBufferPool, 8192); + parser.init(new Parser.Listener.Adapter() { @Override public void onWindowUpdate(WindowUpdateFrame frame) { frames.add(frame); } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); + }); int streamId = 13; int windowUpdate = 17; @@ -78,15 +77,15 @@ public class WindowUpdateGenerateParseTest WindowUpdateGenerator generator = new WindowUpdateGenerator(new HeaderGenerator()); final List frames = new ArrayList<>(); - Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter() + Parser parser = new Parser(byteBufferPool, 8192); + parser.init(new Parser.Listener.Adapter() { @Override public void onWindowUpdate(WindowUpdateFrame frame) { frames.add(frame); } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); + }); int streamId = 13; int windowUpdate = 17; diff --git a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackContext.java b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackContext.java index 6a774a51f07..142e3983aa4 100644 --- a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackContext.java +++ b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackContext.java @@ -41,7 +41,7 @@ import org.slf4j.LoggerFactory; */ public class HpackContext { - public static final Logger LOG = LoggerFactory.getLogger(HpackContext.class); + private static final Logger LOG = LoggerFactory.getLogger(HpackContext.class); private static final String EMPTY = ""; public static final String[][] STATIC_TABLE = { @@ -114,6 +114,7 @@ public class HpackContext private static final StaticEntry[] __staticTableByHeader = new StaticEntry[HttpHeader.values().length]; private static final StaticEntry[] __staticTable = new StaticEntry[STATIC_TABLE.length]; public static final int STATIC_SIZE = STATIC_TABLE.length - 1; + public static final int DEFAULT_MAX_TABLE_CAPACITY = 4096; static { @@ -183,26 +184,26 @@ public class HpackContext } } - private int _maxDynamicTableSizeInBytes; - private int _dynamicTableSizeInBytes; private final DynamicTable _dynamicTable; private final Map _fieldMap = new HashMap<>(); private final Map _nameMap = new HashMap<>(); + private int _maxTableSize; + private int _tableSize; - HpackContext(int maxDynamicTableSize) + HpackContext(int maxTableSize) { - _maxDynamicTableSizeInBytes = maxDynamicTableSize; - int guesstimateEntries = 10 + maxDynamicTableSize / (32 + 10 + 10); + _maxTableSize = maxTableSize; + int guesstimateEntries = 10 + maxTableSize / (32 + 10 + 10); _dynamicTable = new DynamicTable(guesstimateEntries); if (LOG.isDebugEnabled()) - LOG.debug(String.format("HdrTbl[%x] created max=%d", hashCode(), maxDynamicTableSize)); + LOG.debug(String.format("HdrTbl[%x] created max=%d", hashCode(), maxTableSize)); } public void resize(int newMaxDynamicTableSize) { if (LOG.isDebugEnabled()) - LOG.debug(String.format("HdrTbl[%x] resized max=%d->%d", hashCode(), _maxDynamicTableSizeInBytes, newMaxDynamicTableSize)); - _maxDynamicTableSizeInBytes = newMaxDynamicTableSize; + LOG.debug(String.format("HdrTbl[%x] resized max=%d->%d", hashCode(), _maxTableSize, newMaxDynamicTableSize)); + _maxTableSize = newMaxDynamicTableSize; _dynamicTable.evict(); } @@ -247,14 +248,14 @@ public class HpackContext { Entry entry = new Entry(field); int size = entry.getSize(); - if (size > _maxDynamicTableSizeInBytes) + if (size > _maxTableSize) { if (LOG.isDebugEnabled()) - LOG.debug(String.format("HdrTbl[%x] !added size %d>%d", hashCode(), size, _maxDynamicTableSizeInBytes)); + LOG.debug(String.format("HdrTbl[%x] !added size %d>%d", hashCode(), size, _maxTableSize)); _dynamicTable.evictAll(); return null; } - _dynamicTableSizeInBytes += size; + _tableSize += size; _dynamicTable.add(entry); _fieldMap.put(field, entry); _nameMap.put(field.getLowerCaseName(), entry); @@ -278,7 +279,7 @@ public class HpackContext */ public int getDynamicTableSize() { - return _dynamicTableSizeInBytes; + return _tableSize; } /** @@ -286,7 +287,7 @@ public class HpackContext */ public int getMaxDynamicTableSize() { - return _maxDynamicTableSizeInBytes; + return _maxTableSize; } public int index(Entry entry) @@ -312,15 +313,15 @@ public class HpackContext @Override public String toString() { - return String.format("HpackContext@%x{entries=%d,size=%d,max=%d}", hashCode(), _dynamicTable.size(), _dynamicTableSizeInBytes, _maxDynamicTableSizeInBytes); + return String.format("HpackContext@%x{entries=%d,size=%d,max=%d}", hashCode(), _dynamicTable.size(), _tableSize, _maxTableSize); } private class DynamicTable { - Entry[] _entries; - int _size; - int _offset; - int _growby; + private Entry[] _entries; + private final int _growby; + private int _size; + private int _offset; private DynamicTable(int initCapacity) { @@ -368,7 +369,7 @@ public class HpackContext private void evict() { - while (_dynamicTableSizeInBytes > _maxDynamicTableSizeInBytes) + while (_tableSize > _maxTableSize) { Entry entry = _entries[_offset]; _entries[_offset] = null; @@ -376,7 +377,7 @@ public class HpackContext _size--; if (LOG.isDebugEnabled()) LOG.debug(String.format("HdrTbl[%x] evict %s", HpackContext.this.hashCode(), entry)); - _dynamicTableSizeInBytes -= entry.getSize(); + _tableSize -= entry.getSize(); entry._slot = -1; _fieldMap.remove(entry.getHttpField()); String lc = entry.getHttpField().getLowerCaseName(); @@ -384,7 +385,7 @@ public class HpackContext _nameMap.remove(lc); } if (LOG.isDebugEnabled()) - LOG.debug(String.format("HdrTbl[%x] entries=%d, size=%d, max=%d", HpackContext.this.hashCode(), _dynamicTable.size(), _dynamicTableSizeInBytes, _maxDynamicTableSizeInBytes)); + LOG.debug(String.format("HdrTbl[%x] entries=%d, size=%d, max=%d", HpackContext.this.hashCode(), _dynamicTable.size(), _tableSize, _maxTableSize)); } private void evictAll() @@ -397,7 +398,7 @@ public class HpackContext _nameMap.clear(); _offset = 0; _size = 0; - _dynamicTableSizeInBytes = 0; + _tableSize = 0; Arrays.fill(_entries, null); } } diff --git a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackDecoder.java b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackDecoder.java index 577caabbb47..14f77d9e5d9 100644 --- a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackDecoder.java +++ b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackDecoder.java @@ -42,19 +42,19 @@ public class HpackDecoder private final MetaDataBuilder _builder; private final HuffmanDecoder _huffmanDecoder; private final NBitIntegerDecoder _integerDecoder; - private int _localMaxDynamicTableSize; + private int _maxTableCapacity; /** - * @param localMaxDynamicTableSize The maximum allowed size of the local dynamic header field table. - * @param maxHeaderSize The maximum allowed size of a headers block, expressed as total of all name and value characters, plus 32 per field + * @param maxHeaderSize The maximum allowed size of a decoded headers block, + * expressed as total of all name and value bytes, plus 32 bytes per field */ - public HpackDecoder(int localMaxDynamicTableSize, int maxHeaderSize) + public HpackDecoder(int maxHeaderSize) { - _context = new HpackContext(localMaxDynamicTableSize); - _localMaxDynamicTableSize = localMaxDynamicTableSize; + _context = new HpackContext(0); _builder = new MetaDataBuilder(maxHeaderSize); _huffmanDecoder = new HuffmanDecoder(); _integerDecoder = new NBitIntegerDecoder(); + setMaxTableCapacity(HpackContext.DEFAULT_MAX_TABLE_CAPACITY); } public HpackContext getHpackContext() @@ -62,9 +62,39 @@ public class HpackDecoder return _context; } - public void setLocalMaxDynamicTableSize(int localMaxdynamciTableSize) + public int getMaxTableCapacity() { - _localMaxDynamicTableSize = localMaxdynamciTableSize; + return _maxTableCapacity; + } + + /** + *

Sets the limit for the capacity of the dynamic header table.

+ *

This value acts as a limit for the values received from the + * remote peer via the HPACK dynamic table size update instruction.

+ *

After calling this method, a SETTINGS frame must be sent to the other + * peer, containing the {@code SETTINGS_HEADER_TABLE_SIZE} setting with + * the value passed as argument to this method.

+ * + * @param maxTableCapacity the limit for capacity of the dynamic header table + */ + public void setMaxTableCapacity(int maxTableCapacity) + { + _maxTableCapacity = maxTableCapacity; + } + + /** + * @param maxTableSizeLimit the local dynamic table max size + * @deprecated use {@link #setMaxTableCapacity(int)} instead + */ + @Deprecated + public void setLocalMaxDynamicTableSize(int maxTableSizeLimit) + { + setMaxTableCapacity(maxTableSizeLimit); + } + + public void setMaxHeaderListSize(int maxHeaderListSize) + { + _builder.setMaxSize(maxHeaderListSize); } public MetaData decode(ByteBuffer buffer) throws HpackException.SessionException, HpackException.StreamException @@ -72,13 +102,12 @@ public class HpackDecoder if (LOG.isDebugEnabled()) LOG.debug(String.format("CtxTbl[%x] decoding %d octets", _context.hashCode(), buffer.remaining())); - // If the buffer is big, don't even think about decoding it. - // Huffman may double the size, but it will only be a temporary allocation until detected in MetaDataBuilder.emit(). - if (buffer.remaining() > _builder.getMaxSize()) - throw new HpackException.SessionException("431 Request Header Fields too large"); + // If the buffer is larger than the max headers size, don't even start decoding it. + int maxSize = _builder.getMaxSize(); + if (maxSize > 0 && buffer.remaining() > maxSize) + throw new HpackException.SessionException("Header fields size too large"); boolean emitted = false; - while (buffer.hasRemaining()) { if (LOG.isDebugEnabled()) @@ -132,8 +161,8 @@ public class HpackDecoder int size = integerDecode(buffer, 5); if (LOG.isDebugEnabled()) LOG.debug("decode resize={}", size); - if (size > _localMaxDynamicTableSize) - throw new IllegalArgumentException(); + if (size > getMaxTableCapacity()) + throw new HpackException.CompressionException("Dynamic table resize exceeded max limit"); if (emitted) throw new HpackException.CompressionException("Dynamic table resize after fields"); _context.resize(size); diff --git a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackEncoder.java b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackEncoder.java index e84bdb4e367..1cee27d691a 100644 --- a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackEncoder.java +++ b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackEncoder.java @@ -94,34 +94,57 @@ public class HpackEncoder private final HpackContext _context; private final boolean _debug; - private int _remoteMaxDynamicTableSize; - private int _localMaxDynamicTableSize; + private int _maxTableCapacity; + private int _tableCapacity; private int _maxHeaderListSize; private int _headerListSize; private boolean _validateEncoding = true; public HpackEncoder() { - this(4096, 4096, -1); - } - - public HpackEncoder(int localMaxDynamicTableSize) - { - this(localMaxDynamicTableSize, 4096, -1); - } - - public HpackEncoder(int localMaxDynamicTableSize, int remoteMaxDynamicTableSize) - { - this(localMaxDynamicTableSize, remoteMaxDynamicTableSize, -1); - } - - public HpackEncoder(int localMaxDynamicTableSize, int remoteMaxDynamicTableSize, int maxHeaderListSize) - { - _context = new HpackContext(remoteMaxDynamicTableSize); - _remoteMaxDynamicTableSize = remoteMaxDynamicTableSize; - _localMaxDynamicTableSize = localMaxDynamicTableSize; - _maxHeaderListSize = maxHeaderListSize; + _context = new HpackContext(0); _debug = LOG.isDebugEnabled(); + setMaxTableCapacity(HpackContext.DEFAULT_MAX_TABLE_CAPACITY); + setTableCapacity(HpackContext.DEFAULT_MAX_TABLE_CAPACITY); + } + + public int getMaxTableCapacity() + { + return _maxTableCapacity; + } + + /** + *

Sets the limit for the capacity of the dynamic header table.

+ *

This value is set by the remote peer via the + * {@code SETTINGS_HEADER_TABLE_SIZE} setting.

+ * + * @param maxTableSizeLimit the limit for capacity of the dynamic header table + */ + public void setMaxTableCapacity(int maxTableSizeLimit) + { + _maxTableCapacity = maxTableSizeLimit; + } + + public int getTableCapacity() + { + return _tableCapacity; + } + + /** + *

Sets the capacity of the dynamic header table.

+ *

The value of the capacity may be changed from {@code 0} + * up to {@link #getMaxTableCapacity()}. + * An HPACK instruction with the new capacity value will + * be sent to the decoder when the next call to + * {@link #encode(ByteBuffer, MetaData)} is made.

+ * + * @param tableCapacity the capacity of the dynamic header table + */ + public void setTableCapacity(int tableCapacity) + { + if (tableCapacity > getMaxTableCapacity()) + throw new IllegalArgumentException("Max table capacity exceeded"); + _tableCapacity = tableCapacity; } public int getMaxHeaderListSize() @@ -139,14 +162,16 @@ public class HpackEncoder return _context; } - public void setRemoteMaxDynamicTableSize(int remoteMaxDynamicTableSize) + @Deprecated + public void setRemoteMaxDynamicTableSize(int maxTableSize) { - _remoteMaxDynamicTableSize = remoteMaxDynamicTableSize; + setTableCapacity(maxTableSize); } - public void setLocalMaxDynamicTableSize(int localMaxDynamicTableSize) + @Deprecated + public void setLocalMaxDynamicTableSize(int maxTableSizeLimit) { - _localMaxDynamicTableSize = localMaxDynamicTableSize; + setMaxTableCapacity(maxTableSizeLimit); } public boolean isValidateEncoding() @@ -182,10 +207,10 @@ public class HpackEncoder _headerListSize = 0; int pos = buffer.position(); - // Check the dynamic table sizes! - int maxDynamicTableSize = Math.min(_remoteMaxDynamicTableSize, _localMaxDynamicTableSize); - if (maxDynamicTableSize != _context.getMaxDynamicTableSize()) - encodeMaxDynamicTableSize(buffer, maxDynamicTableSize); + // If max table size changed, send the correspondent instruction. + int tableCapacity = getTableCapacity(); + if (tableCapacity != _context.getMaxDynamicTableSize()) + encodeMaxDynamicTableSize(buffer, tableCapacity); // Add Request/response meta fields if (metadata.isRequest()) @@ -259,14 +284,9 @@ public class HpackEncoder } } - // Check size - if (_maxHeaderListSize > 0 && _headerListSize > _maxHeaderListSize) - { - if (LOG.isDebugEnabled()) - LOG.warn("Header list size too large {} > {} metadata={}", _headerListSize, _maxHeaderListSize, metadata); - else - LOG.warn("Header list size too large {} > {}", _headerListSize, _maxHeaderListSize); - } + int maxHeaderListSize = getMaxHeaderListSize(); + if (maxHeaderListSize > 0 && _headerListSize > maxHeaderListSize) + throw new HpackException.SessionException("Header size %d > %d", _headerListSize, maxHeaderListSize); if (LOG.isDebugEnabled()) LOG.debug(String.format("CtxTbl[%x] encoded %d octets", _context.hashCode(), buffer.position() - pos)); @@ -283,13 +303,11 @@ public class HpackEncoder } } - public void encodeMaxDynamicTableSize(ByteBuffer buffer, int maxDynamicTableSize) + public void encodeMaxDynamicTableSize(ByteBuffer buffer, int maxTableSize) { - if (maxDynamicTableSize > _remoteMaxDynamicTableSize) - throw new IllegalArgumentException(); buffer.put((byte)0x20); - NBitIntegerEncoder.encode(buffer, 5, maxDynamicTableSize); - _context.resize(maxDynamicTableSize); + NBitIntegerEncoder.encode(buffer, 5, maxTableSize); + _context.resize(maxTableSize); } public void encode(ByteBuffer buffer, HttpField field) diff --git a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/MetaDataBuilder.java b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/MetaDataBuilder.java index b00be5c232e..b56df9365a5 100644 --- a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/MetaDataBuilder.java +++ b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/MetaDataBuilder.java @@ -25,8 +25,8 @@ import org.eclipse.jetty.http2.hpack.HpackException.SessionException; public class MetaDataBuilder { - private final int _maxSize; private final HttpFields.Mutable _fields = HttpFields.build(); + private int _maxSize; private int _size; private Integer _status; private String _method; @@ -48,8 +48,6 @@ public class MetaDataBuilder } /** - * Get the maxSize. - * * @return the maxSize */ public int getMaxSize() @@ -57,6 +55,11 @@ public class MetaDataBuilder return _maxSize; } + public void setMaxSize(int maxSize) + { + _maxSize = maxSize; + } + /** * Get the size. * @@ -76,8 +79,9 @@ public class MetaDataBuilder String value = field.getValue(); int fieldSize = name.length() + (value == null ? 0 : value.length()); _size += fieldSize + 32; - if (_size > _maxSize) - throw new SessionException("Header size %d > %d", _size, _maxSize); + int maxSize = getMaxSize(); + if (maxSize > 0 && _size > maxSize) + throw new SessionException("Header size %d > %d", _size, maxSize); if (field instanceof StaticTableHttpField) { diff --git a/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackDecoderTest.java b/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackDecoderTest.java index 3a0de9189eb..87ad45bf234 100644 --- a/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackDecoderTest.java +++ b/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackDecoderTest.java @@ -23,7 +23,7 @@ import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http2.hpack.HpackException.CompressionException; import org.eclipse.jetty.http2.hpack.HpackException.SessionException; import org.eclipse.jetty.http2.hpack.HpackException.StreamException; -import org.eclipse.jetty.util.TypeUtil; +import org.eclipse.jetty.util.StringUtil; import org.hamcrest.Matchers; import org.junit.jupiter.api.Test; @@ -57,11 +57,13 @@ public class HpackDecoderTest @Test public void testDecodeD3() throws Exception { - HpackDecoder decoder = new HpackDecoder(4096, 8192); + HpackDecoder decoder = new HpackDecoder(8192); + decoder.setMaxTableCapacity(HpackContext.DEFAULT_MAX_TABLE_CAPACITY); + decoder.getHpackContext().resize(decoder.getMaxTableCapacity()); // First request String encoded = "828684410f7777772e6578616d706c652e636f6d"; - ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); + ByteBuffer buffer = ByteBuffer.wrap(StringUtil.fromHexString(encoded)); MetaData.Request request = (MetaData.Request)decoder.decode(buffer); @@ -73,7 +75,7 @@ public class HpackDecoderTest // Second request encoded = "828684be58086e6f2d6361636865"; - buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); + buffer = ByteBuffer.wrap(StringUtil.fromHexString(encoded)); request = (MetaData.Request)decoder.decode(buffer); @@ -88,7 +90,7 @@ public class HpackDecoderTest // Third request encoded = "828785bf400a637573746f6d2d6b65790c637573746f6d2d76616c7565"; - buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); + buffer = ByteBuffer.wrap(StringUtil.fromHexString(encoded)); request = (MetaData.Request)decoder.decode(buffer); @@ -105,11 +107,13 @@ public class HpackDecoderTest @Test public void testDecodeD4() throws Exception { - HpackDecoder decoder = new HpackDecoder(4096, 8192); + HpackDecoder decoder = new HpackDecoder(8192); + decoder.setMaxTableCapacity(HpackContext.DEFAULT_MAX_TABLE_CAPACITY); + decoder.getHpackContext().resize(decoder.getMaxTableCapacity()); // First request String encoded = "828684418cf1e3c2e5f23a6ba0ab90f4ff"; - ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); + ByteBuffer buffer = ByteBuffer.wrap(StringUtil.fromHexString(encoded)); MetaData.Request request = (MetaData.Request)decoder.decode(buffer); @@ -121,7 +125,7 @@ public class HpackDecoderTest // Second request encoded = "828684be5886a8eb10649cbf"; - buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); + buffer = ByteBuffer.wrap(StringUtil.fromHexString(encoded)); request = (MetaData.Request)decoder.decode(buffer); @@ -140,9 +144,9 @@ public class HpackDecoderTest { String value = "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="; - HpackDecoder decoder = new HpackDecoder(4096, 8192); + HpackDecoder decoder = new HpackDecoder(8192); String encoded = "8682418cF1E3C2E5F23a6bA0Ab90F4Ff841f0822426173696320515778685a475270626a70766347567549484e6c633246745a513d3d"; - byte[] bytes = TypeUtil.fromHexString(encoded); + byte[] bytes = StringUtil.fromHexString(encoded); byte[] array = new byte[bytes.length + 1]; System.arraycopy(bytes, 0, array, 1, bytes.length); ByteBuffer buffer = ByteBuffer.wrap(array, 1, bytes.length).slice(); @@ -162,10 +166,10 @@ public class HpackDecoderTest @Test public void testDecodeHuffmanWithArrayOffset() throws Exception { - HpackDecoder decoder = new HpackDecoder(4096, 8192); + HpackDecoder decoder = new HpackDecoder(8192); String encoded = "8286418cf1e3c2e5f23a6ba0ab90f4ff84"; - byte[] bytes = TypeUtil.fromHexString(encoded); + byte[] bytes = StringUtil.fromHexString(encoded); byte[] array = new byte[bytes.length + 1]; System.arraycopy(bytes, 0, array, 1, bytes.length); ByteBuffer buffer = ByteBuffer.wrap(array, 1, bytes.length).slice(); @@ -184,9 +188,9 @@ public class HpackDecoderTest { // Response encoded by nghttpx String encoded = "886196C361Be940b6a65B6850400B8A00571972e080a62D1Bf5f87497cA589D34d1f9a0f0d0234327690Aa69D29aFcA954D3A5358980Ae112e0f7c880aE152A9A74a6bF3"; - ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); + ByteBuffer buffer = ByteBuffer.wrap(StringUtil.fromHexString(encoded)); - HpackDecoder decoder = new HpackDecoder(4096, 8192); + HpackDecoder decoder = new HpackDecoder(8192); MetaData.Response response = (MetaData.Response)decoder.decode(buffer); assertThat(response.getStatus(), is(200)); @@ -203,8 +207,8 @@ public class HpackDecoderTest public void testResize() throws Exception { String encoded = "203f136687A0E41d139d090760881c6490B2Cd39Ba7f"; - ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); - HpackDecoder decoder = new HpackDecoder(4096, 8192); + ByteBuffer buffer = ByteBuffer.wrap(StringUtil.fromHexString(encoded)); + HpackDecoder decoder = new HpackDecoder(8192); MetaData metaData = decoder.decode(buffer); assertThat(metaData.getFields().get(HttpHeader.HOST), is("localhost0")); assertThat(metaData.getFields().get(HttpHeader.COOKIE), is("abcdefghij")); @@ -225,8 +229,8 @@ public class HpackDecoderTest */ String encoded = "203f136687A0E41d139d090760881c6490B2Cd39Ba7f20"; - ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); - HpackDecoder decoder = new HpackDecoder(4096, 8192); + ByteBuffer buffer = ByteBuffer.wrap(StringUtil.fromHexString(encoded)); + HpackDecoder decoder = new HpackDecoder(8192); try { decoder.decode(buffer); @@ -242,9 +246,10 @@ public class HpackDecoderTest public void testTooBigToIndex() throws Exception { String encoded = "3f610f17FfEc02Df3990A190A0D4Ee5b3d2940Ec98Aa4a62D127D29e273a0aA20dEcAa190a503b262d8a2671D4A2672a927aA874988a2471D05510750c951139EdA2452a3a548cAa1aA90bE4B228342864A9E0D450A5474a92992a1aA513395448E3A0Aa17B96cFe3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f14E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F353F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F54f"; - ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); + ByteBuffer buffer = ByteBuffer.wrap(StringUtil.fromHexString(encoded)); - HpackDecoder decoder = new HpackDecoder(128, 8192); + HpackDecoder decoder = new HpackDecoder(8192); + decoder.setMaxTableCapacity(128); MetaData metaData = decoder.decode(buffer); assertThat(decoder.getHpackContext().getDynamicTableSize(), is(0)); @@ -255,9 +260,10 @@ public class HpackDecoderTest public void testUnknownIndex() throws Exception { String encoded = "BE"; - ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); + ByteBuffer buffer = ByteBuffer.wrap(StringUtil.fromHexString(encoded)); - HpackDecoder decoder = new HpackDecoder(128, 8192); + HpackDecoder decoder = new HpackDecoder(8192); + decoder.setMaxTableCapacity(128); try { @@ -442,10 +448,10 @@ public class HpackDecoderTest @Test public void testHuffmanEncodedStandard() throws Exception { - HpackDecoder decoder = new HpackDecoder(4096, 8192); + HpackDecoder decoder = new HpackDecoder(8192); String encoded = "82868441" + "83" + "49509F"; - ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); + ByteBuffer buffer = ByteBuffer.wrap(StringUtil.fromHexString(encoded)); MetaData.Request request = (MetaData.Request)decoder.decode(buffer); @@ -460,10 +466,10 @@ public class HpackDecoderTest @Test public void testHuffmanEncodedExtraPadding() { - HpackDecoder decoder = new HpackDecoder(4096, 8192); + HpackDecoder decoder = new HpackDecoder(8192); String encoded = "82868441" + "84" + "49509FFF"; - ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); + ByteBuffer buffer = ByteBuffer.wrap(StringUtil.fromHexString(encoded)); CompressionException ex = assertThrows(CompressionException.class, () -> decoder.decode(buffer)); assertThat(ex.getMessage(), Matchers.containsString("bad_termination")); } @@ -472,10 +478,10 @@ public class HpackDecoderTest @Test public void testHuffmanEncodedZeroPadding() { - HpackDecoder decoder = new HpackDecoder(4096, 8192); + HpackDecoder decoder = new HpackDecoder(8192); String encoded = "82868441" + "83" + "495090"; - ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); + ByteBuffer buffer = ByteBuffer.wrap(StringUtil.fromHexString(encoded)); CompressionException ex = assertThrows(CompressionException.class, () -> decoder.decode(buffer)); assertThat(ex.getMessage(), Matchers.containsString("incorrect_padding")); @@ -485,10 +491,10 @@ public class HpackDecoderTest @Test public void testHuffmanEncodedWithEOS() { - HpackDecoder decoder = new HpackDecoder(4096, 8192); + HpackDecoder decoder = new HpackDecoder(8192); String encoded = "82868441" + "87" + "497FFFFFFF427F"; - ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); + ByteBuffer buffer = ByteBuffer.wrap(StringUtil.fromHexString(encoded)); CompressionException ex = assertThrows(CompressionException.class, () -> decoder.decode(buffer)); assertThat(ex.getMessage(), Matchers.containsString("eos_in_content")); @@ -497,10 +503,10 @@ public class HpackDecoderTest @Test public void testHuffmanEncodedOneIncompleteOctet() { - HpackDecoder decoder = new HpackDecoder(4096, 8192); + HpackDecoder decoder = new HpackDecoder(8192); String encoded = "82868441" + "81" + "FE"; - ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); + ByteBuffer buffer = ByteBuffer.wrap(StringUtil.fromHexString(encoded)); CompressionException ex = assertThrows(CompressionException.class, () -> decoder.decode(buffer)); assertThat(ex.getMessage(), Matchers.containsString("bad_termination")); @@ -509,10 +515,10 @@ public class HpackDecoderTest @Test public void testHuffmanEncodedTwoIncompleteOctet() { - HpackDecoder decoder = new HpackDecoder(4096, 8192); + HpackDecoder decoder = new HpackDecoder(8192); String encoded = "82868441" + "82" + "FFFE"; - ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); + ByteBuffer buffer = ByteBuffer.wrap(StringUtil.fromHexString(encoded)); CompressionException ex = assertThrows(CompressionException.class, () -> decoder.decode(buffer)); assertThat(ex.getMessage(), Matchers.containsString("bad_termination")); @@ -521,10 +527,10 @@ public class HpackDecoderTest @Test public void testZeroLengthName() { - HpackDecoder decoder = new HpackDecoder(4096, 8192); + HpackDecoder decoder = new HpackDecoder(8192); String encoded = "00000130"; - ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); + ByteBuffer buffer = ByteBuffer.wrap(StringUtil.fromHexString(encoded)); SessionException ex = assertThrows(SessionException.class, () -> decoder.decode(buffer)); assertThat(ex.getMessage(), Matchers.containsString("Header size 0")); } @@ -532,10 +538,10 @@ public class HpackDecoderTest @Test public void testZeroLengthValue() throws Exception { - HpackDecoder decoder = new HpackDecoder(4096, 8192); + HpackDecoder decoder = new HpackDecoder(8192); String encoded = "00016800"; - ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); + ByteBuffer buffer = ByteBuffer.wrap(StringUtil.fromHexString(encoded)); MetaData metaData = decoder.decode(buffer); assertThat(metaData.getFields().size(), is(1)); assertThat(metaData.getFields().get("h"), is("")); @@ -544,10 +550,10 @@ public class HpackDecoderTest @Test public void testUpperCaseName() { - HpackDecoder decoder = new HpackDecoder(4096, 8192); + HpackDecoder decoder = new HpackDecoder(8192); String encoded = "0001480130"; - ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); + ByteBuffer buffer = ByteBuffer.wrap(StringUtil.fromHexString(encoded)); StreamException ex = assertThrows(StreamException.class, () -> decoder.decode(buffer)); assertThat(ex.getMessage(), Matchers.containsString("Uppercase header")); } @@ -555,10 +561,10 @@ public class HpackDecoderTest @Test public void testWhiteSpaceName() { - HpackDecoder decoder = new HpackDecoder(4096, 8192); + HpackDecoder decoder = new HpackDecoder(8192); String encoded = "0001200130"; - ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); + ByteBuffer buffer = ByteBuffer.wrap(StringUtil.fromHexString(encoded)); StreamException ex = assertThrows(StreamException.class, () -> decoder.decode(buffer)); assertThat(ex.getMessage(), Matchers.containsString("Illegal header")); } diff --git a/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackEncoderTest.java b/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackEncoderTest.java index 0f209c0d285..7452d9f1da6 100644 --- a/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackEncoderTest.java +++ b/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackEncoderTest.java @@ -33,7 +33,7 @@ public class HpackEncoderTest @Test public void testUnknownFieldsContextManagement() throws Exception { - HpackEncoder encoder = new HpackEncoder(38 * 5); + HpackEncoder encoder = newHpackEncoder(38 * 5); HttpFields.Mutable fields = HttpFields.build(); HttpField[] field = @@ -144,8 +144,9 @@ public class HpackEncoderTest @Test public void testLargeFieldsNotIndexed() { - HpackEncoder encoder = new HpackEncoder(38 * 5); + HpackEncoder encoder = newHpackEncoder(38 * 5); HpackContext ctx = encoder.getHpackContext(); + ctx.resize(encoder.getMaxTableCapacity()); ByteBuffer buffer = BufferUtil.allocate(4096); @@ -170,8 +171,9 @@ public class HpackEncoderTest @Test public void testIndexContentLength() { - HpackEncoder encoder = new HpackEncoder(38 * 5); + HpackEncoder encoder = newHpackEncoder(38 * 5); HpackContext ctx = encoder.getHpackContext(); + ctx.resize(encoder.getMaxTableCapacity()); ByteBuffer buffer = BufferUtil.allocate(4096); @@ -192,7 +194,7 @@ public class HpackEncoderTest @Test public void testNeverIndexSetCookie() throws Exception { - HpackEncoder encoder = new HpackEncoder(38 * 5); + HpackEncoder encoder = newHpackEncoder(38 * 5); ByteBuffer buffer = BufferUtil.allocate(4096); HttpFields.Mutable fields = HttpFields.build() @@ -226,20 +228,20 @@ public class HpackEncoderTest { HttpFields.Mutable fields = HttpFields.build(); - HpackEncoder encoder = new HpackEncoder(128); + HpackEncoder encoder = newHpackEncoder(128); ByteBuffer buffer0 = BufferUtil.allocate(4096); int pos = BufferUtil.flipToFill(buffer0); encoder.encode(buffer0, new MetaData(HttpVersion.HTTP_2, fields)); BufferUtil.flipToFlush(buffer0, pos); - encoder = new HpackEncoder(128); + encoder = newHpackEncoder(128); fields.add(new HttpField("user-agent", "jetty/test")); ByteBuffer buffer1 = BufferUtil.allocate(4096); pos = BufferUtil.flipToFill(buffer1); encoder.encode(buffer1, new MetaData(HttpVersion.HTTP_2, fields)); BufferUtil.flipToFlush(buffer1, pos); - encoder = new HpackEncoder(128); + encoder = newHpackEncoder(128); encoder.setValidateEncoding(false); fields.add(new HttpField(":path", "This is a very large field, whose size is larger than the dynamic table so it should not be indexed as it will not fit in the table ever!" + @@ -251,7 +253,7 @@ public class HpackEncoderTest encoder.encode(buffer2, new MetaData(HttpVersion.HTTP_2, fields)); BufferUtil.flipToFlush(buffer2, pos); - encoder = new HpackEncoder(128); + encoder = newHpackEncoder(128); encoder.setValidateEncoding(false); fields.add(new HttpField("host", "somehost")); ByteBuffer buffer = BufferUtil.allocate(4096); @@ -292,12 +294,12 @@ public class HpackEncoderTest .add("host", "localhost0") .add("cookie", "abcdefghij"); - HpackEncoder encoder = new HpackEncoder(4096); + HpackEncoder encoder = newHpackEncoder(4096); ByteBuffer buffer = BufferUtil.allocate(4096); int pos = BufferUtil.flipToFill(buffer); encoder.encodeMaxDynamicTableSize(buffer, 0); - encoder.setRemoteMaxDynamicTableSize(50); + encoder.setTableCapacity(50); encoder.encode(buffer, new MetaData(HttpVersion.HTTP_2, fields)); BufferUtil.flipToFlush(buffer, pos); @@ -306,4 +308,12 @@ public class HpackEncoderTest assertThat(context.getMaxDynamicTableSize(), Matchers.is(50)); assertThat(context.size(), Matchers.is(1)); } + + private static HpackEncoder newHpackEncoder(int tableCapacity) + { + HpackEncoder encoder = new HpackEncoder(); + encoder.setMaxTableCapacity(tableCapacity); + encoder.setTableCapacity(tableCapacity); + return encoder; + } } diff --git a/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackPerfTest.java b/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackPerfTest.java index d693d8ff629..0d9993403b4 100644 --- a/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackPerfTest.java +++ b/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackPerfTest.java @@ -32,7 +32,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; public class HpackPerfTest { - int _maxDynamicTableSize = 4 * 1024; + int _tableCapacity = 4 * 1024; int _unencodedSize; int _encodedSize; @@ -46,7 +46,7 @@ public class HpackPerfTest @AfterEach public void after() { - System.err.printf("dynamictable=%d unencoded=%d encoded=%d p=%3.1f%%%n", _maxDynamicTableSize, _unencodedSize, _encodedSize, 100.0 * _encodedSize / _unencodedSize); + System.err.printf("dynamictable=%d unencoded=%d encoded=%d p=%3.1f%%%n", _tableCapacity, _unencodedSize, _encodedSize, 100.0 * _encodedSize / _unencodedSize); } @Test @@ -92,7 +92,9 @@ public class HpackPerfTest { if (type.equals(story.get("context"))) { - HpackEncoder encoder = new HpackEncoder(_maxDynamicTableSize, _maxDynamicTableSize); + HpackEncoder encoder = new HpackEncoder(); + encoder.setMaxTableCapacity(_tableCapacity); + encoder.setTableCapacity(_tableCapacity); encoder.setValidateEncoding(false); Object[] cases = (Object[])story.get("cases"); diff --git a/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackTest.java b/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackTest.java index 8073a331c6a..e90ebf9a255 100644 --- a/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackTest.java +++ b/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackTest.java @@ -43,7 +43,7 @@ public class HpackTest public void encodeDecodeResponseTest() throws Exception { HpackEncoder encoder = new HpackEncoder(); - HpackDecoder decoder = new HpackDecoder(4096, 8192); + HpackDecoder decoder = new HpackDecoder(8192); ByteBuffer buffer = BufferUtil.allocateDirect(16 * 1024); HttpFields.Mutable fields0 = HttpFields.build() @@ -98,7 +98,7 @@ public class HpackTest public void encodeDecodeTooLargeTest() throws Exception { HpackEncoder encoder = new HpackEncoder(); - HpackDecoder decoder = new HpackDecoder(4096, 164); + HpackDecoder decoder = new HpackDecoder(164); ByteBuffer buffer = BufferUtil.allocateDirect(16 * 1024); HttpFields fields0 = HttpFields.build() @@ -158,8 +158,11 @@ public class HpackTest @Test public void evictReferencedFieldTest() throws Exception { - HpackEncoder encoder = new HpackEncoder(200, 200); - HpackDecoder decoder = new HpackDecoder(200, 1024); + HpackDecoder decoder = new HpackDecoder(1024); + decoder.setMaxTableCapacity(200); + HpackEncoder encoder = new HpackEncoder(); + encoder.setMaxTableCapacity(decoder.getMaxTableCapacity()); + encoder.setTableCapacity(decoder.getMaxTableCapacity()); ByteBuffer buffer = BufferUtil.allocateDirect(16 * 1024); String longEnoughToBeEvicted = "012345678901234567890123456789012345678901234567890"; @@ -202,7 +205,7 @@ public class HpackTest public void testHopHeadersAreRemoved() throws Exception { HpackEncoder encoder = new HpackEncoder(); - HpackDecoder decoder = new HpackDecoder(4096, 16384); + HpackDecoder decoder = new HpackDecoder(16384); HttpFields input = HttpFields.build() .add(HttpHeader.ACCEPT, "*") @@ -229,7 +232,7 @@ public class HpackTest public void testTETrailers() throws Exception { HpackEncoder encoder = new HpackEncoder(); - HpackDecoder decoder = new HpackDecoder(4096, 16384); + HpackDecoder decoder = new HpackDecoder(16384); String teValue = "trailers"; String trailerValue = "Custom"; @@ -254,7 +257,7 @@ public class HpackTest public void testColonHeaders() throws Exception { HpackEncoder encoder = new HpackEncoder(); - HpackDecoder decoder = new HpackDecoder(4096, 16384); + HpackDecoder decoder = new HpackDecoder(16384); HttpFields input = HttpFields.build() .add(":status", "200") diff --git a/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/HttpClientTransportOverHTTP2Test.java b/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/HttpClientTransportOverHTTP2Test.java index f298229f004..de3a6023ed7 100644 --- a/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/HttpClientTransportOverHTTP2Test.java +++ b/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/HttpClientTransportOverHTTP2Test.java @@ -31,7 +31,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; -import java.util.function.UnaryOperator; import java.util.stream.IntStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -460,7 +459,8 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest OutputStream output = socket.getOutputStream(); InputStream input = socket.getInputStream(); - ServerParser parser = new ServerParser(byteBufferPool, new ServerParser.Listener.Adapter() + ServerParser parser = new ServerParser(byteBufferPool, 8192, RateControl.NO_RATE_CONTROL); + parser.init(new ServerParser.Listener.Adapter() { @Override public void onPreface() @@ -512,8 +512,7 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest x.printStackTrace(); } } - }, 4096, 8192, RateControl.NO_RATE_CONTROL); - parser.init(UnaryOperator.identity()); + }); byte[] bytes = new byte[1024]; while (true) @@ -586,7 +585,8 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) { // Disable checks for invalid headers. - ((HTTP2Session)stream.getSession()).getGenerator().setValidateHpackEncoding(false); + Generator generator = ((HTTP2Session)stream.getSession()).getGenerator(); + generator.getHpackEncoder().setValidateEncoding(false); // Produce an invalid HPACK block by adding a request pseudo-header to the response. HttpFields fields = HttpFields.build() .put(":method", "get"); diff --git a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/AbstractHTTP2ServerConnectionFactory.java b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/AbstractHTTP2ServerConnectionFactory.java index 1629f8ac633..b96e4cfd108 100644 --- a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/AbstractHTTP2ServerConnectionFactory.java +++ b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/AbstractHTTP2ServerConnectionFactory.java @@ -32,6 +32,7 @@ import org.eclipse.jetty.http2.api.server.ServerSessionListener; import org.eclipse.jetty.http2.frames.Frame; import org.eclipse.jetty.http2.frames.SettingsFrame; import org.eclipse.jetty.http2.generator.Generator; +import org.eclipse.jetty.http2.hpack.HpackContext; import org.eclipse.jetty.http2.parser.RateControl; import org.eclipse.jetty.http2.parser.ServerParser; import org.eclipse.jetty.http2.parser.WindowRateControl; @@ -53,12 +54,13 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne { private final HTTP2SessionContainer sessionContainer = new HTTP2SessionContainer(); private final HttpConfiguration httpConfiguration; - private int maxDynamicTableSize = 4096; + private int maxDecoderTableCapacity = HpackContext.DEFAULT_MAX_TABLE_CAPACITY; + private int maxEncoderTableCapacity = HpackContext.DEFAULT_MAX_TABLE_CAPACITY; private int initialSessionRecvWindow = 1024 * 1024; private int initialStreamRecvWindow = 512 * 1024; private int maxConcurrentStreams = 128; private int maxHeaderBlockFragment = 0; - private int maxFrameLength = Frame.DEFAULT_MAX_LENGTH; + private int maxFrameSize = Frame.DEFAULT_MAX_LENGTH; private int maxSettingsKeys = SettingsFrame.DEFAULT_MAX_KEYS; private boolean connectProtocolEnabled = true; private RateControl.Factory rateControlFactory = new WindowRateControl.Factory(50); @@ -88,15 +90,52 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne setUseOutputDirectByteBuffers(httpConfiguration.isUseOutputDirectByteBuffers()); } - @ManagedAttribute("The HPACK dynamic table maximum size") - public int getMaxDynamicTableSize() + @ManagedAttribute("The HPACK encoder dynamic table maximum capacity") + public int getMaxEncoderTableCapacity() { - return maxDynamicTableSize; + return maxEncoderTableCapacity; } - public void setMaxDynamicTableSize(int maxDynamicTableSize) + /** + *

Sets the limit for the encoder HPACK dynamic table capacity.

+ *

Setting this value to {@code 0} disables the use of the dynamic table.

+ * + * @param maxEncoderTableCapacity The HPACK encoder dynamic table maximum capacity + */ + public void setMaxEncoderTableCapacity(int maxEncoderTableCapacity) { - this.maxDynamicTableSize = maxDynamicTableSize; + this.maxEncoderTableCapacity = maxEncoderTableCapacity; + } + + @ManagedAttribute("The HPACK decoder dynamic table maximum capacity") + public int getMaxDecoderTableCapacity() + { + return maxDecoderTableCapacity; + } + + public void setMaxDecoderTableCapacity(int maxDecoderTableCapacity) + { + this.maxDecoderTableCapacity = maxDecoderTableCapacity; + } + + /** + * @return the max decoder table size + * @deprecated use {@link #getMaxDecoderTableCapacity()} instead + */ + @Deprecated + public int getMaxDynamicTableSize() + { + return getMaxDecoderTableCapacity(); + } + + /** + * @param maxTableSize the max decoder table size + * @deprecated use {@link #setMaxDecoderTableCapacity(int)} instead + */ + @Deprecated + public void setMaxDynamicTableSize(int maxTableSize) + { + setMaxDecoderTableCapacity(maxTableSize); } @ManagedAttribute("The initial size of session's flow control receive window") @@ -164,15 +203,27 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne this.streamIdleTimeout = streamIdleTimeout; } - @ManagedAttribute("The max frame length in bytes") + @Deprecated public int getMaxFrameLength() { - return maxFrameLength; + return getMaxFrameSize(); } - public void setMaxFrameLength(int maxFrameLength) + @Deprecated + public void setMaxFrameLength(int maxFrameSize) { - this.maxFrameLength = maxFrameLength; + setMaxFrameSize(maxFrameSize); + } + + @ManagedAttribute("The max frame size in bytes") + public int getMaxFrameSize() + { + return maxFrameSize; + } + + public void setMaxFrameSize(int maxFrameSize) + { + this.maxFrameSize = maxFrameSize; } @ManagedAttribute("The max number of keys in all SETTINGS frames") @@ -245,12 +296,16 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne protected Map newSettings() { Map settings = new HashMap<>(); - settings.put(SettingsFrame.HEADER_TABLE_SIZE, getMaxDynamicTableSize()); - settings.put(SettingsFrame.INITIAL_WINDOW_SIZE, getInitialStreamRecvWindow()); - int maxConcurrentStreams = getMaxConcurrentStreams(); - if (maxConcurrentStreams >= 0) - settings.put(SettingsFrame.MAX_CONCURRENT_STREAMS, maxConcurrentStreams); - settings.put(SettingsFrame.MAX_HEADER_LIST_SIZE, getHttpConfiguration().getRequestHeaderSize()); + int maxTableSize = getMaxDecoderTableCapacity(); + if (maxTableSize != HpackContext.DEFAULT_MAX_TABLE_CAPACITY) + settings.put(SettingsFrame.HEADER_TABLE_SIZE, maxTableSize); + int initialStreamRecvWindow = getInitialStreamRecvWindow(); + if (initialStreamRecvWindow != FlowControlStrategy.DEFAULT_WINDOW_SIZE) + settings.put(SettingsFrame.INITIAL_WINDOW_SIZE, initialStreamRecvWindow); + settings.put(SettingsFrame.MAX_CONCURRENT_STREAMS, getMaxConcurrentStreams()); + int maxHeadersSize = getHttpConfiguration().getRequestHeaderSize(); + if (maxHeadersSize > 0) + settings.put(SettingsFrame.MAX_HEADER_LIST_SIZE, maxHeadersSize); settings.put(SettingsFrame.ENABLE_CONNECT_PROTOCOL, isConnectProtocolEnabled() ? 1 : 0); return settings; } @@ -260,11 +315,17 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne { ServerSessionListener listener = newSessionListener(connector, endPoint); - Generator generator = new Generator(connector.getByteBufferPool(), isUseOutputDirectByteBuffers(), getMaxDynamicTableSize(), getMaxHeaderBlockFragment()); + Generator generator = new Generator(connector.getByteBufferPool(), isUseOutputDirectByteBuffers(), getMaxHeaderBlockFragment()); FlowControlStrategy flowControl = getFlowControlStrategyFactory().newFlowControlStrategy(); - HTTP2ServerSession session = new HTTP2ServerSession(connector.getScheduler(), endPoint, generator, listener, flowControl); + + ServerParser parser = newServerParser(connector, getRateControlFactory().newRateControl(endPoint)); + parser.setMaxFrameSize(getMaxFrameSize()); + parser.setMaxSettingsKeys(getMaxSettingsKeys()); + + HTTP2ServerSession session = new HTTP2ServerSession(connector.getScheduler(), endPoint, parser, generator, listener, flowControl); session.setMaxLocalStreams(getMaxConcurrentStreams()); session.setMaxRemoteStreams(getMaxConcurrentStreams()); + session.setMaxEncoderTableCapacity(getMaxEncoderTableCapacity()); // For a single stream in a connection, there will be a race between // the stream idle timeout and the connection idle timeout. However, // the typical case is that the connection will be busier and the @@ -276,25 +337,22 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne session.setWriteThreshold(getHttpConfiguration().getOutputBufferSize()); session.setConnectProtocolEnabled(isConnectProtocolEnabled()); - ServerParser parser = newServerParser(connector, session, getRateControlFactory().newRateControl(endPoint)); - parser.setMaxFrameLength(getMaxFrameLength()); - parser.setMaxSettingsKeys(getMaxSettingsKeys()); - RetainableByteBufferPool retainableByteBufferPool = connector.getByteBufferPool().asRetainableByteBufferPool(); - HTTP2Connection connection = new HTTP2ServerConnection(retainableByteBufferPool, connector.getExecutor(), - endPoint, httpConfiguration, parser, session, getInputBufferSize(), listener); + endPoint, httpConfiguration, session, getInputBufferSize(), listener); connection.setUseInputDirectByteBuffers(isUseInputDirectByteBuffers()); connection.setUseOutputDirectByteBuffers(isUseOutputDirectByteBuffers()); connection.addEventListener(sessionContainer); + parser.init(connection); + return configure(connection, connector, endPoint); } protected abstract ServerSessionListener newSessionListener(Connector connector, EndPoint endPoint); - protected ServerParser newServerParser(Connector connector, ServerParser.Listener listener, RateControl rateControl) + protected ServerParser newServerParser(Connector connector, RateControl rateControl) { - return new ServerParser(connector.getByteBufferPool(), listener, getMaxDynamicTableSize(), getHttpConfiguration().getRequestHeaderSize(), rateControl); + return new ServerParser(connector.getByteBufferPool(), getHttpConfiguration().getRequestHeaderSize(), rateControl); } @ManagedObject("The container of HTTP/2 sessions") diff --git a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnection.java b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnection.java index bac312abc8f..709599ad7b4 100644 --- a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnection.java +++ b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnection.java @@ -53,7 +53,7 @@ import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.TypeUtil; import org.eclipse.jetty.util.thread.AutoLock; -public class HTTP2ServerConnection extends HTTP2Connection +public class HTTP2ServerConnection extends HTTP2Connection implements ServerParser.Listener { /** * @param protocol An HTTP2 protocol variant @@ -86,19 +86,13 @@ public class HTTP2ServerConnection extends HTTP2Connection private final HttpConfiguration httpConfig; private boolean recycleHttpChannels = true; - public HTTP2ServerConnection(RetainableByteBufferPool retainableByteBufferPool, Executor executor, EndPoint endPoint, HttpConfiguration httpConfig, ServerParser parser, ISession session, int inputBufferSize, ServerSessionListener listener) + public HTTP2ServerConnection(RetainableByteBufferPool retainableByteBufferPool, Executor executor, EndPoint endPoint, HttpConfiguration httpConfig, HTTP2ServerSession session, int inputBufferSize, ServerSessionListener listener) { - super(retainableByteBufferPool, executor, endPoint, parser, session, inputBufferSize); + super(retainableByteBufferPool, executor, endPoint, session, inputBufferSize); this.listener = listener; this.httpConfig = httpConfig; } - @Override - protected ServerParser getParser() - { - return (ServerParser)super.getParser(); - } - public boolean isRecycleHttpChannels() { return recycleHttpChannels; @@ -134,6 +128,12 @@ public class HTTP2ServerConnection extends HTTP2Connection } } + @Override + public void onPreface() + { + ((HTTP2ServerSession)getSession()).onPreface(); + } + public void onNewStream(Connector connector, IStream stream, HeadersFrame frame) { if (LOG.isDebugEnabled()) @@ -215,7 +215,7 @@ public class HTTP2ServerConnection extends HTTP2Connection .map(HTTP2Channel.Server::isIdle) .reduce(true, Boolean::logicalAnd); if (LOG.isDebugEnabled()) - LOG.debug("{} idle timeout on {}: {}", result ? "Processed" : "Ignored", session, failure); + LOG.debug("{} idle timeout on {}", result ? "Processed" : "Ignored", session, failure); return result; } @@ -294,7 +294,7 @@ public class HTTP2ServerConnection extends HTTP2Connection { if (HttpMethod.PRI.is(request.getMethod())) { - getParser().directUpgrade(); + ((HTTP2ServerSession)getSession()).directUpgrade(); } else { @@ -317,7 +317,7 @@ public class HTTP2ServerConnection extends HTTP2Connection responseFields.put(HttpHeader.UPGRADE, "h2c"); responseFields.put(HttpHeader.CONNECTION, "Upgrade"); - getParser().standardUpgrade(); + ((HTTP2ServerSession)getSession()).standardUpgrade(); // We fake that we received a client preface, so that we can send the // server preface as the first HTTP/2 frame as required by the spec. diff --git a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerSession.java b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerSession.java index e53ef75e79c..d5caba00cae 100644 --- a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerSession.java +++ b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerSession.java @@ -46,12 +46,18 @@ public class HTTP2ServerSession extends HTTP2Session implements ServerParser.Lis private final ServerSessionListener listener; - public HTTP2ServerSession(Scheduler scheduler, EndPoint endPoint, Generator generator, ServerSessionListener listener, FlowControlStrategy flowControl) + public HTTP2ServerSession(Scheduler scheduler, EndPoint endPoint, ServerParser parser, Generator generator, ServerSessionListener listener, FlowControlStrategy flowControl) { - super(scheduler, endPoint, generator, listener, flowControl, 2); + super(scheduler, endPoint, parser, generator, listener, flowControl, 2); this.listener = listener; } + @Override + public ServerParser getParser() + { + return (ServerParser)super.getParser(); + } + @Override public void onPreface() { @@ -186,4 +192,14 @@ public class HTTP2ServerSession extends HTTP2Session implements ServerParser.Lis break; } } + + public void directUpgrade() + { + getParser().directUpgrade(); + } + + public void standardUpgrade() + { + getParser().standardUpgrade(); + } } diff --git a/jetty-http2/http2-server/src/test/java/org/eclipse/jetty/http2/server/CloseTest.java b/jetty-http2/http2-server/src/test/java/org/eclipse/jetty/http2/server/CloseTest.java index 2456ba204ea..f682661885d 100644 --- a/jetty-http2/http2-server/src/test/java/org/eclipse/jetty/http2/server/CloseTest.java +++ b/jetty-http2/http2-server/src/test/java/org/eclipse/jetty/http2/server/CloseTest.java @@ -21,7 +21,6 @@ import java.util.HashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; -import java.util.function.UnaryOperator; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpVersion; @@ -87,7 +86,8 @@ public class CloseTest extends AbstractServerTest output.write(BufferUtil.toArray(buffer)); } - Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter() + Parser parser = new Parser(byteBufferPool, 8192); + parser.init(new Parser.Listener.Adapter() { @Override public void onHeaders(HeadersFrame frame) @@ -104,8 +104,7 @@ public class CloseTest extends AbstractServerTest throw new RuntimeIOException(x); } } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); + }); parseResponse(client, parser); @@ -152,7 +151,8 @@ public class CloseTest extends AbstractServerTest // Don't close the connection; the server should close. final CountDownLatch responseLatch = new CountDownLatch(1); - Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter() + Parser parser = new Parser(byteBufferPool, 8192); + parser.init(new Parser.Listener.Adapter() { @Override public void onHeaders(HeadersFrame frame) @@ -161,8 +161,7 @@ public class CloseTest extends AbstractServerTest // HEADERS, the server is able to send us the response. responseLatch.countDown(); } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); + }); parseResponse(client, parser); @@ -217,7 +216,8 @@ public class CloseTest extends AbstractServerTest final CountDownLatch responseLatch = new CountDownLatch(1); final CountDownLatch closeLatch = new CountDownLatch(1); - Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter() + Parser parser = new Parser(byteBufferPool, 8192); + parser.init(new Parser.Listener.Adapter() { @Override public void onHeaders(HeadersFrame frame) @@ -230,8 +230,7 @@ public class CloseTest extends AbstractServerTest { closeLatch.countDown(); } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); + }); parseResponse(client, parser); diff --git a/jetty-http2/http2-server/src/test/java/org/eclipse/jetty/http2/server/HTTP2CServerTest.java b/jetty-http2/http2-server/src/test/java/org/eclipse/jetty/http2/server/HTTP2CServerTest.java index a063d29b7f8..ed76afa616d 100644 --- a/jetty-http2/http2-server/src/test/java/org/eclipse/jetty/http2/server/HTTP2CServerTest.java +++ b/jetty-http2/http2-server/src/test/java/org/eclipse/jetty/http2/server/HTTP2CServerTest.java @@ -25,7 +25,6 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; -import java.util.function.UnaryOperator; import org.eclipse.jetty.http.HostPortHttpField; import org.eclipse.jetty.http.HttpFields; @@ -147,7 +146,8 @@ public class HTTP2CServerTest extends AbstractServerTest final AtomicReference headersRef = new AtomicReference<>(); final AtomicReference dataRef = new AtomicReference<>(); final AtomicReference latchRef = new AtomicReference<>(new CountDownLatch(2)); - Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter() + Parser parser = new Parser(byteBufferPool, 8192); + parser.init(new Parser.Listener.Adapter() { @Override public void onHeaders(HeadersFrame frame) @@ -162,8 +162,7 @@ public class HTTP2CServerTest extends AbstractServerTest dataRef.set(frame); latchRef.get().countDown(); } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); + }); parseResponse(client, parser); @@ -241,7 +240,8 @@ public class HTTP2CServerTest extends AbstractServerTest final AtomicReference headersRef = new AtomicReference<>(); final AtomicReference dataRef = new AtomicReference<>(); - Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter() + Parser parser = new Parser(byteBufferPool, 8192); + parser.init(new Parser.Listener.Adapter() { @Override public void onSettings(SettingsFrame frame) @@ -262,8 +262,7 @@ public class HTTP2CServerTest extends AbstractServerTest dataRef.set(frame); latch.countDown(); } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); + }); parseResponse(client, parser); diff --git a/jetty-http2/http2-server/src/test/java/org/eclipse/jetty/http2/server/HTTP2ServerTest.java b/jetty-http2/http2-server/src/test/java/org/eclipse/jetty/http2/server/HTTP2ServerTest.java index cc76a3d9925..667cae9737d 100644 --- a/jetty-http2/http2-server/src/test/java/org/eclipse/jetty/http2/server/HTTP2ServerTest.java +++ b/jetty-http2/http2-server/src/test/java/org/eclipse/jetty/http2/server/HTTP2ServerTest.java @@ -28,7 +28,6 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; -import java.util.function.UnaryOperator; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -88,15 +87,15 @@ public class HTTP2ServerTest extends AbstractServerTest } final CountDownLatch latch = new CountDownLatch(1); - Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter() + Parser parser = new Parser(byteBufferPool, 8192); + parser.init(new Parser.Listener.Adapter() { @Override public void onGoAway(GoAwayFrame frame) { latch.countDown(); } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); + }); parseResponse(client, parser); @@ -132,7 +131,8 @@ public class HTTP2ServerTest extends AbstractServerTest } final AtomicReference frameRef = new AtomicReference<>(); - Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter() + Parser parser = new Parser(byteBufferPool, 8192); + parser.init(new Parser.Listener.Adapter() { @Override public void onSettings(SettingsFrame frame) @@ -146,8 +146,7 @@ public class HTTP2ServerTest extends AbstractServerTest frameRef.set(frame); latch.countDown(); } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); + }); parseResponse(client, parser); @@ -191,7 +190,8 @@ public class HTTP2ServerTest extends AbstractServerTest final AtomicReference headersRef = new AtomicReference<>(); final AtomicReference dataRef = new AtomicReference<>(); - Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter() + Parser parser = new Parser(byteBufferPool, 8192); + parser.init(new Parser.Listener.Adapter() { @Override public void onSettings(SettingsFrame frame) @@ -212,8 +212,7 @@ public class HTTP2ServerTest extends AbstractServerTest dataRef.set(frame); latch.countDown(); } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); + }); parseResponse(client, parser); @@ -251,7 +250,8 @@ public class HTTP2ServerTest extends AbstractServerTest output.write(BufferUtil.toArray(buffer)); } - Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter() + Parser parser = new Parser(byteBufferPool, 8192); + parser.init(new Parser.Listener.Adapter() { @Override public void onGoAway(GoAwayFrame frame) @@ -259,8 +259,7 @@ public class HTTP2ServerTest extends AbstractServerTest assertEquals(ErrorCode.FRAME_SIZE_ERROR.code, frame.getError()); latch.countDown(); } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); + }); parseResponse(client, parser); @@ -289,7 +288,8 @@ public class HTTP2ServerTest extends AbstractServerTest output.write(BufferUtil.toArray(buffer)); } - Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter() + Parser parser = new Parser(byteBufferPool, 8192); + parser.init(new Parser.Listener.Adapter() { @Override public void onGoAway(GoAwayFrame frame) @@ -297,8 +297,7 @@ public class HTTP2ServerTest extends AbstractServerTest assertEquals(ErrorCode.PROTOCOL_ERROR.code, frame.getError()); latch.countDown(); } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); + }); parseResponse(client, parser); @@ -366,8 +365,8 @@ public class HTTP2ServerTest extends AbstractServerTest // The server will close the connection abruptly since it // cannot write and therefore cannot even send the GO_AWAY. - Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter(), 4096, 8192); - parser.init(UnaryOperator.identity()); + Parser parser = new Parser(byteBufferPool, 8192); + parser.init(new Parser.Listener.Adapter()); boolean closed = parseResponse(client, parser, 2 * delay); assertTrue(closed); } @@ -404,8 +403,8 @@ public class HTTP2ServerTest extends AbstractServerTest } output.flush(); - Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter(), 4096, 8192); - parser.init(UnaryOperator.identity()); + Parser parser = new Parser(byteBufferPool, 8192); + parser.init(new Parser.Listener.Adapter()); boolean closed = parseResponse(client, parser); assertTrue(closed); @@ -561,7 +560,7 @@ public class HTTP2ServerTest extends AbstractServerTest return null; } }); - generator = new Generator(byteBufferPool, 4096, 4); + generator = new Generator(byteBufferPool, 4); ByteBufferPool.Lease lease = frames.call(); @@ -577,7 +576,8 @@ public class HTTP2ServerTest extends AbstractServerTest assertTrue(serverLatch.await(5, TimeUnit.SECONDS)); final CountDownLatch clientLatch = new CountDownLatch(1); - Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter() + Parser parser = new Parser(byteBufferPool, 8192); + parser.init(new Parser.Listener.Adapter() { @Override public void onHeaders(HeadersFrame frame) @@ -585,8 +585,7 @@ public class HTTP2ServerTest extends AbstractServerTest if (frame.isEndStream()) clientLatch.countDown(); } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); + }); boolean closed = parseResponse(client, parser); assertTrue(clientLatch.await(5, TimeUnit.SECONDS)); 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 317538d6cce..bf798135be2 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 @@ -173,7 +173,7 @@ public class ClientHTTP3Session extends ClientProtocolSession { int maxTableCapacity = value.intValue(); encoder.setMaxTableCapacity(maxTableCapacity); - encoder.setTableCapacity(Math.min(maxTableCapacity, configuration.getInitialEncoderTableCapacity())); + encoder.setTableCapacity(Math.min(maxTableCapacity, configuration.getMaxEncoderTableCapacity())); } else if (key == SettingsFrame.MAX_FIELD_SECTION_SIZE) { 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 index d2deaa6ec67..2d73fdd80ee 100644 --- 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 @@ -28,8 +28,8 @@ public class HTTP3Configuration private boolean useInputDirectByteBuffers = true; private boolean useOutputDirectByteBuffers = true; private int maxBlockedStreams = 64; - private int maxTableCapacity = 64 * 1024; - private int initialTableCapacity = 64 * 1024; + private int maxDecoderTableCapacity = 64 * 1024; + private int maxEncoderTableCapacity = 64 * 1024; private int maxRequestHeadersSize = 8 * 1024; private int maxResponseHeadersSize = 8 * 1024; @@ -122,7 +122,7 @@ public class HTTP3Configuration @ManagedAttribute("The local QPACK max decoder dynamic table capacity") public int getMaxDecoderTableCapacity() { - return maxTableCapacity; + return maxDecoderTableCapacity; } /** @@ -132,17 +132,17 @@ public class HTTP3Configuration * communicated to the remote QPACK encoder via the SETTINGS frame.

* * @param maxTableCapacity the QPACK decoder dynamic table max capacity - * @see #setInitialEncoderTableCapacity(int) + * @see #setMaxEncoderTableCapacity(int) */ public void setMaxDecoderTableCapacity(int maxTableCapacity) { - this.maxTableCapacity = maxTableCapacity; + this.maxDecoderTableCapacity = maxTableCapacity; } @ManagedAttribute("The local QPACK initial encoder dynamic table capacity") - public int getInitialEncoderTableCapacity() + public int getMaxEncoderTableCapacity() { - return initialTableCapacity; + return maxEncoderTableCapacity; } /** @@ -151,12 +151,12 @@ public class HTTP3Configuration *

This value is configured in the local QPACK encoder, and may be * overwritten by a smaller value received via the SETTINGS frame.

* - * @param initialTableCapacity the QPACK encoder dynamic table initial capacity + * @param maxTableCapacity the QPACK encoder dynamic table initial capacity * @see #setMaxDecoderTableCapacity(int) */ - public void setInitialEncoderTableCapacity(int initialTableCapacity) + public void setMaxEncoderTableCapacity(int maxTableCapacity) { - this.initialTableCapacity = initialTableCapacity; + this.maxEncoderTableCapacity = maxTableCapacity; } @ManagedAttribute("The max number of QPACK blocked streams") 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 bf6c4ac98a4..152759d9c10 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 @@ -172,7 +172,7 @@ public class ServerHTTP3Session extends ServerProtocolSession { int maxTableCapacity = value.intValue(); encoder.setMaxTableCapacity(maxTableCapacity); - encoder.setTableCapacity(Math.min(maxTableCapacity, configuration.getInitialEncoderTableCapacity())); + encoder.setTableCapacity(Math.min(maxTableCapacity, configuration.getMaxEncoderTableCapacity())); } else if (key == SettingsFrame.MAX_FIELD_SECTION_SIZE) { 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 ad28e45cc13..d17576398cb 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 @@ -378,7 +378,7 @@ public class ClientServerTest extends AbstractClientServerTest http3Configuration.setMaxRequestHeadersSize(maxRequestHeadersSize); // Disable the dynamic table, otherwise the large header // is sent as string literal on the encoder stream. - http3Configuration.setInitialEncoderTableCapacity(0); + http3Configuration.setMaxEncoderTableCapacity(0); Session.Client clientSession = newSession(new Session.Client.Listener() {}); CountDownLatch requestFailureLatch = new CountDownLatch(1); @@ -459,7 +459,7 @@ public class ClientServerTest extends AbstractClientServerTest HTTP3Configuration http3Configuration = h3.getHTTP3Configuration(); // Disable the dynamic table, otherwise the large header // is sent as string literal on the encoder stream. - http3Configuration.setInitialEncoderTableCapacity(0); + http3Configuration.setMaxEncoderTableCapacity(0); http3Configuration.setMaxResponseHeadersSize(maxResponseHeadersSize); Session.Client clientSession = newSession(new Session.Client.Listener()