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 <simone.bordet@gmail.com>
This commit is contained in:
Simone Bordet 2023-05-27 19:14:01 +02:00 committed by GitHub
parent debb124dc9
commit 420ec7cc1d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
52 changed files with 1388 additions and 522 deletions

View File

@ -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
*/

View File

@ -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)
{

View File

@ -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<String> 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)
/**
* <p>Sets the limit for the encoder HPACK dynamic table capacity.</p>
* <p>Setting this value to {@code 0} disables the use of the dynamic table.</p>
*
* @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()
{

View File

@ -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<Session> promise = (Promise<Session>)context.get(SESSION_PROMISE_CONTEXT_KEY);
Promise<Session> sessionPromise = (Promise<Session>)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<Session> promise;
private final Session.Listener listener;
private HTTP2ClientConnection(HTTP2Client client, RetainableByteBufferPool retainableByteBufferPool, Executor executor, EndPoint endpoint, Parser parser, ISession session, int bufferSize, Promise<Session> promise, Session.Listener listener)
private HTTP2ClientConnection(HTTP2Client client, RetainableByteBufferPool retainableByteBufferPool, Executor executor, EndPoint endpoint, HTTP2ClientSession session, int bufferSize, Promise<Session> 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<Integer, Integer> 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);

View File

@ -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<Integer, Integer> 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);

View File

@ -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));

View File

@ -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<Callback> exchanger = new Exchanger<>();
@ -403,13 +408,14 @@ public abstract class FlowControlStrategyTest
{
int windowSize = 1536;
Exchanger<Callback> exchanger = new Exchanger<>();
CountDownLatch settingsLatch = new CountDownLatch(1);
CountDownLatch dataLatch = new CountDownLatch(1);
AtomicReference<HTTP2Session> serverSessionRef = new AtomicReference<>();
start(new ServerSessionListener.Adapter()
{
@Override
public Map<Integer, Integer> onPreface(Session session)
{
serverSessionRef.set((HTTP2Session)session);
Map<Integer, Integer> 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<Stream> streamPromise = new FuturePromise<>();
session.newStream(requestFrame, streamPromise, null);
clientSession.newStream(requestFrame, streamPromise, null);
Stream stream = streamPromise.get(5, TimeUnit.SECONDS);
int length = 5 * windowSize;

View File

@ -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));
}
}

View File

@ -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();

View File

@ -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<SettingsFrame> 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)

View File

@ -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<CountDownLatch> 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<CountDownLatch> 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<Integer, Integer> onPreface(Session session)
{
Map<Integer, Integer> 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<Integer, Integer> onPreface(Session session)
{
Map<Integer, Integer> 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<Integer, Integer> onPreface(Session session)
{
Map<Integer, Integer> 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<Integer, Integer> onPreface(Session session)
{
Map<Integer, Integer> 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));
}
}

View File

@ -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");

View File

@ -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;

View File

@ -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<Integer, Integer> entry : frame.getSettings().entrySet())
Map<Integer, Integer> 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<Integer, Integer> settings, boolean local)
{
for (Map.Entry<Integer, Integer> 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<Stream> 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:

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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<Listener> 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()

View File

@ -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();
}
/**
* <p>A direct upgrade is an unofficial upgrade from HTTP/1.1 to HTTP/2.0.</p>
* <p>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();

View File

@ -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;

View File

@ -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<HeadersFrame> 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)

View File

@ -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<DataFrame> 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<DataFrame> 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)

View File

@ -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)
{

View File

@ -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<GoAwayFrame> 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<GoAwayFrame> 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;

View File

@ -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<HeadersFrame> 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<HeadersFrame> 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)

View File

@ -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);

View File

@ -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)

View File

@ -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<PingFrame> 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<PingFrame> 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<PingFrame> 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);

View File

@ -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<PriorityFrame> 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<PriorityFrame> 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;

View File

@ -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<PushPromiseFrame> 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<PushPromiseFrame> 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;

View File

@ -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<ResetFrame> 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<ResetFrame> 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;

View File

@ -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<SettingsFrame> frames = testGenerateParse(Collections.<Integer, Integer>emptyMap());
List<SettingsFrame> 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<SettingsFrame> frames = testGenerateParse(settings1);
List<SettingsFrame> frames = testGenerateParse(settings1, false);
assertEquals(1, frames.size());
SettingsFrame frame = frames.get(0);
Map<Integer, Integer> settings2 = frame.getSettings();
@ -67,26 +66,26 @@ public class SettingsGenerateParseTest
assertEquals(value2, settings2.get(key2));
}
private List<SettingsFrame> testGenerateParse(Map<Integer, Integer> settings)
private List<SettingsFrame> testGenerateParse(Map<Integer, Integer> settings, boolean reply)
{
SettingsGenerator generator = new SettingsGenerator(new HeaderGenerator());
List<SettingsFrame> 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<Integer, Integer> 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<SettingsFrame> 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<Integer, Integer> 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<Integer, Integer> 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<Integer, Integer> 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<Integer, Integer> settings = new HashMap<>();
settings.put(13, 17);

View File

@ -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<ByteBuffer, ByteBuffer> 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)

View File

@ -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<WindowUpdateFrame> 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<WindowUpdateFrame> 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;

View File

@ -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<HttpField, Entry> _fieldMap = new HashMap<>();
private final Map<String, Entry> _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);
}
}

View File

@ -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;
}
/**
* <p>Sets the limit for the capacity of the dynamic header table.</p>
* <p>This value acts as a limit for the values received from the
* remote peer via the HPACK dynamic table size update instruction.</p>
* <p>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.</p>
*
* @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);

View File

@ -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;
}
/**
* <p>Sets the limit for the capacity of the dynamic header table.</p>
* <p>This value is set by the remote peer via the
* {@code SETTINGS_HEADER_TABLE_SIZE} setting.</p>
*
* @param maxTableSizeLimit the limit for capacity of the dynamic header table
*/
public void setMaxTableCapacity(int maxTableSizeLimit)
{
_maxTableCapacity = maxTableSizeLimit;
}
public int getTableCapacity()
{
return _tableCapacity;
}
/**
* <p>Sets the capacity of the dynamic header table.</p>
* <p>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.</p>
*
* @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)

View File

@ -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)
{

View File

@ -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"));
}

View File

@ -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;
}
}

View File

@ -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");

View File

@ -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")

View File

@ -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");

View File

@ -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)
/**
* <p>Sets the limit for the encoder HPACK dynamic table capacity.</p>
* <p>Setting this value to {@code 0} disables the use of the dynamic table.</p>
*
* @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<Integer, Integer> newSettings()
{
Map<Integer, Integer> 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")

View File

@ -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.

View File

@ -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();
}
}

View File

@ -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);

View File

@ -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<HeadersFrame> headersRef = new AtomicReference<>();
final AtomicReference<DataFrame> dataRef = new AtomicReference<>();
final AtomicReference<CountDownLatch> 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<HeadersFrame> headersRef = new AtomicReference<>();
final AtomicReference<DataFrame> 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);

View File

@ -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<HeadersFrame> 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<HeadersFrame> headersRef = new AtomicReference<>();
final AtomicReference<DataFrame> 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));

View File

@ -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)
{

View File

@ -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.</p>
*
* @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
* <p>This value is configured in the local QPACK encoder, and may be
* overwritten by a smaller value received via the SETTINGS frame.</p>
*
* @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")

View File

@ -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)
{

View File

@ -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()