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