82c40822950093f19c50b8989dd5b66b0995e686
[percentcodec] / cachecontrol / src / main / java / de / halbekunst / juplo / cachecontrol / AcceleratorFilter.java
1 package de.halbekunst.juplo.cachecontrol;
2
3 import de.halbekunst.juplo.cachecontrol.CacheControl.CacheMethodHandle;
4 import java.io.IOException;
5 import java.io.OutputStream;
6 import java.io.PrintWriter;
7 import java.util.Enumeration;
8 import java.util.Map;
9 import java.util.zip.GZIPOutputStream;
10 import javax.servlet.Filter;
11 import javax.servlet.FilterChain;
12 import javax.servlet.FilterConfig;
13 import javax.servlet.ServletException;
14 import javax.servlet.ServletOutputStream;
15 import javax.servlet.ServletRequest;
16 import javax.servlet.ServletResponse;
17 import javax.servlet.http.HttpServletRequest;
18 import javax.servlet.http.HttpServletResponse;
19 import javax.servlet.http.HttpServletResponseWrapper;
20 import org.slf4j.Logger;
21 import org.slf4j.LoggerFactory;
22 import org.springframework.beans.factory.annotation.Autowired;
23 import org.springframework.beans.factory.annotation.Configurable;
24
25
26
27 /**
28  *
29  * @author kai
30  */
31 @Configurable
32 public class AcceleratorFilter implements Filter {
33   private final static Logger log = LoggerFactory.getLogger(AcceleratorFilter.class);
34
35   public final static String REQUEST_URI_ATTRIBUTE = "javax.servlet.include.request_uri";
36
37
38   @Autowired CacheControl cacheControl;
39   @Autowired(required=true) Integer buffer;
40   @Autowired(required=true) String eTag;
41   @Autowired(required=true) Boolean weak;
42   @Autowired(required=true) Long lastModified;
43   @Autowired(required=true) Integer cacheSeconds;
44
45
46   @Override
47   public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
48     if (!(request instanceof HttpServletRequest)) {
49       log.error("AcceleratorFilter can only handle HTTP-requests");
50       chain.doFilter(request, response);
51       return;
52     }
53
54     /** Prüfen, ob es sich um eine Anfrage für einen JSP-Include handelt */
55     if (request.getAttribute(REQUEST_URI_ATTRIBUTE) != null) {
56       log.debug("Includes cannot be accelerated");
57       chain.doFilter(request, response);
58       return;
59     }
60
61     AccelerationWrapper wrapper = new AccelerationWrapper((HttpServletRequest)request, (HttpServletResponse)response);
62     cacheControl.init(wrapper);
63     chain.doFilter(request, wrapper);
64     wrapper.finish();
65   }
66
67   @Override
68   public void init(FilterConfig filterConfig) throws ServletException {
69   }
70
71   @Override
72   public void destroy() {
73   }
74
75
76   class AccelerationWrapper extends HttpServletResponseWrapper implements CacheMethodHandle {
77
78     private final HttpServletRequest request;
79     private final HttpServletResponse response;
80
81     private boolean zipped;
82     private GZIPServletOutputStream out;
83
84     private long now;
85     private int buffer;
86     private int status;
87     private String type;
88
89
90     AccelerationWrapper(HttpServletRequest request, HttpServletResponse response) throws IOException {
91       super(response);
92
93       this.request = request;
94       this.response = response;
95
96       now = System.currentTimeMillis();
97       buffer = AcceleratorFilter.this.buffer;
98       status = HttpServletResponse.SC_OK;
99
100       zipped = false;
101       Enumeration values = request.getHeaders(HeaderNames.HEADER_ACCEPT_ENCODING);
102       while (values.hasMoreElements()) {
103         String value = (String) values.nextElement();
104         if (value.indexOf("gzip") != -1) {
105           zipped = true;
106           response.addHeader(HeaderNames.HEADER_CONTENT_ENCODING, "gzip");
107           break;
108         }
109       }
110     }
111
112
113     public void finish() throws IOException {
114       if (zipped && out != null)
115         out.zout.finish();
116     }
117
118
119     @Override
120     public void setStatus(int sc) {
121       response.setStatus(sc);
122       status = sc;
123       try {
124         cacheControl.decorate(request, response, this);
125       }
126       catch (Exception e) {
127         log.error("Error while decorating response", e);
128       }
129     }
130
131     @Override
132     public void setStatus(int sc, String sm) {
133       response.setStatus(sc,sm);
134       status = sc;
135       try {
136         cacheControl.decorate(request, response, this);
137       }
138       catch (Exception e) {
139         log.error("Error while decorating response", e);
140       }
141     }
142
143     @Override
144     public ServletOutputStream getOutputStream() throws IOException {
145       if (out == null)
146         out = new GZIPServletOutputStream(response.getOutputStream(), zipped);
147       return out;
148     }
149
150     @Override
151     public PrintWriter getWriter() throws IOException {
152       return new PrintWriter(getOutputStream());
153     }
154
155     @Override
156     public void setContentType(String type) {
157       this.type = type;
158       response.setContentType(type);
159     }
160
161     @Override
162     public void setBufferSize(int size) {
163       this.buffer = size;
164       response.setBufferSize(size);
165     }
166
167     @Override
168     public int getBufferSize() {
169       return buffer;
170     }
171
172     @Override
173     public void flushBuffer() throws IOException {
174       cacheControl.decorate(request, response, this);
175       if (zipped && out != null) {
176         out.zout.finish();
177       }
178       response.flushBuffer();
179     }
180
181     @Override
182     public void resetBuffer() {
183       response.resetBuffer();
184     }
185
186     @Override
187     public boolean isCommitted() {
188       return response.isCommitted();
189     }
190
191     @Override
192     public void reset() {
193       response.reset();
194     }
195
196
197
198     @Override
199     public long getTimestamp() {
200       return now;
201     }
202
203     @Override
204     public int accepts(HttpServletRequest request) {
205       return status;
206     }
207
208     @Override
209     public int getCacheSeconds(HttpServletRequest request) {
210       return cacheSeconds;
211     }
212
213     @Override
214     public long getLastModified(HttpServletRequest request) {
215       return lastModified;
216     }
217
218     @Override
219     public String getETag(HttpServletRequest request) {
220       return eTag;
221     }
222
223     @Override
224     public boolean isETagWeak() {
225       return weak;
226     }
227
228     @Override
229     public void cacheControl(HttpServletRequest request, Map<String, String> cacheControlMap) {
230     }
231
232
233     class GZIPServletOutputStream extends ServletOutputStream {
234
235       private final OutputStream out;
236       private final GZIPOutputStream zout;
237       private boolean untouched = true;
238
239       public GZIPServletOutputStream(ServletOutputStream out, boolean zipped) throws IOException {
240         if (zipped) {
241           this.zout = new GZIPOutputStream(out, buffer);
242           this.out = this.zout;
243         }
244         else {
245           this.out = out;
246           this.zout = null;
247         }
248       }
249
250
251       @Override
252       public void write(int i) throws IOException {
253         if (untouched) {
254           untouched = false;
255           try {
256             AcceleratorFilter.this.cacheControl.decorate(AccelerationWrapper.this.request, response, buffer);
257           }
258           catch (Exception e) {
259             log.error("Error while guessing Cache-Header's", e);
260             response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
261           }
262         }
263         out.write(i);
264       }
265     }
266   }
267 }