You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by re...@apache.org on 2018/02/08 16:29:08 UTC

svn commit: r1823578 - in /tomcat/trunk: java/org/apache/coyote/http2/ webapps/docs/

Author: remm
Date: Thu Feb  8 16:29:08 2018
New Revision: 1823578

URL: http://svn.apache.org/viewvc?rev=1823578&view=rev
Log:
Add an async parser for HTTP/2. Will continue improving it. Although it looks rather scary for GC, it actually seems to perform "ok" with h2load.

Added:
    tomcat/trunk/java/org/apache/coyote/http2/Http2AsyncParser.java   (with props)
Modified:
    tomcat/trunk/java/org/apache/coyote/http2/ByteUtil.java
    tomcat/trunk/java/org/apache/coyote/http2/Http2AsyncUpgradeHandler.java
    tomcat/trunk/java/org/apache/coyote/http2/Http2Parser.java
    tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java
    tomcat/trunk/webapps/docs/changelog.xml

Modified: tomcat/trunk/java/org/apache/coyote/http2/ByteUtil.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http2/ByteUtil.java?rev=1823578&r1=1823577&r2=1823578&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/coyote/http2/ByteUtil.java (original)
+++ tomcat/trunk/java/org/apache/coyote/http2/ByteUtil.java Thu Feb  8 16:29:08 2018
@@ -16,6 +16,8 @@
  */
 package org.apache.coyote.http2;
 
+import java.nio.ByteBuffer;
+
 /**
  * Utility class for extracting values from byte arrays.
  */
@@ -37,6 +39,12 @@ class ByteUtil {
     }
 
 
