Add support for WebSocket.addUtf8Text().
Also update WebSocketImpl to the latest SDK version.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 86a1110..110e2f8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 1.1.0
+
+* Add `WebSocketSink.addUtf8Text()`. This matches `dart:io`'s
+ `WebSocket.addUtf8Text()`.
+
## 1.0.4
* Support `crypto` 2.0.0.
diff --git a/lib/html.dart b/lib/html.dart
index 07a17ed..af390d2 100644
--- a/lib/html.dart
+++ b/lib/html.dart
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
+import 'dart:convert';
import 'dart:html';
import 'dart:typed_data';
@@ -129,6 +130,8 @@
: _channel = channel,
super(channel._controller.foreign.sink);
+ void addUtf8Text(List<int> bytes) => super.add(UTF8.decode(bytes));
+
Future close([int closeCode, String closeReason]) {
_channel._localCloseCode = closeCode;
_channel._localCloseReason = closeReason;
diff --git a/lib/io.dart b/lib/io.dart
index 8bf087b..1ea1aab 100644
--- a/lib/io.dart
+++ b/lib/io.dart
@@ -88,6 +88,8 @@
: _webSocket = webSocket,
super(webSocket);
+ void addUtf8Text(List<int> bytes) => _webSocket.addUtf8Text(bytes);
+
Future close([int closeCode, String closeReason]) =>
_webSocket.close(closeCode, closeReason);
}
diff --git a/lib/src/channel.dart b/lib/src/channel.dart
index 6aff42c..a5d2956 100644
--- a/lib/src/channel.dart
+++ b/lib/src/channel.dart
@@ -98,8 +98,8 @@
/// The sink exposed by a [WebSocketChannel].
///
-/// This is like a normal [StreamSink], except that it supports extra arguments
-/// to [close].
+/// This is like a normal [StreamSink], except that it supports [addUtf8Text]
+/// and extra arguments to [close].
class WebSocketSink extends DelegatingStreamSink {
final WebSocketImpl _webSocket;
@@ -107,6 +107,16 @@
: _webSocket = webSocket,
super(webSocket);
+ /// Sends a text message with text represented by [bytes].
+ ///
+ /// The [bytes] should be valid UTF-8 encoded Unicode characters. If they are
+ /// not, the receiving end will close the connection.
+ ///
+ /// On some implementations (such as [IOWebSocketChannel]), this avoids the
+ /// overhead of encoding [bytes] as a UTF-16 Dart string and then back to
+ /// UTF-8 to send across the socket.
+ void addUtf8Text(List<int> bytes) => _webSocket.addUtf8Text(bytes);
+
/// Closes the web socket connection.
///
/// [closeCode] and [closeReason] are the [close code][] and [reason][] sent
diff --git a/lib/src/copy/bytes_builder.dart b/lib/src/copy/bytes_builder.dart
index 573eed5..3d200db 100644
--- a/lib/src/copy/bytes_builder.dart
+++ b/lib/src/copy/bytes_builder.dart
@@ -9,7 +9,7 @@
// Because it's copied directly, there are no modifications from the original.
//
// This is up-to-date as of sdk revision
-// e41fb4cafd6052157dbc1490d437045240f4773f.
+// b40c20bfbccd0e4d98d194b9d362d04a9f7c91f3.
import 'dart:math';
import 'dart:typed_data';
@@ -55,7 +55,7 @@
/**
* Returns the contents of `this` and clears `this`.
*
- * The list returned is a view of the the internal buffer, limited to the
+ * The list returned is a view of the internal buffer, limited to the
* [length].
*/
List<int> takeBytes();
@@ -163,14 +163,17 @@
class _BytesBuilder implements BytesBuilder {
int _length = 0;
- final _chunks = <List<int>>[];
+ final List<Uint8List> _chunks = [];
void add(List<int> bytes) {
- if (bytes is! Uint8List) {
- bytes = new Uint8List.fromList(bytes);
+ Uint8List typedBytes;
+ if (bytes is Uint8List) {
+ typedBytes = bytes;
+ } else {
+ typedBytes = new Uint8List.fromList(bytes);
}
- _chunks.add(bytes);
- _length += bytes.length;
+ _chunks.add(typedBytes);
+ _length += typedBytes.length;
}
void addByte(int byte) { add([byte]); }
diff --git a/lib/src/copy/io_sink.dart b/lib/src/copy/io_sink.dart
index 34abcad..86bab15 100644
--- a/lib/src/copy/io_sink.dart
+++ b/lib/src/copy/io_sink.dart
@@ -9,7 +9,7 @@
// desired public API and to remove "dart:io" dependencies have been made.
//
// This is up-to-date as of sdk revision
-// e41fb4cafd6052157dbc1490d437045240f4773f.
+// b40c20bfbccd0e4d98d194b9d362d04a9f7c91f3.
import 'dart:async';
diff --git a/lib/src/copy/web_socket.dart b/lib/src/copy/web_socket.dart
index c77dc2f..01ba332 100644
--- a/lib/src/copy/web_socket.dart
+++ b/lib/src/copy/web_socket.dart
@@ -9,7 +9,7 @@
// desired public API and to remove "dart:io" dependencies have been made.
//
// This is up-to-date as of sdk revision
-// e41fb4cafd6052157dbc1490d437045240f4773f.
+// b40c20bfbccd0e4d98d194b9d362d04a9f7c91f3.
/**
* Web socket status codes used when closing a web socket connection.
diff --git a/lib/src/copy/web_socket_impl.dart b/lib/src/copy/web_socket_impl.dart
index 38db9e3..9aa83d1 100644
--- a/lib/src/copy/web_socket_impl.dart
+++ b/lib/src/copy/web_socket_impl.dart
@@ -10,7 +10,7 @@
// desired public API and to remove "dart:io" dependencies have been made.
//
// This is up-to-date as of sdk revision
-// e41fb4cafd6052157dbc1490d437045240f4773f.
+// b40c20bfbccd0e4d98d194b9d362d04a9f7c91f3.
import 'dart:async';
import 'dart:convert';
@@ -57,21 +57,14 @@
static const int RESERVED_F = 15;
}
-/**
- * Stores the header and integer value derived from negotiation of
- * client_max_window_bits and server_max_window_bits. headerValue will be
- * set in the Websocket response headers.
- */
-class _CompressionMaxWindowBits {
- String headerValue;
- int maxWindowBits;
- _CompressionMaxWindowBits([this.headerValue, this.maxWindowBits]);
- String toString() => headerValue;
+class _EncodedString {
+ final List<int> bytes;
+ _EncodedString(this.bytes);
}
/**
* The web socket protocol transformer handles the protocol byte stream
- * which is supplied through the [:handleData:]. As the protocol is processed,
+ * which is supplied through the `handleData`. As the protocol is processed,
* it'll output frame data as either a List<int> or String.
*
* Important information about usage: Be sure you use cancelOnError, so the
@@ -80,7 +73,8 @@
*/
// TODO(ajohnsen): make this transformer reusable?
class _WebSocketProtocolTransformer
- implements StreamTransformer<List<int>, dynamic>, EventSink<List<int>> {
+ implements EventSink<List<int>>, StreamTransformer<
+ List<int>, dynamic/*List<int>|_WebSocketPing|_WebSocketPong>*/> {
static const int START = 0;
static const int LEN_FIRST = 1;
static const int LEN_REST = 2;
@@ -108,7 +102,7 @@
int closeCode = WebSocketStatus.NO_STATUS_RECEIVED;
String closeReason = "";
- EventSink _eventSink;
+ EventSink<dynamic/*List<int>|_WebSocketPing|_WebSocketPong>*/> _eventSink;
final bool _serverSide;
final List _maskingBytes = new List(4);
@@ -116,7 +110,8 @@
_WebSocketProtocolTransformer([this._serverSide = false]);
- Stream bind(Stream stream) {
+ Stream<dynamic/*List<int>|_WebSocketPing|_WebSocketPong>*/> bind(
+ Stream<List<int>> stream) {
return new Stream.eventTransformed(stream, (EventSink eventSink) {
if (_eventSink != null) {
throw new StateError("WebSocket transformer already used.");
@@ -422,7 +417,8 @@
_WebSocketOutgoingTransformer(this.webSocket);
Stream<List<int>> bind(Stream stream) {
- return new Stream.eventTransformed(stream, (eventSink) {
+ return new Stream<List<int>>.eventTransformed(
+ stream, (EventSink<List<int>> eventSink) {
if (_eventSink != null) {
throw new StateError("WebSocket transformer already used");
}
@@ -446,13 +442,14 @@
if (message is String) {
opcode = _WebSocketOpcode.TEXT;
data = UTF8.encode(message);
+ } else if (message is List<int>) {
+ opcode = _WebSocketOpcode.BINARY;
+ data = message;
+ } else if (message is _EncodedString) {
+ opcode = _WebSocketOpcode.TEXT;
+ data = message.bytes;
} else {
- if (message is List<int>) {
- data = message;
- opcode = _WebSocketOpcode.BINARY;
- } else {
- throw new ArgumentError(message);
- }
+ throw new ArgumentError(message);
}
} else {
opcode = _WebSocketOpcode.TEXT;
@@ -484,9 +481,8 @@
opcode,
data,
webSocket._serverSide,
- false).forEach((e) {
- _eventSink.add(e);
- });
+ false)
+ .forEach((e) { _eventSink.add(e); });
static Iterable<List<int>> createFrame(
int opcode, List<int> data, bool serverSide, bool compressed) {
@@ -807,6 +803,12 @@
String get closeReason => _closeReason;
void add(data) { _sink.add(data); }
+ void addUtf8Text(List<int> bytes) {
+ if (bytes is! List<int>) {
+ throw new ArgumentError.value(bytes, "bytes", "Is not a list of bytes");
+ }
+ _sink.add(new _EncodedString(bytes));
+ }
void addError(error, [StackTrace stackTrace]) {
_sink.addError(error, stackTrace);
}
diff --git a/lib/src/sink_completer.dart b/lib/src/sink_completer.dart
index d932fd7..5d1187a 100644
--- a/lib/src/sink_completer.dart
+++ b/lib/src/sink_completer.dart
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
+import 'dart:convert';
import 'channel.dart';
@@ -90,6 +91,17 @@
}
}
+ void addUtf8Text(List<int> bytes) {
+ if (_canSendDirectly) {
+ _destinationSink.addUtf8Text(bytes);
+ } else {
+ _ensureController();
+ // Ideally we wouldn't ever have to decode these bytes, but it's the
+ // cleanest way to get [setDestinationSink] to work.
+ _controller.add(UTF8.decode(bytes));
+ }
+ }
+
void addError(error, [StackTrace stackTrace]) {
if (_canSendDirectly) {
_destinationSink.addError(error, stackTrace);
diff --git a/pubspec.yaml b/pubspec.yaml
index 3a95d49..ccb7d26 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,11 +1,11 @@
name: web_socket_channel
-version: 1.0.4
+version: 1.1.0
description: StreamChannel wrappers for WebSockets.
author: Dart Team <misc@dartlang.org>
homepage: https://github.com/dart-lang/web_socket_channel
environment:
- sdk: '>=1.13.0 <2.0.0'
+ sdk: '>=1.20.0 <2.0.0'
dependencies:
async: '^1.3.0'
diff --git a/test/html_test.dart b/test/html_test.dart
index 9954eb6..690327c 100644
--- a/test/html_test.dart
+++ b/test/html_test.dart
@@ -10,6 +10,7 @@
"html_test_server.dart.")
import 'dart:async';
+import 'dart:convert';
import 'dart:html';
import 'dart:typed_data';
@@ -52,6 +53,15 @@
expect(await queue.next, equals("foo"));
});
+ test("sends raw UTF-8 bytes", () async {
+ var webSocket = new WebSocket("ws://localhost:1234");
+ channel = new HtmlWebSocketChannel(webSocket);
+
+ var queue = new StreamQueue(channel.stream);
+ channel.sink.addUtf8Text(UTF8.encode("ṗĭñḡ"));
+ expect(await queue.next, equals("ṗĭñḡ"));
+ });
+
test(".connect defaults to binary lists", () async {
channel = new HtmlWebSocketChannel.connect("ws://localhost:1234");
diff --git a/test/io_test.dart b/test/io_test.dart
index fcf8c7c..53a63eb 100644
--- a/test/io_test.dart
+++ b/test/io_test.dart
@@ -5,6 +5,7 @@
@TestOn('vm')
import 'dart:io';
+import 'dart:convert';
import 'package:test/test.dart';
@@ -49,6 +50,21 @@
}));
});
+ test("sends raw UTF-8 bytes", () async {
+ server = await HttpServer.bind("localhost", 0);
+ server.transform(new WebSocketTransformer()).listen((webSocket) {
+ var channel = new IOWebSocketChannel(webSocket);
+ channel.stream.listen((request) {
+ expect(request, equals("ṗĭñḡ"));
+ channel.close();
+ });
+ });
+
+ var channel = new IOWebSocketChannel.connect(
+ "ws://localhost:${server.port}");
+ channel.sink.addUtf8Text(UTF8.encode("ṗĭñḡ"));
+ });
+
test(".connect communicates immediately", () async {
server = await HttpServer.bind("localhost", 0);
server.transform(new WebSocketTransformer()).listen((webSocket) {