1
2
3
4 package com.atlassian.gzipfilter;
5
6 import com.atlassian.gzipfilter.selector.GzipCompatibilitySelector;
7 import com.atlassian.gzipfilter.util.HttpContentType;
8 import org.slf4j.Logger;
9 import org.slf4j.LoggerFactory;
10
11 import java.io.IOException;
12 import java.io.PrintWriter;
13 import javax.servlet.ServletOutputStream;
14 import javax.servlet.ServletResponse;
15 import javax.servlet.http.HttpServletResponse;
16 import javax.servlet.http.HttpServletResponseWrapper;
17
18
19
20
21
22
23
24
25
26
27
28 public class SelectingResponseWrapper extends HttpServletResponseWrapper
29 {
30 private static final Logger log = LoggerFactory.getLogger(SelectingResponseWrapper.class);
31
32 private final RoutablePrintWriter routablePrintWriter;
33 private final RoutableServletOutputStream routableServletOutputStream;
34 private final GzipCompatibilitySelector compatibilitySelector;
35
36 private final GzipResponseWrapper wrappedResponse;
37
38 private boolean gzippablePage = false;
39 private boolean headersCommitted = false;
40
41 public SelectingResponseWrapper(final HttpServletResponse unWrappedResponse, GzipCompatibilitySelector compatibilitySelector, String defaultEncoding)
42 {
43 super(unWrappedResponse);
44 this.wrappedResponse = new GzipResponseWrapper(unWrappedResponse, defaultEncoding);
45 this.compatibilitySelector = compatibilitySelector;
46
47 Runnable gzipHeadersCommitter = new Runnable()
48 {
49
50
51
52
53 public void run()
54 {
55 commitGzipHeaders();
56 }
57 };
58 routablePrintWriter = new RoutablePrintWriter(
59 new RoutablePrintWriterDestinationFactory(unWrappedResponse),
60 gzipHeadersCommitter);
61 routableServletOutputStream = new RoutableServletOutputStream(
62 new RoutableServletOutputStreamDestinationFactory(unWrappedResponse),
63 gzipHeadersCommitter);
64 }
65
66 public void setContentType(String type)
67 {
68 super.setContentType(type);
69
70 if (type != null)
71 {
72 final HttpContentType httpContentType = new HttpContentType(type);
73 if (compatibilitySelector.shouldGzip(httpContentType.getType()))
74 {
75 activateGzip(httpContentType.getEncoding());
76 }
77 else
78 {
79 deactivateGzip();
80 }
81 }
82 }
83
84
85
86
87
88
89 public void sendRedirect(String location) throws IOException
90 {
91 if (!wrappedResponse.isCommitted() && gzippablePage)
92 deactivateGzip();
93 super.sendRedirect(location);
94 }
95
96
97
98
99
100
101
102
103
104 public void setStatus(int statusCode, String sm)
105 {
106 super.setStatus(statusCode, sm);
107 if (!shouldGzip(statusCode))
108 {
109 deactivateGzip();
110 }
111 }
112
113
114
115
116
117
118
119
120
121 public void setStatus(int statusCode)
122 {
123 super.setStatus(statusCode);
124 if (!shouldGzip(statusCode))
125 {
126 deactivateGzip();
127 }
128 }
129
130 public void sendError(int sc, String msg) throws IOException
131 {
132 if (gzippablePage)
133 {
134 deactivateGzip();
135 }
136
137 super.sendError(sc, msg);
138 }
139
140 public void sendError(int sc) throws IOException
141 {
142 if (gzippablePage)
143 {
144 deactivateGzip();
145 }
146
147 super.sendError(sc);
148 }
149
150
151
152
153
154
155 private boolean shouldGzip(int statusCode)
156 {
157 return statusCode != SC_NO_CONTENT && statusCode != SC_NOT_MODIFIED;
158 }
159
160
161
162
163 private void commitGzipHeaders()
164 {
165 if (headersCommitted) {
166 return;
167 }
168 if (!gzippablePage) {
169 log.trace("Not a gzippable page");
170 return;
171 }
172 if (wrappedResponse.isCommitted())
173 {
174 log.debug("Response is committed, can't set gzip headers");
175 return;
176 }
177
178 log.debug("Setting gzip headers");
179 wrappedResponse.setHeader("Content-Encoding", "gzip");
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202 wrappedResponse.setHeader("Vary", "User-Agent");
203
204
205
206
207
208
209
210
211
212
213
214
215
216 headersCommitted = true;
217 }
218
219 private void activateGzip(String encoding)
220 {
221 if (gzippablePage)
222 {
223 return;
224 }
225
226 if (wrappedResponse.isCommitted()) {
227 log.debug("Response is committed, gzip can not be activated");
228 return;
229 }
230
231 if (headersCommitted) {
232 log.debug("Headers are committed, gzip can not be activated");
233 return;
234 }
235
236
237
238
239 if(wrappedResponse.containsHeader("Content-Length"))
240 {
241 log.debug("Gzip compression can not be activated when the Content-Length header has already been set "
242 + "on the response, and therefore uncompressed content will be sent instead");
243 return;
244 }
245
246 if (encoding != null) {
247 wrappedResponse.setEncoding(encoding);
248 }
249
250 routablePrintWriter.updateDestination(new RoutablePrintWriterDestinationFactory(wrappedResponse));
251 routableServletOutputStream.updateDestination(new RoutableServletOutputStreamDestinationFactory(wrappedResponse));
252 gzippablePage = true;
253 log.debug("gzip activated");
254 }
255
256 private void deactivateGzip()
257 {
258 gzippablePage = false;
259
260 routablePrintWriter.updateDestination(new RoutablePrintWriterDestinationFactory(getResponse()));
261 routableServletOutputStream.updateDestination(new RoutableServletOutputStreamDestinationFactory(getResponse()));
262 log.debug("gzip deactivated");
263 }
264
265
266
267
268 @Override
269 public void setContentLength(int contentLength)
270 {
271 if (!gzippablePage) { super.setContentLength(contentLength); }
272 }
273
274
275
276
277 @Override
278 public void flushBuffer() throws IOException
279 {
280 if (!gzippablePage)
281 {
282 log.debug("Flushing buffer");
283 super.flushBuffer();
284 }
285 }
286
287
288
289
290 @Override
291 public void setHeader(String name, String value)
292 {
293 if (name.toLowerCase().equals("content-type"))
294 {
295 setContentType(value);
296 }
297 else if (!gzippablePage || !name.toLowerCase().equals("content-length"))
298 {
299 super.setHeader(name, value);
300 }
301 }
302
303
304
305
306 @Override
307 public void addHeader(String name, String value)
308 {
309 if (name.toLowerCase().equals("content-type"))
310 {
311 setContentType(value);
312 }
313 else if (!gzippablePage || !name.toLowerCase().equals("content-length"))
314 {
315 super.addHeader(name, value);
316 }
317 }
318
319 @Override
320 public ServletOutputStream getOutputStream()
321 {
322 return routableServletOutputStream;
323 }
324
325 @Override
326 public PrintWriter getWriter()
327 {
328 return routablePrintWriter;
329 }
330
331 public void finishResponse()
332 {
333 if (gzippablePage)
334 {
335 commitGzipHeaders();
336 wrappedResponse.finishResponse();
337 }
338 }
339
340 private static class RoutablePrintWriterDestinationFactory implements RoutablePrintWriter.DestinationFactory
341 {
342 private final ServletResponse servletResponse;
343
344 public RoutablePrintWriterDestinationFactory(final ServletResponse servletResponse)
345 {
346 this.servletResponse = servletResponse;
347 }
348
349 public PrintWriter activateDestination() throws IOException
350 {
351 return servletResponse.getWriter();
352 }
353 }
354
355 private static class RoutableServletOutputStreamDestinationFactory implements RoutableServletOutputStream.DestinationFactory
356 {
357 private final ServletResponse servletResponse;
358
359 public RoutableServletOutputStreamDestinationFactory (final ServletResponse servletResponse)
360 {
361 this.servletResponse = servletResponse;
362 }
363
364 public ServletOutputStream create() throws IOException
365 {
366 return servletResponse.getOutputStream();
367 }
368 }
369 }