Chromium Code Reviews| Index: runtime/bin/websocket_impl.dart |
| diff --git a/runtime/bin/websocket_impl.dart b/runtime/bin/websocket_impl.dart |
| index 29aba714cb049739ac1110590d5574d2bd9b0478..3e555bde94df47c9e9ffcf821535306bd5fad1cc 100644 |
| --- a/runtime/bin/websocket_impl.dart |
| +++ b/runtime/bin/websocket_impl.dart |
| @@ -2,6 +2,8 @@ |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| +final String _webSocketGUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; |
| + |
| class _WebSocketMessageType { |
| static final int NONE = 0; |
| static final int BINARY = 1; |
| @@ -252,7 +254,7 @@ class _WebSocketProtocolProcessor { |
| */ |
| void closed() { |
| if (_state == START || _state == CLOSED || _state == FAILURE) return; |
| - _reportError(new WebSocketException("Protocol error")); |
| + _reportError(new WebSocketException("Protocol error $_state")); |
| _state = CLOSED; |
| } |
| @@ -277,6 +279,7 @@ class _WebSocketProtocolProcessor { |
| if (_remainingPayloadBytes == 0) { |
| if (_currentMessageType ==_WebSocketMessageType.CLOSE) { |
| if (onClosed != null) onClosed(null, null); |
| + _state = CLOSED; |
| } else { |
| _frameEnd(); |
| } |
| @@ -342,15 +345,19 @@ class _WebSocketProtocolProcessor { |
| } |
| -class _WebSocketConnection implements WebSocketConnection { |
| - _WebSocketConnection(Socket this._socket) { |
| +class _WebSocketConnectionBase { |
| + void _socketReady(Socket socket, List<int> data) { |
| + assert(_socket == null); |
| + _socket = socket; |
| _WebSocketProtocolProcessor processor = new _WebSocketProtocolProcessor(); |
| processor.onMessageStart = _onWebSocketMessageStart; |
| processor.onMessageData = _onWebSocketMessageData; |
| processor.onMessageEnd = _onWebSocketMessageEnd; |
| processor.onClosed = _onWebSocketClosed; |
| processor.onError = _onWebSocketError; |
| - |
| + if (data != null) { |
| + processor.update(data, 0, data.length); |
| + } |
| _socket.onData = () { |
| int available = _socket.available(); |
| List<int> data = new List<int>(available); |
| @@ -364,14 +371,12 @@ class _WebSocketConnection implements WebSocketConnection { |
| // that as an error. |
| if (_closeTimer != null) _closeTimer.cancel(); |
| } else { |
| - if (_onError != null) { |
| - _onError(new WebSocketException("Unexpected close")); |
| - } |
| + _reportError(new WebSocketException("Unexpected close")); |
| } |
| _socket.close(); |
| }; |
| _socket.onError = (e) { |
| - if (_onError != null) _onError(e); |
| + _reportError(e); |
| _socket.close(); |
| }; |
| } |
| @@ -428,6 +433,7 @@ class _WebSocketConnection implements WebSocketConnection { |
| if (_closeReceived) { |
| // Close the socket when the close frame has been sent - if it |
| // does not take too long. |
| + _socket.close(true); |
| _socket.outputStream.onNoPendingWrites = () { |
| if (_closeTimer != null) _closeTimer.cancel(); |
| _socket.close(); |
| @@ -489,7 +495,7 @@ class _WebSocketConnection implements WebSocketConnection { |
| } |
| _onWebSocketError(e) { |
| - if (_onError != null) _onError(e); |
| + _reportError(e); |
| _socket.close(); |
| } |
| @@ -528,6 +534,14 @@ class _WebSocketConnection implements WebSocketConnection { |
| } |
| } |
| + void _reportError(e) { |
| + if (_onError != null) { |
| + _onError(e); |
| + } else { |
| + throw e; |
| + } |
| + } |
| + |
| Socket _socket; |
| Timer _closeTimer; |
| @@ -543,11 +557,19 @@ class _WebSocketConnection implements WebSocketConnection { |
| } |
| +class _WebSocketConnection extends _WebSocketConnectionBase implements WebSocketConnection { |
|
Mads Ager (google)
2012/05/02 08:18:17
Long line.
What is the reason to have both _WebSo
Søren Gjesse
2012/05/02 10:22:45
The WebSocketConnectionBase is also used as a base
|
| + _WebSocketConnection(Socket socket, List<int> data) { |
| + _socketReady(socket, data); |
| + } |
| +} |
| + |
| + |
| class _WebSocketHandler implements WebSocketHandler { |
| void onRequest(HttpRequest request, HttpResponse response) { |
| // Check that this is a web socket upgrade. |
| if (!_isWebSocketUpgrade(request)) { |
| response.statusCode = HttpStatus.BAD_REQUEST; |
| + response.outputStream.close(); |
| return; |
| } |
| @@ -555,15 +577,16 @@ class _WebSocketHandler implements WebSocketHandler { |
| response.statusCode = HttpStatus.SWITCHING_PROTOCOLS; |
| response.headers.add(HttpHeaders.CONNECTION, "Upgrade"); |
| response.headers.add(HttpHeaders.UPGRADE, "websocket"); |
| - String x = request.headers.value("Sec-WebSocket-Key"); |
| - String y = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; |
| - String z = _Base64._encode(_Sha1._hash("$x$y".charCodes())); |
| - response.headers.add("Sec-WebSocket-Accept", z); |
| + String key = request.headers.value("Sec-WebSocket-Key"); |
| + String accept = |
| + _Base64._encode(_Sha1._hash("$key$_webSocketGUID".charCodes())); |
| + response.headers.add("Sec-WebSocket-Accept", accept); |
| response.contentLength = 0; |
| // Upgrade the connection and get the underlying socket. |
| - Socket socket = response.detachSocket(); |
| - WebSocketConnection conn = new _WebSocketConnection(socket); |
| + DetachedSocket detached = response.detachSocket(); |
| + WebSocketConnection conn = |
| + new _WebSocketConnection(detached.socket, detached.unparsedData); |
| if (_onOpen != null) _onOpen(conn); |
| } |
| @@ -572,6 +595,9 @@ class _WebSocketHandler implements WebSocketHandler { |
| } |
| bool _isWebSocketUpgrade(HttpRequest request) { |
| + if (request.method != "GET") { |
| + return false; |
| + } |
| if (request.headers[HttpHeaders.CONNECTION] == null) { |
| return false; |
| } |
| @@ -597,3 +623,112 @@ class _WebSocketHandler implements WebSocketHandler { |
| Function _onOpen; |
| } |
| + |
| + |
| +class _WebSocketClientConnection extends _WebSocketConnectionBase implements WebSocketClientConnection { |
|
Mads Ager (google)
2012/05/02 08:18:17
Long line.
Søren Gjesse
2012/05/02 10:22:45
Done.
|
| + _WebSocketClientConnection(HttpClientConnection this._conn, |
| + [List<String> protocols]) { |
| + _conn.onRequest = _onHttpClientRequest; |
| + _conn.onResponse = _onHttpClientResponse; |
| + _conn.onError = (e) => _reportError(e); |
| + } |
| + |
| + void set onRequest(void callback(HttpClientRequest request)) { |
| + _onRequest = callback; |
| + } |
| + |
| + void set onOpen(void callback()) { |
| + _onOpen = callback; |
| + } |
| + |
| + void set onNoUpgrade(void callback(HttpClientResponse request)) { |
| + _onNoUpgrade = callback; |
| + } |
| + |
| + void _onHttpClientRequest(HttpClientRequest request) { |
| + if (_onRequest != null) { |
| + _onRequest(request); |
| + } |
| + // Setup the initial handshake. |
| + request.headers.add(HttpHeaders.CONNECTION, "upgrade"); |
| + request.headers.set(HttpHeaders.UPGRADE, "websocket"); |
| + request.headers.set("Sec-WebSocket-Key", _generateNonce()); |
|
Anders Johnsen
2012/05/02 07:51:57
Even though it's not the case, this looks like the
Mads Ager (google)
2012/05/02 08:18:17
Or have a getter that calculates on first access a
Søren Gjesse
2012/05/02 10:22:45
Done.
Søren Gjesse
2012/05/02 10:22:45
Changed as Anders suggested.
|
| + request.headers.set("Sec-WebSocket-Version", "13"); |
| + request.contentLength = 0; |
| + request.outputStream.close(); |
| + } |
| + |
| + void _onHttpClientResponse(HttpClientResponse response) { |
| + if (response.statusCode != HttpStatus.SWITCHING_PROTOCOLS) { |
| + if (_onNoUpgrade != null) { |
| + _onNoUpgrade(response); |
| + } else { |
| + _conn.detachSocket().socket.close(); |
| + throw new WebSocketException("Protocol upgrade refused"); |
| + } |
| + return; |
| + } |
| + |
| + if (!_isWebSocketUpgrade(response)) { |
| + _conn.detachSocket().socket.close(); |
| + throw new WebSocketException("Protocol upgrade failed"); |
| + return; |
| + } |
| + |
| + // Connection upgrade successful. |
| + DetachedSocket detached = _conn.detachSocket(); |
| + _socketReady(detached.socket, detached.unparsedData); |
|
Mads Ager (google)
2012/05/02 08:18:17
Instead of picking out the socket and the unparsed
Søren Gjesse
2012/05/02 10:22:45
Done.
|
| + if (_onOpen != null) _onOpen(); |
| + } |
| + |
| + String _generateNonce() { |
| + void intToBigEndianBytes(int value, List<int> bytes, int offset) { |
| + bytes[offset] = (value >> 24) & 0xFF; |
| + bytes[offset + 1] = (value >> 16) & 0xFF; |
| + bytes[offset + 2] = (value >> 8) & 0xFF; |
| + bytes[offset + 3] = value & 0xFF; |
| + } |
| + |
| + // Generate 16 random bytes. |
| + List<int> nonce = new List<int>(16); |
| + for (int i = 0; i < 4; i++) { |
| + int r = (Math.random() * 0x100000000).toInt(); |
| + intToBigEndianBytes(r, nonce, i * 4); |
| + } |
| + _nonce = _Base64._encode(nonce); |
| + return _nonce; |
| + } |
| + |
| + bool _isWebSocketUpgrade(HttpResponse response) { |
| + if (response.headers[HttpHeaders.CONNECTION] == null) { |
| + return false; |
| + } |
| + bool isUpgrade = false; |
| + response.headers[HttpHeaders.CONNECTION].forEach((String value) { |
| + if (value.toLowerCase() == "upgrade") isUpgrade = true; |
| + }); |
| + if (!isUpgrade) return false; |
| + String upgrade = response.headers.value(HttpHeaders.UPGRADE); |
| + if (upgrade == null || upgrade.toLowerCase() != "websocket") { |
| + return false; |
| + } |
| + String accept = response.headers.value("Sec-WebSocket-Accept"); |
| + if (accept == null) { |
| + return false; |
| + } |
| + List<int> expectedAccept = |
| + _Sha1._hash("$_nonce$_webSocketGUID".charCodes()); |
| + List<int> receivedAccept = _Base64._decode(accept); |
| + if (expectedAccept.length != receivedAccept.length) return false; |
| + for (int i = 0; i < expectedAccept.length; i++) { |
| + if (expectedAccept[i] != receivedAccept[i]) return false; |
| + } |
| + return true; |
| + } |
| + |
| + Function _onRequest; |
| + Function _onOpen; |
| + Function _onNoUpgrade; |
| + HttpClientConnection _conn; |
| + String _nonce; |
| +} |