This is an automated email from the ASF dual-hosted git repository. markt pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/main by this push: new 69eff83577 Expected behaviour has been clarified when writing >= c-l bytes to body 69eff83577 is described below commit 69eff83577f7c00cbaaca9384ab4b1989f516797 Author: Mark Thomas <ma...@apache.org> AuthorDate: Thu Aug 22 13:33:10 2024 +0100 Expected behaviour has been clarified when writing >= c-l bytes to body --- .../catalina/connector/LocalStrings.properties | 1 + .../apache/catalina/connector/OutputBuffer.java | 30 ++++++++++--- .../catalina/connector/TestCoyoteOutputStream.java | 50 ++++++++++++++++++++++ 3 files changed, 76 insertions(+), 5 deletions(-) diff --git a/java/org/apache/catalina/connector/LocalStrings.properties b/java/org/apache/catalina/connector/LocalStrings.properties index 077cf46136..d20cd26bc4 100644 --- a/java/org/apache/catalina/connector/LocalStrings.properties +++ b/java/org/apache/catalina/connector/LocalStrings.properties @@ -83,6 +83,7 @@ coyoteResponse.setBufferSize.ise=Cannot change buffer size after data has been w inputBuffer.requiresNonBlocking=Not available in non blocking mode inputBuffer.streamClosed=Stream closed +outputBuffer.closed=The response may not be written to once it has been closed outputBuffer.writeNull=The String argument to write(String,int,int) may not be null request.asyncNotSupported=A filter or servlet of the current chain does not support asynchronous operations. diff --git a/java/org/apache/catalina/connector/OutputBuffer.java b/java/org/apache/catalina/connector/OutputBuffer.java index b185561ef3..04cf82e632 100644 --- a/java/org/apache/catalina/connector/OutputBuffer.java +++ b/java/org/apache/catalina/connector/OutputBuffer.java @@ -358,11 +358,11 @@ public class OutputBuffer extends Writer { private void writeBytes(byte b[], int off, int len) throws IOException { if (closed) { - return; + throw new IOException(sm.getString("outputBuffer.closed")); } append(b, off, len); - bytesWritten += len; + updateBytesWritten(len); // if called from within flush(), then immediately flush // remaining bytes @@ -376,12 +376,12 @@ public class OutputBuffer extends Writer { private void writeBytes(ByteBuffer from) throws IOException { if (closed) { - return; + throw new IOException(sm.getString("outputBuffer.closed")); } int remaining = from.remaining(); append(from); - bytesWritten += remaining; + updateBytesWritten(remaining); // if called from within flush(), then immediately flush // remaining bytes @@ -394,6 +394,10 @@ public class OutputBuffer extends Writer { public void writeByte(int b) throws IOException { + if (closed) { + throw new IOException(sm.getString("outputBuffer.closed")); + } + if (suspended) { return; } @@ -403,8 +407,24 @@ public class OutputBuffer extends Writer { } transfer((byte) b, bb); - bytesWritten++; + updateBytesWritten(1); + } + + private void updateBytesWritten(int increment) throws IOException { + bytesWritten += increment; + int contentLength = coyoteResponse.getContentLength(); + + /* + * Handle the requirements of section 5.7 of the Servlet specification - Closure of the Response Object. + * + * Currently this just handles the simple case. There is work in progress to better define what should happen if + * an attempt is made to write > content-length bytes. When that work is complete, this is likely where the + * implementation will end up. + */ + if (contentLength != -1 && bytesWritten >= contentLength) { + close(); + } } diff --git a/test/org/apache/catalina/connector/TestCoyoteOutputStream.java b/test/org/apache/catalina/connector/TestCoyoteOutputStream.java index d808db76f7..bf282f5d67 100644 --- a/test/org/apache/catalina/connector/TestCoyoteOutputStream.java +++ b/test/org/apache/catalina/connector/TestCoyoteOutputStream.java @@ -21,6 +21,7 @@ import java.io.IOException; import java.io.RandomAccessFile; import java.nio.channels.FileChannel.MapMode; import java.nio.charset.StandardCharsets; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import jakarta.servlet.AsyncContext; @@ -290,4 +291,53 @@ public class TestCoyoteOutputStream extends TomcatBaseTest { } } + + + @Test + public void testWriteAfterBodyComplete() throws Exception { + Tomcat tomcat = getTomcatInstance(); + + Context root = tomcat.addContext("", TEMP_DIR); + Tomcat.addServlet(root, "servlet", new WriteAfterBodyCompleteServlet()); + root.addServletMappingDecoded("/", "servlet"); + + tomcat.start(); + + ByteChunk bc = new ByteChunk(); + int rc = getUrl("http://localhost:" + getPort() + "/", bc, null, null); + Assert.assertEquals(HttpServletResponse.SC_OK, rc); + + // Wait upto 5s + int count = 0; + boolean exceptionSeen = WriteAfterBodyCompleteServlet.exceptionSeen.get(); + while (count < 50 && !exceptionSeen) { + Thread.sleep(100); + exceptionSeen = WriteAfterBodyCompleteServlet.exceptionSeen.get(); + count++; + } + Assert.assertTrue(exceptionSeen); + } + + + private static final class WriteAfterBodyCompleteServlet extends HttpServlet { + + private static final long serialVersionUID = 1L; + private static final AtomicBoolean exceptionSeen = new AtomicBoolean(false); + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + resp.setCharacterEncoding(StandardCharsets.UTF_8); + resp.setContentType("text/plain"); + resp.setContentLengthLong(10); + ServletOutputStream sos = resp.getOutputStream(); + sos.write("0123456789".getBytes(StandardCharsets.UTF_8)); + + // sos should now be closed. A further write should trigger an error. + try { + sos.write("anything".getBytes(StandardCharsets.UTF_8)); + } catch (IOException ioe) { + exceptionSeen.set(true); + } + } + } } --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org