This is an automated email from the ASF dual-hosted git repository. markt pushed a commit to branch 10.1.x in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/10.1.x by this push: new f902edf085 Fix BZ 69302 Client reset should trigger ReadListener.onError ... f902edf085 is described below commit f902edf085c0c73139a66d1bfc4d5790a416b130 Author: Mark Thomas <ma...@apache.org> AuthorDate: Tue Sep 3 15:08:34 2024 +0100 Fix BZ 69302 Client reset should trigger ReadListener.onError ... ... if the request body has not been fully read. https://bz.apache.org/bugzilla/show_bug.cgi?id=69302 --- java/org/apache/coyote/AbstractProcessor.java | 4 +++ java/org/apache/coyote/ActionCode.java | 6 +++++ .../apache/coyote/http2/LocalStrings.properties | 1 + java/org/apache/coyote/http2/Stream.java | 30 +++++++++++++++++++++- java/org/apache/tomcat/util/net/DispatchType.java | 3 ++- .../apache/coyote/http2/TestAsyncReadListener.java | 19 +++++++++++++- .../apache/coyote/http2/TestHttp2Section_5_1.java | 6 ++--- webapps/docs/changelog.xml | 5 ++++ 8 files changed, 68 insertions(+), 6 deletions(-) diff --git a/java/org/apache/coyote/AbstractProcessor.java b/java/org/apache/coyote/AbstractProcessor.java index 6e750dbbba..bddca91c3e 100644 --- a/java/org/apache/coyote/AbstractProcessor.java +++ b/java/org/apache/coyote/AbstractProcessor.java @@ -602,6 +602,10 @@ public abstract class AbstractProcessor extends AbstractProcessorLight implement addDispatch(DispatchType.NON_BLOCKING_WRITE); break; } + case DISPATCH_ERROR: { + addDispatch(DispatchType.NON_BLOCKING_ERROR); + break; + } case DISPATCH_EXECUTE: { executeDispatches(); break; diff --git a/java/org/apache/coyote/ActionCode.java b/java/org/apache/coyote/ActionCode.java index 62d236633a..179e02d7da 100644 --- a/java/org/apache/coyote/ActionCode.java +++ b/java/org/apache/coyote/ActionCode.java @@ -237,6 +237,12 @@ public enum ActionCode { */ DISPATCH_WRITE, + /** + * Indicates that the container needs to trigger a call to onError() for the registered non-blocking write and/or + * read listener(s). + */ + DISPATCH_ERROR, + /** * Execute any non-blocking dispatches that have been registered via {@link #DISPATCH_READ} or * {@link #DISPATCH_WRITE}. Typically required when the non-blocking listeners are configured on a thread where the diff --git a/java/org/apache/coyote/http2/LocalStrings.properties b/java/org/apache/coyote/http2/LocalStrings.properties index c9bcb28a01..6bdb05696a 100644 --- a/java/org/apache/coyote/http2/LocalStrings.properties +++ b/java/org/apache/coyote/http2/LocalStrings.properties @@ -85,6 +85,7 @@ http2Protocol.jmxRegistration.fail=JMX registration for the HTTP/2 protocol fail pingManager.roundTripTime=Connection [{0}] Round trip time measured as [{1}]ns +stream.clientResetRequest=Client reset the stream before the request was fully read 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 [{2}] 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 876b85158a..2b81241f4d 100644 --- a/java/org/apache/coyote/http2/Stream.java +++ b/java/org/apache/coyote/http2/Stream.java @@ -33,6 +33,8 @@ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.function.Supplier; +import jakarta.servlet.RequestDispatcher; + import org.apache.coyote.ActionCode; import org.apache.coyote.CloseNowException; import org.apache.coyote.InputBuffer; @@ -1238,7 +1240,7 @@ class Stream extends AbstractNonZeroStream implements HeaderEmitter { // If readInterest is true, data must be available to read no later than this time. private volatile long readTimeoutExpiry; private volatile boolean closed; - private boolean resetReceived; + private volatile boolean resetReceived; @SuppressWarnings("deprecation") @Override @@ -1334,6 +1336,16 @@ class Stream extends AbstractNonZeroStream implements HeaderEmitter { return true; } + if (resetReceived) { + // Trigger ReadListener.onError() + getCoyoteRequest().setAttribute(RequestDispatcher.ERROR_EXCEPTION, + new IOException(sm.getString("stream.clientResetRequest"))); + coyoteRequest.action(ActionCode.DISPATCH_ERROR, null); + coyoteRequest.action(ActionCode.DISPATCH_EXECUTE, null); + + return false; + } + if (!isRequestBodyFullyRead()) { readInterest = true; long readTimeout = handler.getProtocol().getStreamReadTimeout(); @@ -1455,6 +1467,22 @@ class Stream extends AbstractNonZeroStream implements HeaderEmitter { inBuffer.notifyAll(); } } + + // If a read is in progress, cancel it. + readStateLock.lock(); + try { + if (readInterest) { + readInterest = false; + } + } finally { + readStateLock.unlock(); + } + + // Trigger ReadListener.onError() + getCoyoteRequest().setAttribute(RequestDispatcher.ERROR_EXCEPTION, + new IOException(sm.getString("stream.clientResetRequest"))); + coyoteRequest.action(ActionCode.DISPATCH_ERROR, null); + coyoteRequest.action(ActionCode.DISPATCH_EXECUTE, null); } @Override diff --git a/java/org/apache/tomcat/util/net/DispatchType.java b/java/org/apache/tomcat/util/net/DispatchType.java index 5914c87044..ee04a0555e 100644 --- a/java/org/apache/tomcat/util/net/DispatchType.java +++ b/java/org/apache/tomcat/util/net/DispatchType.java @@ -24,7 +24,8 @@ package org.apache.tomcat.util.net; public enum DispatchType { NON_BLOCKING_READ(SocketEvent.OPEN_READ), - NON_BLOCKING_WRITE(SocketEvent.OPEN_WRITE); + NON_BLOCKING_WRITE(SocketEvent.OPEN_WRITE), + NON_BLOCKING_ERROR(SocketEvent.ERROR); private final SocketEvent status; diff --git a/test/org/apache/coyote/http2/TestAsyncReadListener.java b/test/org/apache/coyote/http2/TestAsyncReadListener.java index 8b9a9bc9b8..2c02223770 100644 --- a/test/org/apache/coyote/http2/TestAsyncReadListener.java +++ b/test/org/apache/coyote/http2/TestAsyncReadListener.java @@ -47,10 +47,27 @@ public class TestAsyncReadListener extends Http2TestBase { asyncServlet = new AsyncServlet(); } + + @Test + public void testEmptyWindowDefaultReadTimeout() throws Exception { + doTestEmptyWindowMaximumTimeout(false); + } + + @Test - public void testEmptyWindow() throws Exception { + public void testEmptyWindowMaximumReadTimeout() throws Exception { + doTestEmptyWindowMaximumTimeout(true); + } + + + public void doTestEmptyWindowMaximumTimeout(boolean useMaxReadTimeout) throws Exception { http2Connect(); + if (useMaxReadTimeout) { + http2Protocol.setStreamReadTimeout(Integer.MAX_VALUE); + http2Protocol.setReadTimeout(Integer.MAX_VALUE); + } + byte[] headersFrameHeader = new byte[9]; ByteBuffer headersPayload = ByteBuffer.allocate(128); byte[] dataFrameHeader = new byte[9]; diff --git a/test/org/apache/coyote/http2/TestHttp2Section_5_1.java b/test/org/apache/coyote/http2/TestHttp2Section_5_1.java index 391ce865b1..312d5c97be 100644 --- a/test/org/apache/coyote/http2/TestHttp2Section_5_1.java +++ b/test/org/apache/coyote/http2/TestHttp2Section_5_1.java @@ -296,10 +296,10 @@ public class TestHttp2Section_5_1 extends Http2TestBase { // Reset stream 3 (client cancel) sendRst(3, Http2Error.NO_ERROR.getCode()); - // Client reset triggers a write error which in turn triggers a server - // reset + // Client reset triggers both a read error and a write error which in turn trigger two server resets parser.readFrame(); - Assert.assertEquals("3-RST-[5]\n", output.getTrace()); + parser.readFrame(); + Assert.assertEquals("3-RST-[5]\n3-RST-[5]\n", output.getTrace()); output.clearTrace(); // Open up the connection window. diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml index 7ae424d8a4..fab0e6ca0b 100644 --- a/webapps/docs/changelog.xml +++ b/webapps/docs/changelog.xml @@ -138,6 +138,11 @@ writing response headers to the access log. Based on a patch and test case provided by hypnoce. (markt) </fix> + <fix> + <bug>69302</bug>: If an HTTP/2 client resets a stream before the request + body is fully written, ensure that any <code>ReadListener</code> is + notified via a call to <code>ReadListener.onErrror()</code>. (markt) + </fix> </changelog> </subsection> <subsection name="Jasper"> --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org