diff --git a/jetty-core/jetty-http2/jetty-http2-client-transport/src/main/java/org/eclipse/jetty/http2/client/transport/HttpClientTransportOverHTTP2.java b/jetty-core/jetty-http2/jetty-http2-client-transport/src/main/java/org/eclipse/jetty/http2/client/transport/HttpClientTransportOverHTTP2.java index 9a129256da4..d87442ab436 100644 --- a/jetty-core/jetty-http2/jetty-http2-client-transport/src/main/java/org/eclipse/jetty/http2/client/transport/HttpClientTransportOverHTTP2.java +++ b/jetty-core/jetty-http2/jetty-http2-client-transport/src/main/java/org/eclipse/jetty/http2/client/transport/HttpClientTransportOverHTTP2.java @@ -103,6 +103,7 @@ public class HttpClientTransportOverHTTP2 extends AbstractHttpClientTransport http2Client.setUseOutputDirectByteBuffers(httpClient.isUseOutputDirectByteBuffers()); http2Client.setConnectBlocking(httpClient.isConnectBlocking()); http2Client.setBindAddress(httpClient.getBindAddress()); + http2Client.setMaxRequestHeadersSize(httpClient.getRequestBufferSize()); http2Client.setMaxResponseHeadersSize(httpClient.getMaxResponseHeadersSize()); } diff --git a/jetty-core/jetty-http2/jetty-http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2Client.java b/jetty-core/jetty-http2/jetty-http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2Client.java index 92329793242..2c9c25d076b 100644 --- a/jetty-core/jetty-http2/jetty-http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2Client.java +++ b/jetty-core/jetty-http2/jetty-http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2Client.java @@ -113,6 +113,7 @@ public class HTTP2Client extends ContainerLifeCycle implements AutoCloseable private int maxDecoderTableCapacity = HpackContext.DEFAULT_MAX_TABLE_CAPACITY; private int maxEncoderTableCapacity = HpackContext.DEFAULT_MAX_TABLE_CAPACITY; private int maxHeaderBlockFragment = 0; + private int maxRequestHeadersSize = 8 * 1024; private int maxResponseHeadersSize = 8 * 1024; private FlowControlStrategy.Factory flowControlStrategyFactory = () -> new BufferingFlowControlStrategy(0.5F); private long streamIdleTimeout; @@ -357,6 +358,17 @@ public class HTTP2Client extends ContainerLifeCycle implements AutoCloseable this.maxHeaderBlockFragment = maxHeaderBlockFragment; } + @ManagedAttribute("The max size of request headers") + public int getMaxRequestHeadersSize() + { + return maxRequestHeadersSize; + } + + public void setMaxRequestHeadersSize(int maxRequestHeadersSize) + { + this.maxRequestHeadersSize = maxRequestHeadersSize; + } + @ManagedAttribute("The max size of response headers") public int getMaxResponseHeadersSize() { diff --git a/jetty-core/jetty-http2/jetty-http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2ClientConnectionFactory.java b/jetty-core/jetty-http2/jetty-http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2ClientConnectionFactory.java index dc2469e2d89..2dcb0fed79b 100644 --- a/jetty-core/jetty-http2/jetty-http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2ClientConnectionFactory.java +++ b/jetty-core/jetty-http2/jetty-http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2ClientConnectionFactory.java @@ -54,10 +54,11 @@ public class HTTP2ClientConnectionFactory implements ClientConnectionFactory Promise sessionPromise = (Promise)context.get(SESSION_PROMISE_CONTEXT_KEY); Generator generator = new Generator(bufferPool, client.isUseOutputDirectByteBuffers(), client.getMaxHeaderBlockFragment()); + generator.getHpackEncoder().setMaxHeaderListSize(client.getMaxRequestHeadersSize()); + FlowControlStrategy flowControl = client.getFlowControlStrategyFactory().newFlowControlStrategy(); Parser parser = new Parser(bufferPool, client.getMaxResponseHeadersSize()); - parser.setMaxFrameSize(client.getMaxFrameSize()); parser.setMaxSettingsKeys(client.getMaxSettingsKeys()); HTTP2ClientSession session = new HTTP2ClientSession(client.getScheduler(), endPoint, parser, generator, listener, flowControl); diff --git a/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/generator/FrameGenerator.java b/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/generator/FrameGenerator.java index 32bffcb4d46..d7264fc8959 100644 --- a/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/generator/FrameGenerator.java +++ b/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/generator/FrameGenerator.java @@ -18,6 +18,7 @@ import java.nio.ByteBuffer; import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http2.frames.Frame; import org.eclipse.jetty.http2.frames.FrameType; +import org.eclipse.jetty.http2.hpack.HpackContext; import org.eclipse.jetty.http2.hpack.HpackEncoder; import org.eclipse.jetty.http2.hpack.HpackException; import org.eclipse.jetty.io.RetainableByteBuffer; @@ -49,9 +50,12 @@ public abstract class FrameGenerator return headerGenerator.isUseDirectByteBuffers(); } - protected RetainableByteBuffer encode(HpackEncoder encoder, MetaData metaData, int maxFrameSize) throws HpackException + protected RetainableByteBuffer encode(HpackEncoder encoder, MetaData metaData) throws HpackException { - RetainableByteBuffer hpacked = headerGenerator.getByteBufferPool().acquire(maxFrameSize, isUseDirectByteBuffers()); + int bufferSize = encoder.getMaxHeaderListSize(); + if (bufferSize <= 0) + bufferSize = HpackContext.DEFAULT_MAX_HEADER_LIST_SIZE; + RetainableByteBuffer hpacked = headerGenerator.getByteBufferPool().acquire(bufferSize, isUseDirectByteBuffers()); try { ByteBuffer byteBuffer = hpacked.getByteBuffer(); diff --git a/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/generator/HeadersGenerator.java b/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/generator/HeadersGenerator.java index c01fed2d3cf..0dbee228def 100644 --- a/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/generator/HeadersGenerator.java +++ b/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/generator/HeadersGenerator.java @@ -56,9 +56,10 @@ public class HeadersGenerator extends FrameGenerator throw new IllegalArgumentException("Invalid stream id: " + streamId); int flags = Flags.NONE; - if (priority != null) flags = Flags.PRIORITY; + if (endStream) + flags |= Flags.END_STREAM; // TODO Look for a way of not allocating a large buffer here. // Possibly the hpack encoder could be changed to take the accumulator, but that is a lot of changes. @@ -66,34 +67,36 @@ public class HeadersGenerator extends FrameGenerator // So long as the buffer is not sliced into continuations, it at least should be available to aggregate // subsequent frames into... but likely only a frame header followed by an accumulated data frame. // It might also be good to be able to split the table into continuation frames as it is generated? - RetainableByteBuffer hpack = encode(encoder, metaData, getMaxFrameSize()); + RetainableByteBuffer hpack = encode(encoder, metaData); BufferUtil.flipToFlush(hpack.getByteBuffer(), 0); int hpackLength = hpack.remaining(); + int maxHeaderBlock = getMaxFrameSize(); + if (maxHeaderBlockFragment > 0) + maxHeaderBlock = Math.min(maxHeaderBlock, maxHeaderBlockFragment); + // Split into CONTINUATION frames if necessary. - if (maxHeaderBlockFragment > 0 && hpackLength > maxHeaderBlockFragment) + if (hpackLength > maxHeaderBlock) { int start = accumulator.remaining(); - if (endStream) - flags |= Flags.END_STREAM; - int length = maxHeaderBlockFragment + (priority == null ? 0 : PriorityFrame.PRIORITY_LENGTH); + int length = maxHeaderBlock + (priority == null ? 0 : PriorityFrame.PRIORITY_LENGTH); - // generate first fragment with as HEADERS with possible priority + // Generate HEADERS frame with possible PRIORITY frame. generateHeader(accumulator, FrameType.HEADERS, length, flags, streamId); generatePriority(accumulator, priority); - accumulator.add(hpack.slice(maxHeaderBlockFragment)); - hpack.skip(maxHeaderBlockFragment); + accumulator.add(hpack.slice(maxHeaderBlock)); + hpack.skip(maxHeaderBlock); - // generate continuation frames that are not the last - while (hpack.remaining() > maxHeaderBlockFragment) + // Generate CONTINUATION frames that are not the last. + while (hpack.remaining() > maxHeaderBlock) { - generateHeader(accumulator, FrameType.CONTINUATION, maxHeaderBlockFragment, Flags.NONE, streamId); - accumulator.add(hpack.slice(maxHeaderBlockFragment)); - hpack.skip(maxHeaderBlockFragment); + generateHeader(accumulator, FrameType.CONTINUATION, maxHeaderBlock, Flags.NONE, streamId); + accumulator.add(hpack.slice(maxHeaderBlock)); + hpack.skip(maxHeaderBlock); } - // generate the last continuation frame + // Generate the last CONTINUATION frame. generateHeader(accumulator, FrameType.CONTINUATION, hpack.remaining(), Flags.END_HEADERS, streamId); accumulator.add(hpack); @@ -102,8 +105,6 @@ public class HeadersGenerator extends FrameGenerator else { flags |= Flags.END_HEADERS; - if (endStream) - flags |= Flags.END_STREAM; int length = hpackLength + (priority == null ? 0 : PriorityFrame.PRIORITY_LENGTH); generateHeader(accumulator, FrameType.HEADERS, length, flags, streamId); diff --git a/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/generator/PushPromiseGenerator.java b/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/generator/PushPromiseGenerator.java index 2ac1e05fff1..fabe7a968c9 100644 --- a/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/generator/PushPromiseGenerator.java +++ b/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/generator/PushPromiseGenerator.java @@ -49,17 +49,17 @@ public class PushPromiseGenerator extends FrameGenerator if (promisedStreamId < 0) throw new IllegalArgumentException("Invalid promised stream id: " + promisedStreamId); - int maxFrameSize = getMaxFrameSize(); - // The promised streamId space. - int extraSpace = 4; - maxFrameSize -= extraSpace; - - RetainableByteBuffer hpack = encode(encoder, metaData, maxFrameSize); + RetainableByteBuffer hpack = encode(encoder, metaData); ByteBuffer hpackByteBuffer = hpack.getByteBuffer(); - int hpackLength = hpackByteBuffer.position(); BufferUtil.flipToFlush(hpackByteBuffer, 0); + int hpackLength = hpackByteBuffer.remaining(); - int length = hpackLength + extraSpace; + // No support for splitting in CONTINUATION frames, + // also PushPromiseBodyParser does not support it. + + // The promised streamId length. + int promisedStreamIdLength = 4; + int length = hpackLength + promisedStreamIdLength; int flags = Flags.END_HEADERS; generateHeader(accumulator, FrameType.PUSH_PROMISE, length, flags, streamId); diff --git a/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/parser/SettingsBodyParser.java b/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/parser/SettingsBodyParser.java index 9310a4fa22b..9058c04d27a 100644 --- a/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/parser/SettingsBodyParser.java +++ b/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/parser/SettingsBodyParser.java @@ -209,6 +209,10 @@ public class SettingsBodyParser extends BodyParser if (maxFrameSize != null && (maxFrameSize < Frame.DEFAULT_MAX_SIZE || maxFrameSize > Frame.MAX_MAX_SIZE)) return connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_settings_max_frame_size"); + Integer maxHeaderListSize = settings.get(SettingsFrame.MAX_HEADER_LIST_SIZE); + if (maxHeaderListSize != null && maxHeaderListSize <= 0) + return connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_settings_max_header_list_size"); + SettingsFrame frame = new SettingsFrame(settings, hasFlag(Flags.ACK)); return onSettings(buffer, frame); } diff --git a/jetty-core/jetty-http2/jetty-http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackContext.java b/jetty-core/jetty-http2/jetty-http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackContext.java index 20923f78c0c..ef5d4efc641 100644 --- a/jetty-core/jetty-http2/jetty-http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackContext.java +++ b/jetty-core/jetty-http2/jetty-http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackContext.java @@ -116,6 +116,7 @@ public class HpackContext 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; + public static final int DEFAULT_MAX_HEADER_LIST_SIZE = 4096; static { diff --git a/jetty-core/jetty-http2/jetty-http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackEncoder.java b/jetty-core/jetty-http2/jetty-http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackEncoder.java index 0abd4e0d7c2..bcbac88974d 100644 --- a/jetty-core/jetty-http2/jetty-http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackEncoder.java +++ b/jetty-core/jetty-http2/jetty-http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackEncoder.java @@ -107,6 +107,7 @@ public class HpackEncoder _debug = LOG.isDebugEnabled(); setMaxTableCapacity(HpackContext.DEFAULT_MAX_TABLE_CAPACITY); setTableCapacity(HpackContext.DEFAULT_MAX_TABLE_CAPACITY); + setMaxHeaderListSize(HpackContext.DEFAULT_MAX_HEADER_LIST_SIZE); } public int getMaxTableCapacity() diff --git a/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/AbstractHTTP2ServerConnectionFactory.java b/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/AbstractHTTP2ServerConnectionFactory.java index 8b9001a212a..d1c7594bbc7 100644 --- a/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/AbstractHTTP2ServerConnectionFactory.java +++ b/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/AbstractHTTP2ServerConnectionFactory.java @@ -300,10 +300,13 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne int maxTableSize = getMaxDecoderTableCapacity(); if (maxTableSize != HpackContext.DEFAULT_MAX_TABLE_CAPACITY) settings.put(SettingsFrame.HEADER_TABLE_SIZE, maxTableSize); + settings.put(SettingsFrame.MAX_CONCURRENT_STREAMS, getMaxConcurrentStreams()); int initialStreamRecvWindow = getInitialStreamRecvWindow(); if (initialStreamRecvWindow != FlowControlStrategy.DEFAULT_WINDOW_SIZE) settings.put(SettingsFrame.INITIAL_WINDOW_SIZE, initialStreamRecvWindow); - settings.put(SettingsFrame.MAX_CONCURRENT_STREAMS, getMaxConcurrentStreams()); + int maxFrameSize = getMaxFrameSize(); + if (maxFrameSize > Frame.DEFAULT_MAX_SIZE) + settings.put(SettingsFrame.MAX_FRAME_SIZE, maxFrameSize); int maxHeadersSize = getHttpConfiguration().getRequestHeaderSize(); if (maxHeadersSize > 0) settings.put(SettingsFrame.MAX_HEADER_LIST_SIZE, maxHeadersSize); @@ -318,10 +321,10 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne Generator generator = new Generator(connector.getByteBufferPool(), isUseOutputDirectByteBuffers(), getMaxHeaderBlockFragment()); generator.getHpackEncoder().setMaxHeaderListSize(getHttpConfiguration().getResponseHeaderSize()); + FlowControlStrategy flowControl = getFlowControlStrategyFactory().newFlowControlStrategy(); ServerParser parser = newServerParser(connector, getRateControlFactory().newRateControl(endPoint)); - parser.setMaxFrameSize(getMaxFrameSize()); parser.setMaxSettingsKeys(getMaxSettingsKeys()); HTTP2ServerSession session = new HTTP2ServerSession(connector.getScheduler(), endPoint, parser, generator, listener, flowControl); diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/HTTP2Test.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/HTTP2Test.java index 66f02599b7e..65b467a2fdb 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/HTTP2Test.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/HTTP2Test.java @@ -45,8 +45,10 @@ 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.hpack.HpackException; +import org.eclipse.jetty.http2.server.AbstractHTTP2ServerConnectionFactory; import org.eclipse.jetty.io.Content; import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Response; import org.eclipse.jetty.util.BufferUtil; @@ -1051,7 +1053,7 @@ public class HTTP2Test extends AbstractTest .put("custom", value); MetaData.Request metaData = newRequest("GET", requestFields); HeadersFrame request = new HeadersFrame(metaData, null, true); - session.newStream(request, new FuturePromise<>(), new Stream.Listener(){}); + session.newStream(request, new FuturePromise<>(), new Stream.Listener() {}); // Test failure and close reason on client. String closeReason = clientCloseReasonFuture.get(5, TimeUnit.SECONDS); @@ -1125,7 +1127,7 @@ public class HTTP2Test extends AbstractTest Session session = newClientSession(listener); MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); HeadersFrame request = new HeadersFrame(metaData, null, true); - session.newStream(request, new FuturePromise<>(), new Stream.Listener(){}); + session.newStream(request, new FuturePromise<>(), new Stream.Listener() {}); // Test failure and close reason on server. String closeReason = serverCloseReasonFuture.get(5, TimeUnit.SECONDS); @@ -1294,6 +1296,48 @@ public class HTTP2Test extends AbstractTest assertTrue(latch.await(5, TimeUnit.SECONDS)); } + @Test + public void testLargeRequestHeaders() throws Exception + { + int maxHeadersSize = 20 * 1024; + HttpConfiguration httpConfig = new HttpConfiguration(); + httpConfig.setRequestHeaderSize(2 * maxHeadersSize); + start(new Handler.Abstract() + { + @Override + public boolean handle(Request request, Response response, Callback callback) + { + callback.succeeded(); + return true; + } + }, httpConfig); + connector.getBean(AbstractHTTP2ServerConnectionFactory.class).setMaxFrameSize(17 * 1024); + http2Client.setMaxFrameSize(18 * 1024); + + Session session = newClientSession(new Session.Listener() {}); + + CountDownLatch responseLatch = new CountDownLatch(1); + HttpFields.Mutable headers = HttpFields.build() + // Symbol "<" needs 15 bits to be Huffman encoded, + // while letters/numbers take typically less than + // 8 bits, and here we want to exceed maxHeadersSize. + .put("X-Large", "<".repeat(maxHeadersSize)); + MetaData.Request request = newRequest("GET", headers); + session.newStream(new HeadersFrame(request, null, true), new Stream.Listener() + { + @Override + public void onHeaders(Stream stream, HeadersFrame frame) + { + assertTrue(frame.isEndStream()); + MetaData.Response response = (MetaData.Response)frame.getMetaData(); + assertEquals(HttpStatus.OK_200, response.getStatus()); + responseLatch.countDown(); + } + }).get(5, TimeUnit.SECONDS); + + assertTrue(responseLatch.await(5, TimeUnit.SECONDS)); + } + private static void sleep(long time) { try diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/HttpClientTransportOverHTTP2Test.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/HttpClientTransportOverHTTP2Test.java index 1a07fd88749..0bd1df1c8c2 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/HttpClientTransportOverHTTP2Test.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/HttpClientTransportOverHTTP2Test.java @@ -145,6 +145,7 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest assertEquals(httpClient.getIdleTimeout(), http2Client.getIdleTimeout()); assertEquals(httpClient.isUseInputDirectByteBuffers(), http2Client.isUseInputDirectByteBuffers()); assertEquals(httpClient.isUseOutputDirectByteBuffers(), http2Client.isUseOutputDirectByteBuffers()); + assertEquals(httpClient.getRequestBufferSize(), http2Client.getMaxRequestHeadersSize()); assertEquals(httpClient.getMaxResponseHeadersSize(), http2Client.getMaxResponseHeadersSize()); } assertTrue(http2Client.isStopped()); diff --git a/jetty-core/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ReverseProxyTest.java b/jetty-core/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ReverseProxyTest.java index 95c19c81619..ea9a5665fda 100644 --- a/jetty-core/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ReverseProxyTest.java +++ b/jetty-core/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ReverseProxyTest.java @@ -152,7 +152,8 @@ public class ReverseProxyTest extends AbstractProxyTest @Override public boolean handle(Request request, Response response, Callback callback) { - response.getHeaders().put("X-Large", "A".repeat(maxResponseHeadersSize)); + // Use "+" because in HTTP/2 is Huffman encoded in more than 8 bits. + response.getHeaders().put("X-Large", "+".repeat(maxResponseHeadersSize)); // With HTTP/1.1, calling response.write() would fail the Handler callback // which would trigger ErrorHandler and result in a 500 to the proxy.