Advanced Caching Example

The HttpServletResponse class includes a convenient write() method that you can use to send a java.io.File to the client. The write method includes an option to allow browsers to cache the files by supplying a last modified date and an ETag. Unfortunately, this doesn't work well in all browsers. Chrome's caching, for example, is very aggressive. It will ignore the last modified date and the ETag in javascript and css files referenced in an html document. To circumvent this issue, I like to append a version number to the css and js files.

In the following example, the getFile() method will look for a version number associated with a js or css file found in the requested query string ("v" parameter). The version number is simply the last modified date of the file represented as a long int ("yyyyMMddHHmmssSSS"). If the requested file version is less than the current file version, then the browser is redirected to the newer file.

Other features in this example include:

  • Using a default index file for a directory
  • Dynamically identifying a working directory
  • URL path parsing to intercept requests to "/WebServices"

Note that this example requires javaxt-core.jar.

package com.example;
import javaxt.http.servlet.*;


public class Servlet extends HttpServlet {
    
    
    private javaxt.io.Directory web;
    private String[] welcomeFiles = new String[]{"index.html", "index.htm", "default.htm"};    


  //**************************************************************************
  //** Constructor
  //**************************************************************************

    public Servlet(){
     
        
      //Set path to the web directory
        java.io.File jarFile = new javaxt.io.Jar(this.getClass()).getFile();
        java.io.File jarDir = jarFile.getParentFile();
        if (jarDir.getName().equals("dist")){ //dev
            web = new javaxt.io.Directory(jarDir.getParentFile() + javaxt.io.Directory.PathSeparator + "web");
        }
        else{ //production
            web = new javaxt.io.Directory(jarDir.getParentFile());
        }
        
    }
    
    
  //**************************************************************************
  //** processRequest
  //**************************************************************************
  /** Used to process http get and post requests. */

    public void processRequest(HttpServletRequest request, HttpServletResponse response) 
        throws ServletException, java.io.IOException {
        
        
        javaxt.utils.URL url = new javaxt.utils.URL(request.getURL());
        String path = url.getPath();
        if (path.length()>1 && path.startsWith("/")) path = path.substring(1);

        String service = path.toLowerCase();
        if (service.contains("/")) service = service.substring(0, service.indexOf("/"));
        if (service.equalsIgnoreCase("WebServices")){
            //Process web service request...
        }        
        else{
            getFile(request, response);
        }
        
    }
    
    
  //**************************************************************************
  //** getFile
  //**************************************************************************
  /** Used to retrieve a local file and return it to the client. The file path
   *  is derived from the requested url.
   */
    private void getFile(HttpServletRequest request, HttpServletResponse response)
        throws java.io.IOException{
        
        

      //Extract file name/path from the url
        javaxt.utils.URL url = new javaxt.utils.URL(request.getURL());
        String path = url.getPath();
        if (path==null || path.length()==1) path = "";
        else path = path.substring(1);

        
      //Construct a list of possible file paths
        java.util.ArrayList<String> files = new java.util.ArrayList<String>();
        files.add(web + path);
        if (path.length()>0 && !path.endsWith("/")) path+="/";
        for (String welcomeFile : welcomeFiles){
            files.add(web + path + welcomeFile);
        }
        
        

      //Loop through all the possible file combinations
        for (String str : files){

          //Ensure that the path doesn't have any illegal directives
            str = str.replace("\\", "/");
            if (str.contains("..") || str.contains("/.") ||
                str.toLowerCase().contains("/keystore")){
                continue;
            }



          //Send file if it exists
            java.io.File file = new java.io.File(str);
            if (file.exists() && file.isFile() && !file.isHidden()){



                String name = file.getName();
                int idx = name.lastIndexOf(".");
                if (idx > -1){
                    String ext = name.substring(idx+1).toLowerCase();

                    
                    if (ext.equals("js") || ext.equals("css")){

                        
                      //Add version number to javascript and css files to ensure  
                      //proper caching. Otherwise, browsers like Chrome may not 
                      //return the correct file to the client.
                        long currVersion = new javaxt.utils.Date(file.lastModified()).toLong();
                        long requestedVersion = 0;
                        try{ requestedVersion = Long.parseLong(url.getParameter("v")); }
                        catch(Exception e){}

                        if (requestedVersion < currVersion){
                            url.setParameter("v", currVersion+"");
                            response.sendRedirect(url.toString(), true);
                            return;
                        }
                    }
                }
                
                
              //Send file
                response.write(file, javaxt.io.File.getContentType(file.getName()), true);
                return;
            }
        }
        
        
      //If we're still here, throw an error
        response.setStatus(404);
    }
}