1 /*
2 * Copyright 2009 Red Hat, Inc.
3 *
4 * Red Hat licenses this file to you under the Apache License, version 2.0
5 * (the "License"); you may not use this file except in compliance with the
6 * 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.http2;
17
18 import org.jboss.netty.buffer.ChannelBuffer;
19 import org.jboss.netty.handler.codec.compression.ZlibEncoder;
20 import org.jboss.netty.handler.codec.compression.ZlibWrapper;
21 import org.jboss.netty.handler.codec.embedder.EncoderEmbedder;
22
23 /**
24 * Compresses an {@link HttpMessage} and an {@link HttpChunk} in {@code gzip} or
25 * {@code deflate} encoding while respecting the {@code "Accept-Encoding"} header.
26 * If there is no matching encoding, no compression is done. For more
27 * information on how this handler modifies the message, please refer to
28 * {@link HttpContentEncoder}.
29 *
30 * @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
31 * @author <a href="http://gleamynode.net/">Trustin Lee</a>
32 * @version $Rev: 1107 $, $Date: 2012-04-15 19:00:57 +0200 (dim., 15 avr. 2012) $
33 */
34 public class HttpContentCompressor extends HttpContentEncoder {
35
36 private final int compressionLevel;
37 private final int windowBits;
38 private final int memLevel;
39
40 /**
41 * Creates a new handler with the default compression level (<tt>6</tt>),
42 * default window size (<tt>15</tt>) and default memory level (<tt>8</tt>).
43 */
44 public HttpContentCompressor() {
45 this(6);
46 }
47
48 /**
49 * Creates a new handler with the specified compression level, default
50 * window size (<tt>15</tt>) and default memory level (<tt>8</tt>).
51 *
52 * @param compressionLevel
53 * {@code 1} yields the fastest compression and {@code 9} yields the
54 * best compression. {@code 0} means no compression. The default
55 * compression level is {@code 6}.
56 */
57 public HttpContentCompressor(int compressionLevel) {
58 this(compressionLevel, 15, 8);
59 }
60
61 /**
62 * Creates a new handler with the specified compression level, window size,
63 * and memory level..
64 *
65 * @param compressionLevel
66 * {@code 1} yields the fastest compression and {@code 9} yields the
67 * best compression. {@code 0} means no compression. The default
68 * compression level is {@code 6}.
69 * @param windowBits
70 * The base two logarithm of the size of the history buffer. The
71 * value should be in the range {@code 9} to {@code 15} inclusive.
72 * Larger values result in better compression at the expense of
73 * memory usage. The default value is {@code 15}.
74 * @param memLevel
75 * How much memory should be allocated for the internal compression
76 * state. {@code 1} uses minimum memory and {@code 9} uses maximum
77 * memory. Larger values result in better and faster compression
78 * at the expense of memory usage. The default value is {@code 8}
79 */
80 public HttpContentCompressor(int compressionLevel, int windowBits, int memLevel) {
81 if (compressionLevel < 0 || compressionLevel > 9) {
82 throw new IllegalArgumentException(
83 "compressionLevel: " + compressionLevel + " (expected: 0-9)");
84 }
85 if (windowBits < 9 || windowBits > 15) {
86 throw new IllegalArgumentException(
87 "windowBits: " + windowBits + " (expected: 9-15)");
88 }
89 if (memLevel < 1 || memLevel > 9) {
90 throw new IllegalArgumentException(
91 "memLevel: " + memLevel + " (expected: 1-9)");
92 }
93 this.compressionLevel = compressionLevel;
94 this.windowBits = windowBits;
95 this.memLevel = memLevel;
96 }
97
98 @Override
99 protected EncoderEmbedder<ChannelBuffer> newContentEncoder(String acceptEncoding) throws Exception {
100 ZlibWrapper wrapper = determineWrapper(acceptEncoding);
101 if (wrapper == null) {
102 return null;
103 }
104
105 return new EncoderEmbedder<ChannelBuffer>(
106 new ZlibEncoder(wrapper, compressionLevel, windowBits, memLevel));
107 }
108
109 @Override
110 protected String getTargetContentEncoding(String acceptEncoding) throws Exception {
111 ZlibWrapper wrapper = determineWrapper(acceptEncoding);
112 if (wrapper == null) {
113 return null;
114 }
115
116 switch (wrapper) {
117 case GZIP:
118 return "gzip";
119 case ZLIB:
120 return "deflate";
121 default:
122 throw new Error();
123 }
124 }
125
126 private ZlibWrapper determineWrapper(String acceptEncoding) {
127 float starQ = -1.0f;
128 float gzipQ = -1.0f;
129 float deflateQ = -1.0f;
130 for (String encoding : acceptEncoding.split(",")) {
131 float q = 1.0f;
132 int equalsPos = encoding.indexOf('=');
133 if (equalsPos != -1) {
134 try {
135 q = Float.valueOf(encoding.substring(equalsPos + 1));
136 } catch (NumberFormatException e) {
137 // Ignore encoding
138 q = 0.0f;
139 }
140 }
141 if (encoding.indexOf("*") >= 0) {
142 starQ = q;
143 } else if (encoding.indexOf("gzip") >= 0 && q > gzipQ) {
144 gzipQ = q;
145 } else if (encoding.indexOf("deflate") >= 0 && q > deflateQ) {
146 deflateQ = q;
147 }
148 }
149 if (gzipQ > 0.0f || deflateQ > 0.0f) {
150 if (gzipQ >= deflateQ) {
151 return ZlibWrapper.GZIP;
152 } else {
153 return ZlibWrapper.ZLIB;
154 }
155 }
156 if (starQ > 0.0f) {
157 if (gzipQ == -1.0f) {
158 return ZlibWrapper.GZIP;
159 }
160 if (deflateQ == -1.0f) {
161 return ZlibWrapper.ZLIB;
162 }
163 }
164 return null;
165 }
166 }