+    static int get31Bits(ByteBuffer input, int firstByte) {
+        return ((input.get(firstByte) & 0x7F) << 24) + ((input.get(firstByte + 1) & 0xFF) << 16) +
+                ((input.get(firstByte + 2) & 0xFF) << 8) + (input.get(firstByte + 3) & 0xFF);
+    }
+
+
     static void set31Bits(byte[] output, int firstByte, int value) {
         output[firstByte] = (byte) ((value & 0x7F000000) >> 24);
         output[firstByte + 1] = (byte) ((value & 0xFF0000) >> 16);
@@ -50,6 +58,11 @@ class ByteUtil {
     }
 
 
+    static int getOneByte(ByteBuffer input, int pos) {
+        return (input.get(pos) & 0xFF);
+    }
+
+
     static int getTwoBytes(byte[] input, int firstByte) {
         return ((input[firstByte] & 0xFF) << 8) +  (input[firstByte + 1] & 0xFF);
     }
@@ -61,6 +74,12 @@ class ByteUtil {
     }
 
 
+    static int getThreeBytes(ByteBuffer input, int firstByte) {
+        return ((input.get(firstByte) & 0xFF) << 16) + ((input.get(firstByte + 1) & 0xFF) << 8) +
+                (input.get(firstByte + 2) & 0xFF);
+    }
+
+
     static void setTwoBytes(byte[] output, int firstByte, int value) {
         output[firstByte] = (byte) ((value & 0xFF00) >> 8);
         output[firstByte + 1] = (byte) (value & 0xFF);

Added: tomcat/trunk/java/org/apache/coyote/http2/Http2AsyncParser.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http2/Http2AsyncParser.java?rev=1823578&view=auto
==============================================================================
--- tomcat/trunk/java/org/apache/coyote/http2/Http2AsyncParser.java (added)
+++ tomcat/trunk/java/org/apache/coyote/http2/Http2AsyncParser.java Thu Feb  8 16:29:08 2018
@@ -0,0 +1,617 @@
+/*
+ *  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;
+import java.nio.ByteBuffer;
+import java.nio.channels.CompletionHandler;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.tomcat.util.buf.ByteBufferUtils;
+import org.apache.tomcat.util.net.SocketEvent;
+import org.apache.tomcat.util.net.SocketWrapperBase;
+import org.apache.tomcat.util.net.SocketWrapperBase.BlockingMode;
+import org.apache.tomcat.util.net.SocketWrapperBase.CompletionCheck;
+import org.apache.tomcat.util.net.SocketWrapperBase.CompletionHandlerCall;
+import org.apache.tomcat.util.net.SocketWrapperBase.CompletionState;
+
+class Http2AsyncParser extends Http2Parser {
+
+    protected final SocketWrapperBase<?> socketWrapper;
+    protected final Http2AsyncUpgradeHandler upgradeHandler;
+    private Throwable error = null;
+
+    Http2AsyncParser(String connectionId, Input input, Output output, SocketWrapperBase<?> socketWrapper, Http2AsyncUpgradeHandler upgradeHandler) {
+        super(connectionId, input, output);
+        this.socketWrapper = socketWrapper;
+        socketWrapper.getSocketBufferHandler().expand(input.getMaxFrameSize());
+        this.upgradeHandler = upgradeHandler;
+    }
+
+
+    protected boolean readFrame(boolean block, FrameType expected)
+            throws IOException, Http2Exception {
+        if (block) {
+            return super.readFrame(block, expected);
+        }
+        handleAsyncException();
+        // TODO: examine if it could be possible to reuse byte buffers or loop over frame readings (= less unRead abuse)
+        ByteBuffer header = ByteBuffer.allocate(9);
+        ByteBuffer framePaylod = ByteBuffer.allocate(input.getMaxFrameSize());
+        FrameCompletionHandler handler = new FrameCompletionHandler(expected, header, framePaylod);
+        FrameCompletionCheck check = new FrameCompletionCheck(handler);
+        CompletionState state =
+                socketWrapper.read(BlockingMode.NON_BLOCK, socketWrapper.getWriteTimeout(), TimeUnit.MILLISECONDS, null, check, handler, header, framePaylod);
+        if (state == CompletionState.ERROR || state == CompletionState.INLINE) {
+            handleAsyncException();
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    private void handleAsyncException()
+            throws IOException, Http2Exception {
+        if (error != null) {
+            Throwable error = this.error;
+            this.error = null;
+            if (error instanceof Http2Exception) {
+                throw (Http2Exception) error;
+            } else if (error instanceof IOException) {
+                throw (IOException) error;
+            } else {
+                throw new RuntimeException(error);
+            }
+        }
+    }
+
+    // TODO: see how to refactor to avoid duplication
+    private void readDataFrame(int streamId, int flags, int payloadSize, ByteBuffer buffer)
+            throws Http2Exception, IOException {
+        // Process the Stream
+        int padLength = 0;
+
+        boolean endOfStream = Flags.isEndOfStream(flags);
+
+        int dataLength;
+        if (Flags.hasPadding(flags)) {
+            padLength = buffer.get() & 0xFF;
+
+            if (padLength >= payloadSize) {
+                throw new ConnectionException(
+                        sm.getString("http2Parser.processFrame.tooMuchPadding", connectionId,
+                                Integer.toString(streamId), Integer.toString(padLength),
+                                Integer.toString(payloadSize)), Http2Error.PROTOCOL_ERROR);
+            }
+            // +1 is for the padding length byte we just read above
+            dataLength = payloadSize - (padLength + 1);
+        } else {
+            dataLength = payloadSize;
+        }
+
+        if (log.isDebugEnabled()) {
+            String padding;
+            if (Flags.hasPadding(flags)) {
+                padding = Integer.toString(padLength);
+            } else {
+                padding = "none";
+            }
+            log.debug(sm.getString("http2Parser.processFrameData.lengths", connectionId,
+                    Integer.toString(streamId), Integer.toString(dataLength), padding));
+        }
+
+        ByteBuffer dest = output.startRequestBodyFrame(streamId, payloadSize);
+        if (dest == null) {
+            swallow(streamId, dataLength, false, buffer);
+            // Process padding before sending any notifications in case padding
+            // is invalid.
+            if (padLength > 0) {
+                swallow(streamId, padLength, true, buffer);
+            }
+            if (endOfStream) {
+                output.receivedEndOfStream(streamId);
+            }
+        } else {
+            synchronized (dest) {
+                if (dest.remaining() < dataLength) {
+                    swallow(streamId, dataLength, false, buffer);
+                    // Client has sent more data than permitted by Window size
+                    throw new StreamException("Client sent more data than stream window allowed", Http2Error.FLOW_CONTROL_ERROR, streamId);
+                }
+                int oldLimit = buffer.limit();
+                buffer.limit(buffer.position() + dataLength);
+                dest.put(buffer);
+                buffer.limit(oldLimit);
+                // Process padding before sending any notifications in case
+                // padding is invalid.
+                if (padLength > 0) {
+                    swallow(streamId, padLength, true, buffer);
+                }
+                if (endOfStream) {
+                    output.receivedEndOfStream(streamId);
+                }
+                output.endRequestBodyFrame(streamId);
+            }
+        }
+        if (buffer.hasRemaining()) {
+            socketWrapper.unRead(buffer);
+        }
+        if (padLength > 0) {
+            output.swallowedPadding(streamId, padLength);
+        }
+    }
+
+
+    private void readHeadersFrame(int streamId, int flags, int payloadSize, ByteBuffer buffer)
+            throws Http2Exception, IOException {
+
+        headersEndStream = Flags.isEndOfStream(flags);
+
+        if (hpackDecoder == null) {
+            hpackDecoder = output.getHpackDecoder();
+        }
+
+        try {
+            hpackDecoder.setHeaderEmitter(output.headersStart(streamId, headersEndStream));
+        } catch (StreamException se) {
+            swallow(streamId, payloadSize, false, buffer);
+            throw se;
+        }
+
+        int padLength = 0;
+        boolean padding = Flags.hasPadding(flags);
+        boolean priority = Flags.hasPriority(flags);
+        int optionalLen = 0;
+        if (padding) {
+            optionalLen = 1;
+        }
+        if (priority) {
+            optionalLen += 5;
+        }
+        if (optionalLen > 0) {
+            byte[] optional = new byte[optionalLen];
+            buffer.get(optional);
+            int optionalPos = 0;
+            if (padding) {
+                padLength = ByteUtil.getOneByte(optional, optionalPos++);
+                if (padLength >= payloadSize) {
+                    throw new ConnectionException(
+                            sm.getString("http2Parser.processFrame.tooMuchPadding", connectionId,
+                                    Integer.toString(streamId), Integer.toString(padLength),
+                                    Integer.toString(payloadSize)), Http2Error.PROTOCOL_ERROR);
+                }
+            }
+            if (priority) {
+                boolean exclusive = ByteUtil.isBit7Set(optional[optionalPos]);
+                int parentStreamId = ByteUtil.get31Bits(optional, optionalPos);
+                int weight = ByteUtil.getOneByte(optional, optionalPos + 4) + 1;
+                output.reprioritise(streamId, parentStreamId, exclusive, weight);
+            }
+
+            payloadSize -= optionalLen;
+            payloadSize -= padLength;
+        }
+
+        readHeaderPayload(streamId, payloadSize, buffer);
+
+        swallow(streamId, padLength, true, buffer);
+
+        if (buffer.hasRemaining()) {
+            socketWrapper.unRead(buffer);
+        }
+
+        if (Flags.isEndOfHeaders(flags)) {
+            onHeadersComplete(streamId);
+        } else {
+            headersCurrentStream = streamId;
+        }
+    }
+
+
+    private void readPriorityFrame(int streamId, ByteBuffer buffer) throws Http2Exception, IOException {
+        byte[] payload = new byte[5];
+        buffer.get(payload);
+        if (buffer.hasRemaining()) {
+            socketWrapper.unRead(buffer);
+        }
+
+        boolean exclusive = ByteUtil.isBit7Set(payload[0]);
+        int parentStreamId = ByteUtil.get31Bits(payload, 0);
+        int weight = ByteUtil.getOneByte(payload, 4) + 1;
+
+        if (streamId == parentStreamId) {
+            throw new StreamException(sm.getString("http2Parser.processFramePriority.invalidParent",
+                    connectionId, Integer.valueOf(streamId)), Http2Error.PROTOCOL_ERROR, streamId);
+        }
+
+        output.reprioritise(streamId, parentStreamId, exclusive, weight);
+    }
+
+
+    private void readRstFrame(int streamId, ByteBuffer buffer) throws Http2Exception, IOException {
+        byte[] payload = new byte[4];
+        buffer.get(payload);
+        if (buffer.hasRemaining()) {
+            socketWrapper.unRead(buffer);
+        }
+
+        long errorCode = ByteUtil.getFourBytes(payload, 0);
+        output.reset(streamId, errorCode);
+        headersCurrentStream = -1;
+        headersEndStream = false;
+    }
+
+
+    private void readSettingsFrame(int flags, int payloadSize, ByteBuffer buffer) throws Http2Exception, IOException {
+        boolean ack = Flags.isAck(flags);
+        if (payloadSize > 0 && ack) {
+            throw new ConnectionException(sm.getString(
+                    "http2Parser.processFrameSettings.ackWithNonZeroPayload"),
+                    Http2Error.FRAME_SIZE_ERROR);
+        }
+
+        if (payloadSize != 0) {
+            // Process the settings
+            byte[] setting = new byte[6];
+            for (int i = 0; i < payloadSize / 6; i++) {
+                buffer.get(setting);
+                int id = ByteUtil.getTwoBytes(setting, 0);
+                long value = ByteUtil.getFourBytes(setting, 2);
+                output.setting(Setting.valueOf(id), value);
+            }
+        }
+        if (buffer.hasRemaining()) {
+            socketWrapper.unRead(buffer);
+        }
+        output.settingsEnd(ack);
+    }
+
+
+    private void readPingFrame(int flags, ByteBuffer buffer) throws IOException {
+        // Read the payload
+        byte[] payload = new byte[8];
+        buffer.get(payload);
+        if (buffer.hasRemaining()) {
+            socketWrapper.unRead(buffer);
+        }
+        output.pingReceive(payload, Flags.isAck(flags));
+    }
+
+
+    private void readGoawayFrame(int payloadSize, ByteBuffer buffer) throws IOException {
+        byte[] payload = new byte[payloadSize];
+        buffer.get(payload);
+        if (buffer.hasRemaining()) {
+            socketWrapper.unRead(buffer);
+        }
+        int lastStreamId = ByteUtil.get31Bits(payload, 0);
+        long errorCode = ByteUtil.getFourBytes(payload, 4);
+        String debugData = null;
+        if (payloadSize > 8) {
+            debugData = new String(payload, 8, payloadSize - 8, StandardCharsets.UTF_8);
+        }
+        output.goaway(lastStreamId, errorCode, debugData);
+    }
+
+
+    private void readPushPromiseFrame(int streamId, ByteBuffer buffer) throws Http2Exception {
+        if (buffer.hasRemaining()) {
+            socketWrapper.unRead(buffer);
+        }
+        throw new ConnectionException(sm.getString("http2Parser.processFramePushPromise",
+                connectionId, Integer.valueOf(streamId)), Http2Error.PROTOCOL_ERROR);
+    }
+
+
+    private void readWindowUpdateFrame(int streamId, ByteBuffer buffer) throws Http2Exception, IOException {
+        byte[] payload = new byte[4];
+        buffer.get(payload);
+        if (buffer.hasRemaining()) {
+            socketWrapper.unRead(buffer);
+        }
+        int windowSizeIncrement = ByteUtil.get31Bits(payload, 0);
+
+        if (log.isDebugEnabled()) {
+            log.debug(sm.getString("http2Parser.processFrameWindowUpdate.debug", connectionId,
+                    Integer.toString(streamId), Integer.toString(windowSizeIncrement)));
+        }
+
+        // Validate the data
+        if (windowSizeIncrement == 0) {
+            if (streamId == 0) {
+                throw new ConnectionException(
+                        sm.getString("http2Parser.processFrameWindowUpdate.invalidIncrement"),
+                        Http2Error.PROTOCOL_ERROR);
+            } else {
+                throw new StreamException(
+                        sm.getString("http2Parser.processFrameWindowUpdate.invalidIncrement"),
+                        Http2Error.PROTOCOL_ERROR, streamId);
+            }
+        }
+
+        output.incrementWindowSize(streamId, windowSizeIncrement);
+    }
+
+
+    private void readContinuationFrame(int streamId, int flags, int payloadSize, ByteBuffer buffer)
+            throws Http2Exception, IOException {
+        if (headersCurrentStream == -1) {
+            // No headers to continue
+            throw new ConnectionException(sm.getString(
+                    "http2Parser.processFrameContinuation.notExpected", connectionId,
+                    Integer.toString(streamId)), Http2Error.PROTOCOL_ERROR);
+        }
+
+        readHeaderPayload(streamId, payloadSize, buffer);
+        if (buffer.hasRemaining()) {
+            socketWrapper.unRead(buffer);
+        }
+
+        if (Flags.isEndOfHeaders(flags)) {
+            headersCurrentStream = -1;
+            onHeadersComplete(streamId);
+        }
+    }
+
+
+    private void readHeaderPayload(int streamId, int payloadSize, ByteBuffer buffer)
+            throws Http2Exception, IOException {
+
+        if (log.isDebugEnabled()) {
+            log.debug(sm.getString("http2Parser.processFrameHeaders.payload", connectionId,
+                    Integer.valueOf(streamId), Integer.valueOf(payloadSize)));
+        }
+
+        int remaining = payloadSize;
+
+        while (remaining > 0) {
+            if (headerReadBuffer.remaining() == 0) {
+                // Buffer needs expansion
+                int newSize;
+                if (headerReadBuffer.capacity() < payloadSize) {
+                    // First step, expand to the current payload. That should
+                    // cover most cases.
+                    newSize = payloadSize;
+                } else {
+                    // Header must be spread over multiple frames. Keep doubling
+                    // buffer size until the header can be read.
+                    newSize = headerReadBuffer.capacity() * 2;
+                }
+                headerReadBuffer = ByteBufferUtils.expand(headerReadBuffer, newSize);
+            }
+            int toRead = Math.min(headerReadBuffer.remaining(), remaining);
+            // headerReadBuffer in write mode
+            int oldLimit = buffer.limit();
+            buffer.limit(buffer.position() + toRead);
+            headerReadBuffer.put(buffer);
+            buffer.limit(oldLimit);
+            // switch to read mode
+            headerReadBuffer.flip();
+            try {
+                hpackDecoder.decode(headerReadBuffer);
+            } catch (HpackException hpe) {
+                throw new ConnectionException(
+                        sm.getString("http2Parser.processFrameHeaders.decodingFailed"),
+                        Http2Error.COMPRESSION_ERROR, hpe);
+            }
+
+            // switches to write mode
+            headerReadBuffer.compact();
+            remaining -= toRead;
+
+            if (hpackDecoder.isHeaderCountExceeded()) {
+                StreamException headerException = new StreamException(sm.getString(
+                        "http2Parser.headerLimitCount", connectionId, Integer.valueOf(streamId)),
+                        Http2Error.ENHANCE_YOUR_CALM, streamId);
+                hpackDecoder.getHeaderEmitter().setHeaderException(headerException);
+            }
+
+            if (hpackDecoder.isHeaderSizeExceeded(headerReadBuffer.position())) {
+                StreamException headerException = new StreamException(sm.getString(
+                        "http2Parser.headerLimitSize", connectionId, Integer.valueOf(streamId)),
+                        Http2Error.ENHANCE_YOUR_CALM, streamId);
+                hpackDecoder.getHeaderEmitter().setHeaderException(headerException);
+            }
+
+            if (hpackDecoder.isHeaderSwallowSizeExceeded(headerReadBuffer.position())) {
+                throw new ConnectionException(sm.getString("http2Parser.headerLimitSize",
+                        connectionId, Integer.valueOf(streamId)), Http2Error.ENHANCE_YOUR_CALM);
+            }
+        }
+    }
+
+
+    private void readUnknownFrame(int streamId, FrameType frameType, int flags, int payloadSize, ByteBuffer buffer)
+            throws IOException {
+        try {
+            swallow(streamId, payloadSize, false, buffer);
+        } catch (ConnectionException e) {
+            // Will never happen because swallow() is called with mustBeZero set
+            // to false
+        }
+        if (buffer.hasRemaining()) {
+            socketWrapper.unRead(buffer);
+        }
+        output.swallowed(streamId, frameType, flags, payloadSize);
+    }
+
+
+    private void swallow(int streamId, int len, boolean mustBeZero, ByteBuffer buffer)
+            throws IOException, ConnectionException {
+        if (log.isDebugEnabled()) {
+            log.debug(sm.getString("http2Parser.swallow.debug", connectionId,
+                    Integer.toString(streamId), Integer.toString(len)));
+        }
+        if (len == 0) {
+            return;
+        }
+        if (!mustBeZero) {
+            buffer.position(buffer.position() + len);
+        } else {
+            int read = 0;
+            byte[] buf = new byte[1024];
+            while (read < len) {
+                int thisTime = Math.min(buf.length, len - read);
+                buffer.get(buf, 0, thisTime);
+                // Validate the padding is zero since receiving non-zero padding
+                // is a strong indication of either a faulty client or a server
+                // side bug.
+                for (int i = 0; i < thisTime; i++) {
+                    if (buf[i] != 0) {
+                        throw new ConnectionException(sm.getString("http2Parser.nonZeroPadding",
+                                connectionId, Integer.toString(streamId)), Http2Error.PROTOCOL_ERROR);
+                    }
+                }
+                read += thisTime;
+            }
+        }
+    }
+
+
+    protected class FrameCompletionCheck implements CompletionCheck {
+        final FrameCompletionHandler handler;
+        boolean validated = false;
+        protected FrameCompletionCheck(FrameCompletionHandler handler) {
+            this.handler = handler;
+        }
+        @Override
+        public CompletionHandlerCall callHandler(CompletionState state,
+                ByteBuffer[] buffers, int offset, int length) {
+            // The first buffer should be 9 bytes long
+            ByteBuffer frameHeaderBuffer = buffers[offset];
+            if (frameHeaderBuffer.position() < 9) {
+                return CompletionHandlerCall.CONTINUE;
+            }
+
+            handler.payloadSize = ByteUtil.getThreeBytes(frameHeaderBuffer, 0);
+            handler.frameType = FrameType.valueOf(ByteUtil.getOneByte(frameHeaderBuffer, 3));
+            handler.flags = ByteUtil.getOneByte(frameHeaderBuffer, 4);
+            handler.streamId = ByteUtil.get31Bits(frameHeaderBuffer, 5);
+            handler.state = state;
+
+            if (!validated) {
+                validated = true;
+                try {
+                    validateFrame(handler.expected, handler.frameType, handler.streamId, handler.flags, handler.payloadSize);
+                } catch (StreamException e) {
+                    error = e;
+                    handler.streamException = true;
+                } catch (Http2Exception e) {
+                    error = e;
+                    // The problem will be handled later, consider the frame read is done
+                    return CompletionHandlerCall.DONE;
+                }
+            }
+
+            if (buffers[offset + 1].position() < handler.payloadSize) {
+                try {
+                    upgradeHandler.checkPauseState();
+                } catch (IOException e) {
+                    error = e;
+                }
+                return CompletionHandlerCall.CONTINUE;
+            }
+
+            return CompletionHandlerCall.DONE;
+        }
+
+    }
+
+    protected class FrameCompletionHandler implements CompletionHandler<Long, Void> {
+        private final FrameType expected;
+        private final ByteBuffer[] buffers;
+        private int payloadSize;
+        private FrameType frameType;
+        private int flags;
+        private int streamId;
+        private boolean streamException = false;
+        private CompletionState state = null;
+
+        protected FrameCompletionHandler(FrameType expected, ByteBuffer... buffers) {
+            this.expected = expected;
+            this.buffers = buffers;
+        }
+
+        @Override
+        public void completed(Long result, Void attachment) {
+            if (streamException || error == null) {
+                buffers[1].flip();
+                try {
+                    if (streamException) {
+                        swallow(streamId, payloadSize, false, buffers[1]);
+                    } else {
+                        switch (frameType) {
+                        case DATA:
+                            readDataFrame(streamId, flags, payloadSize, buffers[1]);
+                            break;
+                        case HEADERS:
+                            readHeadersFrame(streamId, flags, payloadSize, buffers[1]);
+                            break;
+                        case PRIORITY:
+                            readPriorityFrame(streamId, buffers[1]);
+                            break;
+                        case RST:
+                            readRstFrame(streamId, buffers[1]);
+                            break;
+                        case SETTINGS:
+                            readSettingsFrame(flags, payloadSize, buffers[1]);
+                            break;
+                        case PUSH_PROMISE:
+                            readPushPromiseFrame(streamId, buffers[1]);
+                            break;
+                        case PING:
+                            readPingFrame(flags, buffers[1]);
+                            break;
+                        case GOAWAY:
+                            readGoawayFrame(payloadSize, buffers[1]);
+                            break;
+                        case WINDOW_UPDATE:
+                            readWindowUpdateFrame(streamId, buffers[1]);
+                            break;
+                        case CONTINUATION:
+                            readContinuationFrame(streamId, flags, payloadSize, buffers[1]);
+                            break;
+                        case UNKNOWN:
+                            readUnknownFrame(streamId, frameType, flags, payloadSize, buffers[1]);
+                        }
+                    }
+                } catch (Exception e) {
+                    failed(e, attachment);
+                    return;
+                }
+            }
+            if (state == CompletionState.DONE) {
+                // The call was not completed inline, so must start reading new frames
+                // or process any error
+                upgradeHandler.upgradeDispatch(SocketEvent.OPEN_READ);
+            }
+        }
+
+        @Override
+        public void failed(Throwable exc, Void attachment) {
+            error = exc;
+            if (state == CompletionState.DONE) {
+                // The call was not completed inline, so must start reading new frames
+                // or process any error
+                upgradeHandler.upgradeDispatch(SocketEvent.OPEN_READ);
+            }
+        }
+        
+    }
+
+}

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

Modified: tomcat/trunk/java/org/apache/coyote/http2/Http2AsyncUpgradeHandler.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http2/Http2AsyncUpgradeHandler.java?rev=1823578&r1=1823577&r2=1823578&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/coyote/http2/Http2AsyncUpgradeHandler.java (original)
+++ tomcat/trunk/java/org/apache/coyote/http2/Http2AsyncUpgradeHandler.java Thu Feb  8 16:29:08 2018
@@ -71,6 +71,12 @@ public class Http2AsyncUpgradeHandler ex
     };
 
     @Override
+    protected Http2Parser getParser(String connectionId) {
+        return new Http2AsyncParser(connectionId, this, this, socketWrapper, this);
+    }
+
+
+    @Override
     protected PingManager getPingManager() {
         return new AsyncPingManager();
     }

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=1823578&r1=1823577&r2=1823578&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/coyote/http2/Http2Parser.java (original)
+++ tomcat/trunk/java/org/apache/coyote/http2/Http2Parser.java Thu Feb  8 16:29:08 2018
@@ -29,22 +29,22 @@ import org.apache.tomcat.util.res.String
 
 class Http2Parser {
 
-    private static final Log log = LogFactory.getLog(Http2Parser.class);
-    private static final StringManager sm = StringManager.getManager(Http2Parser.class);
+    protected static final Log log = LogFactory.getLog(Http2Parser.class);
+    protected static final StringManager sm = StringManager.getManager(Http2Parser.class);
 
     static final byte[] CLIENT_PREFACE_START =
             "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".getBytes(StandardCharsets.ISO_8859_1);
 
-    private final String connectionId;
-    private final Input input;
-    private final Output output;
-    private final byte[] frameHeaderBuffer = new byte[9];
+    protected final String connectionId;
+    protected final Input input;
+    protected final Output output;
+    protected final byte[] frameHeaderBuffer = new byte[9];
 
-    private volatile HpackDecoder hpackDecoder;
-    private volatile ByteBuffer headerReadBuffer =
+    protected volatile HpackDecoder hpackDecoder;
+    protected volatile ByteBuffer headerReadBuffer =
             ByteBuffer.allocate(Constants.DEFAULT_HEADER_READ_BUFFER_SIZE);
-    private volatile int headersCurrentStream = -1;
-    private volatile boolean headersEndStream = false;
+    protected volatile int headersCurrentStream = -1;
+    protected volatile boolean headersEndStream = false;
 
     Http2Parser(String connectionId, Input input, Output output) {
         this.connectionId = connectionId;
@@ -70,7 +70,7 @@ class Http2Parser {
     }
 
 
-    private boolean readFrame(boolean block, FrameType expected)
+    protected boolean readFrame(boolean block, FrameType expected)
             throws IOException, Http2Exception {
 
         if (!input.fill(block, frameHeaderBuffer)) {
@@ -448,7 +448,7 @@ class Http2Parser {
     }
 
 
-    private void onHeadersComplete(int streamId) throws Http2Exception {
+    protected void onHeadersComplete(int streamId) throws Http2Exception {
         // Any left over data is a compression error
         if (headerReadBuffer.position() > 0) {
             throw new ConnectionException(
@@ -525,7 +525,7 @@ class Http2Parser {
      * For validation applicable to some but not all frame types, use your
      * judgement.
      */
-    private void validateFrame(FrameType expected, FrameType frameType, int streamId, int flags,
+    protected void validateFrame(FrameType expected, FrameType frameType, int streamId, int flags,
             int payloadSize) throws Http2Exception {
 
         if (log.isDebugEnabled()) {

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=1823578&r1=1823577&r2=1823578&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java (original)
+++ tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java Thu Feb  8 16:29:08 2018
@@ -188,7 +188,7 @@ class Http2UpgradeHandler extends Abstra
             queuedRunnable = new ConcurrentLinkedQueue<>();
         }
 
-        parser = new Http2Parser(connectionId, this, this);
+        parser = getParser(connectionId);
 
         Stream stream = null;
 
@@ -250,6 +250,11 @@ class Http2UpgradeHandler extends Abstra
     }
 
 
+    protected Http2Parser getParser(String connectionId) {
+        return new Http2Parser(connectionId, this, this);
+    }
+
+
     private void processStreamOnContainerThread(Stream stream) {
         StreamProcessor streamProcessor = new StreamProcessor(this, stream, adapter, socketWrapper);
         streamProcessor.setSslSupport(sslSupport);
@@ -409,7 +414,7 @@ class Http2UpgradeHandler extends Abstra
     }
 
 
-    private void checkPauseState() throws IOException {
+    void checkPauseState() throws IOException {
         if (connectionState.get() == ConnectionState.PAUSING) {
             if (pausedNanoTime + pingManager.getRoundTripTimeNano() < System.nanoTime()) {
                 connectionState.compareAndSet(ConnectionState.PAUSING, ConnectionState.PAUSED);

Modified: tomcat/trunk/webapps/docs/changelog.xml
URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/docs/changelog.xml?rev=1823578&r1=1823577&r2=1823578&view=diff
==============================================================================
--- tomcat/trunk/webapps/docs/changelog.xml (original)
+++ tomcat/trunk/webapps/docs/changelog.xml Thu Feb  8 16:29:08 2018
@@ -57,6 +57,13 @@
       </add>
     </changelog>
   </subsection>
+  <subsection name="Coyote">
+    <changelog>
+      <add>
+        Add async HTTP/2 parser for NIO2. (remm)
+      </add>
+    </changelog>
+  </subsection>
   <subsection name="Web applications">
     <changelog>
       <add>



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