Merge pull request #7637 from eclipse/jetty-10.0.x-7625-http3-settings
Fixes #7625 - HTTP/3 error against www.google.com
This commit is contained in:
commit
ec5f05fc10
|
@ -92,6 +92,11 @@ public class ClientHTTP3Session extends ClientProtocolSession
|
||||||
return decoder;
|
return decoder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public QpackEncoder getQpackEncoder()
|
||||||
|
{
|
||||||
|
return encoder;
|
||||||
|
}
|
||||||
|
|
||||||
public HTTP3SessionClient getSessionClient()
|
public HTTP3SessionClient getSessionClient()
|
||||||
{
|
{
|
||||||
return session;
|
return session;
|
||||||
|
|
|
@ -146,4 +146,24 @@ public class HTTP3SessionClient extends HTTP3Session implements Session.Client
|
||||||
return GoAwayFrame.CLIENT_GRACEFUL;
|
return GoAwayFrame.CLIENT_GRACEFUL;
|
||||||
return super.newGoAwayFrame(graceful);
|
return super.newGoAwayFrame(graceful);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onSettingMaxTableCapacity(long value)
|
||||||
|
{
|
||||||
|
getProtocolSession().getQpackEncoder().setCapacity((int)value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onSettingMaxFieldSectionSize(long value)
|
||||||
|
{
|
||||||
|
getProtocolSession().getQpackDecoder().setMaxHeaderSize((int)value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onSettingMaxBlockedStreams(long value)
|
||||||
|
{
|
||||||
|
ClientHTTP3Session session = getProtocolSession();
|
||||||
|
session.getQpackDecoder().setMaxBlockedStreams((int)value);
|
||||||
|
session.getQpackEncoder().setMaxBlockedStreams((int)value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,11 +17,18 @@ import java.util.Map;
|
||||||
|
|
||||||
public class SettingsFrame extends Frame
|
public class SettingsFrame extends Frame
|
||||||
{
|
{
|
||||||
|
public static final long MAX_TABLE_CAPACITY = 0x01;
|
||||||
public static final long MAX_FIELD_SECTION_SIZE = 0x06;
|
public static final long MAX_FIELD_SECTION_SIZE = 0x06;
|
||||||
|
public static final long MAX_BLOCKED_STREAMS = 0x07;
|
||||||
|
|
||||||
public static boolean isReserved(long key)
|
public static boolean isReserved(long key)
|
||||||
{
|
{
|
||||||
return key >= 0 && key <= 5;
|
if (key == MAX_TABLE_CAPACITY ||
|
||||||
|
key == MAX_FIELD_SECTION_SIZE ||
|
||||||
|
key == MAX_BLOCKED_STREAMS)
|
||||||
|
return false;
|
||||||
|
// Other HTTP/2 settings are reserved and must not be sent/received.
|
||||||
|
return key >= 0x00 && key <= 0x05;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Map<Long, Long> settings;
|
private final Map<Long, Long> settings;
|
||||||
|
|
|
@ -371,9 +371,32 @@ public abstract class HTTP3Session extends ContainerLifeCycle implements Session
|
||||||
{
|
{
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("received {} on {}", frame, this);
|
LOG.debug("received {} on {}", frame, this);
|
||||||
|
|
||||||
|
frame.getSettings().forEach((key, value) ->
|
||||||
|
{
|
||||||
|
if (key == SettingsFrame.MAX_TABLE_CAPACITY)
|
||||||
|
onSettingMaxTableCapacity(value);
|
||||||
|
else if (key == SettingsFrame.MAX_FIELD_SECTION_SIZE)
|
||||||
|
onSettingMaxFieldSectionSize(value);
|
||||||
|
else if (key == SettingsFrame.MAX_BLOCKED_STREAMS)
|
||||||
|
onSettingMaxBlockedStreams(value);
|
||||||
|
});
|
||||||
|
|
||||||
notifySettings(frame);
|
notifySettings(frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void onSettingMaxTableCapacity(long value)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onSettingMaxFieldSectionSize(long value)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onSettingMaxBlockedStreams(long value)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
private void notifySettings(SettingsFrame frame)
|
private void notifySettings(SettingsFrame frame)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
|
@ -50,7 +50,8 @@ public class QpackDecoder implements Dumpable
|
||||||
private final List<EncodedFieldSection> _encodedFieldSections = new ArrayList<>();
|
private final List<EncodedFieldSection> _encodedFieldSections = new ArrayList<>();
|
||||||
private final NBitIntegerParser _integerDecoder = new NBitIntegerParser();
|
private final NBitIntegerParser _integerDecoder = new NBitIntegerParser();
|
||||||
private final InstructionHandler _instructionHandler = new InstructionHandler();
|
private final InstructionHandler _instructionHandler = new InstructionHandler();
|
||||||
private final int _maxHeaderSize;
|
private int _maxHeaderSize;
|
||||||
|
private int _maxBlockedStreams;
|
||||||
|
|
||||||
private static class MetaDataNotification
|
private static class MetaDataNotification
|
||||||
{
|
{
|
||||||
|
@ -87,6 +88,27 @@ public class QpackDecoder implements Dumpable
|
||||||
return _context;
|
return _context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getMaxHeaderSize()
|
||||||
|
{
|
||||||
|
return _maxHeaderSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaxHeaderSize(int maxHeaderSize)
|
||||||
|
{
|
||||||
|
_maxHeaderSize = maxHeaderSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMaxBlockedStreams()
|
||||||
|
{
|
||||||
|
// TODO: implement logic about blocked streams by calling this method.
|
||||||
|
return _maxBlockedStreams;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaxBlockedStreams(int maxBlockedStreams)
|
||||||
|
{
|
||||||
|
_maxBlockedStreams = maxBlockedStreams;
|
||||||
|
}
|
||||||
|
|
||||||
public interface Handler
|
public interface Handler
|
||||||
{
|
{
|
||||||
void onMetaData(long streamId, MetaData metadata);
|
void onMetaData(long streamId, MetaData metadata);
|
||||||
|
@ -110,7 +132,8 @@ public class QpackDecoder implements Dumpable
|
||||||
LOG.debug("Decoding: streamId={}, buffer={}", streamId, BufferUtil.toDetailString(buffer));
|
LOG.debug("Decoding: streamId={}, buffer={}", streamId, BufferUtil.toDetailString(buffer));
|
||||||
|
|
||||||
// If the buffer is big, don't even think about decoding it
|
// If the buffer is big, don't even think about decoding it
|
||||||
if (buffer.remaining() > _maxHeaderSize)
|
int maxHeaderSize = getMaxHeaderSize();
|
||||||
|
if (buffer.remaining() > maxHeaderSize)
|
||||||
throw new QpackException.SessionException(QPACK_DECOMPRESSION_FAILED, "header_too_large");
|
throw new QpackException.SessionException(QPACK_DECOMPRESSION_FAILED, "header_too_large");
|
||||||
|
|
||||||
_integerDecoder.setPrefix(8);
|
_integerDecoder.setPrefix(8);
|
||||||
|
@ -139,7 +162,7 @@ public class QpackDecoder implements Dumpable
|
||||||
// Decode it straight away if we can, otherwise add it to the list of EncodedFieldSections.
|
// Decode it straight away if we can, otherwise add it to the list of EncodedFieldSections.
|
||||||
if (requiredInsertCount <= insertCount)
|
if (requiredInsertCount <= insertCount)
|
||||||
{
|
{
|
||||||
MetaData metaData = encodedFieldSection.decode(_context, _maxHeaderSize);
|
MetaData metaData = encodedFieldSection.decode(_context, maxHeaderSize);
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("Decoded: streamId={}, metadata={}", streamId, metaData);
|
LOG.debug("Decoded: streamId={}, metadata={}", streamId, metaData);
|
||||||
_metaDataNotifications.add(new MetaDataNotification(streamId, metaData, handler));
|
_metaDataNotifications.add(new MetaDataNotification(streamId, metaData, handler));
|
||||||
|
|
|
@ -91,7 +91,7 @@ public class QpackEncoder implements Dumpable
|
||||||
private final List<Instruction> _instructions = new ArrayList<>();
|
private final List<Instruction> _instructions = new ArrayList<>();
|
||||||
private final Instruction.Handler _handler;
|
private final Instruction.Handler _handler;
|
||||||
private final QpackContext _context;
|
private final QpackContext _context;
|
||||||
private final int _maxBlockedStreams;
|
private int _maxBlockedStreams;
|
||||||
private final Map<Long, StreamInfo> _streamInfoMap = new HashMap<>();
|
private final Map<Long, StreamInfo> _streamInfoMap = new HashMap<>();
|
||||||
private final EncoderInstructionParser _parser;
|
private final EncoderInstructionParser _parser;
|
||||||
private final InstructionHandler _instructionHandler = new InstructionHandler();
|
private final InstructionHandler _instructionHandler = new InstructionHandler();
|
||||||
|
@ -106,6 +106,21 @@ public class QpackEncoder implements Dumpable
|
||||||
_parser = new EncoderInstructionParser(_instructionHandler);
|
_parser = new EncoderInstructionParser(_instructionHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getMaxBlockedStreams()
|
||||||
|
{
|
||||||
|
return _maxBlockedStreams;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaxBlockedStreams(int maxBlockedStreams)
|
||||||
|
{
|
||||||
|
_maxBlockedStreams = maxBlockedStreams;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCapacity()
|
||||||
|
{
|
||||||
|
return _context.getDynamicTable().getCapacity();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the capacity of the DynamicTable and send a instruction to set the capacity on the remote Decoder.
|
* Set the capacity of the DynamicTable and send a instruction to set the capacity on the remote Decoder.
|
||||||
*
|
*
|
||||||
|
@ -411,7 +426,7 @@ public class QpackEncoder implements Dumpable
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_blockedStreams < _maxBlockedStreams)
|
if (_blockedStreams < getMaxBlockedStreams())
|
||||||
{
|
{
|
||||||
_blockedStreams++;
|
_blockedStreams++;
|
||||||
sectionInfo.block();
|
sectionInfo.block();
|
||||||
|
|
|
@ -95,6 +95,26 @@ public class HTTP3SessionServer extends HTTP3Session implements Session.Server
|
||||||
return super.newGoAwayFrame(graceful);
|
return super.newGoAwayFrame(graceful);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onSettingMaxTableCapacity(long value)
|
||||||
|
{
|
||||||
|
getProtocolSession().getQpackEncoder().setCapacity((int)value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onSettingMaxFieldSectionSize(long value)
|
||||||
|
{
|
||||||
|
getProtocolSession().getQpackDecoder().setMaxHeaderSize((int)value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onSettingMaxBlockedStreams(long value)
|
||||||
|
{
|
||||||
|
ServerHTTP3Session session = getProtocolSession();
|
||||||
|
session.getQpackDecoder().setMaxBlockedStreams((int)value);
|
||||||
|
session.getQpackEncoder().setMaxBlockedStreams((int)value);
|
||||||
|
}
|
||||||
|
|
||||||
private void notifyAccept()
|
private void notifyAccept()
|
||||||
{
|
{
|
||||||
Server.Listener listener = getListener();
|
Server.Listener listener = getListener();
|
||||||
|
|
|
@ -91,6 +91,11 @@ public class ServerHTTP3Session extends ServerProtocolSession
|
||||||
return decoder;
|
return decoder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public QpackEncoder getQpackEncoder()
|
||||||
|
{
|
||||||
|
return encoder;
|
||||||
|
}
|
||||||
|
|
||||||
public HTTP3SessionServer getSessionServer()
|
public HTTP3SessionServer getSessionServer()
|
||||||
{
|
{
|
||||||
return session;
|
return session;
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
package org.eclipse.jetty.http3.tests;
|
package org.eclipse.jetty.http3.tests;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.AbstractMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
@ -34,6 +35,7 @@ import org.eclipse.jetty.http3.frames.SettingsFrame;
|
||||||
import org.eclipse.jetty.http3.internal.HTTP3ErrorCode;
|
import org.eclipse.jetty.http3.internal.HTTP3ErrorCode;
|
||||||
import org.eclipse.jetty.http3.internal.HTTP3Session;
|
import org.eclipse.jetty.http3.internal.HTTP3Session;
|
||||||
import org.eclipse.jetty.http3.server.AbstractHTTP3ServerConnectionFactory;
|
import org.eclipse.jetty.http3.server.AbstractHTTP3ServerConnectionFactory;
|
||||||
|
import org.eclipse.jetty.http3.server.internal.HTTP3SessionServer;
|
||||||
import org.eclipse.jetty.quic.client.ClientQuicSession;
|
import org.eclipse.jetty.quic.client.ClientQuicSession;
|
||||||
import org.eclipse.jetty.quic.common.QuicSession;
|
import org.eclipse.jetty.quic.common.QuicSession;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
@ -41,6 +43,7 @@ import org.junit.jupiter.params.ParameterizedTest;
|
||||||
import org.junit.jupiter.params.provider.ValueSource;
|
import org.junit.jupiter.params.provider.ValueSource;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
@ -90,6 +93,59 @@ public class ClientServerTest extends AbstractClientServerTest
|
||||||
assertTrue(clientSettingsLatch.await(5, TimeUnit.SECONDS));
|
assertTrue(clientSettingsLatch.await(5, TimeUnit.SECONDS));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSettings() throws Exception
|
||||||
|
{
|
||||||
|
Map.Entry<Long, Long> maxTableCapacity = new AbstractMap.SimpleEntry<>(SettingsFrame.MAX_TABLE_CAPACITY, 1024L);
|
||||||
|
Map.Entry<Long, Long> maxHeaderSize = new AbstractMap.SimpleEntry<>(SettingsFrame.MAX_FIELD_SECTION_SIZE, 2048L);
|
||||||
|
Map.Entry<Long, Long> maxBlockedStreams = new AbstractMap.SimpleEntry<>(SettingsFrame.MAX_BLOCKED_STREAMS, 16L);
|
||||||
|
CountDownLatch settingsLatch = new CountDownLatch(2);
|
||||||
|
AtomicReference<HTTP3SessionServer> serverSessionRef = new AtomicReference<>();
|
||||||
|
start(new Session.Server.Listener()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public Map<Long, Long> onPreface(Session session)
|
||||||
|
{
|
||||||
|
serverSessionRef.set((HTTP3SessionServer)session);
|
||||||
|
return Map.ofEntries(maxTableCapacity, maxHeaderSize, maxBlockedStreams);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSettings(Session session, SettingsFrame frame)
|
||||||
|
{
|
||||||
|
settingsLatch.countDown();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
HTTP3SessionClient clientSession = (HTTP3SessionClient)newSession(new Session.Client.Listener()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public Map<Long, Long> onPreface(Session session)
|
||||||
|
{
|
||||||
|
return Map.ofEntries(maxTableCapacity, maxHeaderSize, maxBlockedStreams);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSettings(Session session, SettingsFrame frame)
|
||||||
|
{
|
||||||
|
settingsLatch.countDown();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
assertTrue(settingsLatch.await(5, TimeUnit.SECONDS));
|
||||||
|
|
||||||
|
HTTP3SessionServer serverSession = serverSessionRef.get();
|
||||||
|
assertEquals(maxTableCapacity.getValue(), serverSession.getProtocolSession().getQpackEncoder().getCapacity());
|
||||||
|
assertEquals(maxBlockedStreams.getValue(), serverSession.getProtocolSession().getQpackEncoder().getMaxBlockedStreams());
|
||||||
|
assertEquals(maxBlockedStreams.getValue(), serverSession.getProtocolSession().getQpackDecoder().getMaxBlockedStreams());
|
||||||
|
assertEquals(maxHeaderSize.getValue(), serverSession.getProtocolSession().getQpackDecoder().getMaxHeaderSize());
|
||||||
|
|
||||||
|
assertEquals(maxTableCapacity.getValue(), clientSession.getProtocolSession().getQpackEncoder().getCapacity());
|
||||||
|
assertEquals(maxBlockedStreams.getValue(), clientSession.getProtocolSession().getQpackEncoder().getMaxBlockedStreams());
|
||||||
|
assertEquals(maxBlockedStreams.getValue(), clientSession.getProtocolSession().getQpackDecoder().getMaxBlockedStreams());
|
||||||
|
assertEquals(maxHeaderSize.getValue(), clientSession.getProtocolSession().getQpackDecoder().getMaxHeaderSize());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGETThenResponseWithoutContent() throws Exception
|
public void testGETThenResponseWithoutContent() throws Exception
|
||||||
{
|
{
|
||||||
|
|
|
@ -44,8 +44,9 @@ public class ExternalServerTest
|
||||||
client.start();
|
client.start();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
HostPort hostPort = new HostPort("google.com:443");
|
||||||
// HostPort hostPort = new HostPort("nghttp2.org:4433");
|
// HostPort hostPort = new HostPort("nghttp2.org:4433");
|
||||||
HostPort hostPort = new HostPort("quic.tech:8443");
|
// HostPort hostPort = new HostPort("quic.tech:8443");
|
||||||
// HostPort hostPort = new HostPort("h2o.examp1e.net:443");
|
// HostPort hostPort = new HostPort("h2o.examp1e.net:443");
|
||||||
// HostPort hostPort = new HostPort("test.privateoctopus.com:4433");
|
// HostPort hostPort = new HostPort("test.privateoctopus.com:4433");
|
||||||
Session.Client session = client.connect(new InetSocketAddress(hostPort.getHost(), hostPort.getPort()), new Session.Client.Listener() {})
|
Session.Client session = client.connect(new InetSocketAddress(hostPort.getHost(), hostPort.getPort()), new Session.Client.Listener() {})
|
||||||
|
|
Loading…
Reference in New Issue