Skip to content

Commit 0e20ff9

Browse files
authored
Run the request body tests against the browser client. (#711)
1 parent c09fb1d commit 0e20ff9

File tree

8 files changed

+222
-171
lines changed

8 files changed

+222
-171
lines changed

pkgs/http_client_conformance_tests/lib/http_client_conformance_tests.dart

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55
import 'package:http/http.dart';
66

77
import 'src/redirect_tests.dart';
8+
import 'src/request_body_streamed_tests.dart';
89
import 'src/request_body_tests.dart';
910
import 'src/request_headers_tests.dart';
1011
import 'src/response_body_tests.dart';
1112
import 'src/response_headers_tests.dart';
1213

1314
export 'src/redirect_tests.dart' show testRedirect;
15+
export 'src/request_body_streamed_tests.dart' show testRequestBodyStreamed;
1416
export 'src/request_body_tests.dart' show testRequestBody;
1517
export 'src/request_headers_tests.dart' show testRequestHeaders;
1618
export 'src/response_body_tests.dart' show testResponseBody;
@@ -32,7 +34,8 @@ void testAll(Client client,
3234
{bool canStreamRequestBody = true,
3335
bool canStreamResponseBody = true,
3436
bool redirectAlwaysAllowed = false}) {
35-
testRequestBody(client, canStreamRequestBody: canStreamRequestBody);
37+
testRequestBody(client);
38+
testRequestBodyStreamed(client, canStreamRequestBody: canStreamRequestBody);
3639
testResponseBody(client, canStreamResponseBody: canStreamResponseBody);
3740
testRequestHeaders(client);
3841
testResponseHeaders(client);

pkgs/http_client_conformance_tests/lib/src/redirect_tests.dart

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5+
import 'package:async/async.dart';
56
import 'package:http/http.dart';
67
import 'package:stream_channel/stream_channel.dart';
78
import 'package:test/test.dart';
@@ -12,22 +13,24 @@ import 'package:test/test.dart';
1213
/// to limit redirects will be skipped.
1314
void testRedirect(Client client, {bool redirectAlwaysAllowed = false}) async {
1415
group('redirects', () {
15-
late String host;
16-
late StreamChannel<Object?> httpServerChannel;
16+
late final String host;
17+
late final StreamChannel<Object?> httpServerChannel;
18+
late final StreamQueue<Object?> httpServerQueue;
1719

18-
setUp(() async {
20+
setUpAll(() async {
1921
httpServerChannel = spawnHybridUri('../lib/src/redirect_server.dart');
20-
host = 'localhost:${await httpServerChannel.stream.first as int}';
22+
httpServerQueue = StreamQueue(httpServerChannel.stream);
23+
host = 'localhost:${await httpServerQueue.next}';
2124
});
22-
tearDown(() => httpServerChannel.sink.add(null));
25+
tearDownAll(() => httpServerChannel.sink.add(null));
2326

2427
test('disallow redirect', () async {
2528
final request = Request('GET', Uri.http(host, '/1'))
2629
..followRedirects = false;
2730
final response = await client.send(request);
2831
expect(response.statusCode, 302);
2932
expect(response.isRedirect, true);
30-
}, skip: redirectAlwaysAllowed ? 'redirects always allowed' : '');
33+
}, skip: redirectAlwaysAllowed ? 'redirects always allowed' : false);
3134

3235
test('allow redirect', () async {
3336
final request = Request('GET', Uri.http(host, '/1'))
@@ -56,7 +59,7 @@ void testRedirect(Client client, {bool redirectAlwaysAllowed = false}) async {
5659
final response = await client.send(request);
5760
expect(response.statusCode, 200);
5861
expect(response.isRedirect, false);
59-
}, skip: redirectAlwaysAllowed ? 'redirects always allowed' : '');
62+
}, skip: redirectAlwaysAllowed ? 'redirects always allowed' : false);
6063

6164
test('too many redirects', () async {
6265
final request = Request('GET', Uri.http(host, '/6'))
@@ -66,7 +69,7 @@ void testRedirect(Client client, {bool redirectAlwaysAllowed = false}) async {
6669
client.send(request),
6770
throwsA(isA<ClientException>()
6871
.having((e) => e.message, 'message', 'Redirect limit exceeded')));
69-
}, skip: redirectAlwaysAllowed ? 'redirects always allowed' : '');
72+
}, skip: redirectAlwaysAllowed ? 'redirects always allowed' : false);
7073

7174
test(
7275
'loop',
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:async';
6+
import 'dart:convert';
7+
import 'dart:io';
8+
9+
import 'package:stream_channel/stream_channel.dart';
10+
11+
/// Starts an HTTP server that captures the content type header and request
12+
/// body.
13+
///
14+
/// Channel protocol:
15+
/// On Startup:
16+
/// - send port
17+
/// On Request Received:
18+
/// - send "Content-Type" header
19+
/// - send request body
20+
/// When Receive Anything:
21+
/// - exit
22+
void hybridMain(StreamChannel<Object?> channel) async {
23+
late HttpServer server;
24+
25+
server = (await HttpServer.bind('localhost', 0))
26+
..listen((request) async {
27+
request.response.headers.set('Access-Control-Allow-Origin', '*');
28+
if (request.method == 'OPTIONS') {
29+
// Handle a CORS preflight request:
30+
// https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#preflighted_requests
31+
request.response.headers
32+
..set('Access-Control-Allow-Methods', 'POST, DELETE')
33+
..set('Access-Control-Allow-Headers', 'Content-Type');
34+
} else {
35+
channel.sink.add(request.headers[HttpHeaders.contentTypeHeader]);
36+
final serverReceivedBody =
37+
await const Utf8Decoder().bind(request).fold('', (p, e) => '$p$e');
38+
channel.sink.add(serverReceivedBody);
39+
}
40+
unawaited(request.response.close());
41+
});
42+
43+
channel.sink.add(server.port);
44+
await channel
45+
.stream.first; // Any writes indicates that the server should exit.
46+
unawaited(server.close());
47+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:async';
6+
import 'dart:convert';
7+
import 'dart:io';
8+
9+
import 'package:stream_channel/stream_channel.dart';
10+
11+
/// Starts an HTTP server that absorbes a request stream of integers and
12+
/// signals the client to quit after 1000 have been received.
13+
///
14+
/// Channel protocol:
15+
/// On Startup:
16+
/// - send port
17+
/// On Integer == 1000 received:
18+
/// - send 1000
19+
/// When Receive Anything:
20+
/// - exit
21+
void hybridMain(StreamChannel<Object?> channel) async {
22+
late HttpServer server;
23+
24+
server = (await HttpServer.bind('localhost', 0))
25+
..listen((request) async {
26+
request.response.headers.set('Access-Control-Allow-Origin', '*');
27+
await const LineSplitter()
28+
.bind(const Utf8Decoder().bind(request))
29+
.forEach((s) {
30+
final lastReceived = int.parse(s.trim());
31+
if (lastReceived == 1000) {
32+
channel.sink.add(lastReceived);
33+
}
34+
});
35+
unawaited(request.response.close());
36+
});
37+
38+
channel.sink.add(server.port);
39+
await channel
40+
.stream.first; // Any writes indicates that the server should exit.
41+
unawaited(server.close());
42+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:async';
6+
import 'dart:convert';
7+
8+
import 'package:async/async.dart';
9+
import 'package:http/http.dart';
10+
import 'package:stream_channel/stream_channel.dart';
11+
import 'package:test/test.dart';
12+
13+
/// Tests that the [Client] correctly implements streamed request body
14+
/// uploading.
15+
///
16+
/// If [canStreamRequestBody] is `false` then tests that assume that the
17+
/// [Client] supports sending HTTP requests with unbounded body sizes will be
18+
/// skipped.
19+
void testRequestBodyStreamed(Client client,
20+
{bool canStreamRequestBody = true}) {
21+
group('streamed requests', () {
22+
late String host;
23+
late StreamChannel<Object?> httpServerChannel;
24+
late StreamQueue<Object?> httpServerQueue;
25+
26+
setUp(() async {
27+
httpServerChannel =
28+
spawnHybridUri('../lib/src/request_body_streamed_server.dart');
29+
httpServerQueue = StreamQueue(httpServerChannel.stream);
30+
host = 'localhost:${await httpServerQueue.next}';
31+
});
32+
tearDown(() => httpServerChannel.sink.add(null));
33+
34+
test('client.send() with StreamedRequest', () async {
35+
// The client continuously streams data to the server until
36+
// instructed to stop (by setting `clientWriting` to `false`).
37+
// The server sets `serverWriting` to `false` after it has
38+
// already received some data.
39+
//
40+
// This ensures that the client supports streamed data sends.
41+
var lastReceived = 0;
42+
43+
Stream<String> count() async* {
44+
var i = 0;
45+
unawaited(
46+
httpServerQueue.next.then((value) => lastReceived = value as int));
47+
do {
48+
yield '${i++}\n';
49+
// Let the event loop run.
50+
await Future<void>.delayed(const Duration());
51+
} while (lastReceived < 1000);
52+
}
53+
54+
final request = StreamedRequest('POST', Uri.http(host, ''));
55+
const Utf8Encoder()
56+
.bind(count())
57+
.listen(request.sink.add, onDone: request.sink.close);
58+
await client.send(request);
59+
60+
expect(lastReceived, greaterThanOrEqualTo(1000));
61+
});
62+
}, skip: canStreamRequestBody ? false : 'does not stream request bodies');
63+
}

0 commit comments

Comments
 (0)