View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2016 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.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   * Factory to create WebSocket connections
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       * Have the factory maintain 1 and only 1 scheduler. All connections share this scheduler.
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         // Create supportedVersions
138         List<Integer> versions = new ArrayList<>();
139         for (int v : handshakes.keySet())
140         {
141             versions.add(v);
142         }
143         Collections.sort(versions, Collections.reverseOrder()); // newest first
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             // Create Servlet Specific Upgrade Request/Response objects
171             ServletUpgradeRequest sockreq = new ServletUpgradeRequest(request);
172             ServletUpgradeResponse sockresp = new ServletUpgradeResponse(response);
173 
174             Object websocketPojo = creator.createWebSocket(sockreq, sockresp);
175 
176             // Handle response forbidden (and similar paths)
177             if (sockresp.isCommitted())
178             {
179                 return false;
180             }
181 
182             if (websocketPojo == null)
183             {
184                 // no creation, sorry
185                 sockresp.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, "Endpoint Creation Failed");
186                 return false;
187             }
188 
189             // Allow Decorators to do their thing
190             websocketPojo = getObjectFactory().decorate(websocketPojo);
191             
192             // Get the original HTTPConnection
193             HttpConnection connection = (HttpConnection)request.getAttribute("org.eclipse.jetty.server.HttpConnection");
194             
195             // Send the upgrade
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      * Default Creator logic
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         /* Not relevant for a Server, as this is defined in the
349          * Connector configuration 
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         // Setup ObjectFactory
369         this.objectFactory = (DecoratedObjectFactory)context.getAttribute(DecoratedObjectFactory.ATTR);
370         if (this.objectFactory == null)
371         {
372             this.objectFactory = new DecoratedObjectFactory();
373         }
374         
375         // Validate Environment
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             // start lifecycle
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             // not a "GET" request (not a websocket upgrade)
406             return false;
407         }
408 
409         String connection = request.getHeader("connection");
410         if (connection == null)
411         {
412             // no "Connection: upgrade" header present.
413             return false;
414         }
415 
416         // Test for "Upgrade" token
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             // no "Upgrade: websocket" header present.
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      * Upgrade the request/response to a WebSocket Connection.
499      * <p/>
500      * This method will not normally return, but will instead throw a UpgradeConnectionException, to exit HTTP handling and initiate WebSocket handling of the
501      * connection.
502      *
503      * @param http     the raw http connection
504      * @param request  The request to upgrade
505      * @param response The response to upgrade
506      * @param driver   The websocket handler implementation to use
507      * @throws IOException
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             // Old pre-RFC version specifications (header not present in RFC-6455)
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             // Per RFC 6455 - 4.4 - Supporting Multiple Versions of WebSocket Protocol
553             // Using the examples as outlined
554             response.setHeader("Sec-WebSocket-Version", supportedVersions);
555             response.sendError(HttpStatus.BAD_REQUEST_400, "Unsupported websocket version specification");
556             return false;
557         }
558 
559         // Initialize / Negotiate Extensions
560         ExtensionStack extensionStack = new ExtensionStack(getExtensionFactory());
561         // The JSR allows for the extensions to be pre-negotiated, filtered, etc...
562         // Usually from a Configurator.
563         if (response.isExtensionsNegotiated())
564         {
565             // Use pre-negotiated extension list from response
566             extensionStack.negotiate(response.getExtensions());
567         }
568         else
569         {
570             // Use raw extension list from request
571             extensionStack.negotiate(request.getExtensions());
572         }
573 
574         // Get original HTTP connection
575         EndPoint endp = http.getEndPoint();
576         Connector connector = http.getConnector();
577         Executor executor = connector.getExecutor();
578         ByteBufferPool bufferPool = connector.getByteBufferPool();
579 
580         // Setup websocket connection
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         // Setup Session
594         WebSocketSession session = createSession(request.getRequestURI(), driver, wsConnection);
595         session.setPolicy(driver.getPolicy());
596         session.setUpgradeRequest(request);
597         // set true negotiated extension list back to response 
598         response.setExtensions(extensionStack.getNegotiatedExtensions());
599         session.setUpgradeResponse(response);
600         wsConnection.addListener(session);
601 
602         // Setup Incoming Routing
603         wsConnection.setNextIncomingFrames(extensionStack);
604         extensionStack.setNextIncoming(session);
605 
606         // Setup Outgoing Routing
607         session.setOutgoingHandler(extensionStack);
608         extensionStack.setNextOutgoing(wsConnection);
609 
610         // Start Components
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         // Tell jetty about the new upgraded connection
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         // Process (version specific) handshake response
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 }