1
2
3
4
5
6
7
8
9
10
11
12
13
14 package org.eclipse.jetty.websocket;
15
16 import java.io.IOException;
17 import java.util.ArrayList;
18 import java.util.Collections;
19 import java.util.Enumeration;
20 import java.util.HashMap;
21 import java.util.List;
22 import java.util.Map;
23
24 import javax.servlet.http.HttpServletRequest;
25 import javax.servlet.http.HttpServletResponse;
26
27 import org.eclipse.jetty.http.HttpException;
28 import org.eclipse.jetty.http.HttpParser;
29 import org.eclipse.jetty.io.ConnectedEndPoint;
30 import org.eclipse.jetty.server.AbstractHttpConnection;
31 import org.eclipse.jetty.server.BlockingHttpConnection;
32 import org.eclipse.jetty.util.QuotedStringTokenizer;
33 import org.eclipse.jetty.util.log.Log;
34 import org.eclipse.jetty.util.log.Logger;
35
36
37
38
39 public class WebSocketFactory
40 {
41 private static final Logger LOG = Log.getLogger(WebSocketFactory.class);
42
43 public interface Acceptor
44 {
45
46
47
48
49
50
51
52
53 WebSocket doWebSocketConnect(HttpServletRequest request, String protocol);
54
55
56
57
58
59
60
61
62 boolean checkOrigin(HttpServletRequest request, String origin);
63 }
64
65 private final Map<String,Class<? extends Extension>> _extensionClasses = new HashMap<String, Class<? extends Extension>>();
66 {
67 _extensionClasses.put("identity",IdentityExtension.class);
68 _extensionClasses.put("fragment",FragmentExtension.class);
69 _extensionClasses.put("x-deflate-frame",DeflateFrameExtension.class);
70 }
71
72 private final Acceptor _acceptor;
73 private WebSocketBuffers _buffers;
74 private int _maxIdleTime = 300000;
75 private int _maxTextMessageSize = 16*1024;
76 private int _maxBinaryMessageSize = -1;
77
78 public WebSocketFactory(Acceptor acceptor)
79 {
80 this(acceptor, 64 * 1024);
81 }
82
83 public WebSocketFactory(Acceptor acceptor, int bufferSize)
84 {
85 _buffers = new WebSocketBuffers(bufferSize);
86 _acceptor = acceptor;
87 }
88
89
90
91
92
93 public Map<String,Class<? extends Extension>> getExtensionClassesMap()
94 {
95 return _extensionClasses;
96 }
97
98
99
100
101
102
103 public long getMaxIdleTime()
104 {
105 return _maxIdleTime;
106 }
107
108
109
110
111
112
113 public void setMaxIdleTime(int maxIdleTime)
114 {
115 _maxIdleTime = maxIdleTime;
116 }
117
118
119
120
121
122
123 public int getBufferSize()
124 {
125 return _buffers.getBufferSize();
126 }
127
128
129
130
131
132
133 public void setBufferSize(int bufferSize)
134 {
135 if (bufferSize != getBufferSize())
136 _buffers = new WebSocketBuffers(bufferSize);
137 }
138
139
140
141
142 public int getMaxTextMessageSize()
143 {
144 return _maxTextMessageSize;
145 }
146
147
148
149
150
151
152 public void setMaxTextMessageSize(int maxTextMessageSize)
153 {
154 _maxTextMessageSize = maxTextMessageSize;
155 }
156
157
158
159
160 public int getMaxBinaryMessageSize()
161 {
162 return _maxBinaryMessageSize;
163 }
164
165
166
167
168
169
170 public void setMaxBinaryMessageSize(int maxBinaryMessageSize)
171 {
172 _maxBinaryMessageSize = maxBinaryMessageSize;
173 }
174
175
176
177
178
179
180
181
182
183
184
185
186
187 public void upgrade(HttpServletRequest request, HttpServletResponse response, WebSocket websocket, String protocol)
188 throws IOException
189 {
190 if (!"websocket".equalsIgnoreCase(request.getHeader("Upgrade")))
191 throw new IllegalStateException("!Upgrade:websocket");
192 if (!"HTTP/1.1".equals(request.getProtocol()))
193 throw new IllegalStateException("!HTTP/1.1");
194
195 int draft = request.getIntHeader("Sec-WebSocket-Version");
196 if (draft < 0)
197 draft = request.getIntHeader("Sec-WebSocket-Draft");
198 AbstractHttpConnection http = AbstractHttpConnection.getCurrentConnection();
199 if (http instanceof BlockingHttpConnection)
200 throw new IllegalStateException("Websockets not supported on blocking connectors");
201 ConnectedEndPoint endp = (ConnectedEndPoint)http.getEndPoint();
202
203 List<String> extensions_requested = new ArrayList<String>();
204 for (Enumeration e=request.getHeaders("Sec-WebSocket-Extensions");e.hasMoreElements();)
205 {
206 QuotedStringTokenizer tok = new QuotedStringTokenizer((String)e.nextElement(),",");
207 while (tok.hasMoreTokens())
208 extensions_requested.add(tok.nextToken());
209 }
210
211
212 final WebSocketConnection connection;
213 final List<Extension> extensions;
214 switch (draft)
215 {
216 case -1:
217 case 0:
218 extensions=Collections.emptyList();
219 connection = new WebSocketConnectionD00(websocket, endp, _buffers, http.getTimeStamp(), _maxIdleTime, protocol);
220 break;
221 case 1:
222 case 2:
223 case 3:
224 case 4:
225 case 5:
226 case 6:
227 extensions=Collections.emptyList();
228 connection = new WebSocketConnectionD06(websocket, endp, _buffers, http.getTimeStamp(), _maxIdleTime, protocol);
229 break;
230 case 7:
231 case 8:
232 extensions= initExtensions(extensions_requested,8-WebSocketConnectionD08.OP_EXT_DATA, 16-WebSocketConnectionD08.OP_EXT_CTRL,3);
233 connection = new WebSocketConnectionD08(websocket, endp, _buffers, http.getTimeStamp(), _maxIdleTime, protocol,extensions,draft);
234 break;
235 case 13:
236 extensions= initExtensions(extensions_requested,8-WebSocketConnectionD13.OP_EXT_DATA, 16-WebSocketConnectionD13.OP_EXT_CTRL,3);
237 connection = new WebSocketConnectionD13(websocket, endp, _buffers, http.getTimeStamp(), _maxIdleTime, protocol,extensions,draft);
238 break;
239 default:
240 LOG.warn("Unsupported Websocket version: "+draft);
241 response.setHeader("Sec-WebSocket-Version","0,6,8,13");
242 throw new HttpException(400, "Unsupported draft specification: " + draft);
243 }
244
245
246 connection.getConnection().setMaxBinaryMessageSize(_maxBinaryMessageSize);
247 connection.getConnection().setMaxTextMessageSize(_maxTextMessageSize);
248
249
250 connection.handshake(request, response, protocol);
251 response.flushBuffer();
252
253
254 connection.fillBuffersFrom(((HttpParser)http.getParser()).getHeaderBuffer());
255 connection.fillBuffersFrom(((HttpParser)http.getParser()).getBodyBuffer());
256
257
258 LOG.debug("Websocket upgrade {} {} {} {}",request.getRequestURI(),draft,protocol,connection);
259 request.setAttribute("org.eclipse.jetty.io.Connection", connection);
260 }
261
262
263
264 protected String[] parseProtocols(String protocol)
265 {
266 if (protocol == null)
267 return new String[]{null};
268 protocol = protocol.trim();
269 if (protocol == null || protocol.length() == 0)
270 return new String[]{null};
271 String[] passed = protocol.split("\\s*,\\s*");
272 String[] protocols = new String[passed.length + 1];
273 System.arraycopy(passed, 0, protocols, 0, passed.length);
274 return protocols;
275 }
276
277
278
279 public boolean acceptWebSocket(HttpServletRequest request, HttpServletResponse response)
280 throws IOException
281 {
282 if ("websocket".equalsIgnoreCase(request.getHeader("Upgrade")))
283 {
284 String origin = request.getHeader("Origin");
285 if (origin==null)
286 origin = request.getHeader("Sec-WebSocket-Origin");
287 if (!_acceptor.checkOrigin(request,origin))
288 {
289 response.sendError(HttpServletResponse.SC_FORBIDDEN);
290 return false;
291 }
292
293
294 WebSocket websocket = null;
295
296 Enumeration<String> protocols = request.getHeaders("Sec-WebSocket-Protocol");
297 String protocol=null;
298 while (protocol==null && protocols!=null && protocols.hasMoreElements())
299 {
300 String candidate = protocols.nextElement();
301 for (String p : parseProtocols(candidate))
302 {
303 websocket = _acceptor.doWebSocketConnect(request, p);
304 if (websocket != null)
305 {
306 protocol = p;
307 break;
308 }
309 }
310 }
311
312
313 if (websocket == null)
314 {
315
316 websocket = _acceptor.doWebSocketConnect(request, null);
317
318 if (websocket==null)
319 {
320 response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
321 return false;
322 }
323 }
324
325
326 upgrade(request, response, websocket, protocol);
327 return true;
328 }
329
330 return false;
331 }
332
333
334
335 public List<Extension> initExtensions(List<String> requested,int maxDataOpcodes,int maxControlOpcodes,int maxReservedBits)
336 {
337 List<Extension> extensions = new ArrayList<Extension>();
338 for (String rExt : requested)
339 {
340 QuotedStringTokenizer tok = new QuotedStringTokenizer(rExt,";");
341 String extName=tok.nextToken().trim();
342 Map<String,String> parameters = new HashMap<String,String>();
343 while (tok.hasMoreTokens())
344 {
345 QuotedStringTokenizer nv = new QuotedStringTokenizer(tok.nextToken().trim(),"=");
346 String name=nv.nextToken().trim();
347 String value=nv.hasMoreTokens()?nv.nextToken().trim():null;
348 parameters.put(name,value);
349 }
350
351 Extension extension = newExtension(extName);
352
353 if (extension==null)
354 continue;
355
356 if (extension.init(parameters))
357 {
358 LOG.debug("add {} {}",extName,parameters);
359 extensions.add(extension);
360 }
361 }
362 LOG.debug("extensions={}",extensions);
363 return extensions;
364 }
365
366
367
368 private Extension newExtension(String name)
369 {
370 try
371 {
372 Class<? extends Extension> extClass = _extensionClasses.get(name);
373 if (extClass!=null)
374 return extClass.newInstance();
375 }
376 catch (Exception e)
377 {
378 LOG.warn(e);
379 }
380
381 return null;
382 }
383 }