This is an automated email from the ASF dual-hosted git repository. markt pushed a commit to branch 9.0.x in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/9.0.x by this push: new 9e767b24cd Allow applications to trigger sending of 103 early hints (#764) 9e767b24cd is described below commit 9e767b24cd8092964246f95ebe8e90cb8073e69d Author: Christopher Schultz <ch...@christopherschultz.net> AuthorDate: Wed Nov 6 05:38:27 2024 -0500 Allow applications to trigger sending of 103 early hints (#764) * Allow applications to trigger sending of 103 early hints * Add unit tests --- java/org/apache/catalina/connector/Response.java | 39 +++++++++-- .../apache/catalina/connector/ResponseFacade.java | 36 ++++++++-- .../apache/coyote/http11/TestHttp11Processor.java | 81 +++++++++++++++++++++- webapps/docs/changelog.xml | 5 ++ 4 files changed, 149 insertions(+), 12 deletions(-) diff --git a/java/org/apache/catalina/connector/Response.java b/java/org/apache/catalina/connector/Response.java index 0e8fe8d2c5..c79bb3b5a4 100644 --- a/java/org/apache/catalina/connector/Response.java +++ b/java/org/apache/catalina/connector/Response.java @@ -91,6 +91,7 @@ public class Response implements HttpServletResponse { System.getProperty("org.apache.catalina.connector.Response.ENFORCE_ENCODING_IN_GET_WRITER", "true")); } + protected static final int SC_EARLY_HINTS = 103; // ----------------------------------------------------- Instance Variables @@ -1137,12 +1138,32 @@ public class Response implements HttpServletResponse { } + /** + * {@inheritDoc} + * <p> + * Calling <code>sendError</code> with a status code of 103 differs from the usual + * behavior. Sending 103 will trigger the container to send a "103 Early Hints" informational response including all + * current headers. The application can continue to use the request and response after calling sendError with a 103 + * status code, including triggering a more typical response of any type. + * <p> + * Starting with Tomcat 12, applications should use {@link #sendEarlyHints}. + */ @Override public void sendError(int status) throws IOException { sendError(status, null); } + /** + * {@inheritDoc} + * <p> + * Calling <code>sendError</code> with a status code of 103 differs from the usual + * behavior. Sending 103 will trigger the container to send a "103 Early Hints" informational response including all + * current headers. The application can continue to use the request and response after calling sendError with a 103 + * status code, including triggering a more typical response of any type. + * <p> + * Starting with Tomcat 12, applications should use {@link #sendEarlyHints}. + */ @Override public void sendError(int status, String message) throws IOException { @@ -1155,16 +1176,20 @@ public class Response implements HttpServletResponse { return; } - setError(); + if (SC_EARLY_HINTS == status) { + sendEarlyHints(); + } else { + setError(); - getCoyoteResponse().setStatus(status); - getCoyoteResponse().setMessage(message); + getCoyoteResponse().setStatus(status); + getCoyoteResponse().setMessage(message); - // Clear any data content that has been buffered - resetBuffer(); + // Clear any data content that has been buffered + resetBuffer(); - // Cause the response to be finished (from the application perspective) - setSuspended(true); + // Cause the response to be finished (from the application perspective) + setSuspended(true); + } } diff --git a/java/org/apache/catalina/connector/ResponseFacade.java b/java/org/apache/catalina/connector/ResponseFacade.java index 068c68982b..dc53b090c3 100644 --- a/java/org/apache/catalina/connector/ResponseFacade.java +++ b/java/org/apache/catalina/connector/ResponseFacade.java @@ -329,6 +329,16 @@ public class ResponseFacade implements HttpServletResponse { response.sendEarlyHints(); } + /** + * {@inheritDoc} + * <p> + * Calling <code>sendError</code> with a status code of 103 differs from the usual + * behavior. Sending 103 will trigger the container to send a "103 Early Hints" informational response including all + * current headers. The application can continue to use the request and response after calling sendError with a 103 + * status code, including triggering a more typical response of any type. + * <p> + * Starting with Tomcat 12, applications should use {@link #sendEarlyHints}. + */ @Override public String encodeUrl(String url) { checkFacade(); @@ -346,16 +356,34 @@ public class ResponseFacade implements HttpServletResponse { @Override public void sendError(int sc, String msg) throws IOException { checkCommitted("coyoteResponse.sendError.ise"); - response.setAppCommitted(true); - response.sendError(sc, msg); + if (Response.SC_EARLY_HINTS == sc) { + sendEarlyHints(); + } else { + response.setAppCommitted(true); + response.sendError(sc, msg); + } } + /** + * {@inheritDoc} + * <p> + * Calling <code>sendError</code> with a status code of 103 differs from the usual + * behavior. Sending 103 will trigger the container to send a "103 Early Hints" informational response including all + * current headers. The application can continue to use the request and response after calling sendError with a 103 + * status code, including triggering a more typical response of any type. + * <p> + * Starting with Tomcat 12, applications should use {@link #sendEarlyHints}. + */ @Override public void sendError(int sc) throws IOException { checkCommitted("coyoteResponse.sendError.ise"); - response.setAppCommitted(true); - response.sendError(sc); + if (Response.SC_EARLY_HINTS == sc) { + sendEarlyHints(); + } else { + response.setAppCommitted(true); + response.sendError(sc); + } } diff --git a/test/org/apache/coyote/http11/TestHttp11Processor.java b/test/org/apache/coyote/http11/TestHttp11Processor.java index 4d97532999..598ba88ef1 100644 --- a/test/org/apache/coyote/http11/TestHttp11Processor.java +++ b/test/org/apache/coyote/http11/TestHttp11Processor.java @@ -1946,16 +1946,95 @@ public class TestHttp11Processor extends TomcatBaseTest { Assert.assertEquals(HttpServletResponse.SC_OK, client.getStatusCode()); } + @Test + public void testEarlyHintsSendError() throws Exception { + Tomcat tomcat = getTomcatInstance(); + + // No file system docBase required + Context ctx = getProgrammaticRootContext(); + + // Add servlet + Tomcat.addServlet(ctx, "EarlyHintsServlet", new EarlyHintsServlet(true, null)); + ctx.addServletMappingDecoded("/ehs", "EarlyHintsServlet"); + + tomcat.start(); + + String request = "GET /ehs HTTP/1.1" + SimpleHttpClient.CRLF + + "Host: localhost:" + getPort() + SimpleHttpClient.CRLF + + SimpleHttpClient.CRLF; + + Client client = new Client(tomcat.getConnector().getLocalPort()); + client.setRequest(new String[] { request }); + + client.connect(600000, 600000); + client.processRequest(false); + + Assert.assertEquals(103, client.getStatusCode()); + + client.readResponse(false); + Assert.assertEquals(HttpServletResponse.SC_OK, client.getStatusCode()); + } + + + @Test + public void testEarlyHintsSendErrorWithMessage() throws Exception { + Tomcat tomcat = getTomcatInstance(); + + // No file system docBase required + Context ctx = getProgrammaticRootContext(); + + // Add servlet + Tomcat.addServlet(ctx, "EarlyHintsServlet", new EarlyHintsServlet(true, "ignored")); + ctx.addServletMappingDecoded("/ehs", "EarlyHintsServlet"); + + tomcat.start(); + + String request = "GET /ehs HTTP/1.1" + SimpleHttpClient.CRLF + + "Host: localhost:" + getPort() + SimpleHttpClient.CRLF + + SimpleHttpClient.CRLF; + + Client client = new Client(tomcat.getConnector().getLocalPort()); + client.setRequest(new String[] { request }); + + client.connect(600000, 600000); + client.processRequest(false); + + Assert.assertEquals(103, client.getStatusCode()); + + client.readResponse(false); + Assert.assertEquals(HttpServletResponse.SC_OK, client.getStatusCode()); + } + + private static class EarlyHintsServlet extends HttpServlet { private static final long serialVersionUID = 1L; + private final boolean useSendError; + private final String errorString; + + EarlyHintsServlet() { + this(false, null); + } + + EarlyHintsServlet(boolean useSendError, String errorString) { + this.useSendError = useSendError; + this.errorString = errorString; + } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.addHeader("Link", "</style.css>; rel=preload; as=style"); - ((ResponseFacade) resp).sendEarlyHints(); + if (useSendError) { + if (null == errorString) { + resp.sendError(103); + } else { + resp.sendError(103, errorString); + } + } else { + ((ResponseFacade) resp).sendEarlyHints(); + } resp.setCharacterEncoding("UTF-8"); resp.setContentType("text/plain"); diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml index c378b8d2ae..8d77263b5a 100644 --- a/webapps/docs/changelog.xml +++ b/webapps/docs/changelog.xml @@ -240,6 +240,11 @@ levels of nested includes. Based on a patch provided by John Engebretson. (markt) </fix> + <add> + All applications to send an early hints informational response by + calling <code>HttpServletResponse.sendError()</code> with a status code + of 103. (schultz) + </add> </changelog> </subsection> <subsection name="Coyote"> --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org