1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.jetty.websocket.server;
20
21 import java.io.IOException;
22 import java.net.URI;
23 import java.net.URISyntaxException;
24 import java.util.ArrayList;
25 import java.util.Collections;
26 import java.util.HashMap;
27 import java.util.Iterator;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Set;
31 import java.util.concurrent.CopyOnWriteArraySet;
32 import java.util.concurrent.Executor;
33
34 import javax.servlet.http.HttpServletRequest;
35 import javax.servlet.http.HttpServletResponse;
36
37 import org.eclipse.jetty.http.HttpStatus;
38 import org.eclipse.jetty.io.ByteBufferPool;
39 import org.eclipse.jetty.io.EndPoint;
40 import org.eclipse.jetty.io.MappedByteBufferPool;
41 import org.eclipse.jetty.server.HttpConnection;
42 import org.eclipse.jetty.util.StringUtil;
43 import org.eclipse.jetty.util.component.ContainerLifeCycle;
44 import org.eclipse.jetty.util.log.Log;
45 import org.eclipse.jetty.util.log.Logger;
46 import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
47 import org.eclipse.jetty.util.thread.Scheduler;
48 import org.eclipse.jetty.websocket.api.InvalidWebSocketException;
49 import org.eclipse.jetty.websocket.api.StatusCode;
50 import org.eclipse.jetty.websocket.api.WebSocketException;
51 import org.eclipse.jetty.websocket.api.WebSocketPolicy;
52 import org.eclipse.jetty.websocket.api.extensions.ExtensionFactory;
53 import org.eclipse.jetty.websocket.api.util.QuoteUtil;
54 import org.eclipse.jetty.websocket.common.LogicalConnection;
55 import org.eclipse.jetty.websocket.common.SessionFactory;
56 import org.eclipse.jetty.websocket.common.SessionListener;
57 import org.eclipse.jetty.websocket.common.WebSocketSession;
58 import org.eclipse.jetty.websocket.common.WebSocketSessionFactory;
59 import org.eclipse.jetty.websocket.common.events.EventDriver;
60 import org.eclipse.jetty.websocket.common.events.EventDriverFactory;
61 import org.eclipse.jetty.websocket.common.extensions.ExtensionStack;
62 import org.eclipse.jetty.websocket.common.extensions.WebSocketExtensionFactory;
63 import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
64 import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
65 import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
66 import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
67
68
69
70
71 public class WebSocketServerFactory extends ContainerLifeCycle implements WebSocketCreator, WebSocketServletFactory, SessionListener
72 {
73 private static final Logger LOG = Log.getLogger(WebSocketServerFactory.class);
74
75 private final ClassLoader contextClassloader;
76 private final Map<Integer, WebSocketHandshake> handshakes = new HashMap<>();
77
78
79
80 private final Scheduler scheduler = new ScheduledExecutorScheduler();
81 private final String supportedVersions;
82 private final WebSocketPolicy defaultPolicy;
83 private final EventDriverFactory eventDriverFactory;
84 private final ByteBufferPool bufferPool;
85 private final WebSocketExtensionFactory extensionFactory;
86 private List<SessionFactory> sessionFactories;
87 private Set<WebSocketSession> openSessions = new CopyOnWriteArraySet<>();
88 private WebSocketCreator creator;
89 private List<Class<?>> registeredSocketClasses;
90
91 public WebSocketServerFactory()
92 {
93 this(WebSocketPolicy.newServerPolicy(), new MappedByteBufferPool());
94 }
95
96 public WebSocketServerFactory(WebSocketPolicy policy)
97 {
98 this(policy, new MappedByteBufferPool());
99 }
100
101 public WebSocketServerFactory(ByteBufferPool bufferPool)
102 {
103 this(WebSocketPolicy.newServerPolicy(), bufferPool);
104 }
105
106 public WebSocketServerFactory(WebSocketPolicy policy, ByteBufferPool bufferPool)
107 {
108 handshakes.put(HandshakeRFC6455.VERSION, new HandshakeRFC6455());
109
110 addBean(scheduler);
111 addBean(bufferPool);
112
113 this.contextClassloader = Thread.currentThread().getContextClassLoader();
114
115 this.registeredSocketClasses = new ArrayList<>();
116
117 this.defaultPolicy = policy;
118 this.eventDriverFactory = new EventDriverFactory(defaultPolicy);
119 this.bufferPool = bufferPool;
120 this.extensionFactory = new WebSocketExtensionFactory(defaultPolicy, this.bufferPool);
121
122
123 this.extensionFactory.unregister("deflate-frame");
124 this.extensionFactory.unregister("permessage-deflate");
125 this.extensionFactory.unregister("x-webkit-deflate-frame");
126
127 this.sessionFactories = new ArrayList<>();
128 this.sessionFactories.add(new WebSocketSessionFactory(this));
129 this.creator = this;
130
131
132 List<Integer> versions = new ArrayList<>();
133 for (int v : handshakes.keySet())
134 {
135 versions.add(v);
136 }
137 Collections.sort(versions, Collections.reverseOrder());
138 StringBuilder rv = new StringBuilder();
139 for (int v : versions)
140 {
141 if (rv.length() > 0)
142 {
143 rv.append(", ");
144 }
145 rv.append(v);
146 }
147 supportedVersions = rv.toString();
148 }
149
150 @Override
151 public boolean acceptWebSocket(HttpServletRequest request, HttpServletResponse response) throws IOException
152 {
153 return acceptWebSocket(getCreator(), request, response);
154 }
155
156 @Override
157 public boolean acceptWebSocket(WebSocketCreator creator, HttpServletRequest request, HttpServletResponse response) throws IOException
158 {
159 ClassLoader old = Thread.currentThread().getContextClassLoader();
160 try
161 {
162 Thread.currentThread().setContextClassLoader(contextClassloader);
163 ServletUpgradeRequest sockreq = new ServletUpgradeRequest(request);
164 ServletUpgradeResponse sockresp = new ServletUpgradeResponse(response);
165
166 Object websocketPojo = creator.createWebSocket(sockreq, sockresp);
167
168
169 if (sockresp.isCommitted())
170 {
171 return false;
172 }
173
174 if (websocketPojo == null)
175 {
176
177 sockresp.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, "Endpoint Creation Failed");
178 return false;
179 }
180
181
182 HttpConnection connection = (HttpConnection)request.getAttribute("org.eclipse.jetty.server.HttpConnection");
183
184
185 EventDriver driver = eventDriverFactory.wrap(websocketPojo);
186 return upgrade(connection, sockreq, sockresp, driver);
187 }
188 catch (URISyntaxException e)
189 {
190 throw new IOException("Unable to accept websocket due to mangled URI", e);
191 }
192 finally
193 {
194 Thread.currentThread().setContextClassLoader(old);
195 }
196 }
197
198 public void addSessionFactory(SessionFactory sessionFactory)
199 {
200 if (sessionFactories.contains(sessionFactory))
201 {
202 return;
203 }
204 this.sessionFactories.add(sessionFactory);
205 }
206
207 @Override
208 public void cleanup()
209 {
210 try
211 {
212 this.stop();
213 }
214 catch (Exception e)
215 {
216 LOG.warn(e);
217 }
218 }
219
220 protected void shutdownAllConnections()
221 {
222 for (WebSocketSession session : openSessions)
223 {
224 if (session.getConnection() != null)
225 {
226 try
227 {
228 session.getConnection().close(
229 StatusCode.SHUTDOWN,
230 "Shutdown");
231 }
232 catch (Throwable t)
233 {
234 LOG.debug("During Shutdown All Connections",t);
235 }
236 }
237 }
238 openSessions.clear();
239 }
240
241 @Override
242 public WebSocketServletFactory createFactory(WebSocketPolicy policy)
243 {
244 return new WebSocketServerFactory(policy, bufferPool);
245 }
246
247 private WebSocketSession createSession(URI requestURI, EventDriver websocket, LogicalConnection connection)
248 {
249 if (websocket == null)
250 {
251 throw new InvalidWebSocketException("Unable to create Session from null websocket");
252 }
253
254 for (SessionFactory impl : sessionFactories)
255 {
256 if (impl.supports(websocket))
257 {
258 try
259 {
260 return impl.createSession(requestURI, websocket, connection);
261 }
262 catch (Throwable e)
263 {
264 throw new InvalidWebSocketException("Unable to create Session", e);
265 }
266 }
267 }
268
269 throw new InvalidWebSocketException("Unable to create Session: unrecognized internal EventDriver type: " + websocket.getClass().getName());
270 }
271
272
273
274
275 @Override
276 public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp)
277 {
278 if (registeredSocketClasses.size() < 1)
279 {
280 throw new WebSocketException("No WebSockets have been registered with the factory. Cannot use default implementation of WebSocketCreator.");
281 }
282
283 if (registeredSocketClasses.size() > 1)
284 {
285 LOG.warn("You have registered more than 1 websocket object, and are using the default WebSocketCreator! Using first registered websocket.");
286 }
287
288 Class<?> firstClass = registeredSocketClasses.get(0);
289 try
290 {
291 return firstClass.newInstance();
292 }
293 catch (InstantiationException | IllegalAccessException e)
294 {
295 throw new WebSocketException("Unable to create instance of " + firstClass, e);
296 }
297 }
298
299 @Override
300 protected void doStop() throws Exception
301 {
302 shutdownAllConnections();
303 super.doStop();
304 }
305
306 @Override
307 public WebSocketCreator getCreator()
308 {
309 return this.creator;
310 }
311
312 public EventDriverFactory getEventDriverFactory()
313 {
314 return eventDriverFactory;
315 }
316
317 @Override
318 public ExtensionFactory getExtensionFactory()
319 {
320 return extensionFactory;
321 }
322
323 public Set<WebSocketSession> getOpenSessions()
324 {
325 return Collections.unmodifiableSet(this.openSessions);
326 }
327
328 @Override
329 public WebSocketPolicy getPolicy()
330 {
331 return defaultPolicy;
332 }
333
334 @Override
335 public void init() throws Exception
336 {
337 start();
338 }
339
340 @Override
341 public boolean isUpgradeRequest(HttpServletRequest request, HttpServletResponse response)
342 {
343 if (!"GET".equalsIgnoreCase(request.getMethod()))
344 {
345
346 return false;
347 }
348
349 String connection = request.getHeader("connection");
350 if (connection == null)
351 {
352
353 return false;
354 }
355
356
357 boolean foundUpgradeToken = false;
358 Iterator<String> iter = QuoteUtil.splitAt(connection, ",");
359 while (iter.hasNext())
360 {
361 String token = iter.next();
362 if ("upgrade".equalsIgnoreCase(token))
363 {
364 foundUpgradeToken = true;
365 break;
366 }
367 }
368
369 if (!foundUpgradeToken)
370 {
371 return false;
372 }
373
374 String upgrade = request.getHeader("Upgrade");
375 if (upgrade == null)
376 {
377
378 return false;
379 }
380
381 if (!"websocket".equalsIgnoreCase(upgrade))
382 {
383 LOG.debug("Not a 'Upgrade: WebSocket' (was [Upgrade: " + upgrade + "])");
384 return false;
385 }
386
387 if (!"HTTP/1.1".equals(request.getProtocol()))
388 {
389 LOG.debug("Not a 'HTTP/1.1' request (was [" + request.getProtocol() + "])");
390 return false;
391 }
392
393 return true;
394 }
395
396 @Override
397 public void onSessionClosed(WebSocketSession session)
398 {
399 this.openSessions.remove(session);
400 }
401
402 @Override
403 public void onSessionOpened(WebSocketSession session)
404 {
405 this.openSessions.add(session);
406 }
407
408 protected String[] parseProtocols(String protocol)
409 {
410 if (protocol == null)
411 {
412 return new String[]{null};
413 }
414 protocol = protocol.trim();
415 if (protocol.length() == 0)
416 {
417 return new String[]{null};
418 }
419 String[] passed = protocol.split("\\s*,\\s*");
420 String[] protocols = new String[passed.length + 1];
421 System.arraycopy(passed, 0, protocols, 0, passed.length);
422 return protocols;
423 }
424
425 @Override
426 public void register(Class<?> websocketPojo)
427 {
428 registeredSocketClasses.add(websocketPojo);
429 }
430
431 @Override
432 public void setCreator(WebSocketCreator creator)
433 {
434 this.creator = creator;
435 }
436
437
438
439
440
441
442
443
444
445
446
447
448
449 private boolean upgrade(HttpConnection http, ServletUpgradeRequest request, ServletUpgradeResponse response, EventDriver driver) throws IOException
450 {
451 if (!"websocket".equalsIgnoreCase(request.getHeader("Upgrade")))
452 {
453 throw new IllegalStateException("Not a 'WebSocket: Upgrade' request");
454 }
455 if (!"HTTP/1.1".equals(request.getHttpVersion()))
456 {
457 throw new IllegalStateException("Not a 'HTTP/1.1' request");
458 }
459
460 int version = request.getHeaderInt("Sec-WebSocket-Version");
461 if (version < 0)
462 {
463
464 version = request.getHeaderInt("Sec-WebSocket-Draft");
465 }
466
467 WebSocketHandshake handshaker = handshakes.get(version);
468 if (handshaker == null)
469 {
470 StringBuilder warn = new StringBuilder();
471 warn.append("Client ").append(request.getRemoteAddress());
472 warn.append(" (:").append(request.getRemotePort());
473 warn.append(") User Agent: ");
474 String ua = request.getHeader("User-Agent");
475 if (ua == null)
476 {
477 warn.append("[unset] ");
478 }
479 else
480 {
481 warn.append('"').append(StringUtil.sanitizeXmlString(ua)).append("\" ");
482 }
483 warn.append("requested WebSocket version [").append(version);
484 warn.append("], Jetty supports version");
485 if (handshakes.size() > 1)
486 {
487 warn.append('s');
488 }
489 warn.append(": [").append(supportedVersions).append("]");
490 LOG.warn(warn.toString());
491
492
493
494 response.setHeader("Sec-WebSocket-Version", supportedVersions);
495 response.sendError(HttpStatus.BAD_REQUEST_400, "Unsupported websocket version specification");
496 return false;
497 }
498
499
500 ExtensionStack extensionStack = new ExtensionStack(getExtensionFactory());
501
502
503 if (response.isExtensionsNegotiated())
504 {
505
506 extensionStack.negotiate(response.getExtensions());
507 }
508 else
509 {
510
511 extensionStack.negotiate(request.getExtensions());
512 }
513
514
515 EndPoint endp = http.getEndPoint();
516 Executor executor = http.getConnector().getExecutor();
517 ByteBufferPool bufferPool = http.getConnector().getByteBufferPool();
518
519
520 WebSocketServerConnection wsConnection = new WebSocketServerConnection(endp, executor, scheduler, driver.getPolicy(), bufferPool);
521
522 extensionStack.setPolicy(driver.getPolicy());
523 extensionStack.configure(wsConnection.getParser());
524 extensionStack.configure(wsConnection.getGenerator());
525
526 if (LOG.isDebugEnabled())
527 {
528 LOG.debug("HttpConnection: {}", http);
529 LOG.debug("WebSocketConnection: {}", wsConnection);
530 }
531
532
533 WebSocketSession session = createSession(request.getRequestURI(), driver, wsConnection);
534 session.setPolicy(driver.getPolicy());
535 session.setUpgradeRequest(request);
536
537 response.setExtensions(extensionStack.getNegotiatedExtensions());
538 session.setUpgradeResponse(response);
539 wsConnection.setSession(session);
540
541
542 wsConnection.setNextIncomingFrames(extensionStack);
543 extensionStack.setNextIncoming(session);
544
545
546 session.setOutgoingHandler(extensionStack);
547 extensionStack.setNextOutgoing(wsConnection);
548
549
550 try
551 {
552 session.start();
553 }
554 catch (Exception e)
555 {
556 throw new IOException("Unable to start Session", e);
557 }
558 try
559 {
560 extensionStack.start();
561 }
562 catch (Exception e)
563 {
564 throw new IOException("Unable to start Extension Stack", e);
565 }
566
567
568 request.setServletAttribute(HttpConnection.UPGRADE_CONNECTION_ATTRIBUTE, wsConnection);
569
570 if (LOG.isDebugEnabled())
571 LOG.debug("Handshake Response: {}", handshaker);
572
573
574 handshaker.doHandshakeResponse(request, response);
575
576 if (LOG.isDebugEnabled())
577 LOG.debug("Websocket upgrade {} {} {} {}", request.getRequestURI(), version, response.getAcceptedSubProtocol(), wsConnection);
578
579 return true;
580 }
581 }