Hi,
The purpose of this paper is to evaluate solutions to manage the browser cache at the application level, using the HTTP headers. This post will start with some explanations concerning the cache in client-server exchanges, then simple examples, and finally with a solution based on http filter allowing the activation of caching on the browser side in order to reduce the loading time of pages.
Explanations
Following, the steps during the exchanges client-server:
Activation of caching for a specified period:
- The user requests a given page to the client (browser);
- The client requests the page to the server;
- The server returns the requested page in a http response and it informs the client (browser) that this page should not be asked again before the expiration of a specified date in response’s header (“Expires: 11/07/2012 23:31:37 p.m. GMT”);
- The client stores the expiration date of the page;
- The client (browser) displays the requested page to the user;
- …
- The user requests again the page to the client (browser);
- if the request is made before the expiration date, the browser will find the page in its cache;
- if the request is made after the expiration date, the browser asks this mentioned page to server;
Comparison of dates:
- The client asks the server to a specific page: if the client has already read the page, it sends a request (to server) containing the last modification date of its cached page (eg “If-Modified-Since: 21/07/2002 13:47:24 GMT”);There also the header If-Unmodified-Since;
- The server server compares this date given by the client with the last modified date of requested page:
- if the page on the server has the same date, then the server informs the client that it can use the version’s page in its cache (“HTTP/1.1 304 Not Modified”). The exchange between client and server stops there;
- if the page on the server is newer, then the server informs the client of the change (“Last-modified: 21/06/2012 08:45:37 p.m. GMT”) and sent this page to client. The browser stores the date of last change of the page.
For example, since HTTP/1.0, the header Pragma allows the browser (to indicate to the cache) to retrieve a document from the original server rather than use the version in the cache: “Pragma: no-cache”.
In HTTP/1.1 (and in the future HTTP2.0 :D), the header Cache-Control can specified via the value no-cache that none cache in the entire exchanges between client and server, must not keep a copy of the response in the browser cache. The next queries to the same resource/content will return the resource on the server without ask a proxy or the browser cache: “Cache-Control: no-cache”.
This same directive via the value max-age allows a server to set the maximum duration of detention in the browser cache. When it is used by a client, it tells to cache desired minimum freshness. The times are specified in seconds: “Cache-Control: max-age=3600”.
For more information see http://en.wikipedia.org/wiki/List_of_HTTP_header_fields.
Simple examples
Example n°1: The server can disable all caches between client (browser) and server:
// Date in the past "Expires: Mon, 26 Jul 1997 05:00:00 GMT" // Always modified because last modified = current date "Last-Modified: 21/12/2012 12:13:14 GMT"); // HTTP/1.1 "Cache-Control: no-cache, must-revalidate" // HTTP/1.0 "Pragma: no-cache"
((HttpServletResponse) res).setHeader("Pragma", "No-cache"); ((HttpServletResponse) res).setHeader("Cache-Control", "no-cache,no-store,max-age=0"); ((HttpServletResponse) res).setDateHeader("Expires", 1);
Example n°2: Activation of browser’s local cache for a specified period
"Expires: 21/12/2012 12:13:14 GMT";
final int CACHE_DURATION_IN_SECOND = 60 * 60 * 24 * 2; // 2 days final long CACHE_DURATION_IN_MS = CACHE_DURATION_IN_SECOND * 1000; long now = System.currentTimeMillis(); ((HttpServletResponse) res).addHeader("Cache-Control", "max-age=" + CACHE_DURATION_IN_SECOND); ((HttpServletResponse) res).addHeader("Cache-Control", "must-revalidate");//optional ((HttpServletResponse) res).setDateHeader("Last-Modified", now); ((HttpServletResponse) res).setDateHeader("Expires", now + CACHE_DURATION_IN_MS);
Configuration Http filter and classes
So, as said in the beginning of this article, I propose you a solution based on http filter allowing the activation of caching on the browser side in order to reduce the loading time of pages. This http filter named HttpCacheFilter will set the cache header in response sent from server to client (browser) for the javascript (*.js), style (*.css) and images (*.gif, *.png, *.ico, *.jpg, *.jpeg) file for one hour.
The web deployment descriptor web.xml must contains the configuration of http filter HTTPCacheForOneHour:
<filter> <filter-name>HTTPCacheForOneHour</filter-name> <filter-class>com.ho.filter.HttpCacheFilter</filter-class> <init-param> <param-name>Cache-Control</param-name> <param-value>max-age=3600, public</param-value> </init-param> </filter> <filter-mapping> <filter-name>HTTPCacheForOneHour</filter-name> <url-pattern>*.js</url-pattern> </filter-mapping> <filter-mapping> <filter-name>HTTPCacheForOneHour</filter-name> <url-pattern>*.css</url-pattern> </filter-mapping> <filter-mapping> <filter-name>HTTPCacheForOneHour</filter-name> <url-pattern>*.gif</url-pattern> </filter-mapping> <filter-mapping> <filter-name>HTTPCacheForOneHour</filter-name> <url-pattern>*.png</url-pattern> </filter-mapping> <filter-mapping> <filter-name>HTTPCacheForOneHour</filter-name> <url-pattern>*.ico</url-pattern> </filter-mapping> <filter-mapping> <filter-name>HTTPCacheForOneHour</filter-name> <url-pattern>*.jpg</url-pattern> </filter-mapping> <filter-mapping> <filter-name>HTTPCacheForOneHour</filter-name> <url-pattern>*.jpeg</url-pattern> </filter-mapping>
HttpCacheFilter:
package com.ho.filter; 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; public class HttpCacheFilter implements Filter { // ---------------------------------------- ATTRIBUTS private final static String HEADER_GET_KEY = "Cache-Control"; private final static String HEADER_PRAGMA = "Pragma"; private final static String HEADER_EXPIRES = "Expires"; private String cacheLifeTimeInstruction = null; // ------------------------------------------ FONCTIONS public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { if (null != cacheLifeTimeInstruction) { ((HttpServletResponse) res).setHeader(HEADER_GET_KEY, cacheLifeTimeInstruction); ((HttpServletResponse) res).setHeader(HEADER_PRAGMA, null); // final int CACHE_DURATION_IN_SECOND = 60 * 60 * 24 * 5; // 5 days final long CACHE_DURATION_IN_MS = CACHE_DURATION_IN_SECOND * 1000; long now = System.currentTimeMillis(); //((HttpServletResponse) res).setDateHeader("Last-Modified", now); ((HttpServletResponse) res).setDateHeader(HEADER_EXPIRES, now + CACHE_DURATION_IN_MS); } // end-if chain.doFilter(req, res); } public void init(FilterConfig config) throws ServletException { cacheLifeTimeInstruction = config.getInitParameter(HEADER_GET_KEY); } public void destroy() { cacheLifeTimeInstruction = null; } }
Tests
To verify the compression, we could the TCP/IP Monitor view of Eclipse. So, a proxy will be configured in order to analyze the request coming to the server localhost on port 9999, then forward these requests to this same server localhost on port 8080.
In the following screenshots, we will compare the differences of results between without / with cache configuration.
…without cache configuration:
– Call n°1: First request to a page
– Call n°2: Second request to the same page
– Call n°3: Third request to the same page with a F5 (Refresh page in browser)
…with cache configuration:
– Call n°1: First request to a page
– Call n°2: Second request to the same page
– Call n°3: Third request to the same page with a F5 (Refresh page in browser)
That’s all!!!
Download: HttpCacheFilter.zip
Best regards,
Huseyin OZVEREN