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