/* Copyright 2011-2014 Red Hat, Inc This file is part of PressGang CCMS. PressGang CCMS is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. PressGang CCMS is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with PressGang CCMS. If not, see <http://www.gnu.org/licenses/>. */ package org.jboss.pressgang.ccms.server.servlet.filter; 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.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Enumeration; import java.util.HashSet; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.regex.Pattern; import org.jboss.pressgang.ccms.server.constants.Constants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A Servlet Filter to Compress the response of a HTTP Request using the GZIP compression algorithm. * <p/> * It allows to you specify what MIME types should be compressed using the "mime-types" init parameter. * <br><br> * Sample web.xml config: * <pre>{@code<filter> <filter-name>compression</filter-name> <filter-class>org.jboss.pressgang.ccms.server.servlet.filter.GZIPCompressionFilter</filter-class> <init-param> <param-name>mime-types</param-name> <param-value>application/json, application/svg+xml, text/*</param-value> </init-param> </filter> <filter-mapping> <filter-name>compression</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>}</pre> */ @WebFilter(urlPatterns = Constants.BASE_REST_PATH + "/*") public class GZIPCompressionFilter implements Filter { private static Logger log = LoggerFactory.getLogger(GZIPCompressionFilter.class); private static AtomicBoolean initialised = new AtomicBoolean(false); private boolean enabled = false; private Set<Pattern> mimeTypes = new HashSet<Pattern>(); @Override public void destroy() { if (enabled) { log.debug("Destroying the GZIP Compression Filter"); enabled = false; initialised.set(false); } } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // If the filter wasn't initialised then forward the request if (!enabled || !(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse) || isIncluded( (HttpServletRequest) request)) { chain.doFilter(request, response); return; } // Check if the request will accept compression boolean supportCompression = false; Enumeration<String> e = ((HttpServletRequest) request).getHeaders("Accept-Encoding"); while (e.hasMoreElements()) { final String headerName = e.nextElement(); if (headerName.indexOf("gzip") != -1) { supportCompression = true; } } // Do the compression if it's supported otherwise continue down the chain if (!supportCompression) { chain.doFilter(request, response); return; } else { final GZIPResponseWrapper responseWrapper = new GZIPResponseWrapper((HttpServletResponse) response, mimeTypes); chain.doFilter(request, responseWrapper); responseWrapper.finish(); } } /** * Checks if the request uri is an include. These cannot be gzipped. */ private boolean isIncluded(final HttpServletRequest request) { final String uri = (String) request.getAttribute("javax.servlet.include.request_uri"); final boolean includeRequest = !(uri == null); return includeRequest; } @Override public void init(FilterConfig filterConfig) throws ServletException { /* * We only want one instance of the compression filter to be active at one time. As such we use a static variable to * determine if one has already been initialised. If it has then we leave this instance alone so it'll just continue * down the Filter Chain. */ if (initialised.compareAndSet(false, true)) { log.debug("Initialising the GZIP Compression Filter"); enabled = true; // Load the mime types that should be compressed. If the parameter isn't set then compress all types. final String compressedMimeTypes = filterConfig.getInitParameter("mime-types"); if (compressedMimeTypes != null) { String[] mimeTypeNames = compressedMimeTypes.split("\\s*,\\s*"); for (int i = 0; i < mimeTypeNames.length; i++) { mimeTypes.add(Pattern.compile(mimeTypeNames[i].replace("+", "\\+").replace("*", ".*"))); } } else { mimeTypes.add(Pattern.compile(".*")); } } } }