All,
Top-posting to add another option for getting 103 Early Hints into the
hands of application developers:
3. Allow applications to call response.sendError(103) or
response.setStatus(103), to which Tomcat will simply call its own
sendEarlyHints() method and return control to the application.
If you do this today in Tomcat 9, the client hangs waiting for a
response after the 103 is sent.
Does this seem like a nice hack to implement?
-chris
On 10/7/24 17:36, Christopher Schultz wrote:
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><${contextPath}/css/site.css>; rel=preload;
as=style</param-value>
</init-param>
<init-param>
<param-name>link.b</param-name>
<param-value><${contextPath}/js/scripts.js>; 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