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