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 2019/07/02 20:17:51 UTC

[tomcat] 02/03: Ensure HEAD response is consistent with GET response for HttpServlet

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

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

commit 8e204b92c45f2c2014af295b03c35cb1fab64878
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Tue Jul 2 21:15:56 2019 +0100

    Ensure HEAD response is consistent with GET response for HttpServlet
    
    Fix generation of the HEAD response when the GET response uses chunking
---
 java/javax/servlet/http/HttpServlet.java     | 19 +++++++--
 test/javax/servlet/http/TestHttpServlet.java | 62 ++++++++++++++++++++++++++++
 webapps/docs/changelog.xml                   |  5 +++
 3 files changed, 82 insertions(+), 4 deletions(-)

diff --git a/java/javax/servlet/http/HttpServlet.java b/java/javax/servlet/http/HttpServlet.java
index f7ea58a..12a537a 100644
--- a/java/javax/servlet/http/HttpServlet.java
+++ b/java/javax/servlet/http/HttpServlet.java
@@ -758,7 +758,7 @@ class NoBodyResponse extends HttpServletResponseWrapper {
     // file private
     NoBodyResponse(HttpServletResponse r) {
         super(r);
-        noBody = new NoBodyOutputStream();
+        noBody = new NoBodyOutputStream(this);
     }
 
     // file private
@@ -847,11 +847,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
@@ -860,8 +862,9 @@ class NoBodyOutputStream extends ServletOutputStream {
     }
 
     @Override
-    public void write(int b) {
+    public void write(int b) throws IOException {
         contentLength++;
+        checkCommit();
     }
 
     @Override
@@ -882,6 +885,7 @@ class NoBodyOutputStream extends ServletOutputStream {
         }
 
         contentLength += len;
+        checkCommit();
     }
 
     @Override
@@ -894,4 +898,11 @@ class NoBodyOutputStream extends ServletOutputStream {
     public void setWriteListener(javax.servlet.WriteListener listener) {
         // TODO SERVLET 3.1
     }
+
+    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 feca2c9..331a0f6 100644
--- a/test/javax/servlet/http/TestHttpServlet.java
+++ b/test/javax/servlet/http/TestHttpServlet.java
@@ -111,6 +111,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.addServletMappingDecoded("/chunking", "ChunkingServlet");
+
+        tomcat.start();
+
+        Map<String,List<String>> getHeaders = new HashMap<>();
+        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<>();
+        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;
@@ -141,4 +186,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/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index bccc8b4..de6e67e 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -101,6 +101,11 @@
       <fix>
         Improve parsing of Content-Range headers. (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>
   <subsection name="Coyote">


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