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/05/13 00:29:45 UTC

svn commit: r1679116 - in /tomcat/trunk/java/org/apache/coyote/http2: Http2Exception.java Http2UpgradeHandler.java

Author: markt
Date: Tue May 12 22:29:45 2015
New Revision: 1679116

URL: http://svn.apache.org/r1679116
Log:
Start to process the initial settings frame sent by the client.

Added:
    tomcat/trunk/java/org/apache/coyote/http2/Http2Exception.java   (with props)
Modified:
    tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java

Added: tomcat/trunk/java/org/apache/coyote/http2/Http2Exception.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http2/Http2Exception.java?rev=1679116&view=auto
==============================================================================
--- tomcat/trunk/java/org/apache/coyote/http2/Http2Exception.java (added)
+++ tomcat/trunk/java/org/apache/coyote/http2/Http2Exception.java Tue May 12 22:29:45 2015
@@ -0,0 +1,60 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.coyote.http2;
+
+import java.io.IOException;
+
+public class Http2Exception extends IOException {
+
+    private static final long serialVersionUID = 1L;
+
+    public static final byte[] NO_ERROR = { 0x00, 0x00, 0x00, 0x00 };
+    public static final byte[] PROTOCOL_ERROR = { 0x00, 0x00, 0x00, 0x01 };
+    public static final byte[] INTERNAL_ERROR = { 0x00, 0x00, 0x00, 0x02 };
+    public static final byte[] FLOW_CONTROL_ERROR = { 0x00, 0x00, 0x00, 0x03 };
+    public static final byte[] SETTINGS_TIMEOUT = { 0x00, 0x00, 0x00, 0x04 };
+    public static final byte[] STREAM_CLOSED = { 0x00, 0x00, 0x00, 0x05 };
+    public static final byte[] FRAME_SIZE_ERROR = { 0x00, 0x00, 0x00, 0x06};
+    public static final byte[] REFUSED_STREAM = { 0x00, 0x00, 0x00, 0x07};
+    public static final byte[] CANCEL = { 0x00, 0x00, 0x00, 0x08};
+    public static final byte[] COMPRESSION_ERROR= { 0x00, 0x00, 0x00, 0x09};
+    public static final byte[] CONNECT_ERROR = { 0x00, 0x00, 0x00, 0x0a};
+    public static final byte[] ENHANCE_YOUR_CALM = { 0x00, 0x00, 0x00, 0x0b};
+    public static final byte[] INADEQUATE_SECURITY = { 0x00, 0x00, 0x00, 0x0c};
+    public static final byte[]  HTTP_1_1_REQUIRED = { 0x00, 0x00, 0x00, 0x0d};
+
+
+    private final int streamId;
+    private final byte[] errorCode;
+
+
+    public Http2Exception(String msg, int streamId, byte[] errorCode) {
+        super(msg);
+        this.streamId = streamId;
+        this.errorCode = errorCode;
+    }
+
+
+    public int getStreamId() {
+        return streamId;
+    }
+
+
+    public byte[] getErrorCode() {
+        return errorCode;
+    }
+}

Propchange: tomcat/trunk/java/org/apache/coyote/http2/Http2Exception.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java?rev=1679116&r1=1679115&r2=1679116&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java (original)
+++ tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java Tue May 12 22:29:45 2015
@@ -16,6 +16,7 @@
  */
 package org.apache.coyote.http2;
 
+import java.io.EOFException;
 import java.io.IOException;
 
 import javax.servlet.http.WebConnection;
@@ -46,11 +47,16 @@ public class Http2UpgradeHandler impleme
     private static final Log log = LogFactory.getLog(Http2UpgradeHandler.class);
     private static final StringManager sm = StringManager.getManager(Http2UpgradeHandler.class);
 
+    private static final int FRAME_SETTINGS = 4;
+
     private volatile SocketWrapperBase<?> socketWrapper;
     private volatile boolean initialized = false;
     private volatile ConnectionPrefaceParser connectionPrefaceParser =
             new ConnectionPrefaceParser();
-    private volatile boolean readFirstFrame = false;
+    private volatile boolean firstFrame = true;
+    private volatile boolean open = true;
+
+    private volatile int settingsMaxFrameSize = 16 * 1024;
 
 
     @Override
