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