3

I'm developing an JSF web app on Tomcat, planning to use Seam in the near future, and I want to add compression of our web pages and resources (i.e. Javascript & CSS files). I'm aware of three methods to GZIP responses in a Java web :

  1. Use Ehcache GZIP filter: it's used in Appfuse, so it's probably solid and it checks if the user agent supports GZIP before applying it, but it seems to have problems with Seam, which we will be using http://seamframework.org/Community/EHCacheGZipFilterIncompatibleWithSeam.

  2. Use pjl-filter. From the stackoverflow question: Tomcat Compression Does Not Add a Content-Encoding: gzip in the Header, it appears it doesn't have any memory leaks, but I don't know if it has problems with Seam or not.

  3. Use Tomcat's built in compression - although it may not provide a content encoding (Tomcat 6.0.14 seems to work fine, but you can only provide a black list for what user agents compression should not be applied to.

Does anyone have experience with these methods in a JSF-Seam environment? Which is the "best" solution?

Thanks, Glen

Community
  • 1
  • 1
user120840
  • 33
  • 1
  • 6

8 Answers8

5

GZIP filter will reduce the initial load time significantly.
You can additionally implement a cacheFilter to bring performance of your screens at par with JavaScript based UI (https://stackoverflow.com/a/35567540/5076414).
For client side components, you can use Primefaces which is JQuery based UI.

Enable GZIP filter in JSF

Simply add this to your

web.xml

<filter>
    <filter-name>gzipResponseFilter</filter-name>
    <filter-class>org.omnifaces.filter.GzipResponseFilter</filter-class>
    <init-param>
        <description>The threshold size in bytes. Must be a number between 0 and 9999. Defaults to 150.</description>
        <param-name>threshold</param-name>
        <param-value>150</param-value>
    </init-param>
    <init-param>
        <description>The mimetypes which needs to be compressed. Must be a commaseparated string. Defaults to the below values.</description>
        <param-name>mimetypes</param-name>
        <param-value>
     text/plain, text/html, text/xml, text/css, text/javascript, text/csv, text/rtf,
     application/xml, application/xhtml+xml, application/x-javascript, application/javascript, application/json,
     image/svg+xml, image/gif, application/x-font-woff, application/font-woff2, image/png
 </param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>gzipResponseFilter</filter-name>
    <url-pattern>/*</url-pattern>
    <dispatcher>REQUEST</dispatcher>
    <dispatcher>ERROR</dispatcher>
</filter-mapping>
<error-page>
    <exception-type>java.lang.Throwable</exception-type>
    <location>/</location>
</error-page>   

And the following to your

pom.xml

    <dependency>
        <groupId>org.omnifaces</groupId>
        <artifactId>omnifaces</artifactId>
        <version>1.11</version>
    </dependency>

How to verify if my screen is using gzip

To see if your contents are already usign gzip and cache, In your Google Chrome Browser -> right click on your screen -> inspect -> click network tab -> refresh your screen. Click on the images, icons, stylesheets and see if you see following in response header

Content-Encoding:gzip if the status of element is 200

Community
  • 1
  • 1
Sacky San
  • 1,535
  • 21
  • 26
1

You should try the Jawr API

Martin Magakian
  • 3,746
  • 5
  • 37
  • 53
1

We use JBoss Seam on JBoss AS proxied and load balanced behind apache + mod_proxy_ajp with mod_deflate to compress outgoing traffic.

Advantages of this setup

  • lots of examples in the net
  • easy configuration/debugging considering quirks of different user agents
  • changing the configuration at runtime (apachectl graceful instead of restarting webapp/tomcat to reflect changes).
Tair
  • 3,779
  • 2
  • 20
  • 33
1

Use Tomcat's built in compression - although it may not provide a content encoding (Tomcat 6.0.14 seems to work fine, but you can only provide a black list for what user agents compression should not be applied to.

I think that you misinterpreted the problem which you found in Tomcat Compression Does Not Add a Content-Encoding: gzip in the Header. That problem is caused by using Apache HTTPD with mod_jk in front of Tomcat which is in turn badly configured that it does not send the Content-Encoding header back from Tomcat. This problem is not caused by Tomcat itself. Tomcat is doing its job perfectly fine.

I'd say, just go ahead with Tomcat's builtin compression. It's as easy as adding compression="on" attribute to the HTTP connector in server.xml. You have next to the noCompressionUserAgents setting also the compressableMimeType setting. Read the HTTP Connector Documentation.

Community
  • 1
  • 1
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
1

How about adding an nginx front-end and letting it to do the compression (and caching)?

http://wiki.nginx.org/Main

In this case, belongs on serverfalut :)

alamar
  • 18,729
  • 4
  • 64
  • 97
0

I tried a Servlet filter to add GZIP compression (not Ehcache though) and couldn't get it work properly. I ended up putting Apache with mod_jk in front of the app Server. All it took was a few minutes to configure GIP compressiion and I'm also feeling a lot more secure since just one app is exposed instead of the whole App Server.

Elmar Weber
  • 2,683
  • 28
  • 28
0

An alternative Servlet filter can be found here:

http://onjava.com/pub/a/onjava/2003/11/19/filters.html

Like Ehcache, it tests to see if the browser supports it. I can't say categorically if it plays nicely with Seam, but I've used it in the past without trouble.

Neil McKeown
  • 443
  • 1
  • 5
  • 17
0

I'm happy with the EhCache filter after some hacking. Here is how it works:

package myapp;

import net.sf.ehcache.constructs.web.GenericResponseWrapper;
import net.sf.ehcache.constructs.web.ResponseUtil;
import static org.jboss.seam.ScopeType.STATELESS;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.annotations.intercept.BypassInterceptors;
import org.jboss.seam.annotations.web.Filter;

import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.GZIPOutputStream;

/**
 * Zip content before sending to the browser.
 * 
 * 
 */
@Name("gzipFilter")
@Scope(STATELESS)
@BypassInterceptors
@Filter(around = "org.jboss.seam.web.ajax4jsfFilterInstantiator")
public class GzipFilter extends net.sf.ehcache.constructs.web.filter.Filter
{

    private static final Logger LOG = Logger.getLogger(GzipFilter.class.getName());

    /**
     * Performs initialisation.
     *
     * @param filterConfig config
     */
    protected void doInit(FilterConfig filterConfig) throws Exception
    {
        //nothing required.
    }


    /**
     * A template method that performs any Filter specific destruction tasks.
     * Called from {@link #destroy()}
     */
    protected void doDestroy()
    {
        //noop
    }

    /**
     * Performs the filtering for a request.
     */
    protected void doFilter(final HttpServletRequest request, final HttpServletResponse response,
                            final FilterChain chain) throws Exception
    {
        if (!isDocStore(request) && !isIncluded(request) && acceptsEncoding(request, "gzip"))
        {
            // Client accepts zipped content
            if (LOG.isLoggable(Level.FINE))
            {
                LOG.fine(request.getRequestURL() + ". Writing with gzip compression");
            }

            // Create a gzip stream
            final ByteArrayOutputStream compressed = new ByteArrayOutputStream();
            final GZIPOutputStream gzout = new GZIPOutputStream(compressed);

            // Handle the request
            final GenericResponseWrapper wrapper = new GenericResponseWrapper(response, gzout);
            chain.doFilter(request, wrapper);
            wrapper.flush();

            gzout.close();

            //return on error or redirect code, because response is already committed
            int statusCode = wrapper.getStatus();
            if (statusCode != HttpServletResponse.SC_OK)
            {
                return;
            }

            //Saneness checks
            byte[] compressedBytes = compressed.toByteArray();
            boolean shouldGzippedBodyBeZero = ResponseUtil.shouldGzippedBodyBeZero(compressedBytes, request);
            boolean shouldBodyBeZero = ResponseUtil.shouldBodyBeZero(request, wrapper.getStatus());
            if (shouldGzippedBodyBeZero || shouldBodyBeZero)
            {
                compressedBytes = new byte[0];
            }

            // Write the zipped body
            //ResponseUtil.addGzipHeader(response);
            response.setHeader("Content-Encoding", "gzip");
            response.setContentLength(compressedBytes.length);


            response.getOutputStream().write(compressedBytes);
        } else
        {
            // Client does not accept zipped content - don't bother zipping
            if (LOG.isLoggable(Level.FINE))
            {
                LOG.fine(request.getRequestURL()
                        + ". Writing without gzip compression because the request does not accept gzip.");
            }
            chain.doFilter(request, response);
        }
    }


    /**
     * Checks if the request uri is an include.
     * These cannot be gzipped.
     *
     * @param request the request
     * @return true if included
     */
    private boolean isIncluded(final HttpServletRequest request)
    {
        final String uri = (String) request.getAttribute("javax.servlet.include.request_uri");
        final boolean includeRequest = !(uri == null);

        if (includeRequest && LOG.isLoggable(Level.FINE))
        {
            LOG.fine(request.getRequestURL() + " resulted in an include request. This is unusable, because" +
                    "the response will be assembled into the overrall response. Not gzipping.");
        }
        return includeRequest;
    }

    private boolean isDocStore(final HttpServletRequest request)
    {
        return request.getRequestURI().indexOf("/docstore/") > 0;
    }

    /**
     * Determine whether the user agent accepts GZIP encoding. This feature is part of HTTP1.1.
     * If a browser accepts GZIP encoding it will advertise this by including in its HTTP header:
     * <p/>
     * <code>
     * Accept-Encoding: gzip
     * </code>
     * <p/>
     * Requests which do not accept GZIP encoding fall into the following categories:
     * <ul>
     * <li>Old browsers, notably IE 5 on Macintosh.
     * <li>Internet Explorer through a proxy. By default HTTP1.1 is enabled but disabled when going
     * through a proxy. 90% of non gzip requests seen on the Internet are caused by this.
     * </ul>
     * As of September 2004, about 34% of Internet requests do not accept GZIP encoding.
     *
     * @param request the request
     * @return true, if the User Agent request accepts GZIP encoding
     */
    protected boolean acceptsGzipEncoding(HttpServletRequest request)
    {
        return acceptsEncoding(request, "gzip");
    }


}
Stefano Travelli
  • 1,889
  • 1
  • 15
  • 18
  • did you upgrade this project to ehcache 2.1+? If yes, how does your code look like now, where _GenericResponseWrapper_ was removed? Thanks. – UrK Mar 18 '13 at 10:48
  • I didn't upgraded to 2.1 yet. By the way, if you have control over the Apache configuration, I think the most voted answer (using mod_deflate) should be considered as preferable. Unfortunately my customer wants it running on Windows IIS. – Stefano Travelli Mar 22 '13 at 09:25
  • Thanks, @Stefano. _GenericResponseWrapper_ was not removed from ehcache. It was never there. It seems, I just forgot to add the ehcache-web dependency. – UrK Mar 22 '13 at 13:03
  • Since this is a legacy project which is not even working yet with newer libraries, I really prefer not to change things too much. Like moving the compression into Apache. This is the exact case of "it works?- don't touch it". :-( – UrK Mar 22 '13 at 13:04