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 <[email protected]> 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: [email protected] For additional commands, e-mail: [email protected]
