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

Unified Diff: sdk/lib/io/websocket_impl.dart

Issue 12387085: Improve web-socket implementation by using making _WebSocketProtocolProcessor a StreamTransformer. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 7 years, 10 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « sdk/lib/io/http_impl.dart ('k') | tests/standalone/io/web_socket_protocol_processor_test.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: sdk/lib/io/websocket_impl.dart
diff --git a/sdk/lib/io/websocket_impl.dart b/sdk/lib/io/websocket_impl.dart
index a849ae1850c41afd52f7a7d72cfbbfe1f523e0e0..b7ca9a066c4832ad68d853317655f2aa801d3a01 100644
--- a/sdk/lib/io/websocket_impl.dart
+++ b/sdk/lib/io/websocket_impl.dart
@@ -33,18 +33,15 @@ class _WebSocketOpcode {
}
/**
- * The web socket protocol processor handles the protocol byte stream
- * which is supplied through the [:update:] and [:closed:]
- * methods. As the protocol is processed the following callbacks are
- * called:
- *
- * [:onMessageStart:]
- * [:onMessageData:]
- * [:onMessageEnd:]
- * [:onClosed:]
+ * The web socket protocol transformer handles the protocol byte stream
+ * which is supplied through the [:handleData:]. As the protocol is processed,
+ * it'll output frame data as either a List<int> or String.
*
+ * Important infomation about usage: Be sure you use unsubscribeOnError, so the
+ * socket will be closed when the processer encounter an error. Not using it
+ * will lead to undefined behaviour.
*/
-class _WebSocketProtocolProcessor {
+class _WebSocketProtocolTransformer extends StreamEventTransformer {
static const int START = 0;
static const int LEN_FIRST = 1;
static const int LEN_REST = 2;
@@ -53,7 +50,7 @@ class _WebSocketProtocolProcessor {
static const int CLOSED = 5;
static const int FAILURE = 6;
- _WebSocketProtocolProcessor() {
+ _WebSocketProtocolTransformer() {
_prepareForNextFrame();
_currentMessageType = _WebSocketMessageType.NONE;
}
@@ -61,9 +58,10 @@ class _WebSocketProtocolProcessor {
/**
* Process data received from the underlying communication channel.
*/
- void update(List<int> buffer, int offset, int count) {
- int index = offset;
- int lastIndex = offset + count;
+ void handleData(List<int> buffer, StreamSink sink) {
+ int count = buffer.length;
+ int index = 0;
+ int lastIndex = count;
try {
if (_state == CLOSED) {
throw new WebSocketException("Data on closed connection");
@@ -89,9 +87,7 @@ class _WebSocketProtocolProcessor {
throw new WebSocketException("Protocol error");
}
_currentMessageType = _WebSocketMessageType.TEXT;
- if (onMessageStart != null) {
- onMessageStart(_WebSocketMessageType.TEXT);
- }
+ _buffer = new StringBuffer();
break;
case _WebSocketOpcode.BINARY:
@@ -99,9 +95,7 @@ class _WebSocketProtocolProcessor {
throw new WebSocketException("Protocol error");
}
_currentMessageType = _WebSocketMessageType.BINARY;
- if (onMessageStart != null) {
- onMessageStart(_WebSocketMessageType.BINARY);
- }
+ _buffer = new _BufferList();
break;
case _WebSocketOpcode.CLOSE:
@@ -124,7 +118,7 @@ class _WebSocketProtocolProcessor {
throw new WebSocketException("Protocol error");
}
if (_len < 126) {
- _lengthDone();
+ _lengthDone(sink);
} else if (_len == 126) {
_len = 0;
_remainingLenBytes = 2;
@@ -140,7 +134,7 @@ class _WebSocketProtocolProcessor {
_len = _len << 8 | byte;
_remainingLenBytes--;
if (_remainingLenBytes == 0) {
- _lengthDone();
+ _lengthDone(sink);
}
break;
@@ -148,7 +142,7 @@ class _WebSocketProtocolProcessor {
_maskingKey = _maskingKey << 8 | byte;
_remainingMaskingKeyBytes--;
if (_remainingMaskingKeyBytes == 0) {
- _maskDone();
+ _maskDone(sink);
}
break;
@@ -184,7 +178,7 @@ class _WebSocketProtocolProcessor {
}
if (_remainingPayloadBytes == 0) {
- _controlFrameEnd();
+ _controlFrameEnd(sink);
}
} else {
switch (_currentMessageType) {
@@ -192,13 +186,18 @@ class _WebSocketProtocolProcessor {
throw new WebSocketException("Protocol error");
case _WebSocketMessageType.TEXT:
- case _WebSocketMessageType.BINARY:
- if (onMessageData != null) {
- onMessageData(buffer, index, payload);
+ _buffer.add(_decodeString(buffer.getRange(index, payload)));
+ index += payload;
+ if (_remainingPayloadBytes == 0) {
+ _messageFrameEnd(sink);
}
+ break;
+
+ case _WebSocketMessageType.BINARY:
+ _buffer.add(buffer.getRange(index, payload));
index += payload;
if (_remainingPayloadBytes == 0) {
- _messageFrameEnd();
+ _messageFrameEnd(sink);
}
break;
@@ -216,99 +215,99 @@ class _WebSocketProtocolProcessor {
index++;
}
} catch (e) {
- if (onClosed != null) onClosed(WebSocketStatus.PROTOCOL_ERROR,
- "Protocol error");
_state = FAILURE;
+ sink.signalError(e);
}
}
- /**
- * Indicate that the underlying communication channel has been closed.
- */
- void closed() {
- if (_state == START || _state == CLOSED || _state == FAILURE) return;
- if (onClosed != null) onClosed(WebSocketStatus.ABNORMAL_CLOSURE,
- "Connection closed unexpectedly");
- _state = CLOSED;
- }
-
- void _lengthDone() {
+ void _lengthDone(StreamSink sink) {
if (_masked) {
_state = MASK;
_remainingMaskingKeyBytes = 4;
} else {
_remainingPayloadBytes = _len;
- _startPayload();
+ _startPayload(sink);
}
}
- void _maskDone() {
+ void _maskDone(StreamSink sink) {
_remainingPayloadBytes = _len;
- _startPayload();
+ _startPayload(sink);
}
- void _startPayload() {
+ void _startPayload(StreamSink sink) {
// If there is no actual payload perform perform callbacks without
// going through the PAYLOAD state.
if (_remainingPayloadBytes == 0) {
if (_isControlFrame()) {
switch (_opcode) {
case _WebSocketOpcode.CLOSE:
- if (onClosed != null) onClosed(1005, "");
_state = CLOSED;
+ sink.close();
break;
case _WebSocketOpcode.PING:
- if (onPing != null) onPing(null);
+ // TODO(ajohnsen): Handle ping.
break;
case _WebSocketOpcode.PONG:
- if (onPong != null) onPong(null);
+ // TODO(ajohnsen): Handle pong.
break;
}
_prepareForNextFrame();
} else {
- _messageFrameEnd();
+ _messageFrameEnd(sink);
}
} else {
_state = PAYLOAD;
}
}
- void _messageFrameEnd() {
+ void _messageFrameEnd(StreamSink sink) {
if (_fin) {
- if (onMessageEnd != null) onMessageEnd();
+ switch (_currentMessageType) {
+ case _WebSocketMessageType.TEXT:
+ sink.add(_buffer.toString());
+ break;
+ case _WebSocketMessageType.BINARY:
+ if (_buffer.length == 0) {
+ sink.add(const []);
+ } else {
+ sink.add(_buffer.readBytes(_buffer.length));
+ }
+ break;
+ }
+ _buffer = null;
_currentMessageType = _WebSocketMessageType.NONE;
}
_prepareForNextFrame();
}
- void _controlFrameEnd() {
+ void _controlFrameEnd(StreamSink sink) {
switch (_opcode) {
case _WebSocketOpcode.CLOSE:
- int status = WebSocketStatus.NO_STATUS_RECEIVED;
- String reason = "";
+ closeCode = WebSocketStatus.NO_STATUS_RECEIVED;
if (_controlPayload.length > 0) {
if (_controlPayload.length == 1) {
throw new WebSocketException("Protocol error");
}
- status = _controlPayload[0] << 8 | _controlPayload[1];
- if (status == WebSocketStatus.NO_STATUS_RECEIVED) {
+ closeCode = _controlPayload[0] << 8 | _controlPayload[1];
+ if (closeCode == WebSocketStatus.NO_STATUS_RECEIVED) {
throw new WebSocketException("Protocol error");
}
if (_controlPayload.length > 2) {
- reason = _decodeString(
+ closeReason = _decodeString(
_controlPayload.getRange(2, _controlPayload.length - 2));
}
}
- if (onClosed != null) onClosed(status, reason);
_state = CLOSED;
+ sink.close();
break;
case _WebSocketOpcode.PING:
- if (onPing != null) onPing(_controlPayload);
+ // TODO(ajohnsen): Handle ping.
break;
case _WebSocketOpcode.PONG:
- if (onPong != null) onPong(_controlPayload);
+ // TODO(ajohnsen): Handle pong.
break;
}
_prepareForNextFrame();
@@ -347,13 +346,10 @@ class _WebSocketProtocolProcessor {
int _currentMessageType;
List<int> _controlPayload;
+ var _buffer; // Either StringBuffer or _BufferList.
- Function onMessageStart;
- Function onMessageData;
- Function onMessageEnd;
- Function onPing;
- Function onPong;
- Function onClosed;
+ int closeCode = WebSocketStatus.NO_STATUS_RECEIVED;
+ String closeReason = "";
}
@@ -430,9 +426,6 @@ class _WebSocketTransformerImpl implements WebSocketTransformer {
class _WebSocketImpl extends Stream implements WebSocket {
final StreamController _controller = new StreamController();
- final _WebSocketProtocolProcessor _processor =
- new _WebSocketProtocolProcessor();
-
final Socket _socket;
int _readyState = WebSocket.CONNECTING;
bool _writeClosed = false;
@@ -514,61 +507,45 @@ class _WebSocketImpl extends Stream implements WebSocket {
_WebSocketImpl._fromSocket(Socket this._socket) {
_readyState = WebSocket.OPEN;
- int type;
- var data;
- _processor.onMessageStart = (int t) {
- type = t;
- if (type == _WebSocketMessageType.TEXT) {
- data = new StringBuffer();
- } else {
- data = [];
- }
- };
- _processor.onMessageData = (buffer, offset, count) {
- if (type == _WebSocketMessageType.TEXT) {
- data.add(_decodeString(buffer.getRange(offset, count)));
- } else {
- data.addAll(buffer.getRange(offset, count));
- }
- };
- _processor.onMessageEnd = () {
- if (type == _WebSocketMessageType.TEXT) {
- _controller.add(data.toString());
- } else {
- _controller.add(data);
- }
- };
- _processor.onClosed = (code, reason) {
- bool clean = true;
- if (_readyState == WebSocket.OPEN) {
- _readyState = WebSocket.CLOSING;
- if (code != WebSocketStatus.NO_STATUS_RECEIVED) {
- _close(code);
- } else {
- _close();
- clean = false;
- }
- _readyState = WebSocket.CLOSED;
- }
- if (_readyState == WebSocket.CLOSED) return;
- _closeCode = code;
- _closeReason = reason;
- _controller.close();
- };
-
- _socket.listen(
- (data) => _processor.update(data, 0, data.length),
- onDone: () => _processor.closed(),
- onError: (error) => _controller.signalError(error));
+ bool closed = false;
+ var transformer = new _WebSocketProtocolTransformer();
+ _socket.transform(transformer).listen(
+ (data) {
+ _controller.add(data);
+ },
+ onError: (error) {
+ if (closed) return;
+ closed = true;
+ _controller.signalError(error);
+ _controller.close();
+ },
+ onDone: () {
+ if (closed) return;
+ closed = true;
+ if (_readyState == WebSocket.OPEN) {
+ _readyState = WebSocket.CLOSING;
+ if (transformer.closeCode != WebSocketStatus.NO_STATUS_RECEIVED) {
+ _close(transformer.closeCode);
+ } else {
+ _close();
+ }
+ _readyState = WebSocket.CLOSED;
+ }
+ _closeCode = transformer.closeCode;
+ _closeReason = transformer.closeReason;
+ _controller.close();
+ if (_writeClosed) _socket.destroy();
+ },
+ unsubscribeOnError: true);
_socket.done
.catchError((error) {
- if (_readyState == WebSocket.CLOSED) return;
+ if (closed) return;
+ closed = true;
_readyState = WebSocket.CLOSED;
- _closeCode = ABNORMAL_CLOSURE;
+ _closeCode = WebSocketStatus.ABNORMAL_CLOSURE;
_controller.signalError(error);
_controller.close();
- _socket.destroy();
})
.whenComplete(() {
_writeClosed = true;
@@ -648,12 +625,7 @@ class _WebSocketImpl extends Stream implements WebSocket {
} else {
opcode = _WebSocketOpcode.TEXT;
}
- try {
- _sendFrame(opcode, data);
- } catch (_) {
- // The socket can be closed before _socket.done have a chance
- // to complete.
- }
+ _sendFrame(opcode, data);
}
void _sendFrame(int opcode, [List<int> data]) {
@@ -686,9 +658,15 @@ class _WebSocketImpl extends Stream implements WebSocket {
header[index++] = dataLength >> (((lengthBytes - 1) - i) * 8) & 0xFF;
}
assert(index == headerSize);
- _socket.add(header);
- if (data != null) {
- _socket.add(data);
+ try {
+ _socket.add(header);
+ if (data != null) {
+ _socket.add(data);
+ }
+ } catch (_) {
+ // The socket can be closed before _socket.done have a chance
+ // to complete.
+ _writeClosed = true;
}
}
}
« no previous file with comments | « sdk/lib/io/http_impl.dart ('k') | tests/standalone/io/web_socket_protocol_processor_test.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698