Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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 <code>GET</code> 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\"}");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand All @@ -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",
Expand Down Expand Up @@ -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.
*
* <p>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
}
}
}
Loading