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

commit 5c2bbbbcceaf9aeb7db55f92f28530eb9e9fb417
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Thu Jul 4 15:48:18 2024 +0100

    Implement early hints for HTTP/1.1
---
 java/org/apache/catalina/connector/Response.java   | 14 +++++++
 .../apache/catalina/connector/ResponseFacade.java  |  4 ++
 java/org/apache/coyote/AbstractProcessor.java      | 11 +++++
 java/org/apache/coyote/ActionCode.java             |  7 +++-
 java/org/apache/coyote/ajp/AjpProcessor.java       |  7 ++++
 .../apache/coyote/http11/Http11OutputBuffer.java   |  3 ++
 java/org/apache/coyote/http11/Http11Processor.java |  8 ++++
 java/org/apache/coyote/http2/StreamProcessor.java  |  7 ++++
 .../apache/coyote/http11/TestHttp11Processor.java  | 49 ++++++++++++++++++++++
 9 files changed, 109 insertions(+), 1 deletion(-)

diff --git a/java/org/apache/catalina/connector/Response.java 
b/java/org/apache/catalina/connector/Response.java
index d751dc231f..0e8fe8d2c5 100644
--- a/java/org/apache/catalina/connector/Response.java
+++ b/java/org/apache/catalina/connector/Response.java
@@ -1123,6 +1123,20 @@ public class Response implements HttpServletResponse {
     }
 
 
