Monday, March 23, 2009

GZip Compress Static Files

My PeopleSoft web server contains several JavaScript and CSS files that I embed in PeopleSoft pages using the various techniques described in this blog. Unlike files served by the PeopleSoft application, the web server does not GZip compress these static text files. Until recently, I used the GZip ServletFilter described by Jayson Falkner in his post Two Servlet Filters Every Web Application Should Have. But GZipping every request for the same static file seemed like an unnecessary waste of CPU cycles. I got to thinking...

Could I eliminate the CPU cycles expended by GZipping those static files on every request? Is there a way store static GZipped content and serve the GZipped version to browsers that accept GZip encoding while still making the plain text version available to other browsers?

Here is the solution I cooked up: using the following rules with the tuckey.org UrlRewriteFilter ServletFilter, I can serve a static GZip file to browsers that accept GZip compression while still serving the plain text version to browsers that don't.

The URL Rewrite rules:

<!-- Browsers that support GZip -->
<rule>
<condition type="header" name="Accept-Encoding">.*gzip.*</condition>
<from>^/scripts/([^&lt;&gt;:"/\|?*]+\.js)$</from>
<to type="forward">/compressed/bin/scripts/$1.gz</to>
<set type="response-header" name="Content-Encoding">gzip</set>
</rule>

<rule>
<condition type="header" name="Accept-Encoding">.*gzip.*</condition>
<from>^/css/([^&lt;&gt;:"/\|?*]+\.css)$</from>
<to type="forward">/compressed/bin/css/$1.gz</to>
<set type="response-header" name="Content-Encoding">gzip</set>
</rule>

<!-- Browsers that do NOT support GZip -->
<rule>
<condition type="header" name="Accept-Encoding" operator="notequal">.*gzip.*</condition>
<from>^/scripts/([^&lt;&gt;:"/\|?*]+\.js)$</from>
<to type="forward">/compressed/minified/scripts/$1</to>
</rule>

<rule>
<condition type="header" name="Accept-Encoding" operator="notequal">.*gzip.*</condition>
<from>^/css/([^&lt;&gt;:"/\|?*]+\.css)$</from>
<to type="forward">/compressed/minified/css/$1</to>
</rule>

From this rules file, you can see that I store GZip compressed versions of my JavaScript files in /compressed/bin/scripts. If a requesting browser has the value gzip in the Accept-Encoding header and the request is for a file in the /scripts/ directory, then the first rule tells the UrlRewriteFilter to add the response header Content-Encoding: gzip and serve the version located at /compressed/bin/scripts/. Rule #2 is similar, but for CSS files. Rules 3 and 4 are the inverse of rules 1 and 2, telling the UrlRewriteFilter to serve files from /compressed/minified/scripts/ and /compressed/minified/css/. For example, if an IE 7 browser requests /scripts/jquery-1.3.2.min.js, then the UrlRewriteFilter will add the Content-Encoding: gzip response header and serve /compressed/bin/scripts/jquery-1.3.2.min.js.gz. If an LWP Perl browser posted the same request, then the UrlRewriteFilter would serve the file located at /compressed/minified/scripts/jquery-1.3.2.min.js (assuming the LWP Accept-Encoding header is not set).

Using URL Rewriting in this manner, I don't serve files from my /scripts/ and /css/ directories, and, therefore, don't need these directories on my web server. To avoid confusion caused by developers searching for these files on my web server, I put readme.txt files in each of these directories to explain that URL's pointing at these directories are rewritten to the /compressed directory.

If you use the UrlRewriteFilter, then be sure to use filter-mapping patterns that won't rewrite PeopleSoft URL's. Here are my mappings for the /css/ and /scripts/ URL's

<filter-mapping>
<filter-name>GzipFilter</filter-name>
<url-pattern>/scripts/*</url-pattern>
</filter-mapping>

<filter-mapping>
<filter-name>GzipFilter</filter-name>
<url-pattern>/css/*</url-pattern>
</filter-mapping>

You can create GZip versions of your files using the following command:

cat $file_location/scripts/jquery.js | gzip --stdout -9 > $file_location/bin/scripts/jquery.js.gz

And on Windows:

cat %file_location%\scripts\jquery.js | gzip --stdout -9 > %file_location%\bin\scripts\jquery.js.gz

Unfortunately, Windows doesn't have a cat or gzip command. To utilize these commands on Windows, I recommend installing UnxUtils.

Caveat: adding ServletFilters as described in this post may violate your PeopleSoft limited use web server license. Consult your license agreement to ensure compliance. If you use WebLogic, you can leverage the full power of your WebLogic instance by purchasing a WebLogic license from your Oracle rep. For a low cost alternative, you can reverse proxy your PeopleSoft web server with Apache's httpd server and use Apache's URL Rewrite engine (mod_rewrite) instead of the ServletFilter mentioned in this post.

12 comments:

omasampath said...

Hi,
Thanks for excellent guiding and I have tried to implement this scenario,but couldn't. Response header message was,
The resource from this URL is not text: http://localhost:8080/TFIP/css/custom/form.css

note : form.css -> form.gz

Thanks
Kumara

Jim Marion said...

@Kumara, I didn't spell it out very well above, but using the rules above, form.css in gzip form should be form.css.gz. I recommend using a debugging proxy like Fiddler to view the response contents and response headers. You should see two things: 1. A Content-Encoding header with a value of gzip and 2. The result should be gzip compressed.

Use Fiddler and let me know what you find.

mymithraa said...

Jim,

Pressing CTRL+J i am able to see Browser Compression is ON (gzip).Is there any way that i can ensure it is working.how to get the compression ratio

Jim Marion said...

@mymithraa, I believe Firebug and/or Fiddler will give you the compression ratio. Fiddler will show you the content encoding header, which tells you the type of compression used.

omasampath said...

Hi,

Can you upload working example for this article please.

Thanks,
Sampath

Jim Marion said...

@omasampath, I'm not sure what you mean by a working example. I gave the configuration information, but a working example would require a web server. It doesn't seem appropriate to upload a fully configured virtual image with WebLogic. Did you have something else in mind?

omasampath said...
This comment has been removed by the author.
omasampath said...

Hi,

Actually I have configured this and deployed on tomcat. But didn't success. Is there any additional server configuration for this or only need rules configuration file?

Thanks
Sampath

Jim Marion said...

@omasampath, you need the configuration file, the jar file, and your *.css.gz files, etc. You also need to modify your web.xml file.

I tested this on tomcat as well.

omasampath said...

Hi,
If you don't mind please give your email and I will send source files.

Thank You
Sampath

kkudi said...

I'm a bit unsure how you use the filter mappings. What is GzipFilter?

I already have this in my web.xml


UrlRewriteFilter
org.tuckey.web.filters.urlrewrite.UrlRewriteFilter



UrlRewriteFilter
/*
REQUEST
FORWARD



and it doesn't seem to forward the requests to the gz folders.

Jim Marion said...

@kkudi, I was just reviewing the blog post, and it does seem that there is quite a bit of information missing. For example, if you use the information in this post, you will be using the mapping described, but for the UrlRewrite Filter, not the GZip filter. The point of the post was to replace the GZip filter. Just make sure you have the UrlRewrite filter defined and that you have the mappings correct.

This was written a few years back and I no longer have this configuration.