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

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: Addressed review comments from ajohnsen@ and ager@ 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.
114 throw UnsupportedOperationException("Web socket PING");
115 break;
116
117 case _WebSocketOpcode.PONG:
118 // TODO(sgjesse): Handle pong.
119 throw UnsupportedOperationException("Web socket PONG");
120 break;
121
122 default:
123 throw new WebSocketException("Protocol error");
124 break;
125 }
126 _state = LEN_FIRST;
127 break;
128
129 case LEN_FIRST:
130 _masked = (byte & 0x80) != 0;
131 _len = byte & 0x7F;
132 if (_len < 126) {
133 _lengthDone();
134 } else if (_len == 126) {
135 _len = 0;
136 _remainingLenBytes = 2;
137 } else if (_len == 127) {
138 _len = 0;
139 _remainingLenBytes = 8;
140 }
141 break;
142
143 case LEN_REST:
144 _len = _len << 8 | byte;
145 _remainingLenBytes--;
146 if (_remainingLenBytes == 0) {
147 _lengthDone();
148 }
149 break;
150
151 case MASK:
152 _maskingKey = _maskingKey << 8 | byte;
153 _remainingMaskingKeyBytes--;
154 if (_remainingMaskingKeyBytes == 0) {
155 _maskDone();
156 }
157 break;
158
159 case PAYLOAD:
160 // The payload is not handled one byte at a time but in blocks.
161 int payload;
162 if (lastIndex - index >= _remainingPayloadBytes) {
163 payload = lastIndex - index;
164 } else {
165 payload = _remainingPayloadBytes;
166 }
167 // Unmask payload if masked.
168 if (_masked) {
169 for (int i = 0; i < payload; i++) {
170 int maskingByte =
171 ((_maskingKey >> ((3 - _unmaskingIndex) * 8)) & 0xFF);
172 buffer[index + i] = buffer[index + i] ^ maskingByte;
173 _unmaskingIndex = (_unmaskingIndex + 1) % 4;
174 }
175 }
176
177 switch (_currentMessageType) {
178 case _WebSocketMessageType.NONE:
179 throw new WebSocketException("Protocol error");
180 break;
181
182 case _WebSocketMessageType.TEXT:
183 case _WebSocketMessageType.BINARY:
184 if (onMessageData != null) {
185 onMessageData(buffer, index, payload);
186 }
187 _remainingPayloadBytes -= payload;
188 index += payload;
189 if (_fin) {
190 _messageEnd();
191 }
192 break;
193
194 case _WebSocketMessageType.CLOSE:
195 // Allocate a buffer for holding the close payload if any.
196 if (_closePayload == null) {
197 _closePayload = new List<int>();
198 }
199 _closePayload.addAll(buffer.getRange(index, payload));
200 _remainingPayloadBytes -= payload;
201 index += payload;
202 if (_fin) {
203 if (_remainingPayloadBytes != 0) {
204 throw new WebSocketException("Protocol error");
205 }
206 int status;
207 String reason;
208 if (_closePayload.length > 0) {
209 if (_closePayload.length == 1) {
210 throw new WebSocketException("Protocol error");
211 }
212 status = _closePayload[0] << 8 | _closePayload[1];
213 if (_closePayload.length > 2) {
214 var decoder = _StringDecoders.decoder(Encoding.UTF_8);
215 decoder.write(_closePayload.getRange(
216 2, _closePayload.length - 2));
217 reason = decoder.decoded;
218 }
219 }
220 if (onClosed != null) onClosed(status, reason);
221 _currentMessageType = _WebSocketMessageType.NONE;
222 _state = CLOSED;
223 }
224 break;
225
226 default:
227 throw new WebSocketException("Protocol error");
228 break;
229 }
230
231 // Hack - as we always do index++ below.
232 index--;
233 break;
234
235 default:
236 throw new WebSocketException("Protocol error");
237 break;
238 }
239
240 // Move to the next byte.
241 index++;
242 }
243 } catch (var e) {
244 _reportError(e);
245 }
246 }
247
248 /**
249 * Indicate that the underlying communication channel has been closed.
250 */
251 void closed() {
252 if (_state == START || _state == CLOSED || _state == FAILURE) return;
253 _reportError(new WebSocketException("Protocol error"));
254 _state = CLOSED;
255 }
256
257 void _lengthDone() {
258 if (_masked) {
259 _state = MASK;
260 _remainingMaskingKeyBytes = 4;
261 } else {
262 _remainingPayloadBytes = _len;
263 _startPayload();
264 }
265 }
266
267 void _maskDone() {
268 _remainingPayloadBytes = _len;
269 _startPayload();
270 }
271
272 void _startPayload() {
273 // Check whether there is any payload. If not indicate empty message or
274 if (_remainingPayloadBytes == 0) {
275 if (_currentMessageType ==_WebSocketMessageType.CLOSE) {
276 if (onClosed != null) onClosed(null, null);
277 } else {
278 _messageEnd();
279 }
280 } else {
281 _state = PAYLOAD;
282 }
283 }
284
285 void _messageEnd() {
286 if (_remainingPayloadBytes != 0) {
287 throw new WebSocketException("Protocol error");
288 }
289 if (onMessageEnd != null) onMessageEnd();
290 _currentMessageType = _WebSocketMessageType.NONE;
291 _reset();
292 }
293
294 void _reset() {
295 _state = START;
296 _fin = null;
297 _opcode = null;
298 _len = null;
299 _masked = null;
300 _maskingKey = 0;
301 _remainingLenBytes = null;
302 _remainingMaskingKeyBytes = null;
303 _remainingPayloadBytes = null;
304 _unmaskingIndex = 0;
305 }
306
307 void _reportError(e) {
308 // Report the error through the error callback if any. Otherwise
309 // throw the error.
310 if (onError != null) {
311 onError(e);
312 _state = _State.FAILURE;
313 } else {
314 throw e;
315 }
316 }
317
318 int _state;
319 bool _fin;
320 int _opcode;
321 int _len;
322 bool _masked;
323 int _maskingKey;
324 int _remainingLenBytes;
325 int _remainingMaskingKeyBytes;
326 int _remainingPayloadBytes;
327 int _unmaskingIndex;
328
329 int _currentMessageType;
330 List<int> _closePayload;
331
332 Function onMessageStart;
333 Function onMessageData;
334 Function onMessageEnd;
335 Function onClosed;
336 Function onError;
337 }
338
339
340 class _WebSocketConnection implements WebSocketConnection {
341 _WebSocketConnection(Socket this._socket) {
342 _WebSocketProtocolProcessor processor = new _WebSocketProtocolProcessor();
343 processor.onMessageStart = _onWebSocketMessageStart;
344 processor.onMessageData = _onWebSocketMessageData;
345 processor.onMessageEnd = _onWebSocketMessageEnd;
346 processor.onClosed = _onWebSocketClosed;
347 processor.onError = _onWebSocketError;
348
349 _socket.onData = () {
350 int available = _socket.available();
351 List<int> data = new List<int>(available);
352 int read = _socket.readList(data, 0, available);
353 processor.update(data, 0, read);
354 };
355 _socket.onClosed = () {
356 processor.closed();
357 if (_closeSent) {
358 // Got socket close in response to close frame. Don't treat
359 // that as an error.
360 if (_closeTimer != null) _closeTimer.cancel();
361 } else {
362 if (_onError != null) {
363 _onError(new WebSocketException("Unexpected close"));
364 }
365 }
366 _socket.close();
367 };
368 _socket.onError = (e) {
369 if (_onError != null) _onError(e);
370 _socket.close();
371 };
372 }
373
374 void set onMessage(void callback(Object message)) {
375 _onMessage = callback;
376 }
377
378 void set onClosed(void callback(int status, String reason)) {
379 _onClosed = callback;
380 }
381
382 void set onError(void callback(e)) {
383 _onError = callback;
384 }
385
386 send(Object message) {
387 if (_closeSent) {
388 throw new WebSocketException("Connection closed");
389 }
390 List<int> data;
391 int opcode;
392 if (message != null) {
393 if (message is String) {
394 opcode = _WebSocketOpcode.TEXT;
395 data = _StringEncoders.encoder(Encoding.UTF_8).encodeString(message);
396 } else {
397 if (message is !List<int>) {
398 throw new IllegalArgumentException(message);
399 }
400 opcode = _WebSocketOpcode.BINARY;
401 data = message;
402 }
403 } else {
404 opcode = _WebSocketOpcode.TEXT;
405 }
406 _sendFrame(opcode, data);
407 }
408
409 close([int status, String reason]) {
410 if (_closeSent) return;
411 List<int> data;
412 if (status != null) {
413 data = new List<int>();
414 data.add((status >> 8) & 0xFF);
415 data.add(status & 0xFF);
416 if (reason != null) {
417 data.addAll(
418 _StringEncoders.encoder(Encoding.UTF_8).encodeString(reason));
419 }
420 }
421 _sendFrame(_WebSocketOpcode.CLOSE, data);
422
423 if (_closeReceived) {
424 // Close the socket when the close frame has been sent - if it
425 // does not take too long.
426 _socket.outputStream.onNoPendingWrites = () {
427 if (_closeTimer != null) _closeTimer.cancel();
428 _socket.close();
429 };
430 _closeTimer = new Timer(5000, (t) {
431 _socket.close();
432 });
433 } else {
434 // Half close the socket and expect a close frame in response
435 // before closing the socket. If a close frame does not arrive
436 // within a reasonable amount of time just close the socket.
437 _socket.close(true);
438 _closeTimer = new Timer(5000, (t) {
439 _socket.close();
440 });
441 }
442 _closeSent = true;
443 }
444
445 _onWebSocketMessageStart(int type) {
446 _currentMessageType = type;
447 if (_currentMessageType == _WebSocketMessageType.TEXT) {
448 _decoder = _StringDecoders.decoder(Encoding.UTF_8);
449 } else {
450 _outputStream = new ListOutputStream();
451 }
452 }
453
454 _onWebSocketMessageData(List<int> buffer, int offset, int count) {
455 if (_currentMessageType == _WebSocketMessageType.TEXT) {
456 _decoder.write(buffer.getRange(offset, count));
457 } else {
458 _outputStream.write(buffer.getRange(offset, count));
459 }
460 }
461
462 _onWebSocketMessageEnd() {
463 if (_onMessage != null) {
464 if (_currentMessageType == _WebSocketMessageType.TEXT) {
465 _onMessage(_decoder.decoded);
466 } else {
467 _onMessage(_outputStream.contents());
468 }
469 }
470 _decoder = null;
471 _outputStream = null;
472 }
473
474 _onWebSocketClosed(int status, String reason) {
475 _closeReceived = true;
476 if (_onClosed != null) _onClosed(status, reason);
477 if (_closeSent) {
478 // Got close frame in response to close frame. Now close the socket.
479 if (_closeTimer != null) _closeTimer.cancel();
480 _socket.close();
481 } else {
482 close(status);
483 }
484 }
485
486 _onWebSocketError(e) {
487 if (_onError != null) _onError(e);
488 _socket.close();
489 }
490
491 _sendFrame(int opcode, List<int> data) {
492 bool mask = false; // Masking not implemented for server.
493 int dataLength = data == null ? 0 : data.length;
494 // Determine the header size.
495 int headerSize = (mask) ? 6 : 2;
496 if (dataLength > 65535) {
497 headerSize += 4;
498 } else if (dataLength > 126) {
499 headerSize += 2;
500 }
501 List<int> header = new List<int>(headerSize);
502 int index = 0;
503 // Set FIN and opcode.
504 header[index++] = 0x80 | opcode;
505 // Determine size and position of length field.
506 int lengthBytes = 1;
507 int firstLengthByte = 1;
508 if (dataLength > 65535) {
509 header[index++] = 127;
510 lengthBytes = 8;
511 } else if (dataLength > 126) {
512 header[index++] = 126;
513 lengthBytes = 2;
514 }
515 // Write the length in network byte order into the header.
516 for (int i = 0; i < lengthBytes; i++) {
517 header[index++] = dataLength >> (((lengthBytes - 1) - i) * 8) & 0xFF;
518 }
519 assert(index == headerSize);
520 _socket.outputStream.write(header);
521 if (data != null) {
522 _socket.outputStream.write(data);
523 }
524 }
525
526 Socket _socket;
527 Timer _closeTimer;
528
529 Function _onMessage;
530 Function _onClosed;
531 Function _onError;
532
533 int _currentMessageType = _WebSocketMessageType.NONE;
534 _StringDecoder _decoder;
535 ListOutputStream _outputStream;
536 bool _closeReceived = false;
537 bool _closeSent = false;
538 }
539
540
541 class _WebSocketHandler implements WebSocketHandler {
542 void onRequest(HttpRequest request, HttpResponse response) {
543 // Check that this is a web socket upgrade.
544 if (!_isWebSocketUpgrade(request)) {
545 response.statusCode = HttpStatus.BAD_REQUEST;
546 return;
547 }
548
549 // Send the upgrade response.
550 response.statusCode = HttpStatus.SWITCHING_PROTOCOLS;
551 response.headers.add(HttpHeaders.CONNECTION, "Upgrade");
552 response.headers.add(HttpHeaders.UPGRADE, "websocket");
553 String x = request.headers.value("Sec-WebSocket-Key");
554 String y = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
555 String z = _Base64._encode(_Sha1._hash("$x$y".charCodes()));
556 response.headers.add("Sec-WebSocket-Accept", z);
557 response.contentLength = 0;
558
559 // Upgrade the connection and get the underlying socket.
560 Socket socket = response.detachSocket();
561 WebSocketConnection conn = new _WebSocketConnection(socket);
562 if (_onConnection != null) _onConnection(conn);
563 }
564
565 void set onConnection(callback(WebSocketConnection connection)) {
566 _onConnection = callback;
567 }
568
569 bool _isWebSocketUpgrade(HttpRequest request) {
570 if (request.headers[HttpHeaders.CONNECTION] == null) {
571 return false;
572 }
573 bool isUpgrade = false;
574 request.headers[HttpHeaders.CONNECTION].forEach((String value) {
575 if (value.toLowerCase() == "upgrade") isUpgrade = true;
576 });
577 if (!isUpgrade) return false;
578 String upgrade = request.headers.value(HttpHeaders.UPGRADE);
579 if (upgrade == null || upgrade.toLowerCase() != "websocket") {
580 return false;
581 }
582 String version = request.headers.value("Sec-WebSocket-Version");
583 if (version == null || version != "13") {
584 return false;
585 }
586 String key = request.headers.value("Sec-WebSocket-Key");
587 if (key == null) {
588 return false;
589 }
590 return true;
591 }
592
593 Function _onConnection;
594 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698