Nicolas Frankel is an IT consultant with 10 years experience in Java / JEE environments. He likes his job so much he writes technical articles on his blog and reviews technical books in his spare time. He also tries to find other geeks like him in universities, as a part-time lecturer. Nicolas is a DZone MVB and is not an employee of DZone and has posted 224 posts at DZone. You can read more from them at their website. View Full User Profile

Decrease Your Pages Load Time, One Year Later

05.18.2010
| 5642 views |
  • submit to reddit

More than one year ago, I blogged about pages load time and Jawr. Since then, either my projects were intranet applications where load time was not an issue or we had no say in the tools used. So, I had no possibility to use any of the tools I found then. Now, I’m soon to be faced by an application that will be used by people outside our network and I definitely want the application to be as responsive as possible. Armed with my previous Yahoo best practices, I hunted on the web for tools that could help me enforce them.

One rule I hold in high esteem is: minimize the number of HTTP requests. I think this rule has the most impact since browsers only send at most 2 HTTP requests at a time. Let’s take a classical example. Buttons play a big part in many applications and most of the time, you put an icon on them in order to tie them to the action in the user’s mind. This is simply done like so:

<button><img src="path/to/my/image" />Label</button>

This means that any screen that display more than one button type has the potential to be slow. One way to dramatically decrease the number of requests is to aggregate all the images in one and use CSS background images. The latter is available since the first version of CSS, just specify the background-image attribute to display the image behind an element . This usage is called CSS Sprites. The code now looks like:

<button class="update">Label</button>
button.update {
background-image: url('path/to/my/image');
background-repeat: no-repeat;
}

The astute reader will have remarked that the previous code has the same effect as the <img> in the <button>. The real usage is to use only the portion of the master image that we want, something akin to that:

button.update {
background: transparent url('path/to/my/aggregate') -50px -50px no-repeat;
}

To aggregate the master image requires the right image tool. To compute the portion we want for each button is a pain in the butt (to speak politely).

SmartSprites LogoLuckily, my old friend Jawr has the right feature to help me. Since version 3.2, it includes SmartSprites, a product that just does all aggregating and computing for me. How does that work? Just by putting some comments in your CSS:

  • to declare how the master(s) image(s) will be constructed
  • to tell which sprite will take part in which master image
/** sprite: master-sprite-gif; sprite-image: url('../img/master-sprite.gif'); sprite-layout: vertical */

button.one {
background-image: url('../img/certificate_preferences.gif'); /** sprite-ref: master-sprite-gif; */
}

button.two {
background-image: url('../img/inbox_into.gif'); /** sprite-ref: master-sprite-gif; */
}

button.three {
background-image: url('../img/mail_forward.gif'); /** sprite-ref: master-sprite-gif; */
}

The previous CSS is an example by which all three GIFs will be found at runtime under ../img/master-sprite.gif. If you look at the generated CSS, it will have the following look:

button.one{background-image:url('../cb3648025844/img/master-sprite.gif');background-position:left -0px;}
button.two{background-image:url('../cb3648025844/img/master-sprite.gif');background-position:left -48px;}
button.three{background-image:url('../cb3648025844/img/master-sprite.gif');background-position:left -96px;}
Of course, the real generated CSS will have been concatenated and minified (see below).

Note: I’m not a GIF expert but I suspect original GIFs should have similar color tables, otherwise, the aggregated GIF will look really strange.

However, Jawr also provides some very interesting features, both old and new:

  • minification, for both CSS and JavaScript. You can also choose the minifying engine (embedded, JSMin or YUI Compressor). The latter is even able to obfuscate your source, thus even making your JS even lighter in the process
  • license inclusion, so that even minified sources can display their license
  • runtime GZip compression if the client browser supports it
  • HTTP header management so that requests for the same resources (beside the first) will be answered by a HTTP 304 – Not Modified code, thus avoiding unnecessary network traffic
  • i18n management in order to use Java standard properties files for internationalized messages in your JavaScript
  • JavaScript to use Jawr without taglibs in plain HTML pages (though it is not recommended since it adds a script to the page, thus another HTTP request)
  • possibility of extension, with new pre- and post- processors
  • ability to switch between development mode (no modification for resources) and production (virtual resources served by Jawr)
  • bundle preprocessing, so that the processing is not done at startup but instead at build time: this can be done either by calling an Ant task or by configuring a Maven plugin
  • for truly engaged application server administrators, support for JMX

During the last year, it seems Jawr has gained some nice upgrades. I don’t know how many people truly use it but I think it deserves some interest when you need to optimize performance on the page loading time front.

You can download the sources for this article in Eclipse/Maven format here.

From http://blog.frankel.ch/decrease-your-pages-load-time-2

Published at DZone with permission of Nicolas Frankel, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Tags:

Comments

Loren Kratzke replied on Tue, 2010/05/18 - 6:04pm

JAWR is a good library and does lots of good stuff for performance. I have nothing bad to say in general however there is one major caveat that you should be aware of if using JAWR in a clustered environment. I have been meaning to post this on the JAWR site but can't resist posting it here now.

If you are operating in a clustered environment and using a CDN then forget about ever doing a rolling restart if using JAWR. Here is the deal. Both of these URLs will return the exact same file from your server (response code 200 in both cases):

 http://myhost.com/context/jsJawr/gzip_626110981/bundles/cool.css
 http://myhost.com/context/jsJawr/gzip_123456789-I-have-no-clue-about-clusters-so-ignore-my-own-hash/bundles/cool.css

Yes, JAWR will generate a unique hash for cache busting, but it totally ignores it in the request. If cool.css changes and you do a rolling restart, when a new box sends a page containing the link with the new hash, and an old box gets the request for the file with the new hash, then the old box will serve the old file in response to the request for the new file. This response will be cached by the CDN and you are toast until you flush the CDN cache.

The proper way for JAWR to operate is to respect its own hash, send a 302 redirect if the hash does not match, and after n redirects it should serve the old file with cache headers set to false so that the CDN does not cache old content under the new name. (Something is better than nothing in almost all cases.)

Fortunately, JAWR will generate all of its bundles at startup time so I wrote my own tags and a controller that implement the correct algorithm. I use an MD5 hash instead of whatever they are using and I prepend it to the file name (since I am not concerned about ignoring a large section of the path). Now we can do zero downtime rolling restarts without the strange side effects of pages getting the wrong css/js and without concern for flushing the CDN (and the 20 minutes of strange behavior associated with that).

If you do not run your app in a cluster, then you can totally ignore this behavior and JAWR will work perfectly for you. And it does work well.

 

 

Loren Kratzke replied on Tue, 2010/05/18 - 9:00pm

I just wanted to add that whether or not you use a CDN, the browser is equally capable of caching the wrong file.

ibrahim chaehoi replied on Thu, 2010/05/20 - 6:42am

Hi Nicolas,

Thanks for your article. I hope that it will give more interest to the Jawr project and Smartsprites for the Java community.

Hi Loren,

Thanks for pointing out this issue.

We will be glad to integrate your patch in Jawr, if you want to share it with the community.

If you like to do so, thanks to create a defect in the issue tracker about it (https://jawr.dev.java.net/servlets/ProjectIssues)

In any case, don't hesitate to share your issue on the discussion forum, it will help us to improve Jawr, and it will allow every Jawr users to find more information on how to manage the different issue, which they can face.

Best regards,

Ibrahim,

 Jawr project leader

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.