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

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

commit 83199ccf21506a97f1fd1f62a11284cab0ca08ec
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Wed May 18 18:13:51 2022 +0100

    Further improvements to HEAD tests
    
    Try and maintain the original intention of the tests while working
    around the issues created by the encoder changes in Java 19+
---
 .../servlet/http/HttpServletDoHeadBaseTest.java    | 75 ++++++++++++++++++----
 1 file changed, 63 insertions(+), 12 deletions(-)

diff --git a/test/jakarta/servlet/http/HttpServletDoHeadBaseTest.java 
b/test/jakarta/servlet/http/HttpServletDoHeadBaseTest.java
index c967b10bcd..cd1d0de0dd 100644
--- a/test/jakarta/servlet/http/HttpServletDoHeadBaseTest.java
+++ b/test/jakarta/servlet/http/HttpServletDoHeadBaseTest.java
@@ -19,6 +19,7 @@ package jakarta.servlet.http;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.io.PrintWriter;
+import java.lang.reflect.Field;
 import java.nio.ByteBuffer;
 import java.nio.charset.StandardCharsets;
 import java.util.HashMap;
@@ -33,6 +34,9 @@ import org.junit.runners.Parameterized.Parameter;
 
 import org.apache.catalina.LifecycleException;
 import org.apache.catalina.Wrapper;
+import org.apache.catalina.connector.OutputBuffer;
+import org.apache.catalina.connector.Response;
+import org.apache.catalina.connector.ResponseFacade;
 import org.apache.catalina.core.StandardContext;
 import org.apache.catalina.startup.Tomcat;
 import org.apache.coyote.http2.Http2TestBase;
@@ -217,10 +221,13 @@ public class HttpServletDoHeadBaseTest extends 
Http2TestBase {
         @Override
         protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
throws ServletException, IOException {
 
+            int originalBufferSize = resp.getBufferSize();
             int adjustedBufferSize = bufferSize;
+            boolean resetBufferSize = false;
 
             if 
(Boolean.parseBoolean(getServletConfig().getInitParameter(LEGACY_DO_HEAD)) &&
-                    useWriter && JreCompat.isJre19Available()) {
+                    JreCompat.isJre19Available() && 
"HEAD".equals(req.getMethod()) && useWriter &&
+                    resetType != ResetType.NONE) {
                 /*
                  * Using legacy HEAD handling with a Writer on Java 19+.
                  * HttpServlet wraps the response. The test is sensitive to
@@ -229,31 +236,48 @@ public class HttpServletDoHeadBaseTest extends 
Http2TestBase {
                  * characters that can be written before the response is
                  * committed needs to be the same.
                  *
-                 * Internally, Tomcat the response buffer is at least 8192
+                 * Internally, the Tomcat response buffer defaults to 8192
                  * bytes. When a Writer is used, an additional 8192 byte buffer
-                 * is used for char -> byte.
+                 * is used for char->byte.
                  *
-                 * With Java <19, the char -> byte buffer is created as a 8192
+                 * With Java <19, the char->byte buffer used by HttpServlet
+                 * processing HEAD requests in legacy mode is created as a 8192
                  * byte buffer which is consistent with the buffer Tomcat uses
                  * internally.
                  *
-                 * With Java 19+, the char -> byte buffer is created as a 512
-                 * byte buffer. Need to adjust the size of the response buffer
-                 * to account for the smaller char -> byte buffer.
+                 * With Java 19+, the char->byte buffer used by HttpServlet
+                 * processing HEAD requests in legacy mode is created as a 512
+                 * byte buffer.
+                 *
+                 * If the response isn't reset, none of this matters as it is
+                 * just the size of the response buffer and the size of the
+                 * response that determines if chunked encoding is used.
+                 * However, if the response is reset then things get 
interesting
+                 * as the amount of response data that can be written before
+                 * committing the response is the combination of the char->byte
+                 * buffer and the response buffer. Because the char->byte 
buffer
+                 * size in legacy mode varies with Java version, the size of 
the
+                 * response buffer needs to be adjusted to compensate so that
+                 * both GET and HEAD can write the same amount of data before
+                 * committing the response. To make matters worse, to obtain 
the
+                 * correct behaviour the response buffer size needs to be reset
+                 * back to 8192 after the reset.
                  *
                  * This is why the legacy mode is problematic and the new
                  * approach of the container handling HEAD is better.
                  */
 
-                // Response buffer is always no smaller than 8192 bytes
-                if (adjustedBufferSize < 8192) {
-                    adjustedBufferSize = 8192;
+                // Response buffer is always no smaller than originalBufferSize
+                if (adjustedBufferSize < originalBufferSize) {
+                    adjustedBufferSize = originalBufferSize;
                 }
 
                 // Adjust for smaller initial char -> byte buffer in Java 19+
-                // 8192 initial size in Java <19
+                // originalBufferSize initial size in Java <19
                 // 512 initial size in Java 19+
-                adjustedBufferSize = adjustedBufferSize + 8192 - 512;
+                adjustedBufferSize = adjustedBufferSize + originalBufferSize - 
512;
+
+                resetBufferSize = true;
             }
 
             resp.setBufferSize(adjustedBufferSize);
@@ -284,10 +308,16 @@ public class HttpServletDoHeadBaseTest extends 
Http2TestBase {
                     }
                     case BUFFER: {
                         resp.resetBuffer();
+                        if (resetBufferSize) {
+                            resetResponseBuffer(resp, originalBufferSize);
+                        }
                         break;
                     }
                     case FULL: {
                         resp.reset();
+                        if (resetBufferSize) {
+                            resetResponseBuffer(resp, originalBufferSize);
+                        }
                         break;
                     }
                 }
@@ -311,6 +341,27 @@ public class HttpServletDoHeadBaseTest extends 
Http2TestBase {
                 os.write(msg.getBytes(StandardCharsets.UTF_8));
             }
         }
+
+        private void resetResponseBuffer(HttpServletResponse resp, int size) 
throws ServletException {
+            // This bypasses various checks but that is OK in this case.
+            try {
+                ResponseFacade responseFacade = (ResponseFacade) 
((HttpServletResponseWrapper) resp).getResponse();
+
+                Field responseField = 
ResponseFacade.class.getDeclaredField("response");
+                responseField.setAccessible(true);
+                Response response = (Response) 
responseField.get(responseFacade);
+
+                Field outputBufferField = 
Response.class.getDeclaredField("outputBuffer");
+                outputBufferField.setAccessible(true);
+                OutputBuffer outputBuffer = (OutputBuffer) 
outputBufferField.get(response);
+
+                Field byteBufferField = 
OutputBuffer.class.getDeclaredField("bb");
+                byteBufferField.setAccessible(true);
+                byteBufferField.set(outputBuffer, 
ByteBuffer.allocate(Math.max(size, bufferSize)));
+            } catch (NoSuchFieldException | IllegalArgumentException | 
IllegalAccessException e) {
+                throw new ServletException(e);
+            }
+        }
     }
 
 


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

Reply via email to