Fixes #7625 - HTTP/3 error against www.google.com
Now properly handling QPACK and HTTP/3 settings. Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
This commit is contained in:
parent
3d9f34696b
commit
55eb982185
|
@ -92,6 +92,11 @@ public class ClientHTTP3Session extends ClientProtocolSession
|
|||
return decoder;
|
||||
}
|
||||
|
||||
public QpackEncoder getQpackEncoder()
|
||||
{
|
||||
return encoder;
|
||||
}
|
||||
|
||||
public HTTP3SessionClient getSessionClient()
|
||||
{
|
||||
return session;
|
||||
|
|
|
@ -146,4 +146,24 @@ public class HTTP3SessionClient extends HTTP3Session implements Session.Client
|
|||
return GoAwayFrame.CLIENT_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 static final long MAX_TABLE_CAPACITY = 0x01;
|
||||
public static final long MAX_FIELD_SECTION_SIZE = 0x06;
|
||||
public static final long MAX_BLOCKED_STREAMS = 0x07;
|
||||
|
||||
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;
|
||||
|
|
|
@ -371,9 +371,32 @@ public abstract class HTTP3Session extends ContainerLifeCycle implements Session
|
|||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
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);
|
||||
}
|
||||
|
||||
protected void onSettingMaxTableCapacity(long value)
|
||||
{
|
||||
}
|
||||
|
||||
protected void onSettingMaxFieldSectionSize(long value)
|
||||
{
|
||||
}
|
||||
|
||||
protected void onSettingMaxBlockedStreams(long value)
|
||||
{
|
||||
}
|
||||
|
||||
private void notifySettings(SettingsFrame frame)
|
||||
{
|
||||
try
|
||||
|
|
|
@ -50,7 +50,8 @@ public class QpackDecoder implements Dumpable
|
|||
private final List<EncodedFieldSection> _encodedFieldSections = new ArrayList<>();
|
||||
private final NBitIntegerParser _integerDecoder = new NBitIntegerParser();
|
||||
private final InstructionHandler _instructionHandler = new InstructionHandler();
|
||||
private final int _maxHeaderSize;
|
||||
private int _maxHeaderSize;
|
||||
private int _maxBlockedStreams;
|
||||
|
||||
private static class MetaDataNotification
|
||||
{
|
||||
|
@ -87,6 +88,27 @@ public class QpackDecoder implements Dumpable
|
|||
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
|
||||
{
|
||||
void onMetaData(long streamId, MetaData metadata);
|
||||
|
@ -110,7 +132,8 @@ public class QpackDecoder implements Dumpable
|
|||
LOG.debug("Decoding: streamId={}, buffer={}", streamId, BufferUtil.toDetailString(buffer));
|
||||
|
||||
// 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");
|
||||
|
||||
_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.
|
||||
if (requiredInsertCount <= insertCount)
|
||||
{
|
||||
MetaData metaData = encodedFieldSection.decode(_context, _maxHeaderSize);
|
||||
MetaData metaData = encodedFieldSection.decode(_context, maxHeaderSize);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Decoded: streamId={}, metadata={}", streamId, metaData);
|
||||
_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 Instruction.Handler _handler;
|
||||
private final QpackContext _context;
|
||||
private final int _maxBlockedStreams;
|
||||
private int _maxBlockedStreams;
|
||||
private final Map<Long, StreamInfo> _streamInfoMap = new HashMap<>();
|
||||
private final EncoderInstructionParser _parser;
|
||||
private final InstructionHandler _instructionHandler = new InstructionHandler();
|
||||
|
@ -106,6 +106,21 @@ public class QpackEncoder implements Dumpable
|
|||
_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.
|
||||
*
|
||||
|
@ -411,7 +426,7 @@ public class QpackEncoder implements Dumpable
|
|||
return true;
|
||||
}
|
||||
|
||||
if (_blockedStreams < _maxBlockedStreams)
|
||||
if (_blockedStreams < getMaxBlockedStreams())
|
||||
{
|
||||
_blockedStreams++;
|
||||
sectionInfo.block();
|
||||
|
|
|
@ -95,6 +95,26 @@ public class HTTP3SessionServer extends HTTP3Session implements Session.Server
|
|||
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()
|
||||
{
|
||||
Server.Listener listener = getListener();
|
||||
|
|
|
@ -91,6 +91,11 @@ public class ServerHTTP3Session extends ServerProtocolSession
|
|||
return decoder;
|
||||
}
|
||||
|
||||
public QpackEncoder getQpackEncoder()
|
||||
{
|
||||
return encoder;
|
||||
}
|
||||
|
||||
public HTTP3SessionServer getSessionServer()
|
||||
{
|
||||
return session;
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
package org.eclipse.jetty.http3.tests;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
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.HTTP3Session;
|
||||
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.common.QuicSession;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
@ -41,6 +43,7 @@ import org.junit.jupiter.params.ParameterizedTest;
|
|||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
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.assertTrue;
|
||||
|
||||
|
@ -90,6 +93,59 @@ public class ClientServerTest extends AbstractClientServerTest
|
|||
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
|
||||
public void testGETThenResponseWithoutContent() throws Exception
|
||||
{
|
||||
|
|
|
@ -44,8 +44,9 @@ public class ExternalServerTest
|
|||
client.start();
|
||||
try
|
||||
{
|
||||
HostPort hostPort = new HostPort("google.com:443");
|
||||
// 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("test.privateoctopus.com:4433");
|
||||
Session.Client session = client.connect(new InetSocketAddress(hostPort.getHost(), hostPort.getPort()), new Session.Client.Listener() {})
|
||||
|
|
Loading…
Reference in New Issue