View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2012 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.util.ArrayList;
23  import java.util.Collections;
24  import java.util.HashMap;
25  import java.util.Iterator;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.Queue;
29  import java.util.concurrent.ConcurrentLinkedQueue;
30  import java.util.concurrent.Executor;
31  
32  import javax.servlet.http.HttpServletRequest;
33  import javax.servlet.http.HttpServletResponse;
34  
35  import org.eclipse.jetty.http.HttpStatus;
36  import org.eclipse.jetty.io.ByteBufferPool;
37  import org.eclipse.jetty.io.EndPoint;
38  import org.eclipse.jetty.io.MappedByteBufferPool;
39  import org.eclipse.jetty.server.HttpConnection;
40  import org.eclipse.jetty.util.component.ContainerLifeCycle;
41  import org.eclipse.jetty.util.log.Log;
42  import org.eclipse.jetty.util.log.Logger;
43  import org.eclipse.jetty.util.thread.Scheduler;
44  import org.eclipse.jetty.util.thread.TimerScheduler;
45  import org.eclipse.jetty.websocket.core.annotations.WebSocket;
46  import org.eclipse.jetty.websocket.core.api.Extension;
47  import org.eclipse.jetty.websocket.core.api.ExtensionRegistry;
48  import org.eclipse.jetty.websocket.core.api.UpgradeRequest;
49  import org.eclipse.jetty.websocket.core.api.UpgradeResponse;
50  import org.eclipse.jetty.websocket.core.api.WebSocketException;
51  import org.eclipse.jetty.websocket.core.api.WebSocketPolicy;
52  import org.eclipse.jetty.websocket.core.extensions.WebSocketExtensionRegistry;
53  import org.eclipse.jetty.websocket.core.io.IncomingFrames;
54  import org.eclipse.jetty.websocket.core.io.OutgoingFrames;
55  import org.eclipse.jetty.websocket.core.io.WebSocketSession;
56  import org.eclipse.jetty.websocket.core.io.event.EventDriver;
57  import org.eclipse.jetty.websocket.core.io.event.EventDriverFactory;
58  import org.eclipse.jetty.websocket.core.protocol.ExtensionConfig;
59  import org.eclipse.jetty.websocket.server.handshake.HandshakeRFC6455;
60  
61  /**
62   * Factory to create WebSocket connections
63   */
64  public class WebSocketServerFactory extends ContainerLifeCycle implements WebSocketCreator
65  {
66      private static final Logger LOG = Log.getLogger(WebSocketServerFactory.class);
67  
68      private final Map<Integer, WebSocketHandshake> handshakes = new HashMap<>();
69      {
70          handshakes.put(HandshakeRFC6455.VERSION,new HandshakeRFC6455());
71      }
72  
73      private final Queue<WebSocketSession> sessions = new ConcurrentLinkedQueue<>();
74      /**
75       * Have the factory maintain 1 and only 1 scheduler. All connections share this scheduler.
76       */
77      private final Scheduler scheduler = new TimerScheduler();
78      private final String supportedVersions;
79      private final WebSocketPolicy basePolicy;
80      private final EventDriverFactory eventDriverFactory;
81      private final WebSocketExtensionRegistry extensionRegistry;
82      private WebSocketCreator creator;
83      private List<Class<?>> registeredSocketClasses;
84  
85      public WebSocketServerFactory(WebSocketPolicy policy)
86      {
87          this(policy,new MappedByteBufferPool());
88      }
89  
90      public WebSocketServerFactory(WebSocketPolicy policy, ByteBufferPool bufferPool)
91      {
92          addBean(scheduler);
93          addBean(bufferPool);
94  
95          this.registeredSocketClasses = new ArrayList<>();
96  
97          this.basePolicy = policy;
98          this.eventDriverFactory = new EventDriverFactory(basePolicy);
99          this.extensionRegistry = new WebSocketExtensionRegistry(basePolicy,bufferPool);
100         this.creator = this;
101 
102         // Create supportedVersions
103         List<Integer> versions = new ArrayList<>();
104         for (int v : handshakes.keySet())
105         {
106             versions.add(v);
107         }
108         Collections.sort(versions,Collections.reverseOrder()); // newest first
109         StringBuilder rv = new StringBuilder();
110         for (int v : versions)
111         {
112             if (rv.length() > 0)
113             {
114                 rv.append(", ");
115             }
116             rv.append(v);
117         }
118         supportedVersions = rv.toString();
119     }
120 
121     public boolean acceptWebSocket(HttpServletRequest request, HttpServletResponse response) throws IOException
122     {
123         ServletWebSocketRequest sockreq = new ServletWebSocketRequest(request);
124         ServletWebSocketResponse sockresp = new ServletWebSocketResponse(response);
125 
126         WebSocketCreator creator = getCreator();
127 
128         Object websocketPojo = creator.createWebSocket(sockreq,sockresp);
129 
130         // Handle response forbidden (and similar paths)
131         if (sockresp.isCommitted())
132         {
133             return false;
134         }
135 
136         if (websocketPojo == null)
137         {
138             // no creation, sorry
139             response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
140             return false;
141         }
142 
143         // Send the upgrade
144         EventDriver driver = eventDriverFactory.wrap(websocketPojo);
145         return upgrade(sockreq,sockresp,driver);
146     }
147 
148     protected void closeConnections()
149     {
150         for (WebSocketSession session : sessions)
151         {
152             session.close();
153         }
154         sessions.clear();
155     }
156 
157     /**
158      * Default Creator logic
159      */
160     @Override
161     public Object createWebSocket(UpgradeRequest req, UpgradeResponse resp)
162     {
163         if (registeredSocketClasses.size() < 1)
164         {
165             throw new WebSocketException("No WebSockets have been registered with the factory.  Cannot use default implementation of WebSocketCreator.");
166         }
167 
168         if (registeredSocketClasses.size() > 1)
169         {
170             LOG.warn("You have registered more than 1 websocket object, and are using the default WebSocketCreator! Using first registered websocket.");
171         }
172 
173         Class<?> firstClass = registeredSocketClasses.get(0);
174         try
175         {
176             return firstClass.newInstance();
177         }
178         catch (InstantiationException | IllegalAccessException e)
179         {
180             throw new WebSocketException("Unable to create instance of " + firstClass,e);
181         }
182     }
183 
184     @Override
185     protected void doStop() throws Exception
186     {
187         closeConnections();
188         super.doStop();
189     }
190 
191     public WebSocketCreator getCreator()
192     {
193         return this.creator;
194     }
195 
196     public ExtensionRegistry getExtensionRegistry()
197     {
198         return extensionRegistry;
199     }
200 
201     /**
202      * Get the base policy in use for WebSockets.
203      * <p>
204      * Note: individual WebSocket implementations can override some of the values in here by using the {@link WebSocket &#064;WebSocket} annotation.
205      * 
206      * @return the base policy
207      */
208     public WebSocketPolicy getPolicy()
209     {
210         return basePolicy;
211     }
212 
213     public List<Extension> initExtensions(List<ExtensionConfig> requested)
214     {
215         List<Extension> extensions = new ArrayList<Extension>();
216 
217         for (ExtensionConfig cfg : requested)
218         {
219             Extension extension = extensionRegistry.newInstance(cfg);
220 
221             if (extension == null)
222             {
223                 continue;
224             }
225 
226             LOG.debug("added {}",extension);
227             extensions.add(extension);
228         }
229         LOG.debug("extensions={}",extensions);
230         return extensions;
231     }
232 
233     public boolean isUpgradeRequest(HttpServletRequest request, HttpServletResponse response)
234     {
235         String upgrade = request.getHeader("Upgrade");
236         if (upgrade == null)
237         {
238             // Quietly fail
239             return false;
240         }
241 
242         if (!"websocket".equalsIgnoreCase(upgrade))
243         {
244             LOG.warn("Not a 'Upgrade: WebSocket' (was [Upgrade: " + upgrade + "])");
245             return false;
246         }
247 
248         if (!"HTTP/1.1".equals(request.getProtocol()))
249         {
250             LOG.warn("Not a 'HTTP/1.1' request (was [" + request.getProtocol() + "])");
251             return false;
252         }
253 
254         return true;
255     }
256 
257     protected String[] parseProtocols(String protocol)
258     {
259         if (protocol == null)
260         {
261             return new String[]
262             { null };
263         }
264         protocol = protocol.trim();
265         if ((protocol == null) || (protocol.length() == 0))
266         {
267             return new String[]
268             { null };
269         }
270         String[] passed = protocol.split("\\s*,\\s*");
271         String[] protocols = new String[passed.length + 1];
272         System.arraycopy(passed,0,protocols,0,passed.length);
273         return protocols;
274     }
275 
276     /**
277      * Register a websocket class pojo with the default {@link WebSocketCreator}.
278      * <p>
279      * Note: only required if using the default {@link WebSocketCreator} provided by this factory.
280      * 
281      * @param websocketPojo
282      *            the class to instantiate for each incoming websocket upgrade request.
283      */
284     public void register(Class<?> websocketPojo)
285     {
286         registeredSocketClasses.add(websocketPojo);
287     }
288 
289     public boolean sessionClosed(WebSocketSession session)
290     {
291         return isRunning() && sessions.remove(session);
292     }
293 
294     public boolean sessionOpened(WebSocketSession session)
295     {
296         if (LOG.isDebugEnabled())
297         {
298             LOG.debug("Session Opened: {}",session);
299         }
300         if (!isRunning())
301         {
302             LOG.warn("Factory is not running");
303             return false;
304         }
305         boolean ret = sessions.offer(session);
306         session.onConnect();
307         return ret;
308     }
309 
310     public void setCreator(WebSocketCreator creator)
311     {
312         this.creator = creator;
313     }
314 
315     /**
316      * Upgrade the request/response to a WebSocket Connection.
317      * <p>
318      * This method will not normally return, but will instead throw a UpgradeConnectionException, to exit HTTP handling and initiate WebSocket handling of the
319      * connection.
320      * 
321      * @param request
322      *            The request to upgrade
323      * @param response
324      *            The response to upgrade
325      * @param driver
326      *            The websocket handler implementation to use
327      * @throws IOException
328      *             in case of I/O errors
329      */
330     public boolean upgrade(ServletWebSocketRequest request, ServletWebSocketResponse response, EventDriver driver) throws IOException
331     {
332         if (!"websocket".equalsIgnoreCase(request.getHeader("Upgrade")))
333         {
334             throw new IllegalStateException("Not a 'WebSocket: Upgrade' request");
335         }
336         if (!"HTTP/1.1".equals(request.getProtocol()))
337         {
338             throw new IllegalStateException("Not a 'HTTP/1.1' request");
339         }
340 
341         int version = request.getIntHeader("Sec-WebSocket-Version");
342         if (version < 0)
343         {
344             // Old pre-RFC version specifications (header not present in RFC-6455)
345             version = request.getIntHeader("Sec-WebSocket-Draft");
346         }
347 
348         WebSocketHandshake handshaker = handshakes.get(version);
349         if (handshaker == null)
350         {
351             LOG.warn("Unsupported Websocket version: " + version);
352             // Per RFC 6455 - 4.4 - Supporting Multiple Versions of WebSocket Protocol
353             // Using the examples as outlined
354             response.setHeader("Sec-WebSocket-Version",supportedVersions);
355             response.sendError(HttpStatus.BAD_REQUEST_400,"Unsupported websocket version specification");
356             return false;
357         }
358 
359         // Create connection
360         HttpConnection http = HttpConnection.getCurrentConnection();
361         EndPoint endp = http.getEndPoint();
362         Executor executor = http.getConnector().getExecutor();
363         ByteBufferPool bufferPool = http.getConnector().getByteBufferPool();
364         WebSocketServerConnection connection = new WebSocketServerConnection(endp,executor,scheduler,driver.getPolicy(),bufferPool,this);
365 
366         LOG.debug("HttpConnection: {}",http);
367         LOG.debug("AsyncWebSocketConnection: {}",connection);
368 
369         // Initialize / Negotiate Extensions
370         WebSocketSession session = new WebSocketSession(driver,connection,getPolicy(),response.getAcceptedSubProtocol());
371         connection.setSession(session);
372         List<Extension> extensions = initExtensions(request.getExtensions());
373         request.setValidExtensions(extensions);
374 
375         // Start with default routing.
376         IncomingFrames incoming = session;
377         OutgoingFrames outgoing = connection;
378 
379         // Connect extensions
380         if (extensions != null)
381         {
382             connection.getParser().configureFromExtensions(extensions);
383             connection.getGenerator().configureFromExtensions(extensions);
384 
385             Iterator<Extension> extIter;
386             // Connect outgoings
387             extIter = extensions.iterator();
388             while (extIter.hasNext())
389             {
390                 Extension ext = extIter.next();
391                 ext.setNextOutgoingFrames(outgoing);
392                 outgoing = ext;
393             }
394 
395             // Connect incomings
396             Collections.reverse(extensions);
397             extIter = extensions.iterator();
398             while (extIter.hasNext())
399             {
400                 Extension ext = extIter.next();
401                 ext.setNextIncomingFrames(incoming);
402                 incoming = ext;
403             }
404         }
405 
406         // configure session for outgoing flows
407         session.setOutgoing(outgoing);
408         // configure connection for incoming flows
409         connection.getParser().setIncomingFramesHandler(incoming);
410 
411         // Tell jetty about the new connection
412         request.setAttribute(HttpConnection.UPGRADE_CONNECTION_ATTRIBUTE,connection);
413 
414         // Process (version specific) handshake response
415         LOG.debug("Handshake Response: {}",handshaker);
416         handshaker.doHandshakeResponse(request,response);
417 
418         LOG.debug("Websocket upgrade {} {} {} {}",request.getRequestURI(),version,response.getAcceptedSubProtocol(),connection);
419         return true;
420     }
421 }