diff --git a/smoothness-demo/src/main/java/org/jlab/demo/presentation/controller/Test.java b/smoothness-demo/src/main/java/org/jlab/demo/presentation/controller/Test.java new file mode 100644 index 00000000..f99c0fe1 --- /dev/null +++ b/smoothness-demo/src/main/java/org/jlab/demo/presentation/controller/Test.java @@ -0,0 +1,37 @@ +package org.jlab.demo.presentation.controller; + +import jakarta.servlet.ServletException; +import jakarta.servlet.annotation.WebServlet; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import org.jlab.smoothness.presentation.filter.CacheFilter; + +/** + * @author ryans + */ +@WebServlet( + name = "test", + urlPatterns = {"/test"}) +public class Test extends HttpServlet { + + /** + * Handles the HTTP GET method. + * + * @param request servlet request + * @param response servlet response + * @throws ServletException if a servlet-specific error occurs + * @throws IOException if an I/O error occurs + */ + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + + // Let's test forcibly caching response! + CacheFilter.CacheControlResponse cachableResponse = (CacheFilter.CacheControlResponse) response; + cachableResponse.setContentType("application/json", CacheFilter.CachableResponse.MAX); + + response.getWriter().println("{\"name\":\"test\"}"); + } +} diff --git a/smoothness-weblib/src/main/java/org/jlab/smoothness/presentation/filter/CacheFilter.java b/smoothness-weblib/src/main/java/org/jlab/smoothness/presentation/filter/CacheFilter.java index a9e53d37..04ab4ae8 100644 --- a/smoothness-weblib/src/main/java/org/jlab/smoothness/presentation/filter/CacheFilter.java +++ b/smoothness-weblib/src/main/java/org/jlab/smoothness/presentation/filter/CacheFilter.java @@ -14,7 +14,10 @@ import java.util.Arrays; /** - * WebFilter for setting response cache directives. + * WebFilter for setting response cache directives. By default, will set maximum cache (1 year) for + * all responses with a mime type in the CACHEABLE_CONTENT_TYPES array. In a given servlet (request + * handler) cast response to CacheControlResponse to override the default behavior and either + * forcibly set to OFF (no cache headers set) or MAX (set max cache headers of 1 year). * * @author ryans */ @@ -25,8 +28,20 @@ asyncSupported = true) public class CacheFilter implements Filter { - private static final long EXPIRE_MILLIS = 31536000000L; // 365 days is max expires per spec + public static final long MAX_EXPIRE_MILLIS = 31536000000L; // 365 days is max expires per spec + public enum CachableResponse { + MAX, // Cache for spec max of 1 year + OFF, // Don't cache in filter, so maybe you can do your own cache logic + AUTO // Based on content type, will determine either OFF or MAX. This is default. + } + + /** + * Only mime types of files that ALWAYS should be cached are included. Notably excluded are types + * text/html and application/json. These rarely should be cached, though sometimes they should and + * in those cases the Servlet needs to manually cast response to CacheControlResponse and use + * setContentType(type, cachable) with value CachableResponse.MAX. + */ private static final String[] CACHEABLE_CONTENT_TYPES = new String[] { "text/css", @@ -60,27 +75,57 @@ public void init(FilterConfig filterConfig) throws ServletException {} @Override public void destroy() {} - class CacheControlResponse extends HttpServletResponseWrapper { + /** + * We use a Wrapper because some application servers don't allow you to set Headers in + * post-processing (filter chain), and in pre-processing the content type often isn't known yet. + * Wrapper allows us to run filter code during servlet processing at the specific moment we learn + * of content type. + * + *

https://stackoverflow.com/questions/2563344/how-to-add-response-headers-based-on-content-type-getting-content-type-before-t + */ + public static class CacheControlResponse extends HttpServletResponseWrapper { + + private CachableResponse cachable = CachableResponse.AUTO; CacheControlResponse(HttpServletResponse response) { super(response); } + public void setContentType(String type, CachableResponse cachable) { + this.cachable = cachable; + this.setContentType(type); + } + @Override public void setContentType(String type) { super.setContentType(type); - if (type != null && Arrays.binarySearch(CACHEABLE_CONTENT_TYPES, type) > -1) { - super.setDateHeader("Expires", System.currentTimeMillis() + EXPIRE_MILLIS); - super.setHeader( - "Cache-Control", null); // Remove header automatically added by SSL/TLS container module - super.setHeader( - "Pragma", null); // Remove header automatically added by SSL/TLS container module - } else { - super.setHeader("Cache-Control", "no-store, no-cache, must-revalidate"); // HTTP 1.1 - super.setHeader("Pragma", "no-cache"); // HTTP 1.0 - super.setDateHeader("Expires", 0); // Proxies + if (cachable == null || cachable == CachableResponse.AUTO) { + + if (type != null && Arrays.binarySearch(CACHEABLE_CONTENT_TYPES, type) > -1) { + setMaxCache(); + } else { + setNoCache(); + } + } else if (cachable == CachableResponse.MAX) { + setMaxCache(); + } else { // OFF + setNoCache(); } } + + private void setNoCache() { + super.setHeader("Cache-Control", "no-store, no-cache, must-revalidate"); // HTTP 1.1 + super.setHeader("Pragma", "no-cache"); // HTTP 1.0 + super.setDateHeader("Expires", 0); // Proxies + } + + private void setMaxCache() { + super.setDateHeader("Expires", System.currentTimeMillis() + MAX_EXPIRE_MILLIS); + super.setHeader( + "Cache-Control", null); // Remove header automatically added by SSL/TLS container module + super.setHeader( + "Pragma", null); // Remove header automatically added by SSL/TLS container module + } } }