View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
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   * Factory to create WebSocket connections
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       * Have the factory maintain 1 and only 1 scheduler. All connections share this scheduler.
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         // Bug #431459 - unregistering compression extensions till they are more stable
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         // Create supportedVersions
132         List<Integer> versions = new ArrayList<>();
133         for (int v : handshakes.keySet())
134         {
135             versions.add(v);
136         }
137         Collections.sort(versions, Collections.reverseOrder()); // newest first
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             // Handle response forbidden (and similar paths)
169             if (sockresp.isCommitted())
170             {
171                 return false;
172             }
173 
174             if (websocketPojo == null)
175             {
176                 // no creation, sorry
177                 sockresp.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, "Endpoint Creation Failed");
178                 return false;
179             }
180 
181             // Get the original HTTPConnection
182             HttpConnection connection = (HttpConnection)request.getAttribute("org.eclipse.jetty.server.HttpConnection");
183             
184             // Send the upgrade
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      * Default Creator logic
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(); // start lifecycle
338     }
339 
340     @Override
341     public boolean isUpgradeRequest(HttpServletRequest request, HttpServletResponse response)
342     {
343         if (!"GET".equalsIgnoreCase(request.getMethod()))
344         {
345             // not a "GET" request (not a websocket upgrade)
346             return false;
347         }
348 
349         String connection = request.getHeader("connection");
350         if (connection == null)
351         {
352             // no "Connection: upgrade" header present.
353             return false;
354         }
355 
356         // Test for "Upgrade" token
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             // no "Upgrade: websocket" header present.
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      * Upgrade the request/response to a WebSocket Connection.
439      * <p/>
440      * This method will not normally return, but will instead throw a UpgradeConnectionException, to exit HTTP handling and initiate WebSocket handling of the
441      * connection.
442      *
443      * @param http     the raw http connection
444      * @param request  The request to upgrade
445      * @param response The response to upgrade
446      * @param driver   The websocket handler implementation to use
447      * @throws IOException
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             // Old pre-RFC version specifications (header not present in RFC-6455)
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             // Per RFC 6455 - 4.4 - Supporting Multiple Versions of WebSocket Protocol
493             // Using the examples as outlined
494             response.setHeader("Sec-WebSocket-Version", supportedVersions);
495             response.sendError(HttpStatus.BAD_REQUEST_400, "Unsupported websocket version specification");
496             return false;
497         }
498 
499         // Initialize / Negotiate Extensions
500         ExtensionStack extensionStack = new ExtensionStack(getExtensionFactory());
501         // The JSR allows for the extensions to be pre-negotiated, filtered, etc...
502         // Usually from a Configurator.
503         if (response.isExtensionsNegotiated())
504         {
505             // Use pre-negotiated extension list from response
506             extensionStack.negotiate(response.getExtensions());
507         }
508         else
509         {
510             // Use raw extension list from request
511             extensionStack.negotiate(request.getExtensions());
512         }
513 
514         // Get original HTTP connection
515         EndPoint endp = http.getEndPoint();
516         Executor executor = http.getConnector().getExecutor();
517         ByteBufferPool bufferPool = http.getConnector().getByteBufferPool();
518         
519         // Setup websocket connection
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         // Setup Session
533         WebSocketSession session = createSession(request.getRequestURI(), driver, wsConnection);
534         session.setPolicy(driver.getPolicy());
535         session.setUpgradeRequest(request);
536         // set true negotiated extension list back to response 
537         response.setExtensions(extensionStack.getNegotiatedExtensions());
538         session.setUpgradeResponse(response);
539         wsConnection.setSession(session);
540 
541         // Setup Incoming Routing
542         wsConnection.setNextIncomingFrames(extensionStack);
543         extensionStack.setNextIncoming(session);
544 
545         // Setup Outgoing Routing
546         session.setOutgoingHandler(extensionStack);
547         extensionStack.setNextOutgoing(wsConnection);
548 
549         // Start Components
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         // Tell jetty about the new upgraded connection
568         request.setServletAttribute(HttpConnection.UPGRADE_CONNECTION_ATTRIBUTE, wsConnection);
569 
570         if (LOG.isDebugEnabled())
571             LOG.debug("Handshake Response: {}", handshaker);
572 
573         // Process (version specific) handshake response
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 }