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