Merged branch 'jetty-12.0.x' into 'jetty-12.1.x'.
Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
This commit is contained in:
commit
4f815f6891
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -54,10 +54,11 @@ public class HTTP2ClientConnectionFactory implements ClientConnectionFactory
|
|||
Promise<Session> sessionPromise = (Promise<Session>)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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue