Precise Java Google
Home  | J2SE  | J2EE  | Book  | Free Books  | Contact
Chapter 1  | Chapter 2  | Chapter 3

Recipe 3.1: Utilizing browser and proxy server cache

Problem

Transferring static or semi-static data from the web server to the web browser for every request is redundant and inefficient.

Background

Browser and web server follow HTTP protocol request-response paradigm to transfer the data between each other. When the browser makes a request, web server sends back the requested data as a response. In this paradigm, the data that transfers from the web server to the browser can be static, semi-static or dynamic as we discussed in the introduction. Although browsers and web servers cache static files such as html and script files, they cannot cache static data or semi-dynamic data that is generated by Servlet because Servlet generates them dynamically. This type of data must be transferred from web server to the browser on every request although it is static, which is redundant and imposes unnecessary overhead on network and application server.


So how can we avoid redundant data transfer from web server to the browser?


Recipe

You can utilize the browser and proxy server caching facility in order to avoid static data transfer on each request. Figure 5.2 shows availability of built in caching facility in a typical web infrastructure.

Browser and Proxy server utilize this caching facility based on HTTP headers exchange. They automatically exchange caching headers for caching static objects such as html files and gifs. For static or semi-static data that is generated by a Servlet, you need to explicitly pass the caching headers in order to cache static data. Some of the HTTP headers such as 'Last-Modified' response header, 'If-Modified-Since' request header support caching. Since Servlet API has wrapper classes around HTTP protocol, it has methods to manipulate these caching headers. For example, HttpServlet.getLastModified() returns when the response object was last modified and the Browser sends 'If-Modified-Since' header to check whether the object is modified or not. By using these headers (generated by the Servlet), you can cache static or semi-static data in the Browsers and Proxy servers. Note that some of the headers are method specific. For example, 'If-Modified-Since' request header can be passed only with http 'GET' method.

The sample for this recipe demonstrates how a simple news forum utilizes browser and proxy server cache. News does not change on every request, rather its content changes when latest news is added to the news page. It uses two HTTP caching headers, If-Modified-Since request header and Last-Modified response header. These two headers work only with 'GET' requests. Figure 5.2 shows how the Browser and Servlet exchange headers in order to decide whether to use the cached content or not. This figure does not show all the players in the headers exchange. If the Proxy server exists as shown in the Figure 5.1 (generally exists in the production environment), it also caches to serve other Browsers. In the first and second steps, Browser sends request without If-Modified-Since header, WebServer sends back Last-Modified header with the fresh content. In the third and fourth steps, Browser caches the content and sends If-Modified-Since time. Then the Servlet decides whether If-Modified-Since time is less than Last-Modified time, if so it returns 304 Not Modified Status code without the content so that the Browser can display its cached copy. If not, the Servlet sends new Last- Modified header along with the updated content. Both Browser and Servlet exchange these headers in order to utilize Browser cache.

In order to implement this technique, the sample uses two servlets (NewsServlet and BaseServlet) and one html page (GetNews.html). When you run this sample, you can submit news with the user name. It displays all the submitted news plus two text fields to add other news. Listing 5.1 shows the code snippet for NewsServlet that takes care of accumulating news in the Hashtable. GetNews.html code is shown in Listing 5.2 that calls NewsServlet to display the news page.

Listing 5.1 code snippet from NewsServlet

public class NewsServlet extends BaseServlet {

private long thisServletLastModifiedTime = 0; // servlet time
private java.util.Hashtable news = new java.util.Hashtable();

public void init() throws javax.servlet.ServletException{
getNews();
}


public void doPost(HttpServletRequest request, HttpServletResponse response)
throws javax.servlet.ServletException, java.io.IOException {
updateNews(request); #1
thisServletLastModifiedTime = System.currentTimeMillis();

doGet(request,response);
}


public void doGet(HttpServletRequest request,HttpServletResponse response)
throws javax.servlet.ServletException, java.io.IOException {
sendFreshResponse(request,response); #2
}


public void destroy(){
thisServletLastModifiedTime = 0;
news = null;
}


private void sendFreshResponse(HttpServletRequest request, HttpServletResponse response) throws javax.servlet.ServletException,java.io.IOException{
try{
Thread.sleep(300); #3
}catch(InterruptedException ie){
ie.printStackTrace();
}
printResponse(request,response);
}


private void updateNews(HttpServletRequest request){
String name = request.getParameter("name");
String value = request.getParameter("news");
if(name != null && value != null){
news.put(name,value);
}
}

public long getLastModified(javax.servlet.http.HttpServletRequest request){
return thisServletLastModifiedTime;
}
}
(annotation)<#1 update news when the news is submitted>
(annotation)<#2 send fresh response if the doGet method is executed>
(annotation)<#3 pause for some time to simmulate real world operations>

 

