diff --git a/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/parser/Parser.java b/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/parser/Parser.java index 2973005ea8af..8233fd04f6bf 100644 --- a/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/parser/Parser.java +++ b/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/parser/Parser.java @@ -66,7 +66,7 @@ public Parser(ByteBufferPool bufferPool, int maxHeaderSize, RateControl rateCont { this.bufferPool = bufferPool; this.headerParser = new HeaderParser(rateControl == null ? RateControl.NO_RATE_CONTROL : rateControl); - this.hpackDecoder = new HpackDecoder(maxHeaderSize, this::loadAndClearBeginNanoTime); + this.hpackDecoder = new HpackDecoder(maxHeaderSize, this::getBeginNanoTime); this.bodyParsers = new BodyParser[FrameType.values().length]; } @@ -104,16 +104,18 @@ private void reset() { headerParser.reset(); state = State.HEADER; - nanoTimeStored = false; } - private long loadAndClearBeginNanoTime() + public long getBeginNanoTime() { - long beginNanoTime = this.beginNanoTime; - nanoTimeStored = false; return beginNanoTime; } + private void clearBeginNanoTime() + { + nanoTimeStored = false; + } + private void storeBeginNanoTime() { if (!nanoTimeStored) @@ -140,11 +142,11 @@ public void parse(ByteBuffer buffer) { while (true) { - storeBeginNanoTime(); switch (state) { case HEADER: { + storeBeginNanoTime(); if (!parseHeader(buffer)) return; break; @@ -153,6 +155,8 @@ public void parse(ByteBuffer buffer) { if (!parseBody(buffer)) return; + if (!continuation) + clearBeginNanoTime(); break; } default: diff --git a/jetty-core/jetty-http2/jetty-http2-common/src/test/java/org/eclipse/jetty/http2/frames/ContinuationParseTest.java b/jetty-core/jetty-http2/jetty-http2-common/src/test/java/org/eclipse/jetty/http2/frames/ContinuationParseTest.java index c98765fa97b1..e0ae2c7a2318 100644 --- a/jetty-core/jetty-http2/jetty-http2-common/src/test/java/org/eclipse/jetty/http2/frames/ContinuationParseTest.java +++ b/jetty-core/jetty-http2/jetty-http2-common/src/test/java/org/eclipse/jetty/http2/frames/ContinuationParseTest.java @@ -161,6 +161,107 @@ public void onConnectionFailure(int error, String reason) } } + @Test + public void testBeginNanoTime() throws Exception + { + ArrayByteBufferPool.Tracking bufferPool = new ArrayByteBufferPool.Tracking(); + HeadersGenerator generator = new HeadersGenerator(new HeaderGenerator(bufferPool), new HpackEncoder()); + + final List frames = new ArrayList<>(); + Parser parser = new Parser(bufferPool, 8192); + parser.init(new Parser.Listener() + { + @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.Accumulator accumulator = new ByteBufferPool.Accumulator(); + generator.generateHeaders(accumulator, streamId, metaData, null, true); + + List byteBuffers = accumulator.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()); + + byteBuffers = accumulator.getByteBuffers(); + 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)); + + accumulator.release(); + + 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.getHttpURI(), request.getHttpURI()); + for (int j = 0; j < fields.size(); ++j) + { + HttpField field = fields.getField(j); + assertTrue(request.getHttpFields().contains(field)); + } + PriorityFrame priority = frame.getPriority(); + assertNull(priority); + assertEquals(beginNanoTime, request.getBeginNanoTime()); + + assertEquals(0, bufferPool.getLeaks().size(), bufferPool.dumpLeaks()); + } + @Test public void testLargeHeadersBlock() throws Exception {