1 /**
2 * Copyright 2003-2007 Greg Luck
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package net.sf.ehcache.constructs.web.filter;
18
19 import net.sf.ehcache.constructs.web.GenericResponseWrapper;
20 import net.sf.ehcache.constructs.web.ResponseUtil;
21 import org.apache.commons.logging.Log;
22 import org.apache.commons.logging.LogFactory;
23
24 import javax.servlet.FilterChain;
25 import javax.servlet.http.HttpServletRequest;
26 import javax.servlet.http.HttpServletResponse;
27 import java.io.ByteArrayOutputStream;
28 import java.util.zip.GZIPOutputStream;
29
30 /**
31 * Provides GZIP compression of responses.
32 * <p/>
33 * See the filter-mappings.xml entry for the gzip filter for the URL patterns
34 * which will be gzipped. At present this includes .jsp, .js and .css.
35 * <p/>
36 * @author <a href="mailto:gluck@thoughtworks.com">Greg Luck</a>
37 * @author <a href="mailto:amurdoch@thoughtworks.com">Adam Murdoch</a>
38 * @version $Id: GzipFilter.java 512 2007-07-10 09:18:45Z gregluck $
39 */
40 public class GzipFilter extends Filter {
41
42 private static final Log LOG = LogFactory.getLog(GzipFilter.class.getName());
43
44 /**
45 * Performs initialisation.
46 */
47 protected void doInit() throws Exception {
48 }
49
50 /**
51 * A template method that performs any Filter specific destruction tasks.
52 * Called from {@link #destroy()}
53 */
54 protected void doDestroy() {
55 //noop
56 }
57
58 /**
59 * Performs the filtering for a request.
60 */
61 protected void doFilter(final HttpServletRequest request, final HttpServletResponse response,
62 final FilterChain chain) throws Exception {
63 if (!isIncluded(request) && acceptsEncoding(request, "gzip")) {
64 // Client accepts zipped content
65 if (LOG.isDebugEnabled()) {
66 LOG.debug(request.getRequestURL() + ". Writing with gzip compression");
67 }
68
69 // Create a gzip stream
70 final ByteArrayOutputStream compressed = new ByteArrayOutputStream();
71 final GZIPOutputStream gzout = new GZIPOutputStream(compressed);
72
73 // Handle the request
74 final GenericResponseWrapper wrapper = new GenericResponseWrapper(response, gzout);
75 chain.doFilter(request, wrapper);
76 wrapper.flush();
77
78 gzout.close();
79
80 //return on error or redirect code, because response is already committed
81 int statusCode = wrapper.getStatus();
82 if (statusCode != HttpServletResponse.SC_OK) {
83 return;
84 }
85
86 //Saneness checks
87 byte[] compressedBytes = compressed.toByteArray();
88 boolean shouldGzippedBodyBeZero = ResponseUtil.shouldGzippedBodyBeZero(compressedBytes, request);
89 boolean shouldBodyBeZero = ResponseUtil.shouldBodyBeZero(request, wrapper.getStatus());
90 if (shouldGzippedBodyBeZero || shouldBodyBeZero) {
91 compressedBytes = new byte[0];
92 }
93
94 // Write the zipped body
95 ResponseUtil.addGzipHeader(response);
96 response.setContentLength(compressedBytes.length);
97
98
99 response.getOutputStream().write(compressedBytes);
100 } else {
101 // Client does not accept zipped content - don't bother zipping
102 if (LOG.isDebugEnabled()) {
103 LOG.debug(request.getRequestURL()
104 + ". Writing without gzip compression because the request does not accept gzip.");
105 }
106 chain.doFilter(request, response);
107 }
108 }
109
110
111 /**
112 * Checks if the request uri is an include.
113 * These cannot be gzipped.
114 */
115 private boolean isIncluded(final HttpServletRequest request) {
116 final String uri = (String) request.getAttribute("javax.servlet.include.request_uri");
117 final boolean includeRequest = !(uri == null);
118
119 if (includeRequest && LOG.isDebugEnabled()) {
120 LOG.debug(request.getRequestURL() + " resulted in an include request. This is unusable, because" +
121 "the response will be assembled into the overrall response. Not gzipping.");
122 }
123 return includeRequest;
124 }
125
126 /**
127 * Determine whether the user agent accepts GZIP encoding. This feature is part of HTTP1.1.
128 * If a browser accepts GZIP encoding it will advertise this by including in its HTTP header:
129 * <p/>
130 * <code>
131 * Accept-Encoding: gzip
132 * </code>
133 * <p/>
134 * Requests which do not accept GZIP encoding fall into the following categories:
135 * <ul>
136 * <li>Old browsers, notably IE 5 on Macintosh.
137 * <li>Internet Explorer through a proxy. By default HTTP1.1 is enabled but disabled when going
138 * through a proxy. 90% of non gzip requests seen on the Internet are caused by this.
139 * </ul>
140 * As of September 2004, about 34% of Internet requests do not accept GZIP encoding.
141 *
142 * @param request
143 * @return true, if the User Agent request accepts GZIP encoding
144 */
145 protected boolean acceptsGzipEncoding(HttpServletRequest request) {
146 return acceptsEncoding(request, "gzip");
147 }
148
149
150 }