1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty.handler.codec.http.websocketx.extensions.compression;
17
18 import io.netty.handler.codec.compression.ZlibCodecFactory;
19 import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionData;
20 import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionDecoder;
21 import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionEncoder;
22 import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionFilterProvider;
23 import io.netty.handler.codec.http.websocketx.extensions.WebSocketServerExtension;
24 import io.netty.handler.codec.http.websocketx.extensions.WebSocketServerExtensionHandshaker;
25
26 import java.util.HashMap;
27 import java.util.Iterator;
28 import java.util.Map.Entry;
29
30 import static io.netty.util.internal.ObjectUtil.*;
31
32
33
34
35
36 public final class PerMessageDeflateServerExtensionHandshaker implements WebSocketServerExtensionHandshaker {
37
38 public static final int MIN_WINDOW_SIZE = 8;
39 public static final int MAX_WINDOW_SIZE = 15;
40
41 static final String PERMESSAGE_DEFLATE_EXTENSION = "permessage-deflate";
42 static final String CLIENT_MAX_WINDOW = "client_max_window_bits";
43 static final String SERVER_MAX_WINDOW = "server_max_window_bits";
44 static final String CLIENT_NO_CONTEXT = "client_no_context_takeover";
45 static final String SERVER_NO_CONTEXT = "server_no_context_takeover";
46
47 private final int compressionLevel;
48 private final boolean allowServerWindowSize;
49 private final int preferredClientWindowSize;
50 private final boolean allowServerNoContext;
51 private final boolean preferredClientNoContext;
52 private final WebSocketExtensionFilterProvider extensionFilterProvider;
53 private final int maxAllocation;
54
55
56
57
58
59
60
61
62 @Deprecated
63 public PerMessageDeflateServerExtensionHandshaker() {
64 this(0);
65 }
66
67
68
69
70
71
72
73 public PerMessageDeflateServerExtensionHandshaker(int maxAllocation) {
74 this(6, ZlibCodecFactory.isSupportingWindowSizeAndMemLevel(), MAX_WINDOW_SIZE, false, false, maxAllocation);
75 }
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97 @Deprecated
98 public PerMessageDeflateServerExtensionHandshaker(int compressionLevel, boolean allowServerWindowSize,
99 int preferredClientWindowSize,
100 boolean allowServerNoContext, boolean preferredClientNoContext) {
101 this(compressionLevel, allowServerWindowSize, preferredClientWindowSize, allowServerNoContext,
102 preferredClientNoContext, 0);
103 }
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124 public PerMessageDeflateServerExtensionHandshaker(int compressionLevel, boolean allowServerWindowSize,
125 int preferredClientWindowSize,
126 boolean allowServerNoContext, boolean preferredClientNoContext, int maxAllocation) {
127 this(compressionLevel, allowServerWindowSize, preferredClientWindowSize, allowServerNoContext,
128 preferredClientNoContext, WebSocketExtensionFilterProvider.DEFAULT, maxAllocation);
129 }
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153 @Deprecated
154 public PerMessageDeflateServerExtensionHandshaker(int compressionLevel, boolean allowServerWindowSize,
155 int preferredClientWindowSize,
156 boolean allowServerNoContext, boolean preferredClientNoContext,
157 WebSocketExtensionFilterProvider extensionFilterProvider) {
158 this(compressionLevel, allowServerWindowSize, preferredClientWindowSize, allowServerNoContext,
159 preferredClientNoContext, extensionFilterProvider, 0);
160 }
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183 public PerMessageDeflateServerExtensionHandshaker(int compressionLevel, boolean allowServerWindowSize,
184 int preferredClientWindowSize,
185 boolean allowServerNoContext, boolean preferredClientNoContext,
186 WebSocketExtensionFilterProvider extensionFilterProvider,
187 int maxAllocation) {
188 if (preferredClientWindowSize > MAX_WINDOW_SIZE || preferredClientWindowSize < MIN_WINDOW_SIZE) {
189 throw new IllegalArgumentException(
190 "preferredServerWindowSize: " + preferredClientWindowSize + " (expected: 8-15)");
191 }
192 if (compressionLevel < 0 || compressionLevel > 9) {
193 throw new IllegalArgumentException(
194 "compressionLevel: " + compressionLevel + " (expected: 0-9)");
195 }
196 this.compressionLevel = compressionLevel;
197 this.allowServerWindowSize = allowServerWindowSize;
198 this.preferredClientWindowSize = preferredClientWindowSize;
199 this.allowServerNoContext = allowServerNoContext;
200 this.preferredClientNoContext = preferredClientNoContext;
201 this.extensionFilterProvider = checkNotNull(extensionFilterProvider, "extensionFilterProvider");
202 this.maxAllocation = checkPositiveOrZero(maxAllocation, "maxAllocation");
203 }
204
205 @Override
206 public WebSocketServerExtension handshakeExtension(WebSocketExtensionData extensionData) {
207 if (!PERMESSAGE_DEFLATE_EXTENSION.equals(extensionData.name())) {
208 return null;
209 }
210
211 boolean deflateEnabled = true;
212 int clientWindowSize = MAX_WINDOW_SIZE;
213 int serverWindowSize = MAX_WINDOW_SIZE;
214 boolean serverNoContext = false;
215 boolean clientNoContext = false;
216
217 Iterator<Entry<String, String>> parametersIterator =
218 extensionData.parameters().entrySet().iterator();
219 while (deflateEnabled && parametersIterator.hasNext()) {
220 Entry<String, String> parameter = parametersIterator.next();
221
222 if (CLIENT_MAX_WINDOW.equalsIgnoreCase(parameter.getKey())) {
223
224 String value = parameter.getValue();
225 if (value != null) {
226
227 clientWindowSize = Integer.parseInt(value);
228 if (clientWindowSize > MAX_WINDOW_SIZE || clientWindowSize < MIN_WINDOW_SIZE) {
229 deflateEnabled = false;
230 }
231 } else {
232
233 clientWindowSize = preferredClientWindowSize;
234 }
235 } else if (SERVER_MAX_WINDOW.equalsIgnoreCase(parameter.getKey())) {
236
237 if (allowServerWindowSize) {
238 serverWindowSize = Integer.parseInt(parameter.getValue());
239 if (serverWindowSize > MAX_WINDOW_SIZE || serverWindowSize < MIN_WINDOW_SIZE) {
240 deflateEnabled = false;
241 }
242 } else {
243 deflateEnabled = false;
244 }
245 } else if (CLIENT_NO_CONTEXT.equalsIgnoreCase(parameter.getKey())) {
246
247 clientNoContext = preferredClientNoContext;
248 } else if (SERVER_NO_CONTEXT.equalsIgnoreCase(parameter.getKey())) {
249
250 if (allowServerNoContext) {
251 serverNoContext = true;
252 } else {
253 deflateEnabled = false;
254 }
255 } else {
256
257 deflateEnabled = false;
258 }
259 }
260
261 if (deflateEnabled) {
262 return new PermessageDeflateExtension(compressionLevel, serverNoContext,
263 serverWindowSize, clientNoContext, clientWindowSize, extensionFilterProvider, maxAllocation);
264 } else {
265 return null;
266 }
267 }
268
269 private static class PermessageDeflateExtension implements WebSocketServerExtension {
270
271 private final int compressionLevel;
272 private final boolean serverNoContext;
273 private final int serverWindowSize;
274 private final boolean clientNoContext;
275 private final int clientWindowSize;
276 private final WebSocketExtensionFilterProvider extensionFilterProvider;
277 private final int maxAllocation;
278
279 PermessageDeflateExtension(int compressionLevel, boolean serverNoContext,
280 int serverWindowSize, boolean clientNoContext, int clientWindowSize,
281 WebSocketExtensionFilterProvider extensionFilterProvider, int maxAllocation) {
282 this.compressionLevel = compressionLevel;
283 this.serverNoContext = serverNoContext;
284 this.serverWindowSize = serverWindowSize;
285 this.clientNoContext = clientNoContext;
286 this.clientWindowSize = clientWindowSize;
287 this.extensionFilterProvider = extensionFilterProvider;
288 this.maxAllocation = maxAllocation;
289 }
290
291 @Override
292 public int rsv() {
293 return RSV1;
294 }
295
296 @Override
297 public WebSocketExtensionEncoder newExtensionEncoder() {
298 return new PerMessageDeflateEncoder(compressionLevel, serverWindowSize, serverNoContext,
299 extensionFilterProvider.encoderFilter());
300 }
301
302 @Override
303 public WebSocketExtensionDecoder newExtensionDecoder() {
304 return new PerMessageDeflateDecoder(clientNoContext, extensionFilterProvider.decoderFilter(),
305 maxAllocation);
306 }
307
308 @Override
309 public WebSocketExtensionData newReponseData() {
310 HashMap<String, String> parameters = new HashMap<String, String>(4);
311 if (serverNoContext) {
312 parameters.put(SERVER_NO_CONTEXT, null);
313 }
314 if (clientNoContext) {
315 parameters.put(CLIENT_NO_CONTEXT, null);
316 }
317 if (serverWindowSize != MAX_WINDOW_SIZE) {
318 parameters.put(SERVER_MAX_WINDOW, Integer.toString(serverWindowSize));
319 }
320 if (clientWindowSize != MAX_WINDOW_SIZE) {
321 parameters.put(CLIENT_MAX_WINDOW, Integer.toString(clientWindowSize));
322 }
323 return new WebSocketExtensionData(PERMESSAGE_DEFLATE_EXTENSION, parameters);
324 }
325 }
326
327 }