1//
2//  ========================================================================
3//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
4//  ------------------------------------------------------------------------
5//  All rights reserved. This program and the accompanying materials
6//  are made available under the terms of the Eclipse Public License v1.0
7//  and Apache License v2.0 which accompanies this distribution.
8//
9//      The Eclipse Public License is available at
10//      http://www.eclipse.org/legal/epl-v10.html
11//
12//      The Apache License v2.0 is available at
13//      http://www.opensource.org/licenses/apache2.0.php
14//
15//  You may elect to redistribute this code under either of these licenses.
16//  ========================================================================
17//
18
19package org.eclipse.jetty.http.gzip;
20
21import java.io.IOException;
22import java.io.OutputStream;
23import java.io.OutputStreamWriter;
24import java.io.PrintWriter;
25import java.io.UnsupportedEncodingException;
26import java.util.Set;
27
28import javax.servlet.ServletOutputStream;
29import javax.servlet.http.HttpServletRequest;
30import javax.servlet.http.HttpServletResponse;
31import javax.servlet.http.HttpServletResponseWrapper;
32
33import org.eclipse.jetty.util.StringUtil;
34
35/*------------------------------------------------------------ */
36/**
37 */
38public abstract class CompressedResponseWrapper extends HttpServletResponseWrapper
39{
40
41    public static final int DEFAULT_BUFFER_SIZE = 8192;
42    public static final int DEFAULT_MIN_COMPRESS_SIZE = 256;
43
44    private Set<String> _mimeTypes;
45    private int _bufferSize=DEFAULT_BUFFER_SIZE;
46    private int _minCompressSize=DEFAULT_MIN_COMPRESS_SIZE;
47    protected HttpServletRequest _request;
48
49    private PrintWriter _writer;
50    private AbstractCompressedStream _compressedStream;
51    private String _etag;
52    private long _contentLength=-1;
53    private boolean _noCompression;
54
55    /* ------------------------------------------------------------ */
56    public CompressedResponseWrapper(HttpServletRequest request, HttpServletResponse response)
57    {
58        super(response);
59        _request = request;
60    }
61
62
63    /* ------------------------------------------------------------ */
64    public long getContentLength()
65    {
66        return _contentLength;
67    }
68
69    /* ------------------------------------------------------------ */
70    public int getBufferSize()
71    {
72        return _bufferSize;
73    }
74
75    /* ------------------------------------------------------------ */
76    public int getMinCompressSize()
77    {
78        return _minCompressSize;
79    }
80
81    /* ------------------------------------------------------------ */
82    public String getETag()
83    {
84        return _etag;
85    }
86
87    /* ------------------------------------------------------------ */
88    public HttpServletRequest getRequest()
89    {
90        return _request;
91    }
92
93    /* ------------------------------------------------------------ */
94    /**
95     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#setMimeTypes(java.util.Set)
96     */
97    public void setMimeTypes(Set<String> mimeTypes)
98    {
99        _mimeTypes = mimeTypes;
100    }
101
102    /* ------------------------------------------------------------ */
103    /**
104     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#setBufferSize(int)
105     */
106    @Override
107    public void setBufferSize(int bufferSize)
108    {
109        _bufferSize = bufferSize;
110        if (_compressedStream!=null)
111            _compressedStream.setBufferSize(bufferSize);
112    }
113
114    /* ------------------------------------------------------------ */
115    /**
116     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#setMinCompressSize(int)
117     */
118    public void setMinCompressSize(int minCompressSize)
119    {
120        _minCompressSize = minCompressSize;
121    }
122
123    /* ------------------------------------------------------------ */
124    /**
125     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#setContentType(java.lang.String)
126     */
127    @Override
128    public void setContentType(String ct)
129    {
130        super.setContentType(ct);
131
132        if (!_noCompression)
133        {
134            if (ct!=null)
135            {
136                int colon=ct.indexOf(";");
137                if (colon>0)
138                    ct=ct.substring(0,colon);
139            }
140
141            if ((_compressedStream==null || _compressedStream.getOutputStream()==null) &&
142                    (_mimeTypes==null && ct!=null && ct.contains("gzip") ||
143                    _mimeTypes!=null && (ct==null||!_mimeTypes.contains(StringUtil.asciiToLowerCase(ct)))))
144            {
145                noCompression();
146            }
147        }
148    }
149
150    /* ------------------------------------------------------------ */
151    /**
152     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#setStatus(int, java.lang.String)
153     */
154    @Override
155    public void setStatus(int sc, String sm)
156    {
157        super.setStatus(sc,sm);
158        if (sc<200 || sc==204 || sc==205 || sc>=300)
159            noCompression();
160    }
161
162    /* ------------------------------------------------------------ */
163    /**
164     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#setStatus(int)
165     */
166    @Override
167    public void setStatus(int sc)
168    {
169        super.setStatus(sc);
170        if (sc<200 || sc==204 || sc==205 || sc>=300)
171            noCompression();
172    }
173
174    /* ------------------------------------------------------------ */
175    /**
176     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#setContentLength(int)
177     */
178    @Override
179    public void setContentLength(int length)
180    {
181        if (_noCompression)
182            super.setContentLength(length);
183        else
184            setContentLength((long)length);
185    }
186
187    /* ------------------------------------------------------------ */
188    protected void setContentLength(long length)
189    {
190        _contentLength=length;
191        if (_compressedStream!=null)
192            _compressedStream.setContentLength();
193        else if (_noCompression && _contentLength>=0)
194        {
195            HttpServletResponse response = (HttpServletResponse)getResponse();
196            if(_contentLength<Integer.MAX_VALUE)
197            {
198                response.setContentLength((int)_contentLength);
199            }
200            else
201            {
202                response.setHeader("Content-Length", Long.toString(_contentLength));
203            }
204        }
205    }
206
207    /* ------------------------------------------------------------ */
208    /**
209     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#addHeader(java.lang.String, java.lang.String)
210     */
211    @Override
212    public void addHeader(String name, String value)
213    {
214        if ("content-length".equalsIgnoreCase(name))
215        {
216            _contentLength=Long.parseLong(value);
217            if (_compressedStream!=null)
218                _compressedStream.setContentLength();
219        }
220        else if ("content-type".equalsIgnoreCase(name))
221        {
222            setContentType(value);
223        }
224        else if ("content-encoding".equalsIgnoreCase(name))
225        {
226            super.addHeader(name,value);
227            if (!isCommitted())
228            {
229                noCompression();
230            }
231        }
232        else if ("etag".equalsIgnoreCase(name))
233            _etag=value;
234        else
235            super.addHeader(name,value);
236    }
237
238    /* ------------------------------------------------------------ */
239    /**
240     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#flushBuffer()
241     */
242    @Override
243    public void flushBuffer() throws IOException
244    {
245        if (_writer!=null)
246            _writer.flush();
247        if (_compressedStream!=null)
248            _compressedStream.flush();
249        else
250            getResponse().flushBuffer();
251    }
252
253    /* ------------------------------------------------------------ */
254    /**
255     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#reset()
256     */
257    @Override
258    public void reset()
259    {
260        super.reset();
261        if (_compressedStream!=null)
262            _compressedStream.resetBuffer();
263        _writer=null;
264        _compressedStream=null;
265        _noCompression=false;
266        _contentLength=-1;
267    }
268
269    /* ------------------------------------------------------------ */
270    /**
271     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#resetBuffer()
272     */
273    @Override
274    public void resetBuffer()
275    {
276        super.resetBuffer();
277        if (_compressedStream!=null)
278            _compressedStream.resetBuffer();
279        _writer=null;
280        _compressedStream=null;
281    }
282
283    /* ------------------------------------------------------------ */
284    /**
285     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#sendError(int, java.lang.String)
286     */
287    @Override
288    public void sendError(int sc, String msg) throws IOException
289    {
290        resetBuffer();
291        super.sendError(sc,msg);
292    }
293
294    /* ------------------------------------------------------------ */
295    /**
296     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#sendError(int)
297     */
298    @Override
299    public void sendError(int sc) throws IOException
300    {
301        resetBuffer();
302        super.sendError(sc);
303    }
304
305    /* ------------------------------------------------------------ */
306    /**
307     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#sendRedirect(java.lang.String)
308     */
309    @Override
310    public void sendRedirect(String location) throws IOException
311    {
312        resetBuffer();
313        super.sendRedirect(location);
314    }
315
316    /* ------------------------------------------------------------ */
317    /**
318     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#noCompression()
319     */
320    public void noCompression()
321    {
322        if (!_noCompression)
323            setDeferredHeaders();
324        _noCompression=true;
325        if (_compressedStream!=null)
326        {
327            try
328            {
329                _compressedStream.doNotCompress(false);
330            }
331            catch (IOException e)
332            {
333                throw new IllegalStateException(e);
334            }
335        }
336    }
337
338    /* ------------------------------------------------------------ */
339    /**
340     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#finish()
341     */
342    public void finish() throws IOException
343    {
344        if (_writer!=null && !_compressedStream.isClosed())
345            _writer.flush();
346        if (_compressedStream!=null)
347            _compressedStream.finish();
348        else
349            setDeferredHeaders();
350    }
351
352    /* ------------------------------------------------------------ */
353    private void setDeferredHeaders()
354    {
355        if (!isCommitted())
356        {
357            if (_contentLength>=0)
358            {
359                if (_contentLength < Integer.MAX_VALUE)
360                    super.setContentLength((int)_contentLength);
361                else
362                    super.setHeader("Content-Length",Long.toString(_contentLength));
363            }
364            if(_etag!=null)
365                super.setHeader("ETag",_etag);
366        }
367    }
368
369    /* ------------------------------------------------------------ */
370    /**
371     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#setHeader(java.lang.String, java.lang.String)
372     */
373    @Override
374    public void setHeader(String name, String value)
375    {
376        if (_noCompression)
377            super.setHeader(name,value);
378        else if ("content-length".equalsIgnoreCase(name))
379        {
380            setContentLength(Long.parseLong(value));
381        }
382        else if ("content-type".equalsIgnoreCase(name))
383        {
384            setContentType(value);
385        }
386        else if ("content-encoding".equalsIgnoreCase(name))
387        {
388            super.setHeader(name,value);
389            if (!isCommitted())
390            {
391                noCompression();
392            }
393        }
394        else if ("etag".equalsIgnoreCase(name))
395            _etag=value;
396        else
397            super.setHeader(name,value);
398    }
399
400    /* ------------------------------------------------------------ */
401    @Override
402    public boolean containsHeader(String name)
403    {
404        if (!_noCompression && "etag".equalsIgnoreCase(name) && _etag!=null)
405            return true;
406        return super.containsHeader(name);
407    }
408
409    /* ------------------------------------------------------------ */
410    /**
411     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#getOutputStream()
412     */
413    @Override
414    public ServletOutputStream getOutputStream() throws IOException
415    {
416        if (_compressedStream==null)
417        {
418            if (getResponse().isCommitted() || _noCompression)
419                return getResponse().getOutputStream();
420
421            _compressedStream=newCompressedStream(_request,(HttpServletResponse)getResponse());
422        }
423        else if (_writer!=null)
424            throw new IllegalStateException("getWriter() called");
425
426        return _compressedStream;
427    }
428
429    /* ------------------------------------------------------------ */
430    /**
431     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#getWriter()
432     */
433    @Override
434    public PrintWriter getWriter() throws IOException
435    {
436        if (_writer==null)
437        {
438            if (_compressedStream!=null)
439                throw new IllegalStateException("getOutputStream() called");
440
441            if (getResponse().isCommitted() || _noCompression)
442                return getResponse().getWriter();
443
444            _compressedStream=newCompressedStream(_request,(HttpServletResponse)getResponse());
445            _writer=newWriter(_compressedStream,getCharacterEncoding());
446        }
447        return _writer;
448    }
449
450    /* ------------------------------------------------------------ */
451    /**
452     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#setIntHeader(java.lang.String, int)
453     */
454    @Override
455    public void setIntHeader(String name, int value)
456    {
457        if ("content-length".equalsIgnoreCase(name))
458        {
459            _contentLength=value;
460            if (_compressedStream!=null)
461                _compressedStream.setContentLength();
462        }
463        else
464            super.setIntHeader(name,value);
465    }
466
467    /* ------------------------------------------------------------ */
468    /**
469     * Allows derived implementations to replace PrintWriter implementation.
470     *
471     * @param out the out
472     * @param encoding the encoding
473     * @return the prints the writer
474     * @throws UnsupportedEncodingException the unsupported encoding exception
475     */
476    protected PrintWriter newWriter(OutputStream out,String encoding) throws UnsupportedEncodingException
477    {
478        return encoding==null?new PrintWriter(out):new PrintWriter(new OutputStreamWriter(out,encoding));
479    }
480
481    /* ------------------------------------------------------------ */
482    /**
483     *@return the underlying CompressedStream implementation
484     */
485    protected abstract AbstractCompressedStream newCompressedStream(HttpServletRequest _request, HttpServletResponse response) throws IOException;
486
487}
488