View Javadoc

1   // ========================================================================
2   // Copyright (c) 2010 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // All rights reserved. This program and the accompanying materials
5   // are made available under the terms of the Eclipse Public License v1.0
6   // and Apache License v2.0 which accompanies this distribution.
7   // The Eclipse Public License is available at
8   // http://www.eclipse.org/legal/epl-v10.html
9   // The Apache License v2.0 is available at
10  // http://www.opensource.org/licenses/apache2.0.php
11  // You may elect to redistribute this code under either of these licenses.
12  // ========================================================================
13  
14  package org.eclipse.jetty.websocket;
15  
16  import java.io.IOException;
17  import javax.servlet.http.HttpServletRequest;
18  import javax.servlet.http.HttpServletResponse;
19  
20  import org.eclipse.jetty.http.HttpException;
21  import org.eclipse.jetty.http.HttpParser;
22  import org.eclipse.jetty.io.ConnectedEndPoint;
23  import org.eclipse.jetty.server.HttpConnection;
24  import org.eclipse.jetty.util.log.Log;
25  
26  /**
27   * Factory to create WebSocket connections
28   */
29  public class WebSocketFactory
30  {
31      public interface Acceptor
32      {
33          WebSocket doWebSocketConnect(HttpServletRequest request, String protocol);
34  
35          String checkOrigin(HttpServletRequest request, String host, String origin);
36      }
37  
38      private final Acceptor _acceptor;
39      private WebSocketBuffers _buffers;
40      private int _maxIdleTime = 300000;
41  
42      public WebSocketFactory(Acceptor acceptor)
43      {
44          this(acceptor, 64 * 1024);
45      }
46  
47      public WebSocketFactory(Acceptor acceptor, int bufferSize)
48      {
49          _buffers = new WebSocketBuffers(bufferSize);
50          _acceptor = acceptor;
51      }
52  
53      /**
54       * Get the maxIdleTime.
55       *
56       * @return the maxIdleTime
57       */
58      public long getMaxIdleTime()
59      {
60          return _maxIdleTime;
61      }
62  
63      /**
64       * Set the maxIdleTime.
65       *
66       * @param maxIdleTime the maxIdleTime to set
67       */
68      public void setMaxIdleTime(int maxIdleTime)
69      {
70          _maxIdleTime = maxIdleTime;
71      }
72  
73      /**
74       * Get the bufferSize.
75       *
76       * @return the bufferSize
77       */
78      public int getBufferSize()
79      {
80          return _buffers.getBufferSize();
81      }
82  
83      /**
84       * Set the bufferSize.
85       *
86       * @param bufferSize the bufferSize to set
87       */
88      public void setBufferSize(int bufferSize)
89      {
90          if (bufferSize != getBufferSize())
91              _buffers = new WebSocketBuffers(bufferSize);
92      }
93  
94      /**
95       * Upgrade the request/response to a WebSocket Connection.
96       * <p>This method will not normally return, but will instead throw a
97       * UpgradeConnectionException, to exit HTTP handling and initiate
98       * WebSocket handling of the connection.
99       *
100      * @param request   The request to upgrade
101      * @param response  The response to upgrade
102      * @param websocket The websocket handler implementation to use
103      * @param origin    The origin of the websocket connection
104      * @param protocol  The websocket protocol
105      * @throws IOException in case of I/O errors
106      */
107     public void upgrade(HttpServletRequest request, HttpServletResponse response, WebSocket websocket, String origin, String protocol)
108             throws IOException
109     {
110         if (!"websocket".equalsIgnoreCase(request.getHeader("Upgrade")))
111             throw new IllegalStateException("!Upgrade:websocket");
112         if (!"HTTP/1.1".equals(request.getProtocol()))
113             throw new IllegalStateException("!HTTP/1.1");
114 
115         int draft = request.getIntHeader("Sec-WebSocket-Version");
116         if (draft < 0)
117             draft = request.getIntHeader("Sec-WebSocket-Draft");
118         HttpConnection http = HttpConnection.getCurrentConnection();
119         ConnectedEndPoint endp = (ConnectedEndPoint)http.getEndPoint();
120 
121         final WebSocketConnection connection;
122         switch (draft)
123         {
124             case -1:
125             case 0:
126                 connection = new WebSocketConnectionD00(websocket, endp, _buffers, http.getTimeStamp(), _maxIdleTime, protocol);
127                 break;
128             case 6:
129                 connection = new WebSocketConnectionD06(websocket, endp, _buffers, http.getTimeStamp(), _maxIdleTime, protocol);
130                 break;
131             default:
132                 Log.warn("Unsupported Websocket version: "+draft);
133                 throw new HttpException(400, "Unsupported draft specification: " + draft);
134         }
135 
136         // Let the connection finish processing the handshake
137         connection.handshake(request, response, origin, protocol);
138         response.flushBuffer();
139 
140         // Give the connection any unused data from the HTTP connection.
141         connection.fillBuffersFrom(((HttpParser)http.getParser()).getHeaderBuffer());
142         connection.fillBuffersFrom(((HttpParser)http.getParser()).getBodyBuffer());
143 
144         // Tell jetty about the new connection
145         request.setAttribute("org.eclipse.jetty.io.Connection", connection);
146     }
147 
148     public static String[] parseProtocols(String protocol)
149     {
150         if (protocol == null)
151             return new String[]{null};
152         protocol = protocol.trim();
153         if (protocol == null || protocol.length() == 0)
154             return new String[]{null};
155         String[] passed = protocol.split("\\s*,\\s*");
156         String[] protocols = new String[passed.length + 1];
157         System.arraycopy(passed, 0, protocols, 0, passed.length);
158         return protocols;
159     }
160 
161     public boolean acceptWebSocket(HttpServletRequest request, HttpServletResponse response)
162             throws IOException
163     {
164         if ("websocket".equalsIgnoreCase(request.getHeader("Upgrade")))
165         {
166             String protocol = request.getHeader("Sec-WebSocket-Protocol");
167             if (protocol == null) // TODO remove once draft period is over
168                 protocol = request.getHeader("WebSocket-Protocol");
169 
170             WebSocket websocket = null;
171             for (String p : WebSocketFactory.parseProtocols(protocol))
172             {
173                 websocket = _acceptor.doWebSocketConnect(request, p);
174                 if (websocket != null)
175                 {
176                     protocol = p;
177                     break;
178                 }
179             }
180 
181             String host = request.getHeader("Host");
182             String origin = request.getHeader("Origin");
183             origin = _acceptor.checkOrigin(request, host, origin);
184 
185             if (websocket != null)
186             {
187                 upgrade(request, response, websocket, origin, protocol);
188                 return true;
189             }
190 
191             response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
192         }
193 
194         return false;
195     }
196 }