1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.jetty.websocket.common.extensions.compress;
20
21 import java.nio.ByteBuffer;
22 import java.util.zip.DataFormatException;
23 import java.util.zip.Deflater;
24 import java.util.zip.Inflater;
25
26 import org.eclipse.jetty.util.BufferUtil;
27 import org.eclipse.jetty.util.TypeUtil;
28 import org.eclipse.jetty.util.log.Log;
29 import org.eclipse.jetty.util.log.Logger;
30 import org.eclipse.jetty.websocket.api.BadPayloadException;
31
32
33
34
35 public class DeflateCompressionMethod implements CompressionMethod
36 {
37 private static class DeflaterProcess implements CompressionMethod.Process
38 {
39 private static final boolean BFINAL_HACK = Boolean.parseBoolean(System.getProperty("jetty.websocket.bfinal.hack","true"));
40
41 private final Deflater deflater;
42 private int bufferSize = DEFAULT_BUFFER_SIZE;
43
44 public DeflaterProcess(boolean nowrap)
45 {
46 deflater = new Deflater(Deflater.BEST_COMPRESSION,nowrap);
47 deflater.setStrategy(Deflater.DEFAULT_STRATEGY);
48 }
49
50 @Override
51 public void begin()
52 {
53 deflater.reset();
54 }
55
56 @Override
57 public void end()
58 {
59 deflater.reset();
60 }
61
62 @Override
63 public void input(ByteBuffer input)
64 {
65 if (LOG.isDebugEnabled())
66 {
67 LOG.debug("input: {}",BufferUtil.toDetailString(input));
68 }
69
70
71 byte raw[] = BufferUtil.toArray(input);
72 deflater.setInput(raw,0,raw.length);
73 deflater.finish();
74 }
75
76 @Override
77 public boolean isDone()
78 {
79 return deflater.finished();
80 }
81
82 @Override
83 public ByteBuffer process()
84 {
85
86 ByteBuffer buf = ByteBuffer.allocate(bufferSize);
87 BufferUtil.clearToFill(buf);
88
89 while (!deflater.finished())
90 {
91 byte out[] = new byte[bufferSize];
92 int len = deflater.deflate(out,0,out.length,Deflater.SYNC_FLUSH);
93
94 if (LOG.isDebugEnabled())
95 {
96 LOG.debug("Deflater: finished={}, needsInput={}, len={}",deflater.finished(),deflater.needsInput(),len);
97 }
98
99 buf.put(out,0,len);
100 }
101 BufferUtil.flipToFlush(buf,0);
102
103 if (BFINAL_HACK)
104 {
105
106
107
108
109
110
111
112 byte b0 = buf.get(0);
113 if ((b0 & 1) != 0)
114 {
115 buf.put(0,(b0 ^= 1));
116 }
117 }
118 return buf;
119 }
120
121 public void setBufferSize(int bufferSize)
122 {
123 this.bufferSize = bufferSize;
124 }
125 }
126
127 private static class InflaterProcess implements CompressionMethod.Process
128 {
129
130 private static final byte[] TAIL = new byte[]
131 { 0x00, 0x00, (byte)0xFF, (byte)0xFF };
132 private final Inflater inflater;
133 private int bufferSize = DEFAULT_BUFFER_SIZE;
134
135 public InflaterProcess(boolean nowrap) {
136 inflater = new Inflater(nowrap);
137 }
138
139 @Override
140 public void begin()
141 {
142 inflater.reset();
143 }
144
145 @Override
146 public void end()
147 {
148 inflater.reset();
149 }
150
151 @Override
152 public void input(ByteBuffer input)
153 {
154 if (LOG.isDebugEnabled())
155 {
156 LOG.debug("inflate: {}",BufferUtil.toDetailString(input));
157 LOG.debug("Input Data: {}",TypeUtil.toHexString(BufferUtil.toArray(input)));
158 }
159
160
161 int len = input.remaining() + 4;
162 byte raw[] = new byte[len];
163 int inlen = input.remaining();
164 input.slice().get(raw,0,inlen);
165 System.arraycopy(TAIL,0,raw,inlen,TAIL.length);
166 inflater.setInput(raw,0,raw.length);
167 }
168
169 @Override
170 public boolean isDone()
171 {
172 return (inflater.getRemaining() <= 0) || inflater.finished();
173 }
174
175 @Override
176 public ByteBuffer process()
177 {
178
179 byte buf[] = new byte[bufferSize];
180 try
181 {
182 int inflated = inflater.inflate(buf);
183 if (inflated == 0)
184 {
185 return null;
186 }
187
188 ByteBuffer ret = BufferUtil.toBuffer(buf,0,inflated);
189
190 if (LOG.isDebugEnabled())
191 {
192 LOG.debug("uncompressed={}",BufferUtil.toDetailString(ret));
193 }
194
195 return ret;
196 }
197 catch (DataFormatException e)
198 {
199 LOG.warn(e);
200 throw new BadPayloadException(e);
201 }
202 }
203
204 public void setBufferSize(int bufferSize)
205 {
206 this.bufferSize = bufferSize;
207 }
208 }
209
210 private static final int DEFAULT_BUFFER_SIZE = 61*1024;
211
212 private static final Logger LOG = Log.getLogger(DeflateCompressionMethod.class);
213
214 private int bufferSize = 64 * 1024;
215 private final DeflaterProcess compress;
216 private final InflaterProcess decompress;
217
218 public DeflateCompressionMethod()
219 {
220
221
222
223
224
225
226
227 boolean nowrap = true;
228
229 this.compress = new DeflaterProcess(nowrap);
230 this.decompress = new InflaterProcess(nowrap);
231 }
232
233 @Override
234 public Process compress()
235 {
236 return compress;
237 }
238
239 @Override
240 public Process decompress()
241 {
242 return decompress;
243 }
244
245 public int getBufferSize()
246 {
247 return bufferSize;
248 }
249
250 public void setBufferSize(int size)
251 {
252 if (size < 64)
253 {
254 throw new IllegalArgumentException("Buffer Size [" + size + "] cannot be less than 64 bytes");
255 }
256 this.bufferSize = size;
257 this.compress.setBufferSize(bufferSize);
258 this.decompress.setBufferSize(bufferSize);
259 }
260 }