On Thu, Aug 22, 2024 at 2:33 PM <ma...@apache.org> wrote: > > 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
I'm not sure yet whether this is a good idea. Usually we want to keep things open for further processing. Maybe we should be doing it using the suspended flag instead and in the facades. Rémy > --- > .../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 > --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org