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