Author: markt Date: Wed Jul 1 13:53:52 2015 New Revision: 1688655 URL: http://svn.apache.org/r1688655 Log: Add some simple unit tests for goaway frames. These required several changes to HTTP/2 processing - tracking pings to match ACKs with original pings - tracking round trip time - regular (every 10s) pings to track changes to round trip time - differentiate between pausing and paused states
Added: tomcat/trunk/java/org/apache/coyote/http2/HeaderSink.java (with props) tomcat/trunk/test/org/apache/coyote/http2/TestHttp2Section_6_8.java (with props) Modified: tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java tomcat/trunk/java/org/apache/coyote/http2/LocalStrings.properties tomcat/trunk/test/org/apache/coyote/http2/Http2TestBase.java Added: tomcat/trunk/java/org/apache/coyote/http2/HeaderSink.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http2/HeaderSink.java?rev=1688655&view=auto ============================================================================== --- tomcat/trunk/java/org/apache/coyote/http2/HeaderSink.java (added) +++ tomcat/trunk/java/org/apache/coyote/http2/HeaderSink.java Wed Jul 1 13:53:52 2015 @@ -0,0 +1,32 @@ +/* + * 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 org.apache.coyote.http2.HpackDecoder.HeaderEmitter; + +/** + * Purpose of this class is to silently swallow any headers. It is used once + * the connection close process has started if headers for new streams are + * received. + */ +public class HeaderSink implements HeaderEmitter { + + @Override + public void emitHeader(String name, String value, boolean neverIndex) { + // NO-OP + } +} Propchange: tomcat/trunk/java/org/apache/coyote/http2/HeaderSink.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=1688655&r1=1688654&r2=1688655&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java (original) +++ tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java Wed Jul 1 13:53:52 2015 @@ -25,9 +25,12 @@ import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; +import java.util.Queue; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; import javax.servlet.http.WebConnection; @@ -84,6 +87,7 @@ public class Http2UpgradeHandler extends private static final int FLAG_END_OF_STREAM = 1; private static final int FLAG_END_OF_HEADERS = 4; + private static final byte[] PING = { 0x00, 0x00, 0x08, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00}; private static final byte[] PING_ACK = { 0x00, 0x00, 0x08, 0x06, 0x01, 0x00, 0x00, 0x00, 0x00 }; private static final byte[] SETTINGS_EMPTY = { 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00 }; @@ -95,10 +99,7 @@ public class Http2UpgradeHandler extends private static final byte[] HTTP2_UPGRADE_ACK = ("HTTP/1.1 101 Switching Protocols\r\n" + "Connection: Upgrade\r\nUpgrade: h2c\r\n\r\n").getBytes(StandardCharsets.ISO_8859_1); - private static final int STATE_NEW = 0; - private static final int STATE_CONNECTED = 1; - private static final int STATE_PAUSED = 2; - private static final int STATE_CLOSED =3; + private static final HeaderSink HEADER_SINK = new HeaderSink(); private final String connectionId; @@ -109,8 +110,9 @@ public class Http2UpgradeHandler extends private volatile Http2Parser parser; // Simple state machine (sequence of states) - - private AtomicInteger connectionState = new AtomicInteger(STATE_NEW); + private AtomicReference<ConnectionState> connectionState = + new AtomicReference<>(ConnectionState.NEW); + private volatile long pausedNanoTime = Long.MAX_VALUE; private final ConnectionSettings remoteSettings = new ConnectionSettings(); private final ConnectionSettings localSettings = new ConnectionSettings(); @@ -129,6 +131,7 @@ public class Http2UpgradeHandler extends // Start at -1 so the 'add 2' logic in closeIdleStreams() works private volatile int maxActiveRemoteStreamId = -1; private volatile int maxProcessedStreamId; + private final PingManager pingManager = new PingManager(); // Tracking for when the connection is blocked (windowSize < 1) private final Map<AbstractStream,int[]> backLogStreams = new ConcurrentHashMap<>(); @@ -162,7 +165,7 @@ public class Http2UpgradeHandler extends log.debug(sm.getString("upgradeHandler.init", connectionId)); } - if (!connectionState.compareAndSet(STATE_NEW, STATE_CONNECTED)) { + if (!connectionState.compareAndSet(ConnectionState.NEW, ConnectionState.CONNECTED)) { return; } @@ -218,6 +221,13 @@ public class Http2UpgradeHandler extends sm.getString("upgradeHandler.invalidPreface", connectionId)); } + // Send a ping to get an idea of round trip time as early as possible + try { + pingManager.sendPing(true); + } catch (IOException ioe) { + throw new ProtocolException(sm.getString("upgradeHandler.pingFailed"), ioe); + } + if (webConnection != null) { // Process the initial request on a container thread StreamProcessor streamProcessor = new StreamProcessor(stream, adapter, socketWrapper); @@ -249,70 +259,69 @@ public class Http2UpgradeHandler extends // Might not be necessary. init() will handle that. init(null); + SocketState result = SocketState.CLOSED; - switch(status) { - case OPEN_READ: - try { - while (true) { - try { - if (!parser.readFrame(false)) { - break; + try { + pingManager.sendPing(false); + + checkPauseState(); + + switch(status) { + case OPEN_READ: + try { + + while (true) { + try { + if (!parser.readFrame(false)) { + break; + } + } catch (StreamException se) { + // Stream errors are not fatal to the connection so + // continue reading frames + closeStream(se); } - } catch (StreamException se) { - // Stream errors are not fatal to the connection so - // continue reading frames - closeStream(se); } + } catch (Http2Exception ce) { + // Really ConnectionError + if (log.isDebugEnabled()) { + log.debug(sm.getString("upgradeHandler.connectionError"), ce); + } + closeConnection(ce); + break; } - } catch (Http2Exception ce) { - // Really ConnectionError - if (log.isDebugEnabled()) { - log.debug(sm.getString("upgradeHandler.connectionError"), ce); - } - closeConnection(ce); - break; - } catch (IOException ioe) { - if (log.isDebugEnabled()) { - log.debug(sm.getString("upgradeHandler.ioerror", connectionId), ioe); - } - close(); - break; - } - result = SocketState.UPGRADED; - break; + result = SocketState.UPGRADED; + break; - case OPEN_WRITE: - try { + case OPEN_WRITE: processWrites(); - } catch (IOException ioe) { - if (log.isDebugEnabled()) { - log.debug(sm.getString("upgradeHandler.ioerror", connectionId), ioe); - } + + result = SocketState.UPGRADED; + break; + + case ASYNC_READ_ERROR: + case ASYNC_WRITE_ERROR: + case CLOSE_NOW: + // This should never happen and will be fatal for this connection. + // Add the exception to trace how this point was reached. + log.error(sm.getString("upgradeHandler.unexpectedStatus", status), + new IllegalStateException()); + //$FALL-THROUGH$ + case DISCONNECT: + case ERROR: + case TIMEOUT: + case STOP: + // For all of the above, including the unexpected values, close the + // connection. close(); break; } - - result = SocketState.UPGRADED; - break; - - case ASYNC_READ_ERROR: - case ASYNC_WRITE_ERROR: - case CLOSE_NOW: - // This should never happen and will be fatal for this connection. - // Add the exception to trace how this point was reached. - log.error(sm.getString("upgradeHandler.unexpectedStatus", status), - new IllegalStateException()); - //$FALL-THROUGH$ - case DISCONNECT: - case ERROR: - case TIMEOUT: - case STOP: - // For all of the above, including the unexpected values, close the - // connection. + } catch (IOException ioe) { + if (log.isDebugEnabled()) { + log.debug(sm.getString("upgradeHandler.ioerror", connectionId), ioe); + } close(); - break; } if (log.isDebugEnabled()) { @@ -333,7 +342,29 @@ public class Http2UpgradeHandler extends log.debug(sm.getString("upgradeHandler.pause.entry", connectionId)); } - connectionState.compareAndSet(STATE_CONNECTED, STATE_PAUSED); + if (connectionState.compareAndSet(ConnectionState.CONNECTED, ConnectionState.PAUSING)) { + pausedNanoTime = System.nanoTime(); + + // Write a GOAWAY frame. + byte[] fixedPayload = new byte[8]; + ByteUtil.set31Bits(fixedPayload, 0, (1 << 31) - 1); + ByteUtil.setFourBytes(fixedPayload, 4, Http2Error.NO_ERROR.getCode()); + byte[] payloadLength = new byte[3]; + ByteUtil.setThreeBytes(payloadLength, 0, 8); + + try { + synchronized (socketWrapper) { + socketWrapper.write(true, payloadLength, 0, payloadLength.length); + socketWrapper.write(true, GOAWAY, 0, GOAWAY.length); + socketWrapper.write(true, fixedPayload, 0, 8); + socketWrapper.flush(true); + } + } catch (IOException ioe) { + // This is fatal for the connection. Ignore it here. There will be + // further attempts at I/O in upgradeDispatch() and it can better + // handle the IO errors. + } + } } @@ -343,6 +374,30 @@ public class Http2UpgradeHandler extends } + private void checkPauseState() throws IOException { + if (connectionState.get() == ConnectionState.PAUSING) { + if (pausedNanoTime + pingManager.getRoundTripTimeNano() < System.nanoTime()) { + connectionState.compareAndSet(ConnectionState.PAUSING, ConnectionState.PAUSED); + + // Write a GOAWAY frame. + byte[] fixedPayload = new byte[8]; + ByteUtil.set31Bits(fixedPayload, 0, maxProcessedStreamId); + ByteUtil.setFourBytes(fixedPayload, 4, Http2Error.NO_ERROR.getCode()); + byte[] payloadLength = new byte[3]; + ByteUtil.setThreeBytes(payloadLength, 0, 8); + + synchronized (socketWrapper) { + socketWrapper.write(true, payloadLength, 0, payloadLength.length); + socketWrapper.write(true, GOAWAY, 0, GOAWAY.length); + socketWrapper.write(true, fixedPayload, 0, 8); + socketWrapper.flush(true); + } + + } + } + } + + private void closeStream(StreamException se) throws ConnectionException, IOException { if (log.isDebugEnabled()) { @@ -661,7 +716,7 @@ public class Http2UpgradeHandler extends } - private Stream getStream(int streamId, boolean unknownIsError) throws ConnectionException{ + private Stream getStream(int streamId, boolean unknownIsError) throws ConnectionException { Integer key = Integer.valueOf(streamId); Stream result = streams.get(key); if (result == null && unknownIsError) { @@ -696,7 +751,7 @@ public class Http2UpgradeHandler extends private void close() { - connectionState.set(STATE_CLOSED); + connectionState.set(ConnectionState.CLOSED); try { socketWrapper.close(); } catch (IOException ioe) { @@ -811,10 +866,12 @@ public class Http2UpgradeHandler extends @Override public void receiveEndOfStream(int streamId) throws ConnectionException { - Stream stream = getStream(streamId, true); - stream.receivedEndOfStream(); - if (!stream.isActive()) { - activeRemoteStreamCount.decrementAndGet(); + Stream stream = getStream(streamId, connectionState.get().isNewStreamAllowed()); + if (stream != null) { + stream.receivedEndOfStream(); + if (!stream.isActive()) { + activeRemoteStreamCount.decrementAndGet(); + } } } @@ -830,20 +887,29 @@ public class Http2UpgradeHandler extends @Override public HeaderEmitter headersStart(int streamId) throws Http2Exception { - Stream stream = getStream(streamId, false); - if (stream == null) { - stream = createRemoteStream(streamId); - } - stream.checkState(FrameType.HEADERS); - stream.receivedStartOfHeaders(); - closeIdleStreams(streamId); - if (localSettings.getMaxConcurrentStreams() < activeRemoteStreamCount.incrementAndGet()) { - activeRemoteStreamCount.decrementAndGet(); - throw new StreamException(sm.getString("upgradeHandler.tooManyRemoteStreams", - Long.toString(localSettings.getMaxConcurrentStreams())), - Http2Error.REFUSED_STREAM, streamId); + if (connectionState.get().isNewStreamAllowed()) { + Stream stream = getStream(streamId, false); + if (stream == null) { + stream = createRemoteStream(streamId); + } + stream.checkState(FrameType.HEADERS); + stream.receivedStartOfHeaders(); + closeIdleStreams(streamId); + if (localSettings.getMaxConcurrentStreams() < activeRemoteStreamCount.incrementAndGet()) { + activeRemoteStreamCount.decrementAndGet(); + throw new StreamException(sm.getString("upgradeHandler.tooManyRemoteStreams", + Long.toString(localSettings.getMaxConcurrentStreams())), + Http2Error.REFUSED_STREAM, streamId); + } + return stream; + } else { + if (log.isDebugEnabled()) { + log.debug(sm.getString("upgradeHandler.noNewStreams", + connectionId, Integer.toString(streamId))); + } + // Stateless so a static can be used to save on GC + return HEADER_SINK; } - return stream; } @@ -877,11 +943,13 @@ public class Http2UpgradeHandler extends @Override public void headersEnd(int streamId) throws ConnectionException { setMaxProcessedStream(streamId); - Stream stream = getStream(streamId, true); - // Process this stream on a container thread - StreamProcessor streamProcessor = new StreamProcessor(stream, adapter, socketWrapper); - streamProcessor.setSslSupport(sslSupport); - socketWrapper.getEndpoint().getExecutor().execute(streamProcessor); + Stream stream = getStream(streamId, connectionState.get().isNewStreamAllowed()); + if (stream != null) { + // Process this stream on a container thread + StreamProcessor streamProcessor = new StreamProcessor(stream, adapter, socketWrapper); + streamProcessor.setSslSupport(sslSupport); + socketWrapper.getEndpoint().getExecutor().execute(streamProcessor); + } } @@ -921,14 +989,7 @@ public class Http2UpgradeHandler extends @Override public void pingReceive(byte[] payload, boolean ack) throws IOException { - if (!ack) { - // Echo it back - synchronized (socketWrapper) { - socketWrapper.write(true, PING_ACK, 0, PING_ACK.length); - socketWrapper.write(true, payload, 0, payload.length); - socketWrapper.flush(true); - } - } + pingManager.receivePing(payload, ack); } @@ -958,4 +1019,115 @@ public class Http2UpgradeHandler extends throws IOException { // NO-OP. } + + + private class PingManager { + + // 10 seconds + private final long pingIntervalNano = 10000000000L; + + private int sequence = 0; + private long lastPingNanoTime = Long.MIN_VALUE; + + private Queue<PingRecord> inflightPings = new ConcurrentLinkedQueue<>(); + private Queue<Long> roundTripTimes = new ConcurrentLinkedQueue<>(); + + /** + * Check to see if a ping was sent recently and, if not, send one. + * + * @throws IOException If an I/O issue prevents the ping from being sent + */ + public void sendPing(boolean force) throws IOException { + long now = System.nanoTime(); + if (force || now - lastPingNanoTime > pingIntervalNano) { + lastPingNanoTime = now; + byte[] payload = new byte[8]; + synchronized (socketWrapper) { + int sentSequence = ++sequence; + PingRecord pingRecord = new PingRecord(sentSequence, now); + inflightPings.add(pingRecord); + ByteUtil.set31Bits(payload, 4, sentSequence); + socketWrapper.write(true, PING, 0, PING.length); + socketWrapper.write(true, payload, 0, payload.length); + socketWrapper.flush(true); + } + } + } + + public void receivePing(byte[] payload, boolean ack) throws IOException { + if (ack) { + // Extract the sequence from the payload + int receivedSequence = ByteUtil.get31Bits(payload, 4); + PingRecord pingRecord = inflightPings.poll(); + while (pingRecord != null && pingRecord.getSequence() < receivedSequence) { + pingRecord = inflightPings.poll(); + } + if (pingRecord == null) { + // Unexpected ACK. Log it. + } else { + long roundTripTime = System.nanoTime() - pingRecord.getSentNanoTime(); + roundTripTimes.add(Long.valueOf(roundTripTime)); + while (roundTripTimes.size() > 3) { + roundTripTimes.poll(); + } + if (log.isDebugEnabled()) { + log.debug(sm.getString("pingManager.roundTripTime", + connectionId, Long.valueOf(roundTripTime))); + } + } + + } else { + // Client originated ping. Echo it back. + synchronized (socketWrapper) { + socketWrapper.write(true, PING_ACK, 0, PING_ACK.length); + socketWrapper.write(true, payload, 0, payload.length); + socketWrapper.flush(true); + } + } + } + + public long getRoundTripTimeNano() { + return (long) roundTripTimes.stream().mapToLong(x -> x.longValue()).average().orElse(0); + } + } + + + private static class PingRecord { + + private final int sequence; + private final long sentNanoTime; + + public PingRecord(int sequence, long sentNanoTime) { + this.sequence = sequence; + this.sentNanoTime = sentNanoTime; + } + + public int getSequence() { + return sequence; + } + + public long getSentNanoTime() { + return sentNanoTime; + } + } + + + private enum ConnectionState { + + NEW(true), + CONNECTED(true), + PAUSING(true), + PAUSED(false), + CLOSED(false); + + private final boolean newStreamsAllowed; + + private ConnectionState(boolean newStreamsAllowed) { + this.newStreamsAllowed = newStreamsAllowed; + } + + public boolean isNewStreamAllowed() { + return newStreamsAllowed; + } + } } 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=1688655&r1=1688654&r2=1688655&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/coyote/http2/LocalStrings.properties (original) +++ tomcat/trunk/java/org/apache/coyote/http2/LocalStrings.properties Wed Jul 1 13:53:52 2015 @@ -61,6 +61,8 @@ http2Parser.processFrameWindowUpdate.inv http2Parser.processFrameWindowUpdate.invalidPayloadSize=Window update frame received with an invalid payload size of [{0}] http2Parser.swallow.debug=Connection [{0}], Stream [{1}], Swallowed [{2}] bytes +pingManager.roundTripTime=Connection [{0}] Round trip time measured as [{1}]ns + stream.closed=Connection [{0}], Stream [{1}], Unable to write to stream once it has been closed stream.header.debug=Connection [{0}], Stream [{1}], HTTP header [{2}], Value [{3}] stream.reprioritisation.debug=Connection [{0}], Stream [{1}], Exclusive [{2}], Parent [{3}], Weight [{4}] @@ -75,12 +77,14 @@ streamStateMachine.invalidFrame=Connecti upgradeHandler.allocate.debug=Connection [{0}], Stream [{1}], allocated [{2}] bytes upgradeHandler.allocate.left=Connection [{0}], Stream [{1}], [{2}] bytes unallocated - trying to allocate to children upgradeHandler.allocate.recipient=Connection [{0}], Stream [{1}], potential recipient [{2}] with weight [{3}] +upgradeHandler.noNewStreams=Connection [{0}], Stream [{1}], Stream ignored as no new streams are permitted on this connection upgradeHandler.rst.debug=Connection [{0}], Stream [{1}], Error [{2}], RST (closing stream) upgradeHandler.goaway.debug=Connection [{0}], Goaway, Last stream [{1}], Error code [{2}], Debug data [{3}] upgradeHandler.init=Connection [{0}] upgradeHandler.invalidPreface=Connection [{0}], Invalid connection preface upgradeHandler.ioerror=Connection [{0}] upgradeHandler.pause.entry=Connection [{0}] Pausing +upgradeHandler.pingFailed=Connection [{0}] Failed to send ping to client upgradeHandler.sendPrefaceFail=Failed to send preface to client upgradeHandler.socketCloseFailed=Error closing socket upgradeHandler.stream.closed=Stream [{0}] has been closed for some time Modified: tomcat/trunk/test/org/apache/coyote/http2/Http2TestBase.java URL: http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/coyote/http2/Http2TestBase.java?rev=1688655&r1=1688654&r2=1688655&view=diff ============================================================================== --- tomcat/trunk/test/org/apache/coyote/http2/Http2TestBase.java (original) +++ tomcat/trunk/test/org/apache/coyote/http2/Http2TestBase.java Wed Jul 1 13:53:52 2015 @@ -69,6 +69,12 @@ public abstract class Http2TestBase exte protected Http2Parser parser; protected OutputStream os; + private long pingAckDelayMillis = 0; + + + protected void setPingAckDelayMillis(long delay) { + pingAckDelayMillis = delay; + } /** * Standard setup. Creates HTTP/2 connection via HTTP upgrade and ensures @@ -95,9 +101,11 @@ public abstract class Http2TestBase exte parser.readFrame(true); parser.readFrame(true); parser.readFrame(true); + parser.readFrame(true); Assert.assertEquals("0-Settings-End\n" + "0-Settings-Ack\n" + + "0-Ping-[0,0,0,0,0,0,0,1]\n" + getSimpleResponseTrace(1) , output.getTrace()); output.clearTrace(); @@ -489,6 +497,13 @@ public abstract class Http2TestBase exte void sendPing(int streamId, boolean ack, byte[] payload) throws IOException { + if (ack && pingAckDelayMillis > 0) { + try { + Thread.sleep(pingAckDelayMillis); + } catch (InterruptedException e) { + // Ignore + } + } byte[] pingHeader = new byte[9]; // length ByteUtil.setThreeBytes(pingHeader, 0, payload.length); @@ -643,7 +658,7 @@ public abstract class Http2TestBase exte } - static class TestOutput implements Output, HeaderEmitter { + class TestOutput implements Output, HeaderEmitter { private StringBuffer trace = new StringBuffer(); private String lastStreamId = "0"; @@ -722,10 +737,12 @@ public abstract class Http2TestBase exte @Override - public void pingReceive(byte[] payload, boolean ack) { + public void pingReceive(byte[] payload, boolean ack) throws IOException { trace.append("0-Ping-"); if (ack) { trace.append("Ack-"); + } else { + sendPing(0, true, payload); } trace.append('['); boolean first = true; Added: tomcat/trunk/test/org/apache/coyote/http2/TestHttp2Section_6_8.java URL: http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/coyote/http2/TestHttp2Section_6_8.java?rev=1688655&view=auto ============================================================================== --- tomcat/trunk/test/org/apache/coyote/http2/TestHttp2Section_6_8.java (added) +++ tomcat/trunk/test/org/apache/coyote/http2/TestHttp2Section_6_8.java Wed Jul 1 13:53:52 2015 @@ -0,0 +1,84 @@ +/* + * 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 org.junit.Assert; +import org.junit.Test; + +/** + * Unit tests for Section 6.8 of + * <a href="https://tools.ietf.org/html/rfc7540">RFC 7540</a>. + * <br> + * The order of tests in this class is aligned with the order of the + * requirements in the RFC. + */ +public class TestHttp2Section_6_8 extends Http2TestBase { + + private static final long PNG_ACK_DELAY_MS = 1000; + + @Test + public void testGoawayIgnoreNewStreams() throws Exception { + setPingAckDelayMillis(PNG_ACK_DELAY_MS); + + // HTTP2 upgrade + http2Connect(); + + getTomcatInstance().getConnector().pause(); + + // Go away + parser.readFrame(true); + Assert.assertEquals("0-Goaway-[2147483647]-[0]-[null]", output.getTrace()); + output.clearTrace(); + + // Should be processed + sendSimpleGetRequest(3); + + Thread.sleep(PNG_ACK_DELAY_MS + 200); + + // Should be ignored + sendSimpleGetRequest(5); + + parser.readFrame(true); + parser.readFrame(true); + + Assert.assertEquals(getSimpleResponseTrace(3), output.getTrace()); + output.clearTrace(); + + // Finally the go away frame + parser.readFrame(true); + Assert.assertEquals("0-Goaway-[3]-[0]-[null]", output.getTrace()); + } + + + @Test + public void testGoawayFrameNonZeroStream() throws Exception { + // HTTP2 upgrade + http2Connect(); + + sendGoaway(1, 1, Http2Error.NO_ERROR.getCode(), null); + + // Go away + parser.readFrame(true); + + Assert.assertTrue(output.getTrace(), output.getTrace().startsWith( + "0-Goaway-[1]-[" + Http2Error.PROTOCOL_ERROR.getCode() + "]-[")); + } + + + // TODO Test header processing and window size processing for ignored + // streams +} Propchange: tomcat/trunk/test/org/apache/coyote/http2/TestHttp2Section_6_8.java ------------------------------------------------------------------------------ svn:eol-style = native --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org