Cache static resources like css, js and images for a specific time period

Introduction

In this post we will see how to cache static resources like css, js and images for a specific period of time. So we will specify the time for how long we want to cache such static resources.

Static resources like css, js images etc. need not be reloaded from the server every time until and unless we want the old one needs to be replaced by the new one.

This improves overall performance of the web application and application loads faster.

Cache is defined as hardware or software feature that stores data in temporary storage space that permits quick access to stored data.

Web browsers like Chrome, Firefox, Internet Explorer etc. use the cache for regularly accessed web pages. This feature enables the browser to retrieve data from cache than from web page files.

The primary goal of the cache memory is to make the performance better by loading the web pages quickly.

Prerequisites

Eclipse Neon, Java 1.8, Servlet 3.1.0, Maven 3.6.0

Example and Source Code

So here I am going to give an example how caching mechanism works in Java based web application.

Creating Project

Create a maven based web project with below information:

Group Id: com.roytuts
Artifact Id: webapp-cache-static-resources

The project structure in Eclipse may look similar to the below image:

cache static resources like css js and images

Updating Build File

We will update the default generated pom.xml file to include required libraries.

In the below pom.xml file we included Servlet and JSTL dependencies. We have also specified the Java compiler version for building our Java classes.

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.roytuts</groupId>
	<artifactId>webapp-cache-static-resources</artifactId>
	<packaging>war</packaging>
	<version>0.0.1-SNAPSHOT</version>
	<url>http://maven.apache.org</url>
	<dependencies>
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>javax.servlet-api</artifactId>
			<version>3.1.0</version>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>jstl</artifactId>
			<version>1.2</version>
		</dependency>
	</dependencies>
	<build>
		<finalName>webapp-cache-static-resources</finalName>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<configuration>
					<source>1.8</source>
					<target>1.8</target>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

Creating Enums

The below enum constants are used in http headers.

The Cache-Control general-header field is used to specify directives for caching mechanisms in both requests and responses. Caching directives are unidirectional, meaning that a given directive in a request is not implying that the same directive is to be given in the response.

Related Posts:

Cache Control Constants

We specify in the HTTP header whether the cache control mechanism should be public or private.

package com.roytuts.cache.resource.enums;
public enum Cacheability {
	PUBLIC("public"),
	PRIVATE("private");
	private String value;
	private Cacheability(String value) {
		this.value = value;
	}
	public String getValue() {
		return this.value;
	}
}

Cache Config Constants

We configure cache parameters with the value, such as, static, private, expirationTime.

package com.roytuts.cache.resource.enums;
public enum CacheConfigParameter {
	STATIC("static"),
	PRIVATE("private"),
	EXPIRATION_TIME("expirationTime");
	private String name;
	private CacheConfigParameter(String name) {
		this.name = name;
	}
	public String getName() {
		return this.name;
	}
}

Cache Header Constants

We specify the cache header parameters with the following values in HTTP headers.

package com.roytuts.cache.resource.enums;
public enum HTTPCacheHeader {
	CACHE_CONTROL("Cache-Control"),
	EXPIRES("Expires"),
	PRAGMA("Pragma"),
	ETAG("ETag");
	private String name;
	private HTTPCacheHeader(String name) {
		this.name = name;
	}
	public String getName() {
		return this.name;
	}
}

Creating Servlet Filters

The below Servlet Filter is used to set cache expiry time for static resources like js, css, image after which the static resources need to be served from server side.

package com.roytuts.cache.resource.filters;
import java.io.IOException;
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 com.roytuts.cache.resource.enums.CacheConfigParameter;
import com.roytuts.cache.resource.enums.Cacheability;
import com.roytuts.cache.resource.enums.HTTPCacheHeader;
/**
 *
 * @author https://roytuts.com
 *
 */
