Skip to content
This repository has been archived by the owner on Apr 29, 2021. It is now read-only.

Commit

Permalink
0.4.0: Fix a major issue, some warnings (#24)
Browse files Browse the repository at this point in the history
* Fix responses in 0.4.0

* Fix presubmit.
  • Loading branch information
matanlurey authored and kharland committed Dec 27, 2016
1 parent 47b22ea commit 3b0736a
Show file tree
Hide file tree
Showing 16 changed files with 80 additions and 50 deletions.
File renamed without changes.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## 0.4.0

- **BREAKING** `readAs<Bytes|String>` to return a `Stream` and `Future` instead
- This is due to the potentially streaming nature of network and file i/o
- For example, HTTP servers commonly send chunked responses
- Added `readAsBytesAll` to auto-concatenate buffers together
- Fix various strong-mode warnings

## 0.3.0

- Use `WebSocketChannel` as the backing implementation for sockets
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Seltzer

[![pub package](https://img.shields.io/pub/v/seltzer.svg)](https://pub.dartlang.org/packages/seltzer)
[![Build Status](https://travis-ci.org/matanlurey/seltzer.svg?branch=master)](https://travis-ci.org/matanlurey/seltzer)

An elegant and rich cross-platform HTTP library for Dart.
Expand Down Expand Up @@ -49,11 +50,10 @@ are expecting once:
import 'package:seltzer/seltzer.dart' as seltzer;
import 'package:seltzer/platform/browser.dart';
void main() {
main() async {
useSeltzerInTheBrowser();
seltzer.get('some/url.json').send().first.then((response) {
print('Retrieved: ${response.payload}');
});
final response = await seltzer.get('some/url.json').send().first;
print('Retrieved: ${await response.readAsString()}');
}
```

Expand Down
2 changes: 1 addition & 1 deletion lib/platform/testing.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export 'package:seltzer/src/testing/replay.dart' show ReplaySeltzerHttp;
/// Initializes `package:seltzer/seltzer.dart` to use [implementation].
///
/// This is appropriate for test implementations that want to use an existing
/// implementation, such as a [ReplaySeltzerHttp].
/// implementation, such as a replay-based HTTP mock or server.
void useSeltzerForTesting(SeltzerHttp implementation) {
setHttpPlatform(implementation);
}
13 changes: 5 additions & 8 deletions lib/platform/vm.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,23 +30,20 @@ class VmSeltzerHttp extends SeltzerHttp {
const VmSeltzerHttp._();

@override
Stream<SeltzerHttpResponse> handle(SeltzerHttpRequest request,
[Object data]) {
Stream<SeltzerHttpResponse> handle(
SeltzerHttpRequest request, [
Object data,
]) {
return new HttpClient()
.openUrl(request.method, Uri.parse(request.url))
.then((r) async {
request.headers.forEach(r.headers.add);
final response = await r.close();
final payload = await response.first;
final headers = <String, String>{};
response.headers.forEach((name, value) {
headers[name] = value.join(' ');
});
if (payload is String) {
return new SeltzerHttpResponse.fromString(payload, headers: headers);
} else {
return new SeltzerHttpResponse.fromBytes(payload, headers: headers);
}
return new SeltzerHttpResponse.fromBytes(response, headers: headers);
}).asStream();
}
}
6 changes: 4 additions & 2 deletions lib/src/interface/http_request.dart
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ abstract class SeltzerHttpRequestBase extends SeltzerHtpRequestMixin {

/// A reusable [Equality] implementation for [SeltzerHttpRequest].
///
/// The default implementation of equality for [SeltzerHttpRequestMixin].
/// The default implementation of equality for requests.
class SeltzerHttpRequestEquality implements Equality<SeltzerHttpRequest> {
static const Equality _mapEquality = const MapEquality();

Expand Down Expand Up @@ -152,5 +152,7 @@ class _DefaultSeltzerHttpRequest extends SeltzerHttpRequestBase {
: super(headers: headers, method: method, url: url);

@override
Stream send([Object payload]) => _handler.handle(this, payload);
Stream<SeltzerHttpResponse> send([Object payload]) {
return _handler.handle(this, payload);
}
}
4 changes: 3 additions & 1 deletion lib/src/interface/http_response.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'dart:async';
import 'socket_message.dart';

/// An HTTP response object.
Expand All @@ -16,7 +17,7 @@ class SeltzerHttpResponse extends SeltzerMessage {

/// Create a new HTTP response with binary data.
SeltzerHttpResponse.fromBytes(
List<int> bytes, {
Stream<List<int>> bytes, {
Map<String, String> headers: const {},
})
: this.headers = new Map<String, String>.unmodifiable(headers),
Expand All @@ -30,6 +31,7 @@ class SeltzerHttpResponse extends SeltzerMessage {
: this.headers = new Map<String, String>.unmodifiable(headers),
super.fromString(string);

@override
toJson() {
return {
'data': super.toJson(),
Expand Down
41 changes: 29 additions & 12 deletions lib/src/interface/socket_message.dart
Original file line number Diff line number Diff line change
@@ -1,43 +1,60 @@
import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';

import 'http.dart';
import 'socket.dart';

/// A single message response by a [SeltzerWebSocket] or [SeltzerHttp] client.
class SeltzerMessage {
final Encoding _encoding;
final List<int> _bytes;
final String _string;
final int _length;
final Stream<List<int>> _bytes;

String _string;

/// Create a new [SeltzerMessage] from string or binary data.
factory SeltzerMessage(data) => data is String
? new SeltzerMessage.fromString(data)
: new SeltzerMessage.fromBytes(data);

/// Create a new [SeltzerMessage] from binary data.
SeltzerMessage.fromBytes(this._bytes, {Encoding encoding: UTF8})
SeltzerMessage.fromBytes(this._bytes, {int length, Encoding encoding: UTF8})
: _encoding = encoding,
_length = length,
_string = null;

/// Create a new [SeltzerMessage] from text data.
SeltzerMessage.fromString(this._string)
: _encoding = null,
_length = null,
_bytes = null;

/// Returns as bytes representing the the message's payload.
List<int> readAsBytes() {
if (_string != null) {
/// Returns as a stream of bytes representing the message's payload.
///
/// Some responses may be streamed into multiple chunks, which means that
/// listening to `stream.first` is not enough. To read the entire chunk in a
/// single call, use [readAsBytesAll].
Stream<List<int>> readAsBytes() => _bytes ?? readAsBytesAll().asStream();

/// Returns bytes representing the message's payload.
Future<List<int>> readAsBytesAll() async {
if (_bytes == null) {
return _string.codeUnits;
}
return _bytes;
var offset = 0;
return _bytes.fold/*<List<int>>*/(
new Uint8List(_length),
(buffer, value) {
buffer.setRange(offset, value.length, value);
offset += value.length;
},
);
}

/// Returns a string representing this message's payload.
String readAsString() {
if (_string != null) {
return _string;
}
return _encoding.decode(_bytes);
Future<String> readAsString() async {
return _string ??= await _encoding.decodeStream(_bytes);
}

toJson() => _string ?? _bytes;
Expand Down
8 changes: 6 additions & 2 deletions lib/src/socket_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,14 @@ class ChannelWebSocket implements SeltzerWebSocket {

static Future<SeltzerMessage> _decodeSocketMessage(payload) async {
if (payload is ByteBuffer) {
return new SeltzerMessage.fromBytes(payload.asUint8List());
return new SeltzerMessage.fromBytes(new Stream.fromIterable([
payload.asUint8List(),
]));
}
if (payload is TypedData) {
return new SeltzerMessage.fromBytes(payload.buffer.asUint8List());
return new SeltzerMessage.fromBytes(new Stream.fromIterable([
payload.buffer.asUint8List(),
]));
}
return new SeltzerMessage.fromString(payload);
}
Expand Down
3 changes: 2 additions & 1 deletion lib/src/testing/record.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ class SeltzerHttpRecorder extends SeltzerHttpHandler {
Object payload,
]) {
SeltzerHttpResponse last;
final transformer = new StreamTransformer.fromHandlers(
final transformer = new StreamTransformer<SeltzerHttpResponse,
SeltzerHttpResponse>.fromHandlers(
handleData: (event, sink) {
last = event;
sink.add(event);
Expand Down
2 changes: 1 addition & 1 deletion lib/src/testing/replay.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class ReplaySeltzerHttp extends SeltzerHttp {
Recording<SeltzerHttpRequest, SeltzerHttpResponse> recording,
) = ReplaySeltzerHttp._;

/// Creates a new [ReplySeltzerHttp] from [pairs] of request/responses.
/// Creates a new [ReplaySeltzerHttp] from [pairs] of request/responses.
factory ReplaySeltzerHttp.fromMap(
Map<SeltzerHttpRequest, SeltzerHttpResponse> pairs,
) {
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: seltzer
version: 0.3.0
version: 0.4.0
description: An elegant and rich cross-platform HTTP library for Dart.
authors:
- Matan Lurey <[email protected]>
Expand Down
12 changes: 6 additions & 6 deletions test/common/http.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const _echoUrl = 'http://localhost:9090';
void runHttpTests() {
test('should make a valid DELETE request', () async {
var response = await delete('$_echoUrl/das/fridge/lacroix').send().first;
expect(JSON.decode(response.readAsString()), {
expect(JSON.decode(await response.readAsString()), {
'headers': {},
'method': 'DELETE',
'url': '/das/fridge/lacroix',
Expand All @@ -19,7 +19,7 @@ void runHttpTests() {

test('should make a valid GET request', () async {
var response = await get('$_echoUrl/flags.json').send().first;
expect(JSON.decode(response.readAsString()), {
expect(JSON.decode(await response.readAsString()), {
'headers': {},
'method': 'GET',
'url': '/flags.json',
Expand All @@ -29,7 +29,7 @@ void runHttpTests() {

test('should make a valid PATCH request', () async {
var response = await patch('$_echoUrl/pants/up').send().first;
expect(JSON.decode(response.readAsString()), {
expect(JSON.decode(await response.readAsString()), {
'headers': {},
'method': 'PATCH',
'url': '/pants/up',
Expand All @@ -39,7 +39,7 @@ void runHttpTests() {

test('should make a valid POST request', () async {
var response = await post('$_echoUrl/users/clear').send().first;
expect(JSON.decode(response.readAsString()), {
expect(JSON.decode(await response.readAsString()), {
'headers': {},
'method': 'POST',
'url': '/users/clear',
Expand All @@ -49,7 +49,7 @@ void runHttpTests() {

test('should make a valid PUT request', () async {
var response = await put('$_echoUrl/pants/on').send().first;
expect(JSON.decode(response.readAsString()), {
expect(JSON.decode(await response.readAsString()), {
'headers': {},
'method': 'PUT',
'url': '/pants/on',
Expand All @@ -60,7 +60,7 @@ void runHttpTests() {
test('should send an HTTP header', () async {
var response =
await (get(_echoUrl)..headers['Authorization'] = 'abc123').send().first;
expect(JSON.decode(response.readAsString()), {
expect(JSON.decode(await response.readAsString()), {
'headers': {
'Authorization': 'abc123',
},
Expand Down
15 changes: 7 additions & 8 deletions test/common/ws.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,20 @@ void runSocketTests() {
group('$SeltzerWebSocket', () {
SeltzerWebSocket webSocket;

setUp(() async {
webSocket = await connect(_echoUrl);
setUp(() {
webSocket = connect(_echoUrl);
});

test('onClose should emit an event when the stream closes.', () async {
webSocket.close();
expect(webSocket.onClose, completion(isNotNull));
expect(await webSocket.onClose, isNotNull);
});

test('sendString should send string data.', () async {
var payload = 'string data';
var completer = new Completer();

webSocket.onMessage.listen(((message) {
expect(message.readAsString(), payload);
webSocket.onMessage.listen(((message) async {
expect(await message.readAsString(), payload);
completer.complete();
}));
webSocket.sendString(payload);
Expand All @@ -35,8 +34,8 @@ void runSocketTests() {
test('sendBytes should send byte data.', () async {
var payload = new Int8List.fromList([1, 2]);
var completer = new Completer();
webSocket.onMessage.listen((message) {
expect(message.readAsBytes(), payload);
webSocket.onMessage.listen((message) async {
expect(await message.readAsBytes().first, payload);
completer.complete();
});
webSocket.sendBytes(payload.buffer);
Expand Down
2 changes: 1 addition & 1 deletion test/runtime/record_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ main() {

runPingTest() async {
final response = await http.post('$_echoUrl/ping').send().last;
expect(JSON.decode(response.readAsString()), {
expect(JSON.decode(await response.readAsString()), {
'data': '',
'headers': {},
'method': 'POST',
Expand Down
4 changes: 2 additions & 2 deletions tool/presubmit.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ echo "PASSED"

# Make sure we pass the analyzer
echo "Checking dartanalyzer..."
FAILS_ANALYZER="$(find lib test tool -name "*.dart" | xargs dartanalyzer --options analysis_options.yaml)"
FAILS_ANALYZER="$(find lib test tool -name "*.dart" | xargs dartanalyzer --options .analysis_options)"
if [[ $FAILS_ANALYZER == *"[error]"* ]]
then
echo "FAILED"
Expand All @@ -34,6 +34,6 @@ dart tool/echo/ws.dart & export SOCKET_ECHO_PID=$!
# Run all of our tests
# If anything fails, we kill the ECHO_PID, otherwise kill at the end.
echo "Running all tests..."
pub run test -p "content-shell,vm" || kill $HTTP_ECHO_PID $SOCKET_ECHO_PID
pub run test -p "content-shell,vm,chrome" || kill $HTTP_ECHO_PID $SOCKET_ECHO_PID
kill $HTTP_ECHO_PID $SOCKET_ECHO_PID

0 comments on commit 3b0736a

Please sign in to comment.