Matt RaibleMatt Raible is a Web Architecture Consultant specializing in open source frameworks.

10+ YEARS


Over 10 years ago, I wrote my first blog post. Since then, I've authored books, had kids, traveled the world, found Trish and blogged about it all.

The Battle of the GZip Filters

When I first added a Compression/GZip filter to AppFuse, I used the one from Roller, which I believe Lance found in this book. This has worked fairly well since I added it in July last year. When I discovered that there were issues with it on Resin, I chaulked it up as "no big deal" since I don't use Resin anyway. But yesterday, when I discovered that it stopped my apps from displaying my 403 <error-code> page, that was the last straw. I remembered seeing the "Two Servlet Filters Every Web Application Should Have" article on ONJava.com about a different implementation, so decided to download the source and try it out.

I quickly discovered that this Filter does work on Resin, so that's quite a bonus. I've had issues getting Roller to work on Resin with the Filter enabled, so I might have to replace Roller's CompressionFilter. However, I did still have to change a few things to convince this Filter to satisfy my needs.

Here are a few things I discovered about this GZIPFilter vs. Roller's CompressionFilter:

  • Don't download the GZIPFilter from the article. There is a newer version of the code. Not much has changed, save for an almost completely re-written GZipResponseStream.java file. This one supposedly does better handling of large files.
  • This Filter has the same problem I experienced with Roller's CompressionFilter: JSP pages don't finish rendering when running my Canoo WebTests. I'm assuming that this is because the buffer hasn't finished spitting out HTML. I ended up writing a new isGZIPSupported() method (in GZIPFilter.java) to do the check for GZip support. This allows my webtests to run smoothly by disabling the filter for HttpUnit.
  • This Filter shares another issue that I found in the CompressionFilter yesterday. When my webapp returns an HttpServletResponse.SC_FORBIDDEN error code (from trying to access a method that denies the users role), the Filter suppresses the error and the user is not served up the 403 error page defined in my web.xml. To fix this, I overrode sendError() in GZIPResponseWrapper.java and added a check for this error code in the getWriter() method.

Overall, I'm pleased with this code because I love the concept of GZip Filtering, and now it's not causing any conflicts in my app or targeted appservers.

