Index: runtime/bin/websocket_impl.dart |
diff --git a/runtime/bin/websocket_impl.dart b/runtime/bin/websocket_impl.dart |
index 29aba714cb049739ac1110590d5574d2bd9b0478..f85c2564c62119c524f73817c94a185867b7c437 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(DetachedSocket detached) { |
+ assert(detached.socket != null); |
+ _socket = detached.socket; |
_WebSocketProtocolProcessor processor = new _WebSocketProtocolProcessor(); |
processor.onMessageStart = _onWebSocketMessageStart; |
processor.onMessageData = _onWebSocketMessageData; |
processor.onMessageEnd = _onWebSocketMessageEnd; |
processor.onClosed = _onWebSocketClosed; |
processor.onError = _onWebSocketError; |
- |
+ if (detached.unparsedData != null) { |
+ processor.update(detached.unparsedData, 0, detached.unparsedData.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(); |
}; |
} |
@@ -388,7 +393,7 @@ class _WebSocketConnection implements WebSocketConnection { |
_onError = callback; |
} |
- send(Object message) { |
+ send(message) { |
if (_closeSent) { |
throw new WebSocketException("Connection closed"); |
} |
@@ -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,20 @@ class _WebSocketConnection implements WebSocketConnection { |
} |
+class _WebSocketConnection |
+ extends _WebSocketConnectionBase implements WebSocketConnection { |
+ _WebSocketConnection(DetachedSocket detached) { |
+ _socketReady(detached); |
+ } |
+} |
+ |
+ |
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 +578,15 @@ 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); |
+ WebSocketConnection conn = |
+ new _WebSocketConnection(response.detachSocket()); |
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,113 @@ class _WebSocketHandler implements WebSocketHandler { |
Function _onOpen; |
} |
+ |
+ |
+class _WebSocketClientConnection |
+ extends _WebSocketConnectionBase implements WebSocketClientConnection { |
+ _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. |
+ _generateNonce(); |
+ request.headers.add(HttpHeaders.CONNECTION, "upgrade"); |
+ request.headers.set(HttpHeaders.UPGRADE, "websocket"); |
+ request.headers.set("Sec-WebSocket-Key", _nonce); |
+ 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. |
+ _socketReady(_conn.detachSocket()); |
+ if (_onOpen != null) _onOpen(); |
+ } |
+ |
+ void _generateNonce() { |
+ assert(_nonce == null); |
+ 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); |
+ } |
+ |
+ bool _isWebSocketUpgrade(HttpClientResponse 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; |
+} |