This is an automated email from the ASF dual-hosted git repository.

markt pushed a commit to branch 8.5.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git

commit bc118c275e745e5f38df33c63fcd3fd555692b64
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Mon Aug 17 18:51:11 2020 +0100

    BZ 64621: Improve handling of HTTP/2 stream resets form client
---
 .../apache/coyote/http2/Http2UpgradeHandler.java   | 35 +++++++----
 .../apache/coyote/http2/LocalStrings.properties    |  1 +
 java/org/apache/coyote/http2/Stream.java           |  8 +--
 .../apache/coyote/http2/TestHttp2Section_5_1.java  | 70 +++++++++++++++++++++-
 webapps/docs/changelog.xml                         |  4 ++
 5 files changed, 102 insertions(+), 16 deletions(-)

diff --git a/java/org/apache/coyote/http2/Http2UpgradeHandler.java 
b/java/org/apache/coyote/http2/Http2UpgradeHandler.java
index b88474f..8f34a40 100644
--- a/java/org/apache/coyote/http2/Http2UpgradeHandler.java
+++ b/java/org/apache/coyote/http2/Http2UpgradeHandler.java
@@ -895,17 +895,28 @@ public class Http2UpgradeHandler extends AbstractStream 
implements InternalHttpU
                                 tracker = backLogStreams.get(stream);
                             }
                             if (tracker != null && 
tracker.getUnusedAllocation() == 0) {
-                                if (log.isDebugEnabled()) {
-                                    
log.debug(sm.getString("upgradeHandler.noAllocation",
-                                            connectionId, 
stream.getIdentifier()));
+                                String msg;
+                                Http2Error error;
+                                if (stream.isActive()) {
+                                    if (log.isDebugEnabled()) {
+                                        
log.debug(sm.getString("upgradeHandler.noAllocation",
+                                                connectionId, 
stream.getIdentifier()));
+                                    }
+                                    // No allocation
+                                    // Close the connection. Do this first 
since
+                                    // closing the stream will raise an 
exception.
+                                    close();
+                                    msg = sm.getString("stream.writeTimeout");
+                                    error = Http2Error.ENHANCE_YOUR_CALM;
+                                } else {
+                                    msg = sm.getString("stream.clientAbort");
+                                    error = Http2Error.CANCEL;
                                 }
-                                // No allocation
-                                // Close the connection. Do this first since
-                                // closing the stream will raise an exception
-                                close();
-                                // Close the stream (in app code so need to
-                                // signal to app stream is closing)
-                                stream.doWriteTimeout();
+                                // Close the stream
+                                // This thread is in application code so need
+                                // to signal to the application that the
+                                // stream is closing
+                                stream.doStreamAbort(msg, error);
                             }
                         } catch (InterruptedException e) {
                             throw new IOException(sm.getString(
@@ -1661,8 +1672,12 @@ public class Http2UpgradeHandler extends AbstractStream 
implements InternalHttpU
     @Override
     public void reset(int streamId, long errorCode) throws Http2Exception  {
         Stream stream = getStream(streamId, true);
+        boolean active = stream.isActive();
         stream.checkState(FrameType.RST);
         stream.receiveReset(errorCode);
+        if (active) {
+            activeRemoteStreamCount.decrementAndGet();
+        }
     }
 
 
diff --git a/java/org/apache/coyote/http2/LocalStrings.properties 
b/java/org/apache/coyote/http2/LocalStrings.properties
index 7b57d2c..36a33cf 100644
--- a/java/org/apache/coyote/http2/LocalStrings.properties
+++ b/java/org/apache/coyote/http2/LocalStrings.properties
@@ -73,6 +73,7 @@ http2Parser.swallow.debug=Connection [{0}], Stream [{1}], 
Swallowed [{2}] bytes
 
 pingManager.roundTripTime=Connection [{0}] Round trip time measured as [{1}]ns
 
+stream.clientAbort=Client reset the stream before the response was complete
 stream.closed=Connection [{0}], Stream [{1}], Unable to write to stream once 
it has been closed
 stream.header.case=Connection [{0}], Stream [{1}], HTTP header name [{2}] must 
be in lower case
 stream.header.connection=Connection [{0}], Stream [{1}], HTTP header 
[connection] is not permitted in an HTTP/2 request
diff --git a/java/org/apache/coyote/http2/Stream.java 
b/java/org/apache/coyote/http2/Stream.java
index 0371be9..189bdfb 100644
--- a/java/org/apache/coyote/http2/Stream.java
+++ b/java/org/apache/coyote/http2/Stream.java
@@ -272,7 +272,7 @@ public class Stream extends AbstractStream implements 
HeaderEmitter {
                     allocationManager.waitForStream(writeTimeout);
                     windowSize = getWindowSize();
                     if (windowSize == 0) {
-                        doWriteTimeout();
+                        doStreamAbort(sm.getString("stream.writeTimeout"), 
Http2Error.ENHANCE_YOUR_CALM);
                     }
                 } catch (InterruptedException e) {
                     // Possible shutdown / rst or similar. Use an IOException 
to
@@ -296,10 +296,8 @@ public class Stream extends AbstractStream implements 
HeaderEmitter {
     }
 
 
-    void doWriteTimeout() throws CloseNowException {
-        String msg = sm.getString("stream.writeTimeout");
-        StreamException se = new StreamException(
-                msg, Http2Error.ENHANCE_YOUR_CALM, getIdAsInt());
+    void doStreamAbort(String msg, Http2Error error) throws CloseNowException {
+        StreamException se = new StreamException(msg, error, getIdAsInt());
         // Prevent the application making further writes
         streamOutputBuffer.closed = true;
         // Prevent Tomcat's error handling trying to write
diff --git a/test/org/apache/coyote/http2/TestHttp2Section_5_1.java 
b/test/org/apache/coyote/http2/TestHttp2Section_5_1.java
index ad575ca..9375516 100644
--- a/test/org/apache/coyote/http2/TestHttp2Section_5_1.java
+++ b/test/org/apache/coyote/http2/TestHttp2Section_5_1.java
@@ -194,7 +194,7 @@ public class TestHttp2Section_5_1 extends Http2TestBase {
 
 
     @Test
-    public void testExceedMaxActiveStreams() throws Exception {
+    public void testExceedMaxActiveStreams01() throws Exception {
         // http2Connect() - modified
         enableHttp2(1);
         configureAndStartWebApplication();
@@ -261,6 +261,74 @@ public class TestHttp2Section_5_1 extends Http2TestBase {
 
 
     @Test
+    public void testExceedMaxActiveStreams02() throws Exception {
+
+        // http2Connect() - modified
+        enableHttp2(1);
+        configureAndStartWebApplication();
+        openClientConnection();
+        doHttpUpgrade();
+        sendClientPreface();
+
+        // validateHttp2InitialResponse() - modified
+        parser.readFrame(true);
+        parser.readFrame(true);
+        parser.readFrame(true);
+        parser.readFrame(true);
+        parser.readFrame(true);
+
+        Assert.assertEquals("0-Settings-[3]-[1]\n" +
+                "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();
+
+        sendLargeGetRequest(3);
+
+        sendSimpleGetRequest(5);
+
+        // Default connection window size is 64k-1.
+        // Initial request will have used 8k leaving 56k-1.
+        // Stream window will be 64k-1.
+        // Expecting
+        // 1 * headers
+        // 56k-1 of body (7 * ~8k)
+        // 1 * error
+        // for a total of 9 frames (could be in any order)
+        for (int i = 0; i < 9; i++) {
+            parser.readFrame(true);
+        }
+
+        Assert.assertTrue(output.getTrace(),
+                output.getTrace().contains("5-RST-[" +
+                        Http2Error.REFUSED_STREAM.getCode() + "]"));
+        output.clearTrace();
+
+        // Connection window is zero.
+        // Stream window is 8k
+
+        // Reset stream 3 (client cancel)
+        sendRst(3, Http2Error.NO_ERROR.getCode());
+        // Client reset triggers a write error which in turn triggers a server
+        // reset
+        parser.readFrame(true);
+        Assert.assertEquals("3-RST-[8]\n", output.getTrace());
+        output.clearTrace();
+
+        // Open up the connection window.
+        sendWindowUpdate(0, (1 << 31) - 2);
+
+        // Confirm another request can be sent once concurrency falls back 
below limit
+        sendSimpleGetRequest(7);
+        parser.readFrame(true);
+        parser.readFrame(true);
+        Assert.assertEquals(getSimpleResponseTrace(7), output.getTrace());
+    }
+
+
+    @Test
     public void testErrorOnWaitingStream() throws Exception {
         // http2Connect() - modified
         enableHttp2(1);
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 84bdd09..5ba104b 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -94,6 +94,10 @@
         is closed in one thread at the same time as the poller is processing an
         event for that socket in another. (markt)
       </fix>
+      <fix>
+        <bug>64621</bug>: Improve handling HTTP/2 stream reset frames received
+        from clients. (markt)
+      </fix>
     </changelog>
   </subsection>
   <subsection name="WebSocket">


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

Reply via email to