All,

On 10/7/24 16:52, Christopher Schultz wrote:
All,

I thought it might be "fun" to play around with 103 Early Hints, which is a feature with support added in servlet.next which means Tomcat 12. It can be used currently with Tomcat 9.0 and later, as long as you are willing to downcast the HttpServletResponse to a Tomcat-private class and you can make a call to Response.sendEarlyHints.

It occurs to me that we (Tomcat) could make it easier for applications to use this feature by providing something like a Filter that would use Tomcat internal libraries, but insulate the application from such ugliness.

I have two ideas for how this would be implemented:

1. A new Filter dedicated to (a) setting Link headers and (b) calling
    sendEarlyHints

2. As an option for the RewriteValve value, e.g. as a flag

I wrote a quick implementation of (1) above and it occurred to me that is was pretty trivial, so I came up with idea (2) above as maybe a way to add a feature to an existing component rather than building a new one.

When I started looking at the changes required for (2), it seems to me that the only thing that made any sense would be to do the following:

RewriteRule .*\.jsp $0 [SEH]

The "SEH" is a proposed "Send Early Hints" flag which would invoke the response.sendEarlyHints method which .... sends a 103 Early Hints response and then proceeds with the rewrite/request.

I've decided that I don't really like (2) because you have to configure a useless rewrite operation just to add the SEH flag. Also, RewriteValve doesn't help you with the Link headers so you are back to writing a Filter and then arranging to have that Filter run *before* RewriteValve which ... could be a problem for you.

So I think I'm going to pursue option (1) above, looking at doing something like I have below. Comments welcome and encouraged.

I build a practical example to test this with my own application, and I found that the following implementation contains everything I needed to use 103 Early Hints in a practical way.

Example configuration:

=== CUT ===

  <filter>
    <description>
      Configures the application to send Early Hints to load resources
      such as CSS and scripts.
    </description>
    <filter-name>early-hints</filter-name>
<filter-class>org.apache.catalina.filters.EarlyHintsFilter</filter-class>
    <init-param>
      <param-name>csp.a</param-name>
      <param-value>default-src:self;</param-value>
    </init-param>
    <init-param>
      <param-name>link.a</param-name>
<param-value>&lt;${contextPath}/css/site.css&gt;; rel=preload; as=style</param-value>
    </init-param>
    <init-param>
      <param-name>link.b</param-name>
<param-value>&lt;${contextPath}/js/scripts.js&gt;; rel=preload; as=style</param-value>
    </init-param>
  </filter>

  <filter-mapping>
    <filter-name>early-hints</filter-name>
    <url-pattern>*.do</url-pattern>
  </filter-mapping>

=== CUT ===

package org.apache.catalina.filters;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;

import org.apache.catalina.connector.ResponseFacade;

/**
 * A Filter that adds a series of
 */
public class EarlyHintsFilter
    implements Filter
{
    private final ArrayList<String> csps = new ArrayList<String>(1);
    private final ArrayList<String> hints = new ArrayList<String>();

    @Override
    public void init(FilterConfig config) throws ServletException {
        Enumeration<String> paramNames = config.getInitParameterNames();
        while(paramNames.hasMoreElements()) {
            String name = paramNames.nextElement();

            if(name.startsWith("csp.")) {
                csps.add(config.getInitParameter(name));
            } else if(name.startsWith("link.")) {
                String hint = config.getInitParameter(name);
                int pos = hint.indexOf("${contextPath}");
                if(pos >= 0) {
hint = hint.replace("${contextPath}", config.getServletContext().getContextPath());
                }

                hints.add(hint);
            } else {
config.getServletContext().log("WARNING: Unexpected init-param to EarlyHintsFilter: " + name);
            }
        }
    }

    @Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletResponse rsp = (HttpServletResponse)response;
        if(!csps.isEmpty()) {
            for(String csp : csps) {
                rsp.addHeader("Content-Security-Policy", csp);
            }
        }
        if(!hints.isEmpty()) {
            for(String hint : hints) {
                rsp.addHeader("Link", hint);
            }

            ((ResponseFacade)rsp).sendEarlyHints();
        }

        chain.doFilter(request, response);
    }
}


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

Reply via email to