Listing 5.2 code snippet from GetNews.html

<BODY>
<center><h1>Web Cache Demo</h1></center>
<center><hr width="100%"></center>
<H4>Click the button to get the latest news</H4>
<FORM method="GET" action=”/NewsServlet”> #1
<INPUT type="submit" name="Get News" value="Get News">
</FORM>

(annotation)<#1 call NewsServlet to initiate the sample demo>

 

Listing 5.3 shows the code for BaseServlet that encapsulates logic to determine whether to send fresh content or Not-Modified header.

Listing 5.3 BaseServlet

public class BaseServlet extends HttpServlet {
protected void service(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
// Get the last modified time for this servlet
long servletLastMod = getLastModified(req); #1
long imsince = req.getDateHeader("If-Modified-Since"); #1

String method = req.getMethod();
if (method.equals("POST")) { #2
super.service(req, res);
return;
}

// For the first request, we will not get If-Modified-Since header // so it is -1
if (req.getDateHeader("If-Modified-Since") == -1) { #3
super.service(req, res);
}

// For the second request onwards, it can be either Last-Modified // time is less than If-Modified-Since time
// or Last-Modified time is greater than If-Modified-Since time
// And Round down to the nearest second since client headers are // in seconds
else if((servletLastMod / 1000 * 1000) > imsince){
super.service(req,res);
}else{ #4
res.setStatus(res.SC_NOT_MODIFIED);
}
}
}

(annotation)<#1 Get both last modified and if-modified-since time>
(annotation)<#2 If it is a POST request, then execute doPost method of NewsServlet>
(annotation)<#3 If it is a first request, then execute doGet method of NewsServlet>
(annotation)<#4 If the Last-Modified time is less than If-Modified-Since time, then send 'Not Modified' status code >

 

Discussion

In the sample, NewsServlet extends BaseServlet. BaseServlet initially interprets the requests and finds out the time of 'If-Modified-Since' header. Then, it compares with the last modified time of NewsServlet for determining whether there is a change in the content, if there is no change then it returns 304 Not Modified status code then the Browser can use its cached copy. If there is a change in the content, it returns updated content with Last-modified time.

Note that once the Browser caches the content, it always uses its cache for GET requests to display the page unless If-Modified-Since time is less than Last-Modified time for its request. But we can cleanup the Browser cache by using Browser tools and can start testing whole cycle from the first step. To delete Browser cache in the Internet Explorer, Go to Internet Explorer's 'Tools' tab, click on 'Internet Options' tab, click on 'Delete Files' under 'Temporary Internet Files' area, check the check box and then click on 'OK' button. This process deletes all cached files in the Internet Explorer.

Performance improves based on number of concurrent accesses to the Servlet. After the first request, the Servlet will not execute doGet () process for the subsequent 'GET' requests until it is forced to do so by 'POST' request. If you do not use this technique, Servlet will execute doGet () method process unnecessarily even though there is no change in the content. Since caching logic is encapsulated in BaseServlet, it can be used in other functional areas by extending BaseServlet. Since this method is browser dependent, you can explore other caching options to boost the performance.

Feed back

We appreciate and welcome your comments on this section. Email commentsZZZ@precisejavaZZZ.com (remove ZZZ which is placed to prevent spam). Please note that we may not be able to reply to all the emails due to huge number of emails that we receive but we appreciate your comments and feedback.

 





Copyright © 2001-2005, Ravi Kalidindi and Rohini Kalidindi. All rights reserved.