+    public void sendEarlyHints() {
+        if (isCommitted()) {
+            return;
+        }
+
+        // Ignore any call from an included servlet
+        if (included) {
+            return;
+        }
+
+        getCoyoteResponse().action(ActionCode.EARLY_HINTS, null);
+    }
+
+
     @Override
     public void sendError(int status) throws IOException {
         sendError(status, null);
diff --git a/java/org/apache/catalina/connector/ResponseFacade.java 
b/java/org/apache/catalina/connector/ResponseFacade.java
index 89aeecf0f9..068c68982b 100644
--- a/java/org/apache/catalina/connector/ResponseFacade.java
+++ b/java/org/apache/catalina/connector/ResponseFacade.java
@@ -325,6 +325,10 @@ public class ResponseFacade implements HttpServletResponse 
{
     }
 
 
+    public void sendEarlyHints() {
+        response.sendEarlyHints();
+    }
+
     @Override
     public String encodeUrl(String url) {
         checkFacade();
diff --git a/java/org/apache/coyote/AbstractProcessor.java 
b/java/org/apache/coyote/AbstractProcessor.java
index 108aa82538..2e1c1fda3c 100644
--- a/java/org/apache/coyote/AbstractProcessor.java
+++ b/java/org/apache/coyote/AbstractProcessor.java
@@ -393,6 +393,14 @@ public abstract class AbstractProcessor extends 
AbstractProcessorLight implement
                 ack((ContinueResponseTiming) param);
                 break;
             }
+            case EARLY_HINTS: {
+                try {
+                    earlyHints();
+                } catch (IOException e) {
+                    handleIOException(e);
+                }
+                break;
+            }
             case CLIENT_FLUSH: {
                 action(ActionCode.COMMIT, null);
                 try {
@@ -750,6 +758,9 @@ public abstract class AbstractProcessor extends 
AbstractProcessorLight implement
     protected abstract void ack(ContinueResponseTiming continueResponseTiming);
 
 
+    protected abstract void earlyHints() throws IOException;
+
+
     /**
      * Callback to write data from the buffer.
      * @throws IOException IO exception during the write
diff --git a/java/org/apache/coyote/ActionCode.java 
b/java/org/apache/coyote/ActionCode.java
index 349c43fe2a..84d0eda0ea 100644
--- a/java/org/apache/coyote/ActionCode.java
+++ b/java/org/apache/coyote/ActionCode.java
@@ -274,5 +274,10 @@ public enum ActionCode {
     /**
      * Obtain the stream identifier for the request. Used with multiplexing 
protocols such as HTTP/2.
      */
-    STREAM_ID
+    STREAM_ID,
+
+    /**
+     * Send an RFC 8297 Early Hints informational response.
+     */
+    EARLY_HINTS
 }
diff --git a/java/org/apache/coyote/ajp/AjpProcessor.java 
b/java/org/apache/coyote/ajp/AjpProcessor.java
index 08c29a97a8..fba8fd624d 100644
--- a/java/org/apache/coyote/ajp/AjpProcessor.java
+++ b/java/org/apache/coyote/ajp/AjpProcessor.java
@@ -1045,6 +1045,13 @@ public class AjpProcessor extends AbstractProcessor {
     }
 
 
+    @Override
+    protected void earlyHints() throws IOException {
+        // TODO Auto-generated method stub
+        // NO-OP for now
+    }
+
+
     @Override
     protected final int available(boolean doRead) {
         if (endOfStream) {
diff --git a/java/org/apache/coyote/http11/Http11OutputBuffer.java 
b/java/org/apache/coyote/http11/Http11OutputBuffer.java
index 2c51cdfb45..6967c30996 100644
--- a/java/org/apache/coyote/http11/Http11OutputBuffer.java
+++ b/java/org/apache/coyote/http11/Http11OutputBuffer.java
@@ -300,7 +300,10 @@ public class Http11OutputBuffer implements 
HttpOutputBuffer {
      */
     protected void commit() throws IOException {
         response.setCommitted(true);
+        writeHeaders();
+    }
 
+    protected void writeHeaders() throws IOException {
         if (headerBuffer.position() > 0) {
             // Sending the response header buffer
             headerBuffer.flip();
diff --git a/java/org/apache/coyote/http11/Http11Processor.java 
b/java/org/apache/coyote/http11/Http11Processor.java
index 045c2ed8b1..1389185e90 100644
--- a/java/org/apache/coyote/http11/Http11Processor.java
+++ b/java/org/apache/coyote/http11/Http11Processor.java
@@ -1243,6 +1243,14 @@ public class Http11Processor extends AbstractProcessor {
     }
 
 
+    @Override
+    protected void earlyHints() throws IOException {
+        writeHeaders(103, response.getMimeHeaders());
+        outputBuffer.writeHeaders();
+        outputBuffer.resetHeaderBuffer();
+    }
+
+
     @Override
     protected final void flush() throws IOException {
         outputBuffer.flush();
diff --git a/java/org/apache/coyote/http2/StreamProcessor.java 
b/java/org/apache/coyote/http2/StreamProcessor.java
index 872cc75687..afd199c97c 100644
--- a/java/org/apache/coyote/http2/StreamProcessor.java
+++ b/java/org/apache/coyote/http2/StreamProcessor.java
@@ -261,6 +261,13 @@ class StreamProcessor extends AbstractProcessor {
     }
 
 
+    @Override
+    protected void earlyHints() throws IOException {
+        // TODO Auto-generated method stub
+        // NO-OP for now
+    }
+
+
     @Override
     protected final void flush() throws IOException {
         stream.getOutputBuffer().flush();
diff --git a/test/org/apache/coyote/http11/TestHttp11Processor.java 
b/test/org/apache/coyote/http11/TestHttp11Processor.java
index 94a3323b3d..d2fd86e85d 100644
--- a/test/org/apache/coyote/http11/TestHttp11Processor.java
+++ b/test/org/apache/coyote/http11/TestHttp11Processor.java
@@ -53,6 +53,7 @@ import org.junit.Test;
 import org.apache.catalina.Context;
 import org.apache.catalina.Wrapper;
 import org.apache.catalina.connector.Connector;
+import org.apache.catalina.connector.ResponseFacade;
 import org.apache.catalina.startup.SimpleHttpClient;
 import org.apache.catalina.startup.TesterServlet;
 import org.apache.catalina.startup.Tomcat;
@@ -1902,4 +1903,52 @@ public class TestHttp11Processor extends TomcatBaseTest {
 
         }
     }
+
+
+    @Test
+    public void testEarlyHints() throws Exception {
+        Tomcat tomcat = getTomcatInstance();
+
+        // No file system docBase required
+        Context ctx = getProgrammaticRootContext();
+
+        // Add servlet
+        Tomcat.addServlet(ctx, "EarlyHintsServlet", new EarlyHintsServlet());
+        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;
+
+        @Override
+        protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
throws ServletException, IOException {
+            resp.addHeader("Link", "</style.css>; rel=preload; as=style");
+
+            ((ResponseFacade) resp).sendEarlyHints();
+
+            resp.setCharacterEncoding("UTF-8");
+            resp.setContentType("text/plain");
+
+            resp.getWriter().write("OK");
+        }
+    }
 }


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

Reply via email to