Skip to content

Commit

Permalink
Reland "support the nsurl session web socket api" (#950)
Browse files Browse the repository at this point in the history
  • Loading branch information
brianquinlan committed Jun 2, 2023
1 parent 8834aec commit 5312366
Show file tree
Hide file tree
Showing 11 changed files with 460 additions and 14 deletions.
4 changes: 4 additions & 0 deletions pkgs/cupertino_http/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 1.1.0

* Add websocket support to `cupertino_api`.

## 1.0.1

* Remove experimental status from "Readme"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,139 @@ import 'package:flutter/foundation.dart';
import 'package:integration_test/integration_test.dart';
import 'package:test/test.dart';

void testWebSocketTask() {
group('websocket', () {
late HttpServer server;
int? lastCloseCode;
String? lastCloseReason;

setUp(() async {
lastCloseCode = null;
lastCloseReason = null;
server = await HttpServer.bind('localhost', 0)
..listen((request) {
if (request.uri.path.endsWith('error')) {
request.response.statusCode = 500;
request.response.close();
} else {
WebSocketTransformer.upgrade(request)
.then((websocket) => websocket.listen((event) {
final code = request.uri.queryParameters['code'];
final reason = request.uri.queryParameters['reason'];

websocket.add(event);
if (!request.uri.queryParameters.containsKey('noclose')) {
websocket.close(
code == null ? null : int.parse(code), reason);
}
}, onDone: () {
lastCloseCode = websocket.closeCode;
lastCloseReason = websocket.closeReason;
}));
}
});
});

tearDown(() async {
await server.close();
});

test('client code and reason', () async {
final session = URLSession.sharedSession();
final task = session.webSocketTaskWithRequest(URLRequest.fromUrl(
Uri.parse('ws://localhost:${server.port}/?noclose')))
..resume();
await task
.sendMessage(URLSessionWebSocketMessage.fromString('Hello World!'));
await task.receiveMessage();
task.cancelWithCloseCode(
4998, Data.fromUint8List(Uint8List.fromList('Bye'.codeUnits)));

// Allow the server to run and save the close code.
while (lastCloseCode == null) {
await Future<void>.delayed(const Duration(milliseconds: 10));
}
expect(lastCloseCode, 4998);
expect(lastCloseReason, 'Bye');
});

test('server code and reason', () async {
final session = URLSession.sharedSession();
final task = session.webSocketTaskWithRequest(URLRequest.fromUrl(
Uri.parse('ws://localhost:${server.port}/?code=4999&reason=fun')))
..resume();
await task
.sendMessage(URLSessionWebSocketMessage.fromString('Hello World!'));
await task.receiveMessage();
await expectLater(task.receiveMessage(),
throwsA(isA<Error>().having((e) => e.code, 'code', 57 // NOT_CONNECTED
)));

expect(task.closeCode, 4999);
expect(task.closeReason!.bytes, 'fun'.codeUnits);
task.cancel();
});

test('data message', () async {
final session = URLSession.sharedSession();
final task = session.webSocketTaskWithRequest(
URLRequest.fromUrl(Uri.parse('ws://localhost:${server.port}')))
..resume();
await task.sendMessage(URLSessionWebSocketMessage.fromData(
Data.fromUint8List(Uint8List.fromList([1, 2, 3]))));
final receivedMessage = await task.receiveMessage();
expect(receivedMessage.type,
URLSessionWebSocketMessageType.urlSessionWebSocketMessageTypeData);
expect(receivedMessage.data!.bytes, [1, 2, 3]);
expect(receivedMessage.string, null);
task.cancel();
});

test('text message', () async {
final session = URLSession.sharedSession();
final task = session.webSocketTaskWithRequest(
URLRequest.fromUrl(Uri.parse('ws://localhost:${server.port}')))
..resume();
await task
.sendMessage(URLSessionWebSocketMessage.fromString('Hello World!'));
final receivedMessage = await task.receiveMessage();
expect(receivedMessage.type,
URLSessionWebSocketMessageType.urlSessionWebSocketMessageTypeString);
expect(receivedMessage.data, null);
expect(receivedMessage.string, 'Hello World!');
task.cancel();
});

test('send failure', () async {
final session = URLSession.sharedSession();
final task = session.webSocketTaskWithRequest(
URLRequest.fromUrl(Uri.parse('ws://localhost:${server.port}/error')))
..resume();
await expectLater(
task.sendMessage(
URLSessionWebSocketMessage.fromString('Hello World!')),
throwsA(isA<Error>().having(
(e) => e.code, 'code', -1011 // NSURLErrorBadServerResponse
)));
task.cancel();
});

test('receive failure', () async {
final session = URLSession.sharedSession();
final task = session.webSocketTaskWithRequest(
URLRequest.fromUrl(Uri.parse('ws://localhost:${server.port}')))
..resume();
await task
.sendMessage(URLSessionWebSocketMessage.fromString('Hello World!'));
await task.receiveMessage();
await expectLater(task.receiveMessage(),
throwsA(isA<Error>().having((e) => e.code, 'code', 57 // NOT_CONNECTED
)));
task.cancel();
});
});
}

void testURLSessionTask(
URLSessionTask Function(URLSession session, Uri url) f) {
group('task states', () {
Expand Down Expand Up @@ -231,4 +364,6 @@ void main() {
testURLSessionTask((session, uri) =>
session.downloadTaskWithRequest(URLRequest.fromUrl(uri)));
});

testWebSocketTask();
}
1 change: 1 addition & 0 deletions pkgs/cupertino_http/ffigen.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ headers:
- '/System/Volumes/Data/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/Foundation.framework/Versions/C/Headers/NSDictionary.h'
- 'src/CUPHTTPClientDelegate.h'
- 'src/CUPHTTPForwardedDelegate.h'
- 'src/CUPHTTPCompletionHelper.h'
preamble: |
// ignore_for_file: always_specify_types
// ignore_for_file: camel_case_types
Expand Down
1 change: 1 addition & 0 deletions pkgs/cupertino_http/ios/Classes/CUPHTTPCompletionHelper.m
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#include "../../src/CUPHTTPCompletionHelper.m"
Loading

0 comments on commit 5312366

Please sign in to comment.