View Javadoc
1   /*
2    * Copyright 2012 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package org.jboss.netty.handler.codec.http;
17  
18  import org.jboss.netty.buffer.ChannelBuffer;
19  import org.jboss.netty.handler.codec.compression.JdkZlibEncoder;
20  import org.jboss.netty.handler.codec.compression.ZlibEncoder;
21  import org.jboss.netty.handler.codec.compression.ZlibWrapper;
22  import org.jboss.netty.handler.codec.embedder.EncoderEmbedder;
23  import org.jboss.netty.logging.InternalLogger;
24  import org.jboss.netty.logging.InternalLoggerFactory;
25  import org.jboss.netty.util.internal.DetectionUtil;
26  import org.jboss.netty.util.internal.StringUtil;
27  import org.jboss.netty.util.internal.SystemPropertyUtil;
28  
29  /**
30   * Compresses an {@link HttpMessage} and an {@link HttpChunk} in {@code gzip} or
31   * {@code deflate} encoding while respecting the {@code "Accept-Encoding"} header.
32   * If there is no matching encoding, no compression is done.  For more
33   * information on how this handler modifies the message, please refer to
34   * {@link HttpContentEncoder}.
35   */
36  public class HttpContentCompressor extends HttpContentEncoder {
37  
38      private static final InternalLogger logger = InternalLoggerFactory.getInstance(HttpContentCompressor.class);
39  
40      private static final int DEFAULT_JDK_WINDOW_SIZE = 15;
41      private static final int DEFAULT_JDK_MEM_LEVEL = 8;
42  
43      private static final boolean noJdkZlibEncoder;
44  
45      static {
46          noJdkZlibEncoder = SystemPropertyUtil.getBoolean("io.netty.noJdkZlibEncoder", false);
47          if (logger.isDebugEnabled()) {
48              logger.debug("-Dio.netty.noJdkZlibEncoder: " + noJdkZlibEncoder);
49          }
50      }
51  
52      private final int compressionLevel;
53      private final int windowBits;
54      private final int memLevel;
55  
56      /**
57       * Creates a new handler with the default compression level (<tt>6</tt>), default window size
58       * ({@value #DEFAULT_JDK_WINDOW_SIZE}) and default memory level ({@value #DEFAULT_JDK_MEM_LEVEL}).
59       */
60      public HttpContentCompressor() {
61          this(6);
62      }
63  
64      /**
65       * Creates a new handler with the specified compression level, default window size
66       * ({@value #DEFAULT_JDK_WINDOW_SIZE}) and default memory level ({@value #DEFAULT_JDK_MEM_LEVEL}).
67       *
68       * @param compressionLevel
69       *        {@code 1} yields the fastest compression and {@code 9} yields the
70       *        best compression.  {@code 0} means no compression.  The default
71       *        compression level is {@code 6}.
72       */
73      public HttpContentCompressor(int compressionLevel) {
74          this(compressionLevel, DEFAULT_JDK_WINDOW_SIZE, DEFAULT_JDK_MEM_LEVEL);
75      }
76  
77      /**
78       * Creates a new handler with the specified compression level, window size, and memory level.
79       *
80       * @param compressionLevel
81       *        {@code 1} yields the fastest compression and {@code 9} yields the
82       *        best compression.  {@code 0} means no compression.  The default
83       *        compression level is {@code 6}.
84       * @param windowBits
85       *        The base two logarithm of the size of the history buffer.  The
86       *        value should be in the range {@code 9} to {@code 15} inclusive.
87       *        Larger values result in better compression at the expense of
88       *        memory usage.  The default value is {@value #DEFAULT_JDK_WINDOW_SIZE}.
89       * @param memLevel
90       *        How much memory should be allocated for the internal compression
91       *        state.  {@code 1} uses minimum memory and {@code 9} uses maximum
92       *        memory.  Larger values result in better and faster compression
93       *        at the expense of memory usage.  The default value is {@value #DEFAULT_JDK_MEM_LEVEL}.
94       */
95      public HttpContentCompressor(int compressionLevel, int windowBits, int memLevel) {
96          if (compressionLevel < 0 || compressionLevel > 9) {
97              throw new IllegalArgumentException(
98                      "compressionLevel: " + compressionLevel + " (expected: 0-9)");
99          }
100         if (windowBits < 9 || windowBits > 15) {
101             throw new IllegalArgumentException(
102                     "windowBits: " + windowBits + " (expected: 9-15)");
103         }
104         if (memLevel < 1 || memLevel > 9) {
105             throw new IllegalArgumentException(
106                     "memLevel: " + memLevel + " (expected: 1-9)");
107         }
108         this.compressionLevel = compressionLevel;
109         this.windowBits = windowBits;
110         this.memLevel = memLevel;
111     }
112 
113     @Override
114     protected EncoderEmbedder<ChannelBuffer> newContentEncoder(
115             HttpMessage msg, String acceptEncoding) throws Exception {
116         String contentEncoding = msg.headers().get(HttpHeaders.Names.CONTENT_ENCODING);
117         if (contentEncoding != null &&
118             !HttpHeaders.Values.IDENTITY.equalsIgnoreCase(contentEncoding)) {
119             // Encoded already.
120             return null;
121         }
122 
123         ZlibWrapper wrapper = determineWrapper(acceptEncoding);
124         if (wrapper == null) {
125             return null;
126         }
127 
128         if (DetectionUtil.javaVersion() < 7 || noJdkZlibEncoder ||
129             windowBits != DEFAULT_JDK_WINDOW_SIZE || memLevel != DEFAULT_JDK_MEM_LEVEL) {
130             return new EncoderEmbedder<ChannelBuffer>(
131                     new ZlibEncoder(wrapper, compressionLevel, windowBits, memLevel));
132         } else {
133             return new EncoderEmbedder<ChannelBuffer>(
134                     new JdkZlibEncoder(wrapper, compressionLevel));
135         }
136     }
137 
138     @Override
139     protected String getTargetContentEncoding(String acceptEncoding) throws Exception {
140         ZlibWrapper wrapper = determineWrapper(acceptEncoding);
141         if (wrapper == null) {
142             return null;
143         }
144 
145         switch (wrapper) {
146         case GZIP:
147             return "gzip";
148         case ZLIB:
149             return "deflate";
150         default:
151             throw new Error();
152         }
153     }
154 
155     private static ZlibWrapper determineWrapper(String acceptEncoding) {
156         float starQ = -1.0f;
157         float gzipQ = -1.0f;
158         float deflateQ = -1.0f;
159         for (String encoding: StringUtil.split(acceptEncoding, ',')) {
160             float q = 1.0f;
161             int equalsPos = encoding.indexOf('=');
162             if (equalsPos != -1) {
163                 try {
164                     q = Float.valueOf(encoding.substring(equalsPos + 1));
165                 } catch (NumberFormatException e) {
166                     // Ignore encoding
167                     q = 0.0f;
168                 }
169             }
170             if (encoding.indexOf('*') >= 0) {
171                 starQ = q;
172             } else if (encoding.contains("gzip") && q > gzipQ) {
173                 gzipQ = q;
174             } else if (encoding.contains("deflate") && q > deflateQ) {
175                 deflateQ = q;
176             }
177         }
178         if (gzipQ > 0.0f || deflateQ > 0.0f) {
179             if (gzipQ >= deflateQ) {
180                 return ZlibWrapper.GZIP;
181             } else {
182                 return ZlibWrapper.ZLIB;
183             }
184         }
185         if (starQ > 0.0f) {
186             if (gzipQ == -1.0f) {
187                 return ZlibWrapper.GZIP;
188             }
189             if (deflateQ == -1.0f) {
190                 return ZlibWrapper.ZLIB;
191             }
192         }
193         return null;
194     }
195 }