@@ -89,10 +95,19 @@ public class Http2UpgradeHandler impleme
             }
             connectionPrefaceParser = null;
 
-            while (processFrame()) {
+            try {
+                while (processFrame()) {
+                }
+                // TODO Catch the Http2Exception and reset the stream / close
+                // the connection as appropriate
+            } catch (IOException ioe) {
+                log.error("TODO: i18n - Frame processing error", ioe);
+                open = false;
             }
 
-            // TODO: CLOSED (GO_AWAY + no open streams apart from 0?) vs LONG
+            if (open) {
+                return SocketState.LONG;
+            }
             break;
 
         case OPEN_WRITE:
@@ -124,11 +139,126 @@ public class Http2UpgradeHandler impleme
     }
 
 
-    private boolean processFrame() {
+    private boolean processFrame() throws IOException {
+        // TODO: Consider refactoring and making this a field to reduce GC.
+        byte[] frameHeader = new byte[9];
+        if (!getFrameHeader(frameHeader)) {
+            return false;
+        }
+
+        int frameType = getFrameType(frameHeader);
+        int streamId = getStreamIdentifier(frameHeader);
+        int payloadSize = getPayloadSize(streamId, frameHeader);
+
+        switch (frameType) {
+        case FRAME_SETTINGS:
+            processFrameSettings(streamId, payloadSize);
+            break;
+        default:
+            // Unknown frame type.
+            processFrameUnknown(streamId, frameType, payloadSize);
+        }
         return false;
     }
 
 
+    private void processFrameSettings(int streamId, int payloadSize) throws IOException {
+        if (streamId != 0) {
+            // TODO i18n
+            throw new Http2Exception("", 0, Http2Exception.FRAME_SIZE_ERROR);
+        }
+
+        if (payloadSize % 6 != 0) {
+            // TODO i18n
+            throw new Http2Exception("", 0, Http2Exception.FRAME_SIZE_ERROR);
+        }
+    }
+
+
+    private void processFrameUnknown(int streamId, int type, int payloadSize) throws IOException {
+        // Swallow the payload
+        log.info("Swallowing [" + payloadSize + "] bytes of unknown frame type + [" + type +
+                "] from stream [" + streamId + "]");
+        swallowPayload(payloadSize);
+    }
+
+
+    private void swallowPayload(int payloadSize) throws IOException {
+        int read = 0;
+        byte[] buffer = new byte[8 * 1024];
+        while (read < payloadSize) {
+            int toRead = Math.min(buffer.length, payloadSize - read);
+            int thisTime = socketWrapper.read(true, buffer, 0, toRead);
+            if (thisTime == -1) {
+                throw new IOException("TODO: i18n");
+            }
+            read += thisTime;
+        }
+    }
+
+
+    private boolean getFrameHeader(byte[] frameHeader) throws IOException {
+        // All frames start with a fixed size header.
+        int headerBytesRead = socketWrapper.read(false, frameHeader, 0, frameHeader.length);
+
+        // No frame header read. Non-blocking between frames, so return.
+        if (headerBytesRead == 0) {
+            return false;
+        }
+
+        // Partial header read. Blocking within a frame to block while the
+        // remainder is read.
+        if (headerBytesRead < frameHeader.length) {
+            int read = socketWrapper.read(true, frameHeader, headerBytesRead,
+                    frameHeader.length - headerBytesRead);
+            if (read == -1) {
+                // TODO i18n
+                throw new EOFException();
+            }
+        }
+
+        return true;
+    }
+
+
+    private int getFrameType(byte[] frameHeader) throws IOException {
+        int frameType = frameHeader[3] & 0xFF;
+        // Make sure the first frame is a settings frame
+        if (firstFrame) {
+            if (frameType != FRAME_SETTINGS) {
+                // TODO i18n
+                throw new Http2Exception("", 0, Http2Exception.PROTOCOL_ERROR);
+            } else {
+                firstFrame = false;
+            }
+        }
+        return frameType;
+    }
+
+
+    private int getStreamIdentifier(byte[] frameHeader) {
+        // MSB of [5] is reserved and must be ignored.
+        return ((frameHeader[5] & 0x7F) << 24) + ((frameHeader[6] & 0xFF) << 16) +
+                ((frameHeader[7] & 0xFF) << 6) + (frameHeader[8] & 0xFF);
+    }
+
+
+    private int getPayloadSize(int streamId, byte[] frameHeader) throws IOException {
+        // Make sure the payload size is valid
+        int payloadSize = ((frameHeader[0] & 0xFF) << 16) +
+                ((frameHeader[1] & 0xFF) << 8) +
+                (frameHeader[2] & 0xFF);
+
+        if (payloadSize > settingsMaxFrameSize) {
+            swallowPayload(payloadSize);
+            // TODO i18n
+            throw new Http2Exception("", streamId, Http2Exception.FRAME_SIZE_ERROR);
+        }
+
+        return payloadSize;
+    }
+
+
     @Override
     public void destroy() {
         // NO-OP



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


Re: svn commit: r1679116 - in /tomcat/trunk/java/org/apache/coyote/http2: Http2Exception.java Http2UpgradeHandler.java

Posted by Mark Thomas <ma...@apache.org>.
On 13/05/2015 14:19, Rémy Maucherat wrote:
> 2015-05-13 0:29 GMT+02:00 <ma...@apache.org>:
> 
>> Author: markt
>> Date: Tue May 12 22:29:45 2015
>> New Revision: 1679116
>>
>> URL: http://svn.apache.org/r1679116
>> Log:
>> Start to process the initial settings frame sent by the client.
>> +    private boolean processFrame() throws IOException {
>> +        // TODO: Consider refactoring and making this a field to reduce
>> GC.
>> +        byte[] frameHeader = new byte[9];
>> +        if (!getFrameHeader(frameHeader)) {
>> +            return false;
>> +        }
>> +
>> +        int frameType = getFrameType(frameHeader);
>> +        int streamId = getStreamIdentifier(frameHeader);
>> +        int payloadSize = getPayloadSize(streamId, frameHeader);
>> +
>> +        switch (frameType) {
>> +        case FRAME_SETTINGS:
>> +            processFrameSettings(streamId, payloadSize);
>> +            break;
>> +        default:
>> +            // Unknown frame type.
>> +            processFrameUnknown(streamId, frameType, payloadSize);
>> +        }
>>          return false;
>>
> 
> That's really a great example on where you can use the new IO calls I
> proposed: they can replace with a single async call multiple reads (incl
> blocking ones) that are needed to process the frame.
> 
> Comments ?

I'm not sure reading into a 9 byte array is such a good example. I
suspect there will be better ones in the header and data frames. I find
it easier to see how gathering writes would be useful.

Mark

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


Re: svn commit: r1679116 - in /tomcat/trunk/java/org/apache/coyote/http2: Http2Exception.java Http2UpgradeHandler.java

Posted by Rémy Maucherat <re...@apache.org>.
2015-05-13 0:29 GMT+02:00 <ma...@apache.org>:

> Author: markt
> Date: Tue May 12 22:29:45 2015
> New Revision: 1679116
>
> URL: http://svn.apache.org/r1679116
> Log:
> Start to process the initial settings frame sent by the client.
> +    private boolean processFrame() throws IOException {
> +        // TODO: Consider refactoring and making this a field to reduce
> GC.
> +        byte[] frameHeader = new byte[9];
> +        if (!getFrameHeader(frameHeader)) {
> +            return false;
> +        }
> +
> +        int frameType = getFrameType(frameHeader);
> +        int streamId = getStreamIdentifier(frameHeader);
> +        int payloadSize = getPayloadSize(streamId, frameHeader);
> +
> +        switch (frameType) {
> +        case FRAME_SETTINGS:
> +            processFrameSettings(streamId, payloadSize);
> +            break;
> +        default:
> +            // Unknown frame type.
> +            processFrameUnknown(streamId, frameType, payloadSize);
> +        }
>          return false;
>

That's really a great example on where you can use the new IO calls I
proposed: they can replace with a single async call multiple reads (incl
blocking ones) that are needed to process the frame.

Comments ?

Rémy