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