You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by ma...@apache.org on 2020/02/17 10:07:02 UTC

[tomcat] 02/04: Fix HEAD response when chunking is used

This is an automated email from the ASF dual-hosted git repository.

markt pushed a commit to branch 7.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git

commit c42c1e5a41392b6feb82f790d919dd2cf32b83ce
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Mon Feb 17 10:00:30 2020 +0000

    Fix HEAD response when chunking is used
    
    Ensure that the HEAD response is consistent with the GET response when
    HttpServlet is relied upon to generate the HEAD response and the GET
    response uses chunking.
---
 java/javax/servlet/http/HttpServlet.java           | 19 +++++--
 test/javax/servlet/http/TestHttpServlet.java       | 63 ++++++++++++++++++++++
 .../apache/catalina/startup/TomcatBaseTest.java    |  9 +++-
 webapps/docs/changelog.xml                         |  5 ++
 4 files changed, 90 insertions(+), 6 deletions(-)

diff --git a/java/javax/servlet/http/HttpServlet.java b/java/javax/servlet/http/HttpServlet.java
index fa69ef5..28c2dc8 100644
--- a/java/javax/servlet/http/HttpServlet.java
+++ b/java/javax/servlet/http/HttpServlet.java
@@ -745,7 +745,7 @@ class NoBodyResponse extends HttpServletResponseWrapper {
     // file private
     NoBodyResponse(HttpServletResponse r) {
         super(r);
-        noBody = new NoBodyOutputStream();
+        noBody = new NoBodyOutputStream(this);
     }
 
     // file private
@@ -828,11 +828,13 @@ class NoBodyOutputStream extends ServletOutputStream {
     private static final ResourceBundle lStrings =
         ResourceBundle.getBundle(LSTRING_FILE);
 
+    private final HttpServletResponse response;
+    private boolean flushed = false;
     private int contentLength = 0;
 
     // file private
-    NoBodyOutputStream() {
-        // NOOP
+    NoBodyOutputStream(HttpServletResponse response) {
+        this.response = response;
     }
 
     // file private
@@ -841,8 +843,9 @@ class NoBodyOutputStream extends ServletOutputStream {
     }
 
     @Override
-    public void write(int b) {
+    public void write(int b) throws IOException {
         contentLength++;
+        checkCommit();
     }
 
     @Override
@@ -863,5 +866,13 @@ class NoBodyOutputStream extends ServletOutputStream {
         }
 
         contentLength += len;
+        checkCommit();
+    }
+
+    private void checkCommit() throws IOException {
+        if (!flushed && contentLength > response.getBufferSize()) {
+            response.flushBuffer();
+            flushed = true;
+        }
     }
 }
diff --git a/test/javax/servlet/http/TestHttpServlet.java b/test/javax/servlet/http/TestHttpServlet.java
index 2190baa..d7e78d8 100644
--- a/test/javax/servlet/http/TestHttpServlet.java
+++ b/test/javax/servlet/http/TestHttpServlet.java
@@ -28,6 +28,7 @@ import org.junit.Assert;
 import org.junit.Test;
 
 import org.apache.catalina.core.StandardContext;
+import org.apache.catalina.startup.CaseInsensitiveKeyMap;
 import org.apache.catalina.startup.Tomcat;
 import org.apache.catalina.startup.TomcatBaseTest;
 import org.apache.tomcat.util.buf.ByteChunk;
@@ -113,6 +114,51 @@ public class TestHttpServlet extends TomcatBaseTest {
     }
 
 
+    @Test
+    public void testChunkingWithHead() throws Exception {
+        Tomcat tomcat = getTomcatInstance();
+
+        // No file system docBase required
+        StandardContext ctx = (StandardContext) tomcat.addContext("", null);
+
+        ChunkingServlet s = new ChunkingServlet();
+        Tomcat.addServlet(ctx, "ChunkingServlet", s);
+        ctx.addServletMapping("/chunking", "ChunkingServlet");
+
+        tomcat.start();
+
+        Map<String,List<String>> getHeaders = new CaseInsensitiveKeyMap<List<String>>();
+        String path = "http://localhost:" + getPort() + "/chunking";
+        ByteChunk out = new ByteChunk();
+
+        int rc = getUrl(path, out, getHeaders);
+        Assert.assertEquals(HttpServletResponse.SC_OK, rc);
+        out.recycle();
+
+        Map<String,List<String>> headHeaders = new HashMap<String,List<String>>();
+        rc = headUrl(path, out, headHeaders);
+        Assert.assertEquals(HttpServletResponse.SC_OK, rc);
+
+        // Headers should be the same (apart from Date)
+        Assert.assertEquals(getHeaders.size(), headHeaders.size());
+        for (Map.Entry<String, List<String>> getHeader : getHeaders.entrySet()) {
+            String headerName = getHeader.getKey();
+            if ("date".equalsIgnoreCase(headerName)) {
+                continue;
+            }
+            Assert.assertTrue(headerName, headHeaders.containsKey(headerName));
+            List<String> getValues = getHeader.getValue();
+            List<String> headValues = headHeaders.get(headerName);
+            Assert.assertEquals(getValues.size(), headValues.size());
+            for (String value : getValues) {
+                Assert.assertTrue(headValues.contains(value));
+            }
+        }
+
+        tomcat.stop();
+    }
+
+
     private static class Bug57602ServletOuter extends HttpServlet {
 
         private static final long serialVersionUID = 1L;
@@ -143,4 +189,21 @@ public class TestHttpServlet extends TomcatBaseTest {
             pw.println("Included");
         }
     }
+
+
+    private static class ChunkingServlet extends HttpServlet {
+
+        private static final long serialVersionUID = 1L;
+
+        @Override
+        protected void doGet(HttpServletRequest req, HttpServletResponse resp)
+                throws ServletException, IOException {
+            resp.setContentType("text/plain");
+            resp.setCharacterEncoding("UTF-8");
+            PrintWriter pw = resp.getWriter();
+            // Trigger chunking
+            pw.write(new char[8192 * 16]);
+            pw.println("Data");
+        }
+    }
 }
diff --git a/test/org/apache/catalina/startup/TomcatBaseTest.java b/test/org/apache/catalina/startup/TomcatBaseTest.java
index e234d67..eec6416 100644
--- a/test/org/apache/catalina/startup/TomcatBaseTest.java
+++ b/test/org/apache/catalina/startup/TomcatBaseTest.java
@@ -667,8 +667,13 @@ public abstract class TomcatBaseTest extends LoggingBaseTest {
         connection.connect();
         int rc = connection.getResponseCode();
         if (resHead != null) {
-            Map<String, List<String>> head = connection.getHeaderFields();
-            resHead.putAll(head);
+            // Skip the entry with null key that is used for the response line
+            // that some Map implementations may not accept.
+            for (Map.Entry<String, List<String>> entry : connection.getHeaderFields().entrySet()) {
+                if (entry.getKey() != null) {
+                    resHead.put(entry.getKey(), entry.getValue());
+                }
+            }
         }
         InputStream is;
         if (rc < 400) {
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index b41e0d2..e2a2cd4 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -67,6 +67,11 @@
         file generated from the default <code>web.xml</code> so the MIME type
         mappings are consistent regardless of how Tomcat is started. (markt)
       </fix>
+      <fix>
+        Ensure that the HEAD response is consistent with the GET response when
+        <code>HttpServlet</code> is relied upon to generate the HEAD response
+        and the GET response uses chunking. (markt)
+      </fix>
     </changelog>
   </subsection>
 </section>


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org