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