Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(116)

Side by Side Diff: runtime/bin/websocket_impl.dart

Issue 10205012: Initial web socket server implementation (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 8 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file.
4
5 class _WebSocketMessageType {
6 static final int NONE = 0;
7 static final int BINARY = 1;
8 static final int TEXT = 2;
9 static final int CLOSE = 3;
10 }
11
12
13 class _WebSocketOpcode {
14 static final int CONTINUATION = 0;
15 static final int TEXT = 1;
16 static final int BINARY = 2;
17 static final int RESERVED_3 = 3;
18 static final int RESERVED_4 = 4;
19 static final int RESERVED_5 = 5;
20 static final int RESERVED_6 = 6;
21 static final int RESERVED_7 = 7;
22 static final int CLOSE = 8;
23 static final int PING = 9;
24 static final int PONG = 10;
25 static final int RESERVED_B = 11;
26 static final int RESERVED_C = 12;
27 static final int RESERVED_D = 13;
28 static final int RESERVED_E = 14;
29 static final int RESERVED_F = 15;
30 }
31
32 /**
33 * The web socket protocol processor handles the protocol byte stream
34 * which is supplied through the [:update:] and [:closed:]
35 * methods. As the protocol is processed the following callbacks are
36 * called:
37 *
38 * [:onMessageStart:]
39 * [:onMessageData:]
40 * [:onMessageEnd:]
41 * [:onClosed:]
42 * [:onError:]
43 *
44 */
45 class _WebSocketProtocolProcessor {
46 static final int START = 0;
47 static final int LEN_FIRST = 1;
48 static final int LEN_REST = 2;
49 static final int MASK = 3;
50 static final int PAYLOAD = 4;
51 static final int CLOSED = 5;
52 static final int FAILURE = 6;
53
54 _WebSocketProtocolProcessor() {
55 _reset();
56 _currentMessageType = _WebSocketMessageType.NONE;
57 }
58
59 /**
60 * Process data received from the underlying communication channel.
61 */
62 void update(List<int> buffer, int offset, int count) {
63 int index = offset;
64 int lastIndex = offset + count;
65 try {
66 if (_state == _State.CLOSED) {
67 throw new WebSocketException("Data on closed connection");
68 }
69 if (_state == _State.FAILURE) {
70 throw new WebSocketException("Data on failed connection");
71 }
72 while ((index < lastIndex) && _state != CLOSED && _state != FAILURE) {
73 int byte = buffer[index];
74 switch (_state) {
75 case START:
76 _fin = (byte & 0x80) != 0;
77 _opcode = (byte & 0xF);
78 switch (_opcode) {
79 case _WebSocketOpcode.CONTINUATION:
80 if (_currentMessageType == _WebSocketMessageType.NONE) {
81 throw new WebSocketException("Protocol error");
82 }
83 break;
84
85 case _WebSocketOpcode.TEXT:
86 if (_currentMessageType != _WebSocketMessageType.NONE) {
87 throw new WebSocketException("Protocol error");
88 }
89 _currentMessageType = _WebSocketMessageType.TEXT;
90 if (onMessageStart != null) {
91 onMessageStart(_WebSocketMessageType.TEXT);
92 }
93 break;
94
95 case _WebSocketOpcode.BINARY:
96 if (_currentMessageType != _WebSocketMessageType.NONE) {
97 throw new WebSocketException("Protocol error");
98 }
99 _currentMessageType = _WebSocketMessageType.BINARY;
100 if (onMessageStart != null) {
101 onMessageStart(_WebSocketMessageType.BINARY);
102 }
103 break;
104
105 case _WebSocketOpcode.CLOSE:
106 if (_currentMessageType != _WebSocketMessageType.NONE) {
107 throw new WebSocketException("Protocol error");
108 }
109 _currentMessageType = _WebSocketMessageType.CLOSE;
110 break;
111
112 case _WebSocketOpcode.PING:
113 // TODO(sgjesse): Handle ping.
Mads Ager (google) 2012/04/25 10:41:55 How about throwing an UnsupportedOperation excepti
Søren Gjesse 2012/04/25 13:57:14 Done.
114 break;
115
116 case _WebSocketOpcode.PONG:
117 // TODO(sgjesse): Handle pong.
Mads Ager (google) 2012/04/25 10:41:55 Ditto.
Søren Gjesse 2012/04/25 13:57:14 Done.
118 break;
119
120 default:
121 throw new WebSocketException("Protocol error");
122 break;
123 }
124 _state = LEN_FIRST;
125 break;
126
127 case LEN_FIRST:
128 _masked = (byte & 0x80) != 0;
129 _len = byte & 0x7F;
130 if (_len < 126) {
131 _lengthDone();
132 } else if (_len == 126) {
133 _ len = 0;
Mads Ager (google) 2012/04/25 10:41:55 _ len -> _len !
Søren Gjesse 2012/04/25 13:57:14 Year - I discovered this when writing the tests.
134 _remainingLenBytes = 2;
135 } else if (_len == 127) {
136 _ len = 0;
Mads Ager (google) 2012/04/25 10:41:55 Ditto!
Søren Gjesse 2012/04/25 13:57:14 Done.
137 _remainingLenBytes = 8;
138 }
139 break;
140
141 case LEN_REST:
142 _len = _len << 8 | byte;
143 _remainingLenBytes--;
144 if (_remainingLenBytes == 0) {
145 _lengthDone();
146 }
147 break;
148
149 case MASK:
150 _maskingKey = _maskingKey << 8 | byte;
151 _remainingMaskingKeyBytes--;
152 if (_remainingMaskingKeyBytes == 0) {
153 _maskDone();
154 }
155 break;
156
157 case PAYLOAD:
158 // The payload is not handled one byte at the time but in blocks.
Mads Ager (google) 2012/04/25 10:41:55 at the time -> at a time
Søren Gjesse 2012/04/25 13:57:14 Done.
159 int payload;
160 if (lastIndex - index >= _remainingPayloadBytes) {
161 payload = lastIndex - index;
162 } else {
163 payload = _remainingPayloadBytes;
164 }
165 // Unmask payload if masked.
166 if (_masked) {
167 for (int i = 0; i < payload; i++) {
168 int maskingByte =
169 ((_maskingKey >> ((3 - _unmaskingIndex) * 8)) & 0xFF);
170 buffer[index + i] = buffer[index + i] ^ maskingByte;
171 _unmaskingIndex = (_unmaskingIndex + 1) % 4;
172 }
173 }
174
175 switch (_currentMessageType) {
176 case _WebSocketMessageType.NONE:
177 throw new WebSocketException("Protocol error");
178 break;
179
180 case _WebSocketMessageType.TEXT:
181 case _WebSocketMessageType.BINARY:
182 if (onMessageData != null) {
183 onMessageData(buffer, index, payload);
184 }
185 _remainingPayloadBytes -= payload;
186 index += payload;
187 if (_fin) {
188 _messageEnd();
189 }
190 break;
191
192 case _WebSocketMessageType.CLOSE:
193 // Allocate a buffer for holding the close payload if any.
194 if (_closePayload == null) {
195 _closePayload = new List<int>();
196 }
197 _closePayload.addAll(buffer.getRange(index, payload));
198 _remainingPayloadBytes -= payload;
199 index += payload;
200 if (_fin) {
201 if (_remainingPayloadBytes != 0) {
202 throw new WebSocketException("Protocol error");
203 }
204 int status;
205 String reason;
206 if (_closePayload.length > 0) {
207 if (_closePayload.length == 1) {
208 throw new WebSocketException("Protocol error");
209 }
210 status = _closePayload[0] << 8 | _closePayload[1];
211 if (_closePayload.length > 2) {
212 var decoder = _StringDecoders.decoder(Encoding.UTF_8);
213 decoder.write(_closePayload.getRange(
214 2, _closePayload.length - 2));
215 reason = decoder.decoded;
216 }
217 }
218 if (onClosed != null) onClosed(status, reason);
219 _currentMessageType = _WebSocketMessageType.NONE;
220 _state = CLOSED;
221 }
222 break;
223
224 default:
225 throw new WebSocketException("Protocol error");
226 break;
227 }
228
229 // Hack - as we always do index++ below.
230 index--;
231 break;
232
233 default:
234 throw new WebSocketException("Protocol error");
235 break;
236 }
237
238 // Move to the next byte.
239 index++;
240 }
241 } catch (var e) {
242 _reportError(e);
243 }
244 }
245
246 /**
247 * Indicate that the underlying communication channel has been closed.
248 */
249 void closed() {
250 if (_state == START || _state == CLOSED || _state == FAILURE) return;
Mads Ager (google) 2012/04/25 10:41:55 Should this change the state of the socket somehow
Søren Gjesse 2012/04/25 13:57:14 Set the state to CLOSED.
251 _reportError(new WebSocketException("Protocol error"));
252 }
253
254 void _lengthDone() {
255 if (_masked) {
256 _state = MASK;
257 _remainingMaskingKeyBytes = 4;
258 } else {
259 _remainingPayloadBytes = _len;
260 _startPayload();
261 }
262 }
263
264 void _maskDone() {
265 _remainingPayloadBytes = _len;
266 _startPayload();
267 }
268
269 void _startPayload() {
270 // Check whether there is any payload. If not indicate empty message or
Mads Ager (google) 2012/04/25 10:41:55 Indentation and the comment should be completed.
Søren Gjesse 2012/04/25 13:57:14 Done.
271 if (_remainingPayloadBytes == 0) {
272 if (_currentMessageType ==_WebSocketMessageType.CLOSE) {
273 if (onClosed != null) onClosed(null, null);
274 } else {
275 _messageEnd();
276 }
277 } else {
278 _state = PAYLOAD;
279 }
280 }
281
282 void _messageEnd() {
283 if (_remainingPayloadBytes != 0) {
284 throw new WebSocketException("Protocol error");
285 }
286 if (onMessageEnd != null) onMessageEnd();
287 _currentMessageType = _WebSocketMessageType.NONE;
288 _reset();
289 }
290
291 void _reset() {
292 _state = START;
293 _fin = null;
294 _opcode = null;
295 _len = null;
296 _masked = null;
297 _maskingKey = 0;
298 _remainingLenBytes = null;
299 _remainingMaskingKeyBytes = null;
300 _remainingPayloadBytes = null;
301 _unmaskingIndex = 0;
302 }
303
304 void _reportError(e) {
305 // Report the error through the error callback if any. Otherwise
306 // throw the error.
307 if (onError != null) {
308 onError(e);
309 _state = _State.FAILURE;
310 } else {
311 throw e;
312 }
313 }
314
315 int _state;
316 bool _fin;
317 int _opcode;
318 int _len;
319 bool _masked;
320 int _maskingKey;
321 int _remainingLenBytes;
322 int _remainingMaskingKeyBytes;
323 int _remainingPayloadBytes;
324 int _unmaskingIndex;
325
326 int _currentMessageType;
327 List<int> _closePayload;
328
329 Function onMessageStart;
330 Function onMessageData;
331 Function onMessageEnd;
332 Function onClosed;
333 Function onError;
334 }
335
336
337 class _WebSocketConnection implements WebSocketConnection {
338 _WebSocketConnection(Socket this._socket) {
339 _WebSocketProtocolProcessor processor = new _WebSocketProtocolProcessor();
340 processor.onMessageStart = _onWebSocketMessageStart;
341 processor.onMessageData = _onWebSocketMessageData;
342 processor.onMessageEnd = _onWebSocketMessageEnd;
343 processor.onClosed = _onWebSocketClosed;
344 processor.onError = _onWebSocketError;
345
346 _socket.onData = () {
347 int available = _socket.available();
348 List<int> data = new List<int>(available);
349 int read = _socket.readList(data, 0, available);
350 processor.update(data, 0, read);
351 };
352 _socket.onClosed = () {
353 processor.closed();
354 if (_closeSent) {
355 // Got socket close in response to close frame. Don't treat
356 // that as an error.
357 if (_closeTimer != null) _closeTimer.cancel();
358 } else {
359 if (_onError != null) {
360 _onError(new WebSocketException("Unexpected close"));
361 }
362 }
363 _socket.close();
364 };
365 _socket.onError = (e) {
366 if (_onError != null) _onError(e);
367 _socket.close();
368 };
369 }
370
371 void set onMessage(void callback(Object message)) {
372 _onMessage = callback;
373 }
374
375 void set onClosed(void callback(int status, String reason)) {
376 _onClosed = callback;
377 }
378
379 void set onError(void callback(e)) {
380 _onError = callback;
381 }
382
383 send(Object message) {
384 if (_closeSent) {
385 throw new WebSocketException("Connection closed");
386 }
387 List<int> data;
388 int opcode;
389 if (message != null) {
390 if (message is String) {
391 opcode = _WebSocketOpcode.TEXT;
392 data = _StringEncoders.encoder(Encoding.UTF_8).encodeString(message);
393 } else {
394 if (message is !List<int>) {
395 throw new IllegalArgumentException(message);
396 }
397 opcode = _WebSocketOpcode.BINARY;
398 data = message;
399 }
400 } else {
401 opcode = _WebSocketOpcode.TEXT;
402 }
403 _sendFrame(opcode, data);
404 }
405
406 close([int status, String reason]) {
407 if (_closeSent) return;
408 List<int> data;
409 if (status != null) {
410 data = new List<int>();
411 data.add((status >> 8) & 0xFF);
412 data.add(status & 0xFF);
413 if (reason != null) {
414 data.addAll(
415 _StringEncoders.encoder(Encoding.UTF_8).encodeString(reason));
416 }
417 }
418 _sendFrame(_WebSocketOpcode.CLOSE, data);
419
420 if (_closeReceived) {
421 // Close the socket when the close framehas been sent - if it
Mads Ager (google) 2012/04/25 10:41:55 framehas -> frame has
Søren Gjesse 2012/04/25 13:57:14 Done.
422 // does not take too long.
423 _socket.outputStream.onNoPendingWrites = () {
424 if (_closeTimer != null) _closeTimer.cancel();
425 _socket.close();
426 };
427 _closeTimer = new Timer(5000, (t) {
428 _socket.close();
429 });
430 } else {
431 // Half close the socket and expect a close frame in response
432 // before closing the socket. If a close frame does not arrive
433 // within a reasonable amount of time just close the socket.
434 _socket.close(true);
435 _closeTimer = new Timer(5000, (t) {
436 _socket.close();
437 });
438 }
439 _closeSent = true;
440 }
441
442 _onWebSocketMessageStart(int type) {
443 _currentMessageType = type;
444 if (_currentMessageType == _WebSocketMessageType.TEXT) {
445 _decoder = _StringDecoders.decoder(Encoding.UTF_8);
446 } else {
447 _outputStream = new ListOutputStream();
448 }
449 }
450
451 _onWebSocketMessageData(List<int> buffer, int offset, int count) {
452 if (_currentMessageType == _WebSocketMessageType.TEXT) {
453 _decoder.write(buffer.getRange(offset, count));
454 } else {
455 _outputStream.write(buffer.getRange(offset, count));
456 }
457 }
458
459 _onWebSocketMessageEnd() {
460 if (_onMessage != null) {
461 if (_currentMessageType == _WebSocketMessageType.TEXT) {
462 _onMessage(_decoder.decoded);
463 } else {
464 _onMessage(_outputStream.contents());
465 }
466 }
467 _decoder = null;
468 _outputStream = null;
469 }
470
471 _onWebSocketClosed(int status, String reason) {
472 _closeReceived = true;
473 if (_onClosed != null) _onClosed(status, reason);
474 if (_closeSent) {
475 // Got close frame in response to close frame. Now close the socket.
476 if (_closeTimer != null) _closeTimer.cancel();
477 _socket.close();
478 } else {
479 close(status);
480 }
481 }
482
483 _onWebSocketError(e) {
484 if (_onError != null) _onError(e);
485 _socket.close();
486 }
487
488 _sendFrame(int opcode, List<int> data) {
489 bool mask = false; // Masking not implemented for server.
490 int dataLength = data == null ? 0 : data.length;
491 // Determine the header size.
492 int headerSize = (mask) ? 6 : 2;
493 if (dataLength > 65535) {
494 headerSize += 4;
495 } else if (dataLength > 126) {
496 headerSize += 2;
497 }
498 List<int> header = new List<int>(headerSize);
499 int index = 0;
500 // Set FIN and opcode.
501 header[index++] = 0x80 | opcode;
502 // Determine size and position of length field.
503 int lengthBytes = 1;
504 int firstLengthByte = 1;
505 if (dataLength > 65535) {
506 header[index++] = 127;
507 lengthBytes = 8;
508 } else if (dataLength > 126) {
509 header[index++] = 126;
510 lengthBytes = 2;
511 }
512 // Write the length in network byte order into the header.
513 for (int i = 0; i < lengthBytes; i++) {
514 header[index++] = dataLength >> (((lengthBytes - 1) - i) * 8) & 0xFF;
515 }
516 assert(index == headerSize);
517 _socket.outputStream.write(header);
518 if (data != null) {
519 _socket.outputStream.write(data);
520 }
521 }
522
523 Socket _socket;
524 Timer _closeTimer;
525
526 Function _onMessage;
527 Function _onClosed;
528 Function _onError;
529
530 int _currentMessageType = _WebSocketMessageType.NONE;
531 _StringDecoder _decoder;
532 ListOutputStream _outputStream;
533 bool _closeReceived = false;
534 bool _closeSent = false;
535 }
536
537
538 class _WebSocketHandler implements WebSocketHandler {
539 void onRequest(HttpRequest request, HttpResponse response) {
540 // Check that this is a web socket upgrade.
541 if (!_isWebSocketUpgrade(request)) {
542 response.statusCode = HttpStatus.BAD_REQUEST;
543 return;
544 }
545
546 // Send the upgrade response.
547 response.statusCode = HttpStatus.SWITCHING_PROTOCOLS;
548 response.headers.add(HttpHeaders.CONNECTION, "Upgrade");
549 response.headers.add(HttpHeaders.UPGRADE, "websocket");
550 String x = request.headers.value("Sec-WebSocket-Key");
551 String y = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
552 String z = _Base64._encode(_Sha1._hash("$x$y".charCodes()));
553 response.headers.add("Sec-WebSocket-Accept", z);
554 response.contentLength = 0;
555
556 // Upgrade the connection and get the underlying socket.
557 Socket socket = response.protocolUpgrade();
558 WebSocketConnection conn = new _WebSocketConnection(socket);
559 if (_onConnection != null) _onConnection(conn);
560 }
561
562 void set onConnection(callback(WebSocketConnection connection)) {
563 _onConnection = callback;
564 }
565
566 bool _isWebSocketUpgrade(HttpRequest request) {
567 if (request.headers[HttpHeaders.CONNECTION] == null) {
568 return false;
569 }
570 bool isUpgrade = false;
571 request.headers[HttpHeaders.CONNECTION].forEach((String value) {
572 if (value.toLowerCase() == "upgrade") isUpgrade = true;
573 });
574 if (!isUpgrade) return false;
575 String upgrade = request.headers.value(HttpHeaders.UPGRADE);
576 if (upgrade == null || upgrade.toLowerCase() != "websocket") {
577 return false;
578 }
579 String version = request.headers.value("Sec-WebSocket-Version");
580 if (version == null || version != "13") {
581 return false;
582 }
583 String key = request.headers.value("Sec-WebSocket-Key");
584 if (key == null) {
585 return false;
586 }
587 return true;
588 }
589
590 Function _onConnection;
591 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698