Skip to content

Commit 2993ea5

Browse files
authored
Implement the ability to run a particular Client implementation in a Zone (#697)
1 parent fa0af10 commit 2993ea5

File tree

4 files changed

+143
-3
lines changed

4 files changed

+143
-3
lines changed

lib/http.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export 'src/base_client.dart';
1616
export 'src/base_request.dart';
1717
export 'src/base_response.dart';
1818
export 'src/byte_stream.dart';
19-
export 'src/client.dart';
19+
export 'src/client.dart' hide zoneClient;
2020
export 'src/exception.dart';
2121
export 'src/multipart_file.dart';
2222
export 'src/multipart_request.dart';

lib/src/client.dart

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
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 'dart:async';
56
import 'dart:convert';
67
import 'dart:typed_data';
78

8-
import '../http.dart' as http;
9+
import 'package:meta/meta.dart';
910

11+
import '../http.dart' as http;
1012
import 'base_client.dart';
1113
import 'base_request.dart';
1214
import 'client_stub.dart'
@@ -32,7 +34,7 @@ abstract class Client {
3234
///
3335
/// Creates an `IOClient` if `dart:io` is available and a `BrowserClient` if
3436
/// `dart:html` is available, otherwise it will throw an unsupported error.
35-
factory Client() => createClient();
37+
factory Client() => zoneClient ?? createClient();
3638

3739
/// Sends an HTTP HEAD request with the given headers to the given URL.
3840
///
@@ -145,3 +147,45 @@ abstract class Client {
145147
/// do so can cause the Dart process to hang.
146148
void close();
147149
}
150+
151+
/// The [Client] for the current [Zone], if one has been set.
152+
///
153+
/// NOTE: This property is explicitly hidden from the public API.
154+
@internal
155+
Client? get zoneClient {
156+
final client = Zone.current[#_clientToken];
157+
return client == null ? null : (client as Client Function())();
158+
}
159+
160+
/// Runs [body] in its own [Zone] with the [Client] returned by [clientFactory]
161+
/// set as the default [Client].
162+
///
163+
/// For example:
164+
///
165+
/// ```
166+
/// class MyAndroidHttpClient extends BaseClient {
167+
/// @override
168+
/// Future<http.StreamedResponse> send(http.BaseRequest request) {
169+
/// // your implementation here
170+
/// }
171+
/// }
172+
///
173+
/// void main() {
174+
/// Client client = Platform.isAndroid ? MyAndroidHttpClient() : Client();
175+
/// runWithClient(myFunction, () => client);
176+
/// }
177+
///
178+
/// void myFunction() {
179+
/// // Uses the `Client` configured in `main`.
180+
/// final response = await get(Uri.parse("https://www.example.com/"));
181+
/// final client = Client();
182+
/// }
183+
/// ```
184+
///
185+
/// The [Client] returned by [clientFactory] is used by the [Client.new] factory
186+
/// and the convenience HTTP functions (e.g. [http.get])
187+
R runWithClient<R>(R Function() body, Client Function() clientFactory,
188+
{ZoneSpecification? zoneSpecification}) =>
189+
runZoned(body,
190+
zoneValues: {#_clientToken: Zone.current.bindCallback(clientFactory)},
191+
zoneSpecification: zoneSpecification);

test/io/client_test.dart

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,20 @@ import 'package:test/test.dart';
1212

1313
import '../utils.dart';
1414

15+
class TestClient extends http.BaseClient {
16+
@override
17+
Future<http.StreamedResponse> send(http.BaseRequest request) {
18+
throw UnimplementedError();
19+
}
20+
}
21+
22+
class TestClient2 extends http.BaseClient {
23+
@override
24+
Future<http.StreamedResponse> send(http.BaseRequest request) {
25+
throw UnimplementedError();
26+
}
27+
}
28+
1529
void main() {
1630
late Uri serverUrl;
1731
setUpAll(() async {
@@ -133,4 +147,30 @@ void main() {
133147

134148
expect(socket, isNotNull);
135149
});
150+
151+
test('runWithClient', () {
152+
http.Client client =
153+
http.runWithClient(() => http.Client(), () => TestClient());
154+
expect(client, isA<TestClient>());
155+
});
156+
157+
test('runWithClient nested', () {
158+
late final http.Client client;
159+
late final http.Client nestedClient;
160+
http.runWithClient(() {
161+
http.runWithClient(
162+
() => nestedClient = http.Client(), () => TestClient2());
163+
client = http.Client();
164+
}, () => TestClient());
165+
expect(client, isA<TestClient>());
166+
expect(nestedClient, isA<TestClient2>());
167+
});
168+
169+
test('runWithClient recursion', () {
170+
// Verify that calling the http.Client() factory inside nested Zones does
171+
// not provoke an infinite recursion.
172+
http.runWithClient(() {
173+
http.runWithClient(() => http.Client(), () => http.Client());
174+
}, () => http.Client());
175+
});
136176
}

test/io/http_test.dart

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@ import 'package:test/test.dart';
99

1010
import '../utils.dart';
1111

12+
class TestClient extends http.BaseClient {
13+
@override
14+
Future<http.StreamedResponse> send(http.BaseRequest request) {
15+
throw UnimplementedError();
16+
}
17+
}
18+
1219
void main() {
1320
late Uri serverUrl;
1421
setUpAll(() async {
@@ -22,6 +29,13 @@ void main() {
2229
expect(response.body, equals(''));
2330
});
2431

32+
test('head runWithClient', () {
33+
expect(
34+
() => http.runWithClient(
35+
() => http.head(serverUrl), () => TestClient()),
36+
throwsUnimplementedError);
37+
});
38+
2539
test('get', () async {
2640
var response = await http.get(serverUrl, headers: {
2741
'X-Random-Header': 'Value',
@@ -43,6 +57,13 @@ void main() {
4357
containsPair('x-other-header', ['Other Value']))))));
4458
});
4559

60+
test('get runWithClient', () {
61+
expect(
62+
() =>
63+
http.runWithClient(() => http.get(serverUrl), () => TestClient()),
64+
throwsUnimplementedError);
65+
});
66+
4667
test('post', () async {
4768
var response = await http.post(serverUrl, headers: {
4869
'X-Random-Header': 'Value',
@@ -151,6 +172,13 @@ void main() {
151172
})));
152173
});
153174

175+
test('post runWithClient', () {
176+
expect(
177+
() => http.runWithClient(
178+
() => http.post(serverUrl, body: 'testing'), () => TestClient()),
179+
throwsUnimplementedError);
180+
});
181+
154182
test('put', () async {
155183
var response = await http.put(serverUrl, headers: {
156184
'X-Random-Header': 'Value',
@@ -259,6 +287,13 @@ void main() {
259287
})));
260288
});
261289

290+
test('put runWithClient', () {
291+
expect(
292+
() => http.runWithClient(
293+
() => http.put(serverUrl, body: 'testing'), () => TestClient()),
294+
throwsUnimplementedError);
295+
});
296+
262297
test('patch', () async {
263298
var response = await http.patch(serverUrl, headers: {
264299
'X-Random-Header': 'Value',
@@ -388,6 +423,13 @@ void main() {
388423
containsPair('x-other-header', ['Other Value']))))));
389424
});
390425

426+
test('patch runWithClient', () {
427+
expect(
428+
() => http.runWithClient(
429+
() => http.patch(serverUrl, body: 'testing'), () => TestClient()),
430+
throwsUnimplementedError);
431+
});
432+
391433
test('read', () async {
392434
var response = await http.read(serverUrl, headers: {
393435
'X-Random-Header': 'Value',
@@ -412,6 +454,13 @@ void main() {
412454
expect(http.read(serverUrl.resolve('/error')), throwsClientException());
413455
});
414456

457+
test('read runWithClient', () {
458+
expect(
459+
() => http.runWithClient(
460+
() => http.read(serverUrl), () => TestClient()),
461+
throwsUnimplementedError);
462+
});
463+
415464
test('readBytes', () async {
416465
var bytes = await http.readBytes(serverUrl, headers: {
417466
'X-Random-Header': 'Value',
@@ -437,5 +486,12 @@ void main() {
437486
expect(
438487
http.readBytes(serverUrl.resolve('/error')), throwsClientException());
439488
});
489+
490+
test('readBytes runWithClient', () {
491+
expect(
492+
() => http.runWithClient(
493+
() => http.readBytes(serverUrl), () => TestClient()),
494+
throwsUnimplementedError);
495+
});
440496
});
441497
}

0 commit comments

Comments
 (0)