You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by ma...@apache.org on 2015/06/04 13:39:51 UTC

svn commit: r1683524 - in /tomcat/trunk/java/org/apache/coyote/http2: Http2Parser.java LocalStrings.properties

Author: markt
Date: Thu Jun  4 11:39:51 2015
New Revision: 1683524

URL: http://svn.apache.org/r1683524
Log:
Add support for continuation frames, refactoring the now common header block processing into a separate method.

Modified:
    tomcat/trunk/java/org/apache/coyote/http2/Http2Parser.java
    tomcat/trunk/java/org/apache/coyote/http2/LocalStrings.properties

Modified: tomcat/trunk/java/org/apache/coyote/http2/Http2Parser.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http2/Http2Parser.java?rev=1683524&r1=1683523&r2=1683524&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/coyote/http2/Http2Parser.java (original)
+++ tomcat/trunk/java/org/apache/coyote/http2/Http2Parser.java Thu Jun  4 11:39:51 2015
@@ -40,6 +40,7 @@ class Http2Parser {
     private static final int FRAME_TYPE_PING = 6;
     private static final int FRAME_TYPE_GOAWAY = 7;
     private static final int FRAME_TYPE_WINDOW_UPDATE = 8;
+    private static final int FRAME_TYPE_CONTINUATION = 9;
 
     private final String connectionId;
     private final Input input;
@@ -48,6 +49,8 @@ class Http2Parser {
 
     private volatile HpackDecoder hpackDecoder;
     private final ByteBuffer headerReadBuffer = ByteBuffer.allocate(1024);
+    private volatile int headersCurrentStream = -1;
+    private volatile boolean headersEndStream = false;
 
     private volatile boolean readPreface = false;
     private volatile int maxPayloadSize = ConnectionSettings.DEFAULT_MAX_FRAME_SIZE;
@@ -98,6 +101,19 @@ class Http2Parser {
                     streamId, ErrorCode.FRAME_SIZE_ERROR);
         }
 
+        if (headersCurrentStream != -1) {
+            if (headersCurrentStream != streamId) {
+                throw new Http2Exception(sm.getString("http2Parser.headers.wrongStream",
+                        connectionId, Integer.toString(headersCurrentStream),
+                        Integer.toString(streamId)), streamId, ErrorCode.COMPRESSION_ERROR);
+            }
+            if (frameType != FRAME_TYPE_CONTINUATION) {
+                throw new Http2Exception(sm.getString("http2Parser.headers.wrongFrameType",
+                        connectionId, Integer.toString(headersCurrentStream),
+                        Integer.toString(frameType)), streamId, ErrorCode.COMPRESSION_ERROR);
+            }
+        }
+
         switch (frameType) {
         case FRAME_TYPE_DATA:
             readDataFrame(streamId, flags, payloadSize);
@@ -120,6 +136,9 @@ class Http2Parser {
         case FRAME_TYPE_WINDOW_UPDATE:
             readWindowUpdateFrame(streamId, flags, payloadSize);
             break;
+        case FRAME_TYPE_CONTINUATION:
+            readContinuationFrame(streamId, flags, payloadSize);
+            break;
         // TODO: Missing types
         default:
             readUnknownFrame(streamId, frameType, flags, payloadSize);
@@ -145,10 +164,9 @@ class Http2Parser {
         // Process the Stream
         int padLength = 0;
 
-        boolean endOfStream = (flags & 0x01) > 0;
-        boolean padding = (flags & 0x08) > 0;
+        boolean endOfStream = Flags.isEndOfStream(flags);
 
-        if (padding) {
+        if (Flags.hasPadding(flags)) {
             byte[] b = new byte[1];
             input.fill(true, b);
             padLength = b[0] & 0xFF;
@@ -187,18 +205,14 @@ class Http2Parser {
                     0, ErrorCode.PROTOCOL_ERROR);
         }
 
-        // TODO Handle end of headers flag
-        // TODO Handle end of stream flag
-        // TODO Handle continuation frames
-
         if (hpackDecoder == null) {
             hpackDecoder = output.getHpackDecoder();
         }
         hpackDecoder.setHeaderEmitter(output.headersStart(streamId));
 
         int padLength = 0;
-        boolean padding = (flags & 0x08) > 0;
-        boolean priority = (flags & 0x20) > 0;
+        boolean padding = Flags.hasPadding(flags);
+        boolean priority = Flags.hasPriority(flags);
         int optionalLen = 0;
         if (padding) {
             optionalLen = 1;
@@ -223,33 +237,25 @@ class Http2Parser {
             payloadSize -= optionalLen;
         }
 
-        while (payloadSize > 0) {
-            int toRead = Math.min(headerReadBuffer.remaining(), payloadSize);
-            // headerReadBuffer in write mode
-            input.fill(true, headerReadBuffer, toRead);
-            // switch to read mode
-            headerReadBuffer.flip();
-            try {
-                hpackDecoder.decode(headerReadBuffer);
-            } catch (HpackException hpe) {
-                throw new Http2Exception(
-                        sm.getString("http2Parser.processFrameHeaders.decodingFailed"),
-                        0, ErrorCode.COMPRESSION_ERROR);
-            }
-            // switches to write mode
-            headerReadBuffer.compact();
-            payloadSize -= toRead;
-        }
-        // Should be empty at this point
-        if (headerReadBuffer.position() > 0) {
-            throw new Http2Exception(
-                    sm.getString("http2Parser.processFrameHeaders.decodingDataLeft"),
-                    0, ErrorCode.COMPRESSION_ERROR);
-        }
+        boolean endOfHeaders = Flags.isEndOfHeaders(flags);
+
+        readHeaderBlock(payloadSize, endOfHeaders);
 
         swallow(padLength);
 
-        output.headersEnd(streamId);
+        if (endOfHeaders) {
+            output.headersEnd(streamId);
+        } else {
+            headersCurrentStream = streamId;
+        }
+
+        if (Flags.isEndOfStream(flags)) {
+            if (headersCurrentStream == -1) {
+                output.endOfStream(streamId);
+            } else {
+                headersEndStream = true;
+            }
+        }
     }
 
 
@@ -296,7 +302,7 @@ class Http2Parser {
             throw new Http2Exception(sm.getString("http2Parser.processFrameSettings.invalidPayloadSize",
                     Integer.toString(payloadSize)), 0, ErrorCode.FRAME_SIZE_ERROR);
         }
-        boolean ack = (flags & 0x1) != 0;
+        boolean ack = Flags.isAck(flags);
         if (payloadSize > 0 && ack) {
             throw new Http2Exception(sm.getString("http2Parser.processFrameSettings.ackWithNonZeroPayload"),
                     0, ErrorCode.FRAME_SIZE_ERROR);
@@ -332,7 +338,7 @@ class Http2Parser {
             throw new Http2Exception(sm.getString("http2Parser.processFramePing.invalidPayloadSize",
                     Integer.toString(payloadSize)), 0, ErrorCode.FRAME_SIZE_ERROR);
         }
-        if ((flags & 0x1) == 0) {
+        if (Flags.isAck(flags)) {
             // Read the payload
             byte[] payload = new byte[8];
             input.fill(true, payload);
@@ -407,6 +413,68 @@ class Http2Parser {
     }
 
 
+    private void readContinuationFrame(int streamId, int flags, int payloadSize)
+            throws IOException {
+        if (log.isDebugEnabled()) {
+            log.debug(sm.getString("http2Parser.processFrame", connectionId,
+                    Integer.toString(streamId), Integer.toString(flags),
+                    Integer.toString(payloadSize)));
+        }
+
+        if (streamId == 0) {
+            throw new Http2Exception(sm.getString(
+                    "http2Parser.processFrameContinuation.invalidStream", connectionId),
+                    0, ErrorCode.PROTOCOL_ERROR);
+        }
+
+        if (headersCurrentStream == -1) {
+            // No headers to continue
+            throw new Http2Exception(sm.getString(
+                    "http2Parser.processFrameContinuation.notExpected", connectionId,
+                    Integer.toString(streamId)), 0, ErrorCode.PROTOCOL_ERROR);
+        }
+
+        boolean endOfHeaders = Flags.isEndOfHeaders(flags);
+        readHeaderBlock(payloadSize, endOfHeaders);
+
+        if (endOfHeaders) {
+            output.headersEnd(streamId);
+            headersCurrentStream = -1;
+            if (headersEndStream) {
+                output.endOfStream(streamId);
+                headersEndStream = false;
+            }
+        }
+    }
+
+
+    private void readHeaderBlock(int payloadSize, boolean endOfHeaders) throws IOException {
+        while (payloadSize > 0) {
+            int toRead = Math.min(headerReadBuffer.remaining(), payloadSize);
+            // headerReadBuffer in write mode
+            input.fill(true, headerReadBuffer, toRead);
+            // switch to read mode
+            headerReadBuffer.flip();
+            try {
+                hpackDecoder.decode(headerReadBuffer);
+            } catch (HpackException hpe) {
+                throw new Http2Exception(
+                        sm.getString("http2Parser.processFrameHeaders.decodingFailed"),
+                        0, ErrorCode.COMPRESSION_ERROR);
+            }
+            // switches to write mode
+            headerReadBuffer.compact();
+            payloadSize -= toRead;
+        }
+
+        if (headerReadBuffer.position() > 0 && endOfHeaders) {
+            throw new Http2Exception(
+                    sm.getString("http2Parser.processFrameHeaders.decodingDataLeft"),
+                    0, ErrorCode.COMPRESSION_ERROR);
+        }
+    }
+
+
     private void readUnknownFrame(int streamId, int frameType, int flags, int payloadSize)
             throws IOException {
         output.swallow(streamId, frameType, flags, payloadSize);

Modified: tomcat/trunk/java/org/apache/coyote/http2/LocalStrings.properties
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http2/LocalStrings.properties?rev=1683524&r1=1683523&r2=1683524&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/coyote/http2/LocalStrings.properties (original)
+++ tomcat/trunk/java/org/apache/coyote/http2/LocalStrings.properties Thu Jun  4 11:39:51 2015
@@ -32,11 +32,15 @@ hpackdecoder.zeroNotValidHeaderTableInde
 
 hpackhuffman.huffmanEncodedHpackValueDidNotEndWithEOS=Huffman encoded value in HPACK headers did not end with EOS padding
 
+http2Parser.headers.wrongFrameType=Connection [{0}], headers in progress for stream [{1}] but a frame of type [{2}] was received
+http2Parser.headers.wrongStream=Connection [{0}], headers in progress for stream [{1}] but a frame for stream [{2}] was received
 http2Parser.payloadTooBig=The payload is [{0}] bytes long but the maximum frame size is [{1}]
 http2Parser.preface.invalid=Invalid connection preface [{0}] presented
 http2Parser.preface.io=Unable to read connection preface
 http2Parser.processFrame=Connection [{0}], Stream [{1}], Flags [{2}], Payload size [{3}]
 http2Parser.processFrame.unexpectedType=Expected frame type [{0}] but received frame type [{1}]
+http2Parser.processFrameContinuation.invalidStream=Connection [{0}], Continuation frame received for stream [0]
+http2Parser.processFrameContinuation.notExpected=Connection [{0}], Continuation frame received for stream [{1}] when no headers were in progress
 http2Parser.processFrameData.invalidStream=Data frame received for stream [0]
 http2Parser.processFrameGoaway.invalidStream=Goaway frame received for stream [{0}]
 http2Parser.processFrameGoaway.payloadTooSmall=Connection [{0}]: Goaway payload size was [{1}] which is less than the minimum 8



---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org