Author: markt Date: Wed May 30 14:11:49 2012 New Revision: 1344266 URL: http://svn.apache.org/viewvc?rev=1344266&view=rev Log: Fix https://issues.apache.org/bugzilla/show_bug.cgi?id=53169 Allow servlets to opt to avoid chunked encoding with a response of unknown length by setting the Connection: close header. Based on a patch suggested by Philippe Marschall.
Modified: tomcat/trunk/java/org/apache/coyote/http11/AbstractHttp11Processor.java tomcat/trunk/test/org/apache/coyote/http11/TestAbstractHttp11Processor.java Modified: tomcat/trunk/java/org/apache/coyote/http11/AbstractHttp11Processor.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http11/AbstractHttp11Processor.java?rev=1344266&r1=1344265&r2=1344266&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/coyote/http11/AbstractHttp11Processor.java (original) +++ tomcat/trunk/java/org/apache/coyote/http11/AbstractHttp11Processor.java Wed May 30 14:11:49 2012 @@ -1131,7 +1131,7 @@ public abstract class AbstractHttp11Proc 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) { @@ -1370,13 +1370,17 @@ public abstract class AbstractHttp11Proc } long contentLength = response.getContentLengthLong(); + boolean connectionClosePresent = false; if (contentLength != -1) { headers.setValue("Content-Length").setLong(contentLength); getOutputBuffer().addActiveFilter (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 + connectionClosePresent = isConnectionClose(headers); + if (entityBody && http11 && !connectionClosePresent) { getOutputBuffer().addActiveFilter (outputFilters[Constants.CHUNKED_FILTER]); contentDelimitation = true; @@ -1422,7 +1426,11 @@ public abstract class AbstractHttp11Proc // Connection: close header. keepAlive = keepAlive && !statusDropsConnection(statusCode); if (!keepAlive) { - headers.addValue(Constants.CONNECTION).setString(Constants.CLOSE); + // Avoid adding the close header twice + if (!connectionClosePresent) { + headers.addValue(Constants.CONNECTION).setString( + Constants.CLOSE); + } } else if (!http11 && !error) { headers.addValue(Constants.CONNECTION).setString(Constants.KEEPALIVE); } @@ -1447,6 +1455,14 @@ public abstract class AbstractHttp11Proc } + 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); /** Modified: tomcat/trunk/test/org/apache/coyote/http11/TestAbstractHttp11Processor.java URL: http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/coyote/http11/TestAbstractHttp11Processor.java?rev=1344266&r1=1344265&r2=1344266&view=diff ============================================================================== --- tomcat/trunk/test/org/apache/coyote/http11/TestAbstractHttp11Processor.java (original) +++ tomcat/trunk/test/org/apache/coyote/http11/TestAbstractHttp11Processor.java Wed May 30 14:11:49 2012 @@ -16,20 +16,28 @@ */ 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 java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; 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.apache.tomcat.util.buf.ByteChunk; +import org.junit.Test; public class TestAbstractHttp11Processor extends TomcatBaseTest { @@ -239,6 +247,105 @@ public class TestAbstractHttp11Processor assertEquals("OK", client.getResponseBody()); } + + @Test + public void testChunking11NoContentLength() throws Exception { + Tomcat tomcat = getTomcatInstance(); + + // Must have a real docBase - just use temp + Context ctxt = tomcat.addContext("", + System.getProperty("java.io.tmpdir")); + + Tomcat.addServlet(ctxt, "NoContentLengthFlushingServlet", + new NoContentLengthFlushingServlet()); + ctxt.addServletMapping("/test", "NoContentLengthFlushingServlet"); + + tomcat.start(); + + ByteChunk responseBody = new ByteChunk(); + Map<String,List<String>> responseHeaders = + new HashMap<String,List<String>>(); + int rc = getUrl("http://localhost:" + getPort() + "/test", responseBody, + responseHeaders); + + assertEquals(HttpServletResponse.SC_OK, rc); + assertTrue(responseHeaders.containsKey("Transfer-Encoding")); + List<String> encodings = responseHeaders.get("Transfer-Encoding"); + assertEquals(1, encodings.size()); + assertEquals("chunked", encodings.get(0)); + } + + @Test + public void testNoChunking11NoContentLengthConnectionClose() + throws Exception { + + Tomcat tomcat = getTomcatInstance(); + + // Must have a real docBase - just use temp + Context ctxt = tomcat.addContext("", + System.getProperty("java.io.tmpdir")); + + Tomcat.addServlet(ctxt, "NoContentLengthConnectionCloseFlushingServlet", + new NoContentLengthConnectionCloseFlushingServlet()); + ctxt.addServletMapping("/test", + "NoContentLengthConnectionCloseFlushingServlet"); + + tomcat.start(); + + ByteChunk responseBody = new ByteChunk(); + Map<String,List<String>> responseHeaders = + new HashMap<String,List<String>>(); + int rc = getUrl("http://localhost:" + getPort() + "/test", responseBody, + responseHeaders); + + assertEquals(HttpServletResponse.SC_OK, rc); + + assertTrue(responseHeaders.containsKey("Connection")); + List<String> connections = responseHeaders.get("Connection"); + assertEquals(1, connections.size()); + assertEquals("close", connections.get(0)); + + assertFalse(responseHeaders.containsKey("Transfer-Encoding")); + + assertEquals("OK", responseBody.toString()); + } + + // flushes with no content-length set + // should result in chunking on HTTP 1.1 + private 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 + private 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(); + } + } + private static final class Client extends SimpleHttpClient { public Client(int port) { --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org