1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.jetty.websocket;
20
21 import java.io.IOException;
22 import java.util.ArrayList;
23 import java.util.Enumeration;
24 import java.util.HashMap;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Queue;
28 import java.util.concurrent.ConcurrentLinkedQueue;
29 import javax.servlet.http.HttpServletRequest;
30 import javax.servlet.http.HttpServletResponse;
31
32 import org.eclipse.jetty.http.HttpException;
33 import org.eclipse.jetty.http.HttpParser;
34 import org.eclipse.jetty.io.ConnectedEndPoint;
35 import org.eclipse.jetty.server.AbstractHttpConnection;
36 import org.eclipse.jetty.server.BlockingHttpConnection;
37 import org.eclipse.jetty.util.QuotedStringTokenizer;
38 import org.eclipse.jetty.util.component.AbstractLifeCycle;
39 import org.eclipse.jetty.util.log.Log;
40 import org.eclipse.jetty.util.log.Logger;
41
42
43
44
45 public class WebSocketFactory extends AbstractLifeCycle
46 {
47 private static final Logger LOG = Log.getLogger(WebSocketFactory.class);
48 private final Queue<WebSocketServletConnection> connections = new ConcurrentLinkedQueue<WebSocketServletConnection>();
49
50 public interface Acceptor
51 {
52
53
54
55
56
57
58
59
60 WebSocket doWebSocketConnect(HttpServletRequest request, String protocol);
61
62
63
64
65
66
67
68
69 boolean checkOrigin(HttpServletRequest request, String origin);
70 }
71
72 private final Map<String,Class<? extends Extension>> _extensionClasses = new HashMap<String, Class<? extends Extension>>();
73 {
74 _extensionClasses.put("identity",IdentityExtension.class);
75 _extensionClasses.put("fragment",FragmentExtension.class);
76 _extensionClasses.put("x-deflate-frame",DeflateFrameExtension.class);
77 }
78
79 private final Acceptor _acceptor;
80 private WebSocketBuffers _buffers;
81 private int _maxIdleTime = 300000;
82 private int _maxTextMessageSize = 16 * 1024;
83 private int _maxBinaryMessageSize = -1;
84 private int _minVersion;
85
86 public WebSocketFactory(Acceptor acceptor)
87 {
88 this(acceptor, 64 * 1024, WebSocketConnectionRFC6455.VERSION);
89 }
90
91 public WebSocketFactory(Acceptor acceptor, int bufferSize)
92 {
93 this(acceptor, bufferSize, WebSocketConnectionRFC6455.VERSION);
94 }
95
96 public WebSocketFactory(Acceptor acceptor, int bufferSize, int minVersion)
97 {
98 _buffers = new WebSocketBuffers(bufferSize);
99 _acceptor = acceptor;
100 _minVersion=WebSocketConnectionRFC6455.VERSION;
101 }
102
103 public int getMinVersion()
104 {
105 return _minVersion;
106 }
107
108
109
110
111
112 public void setMinVersion(int minVersion)
113 {
114 _minVersion = minVersion;
115 }
116
117
118
119
120 public Map<String,Class<? extends Extension>> getExtensionClassesMap()
121 {
122 return _extensionClasses;
123 }
124
125
126
127
128
129
130 public long getMaxIdleTime()
131 {
132 return _maxIdleTime;
133 }
134
135
136
137
138
139
140 public void setMaxIdleTime(int maxIdleTime)
141 {
142 _maxIdleTime = maxIdleTime;
143 }
144
145
146
147
148
149
150 public int getBufferSize()
151 {
152 return _buffers.getBufferSize();
153 }
154
155
156
157
158
159
160 public void setBufferSize(int bufferSize)
161 {
162 if (bufferSize != getBufferSize())
163 _buffers = new WebSocketBuffers(bufferSize);
164 }
165
166
167
168
169 public int getMaxTextMessageSize()
170 {
171 return _maxTextMessageSize;
172 }
173
174
175
176
177
178
179 public void setMaxTextMessageSize(int maxTextMessageSize)
180 {
181 _maxTextMessageSize = maxTextMessageSize;
182 }
183
184
185
186
187 public int getMaxBinaryMessageSize()
188 {
189 return _maxBinaryMessageSize;
190 }
191
192
193
194
195
196
197 public void setMaxBinaryMessageSize(int maxBinaryMessageSize)
198 {
199 _maxBinaryMessageSize = maxBinaryMessageSize;
200 }
201
202 @Override
203 protected void doStop() throws Exception
204 {
205 closeConnections();
206 }
207
208
209
210
211
212
213
214
215
216
217
218
219
220 public void upgrade(HttpServletRequest request, HttpServletResponse response, WebSocket websocket, String protocol)
221 throws IOException
222 {
223 if (!"websocket".equalsIgnoreCase(request.getHeader("Upgrade")))
224 throw new IllegalStateException("!Upgrade:websocket");
225 if (!"HTTP/1.1".equals(request.getProtocol()))
226 throw new IllegalStateException("!HTTP/1.1");
227
228 int draft = request.getIntHeader("Sec-WebSocket-Version");
229 if (draft < 0) {
230
231 draft = request.getIntHeader("Sec-WebSocket-Draft");
232 }
233
234 int requestedVersion = draft;
235 AbstractHttpConnection http = AbstractHttpConnection.getCurrentConnection();
236 if (http instanceof BlockingHttpConnection)
237 throw new IllegalStateException("Websockets not supported on blocking connectors");
238 ConnectedEndPoint endp = (ConnectedEndPoint)http.getEndPoint();
239
240 List<String> extensions_requested = new ArrayList<String>();
241 @SuppressWarnings("unchecked")
242 Enumeration<String> e = request.getHeaders("Sec-WebSocket-Extensions");
243 while (e.hasMoreElements())
244 {
245 QuotedStringTokenizer tok = new QuotedStringTokenizer(e.nextElement(),",");
246 while (tok.hasMoreTokens())
247 {
248 extensions_requested.add(tok.nextToken());
249 }
250 }
251
252 final WebSocketServletConnection connection;
253 if (draft<_minVersion)
254 draft=Integer.MAX_VALUE;
255 switch (draft)
256 {
257 case -1:
258 case 0:
259 {
260 connection = new WebSocketServletConnectionD00(this, websocket, endp, _buffers, http.getTimeStamp(), _maxIdleTime, protocol);
261 break;
262 }
263 case 1:
264 case 2:
265 case 3:
266 case 4:
267 case 5:
268 case 6:
269 {
270 connection = new WebSocketServletConnectionD06(this, websocket, endp, _buffers, http.getTimeStamp(), _maxIdleTime, protocol);
271 break;
272 }
273 case 7:
274 case 8:
275 {
276 List<Extension> extensions = initExtensions(extensions_requested, 8 - WebSocketConnectionD08.OP_EXT_DATA, 16 - WebSocketConnectionD08.OP_EXT_CTRL, 3);
277 connection = new WebSocketServletConnectionD08(this, websocket, endp, _buffers, http.getTimeStamp(), _maxIdleTime, protocol, extensions, draft);
278 break;
279 }
280 case WebSocketConnectionRFC6455.VERSION:
281 {
282 List<Extension> extensions = initExtensions(extensions_requested, 8 - WebSocketConnectionRFC6455.OP_EXT_DATA, 16 - WebSocketConnectionRFC6455.OP_EXT_CTRL, 3);
283 connection = new WebSocketServletConnectionRFC6455(this, websocket, endp, _buffers, http.getTimeStamp(), _maxIdleTime, protocol, extensions, draft);
284 break;
285 }
286 default:
287 {
288
289
290 String versions="13";
291 if (_minVersion<=8)
292 versions+=", 8";
293 if (_minVersion<=6)
294 versions+=", 6";
295 if (_minVersion<=0)
296 versions+=", 0";
297
298 response.setHeader("Sec-WebSocket-Version", versions);
299
300
301 StringBuilder err = new StringBuilder();
302 err.append("Unsupported websocket client version specification ");
303 if(requestedVersion >= 0) {
304 err.append("[").append(requestedVersion).append("]");
305 } else {
306 err.append("<Unspecified, likely a pre-draft version of websocket>");
307 }
308 err.append(", configured minVersion [").append(_minVersion).append("]");
309 err.append(", reported supported versions [").append(versions).append("]");
310 LOG.warn(err.toString());
311
312 throw new HttpException(400, "Unsupported websocket version specification");
313 }
314 }
315
316 addConnection(connection);
317
318
319 connection.getConnection().setMaxBinaryMessageSize(_maxBinaryMessageSize);
320 connection.getConnection().setMaxTextMessageSize(_maxTextMessageSize);
321
322
323 connection.handshake(request, response, protocol);
324 response.flushBuffer();
325
326
327 connection.fillBuffersFrom(((HttpParser)http.getParser()).getHeaderBuffer());
328 connection.fillBuffersFrom(((HttpParser)http.getParser()).getBodyBuffer());
329
330
331 LOG.debug("Websocket upgrade {} {} {} {}",request.getRequestURI(),draft,protocol,connection);
332 request.setAttribute("org.eclipse.jetty.io.Connection", connection);
333 }
334
335 protected String[] parseProtocols(String protocol)
336 {
337 if (protocol == null)
338 return new String[]{null};
339 protocol = protocol.trim();
340 if (protocol == null || protocol.length() == 0)
341 return new String[]{null};
342 String[] passed = protocol.split("\\s*,\\s*");
343 String[] protocols = new String[passed.length + 1];
344 System.arraycopy(passed, 0, protocols, 0, passed.length);
345 return protocols;
346 }
347
348 public boolean acceptWebSocket(HttpServletRequest request, HttpServletResponse response)
349 throws IOException
350 {
351 if ("websocket".equalsIgnoreCase(request.getHeader("Upgrade")))
352 {
353 String origin = request.getHeader("Origin");
354 if (origin==null)
355 origin = request.getHeader("Sec-WebSocket-Origin");
356 if (!_acceptor.checkOrigin(request,origin))
357 {
358 response.sendError(HttpServletResponse.SC_FORBIDDEN);
359 return false;
360 }
361
362
363 WebSocket websocket = null;
364
365 @SuppressWarnings("unchecked")
366 Enumeration<String> protocols = request.getHeaders("Sec-WebSocket-Protocol");
367 String protocol=null;
368 while (protocol==null && protocols!=null && protocols.hasMoreElements())
369 {
370 String candidate = protocols.nextElement();
371 for (String p : parseProtocols(candidate))
372 {
373 websocket = _acceptor.doWebSocketConnect(request, p);
374 if (websocket != null)
375 {
376 protocol = p;
377 break;
378 }
379 }
380 }
381
382
383 if (websocket == null)
384 {
385
386 websocket = _acceptor.doWebSocketConnect(request, null);
387
388 if (websocket==null)
389 {
390 response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
391 return false;
392 }
393 }
394
395
396 upgrade(request, response, websocket, protocol);
397 return true;
398 }
399
400 return false;
401 }
402
403 public List<Extension> initExtensions(List<String> requested,int maxDataOpcodes,int maxControlOpcodes,int maxReservedBits)
404 {
405 List<Extension> extensions = new ArrayList<Extension>();
406 for (String rExt : requested)
407 {
408 QuotedStringTokenizer tok = new QuotedStringTokenizer(rExt,";");
409 String extName=tok.nextToken().trim();
410 Map<String,String> parameters = new HashMap<String,String>();
411 while (tok.hasMoreTokens())
412 {
413 QuotedStringTokenizer nv = new QuotedStringTokenizer(tok.nextToken().trim(),"=");
414 String name=nv.nextToken().trim();
415 String value=nv.hasMoreTokens()?nv.nextToken().trim():null;
416 parameters.put(name,value);
417 }
418
419 Extension extension = newExtension(extName);
420
421 if (extension==null)
422 continue;
423
424 if (extension.init(parameters))
425 {
426 LOG.debug("add {} {}",extName,parameters);
427 extensions.add(extension);
428 }
429 }
430 LOG.debug("extensions={}",extensions);
431 return extensions;
432 }
433
434 private Extension newExtension(String name)
435 {
436 try
437 {
438 Class<? extends Extension> extClass = _extensionClasses.get(name);
439 if (extClass!=null)
440 return extClass.newInstance();
441 }
442 catch (Exception e)
443 {
444 LOG.warn(e);
445 }
446
447 return null;
448 }
449
450 protected boolean addConnection(WebSocketServletConnection connection)
451 {
452 return isRunning() && connections.add(connection);
453 }
454
455 protected boolean removeConnection(WebSocketServletConnection connection)
456 {
457 return connections.remove(connection);
458 }
459
460 protected void closeConnections()
461 {
462 for (WebSocketServletConnection connection : connections)
463 connection.shutdown();
464 }
465 }