1 | // Copyright 2004-2007 Jean-Francois Poilpret |
2 | // |
3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
4 | // you may not use this file except in compliance with the License. |
5 | // You may obtain a copy of the License at |
6 | // |
7 | // http://www.apache.org/licenses/LICENSE-2.0 |
8 | // |
9 | // Unless required by applicable law or agreed to in writing, software |
10 | // distributed under the License is distributed on an "AS IS" BASIS, |
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 | // See the License for the specific language governing permissions and |
13 | // limitations under the License. |
14 | |
15 | package net.sourceforge.hiveutils.web.util; |
16 | |
17 | import java.io.BufferedInputStream; |
18 | import java.io.BufferedOutputStream; |
19 | import java.io.IOException; |
20 | import java.io.InputStream; |
21 | import java.io.OutputStream; |
22 | import java.util.zip.GZIPInputStream; |
23 | import java.util.zip.GZIPOutputStream; |
24 | |
25 | import javax.servlet.Filter; |
26 | import javax.servlet.FilterChain; |
27 | import javax.servlet.FilterConfig; |
28 | import javax.servlet.ServletException; |
29 | import javax.servlet.ServletInputStream; |
30 | import javax.servlet.ServletOutputStream; |
31 | import javax.servlet.ServletRequest; |
32 | import javax.servlet.ServletResponse; |
33 | import javax.servlet.http.HttpServletRequest; |
34 | import javax.servlet.http.HttpServletRequestWrapper; |
35 | import javax.servlet.http.HttpServletResponse; |
36 | import javax.servlet.http.HttpServletResponseWrapper; |
37 | |
38 | import org.apache.commons.logging.Log; |
39 | import org.apache.commons.logging.LogFactory; |
40 | |
41 | /** |
42 | * Servlet Filter that uncompresses incoming request (if compressed with gzip) |
43 | * and conditionnally compresses outgoing responses (if client accepts gzip |
44 | * encoding). |
45 | * |
46 | * @author Jean-Francois Poilpret |
47 | */ |
48 | public class GzipFilter implements Filter |
49 | { |
50 | private static final Log _logger = LogFactory.getLog(GzipFilter.class); |
51 | private static final int IN_BUFFER_SIZE = 512; |
52 | private static final int OUT_BUFFER_SIZE = 512; |
53 | |
54 | public void init(FilterConfig config) throws ServletException |
55 | { |
56 | _config = config; |
57 | // Get gzip size threshold |
58 | _gzipThreshold = parseValue("gzip-threshold", 0, "init() gzip-threshold is incorrect"); |
59 | |
60 | // Get gzip input buffer size |
61 | _gzipInBufferSize = parseValue( "gzip-in-buffer-size", |
62 | IN_BUFFER_SIZE, |
63 | "init() gzip-in-buffer-size is incorrect"); |
64 | |
65 | // Get gzip output buffer size |
66 | _gzipOutBufferSize = parseValue("gzip-out-buffer-size", |
67 | OUT_BUFFER_SIZE, |
68 | "init() gzip-out-buffer-size is incorrect"); |
69 | } |
70 | |
71 | protected int parseValue(String property, int min, String message) |
72 | { |
73 | // Get gzip size threshold |
74 | String input = _config.getInitParameter(property); |
75 | int value = min; |
76 | if (input != null) |
77 | { |
78 | try |
79 | { |
80 | value = Integer.parseInt(input); |
81 | if (value < min) |
82 | { |
83 | value = min; |
84 | } |
85 | } |
86 | catch (NumberFormatException e) |
87 | { |
88 | _logger.warn(message, e); |
89 | } |
90 | } |
91 | return value; |
92 | } |
93 | |
94 | public void destroy() |
95 | { |
96 | } |
97 | |
98 | public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) |
99 | throws IOException, ServletException |
100 | { |
101 | HttpServletRequest req = (HttpServletRequest) request; |
102 | HttpServletResponse res = (HttpServletResponse) response; |
103 | |
104 | String encoding = req.getHeader("content-encoding"); |
105 | String accepts = req.getHeader("accept-encoding"); |
106 | if ("gzip".equals(encoding)) |
107 | { |
108 | req = new GzipRequest(req); |
109 | } |
110 | if (accepts != null && accepts.indexOf("gzip") != -1) |
111 | { |
112 | res = new GzipResponse(res); |
113 | } |
114 | |
115 | try |
116 | { |
117 | chain.doFilter(req, res); |
118 | } |
119 | finally |
120 | { |
121 | if (res instanceof GzipResponse) |
122 | { |
123 | ((GzipResponse) res).finish(); |
124 | } |
125 | } |
126 | } |
127 | |
128 | // Specific wrapper for servlet request |
129 | private class GzipRequest extends HttpServletRequestWrapper |
130 | { |
131 | public GzipRequest(HttpServletRequest request) |
132 | { |
133 | super(request); |
134 | } |
135 | |
136 | public ServletInputStream getInputStream() throws IOException |
137 | { |
138 | if (_stream == null) |
139 | { |
140 | _stream = new GzipServletInputStream(super.getInputStream()); |
141 | } |
142 | return _stream; |
143 | } |
144 | |
145 | // Specific servlet input stream (merely delegates to GZIPInputStream) |
146 | private class GzipServletInputStream extends ServletInputStream |
147 | { |
148 | public GzipServletInputStream(ServletInputStream is) throws IOException |
149 | { |
150 | _input = new BufferedInputStream(new GZIPInputStream(is), _gzipInBufferSize); |
151 | } |
152 | |
153 | public int read() throws IOException |
154 | { |
155 | return _input.read(); |
156 | } |
157 | |
158 | protected final InputStream _input; |
159 | } |
160 | |
161 | protected GzipServletInputStream _stream = null; |
162 | } |
163 | |
164 | // Specific wrapper for servlet response |
165 | private class GzipResponse extends HttpServletResponseWrapper |
166 | { |
167 | public GzipResponse(HttpServletResponse response) |
168 | { |
169 | super(response); |
170 | } |
171 | |
172 | public ServletOutputStream getOutputStream() throws IOException |
173 | { |
174 | if (_stream == null) |
175 | { |
176 | _stream = new GzipServletOutputStream(super.getOutputStream()); |
177 | } |
178 | return _stream; |
179 | } |
180 | |
181 | public void finish() throws IOException |
182 | { |
183 | if (_stream != null) |
184 | { |
185 | _stream.finish(); |
186 | } |
187 | } |
188 | |
189 | // Specific servlet input stream |
190 | private class GzipServletOutputStream extends ServletOutputStream |
191 | { |
192 | public GzipServletOutputStream(ServletOutputStream os) |
193 | throws IOException |
194 | { |
195 | _os = os; |
196 | if (_gzipThreshold >= 0) |
197 | { |
198 | _buffer = new byte[_gzipThreshold]; |
199 | } |
200 | } |
201 | |
202 | public void write(int b) throws IOException |
203 | { |
204 | if (_current == 0) |
205 | { |
206 | _startTime = System.currentTimeMillis(); |
207 | } |
208 | if (_current < _gzipThreshold) |
209 | { |
210 | // Still under gzip threshold, just buffer data temporarily |
211 | _buffer[_current++] = (byte) b; |
212 | } |
213 | else |
214 | { |
215 | // Over gzip threshold already |
216 | if (_output == null) |
217 | { |
218 | // We have just reached the threshold, write out buffer first |
219 | GzipResponse.this.setHeader("Content-Encoding", "gzip"); |
220 | _output = new GZIPOutputStream(_os); |
221 | _os = new BufferedOutputStream(_output, _gzipOutBufferSize); |
222 | _os.write(_buffer); |
223 | _buffer = null; |
224 | } |
225 | // Add the new byte to gzip output |
226 | _os.write(b); |
227 | } |
228 | } |
229 | |
230 | public void finish() throws IOException |
231 | { |
232 | if (_output == null) |
233 | { |
234 | // The total size is under gzip threshold, just write out the buffer |
235 | // directly |
236 | _os.write(_buffer, 0, _current); |
237 | _os.flush(); |
238 | } |
239 | else |
240 | { |
241 | _os.flush(); |
242 | _output.finish(); |
243 | _startTime = System.currentTimeMillis() - _startTime; |
244 | _logger.debug("GZIP TIME = " + _startTime + " ms"); |
245 | } |
246 | } |
247 | |
248 | protected OutputStream _os; |
249 | protected GZIPOutputStream _output = null; |
250 | protected byte[] _buffer = null; |
251 | protected int _current = 0; |
252 | protected long _startTime = 0; |
253 | } |
254 | |
255 | protected GzipServletOutputStream _stream = null; |
256 | } |
257 | |
258 | protected FilterConfig _config; |
259 | protected int _gzipThreshold = 0; |
260 | protected int _gzipInBufferSize = IN_BUFFER_SIZE; |
261 | protected int _gzipOutBufferSize = OUT_BUFFER_SIZE; |
262 | } |
263 | |