Index: test/webapp-3.0/WEB-INF/web.xml =================================================================== --- test/webapp-3.0/WEB-INF/web.xml (revision 1332331) +++ test/webapp-3.0/WEB-INF/web.xml (working copy) @@ -95,6 +95,26 @@ Bug49922 *.od + + NoContentLengthFlushingServlet + + org.apache.catalina.core.TestStandardContext$NoContentLengthFlushingServlet + + + + NoContentLengthFlushingServlet + /noContentLengthFlushingServlet/servlet + + + NoContentLengthConnectionCloseFlushingServlet + + org.apache.catalina.core.TestStandardContext$NoContentLengthConnectionCloseFlushingServlet + + + + NoContentLengthConnectionCloseFlushingServlet + /noContentLengthConnectionCloseFlushingServlet/servlet + Index: test/org/apache/coyote/http11/TestAbstractHttp11Processor.java =================================================================== --- test/org/apache/coyote/http11/TestAbstractHttp11Processor.java (revision 1332331) +++ test/org/apache/coyote/http11/TestAbstractHttp11Processor.java (working copy) @@ -16,20 +16,19 @@ */ package org.apache.coyote.http11; -import java.io.File; -import java.io.IOException; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import org.junit.Test; +import java.io.File; +import java.io.IOException; import org.apache.catalina.Context; import org.apache.catalina.startup.SimpleHttpClient; import org.apache.catalina.startup.TesterServlet; import org.apache.catalina.startup.Tomcat; import org.apache.catalina.startup.TomcatBaseTest; +import org.junit.Test; public class TestAbstractHttp11Processor extends TomcatBaseTest { @@ -238,7 +237,62 @@ assertTrue(client.isResponse200()); assertEquals("OK", client.getResponseBody()); } + + @Test + public void testChunking11NoContentLength() throws Exception { + Tomcat tomcat = getTomcatInstance(); + + // Use the normal Tomcat ROOT context + File root = new File("test/webapp-3.0"); + tomcat.addWebapp("", root.getAbsolutePath()); + + tomcat.start(); + + String request = + "GET /noContentLengthFlushingServlet/servlet HTTP/1.1" + SimpleHttpClient.CRLF + + "Host: any" + SimpleHttpClient.CRLF + + "Accept: text/event-stream" + + SimpleHttpClient.CRLF + + SimpleHttpClient.CRLF; + + Client client = new Client(tomcat.getConnector().getLocalPort()); + client.setRequest(new String[] {request}); + + client.connect(); + client.processRequest(); + assertTrue(client.isResponse200()); + assertTrue(client.getResponseHeaders().contains("Transfer-Encoding: chunked")); + } + + @Test + public void testNoChunking11NoContentLengthConnectionClose() throws Exception { + Tomcat tomcat = getTomcatInstance(); + + // Use the normal Tomcat ROOT context + File root = new File("test/webapp-3.0"); + tomcat.addWebapp("", root.getAbsolutePath()); + + tomcat.start(); + + String request = + "GET /noContentLengthConnectionCloseFlushingServlet/servlet HTTP/1.1" + SimpleHttpClient.CRLF + + "Host: any" + SimpleHttpClient.CRLF + + "Accept: text/event-stream" + + SimpleHttpClient.CRLF + + SimpleHttpClient.CRLF; + + Client client = new Client(tomcat.getConnector().getLocalPort()); + client.setRequest(new String[] {request}); + + client.connect(); + client.processRequest(); + assertTrue(client.isResponse200()); + assertTrue(client.getResponseHeaders().contains("Connection: close")); + assertFalse(client.getResponseHeaders().contains("Transfer-Encoding: chunked")); + assertEquals("OK", client.getResponseBody()); + } + private static final class Client extends SimpleHttpClient { public Client(int port) { Index: test/org/apache/catalina/core/TestStandardContext.java =================================================================== --- test/org/apache/catalina/core/TestStandardContext.java (revision 1332331) +++ test/org/apache/catalina/core/TestStandardContext.java (working copy) @@ -440,7 +440,43 @@ } } } + + // flushes with no content-length set + // should result in chunking on HTTP 1.1 + public static final class NoContentLengthFlushingServlet extends HttpServlet { + private static final long serialVersionUID = 1L; + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + resp.setStatus(HttpServletResponse.SC_OK); + resp.setContentType("text/plain"); + resp.getWriter().write("OK"); + resp.flushBuffer(); + } + + } + + // flushes with no content-length set but sets Connection: close header + // should no result in chunking on HTTP 1.1 + public static final class NoContentLengthConnectionCloseFlushingServlet extends HttpServlet { + + private static final long serialVersionUID = 1L; + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + resp.setStatus(HttpServletResponse.SC_OK); + resp.setContentType("text/event-stream"); + resp.addHeader("Connection", "close"); + resp.flushBuffer(); + resp.getWriter().write("OK"); + resp.flushBuffer(); + } + + } + /** * Test case for bug 49711: HttpServletRequest.getParts does not work * in a filter. Index: java/org/apache/coyote/http11/AbstractHttp11Processor.java =================================================================== --- java/org/apache/coyote/http11/AbstractHttp11Processor.java (revision 1332331) +++ java/org/apache/coyote/http11/AbstractHttp11Processor.java (working copy) @@ -1131,7 +1131,7 @@ MimeHeaders headers = request.getMimeHeaders(); // Check connection header - MessageBytes connectionValueMB = headers.getValue("connection"); + MessageBytes connectionValueMB = headers.getValue(Constants.CONNECTION); if (connectionValueMB != null) { ByteChunk connectionValueBC = connectionValueMB.getByteChunk(); if (findBytes(connectionValueBC, Constants.CLOSE_BYTES) != -1) { @@ -1376,7 +1376,10 @@ (outputFilters[Constants.IDENTITY_FILTER]); contentDelimitation = true; } else { - if (entityBody && http11) { + // if the response code supports an entity body + // and we're on HTTP 1.1 then we chunk + // unless we have a Connection: close header + if (entityBody && http11 && !isConnectionClose(headers)) { getOutputBuffer().addActiveFilter (outputFilters[Constants.CHUNKED_FILTER]); contentDelimitation = true; @@ -1446,6 +1449,14 @@ getOutputBuffer().endHeaders(); } + + private boolean isConnectionClose(MimeHeaders headers) { + MessageBytes connection = headers.getValue(Constants.CONNECTION); + if (connection == null) { + return false; + } + return connection.equals(Constants.CLOSE); + } abstract boolean prepareSendfile(OutputFilter[] outputFilters);