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 + } } }