Improving Java web site performance with asset caching

Posted by Matt Parrish Wed, 30 Apr 2008 04:04:00 GMT

In this post, I’ll be talking about a solution I developed at my day job to improve the performance of our web site by allowing the browser to cache JavaScript, CSS, and image files. We were noticing that much of our traffic was from requests for these assets, rather than our page content. Since these asset files rarely change (once per production deployment), we wanted to have the user’s browser cache them until the next build.

We use Yahoo’s YSlow Firefox plugin to analyze the performance characteristics of our site and we were getting bad grades for the following category: Add an Expires or a Cache-Control Header. Images are easy enough to cache. Just add an HTTP response header for the images to have them expire 10 years in the future. If you ever need to change an image, instead of modifying it, just create a new one with a different URL. To set this exipres header, I created the following class, called StaticFileFilter:

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

/**
 * @author Matt Parrish
 */
public class StaticFileFilter implements Filter {

    public void destroy() {
    }

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;

        long expires = 365*24*60*60*1000;
        httpResponse.setDateHeader("Expires", System.currentTimeMillis() + expires);
        httpResponse.setHeader("Cache-Control", "max-age=" + expires);
    }

    public void init(FilterConfig config) throws ServletException {
    }

}

In web.xml, we add this filter for all images:

  <filter>
    <filter-name>StaticFileFilter</filter-name>
    <filter-class>org.pearware.web.filter.StaticFileFilter</filter-class>
  </filter>

  <filter-mapping>
    <filter-name>StaticFileFilter</filter-name>
    <url-pattern>*.png</url-pattern>
  </filter-mapping>

  <filter-mapping>
    <filter-name>StaticFileFilter</filter-name>
    <url-pattern>*.jpg</url-pattern>
  </filter-mapping>

  <filter-mapping>
    <filter-name>StaticFileFilter</filter-name>
    <url-pattern>*.gif</url-pattern>
  </filter-mapping>

So, now we have our image files cached for 10 years. JavaScript and CSS files, however, tend to change frequently, possibly with every new build. Unlike with images, it really becomes a pain to rename the files, especially when using Source Control and you want to track the changes of the files over time. My solution was to have the build number become part of the URL for these assets, without having to change the names or locations of the files. The StaticFileFilter then becomes responsible for translating the build-dependent URL into the location for the actual resource. Here’s the new body of the StaticFileFilter.

        String buildNumber = SomeWayTo.getBuildNumber();
        String oldRequestURI = httpRequest.getRequestURI();
        String requestURI = oldRequestURI.replaceFirst(buildNumber + "/(js|css|images)", "$1");
        long expires = 365*24*60*60*1000;
        httpResponse.setDateHeader("Expires", System.currentTimeMillis() + expires);
        httpResponse.setHeader("Cache-Control", "max-age=" + expires);
        request.getRequestDispatcher(requestURI).forward(request, response);

What this does is change the requested URI from, say, /2.3/images/sample.jpg to /images/sample.jpg. The last line of code is responsible for requesting the image at the actual path. The only magic here is how to get the build number. For us, we update a properties file with the build number before every QA build and have a class that reads in the property.

The other piece to this is that our XHTML page needs to reference images as <img src=”/2.3/images/sample.jpg”/> instead of <img src=”/images/sample.jpg”/>. Since the build number changes with each deployment, we need to dynamically generate this build number. Here’s how I’m doing it, using Freemarker:

[#macro css href]
<link rel="stylesheet" type="text/css" href="${base}/${someWayToGet.buildNumber}/${href}.css"/>
[/#macro]

[#macro script src]
<script type="text/javascript" src="${base}/${someWayToGet.buildNumber}/${src}.js"></script>
[/#macro]

[#macro img src params...]
[#compress]
<img src="${base}/${someWayToGet.buildNumber}/${src}"[#list params?keys as attr] ${attr}="${params[attr]}"[/#list]/>
[/#compress]
[/#macro]

[@script src="js/prototype"/]
[@css href="css/main"/]
[@img src="images/logo.png" alt="Logo"/]

What you can see is that the StaticFileFilter is setup to serve all of our CSS, JavaScript and images. The benefit is that we are now free to modify any of our assets, yet still instruct the browser to cache them for 10 years. When we push out a new build, the browser will see new URL’s for the assets due to the new build number. Our deployment doesn’t change; we don’t need to move files around to support this. It’s been working great for us. Our YSlow score has gone up and our HTTP traffic for these files has decreased dramatically, freeing up our server and network for serving up the actual content of our application

Switched to pure ruby ldap library

Posted by Matt Parrish Mon, 20 Nov 2006 19:58:00 GMT

I wrote an article awhile back about using the Ruby/LDAP library to handle LDAP authentication in Ruby on Rails. I just finished swapping out the LDAP client library in that application from Ruby/LDAP to ruby-net-ldap. The problems with Ruby/LDAP are that it isn’t a GEM, so installation is a bit more difficult, and it relies on a common LDAP library, like OpenLDAP, to already be installed on the system. The ruby-net-ldap library is written in pure Ruby, so no other library needs to be installed on the system.

Here is the new code that performs the authentication:

require "net/ldap" 

class User < ActiveRecord::Base
  def self.authenticate(login, password, host, port)
    if login.to_s.length > 0 and password.to_s.length > 0
      ldap = Net::LDAP.new
      ldap.host = host
      ldap.port = port
      ldap.auth = "cn=#{login},cn=users,o=xyz...", password
      if ldap.bin
        return find(:first, :conditions => ['username=?', login])
      else
        return false
      end
    end
  end
end

ActiveMailer email performance on site5

Posted by Matt Parrish Thu, 09 Nov 2006 19:52:00 GMT

The site I am working on, RealIdaho.com, is hosted by site5. There are some forms that, when filled out by the user, send confirmation emails to the user, to us internally, and to the realtor’s cellphone. We started getting some reports that the confirmation page wouldn’t come up, and users were resubmitting the form many times. I discovered a few things while investigating that may be useful to others.

The first thing I noticed, is that the page would timeout after 15 seconds and just return a blank page. Not very friendly, and definitely explains why the users were unsure that the form was submitted properly. The site however continued to process the request, and eventually got all the emails out. I contacted the site5 support team and asked the 1) why is the page timing out after only 15 seconds, and 2) why was sending email so slow.

Here’s the answer I got for item 1:

“Changing the timeout would affect the global settings for apache. This results in a significant increase in the number of open connections that apache has and degrades server performance. I can ask about this but I don’t think it will be possible to increase the timeout.”

While I understand their reasoning, 15 seconds seems awfully short. I believe the default for apache is 5 minutes. Hopefully, they’ll consider increasing that amount at least to 30-60 seconds. While I never want a page that takes more than 5 seconds, I’m sure we’ll have some pages that do take longer, especially ones that need to communicate with external systems.

Regarding number 2, the support technician suggested using sendmail directly instead of sending email over SMTP, as that should be much faster. So, I went into RAILS_ROOT/config/environments/production.rb and added the following line:

ActiveMailer::Base.delivery_method = :sendmail

Here are the performance numbers:

Previous method (SMTP):

Sending Mail to user (25.22760)
Sending Mail to internal (31.11207)
Sending Mail notification (20.70688)

Current method (Sendmail):

Sending Mail to user (0.16841)
Sending Mail to internal (0.28479)
Sending Mail notification (0.24701)

Wow, that’s quite a difference! When I tested the form submission, the confirmation page appeared almost immediately. Now that’s more like it. Now I just need to see about increasing the timeout.