leodutra
9/22/2015 - 5:53 PM

Filtro para controle de cache de resources para Servlets 2.3+ (Websphere 5+)

Filtro para controle de cache de resources para Servlets 2.3+ (Websphere 5+)

<filter>
    <description>Cache control filter for common Java web applications. When init parameters are not defined, defaults to internal definitions.</description>
    <display-name>CacheControlFilter</display-name>
    <filter-name>CacheControlFilter</filter-name>
    <filter-class>com.github.leodutra.filters.CacheControlFilter</filter-class>
    <init-param>
        <param-name>max-age-seconds</param-name>
        <param-value>10800</param-value>
    </init-param>
    <init-param>
        <param-name>cached-extensions</param-name>
        <param-value>.js, .css, .png, .jpg, .svg, .ico, .webp, .eot, .woff, .woff2, .ttf, .gif</param-value>
    </init-param>
    <init-param>
        <param-name>ignored-extensions</param-name>
        <param-value>.do, .jsp, .action, .ext, .html, .xhtml, .ajax</param-value>
    </init-param>   
</filter>
<filter-mapping>
    <filter-name>CacheControlFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
/**/
package com.github.leodutra.filters;

import java.io.IOException;
import java.util.Date;

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.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Cache control filter for common Java web applications.<br>
 * When web.xml init parameters are not defined, uses the internal default definitions.
 * 
 * @author leonardodutra (https://github.com/LeoDutra)
 */
public final class CacheControlFilter implements Filter {

	private static final long DEFAULT_MAX_AGE_SECONDS = 60 * 60 * 3; // 3 horas

	private static final String[] DEFAULT_CACHED_EXTENSIONS = new String[] { ".js", ".css", ".png", ".jpg", ".svg",
			".ico", ".webp", ".eot", ".woff", ".woff2", ".ttf", ".gif" };

	private static final String[] DEFAULT_IGNORED_EXTENSIONS = new String[] { ".do", ".jsp", ".action", ".ext",
			".html", ".xhtml", ".ajax" };

	private static final String EXTENSIONS_SPLIT_PATTERN = ",\\s?";

	private static final String CACHE_CONTROL_NO_CACHE = "private, no-cache, must-revalidate, max-age=15, proxy-revalidate, s-maxage=15"; // 15 sec against multicast
	private static final String PRAGMA_NO_CACHE = "no-cache";

	private static final String BLANK_STR = "";

	private static final String HEADER_EXPIRES = "Expires";
	private static final String HEADER_PRAGMA = "Pragma";
	private static final String HEADER_CACHE_CONTROL = "Cache-Control";
	private static final String HTTP_1_1_PROTOCOL = "HTTP/1.1";

	private long maxAgeSeconds;
	private long maxAgeMillis;

	private String timedCacheControl;
	private String[] cachedExtensions;
	private String[] ignoredExtensions;

	/*
	 	Useful Cache-Control response headers include: (source: https://www.mnot.net/cache_docs/)
	
		- max-age=[seconds] — specifies the maximum amount of time that a representation will be considered fresh. Similar to Expires, this directive is relative to the time of the request, rather than absolute. [seconds] is the number of seconds from the time of the request you wish the representation to be fresh for.
		
		- s-maxage=[seconds] — similar to max-age, except that it only applies to shared (e.g., proxy) caches.
		
		- public — marks authenticated responses as cacheable; normally, if HTTP authentication is required, responses are automatically private.
		
		- private — allows caches that are specific to one user (e.g., in a browser) to store the response; shared caches (e.g., in a proxy) may not.
		
		- no-cache — forces caches to submit the request to the origin server for validation before releasing a cached copy, every time. This is useful to assure that authentication is respected (in combination with public), or to maintain rigid freshness, without sacrificing all of the benefits of caching.
		
		- no-store — instructs caches not to keep a copy of the representation under any conditions.
		
		- must-revalidate — tells caches that they must obey any freshness information you give them about a representation. HTTP allows caches to serve stale representations under special conditions; by specifying this header, you’re telling the cache that you want it to strictly follow your rules.
		
		- proxy-revalidate — similar to must-revalidate, except that it only applies to proxy caches.
	 */

	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
			ServletException {

		final HttpServletRequest httpRequest = (HttpServletRequest) request;
		final HttpServletResponse httpResponse = (HttpServletResponse) response;

		final String uri = httpRequest.getRequestURI();

		boolean hasIgnoredExtension = false;

		for (final String ignored : ignoredExtensions) {
			if (uri.endsWith(ignored)) {
				hasIgnoredExtension = true;
				break;
			}
		}

		if (!hasIgnoredExtension) {

			boolean hasCacheableExtension = false;

			for (final String extension : cachedExtensions) {
				if (uri.endsWith(extension)) {
					hasCacheableExtension = true;
					break;
				}
			}

			if (hasCacheableExtension) {

				httpResponse.setDateHeader(HEADER_EXPIRES, new Date().getTime() + maxAgeMillis);

				if (request.getProtocol().equals(HTTP_1_1_PROTOCOL)) {
					httpResponse.setHeader(HEADER_CACHE_CONTROL, timedCacheControl);
				}
				// DO NOT USE PRAGMA=CACHE

			} else {

				httpResponse.setDateHeader(HEADER_EXPIRES, -1);

				if (request.getProtocol().equals(HTTP_1_1_PROTOCOL)) {
					httpResponse.setHeader(HEADER_CACHE_CONTROL, CACHE_CONTROL_NO_CACHE);
				} else {
					httpResponse.setHeader(HEADER_PRAGMA, PRAGMA_NO_CACHE);
				}
			}
		}

		chain.doFilter(request, response);
	}

	@Override
	public void init(FilterConfig filterConfig) throws ServletException {

		final String initMaxAgeSeconds = filterConfig.getInitParameter("max-age-seconds");
		final String initCachedExtensions = filterConfig.getInitParameter("cached-extensions");
		final String initIgnoredExtensions = filterConfig.getInitParameter("ignored-extensions");

		if (isNotBlank(initMaxAgeSeconds)) {
			maxAgeSeconds = Long.parseLong(initMaxAgeSeconds.trim(), 10);
		} else {
			maxAgeSeconds = DEFAULT_MAX_AGE_SECONDS;
		}

		if (isNotBlank(initCachedExtensions)) {
			cachedExtensions = initCachedExtensions.trim().split(EXTENSIONS_SPLIT_PATTERN);
		} else {
			cachedExtensions = DEFAULT_CACHED_EXTENSIONS;
		}

		if (isNotBlank(initIgnoredExtensions)) {
			ignoredExtensions = initIgnoredExtensions.trim().split(EXTENSIONS_SPLIT_PATTERN);
		} else {
			ignoredExtensions = DEFAULT_IGNORED_EXTENSIONS;
		}

		maxAgeMillis = maxAgeSeconds * 1000;

		timedCacheControl = new StringBuilder().append("private, must-revalidate, max-age=").append(maxAgeSeconds)
				.append(", proxy-revalidate, s-maxage=").append(maxAgeSeconds).toString(); // https://gist.github.com/LeoDutra/f417cdfd9aaf361e3c6b
	}

	private static final boolean isNotBlank(String str) {
		return str != null && !BLANK_STR.equals(str);
	}

	@Override
	public void destroy() {
	}
}