diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/MetaData.java b/jetty-http/src/main/java/org/eclipse/jetty/http/MetaData.java index 6ddc9237100..8721ce3522a 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/MetaData.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/MetaData.java @@ -151,6 +151,13 @@ public class MetaData implements Iterable version, fields, contentLength); } + public Request(long beginNanoTime, String method, String scheme, HostPortHttpField authority, String uri, HttpVersion version, HttpFields fields, long contentLength) + { + this(beginNanoTime, method, + HttpURI.build().scheme(scheme).host(authority == null ? null : authority.getHost()).port(authority == null ? -1 : authority.getPort()).pathQuery(uri), + version, fields, contentLength); + } + public Request(String method, HttpURI uri, HttpVersion version, HttpFields fields, long contentLength, Supplier trailers) { this(NanoTime.now(), method, uri, version, fields, contentLength, trailers); @@ -222,9 +229,19 @@ public class MetaData implements Iterable this(scheme == null ? null : scheme.asString(), authority, path, fields, protocol); } + public ConnectRequest(long beginNanoTime, HttpScheme scheme, HostPortHttpField authority, String path, HttpFields fields, String protocol) + { + this(beginNanoTime, scheme == null ? null : scheme.asString(), authority, path, fields, protocol); + } + public ConnectRequest(String scheme, HostPortHttpField authority, String path, HttpFields fields, String protocol) { - super(HttpMethod.CONNECT.asString(), + this(NanoTime.now(), scheme, authority, path, fields, protocol); + } + + public ConnectRequest(long beginNanoTime, String scheme, HostPortHttpField authority, String path, HttpFields fields, String protocol) + { + super(beginNanoTime, HttpMethod.CONNECT.asString(), HttpURI.build().scheme(scheme).host(authority == null ? null : authority.getHost()).port(authority == null ? -1 : authority.getPort()).pathQuery(path), HttpVersion.HTTP_2, fields, Long.MIN_VALUE, null); _protocol = protocol; diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/Parser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/Parser.java index e43be18e6e4..21d83ddb273 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/Parser.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/Parser.java @@ -30,6 +30,7 @@ import org.eclipse.jetty.http2.frames.SettingsFrame; import org.eclipse.jetty.http2.frames.WindowUpdateFrame; import org.eclipse.jetty.http2.hpack.HpackDecoder; import org.eclipse.jetty.io.ByteBufferPool; +import org.eclipse.jetty.util.NanoTime; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -52,6 +53,8 @@ public class Parser private int maxSettingsKeys = SettingsFrame.DEFAULT_MAX_KEYS; private boolean continuation; private State state = State.HEADER; + private long beginNanoTime; + private boolean nanoTimeStored; @Deprecated public Parser(ByteBufferPool byteBufferPool, int maxTableCapacity, int maxHeaderSize) @@ -74,7 +77,7 @@ public class Parser { this.byteBufferPool = byteBufferPool; this.headerParser = new HeaderParser(rateControl == null ? RateControl.NO_RATE_CONTROL : rateControl); - this.hpackDecoder = new HpackDecoder(maxHeaderSize); + this.hpackDecoder = new HpackDecoder(maxHeaderSize, this::getBeginNanoTime); this.bodyParsers = new BodyParser[FrameType.values().length]; } @@ -114,6 +117,25 @@ public class Parser state = State.HEADER; } + public long getBeginNanoTime() + { + return beginNanoTime; + } + + private void clearBeginNanoTime() + { + nanoTimeStored = false; + } + + private void storeBeginNanoTime() + { + if (!nanoTimeStored) + { + beginNanoTime = NanoTime.now(); + nanoTimeStored = true; + } + } + /** *

Parses the given {@code buffer} bytes and emit events to a {@link Listener}.

*

When this method returns, the buffer may not be fully consumed, so invocations @@ -135,6 +157,7 @@ public class Parser { case HEADER: { + storeBeginNanoTime(); if (!parseHeader(buffer)) return; break; @@ -143,6 +166,8 @@ public class Parser { if (!parseBody(buffer)) return; + if (!continuation) + clearBeginNanoTime(); break; } default: diff --git a/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/ContinuationParseTest.java b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/ContinuationParseTest.java index 4443893c266..7eb1f8f10e2 100644 --- a/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/ContinuationParseTest.java +++ b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/ContinuationParseTest.java @@ -158,6 +158,102 @@ public class ContinuationParseTest } } + @Test + public void testBeginNanoTime() throws Exception + { + ByteBufferPool bufferPool = new MappedByteBufferPool(128); + HeadersGenerator generator = new HeadersGenerator(new HeaderGenerator(), new HpackEncoder()); + + final List frames = new ArrayList<>(); + Parser parser = new Parser(bufferPool, 8192); + parser.init(new Parser.Listener.Adapter() + { + @Override + public void onHeaders(HeadersFrame frame) + { + frames.add(frame); + } + + @Override + public void onConnectionFailure(int error, String reason) + { + frames.add(new HeadersFrame(null, null, false)); + } + }); + + int streamId = 13; + HttpFields fields = HttpFields.build() + .put("Accept", "text/html") + .put("User-Agent", "Jetty"); + MetaData.Request metaData = new MetaData.Request("GET", HttpScheme.HTTP.asString(), new HostPortHttpField("localhost:8080"), "/path", HttpVersion.HTTP_2, fields, -1); + + ByteBufferPool.Lease lease = new ByteBufferPool.Lease(bufferPool); + generator.generateHeaders(lease, streamId, metaData, null, true); + + List byteBuffers = lease.getByteBuffers(); + assertEquals(2, byteBuffers.size()); + + ByteBuffer headersBody = byteBuffers.remove(1); + int start = headersBody.position(); + int length = headersBody.remaining(); + int firstHalf = length / 2; + int lastHalf = length - firstHalf; + + // Adjust the length of the HEADERS frame. + ByteBuffer headersHeader = byteBuffers.get(0); + headersHeader.put(0, (byte)((firstHalf >>> 16) & 0xFF)); + headersHeader.put(1, (byte)((firstHalf >>> 8) & 0xFF)); + headersHeader.put(2, (byte)(firstHalf & 0xFF)); + + // Remove the END_HEADERS flag from the HEADERS header. + headersHeader.put(4, (byte)(headersHeader.get(4) & ~Flags.END_HEADERS)); + + // New HEADERS body. + headersBody.position(start); + headersBody.limit(start + firstHalf); + byteBuffers.add(headersBody.slice()); + + // Split the rest of the HEADERS body into a CONTINUATION frame. + byte[] continuationHeader = new byte[9]; + continuationHeader[0] = (byte)((lastHalf >>> 16) & 0xFF); + continuationHeader[1] = (byte)((lastHalf >>> 8) & 0xFF); + continuationHeader[2] = (byte)(lastHalf & 0xFF); + continuationHeader[3] = (byte)FrameType.CONTINUATION.getType(); + continuationHeader[4] = Flags.END_HEADERS; + continuationHeader[5] = 0x00; + continuationHeader[6] = 0x00; + continuationHeader[7] = 0x00; + continuationHeader[8] = (byte)streamId; + byteBuffers.add(ByteBuffer.wrap(continuationHeader)); + // CONTINUATION body. + headersBody.position(start + firstHalf); + headersBody.limit(start + length); + byteBuffers.add(headersBody.slice()); + + assertEquals(4, byteBuffers.size()); + parser.parse(byteBuffers.get(0)); + long beginNanoTime = parser.getBeginNanoTime(); + parser.parse(byteBuffers.get(1)); + parser.parse(byteBuffers.get(2)); + parser.parse(byteBuffers.get(3)); + + assertEquals(1, frames.size()); + HeadersFrame frame = frames.get(0); + assertEquals(streamId, frame.getStreamId()); + assertTrue(frame.isEndStream()); + MetaData.Request request = (MetaData.Request)frame.getMetaData(); + assertEquals(metaData.getMethod(), request.getMethod()); + assertEquals(metaData.getURIString(), request.getURIString()); + for (int i = 0; i < fields.size(); ++i) + { + HttpField field = fields.getField(i); + assertTrue(request.getFields().contains(field)); + } + PriorityFrame priority = frame.getPriority(); + assertNull(priority); + assertEquals(beginNanoTime, request.getBeginNanoTime()); + } + @Test public void testLargeHeadersBlock() throws Exception { diff --git a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackDecoder.java b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackDecoder.java index 70b950011d4..a2a470815b5 100644 --- a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackDecoder.java +++ b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackDecoder.java @@ -14,6 +14,7 @@ package org.eclipse.jetty.http2.hpack; import java.nio.ByteBuffer; +import java.util.function.LongSupplier; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpHeader; @@ -42,14 +43,18 @@ public class HpackDecoder private final MetaDataBuilder _builder; private final HuffmanDecoder _huffmanDecoder; private final NBitIntegerDecoder _integerDecoder; + private final LongSupplier _beginNanoTimeSupplier; private int _maxTableCapacity; /** * @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 + * @param beginNanoTimeSupplier The supplier of a nano timestamp taken at + * the time the first byte was read */ - public HpackDecoder(int maxHeaderSize) + public HpackDecoder(int maxHeaderSize, LongSupplier beginNanoTimeSupplier) { + _beginNanoTimeSupplier = beginNanoTimeSupplier; _context = new HpackContext(HpackContext.DEFAULT_MAX_TABLE_CAPACITY); _builder = new MetaDataBuilder(maxHeaderSize); _huffmanDecoder = new HuffmanDecoder(); @@ -305,6 +310,7 @@ public class HpackDecoder } } + _builder.setBeginNanoTime(_beginNanoTimeSupplier.getAsLong()); return _builder.build(); } diff --git a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/MetaDataBuilder.java b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/MetaDataBuilder.java index a2afc848dd3..c65d84d8842 100644 --- a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/MetaDataBuilder.java +++ b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/MetaDataBuilder.java @@ -22,6 +22,7 @@ import org.eclipse.jetty.http.HttpScheme; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http2.hpack.HpackException.SessionException; +import org.eclipse.jetty.util.NanoTime; public class MetaDataBuilder { @@ -38,6 +39,7 @@ public class MetaDataBuilder private HpackException.StreamException _streamException; private boolean _request; private boolean _response; + private long _beginNanoTime = Long.MIN_VALUE; /** * @param maxHeadersSize The maximum size of the headers, expressed as total name and value characters. @@ -60,6 +62,13 @@ public class MetaDataBuilder _maxSize = maxSize; } + public void setBeginNanoTime(long beginNanoTime) + { + if (beginNanoTime == Long.MIN_VALUE) + beginNanoTime++; + _beginNanoTime = beginNanoTime; + } + /** * Get the size. * @@ -248,10 +257,13 @@ public class MetaDataBuilder if (_path == null) throw new HpackException.StreamException("No Path"); } + long nanoTime = _beginNanoTime == Long.MIN_VALUE ? NanoTime.now() : _beginNanoTime; + _beginNanoTime = Long.MIN_VALUE; if (isConnect) - return new MetaData.ConnectRequest(_scheme, _authority, _path, fields, _protocol); + return new MetaData.ConnectRequest(nanoTime, _scheme, _authority, _path, fields, _protocol); else return new MetaData.Request( + nanoTime, _method, _scheme.asString(), _authority, diff --git a/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackDecoderTest.java b/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackDecoderTest.java index 591775f84c8..74073143bad 100644 --- a/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackDecoderTest.java +++ b/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackDecoderTest.java @@ -23,6 +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.NanoTime; import org.eclipse.jetty.util.StringUtil; import org.hamcrest.Matchers; import org.junit.jupiter.api.Test; @@ -57,7 +58,7 @@ public class HpackDecoderTest @Test public void testDecodeD3() throws Exception { - HpackDecoder decoder = new HpackDecoder(8192); + HpackDecoder decoder = new HpackDecoder(8192, NanoTime::now); // First request String encoded = "828684410f7777772e6578616d706c652e636f6d"; @@ -105,7 +106,7 @@ public class HpackDecoderTest @Test public void testDecodeD4() throws Exception { - HpackDecoder decoder = new HpackDecoder(8192); + HpackDecoder decoder = new HpackDecoder(8192, NanoTime::now); // First request String encoded = "828684418cf1e3c2e5f23a6ba0ab90f4ff"; @@ -140,7 +141,7 @@ public class HpackDecoderTest { String value = "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="; - HpackDecoder decoder = new HpackDecoder(8192); + HpackDecoder decoder = new HpackDecoder(8192, NanoTime::now); String encoded = "8682418cF1E3C2E5F23a6bA0Ab90F4Ff841f0822426173696320515778685a475270626a70766347567549484e6c633246745a513d3d"; byte[] bytes = StringUtil.fromHexString(encoded); byte[] array = new byte[bytes.length + 1]; @@ -162,7 +163,7 @@ public class HpackDecoderTest @Test public void testDecodeHuffmanWithArrayOffset() throws Exception { - HpackDecoder decoder = new HpackDecoder(8192); + HpackDecoder decoder = new HpackDecoder(8192, NanoTime::now); String encoded = "8286418cf1e3c2e5f23a6ba0ab90f4ff84"; byte[] bytes = StringUtil.fromHexString(encoded); @@ -186,7 +187,7 @@ public class HpackDecoderTest String encoded = "886196C361Be940b6a65B6850400B8A00571972e080a62D1Bf5f87497cA589D34d1f9a0f0d0234327690Aa69D29aFcA954D3A5358980Ae112e0f7c880aE152A9A74a6bF3"; ByteBuffer buffer = ByteBuffer.wrap(StringUtil.fromHexString(encoded)); - HpackDecoder decoder = new HpackDecoder(8192); + HpackDecoder decoder = new HpackDecoder(8192, NanoTime::now); MetaData.Response response = (MetaData.Response)decoder.decode(buffer); assertThat(response.getStatus(), is(200)); @@ -204,7 +205,7 @@ public class HpackDecoderTest { String encoded = "203f136687A0E41d139d090760881c6490B2Cd39Ba7f"; ByteBuffer buffer = ByteBuffer.wrap(StringUtil.fromHexString(encoded)); - HpackDecoder decoder = new HpackDecoder(8192); + HpackDecoder decoder = new HpackDecoder(8192, NanoTime::now); MetaData metaData = decoder.decode(buffer); assertThat(metaData.getFields().get(HttpHeader.HOST), is("localhost0")); assertThat(metaData.getFields().get(HttpHeader.COOKIE), is("abcdefghij")); @@ -226,7 +227,7 @@ public class HpackDecoderTest String encoded = "203f136687A0E41d139d090760881c6490B2Cd39Ba7f20"; ByteBuffer buffer = ByteBuffer.wrap(StringUtil.fromHexString(encoded)); - HpackDecoder decoder = new HpackDecoder(8192); + HpackDecoder decoder = new HpackDecoder(8192, NanoTime::now); try { decoder.decode(buffer); @@ -244,7 +245,7 @@ public class HpackDecoderTest String encoded = "3f610f17FfEc02Df3990A190A0D4Ee5b3d2940Ec98Aa4a62D127D29e273a0aA20dEcAa190a503b262d8a2671D4A2672a927aA874988a2471D05510750c951139EdA2452a3a548cAa1aA90bE4B228342864A9E0D450A5474a92992a1aA513395448E3A0Aa17B96cFe3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f14E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F353F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F54f"; ByteBuffer buffer = ByteBuffer.wrap(StringUtil.fromHexString(encoded)); - HpackDecoder decoder = new HpackDecoder(8192); + HpackDecoder decoder = new HpackDecoder(8192, NanoTime::now); decoder.setMaxTableCapacity(128); MetaData metaData = decoder.decode(buffer); @@ -258,7 +259,7 @@ public class HpackDecoderTest String encoded = "BE"; ByteBuffer buffer = ByteBuffer.wrap(StringUtil.fromHexString(encoded)); - HpackDecoder decoder = new HpackDecoder(8192); + HpackDecoder decoder = new HpackDecoder(8192, NanoTime::now); decoder.setMaxTableCapacity(128); try @@ -444,7 +445,7 @@ public class HpackDecoderTest @Test public void testHuffmanEncodedStandard() throws Exception { - HpackDecoder decoder = new HpackDecoder(8192); + HpackDecoder decoder = new HpackDecoder(8192, NanoTime::now); String encoded = "82868441" + "83" + "49509F"; ByteBuffer buffer = ByteBuffer.wrap(StringUtil.fromHexString(encoded)); @@ -462,7 +463,7 @@ public class HpackDecoderTest @Test public void testHuffmanEncodedExtraPadding() { - HpackDecoder decoder = new HpackDecoder(8192); + HpackDecoder decoder = new HpackDecoder(8192, NanoTime::now); String encoded = "82868441" + "84" + "49509FFF"; ByteBuffer buffer = ByteBuffer.wrap(StringUtil.fromHexString(encoded)); @@ -474,7 +475,7 @@ public class HpackDecoderTest @Test public void testHuffmanEncodedZeroPadding() { - HpackDecoder decoder = new HpackDecoder(8192); + HpackDecoder decoder = new HpackDecoder(8192, NanoTime::now); String encoded = "82868441" + "83" + "495090"; ByteBuffer buffer = ByteBuffer.wrap(StringUtil.fromHexString(encoded)); @@ -487,7 +488,7 @@ public class HpackDecoderTest @Test public void testHuffmanEncodedWithEOS() { - HpackDecoder decoder = new HpackDecoder(8192); + HpackDecoder decoder = new HpackDecoder(8192, NanoTime::now); String encoded = "82868441" + "87" + "497FFFFFFF427F"; ByteBuffer buffer = ByteBuffer.wrap(StringUtil.fromHexString(encoded)); @@ -499,7 +500,7 @@ public class HpackDecoderTest @Test public void testHuffmanEncodedOneIncompleteOctet() { - HpackDecoder decoder = new HpackDecoder(8192); + HpackDecoder decoder = new HpackDecoder(8192, NanoTime::now); String encoded = "82868441" + "81" + "FE"; ByteBuffer buffer = ByteBuffer.wrap(StringUtil.fromHexString(encoded)); @@ -511,7 +512,7 @@ public class HpackDecoderTest @Test public void testHuffmanEncodedTwoIncompleteOctet() { - HpackDecoder decoder = new HpackDecoder(8192); + HpackDecoder decoder = new HpackDecoder(8192, NanoTime::now); String encoded = "82868441" + "82" + "FFFE"; ByteBuffer buffer = ByteBuffer.wrap(StringUtil.fromHexString(encoded)); @@ -523,7 +524,7 @@ public class HpackDecoderTest @Test public void testZeroLengthName() { - HpackDecoder decoder = new HpackDecoder(8192); + HpackDecoder decoder = new HpackDecoder(8192, NanoTime::now); String encoded = "00000130"; ByteBuffer buffer = ByteBuffer.wrap(StringUtil.fromHexString(encoded)); @@ -534,7 +535,7 @@ public class HpackDecoderTest @Test public void testZeroLengthValue() throws Exception { - HpackDecoder decoder = new HpackDecoder(8192); + HpackDecoder decoder = new HpackDecoder(8192, NanoTime::now); String encoded = "00016800"; ByteBuffer buffer = ByteBuffer.wrap(StringUtil.fromHexString(encoded)); @@ -546,7 +547,7 @@ public class HpackDecoderTest @Test public void testUpperCaseName() { - HpackDecoder decoder = new HpackDecoder(8192); + HpackDecoder decoder = new HpackDecoder(8192, NanoTime::now); String encoded = "0001480130"; ByteBuffer buffer = ByteBuffer.wrap(StringUtil.fromHexString(encoded)); @@ -557,7 +558,7 @@ public class HpackDecoderTest @Test public void testWhiteSpaceName() { - HpackDecoder decoder = new HpackDecoder(8192); + HpackDecoder decoder = new HpackDecoder(8192, NanoTime::now); String encoded = "0001200130"; ByteBuffer buffer = ByteBuffer.wrap(StringUtil.fromHexString(encoded)); diff --git a/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackTest.java b/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackTest.java index e90ebf9a255..bcc63b1bf8d 100644 --- a/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackTest.java +++ b/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackTest.java @@ -24,6 +24,7 @@ import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http.MetaData.Response; import org.eclipse.jetty.http.PreEncodedHttpField; import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.NanoTime; import org.junit.jupiter.api.Test; import static org.hamcrest.MatcherAssert.assertThat; @@ -43,7 +44,7 @@ public class HpackTest public void encodeDecodeResponseTest() throws Exception { HpackEncoder encoder = new HpackEncoder(); - HpackDecoder decoder = new HpackDecoder(8192); + HpackDecoder decoder = new HpackDecoder(8192, NanoTime::now); ByteBuffer buffer = BufferUtil.allocateDirect(16 * 1024); HttpFields.Mutable fields0 = HttpFields.build() @@ -98,7 +99,7 @@ public class HpackTest public void encodeDecodeTooLargeTest() throws Exception { HpackEncoder encoder = new HpackEncoder(); - HpackDecoder decoder = new HpackDecoder(164); + HpackDecoder decoder = new HpackDecoder(164, NanoTime::now); ByteBuffer buffer = BufferUtil.allocateDirect(16 * 1024); HttpFields fields0 = HttpFields.build() @@ -158,7 +159,7 @@ public class HpackTest @Test public void evictReferencedFieldTest() throws Exception { - HpackDecoder decoder = new HpackDecoder(1024); + HpackDecoder decoder = new HpackDecoder(1024, NanoTime::now); decoder.setMaxTableCapacity(200); HpackEncoder encoder = new HpackEncoder(); encoder.setMaxTableCapacity(decoder.getMaxTableCapacity()); @@ -205,7 +206,7 @@ public class HpackTest public void testHopHeadersAreRemoved() throws Exception { HpackEncoder encoder = new HpackEncoder(); - HpackDecoder decoder = new HpackDecoder(16384); + HpackDecoder decoder = new HpackDecoder(16384, NanoTime::now); HttpFields input = HttpFields.build() .add(HttpHeader.ACCEPT, "*") @@ -232,7 +233,7 @@ public class HpackTest public void testTETrailers() throws Exception { HpackEncoder encoder = new HpackEncoder(); - HpackDecoder decoder = new HpackDecoder(16384); + HpackDecoder decoder = new HpackDecoder(16384, NanoTime::now); String teValue = "trailers"; String trailerValue = "Custom"; @@ -257,7 +258,7 @@ public class HpackTest public void testColonHeaders() throws Exception { HpackEncoder encoder = new HpackEncoder(); - HpackDecoder decoder = new HpackDecoder(16384); + HpackDecoder decoder = new HpackDecoder(16384, NanoTime::now); HttpFields input = HttpFields.build() .add(":status", "200") diff --git a/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/parser/MessageParser.java b/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/parser/MessageParser.java index f06ff0d3e4b..4e035c6529b 100644 --- a/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/parser/MessageParser.java +++ b/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/parser/MessageParser.java @@ -23,6 +23,7 @@ import org.eclipse.jetty.http3.internal.Grease; import org.eclipse.jetty.http3.internal.HTTP3ErrorCode; import org.eclipse.jetty.http3.qpack.QpackDecoder; import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.NanoTime; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -43,12 +44,15 @@ public class MessageParser private final BooleanSupplier isLast; private BodyParser unknownBodyParser; private State state = State.HEADER; - protected boolean dataMode; + private boolean dataMode; + private long beginNanoTime; + private boolean beginNanoTimeStored; public MessageParser(ParserListener listener, QpackDecoder decoder, long streamId, BooleanSupplier isLast) { this.listener = listener; this.decoder = decoder; + decoder.setBeginNanoTimeSupplier(this::getBeginNanoTime); this.streamId = streamId; this.isLast = isLast; } @@ -66,6 +70,21 @@ public class MessageParser { headerParser.reset(); state = State.HEADER; + beginNanoTimeStored = false; + } + + private void storeBeginNanoTime() + { + if (!beginNanoTimeStored) + { + beginNanoTime = NanoTime.now(); + beginNanoTimeStored = true; + } + } + + private long getBeginNanoTime() + { + return beginNanoTime; } public ParserListener getListener() @@ -101,6 +120,7 @@ public class MessageParser { case HEADER: { + storeBeginNanoTime(); if (headerParser.parse(buffer)) { state = State.BODY; diff --git a/jetty-http3/http3-common/src/test/java/org/eclipse/jetty/http3/internal/DataGenerateParseTest.java b/jetty-http3/http3-common/src/test/java/org/eclipse/jetty/http3/internal/DataGenerateParseTest.java index 6114db0bd3f..31f36e22f0e 100644 --- a/jetty-http3/http3-common/src/test/java/org/eclipse/jetty/http3/internal/DataGenerateParseTest.java +++ b/jetty-http3/http3-common/src/test/java/org/eclipse/jetty/http3/internal/DataGenerateParseTest.java @@ -23,9 +23,11 @@ import org.eclipse.jetty.http3.frames.DataFrame; import org.eclipse.jetty.http3.internal.generator.MessageGenerator; import org.eclipse.jetty.http3.internal.parser.MessageParser; import org.eclipse.jetty.http3.internal.parser.ParserListener; +import org.eclipse.jetty.http3.qpack.QpackDecoder; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.NullByteBufferPool; import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.NanoTime; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertArrayEquals; @@ -58,6 +60,8 @@ public class DataGenerateParseTest new MessageGenerator(null, true).generate(lease, 0, input, null); List frames = new ArrayList<>(); + QpackDecoder decoder = new QpackDecoder(instructions -> {}); + decoder.setBeginNanoTimeSupplier(NanoTime::now); MessageParser parser = new MessageParser(new ParserListener() { @Override @@ -65,7 +69,7 @@ public class DataGenerateParseTest { frames.add(frame); } - }, null, 13, () -> true); + }, decoder, 13, () -> true); parser.init(UnaryOperator.identity()); for (ByteBuffer buffer : lease.getByteBuffers()) { diff --git a/jetty-http3/http3-common/src/test/java/org/eclipse/jetty/http3/internal/HeadersGenerateParseTest.java b/jetty-http3/http3-common/src/test/java/org/eclipse/jetty/http3/internal/HeadersGenerateParseTest.java index abe1637c11f..da080d2d932 100644 --- a/jetty-http3/http3-common/src/test/java/org/eclipse/jetty/http3/internal/HeadersGenerateParseTest.java +++ b/jetty-http3/http3-common/src/test/java/org/eclipse/jetty/http3/internal/HeadersGenerateParseTest.java @@ -31,6 +31,7 @@ import org.eclipse.jetty.http3.qpack.QpackDecoder; import org.eclipse.jetty.http3.qpack.QpackEncoder; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.NullByteBufferPool; +import org.eclipse.jetty.util.NanoTime; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -54,6 +55,7 @@ public class HeadersGenerateParseTest QpackDecoder decoder = new QpackDecoder(instructions -> {}); decoder.setMaxHeadersSize(4 * 1024); + decoder.setBeginNanoTimeSupplier(NanoTime::now); List frames = new ArrayList<>(); MessageParser parser = new MessageParser(new ParserListener() { diff --git a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/QpackDecoder.java b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/QpackDecoder.java index ebed4579154..f0b2fba0c71 100644 --- a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/QpackDecoder.java +++ b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/QpackDecoder.java @@ -21,6 +21,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.LongSupplier; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.MetaData; @@ -59,6 +60,7 @@ public class QpackDecoder implements Dumpable private int _maxHeadersSize; private int _maxBlockedStreams; private int _maxTableCapacity; + private LongSupplier _beginNanoTimeSupplier; private static class MetaDataNotification { @@ -103,6 +105,11 @@ public class QpackDecoder implements Dumpable return _maxHeadersSize; } + public void setBeginNanoTimeSupplier(LongSupplier beginNanoTimeSupplier) + { + _beginNanoTimeSupplier = beginNanoTimeSupplier; + } + /** * @param maxHeadersSize The maximum allowed size of a headers block, expressed as total of all name and value characters, plus 32 per field */ @@ -180,7 +187,7 @@ public class QpackDecoder implements Dumpable { // Parse the buffer into an Encoded Field Section. int base = signBit ? requiredInsertCount - deltaBase - 1 : requiredInsertCount + deltaBase; - EncodedFieldSection encodedFieldSection = new EncodedFieldSection(streamId, handler, requiredInsertCount, base, buffer); + EncodedFieldSection encodedFieldSection = new EncodedFieldSection(streamId, handler, requiredInsertCount, base, buffer, _beginNanoTimeSupplier.getAsLong()); // Decode it straight away if we can, otherwise add it to the list of EncodedFieldSections. if (requiredInsertCount <= insertCount) diff --git a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/metadata/MetaDataBuilder.java b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/metadata/MetaDataBuilder.java index 014aa749d6d..47c3024cac3 100644 --- a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/metadata/MetaDataBuilder.java +++ b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/metadata/MetaDataBuilder.java @@ -22,6 +22,7 @@ import org.eclipse.jetty.http.HttpScheme; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http3.qpack.QpackException; +import org.eclipse.jetty.util.NanoTime; import static org.eclipse.jetty.http3.qpack.QpackException.H3_GENERAL_PROTOCOL_ERROR; @@ -40,6 +41,7 @@ public class MetaDataBuilder private QpackException.StreamException _streamException; private boolean _request; private boolean _response; + private long _beginNanoTime = Long.MIN_VALUE; /** * @param maxHeadersSize The maximum size of the headers, expressed as total name and value characters. @@ -59,6 +61,13 @@ public class MetaDataBuilder return _maxSize; } + public void setBeginNanoTime(long beginNanoTime) + { + if (beginNanoTime == Long.MIN_VALUE) + beginNanoTime++; + _beginNanoTime = beginNanoTime; + } + /** * Get the size. * @@ -246,10 +255,13 @@ public class MetaDataBuilder if (_path == null) throw new QpackException.StreamException(H3_GENERAL_PROTOCOL_ERROR, "No Path"); } + long nanoTime = _beginNanoTime == Long.MIN_VALUE ? NanoTime.now() : _beginNanoTime; + _beginNanoTime = Long.MIN_VALUE; if (isConnect) - return new MetaData.ConnectRequest(_scheme, _authority, _path, fields, _protocol); + return new MetaData.ConnectRequest(nanoTime, _scheme, _authority, _path, fields, _protocol); else return new MetaData.Request( + nanoTime, _method, _scheme.asString(), _authority, diff --git a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/parser/EncodedFieldSection.java b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/parser/EncodedFieldSection.java index ddcb939dacf..1b900bbe984 100644 --- a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/parser/EncodedFieldSection.java +++ b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/parser/EncodedFieldSection.java @@ -44,13 +44,15 @@ public class EncodedFieldSection private final int _requiredInsertCount; private final int _base; private final QpackDecoder.Handler _handler; + private final long _beginNanoTime; - public EncodedFieldSection(long streamId, QpackDecoder.Handler handler, int requiredInsertCount, int base, ByteBuffer content) throws QpackException + public EncodedFieldSection(long streamId, QpackDecoder.Handler handler, int requiredInsertCount, int base, ByteBuffer content, long beginNanoTime) throws QpackException { _streamId = streamId; _requiredInsertCount = requiredInsertCount; _base = base; _handler = handler; + _beginNanoTime = beginNanoTime; try { @@ -104,6 +106,7 @@ public class EncodedFieldSection HttpField decodedField = encodedField.decode(context); metaDataBuilder.emit(decodedField); } + metaDataBuilder.setBeginNanoTime(_beginNanoTime); return metaDataBuilder.build(); } diff --git a/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/BlockedStreamsTest.java b/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/BlockedStreamsTest.java index e32a98778b5..edae7d0a2b4 100644 --- a/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/BlockedStreamsTest.java +++ b/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/BlockedStreamsTest.java @@ -24,6 +24,7 @@ import org.eclipse.jetty.http3.qpack.internal.instruction.LiteralNameEntryInstru import org.eclipse.jetty.http3.qpack.internal.instruction.SectionAcknowledgmentInstruction; import org.eclipse.jetty.http3.qpack.internal.instruction.SetCapacityInstruction; import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.NanoTime; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -56,6 +57,7 @@ public class BlockedStreamsTest _decoderHandler = new TestDecoderHandler(); _encoder = new QpackEncoder(_encoderHandler, MAX_BLOCKED_STREAMS); _decoder = new QpackDecoder(_decoderHandler, MAX_HEADER_SIZE); + _decoder.setBeginNanoTimeSupplier(NanoTime::now); } @Test diff --git a/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/EncodeDecodeTest.java b/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/EncodeDecodeTest.java index 1bebb6bb758..4f1cf20e6ae 100644 --- a/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/EncodeDecodeTest.java +++ b/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/EncodeDecodeTest.java @@ -28,6 +28,7 @@ import org.eclipse.jetty.http3.qpack.internal.instruction.SetCapacityInstruction import org.eclipse.jetty.http3.qpack.internal.parser.DecoderInstructionParser; import org.eclipse.jetty.http3.qpack.internal.parser.EncoderInstructionParser; import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.NanoTime; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -65,6 +66,7 @@ public class EncodeDecodeTest } }; _decoder = new QpackDecoder(_decoderHandler, MAX_HEADER_SIZE); + _decoder.setBeginNanoTimeSupplier(NanoTime::now); _encoderInstructionParser = new EncoderInstructionParser(new EncoderParserDebugHandler(_encoder)); _decoderInstructionParser = new DecoderInstructionParser(new DecoderParserDebugHandler(_decoder)); diff --git a/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/EvictionTest.java b/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/EvictionTest.java index 14f566cd438..d8a6f2c713e 100644 --- a/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/EvictionTest.java +++ b/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/EvictionTest.java @@ -20,6 +20,7 @@ import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; +import org.eclipse.jetty.util.NanoTime; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -40,6 +41,7 @@ public class EvictionTest _decoder = new QpackDecoder(_decoderHandler); _decoder.setMaxHeadersSize(1024); _decoder.setMaxTableCapacity(4 * 1024); + _decoder.setBeginNanoTimeSupplier(NanoTime::now); _encoder = new QpackEncoder(_encoderHandler) { diff --git a/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/SectionAcknowledgmentTest.java b/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/SectionAcknowledgmentTest.java index 7f149dd7f42..718ef18f3e3 100644 --- a/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/SectionAcknowledgmentTest.java +++ b/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/SectionAcknowledgmentTest.java @@ -18,6 +18,7 @@ import java.nio.ByteBuffer; import org.eclipse.jetty.http3.qpack.QpackException.SessionException; import org.eclipse.jetty.http3.qpack.internal.instruction.SectionAcknowledgmentInstruction; import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.NanoTime; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -48,6 +49,7 @@ public class SectionAcknowledgmentTest _decoderHandler = new TestDecoderHandler(); _encoder = new QpackEncoder(_encoderHandler, MAX_BLOCKED_STREAMS); _decoder = new QpackDecoder(_decoderHandler, MAX_HEADER_SIZE); + _decoder.setBeginNanoTimeSupplier(NanoTime::now); } @Test