View Javadoc

    1   //========================================================================
    2   //Copyright 2007 Mort Bay Consulting Pty. Ltd.
    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   //http://www.apache.org/licenses/LICENSE-2.0
    8   //Unless required by applicable law or agreed to in writing, software
    9   //distributed under the License is distributed on an "AS IS" BASIS,
    10  //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  //See the License for the specific language governing permissions and
    12  //limitations under the License.
    13  //========================================================================
    14  package org.mortbay.servlet;
    15
    16  import java.io.IOException;
    17  import java.io.OutputStream;
    18  import java.io.OutputStreamWriter;
    19  import java.io.PrintWriter;
    20  import java.util.HashSet;
    21  import java.util.Set;
    22  import java.util.StringTokenizer;
    23  import java.util.zip.GZIPOutputStream;
    24
    25  import javax.servlet.FilterChain;
    26  import javax.servlet.FilterConfig;
    27  import javax.servlet.ServletException;
    28  import javax.servlet.ServletOutputStream;
    29  import javax.servlet.ServletRequest;
    30  import javax.servlet.ServletResponse;
    31  import javax.servlet.http.HttpServletRequest;
    32  import javax.servlet.http.HttpServletResponse;
    33  import javax.servlet.http.HttpServletResponseWrapper;
    34
    35  import org.mortbay.util.ByteArrayOutputStream2;
    36  import org.mortbay.util.StringUtil;
    37
    38  /* ------------------------------------------------------------ */
    39  /** GZIP Filter
    40   * This filter will gzip the content of a response iff: <ul>
    41   * <li>The filter is mapped to a matching path</li>
    42   * <li>The response status code is >=200 and <300
    43   * <li>The content length is unknown or more than the <code>minGzipSize</code> initParameter or the minGzipSize is 0(default)</li>
    44   * <li>The content-type is in the coma separated list of mimeTypes set in the <code>mimeTypes</code> initParameter or
    45   * if no mimeTypes are defined the content-type is not "application/gzip"</li>
    46   * <li>No content-encoding is specified by the resource</li>
    47   * </ul>
    48   *
    49   * <p>
    50   * Compressing the content can greatly improve the network bandwidth usage, but at a cost of memory and
    51   * CPU cycles.   If this filter is mapped for static content, then use of efficient direct NIO may be
    52   * prevented, thus use of the gzip mechanism of the {@link org.mortbay.jetty.servlet.DefaultServlet} is
    53   * advised instead.
    54   * </p>
    55   * <p>
    56   * This filter extends {@link UserAgentFilter} and if the the initParameter <code>excludedAgents</code>
    57   * is set to a comma separated list of user agents, then these agents will be excluded from gzip content.
    58   * </p>
    59   *
    60   * @author gregw
    61   *
    62   */
    63  public class GzipFilter extends UserAgentFilter
    64  {
    65      protected Set _mimeTypes;
    66      protected int _bufferSize=8192;
    67      protected int _minGzipSize=0;
    68      protected Set _excluded;
    69
    70      public void init(FilterConfig filterConfig) throws ServletException
    71      {
    72          super.init(filterConfig);
    73
    74          String tmp=filterConfig.getInitParameter("bufferSize");
    75          if (tmp!=null)
    76              _bufferSize=Integer.parseInt(tmp);
    77
    78          tmp=filterConfig.getInitParameter("minGzipSize");
    79          if (tmp!=null)
    80              _minGzipSize=Integer.parseInt(tmp);
    81
    82          tmp=filterConfig.getInitParameter("mimeTypes");
    83          if (tmp!=null)
    84          {
    85              _mimeTypes=new HashSet();
    86              StringTokenizer tok = new StringTokenizer(tmp,",",false);
    87              while (tok.hasMoreTokens())
    88                  _mimeTypes.add(tok.nextToken());
    89          }
    90
    91          tmp=filterConfig.getInitParameter("excludedAgents");
    92          if (tmp!=null)
    93          {
    94              _excluded=new HashSet();
    95              StringTokenizer tok = new StringTokenizer(tmp,",",false);
    96              while (tok.hasMoreTokens())
    97                  _excluded.add(tok.nextToken());
    98          }
    99      }
    100
    101     public void destroy()
    102     {
    103     }
    104
    105     public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
    106         throws IOException, ServletException
    107     {
    108         HttpServletRequest request=(HttpServletRequest)req;
    109         HttpServletResponse response=(HttpServletResponse)res;
    110
    111         String ae = request.getHeader("accept-encoding");
    112         if (ae != null && ae.indexOf("gzip")>=0 && !response.containsHeader("Content-Encoding"))
    113         {
    114             if (_excluded!=null)
    115             {
    116                 String ua=getUserAgent(request);
    117                 if (_excluded.contains(ua))
    118                 {
    119                     super.doFilter(request,response,chain);
    120                     return;
    121                 }
    122             }
    123
    124             GZIPResponseWrapper wrappedResponse=newGZIPResponseWrapper(request,response);
    125
    126             boolean exceptional=true;
    127             try
    128             {
    129                 super.doFilter(request,wrappedResponse,chain);
    130                 exceptional=false;
    131             }
    132             finally
    133             {
    134                 if (exceptional && !response.isCommitted())
    135                 {
    136                     wrappedResponse.resetBuffer();
    137                     wrappedResponse.noGzip();
    138                 }
    139                 else
    140                     wrappedResponse.finish();
    141             }
    142         }
    143         else
    144         {
    145             super.doFilter(request,response,chain);
    146         }
    147     }
    148
    149     protected GZIPResponseWrapper newGZIPResponseWrapper(HttpServletRequest request, HttpServletResponse response)
    150     {
    151         return new GZIPResponseWrapper(request,response);
    152     }
    153
    154
    155     public class GZIPResponseWrapper extends HttpServletResponseWrapper
    156     {
    157         HttpServletRequest _request;
    158         boolean _noGzip;
    159         PrintWriter _writer;
    160         GzipStream _gzStream;
    161         long _contentLength=-1;
    162
    163         public GZIPResponseWrapper(HttpServletRequest request, HttpServletResponse response)
    164         {
    165             super(response);
    166             _request=request;
    167         }
    168
    169         public void setContentType(String ct)
    170         {
    171             super.setContentType(ct);
    172             int colon=ct.indexOf(";");
    173             if (colon>0)
    174                 ct=ct.substring(0,colon);
    175
    176             if ((_gzStream==null || _gzStream._out==null) &&
    177                 (_mimeTypes==null && "application/gzip".equalsIgnoreCase(ct) ||
    178                  _mimeTypes!=null && !_mimeTypes.contains(StringUtil.asciiToLowerCase(ct))))
    179             {
    180                 noGzip();
    181             }
    182         }
    183
    184
    185         public void setStatus(int sc, String sm)
    186         {
    187             super.setStatus(sc,sm);
    188             if (sc<200||sc>=300)
    189                 noGzip();
    190         }
    191
    192         public void setStatus(int sc)
    193         {
    194             super.setStatus(sc);
    195             if (sc<200||sc>=300)
    196                 noGzip();
    197         }
    198
    199         public void setContentLength(int length)
    200         {
    201             _contentLength=length;
    202             if (_gzStream!=null)
    203                 _gzStream.setContentLength(length);
    204         }
    205
    206         public void setHeader(String name, String value)
    207         {
    208             if ("content-length".equalsIgnoreCase(name))
    209             {
    210                 _contentLength=Long.parseLong(value);
    211                 if (_gzStream!=null)
    212                     _gzStream.setContentLength(_contentLength);
    213             }
    214             else if ("content-type".equalsIgnoreCase(name))
    215             {
    216                 setContentType(value);
    217             }
    218             else if ("content-encoding".equalsIgnoreCase(name))
    219             {
    220                 super.setHeader(name,value);
    221                 if (!isCommitted())
    222                 {
    223                     noGzip();
    224                 }
    225             }
    226             else
    227                 super.setHeader(name,value);
    228         }
    229
    230         public void setIntHeader(String name, int value)
    231         {
    232             if ("content-length".equalsIgnoreCase(name))
    233             {
    234                 _contentLength=value;
    235                 if (_gzStream!=null)
    236                     _gzStream.setContentLength(_contentLength);
    237             }
    238             else
    239                 super.setIntHeader(name,value);
    240         }
    241
    242         public void flushBuffer() throws IOException
    243         {
    244             if (_writer!=null)
    245                 _writer.flush();
    246             if (_gzStream!=null)
    247                 _gzStream.finish();
    248             else
    249                 getResponse().flushBuffer();
    250         }
    251
    252         public void reset()
    253         {
    254             super.reset();
    255             if (_gzStream!=null)
    256                 _gzStream.resetBuffer();
    257             _writer=null;
    258             _gzStream=null;
    259             _noGzip=false;
    260             _contentLength=-1;
    261         }
    262
    263         public void resetBuffer()
    264         {
    265             super.resetBuffer();
    266             if (_gzStream!=null)
    267                 _gzStream.resetBuffer();
    268             _writer=null;
    269             _gzStream=null;
    270         }
    271
    272         public void sendError(int sc, String msg) throws IOException
    273         {
    274             resetBuffer();
    275             super.sendError(sc,msg);
    276         }
    277
    278         public void sendError(int sc) throws IOException
    279         {
    280             resetBuffer();
    281             super.sendError(sc);
    282         }
    283
    284         public void sendRedirect(String location) throws IOException
    285         {
    286             resetBuffer();
    287             super.sendRedirect(location);
    288         }
    289
    290         public ServletOutputStream getOutputStream() throws IOException
    291         {
    292             if (_gzStream==null)
    293             {
    294                 if (getResponse().isCommitted() || _noGzip)
    295                     return getResponse().getOutputStream();
    296
    297                 _gzStream=newGzipStream(_request,(HttpServletResponse)getResponse(),_contentLength,_bufferSize,_minGzipSize);
    298             }
    299             else if (_writer!=null)
    300                 throw new IllegalStateException("getWriter() called");
    301
    302             return _gzStream;
    303         }
    304
    305         public PrintWriter getWriter() throws IOException
    306         {
    307             if (_writer==null)
    308             {
    309                 if (_gzStream!=null)
    310                     throw new IllegalStateException("getOutputStream() called");
    311
    312                 if (getResponse().isCommitted() || _noGzip)
    313                     return getResponse().getWriter();
    314
    315                 _gzStream=newGzipStream(_request,(HttpServletResponse)getResponse(),_contentLength,_bufferSize,_minGzipSize);
    316                 String encoding = getCharacterEncoding();
    317                 _writer=encoding==null?new PrintWriter(_gzStream):new PrintWriter(new OutputStreamWriter(_gzStream,encoding));
    318             }
    319             return _writer;
    320         }
    321
    322         void noGzip()
    323         {
    324             _noGzip=true;
    325             if (_gzStream!=null)
    326             {
    327                 try
    328                 {
    329                     _gzStream.doNotGzip();
    330                 }
    331                 catch (IOException e)
    332                 {
    333                     throw new IllegalStateException();
    334                 }
    335             }
    336         }
    337
    338         void finish() throws IOException
    339         {
    340             if (_writer!=null)
    341                 _writer.flush();
    342             if (_gzStream!=null)
    343                 _gzStream.finish();
    344         }
    345
    346         protected GzipStream newGzipStream(HttpServletRequest request,HttpServletResponse response,long contentLength,int bufferSize, int minGzipSize) throws IOException
    347         {
    348             return new GzipStream(request,response,contentLength,bufferSize,minGzipSize);
    349         }
    350     }
    351
    352
    353     public static class GzipStream extends ServletOutputStream
    354     {
    355         protected HttpServletRequest _request;
    356         protected HttpServletResponse _response;
    357         protected OutputStream _out;
    358         protected ByteArrayOutputStream2 _bOut;
    359         protected GZIPOutputStream _gzOut;
    360         protected boolean _closed;
    361         protected int _bufferSize;
    362         protected int _minGzipSize;
    363         protected long _contentLength;
    364
    365         public GzipStream(HttpServletRequest request,HttpServletResponse response,long contentLength,int bufferSize, int minGzipSize) throws IOException
    366         {
    367             _request=request;
    368             _response=response;
    369             _contentLength=contentLength;
    370             _bufferSize=bufferSize;
    371             _minGzipSize=minGzipSize;
    372             if (minGzipSize==0)
    373                 doGzip();
    374         }
    375
    376         public void resetBuffer()
    377         {
    378             _closed=false;
    379             _out=null;
    380             _bOut=null;
    381             if (_gzOut!=null && !_response.isCommitted())
    382                 _response.setHeader("Content-Encoding",null);
    383             _gzOut=null;
    384         }
    385
    386         public void setContentLength(long length)
    387         {
    388             _contentLength=length;
    389         }
    390
    391         public void flush() throws IOException
    392         {
    393             if (_out==null || _bOut!=null)
    394             {
    395                 if (_contentLength>0 && _contentLength<_minGzipSize)
    396                     doNotGzip();
    397                 else
    398                     doGzip();
    399             }
    400
    401             _out.flush();
    402         }
    403
    404         public void close() throws IOException
    405         {
    406             if (_request.getAttribute("javax.servlet.include.request_uri")!=null)
    407                 flush();
    408             else
    409             {
    410                 if (_bOut!=null)
    411                 {
    412                     if (_contentLength<0)
    413                         _contentLength=_bOut.getCount();
    414                     if (_contentLength<_minGzipSize)
    415                         doNotGzip();
    416                     else
    417                         doGzip();
    418                 }
    419                 else if (_out==null)
    420                 {
    421                     doNotGzip();
    422                 }
    423
    424                 if (_gzOut!=null)
    425                     _gzOut.finish();
    426                 _out.close();
    427                 _closed=true;
    428             }
    429         }
    430
    431         public void finish() throws IOException
    432         {
    433             if (!_closed)
    434             {
    435                 if (_out==null || _bOut!=null)
    436                 {
    437                     if (_contentLength>0 && _contentLength<_minGzipSize)
    438                         doNotGzip();
    439                     else
    440                         doGzip();
    441                 }
    442
    443                 if (_gzOut!=null)
    444                     _gzOut.finish();
    445             }
    446         }
    447
    448         public void write(int b) throws IOException
    449         {
    450             checkOut(1);
    451             _out.write(b);
    452         }
    453
    454         public void write(byte b[]) throws IOException
    455         {
    456             checkOut(b.length);
    457             _out.write(b);
    458         }
    459
    460         public void write(byte b[], int off, int len) throws IOException
    461         {
    462             checkOut(len);
    463             _out.write(b,off,len);
    464         }
    465
    466         protected boolean setContentEncodingGzip()
    467         {
    468             _response.setHeader("Content-Encoding", "gzip");
    469             return _response.containsHeader("Content-Encoding");
    470         }
    471
    472         public void doGzip() throws IOException
    473         {
    474             if (_gzOut==null)
    475             {
    476                 if (_response.isCommitted())
    477                     throw new IllegalStateException();
    478
    479                 if (setContentEncodingGzip())
    480                 {
    481                     _out=_gzOut=new GZIPOutputStream(_response.getOutputStream(),_bufferSize);
    482
    483                     if (_bOut!=null)
    484                     {
    485                         _out.write(_bOut.getBuf(),0,_bOut.getCount());
    486                         _bOut=null;
    487                     }
    488                 }
    489                 else
    490                     doNotGzip();
    491             }
    492         }
    493
    494         public void doNotGzip() throws IOException
    495         {
    496             if (_gzOut!=null)
    497                 throw new IllegalStateException();
    498             if (_out==null || _bOut!=null )
    499             {
    500                 _out=_response.getOutputStream();
    501                 if (_contentLength>=0)
    502                 {
    503                     if(_contentLength<Integer.MAX_VALUE)
    504                         _response.setContentLength((int)_contentLength);
    505                     else
    506                         _response.setHeader("Content-Length",Long.toString(_contentLength));
    507                 }
    508
    509                 if (_bOut!=null)
    510                     _out.write(_bOut.getBuf(),0,_bOut.getCount());
    511                 _bOut=null;
    512             }
    513         }
    514
    515         private void checkOut(int length) throws IOException
    516         {
    517             if (_closed)
    518             {
    519                 new Throwable().printStackTrace();
    520                 throw new IOException("CLOSED");
    521             }
    522
    523             if (_out==null)
    524             {
    525                 if (_response.isCommitted() || (_contentLength>=0 && _contentLength<_minGzipSize))
    526                     doNotGzip();
    527                 else if (length>_minGzipSize)
    528                     doGzip();
    529                 else
    530                     _out=_bOut=new ByteArrayOutputStream2(_bufferSize);
    531             }
    532             else if (_bOut!=null)
    533             {
    534                 if (_response.isCommitted() || (_contentLength>=0 && _contentLength<_minGzipSize))
    535                     doNotGzip();
    536                 else if (length>=(_bOut.size()-_bOut.getCount()))
    537                     doGzip();
    538             }
    539         }
    540     }
    541 }