public class CacheFilter implements Filter {
	private Cacheability cacheability;
	private boolean isStatic;
	private long seconds;
	/**
	 * Default constructor.
	 */
	public CacheFilter() {
	}
	public void destroy() {
	}
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		HttpServletResponse httpServletResponse = (HttpServletResponse) response;
		StringBuilder cacheControl = new StringBuilder(cacheability.getValue()).append(", max-age=").append(seconds);
		if (!isStatic) {
			cacheControl.append(", must-revalidate");
		}
		// Set cache directives
		httpServletResponse.setHeader(HTTPCacheHeader.CACHE_CONTROL.getName(), cacheControl.toString());
		httpServletResponse.setDateHeader(HTTPCacheHeader.EXPIRES.getName(),
				System.currentTimeMillis() + seconds * 1000L);
		if (httpServletResponse.containsHeader("Pragma")) {
			httpServletResponse.setHeader(HTTPCacheHeader.PRAGMA.getName(), null);
		}
		// pass the request along the filter chain
		chain.doFilter(request, response);
	}
	public void init(FilterConfig fConfig) throws ServletException {
		cacheability = (Boolean.valueOf(fConfig.getInitParameter(CacheConfigParameter.PRIVATE.getName())))
				? Cacheability.PRIVATE
				: Cacheability.PUBLIC;
		isStatic = Boolean.valueOf(fConfig.getInitParameter(CacheConfigParameter.STATIC.getName()));
		try {
			seconds = Long.valueOf(fConfig.getInitParameter(CacheConfigParameter.EXPIRATION_TIME.getName()));
		} catch (NumberFormatException e) {
			throw new ServletException(new StringBuilder("The initialization parameter ")
					.append(CacheConfigParameter.EXPIRATION_TIME.getName()).append(" is missing for filter ")
					.append(fConfig.getFilterName()).append(".").toString());
		}
	}
}

The below Servlet filter will destroy the cache once time expires.

package com.roytuts.cache.resource.filters;
import java.io.IOException;
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 com.roytuts.cache.resource.enums.HTTPCacheHeader;
/**
 *
 * @author https://roytuts.com
 *
 */
public class NoCacheFilter implements Filter {
	public NoCacheFilter() {
	}
	public void destroy() {
	}
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		HttpServletResponse httpServletResponse = (HttpServletResponse) response;
		httpServletResponse.setHeader(HTTPCacheHeader.CACHE_CONTROL.getName(), "no-cache, no-store, must-revalidate");
		httpServletResponse.setDateHeader(HTTPCacheHeader.EXPIRES.getName(), 0L);
		chain.doFilter(request, response);
	}
	public void init(FilterConfig fConfig) throws ServletException {
	}
}

Deployment Descriptor

The below entries are required in order to let Servlet Filter know for which requests the cache headers need to be applied.

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
		 http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
	version="3.1">
	<display-name>Cache static resources in webapp</display-name>
	<welcome-file-list>
		<welcome-file>index.jsp</welcome-file>
	</welcome-file-list>
	<filter>
		<display-name>NoCacheFilter</display-name>
		<filter-name>NoCacheFilter</filter-name>
		<filter-class>com.roytuts.cache.resource.filters.NoCacheFilter</filter-class>
	</filter>
	<filter-mapping>
		<filter-name>NoCacheFilter</filter-name>
		<url-pattern>/NoCacheFilter</url-pattern>
	</filter-mapping>
	<filter-mapping>
		<filter-name>NoCacheFilter</filter-name>
		<url-pattern>*.jsf</url-pattern>
		<dispatcher>REQUEST</dispatcher>
		<dispatcher>FORWARD</dispatcher>
	</filter-mapping>
	<filter>
		<filter-name>imagesCache</filter-name>
		<filter-class>com.roytuts.cache.resource.filters.CacheFilter</filter-class>
		<init-param>
			<param-name>static</param-name>
			<param-value>true</param-value>
		</init-param>
		<init-param>
			<param-name>expirationTime</param-name>
			<param-value>2592000</param-value>
		</init-param>
	</filter>
	<filter>
		<filter-name>cssCache</filter-name>
		<filter-class>com.roytuts.cache.resource.filters.CacheFilter</filter-class>
		<init-param>
			<param-name>expirationTime</param-name>
			<param-value>604800</param-value>
		</init-param>
	</filter>
	<filter>
		<filter-name>jsCache</filter-name>
		<filter-class>com.roytuts.cache.resource.filters.CacheFilter</filter-class>
		<init-param>
			<param-name>private</param-name>
			<param-value>true</param-value>
		</init-param>
		<init-param>
			<param-name>expirationTime</param-name>
			<param-value>216000</param-value>
		</init-param>
	</filter>
	<filter-mapping>
		<filter-name>imagesCache</filter-name>
		<url-pattern>/img/*</url-pattern>
	</filter-mapping>
	<filter-mapping>
		<filter-name>cssCache</filter-name>
		<url-pattern>*.css</url-pattern>
	</filter-mapping>
	<filter-mapping>
		<filter-name>jsCache</filter-name>
		<url-pattern>*.js</url-pattern>
	</filter-mapping>
</web-app>

From the above deployment descriptor we can see that css, js and images(inside img directory) will be cached for a specific period.

We have already mentioned the time period for how long these static resources will be cached.

Once the time expires the cached resources will be loaded from server again.

This example does not have any static resources, so it does not show working example but it gives only idea how to cache static resources to improve performance of the web site.

Source Code

download source code

That’s all. Thanks for reading.

Leave a Reply

Your email address will not be published. Required fields are marked *