GZIPFilter.isGZIPSupported(HttpServletRequest):

    private boolean isGZIPSupported(HttpServletRequest req) {
        String browserEncodings = req.getHeader("accept-encoding");
        boolean supported =
            ((browserEncodings != null&&
            (browserEncodings.indexOf("gzip"!= -1));

        String userAgent = req.getHeader("user-agent");

        if (userAgent.startsWith("httpunit")) {
            if (log.isDebugEnabled()) {
                log.debug("httpunit detected, disabling filter...");
            }

            return false;
        else {
            return supported;
        }
    }

GZIPResponseWrapper.sendError(int, java.lang.String):

    public void sendError(int error, String messagethrows IOException {
        super.sendError(error, message);
        this.error = error;

        if (log.isDebugEnabled()) {
            log.debug("sending error: " + error + " [" + message + "]");
        }
    }

GZIPResponseWrapper.getWriter():

    public PrintWriter getWriter() throws IOException {
        // If access denied, don't create new stream or write because
        // it causes the web.xml's 403 page to not render
        if (this.error == HttpServletResponse.SC_FORBIDDEN) {
            return super.getWriter();
        }

        if (writer != null) {
            return (writer);
        }

Posted in Java at Jan 09 2004, 11:30:43 AM MST 15 Comments
Comments:

Perhaps this is irrelevant now, but did you ever try just using the GZip code that ships with Tomcat? I believe it's under examples or something...

Posted by Will Gayther on January 09, 2004 at 12:59 PM MST #

I've heard about that, but since that is a vendor-specific solution, I prefer the Fitler approach. Apache's HTTP Server also has a mod_deflate module that can be used.

Posted by Matt Raible on January 09, 2004 at 01:03 PM MST #

why don't you let the user decide which gzip-filter to use? resin ships with its own gzip-filter which works fine for it as does tomcat. i think such a kind of tools do not belong into a web applikation framework.

Posted by andi on January 09, 2004 at 01:11 PM MST #

Andi,

This is a reasonable suggestion, but most developers I've talked to recently give me a blank stare when I mention gzip-filtering. For folks that use AppFuse as the basis of their apps, they can easily rip out the code and use their appserver's gzip-filter, I just want to provide at least <strong>one</strong> option.

Posted by Matt Raible on January 09, 2004 at 01:23 PM MST #

yes, you are right, sadly not many developers know about optimization techniques like this. i can only recommend these two books: "web performance tuning" and "web caching". they have shown me how important it is to take a look outside of the war-dir!

Posted by andi on January 09, 2004 at 02:24 PM MST #

  • Probably should use getHeaders() instead of getHeader().
  • Not sure the check for HttpUnit should be necessary. Why is HttpUnit sending an accept gzip header?
  • The check for the 403 seems overly specific. Wouldn't it make sense to avoid the compressed output stream for any error response?
  • I started with the filter at http://www.servlets.com/soapbox/filters.html, but I also had to modify it significantly to make it work.

Posted by Jay Dunning on January 09, 2004 at 04:06 PM MST #

err...I don't believe the Tomcat filter is Tomcat specific, and since it's licensed under the apache license, I know it can be redistributed.

Posted by Will Gayther on January 09, 2004 at 04:56 PM MST #

Jay - the reason for the check for HttpUnit is because the JSPs don't finish loading with Canoo's WebTest. I think Canoo grabs the output and then parses the HTML to do stuff. Probably a bug in Canoo's WebTest. As for the errors - you're probably right, but 404 and 500 seem to work fine, so I wasn't worried about the others.

Posted by Matt Raible on January 09, 2004 at 06:04 PM MST #

http://www.caucho.com/resin-3.0/servlet/filter-library.xtp#GzipFilter

Posted by Unknown on January 18, 2004 at 06:43 AM MST #

Matt, I'm confused as to how the GZip filter works in the appfuse struts application. Looking at UserAction.java, you are doing standard Struts - "return mapping.findForward("edit");" - forwarding to the JSP. Based on the results I obtain (and someone that posted to the Struts forum) once you do a forward, the filter does not get to handle any output that is subsequently generated. Here's a post from Craig McClanahan saying that you need to be at Servlet spec 2.4 for this to be handled: http://www.mail-archive.com/struts-user@jakarta.apache.org/msg89877.html If I'm missing something I would appreciate an explanation. I was trying to use a filter with FOP to convert an SVG graphic (created by the JSP page I forward to) into a PDF for display on the user's browser. Thanks - Richard

Posted by Richard Mixon on January 21, 2004 at 09:31 PM MST #

Richard - it works because *somewhere* along the way, the *.do request has to return HTML back to the client. When it does so, the GZIPFilter compresses it. It doesn't compress it between forward requests. If you want to do that, you'd have to use a Filter with <dispatcher>FORWARD</dispatcher> as part of it's definition in web.xml. This is a Servlet 2.4 feature.

Posted by Matt Raible on January 22, 2004 at 08:11 AM MST #

Matt, not trying to be hard-headed.

It looks to me like appfuse is doing a forward in UserAction.java.

So will the HTML generated by the forward from UserAction.do to the JSP page be compressed?

Posted by Richard Mixon on January 22, 2004 at 10:48 AM MST #

Sorry, in the previous post, I meant to say will it be compressed under the Servlet 2.3 spec (Tomcat 4.1.2x).

Posted by Richard Mixon on January 22, 2004 at 10:49 AM MST #

The response will not be compressed until an ActionForward is returned that points to a JSP (or a tile definition). Since it's impossible to map filters to forwards in 2.3, the filter will not be invoked when you forward from one action to another.

Posted by Matt Raible on January 22, 2004 at 10:49 AM MST #

Matt, I meant to comment a long time ago that you should try replacing Roller's CompressionFilter with Jayson's (in Roller, that is). Since it does seem to have better things going for it (like being actively kept up) we should use it instead - perhaps with your tweaks for sendError().

Posted by Lance on February 05, 2004 at 08:22 AM MST #

Post a Comment:
Comments are closed for this entry.