Skip to content

Commit 4726f43

Browse files
authored
Integrate cronet_http with package:http_profile (#1167)
1 parent 46d49e3 commit 4726f43

File tree

6 files changed

+415
-16
lines changed

6 files changed

+415
-16
lines changed

pkgs/cronet_http/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 1.3.0
2+
3+
* Add integration to the
4+
[DevTools Network View](https://docs.flutter.dev/tools/devtools/network).
5+
16
## 1.2.1
27

38
* Upgrade `package:jni` to 0.9.2 to fix the build error in the latest versions
Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
// Copyright (c) 2024, 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:io';
7+
8+
import 'package:cronet_http/src/cronet_client.dart';
9+
import 'package:flutter_test/flutter_test.dart';
10+
import 'package:http/http.dart';
11+
import 'package:http_profile/http_profile.dart';
12+
import 'package:integration_test/integration_test.dart';
13+
14+
void main() {
15+
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
16+
17+
group('profile', () {
18+
final profilingEnabled = HttpClientRequestProfile.profilingEnabled;
19+
20+
setUpAll(() {
21+
HttpClientRequestProfile.profilingEnabled = true;
22+
});
23+
24+
tearDownAll(() {
25+
HttpClientRequestProfile.profilingEnabled = profilingEnabled;
26+
});
27+
28+
group('POST', () {
29+
late HttpServer successServer;
30+
late Uri successServerUri;
31+
late HttpClientRequestProfile profile;
32+
33+
setUpAll(() async {
34+
successServer = (await HttpServer.bind('localhost', 0))
35+
..listen((request) async {
36+
await request.drain<void>();
37+
request.response.headers.set('Content-Type', 'text/plain');
38+
request.response.headers.set('Content-Length', '11');
39+
request.response.write('Hello World');
40+
await request.response.close();
41+
});
42+
successServerUri = Uri.http('localhost:${successServer.port}');
43+
final client = CronetClientWithProfile.defaultCronetEngine();
44+
await client.post(successServerUri,
45+
headers: {'Content-Type': 'text/plain'}, body: 'Hi');
46+
profile = client.profile!;
47+
});
48+
tearDownAll(() {
49+
successServer.close();
50+
});
51+
52+
test('profile attributes', () {
53+
expect(profile.events, isEmpty);
54+
expect(profile.requestMethod, 'POST');
55+
expect(profile.requestUri, successServerUri.toString());
56+
expect(profile.connectionInfo,
57+
containsPair('package', 'package:cronet_http'));
58+
});
59+
60+
test('request attributes', () {
61+
expect(profile.requestData.bodyBytes, 'Hi'.codeUnits);
62+
expect(profile.requestData.contentLength, 2);
63+
expect(profile.requestData.endTime, isNotNull);
64+
expect(profile.requestData.error, isNull);
65+
expect(
66+
profile.requestData.headers, containsPair('Content-Length', ['2']));
67+
expect(profile.requestData.headers,
68+
containsPair('Content-Type', ['text/plain; charset=utf-8']));
69+
expect(profile.requestData.persistentConnection, isNull);
70+
expect(profile.requestData.proxyDetails, isNull);
71+
expect(profile.requestData.startTime, isNotNull);
72+
});
73+
74+
test('response attributes', () {
75+
expect(profile.responseData.bodyBytes, 'Hello World'.codeUnits);
76+
expect(profile.responseData.compressionState, isNull);
77+
expect(profile.responseData.contentLength, 11);
78+
expect(profile.responseData.endTime, isNotNull);
79+
expect(profile.responseData.error, isNull);
80+
expect(profile.responseData.headers,
81+
containsPair('content-type', ['text/plain']));
82+
expect(profile.responseData.headers,
83+
containsPair('content-length', ['11']));
84+
expect(profile.responseData.isRedirect, false);
85+
expect(profile.responseData.persistentConnection, isNull);
86+
expect(profile.responseData.reasonPhrase, 'OK');
87+
expect(profile.responseData.redirects, isEmpty);
88+
expect(profile.responseData.startTime, isNotNull);
89+
expect(profile.responseData.statusCode, 200);
90+
});
91+
});
92+
93+
group('failed POST request', () {
94+
late HttpClientRequestProfile profile;
95+
96+
setUpAll(() async {
97+
final client = CronetClientWithProfile.defaultCronetEngine();
98+
try {
99+
await client.post(Uri.http('thisisnotahost'),
100+
headers: {'Content-Type': 'text/plain'}, body: 'Hi');
101+
fail('expected exception');
102+
} on ClientException {
103+
// Expected exception.
104+
}
105+
profile = client.profile!;
106+
});
107+
108+
test('profile attributes', () {
109+
expect(profile.events, isEmpty);
110+
expect(profile.requestMethod, 'POST');
111+
expect(profile.requestUri, 'http://thisisnotahost');
112+
expect(profile.connectionInfo,
113+
containsPair('package', 'package:cronet_http'));
114+
});
115+
116+
test('request attributes', () {
117+
expect(profile.requestData.bodyBytes, 'Hi'.codeUnits);
118+
expect(profile.requestData.contentLength, 2);
119+
expect(profile.requestData.endTime, isNotNull);
120+
expect(profile.requestData.error, startsWith('ClientException:'));
121+
expect(
122+
profile.requestData.headers, containsPair('Content-Length', ['2']));
123+
expect(profile.requestData.headers,
124+
containsPair('Content-Type', ['text/plain; charset=utf-8']));
125+
expect(profile.requestData.persistentConnection, isNull);
126+
expect(profile.requestData.proxyDetails, isNull);
127+
expect(profile.requestData.startTime, isNotNull);
128+
});
129+
130+
test('response attributes', () {
131+
expect(profile.responseData.bodyBytes, isEmpty);
132+
expect(profile.responseData.compressionState, isNull);
133+
expect(profile.responseData.contentLength, isNull);
134+
expect(profile.responseData.endTime, isNull);
135+
expect(profile.responseData.error, isNull);
136+
expect(profile.responseData.headers, isNull);
137+
expect(profile.responseData.isRedirect, isNull);
138+
expect(profile.responseData.persistentConnection, isNull);
139+
expect(profile.responseData.reasonPhrase, isNull);
140+
expect(profile.responseData.redirects, isEmpty);
141+
expect(profile.responseData.startTime, isNull);
142+
expect(profile.responseData.statusCode, isNull);
143+
});
144+
});
145+
146+
group('failed POST response', () {
147+
late HttpServer successServer;
148+
late Uri successServerUri;
149+
late HttpClientRequestProfile profile;
150+
151+
setUpAll(() async {
152+
successServer = (await HttpServer.bind('localhost', 0))
153+
..listen((request) async {
154+
await request.drain<void>();
155+
request.response.headers.set('Content-Type', 'text/plain');
156+
request.response.headers.set('Content-Length', '11');
157+
final socket = await request.response.detachSocket();
158+
await socket.close();
159+
});
160+
successServerUri = Uri.http('localhost:${successServer.port}');
161+
final client = CronetClientWithProfile.defaultCronetEngine();
162+
163+
try {
164+
await client.post(successServerUri,
165+
headers: {'Content-Type': 'text/plain'}, body: 'Hi');
166+
fail('expected exception');
167+
} on ClientException {
168+
// Expected exception.
169+
}
170+
profile = client.profile!;
171+
});
172+
tearDownAll(() {
173+
successServer.close();
174+
});
175+
176+
test('profile attributes', () {
177+
expect(profile.events, isEmpty);
178+
expect(profile.requestMethod, 'POST');
179+
expect(profile.requestUri, successServerUri.toString());
180+
expect(profile.connectionInfo,
181+
containsPair('package', 'package:cronet_http'));
182+
});
183+
184+
test('request attributes', () {
185+
expect(profile.requestData.bodyBytes, 'Hi'.codeUnits);
186+
expect(profile.requestData.contentLength, 2);
187+
expect(profile.requestData.endTime, isNotNull);
188+
expect(profile.requestData.error, isNull);
189+
expect(
190+
profile.requestData.headers, containsPair('Content-Length', ['2']));
191+
expect(profile.requestData.headers,
192+
containsPair('Content-Type', ['text/plain; charset=utf-8']));
193+
expect(profile.requestData.persistentConnection, isNull);
194+
expect(profile.requestData.proxyDetails, isNull);
195+
expect(profile.requestData.startTime, isNotNull);
196+
});
197+
198+
test('response attributes', () {
199+
expect(profile.responseData.bodyBytes, isEmpty);
200+
expect(profile.responseData.compressionState, isNull);
201+
expect(profile.responseData.contentLength, 11);
202+
expect(profile.responseData.endTime, isNotNull);
203+
expect(profile.responseData.error, startsWith('ClientException:'));
204+
expect(profile.responseData.headers,
205+
containsPair('content-type', ['text/plain']));
206+
expect(profile.responseData.headers,
207+
containsPair('content-length', ['11']));
208+
expect(profile.responseData.isRedirect, false);
209+
expect(profile.responseData.persistentConnection, isNull);
210+
expect(profile.responseData.reasonPhrase, 'OK');
211+
expect(profile.responseData.redirects, isEmpty);
212+
expect(profile.responseData.startTime, isNotNull);
213+
expect(profile.responseData.statusCode, 200);
214+
});
215+
});
216+
217+
group('redirects', () {
218+
late HttpServer successServer;
219+
late Uri successServerUri;
220+
late HttpClientRequestProfile profile;
221+
222+
setUpAll(() async {
223+
successServer = (await HttpServer.bind('localhost', 0))
224+
..listen((request) async {
225+
if (request.requestedUri.pathSegments.isEmpty) {
226+
unawaited(request.response.close());
227+
} else {
228+
final n = int.parse(request.requestedUri.pathSegments.last);
229+
final nextPath = n - 1 == 0 ? '' : '${n - 1}';
230+
unawaited(request.response
231+
.redirect(successServerUri.replace(path: '/$nextPath')));
232+
}
233+
});
234+
successServerUri = Uri.http('localhost:${successServer.port}');
235+
});
236+
tearDownAll(() {
237+
successServer.close();
238+
});
239+
240+
test('no redirects', () async {
241+
final client = CronetClientWithProfile.defaultCronetEngine();
242+
await client.get(successServerUri);
243+
profile = client.profile!;
244+
245+
expect(profile.responseData.redirects, isEmpty);
246+
});
247+
248+
test('follow redirects', () async {
249+
final client = CronetClientWithProfile.defaultCronetEngine();
250+
await client.send(Request('GET', successServerUri.replace(path: '/3'))
251+
..followRedirects = true
252+
..maxRedirects = 4);
253+
profile = client.profile!;
254+
255+
expect(profile.requestData.followRedirects, true);
256+
expect(profile.requestData.maxRedirects, 4);
257+
expect(profile.responseData.isRedirect, false);
258+
259+
expect(profile.responseData.redirects, [
260+
HttpProfileRedirectData(
261+
statusCode: 302,
262+
method: 'GET',
263+
location: successServerUri.replace(path: '/2').toString()),
264+
HttpProfileRedirectData(
265+
statusCode: 302,
266+
method: 'GET',
267+
location: successServerUri.replace(path: '/1').toString()),
268+
HttpProfileRedirectData(
269+
statusCode: 302,
270+
method: 'GET',
271+
location: successServerUri.replace(path: '/').toString(),
272+
)
273+
]);
274+
});
275+
276+
test('no follow redirects', () async {
277+
final client = CronetClientWithProfile.defaultCronetEngine();
278+
await client.send(Request('GET', successServerUri.replace(path: '/3'))
279+
..followRedirects = false);
280+
profile = client.profile!;
281+
282+
expect(profile.requestData.followRedirects, false);
283+
expect(profile.responseData.isRedirect, true);
284+
expect(profile.responseData.redirects, isEmpty);
285+
});
286+
});
287+
});
288+
}

pkgs/cronet_http/example/integration_test/client_test.dart

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,41 @@
44

55
import 'package:cronet_http/cronet_http.dart';
66
import 'package:http_client_conformance_tests/http_client_conformance_tests.dart';
7+
import 'package:http_profile/http_profile.dart';
78
import 'package:integration_test/integration_test.dart';
89
import 'package:test/test.dart';
910

1011
Future<void> testConformance() async {
11-
group(
12-
'default cronet engine',
13-
() => testAll(
14-
CronetClient.defaultCronetEngine,
15-
canStreamRequestBody: false,
16-
canReceiveSetCookieHeaders: true,
17-
canSendCookieHeaders: true,
18-
));
12+
group('default cronet engine', () {
13+
group('profile enabled', () {
14+
final profile = HttpClientRequestProfile.profilingEnabled;
15+
HttpClientRequestProfile.profilingEnabled = true;
16+
try {
17+
testAll(
18+
CronetClient.defaultCronetEngine,
19+
canStreamRequestBody: false,
20+
canReceiveSetCookieHeaders: true,
21+
canSendCookieHeaders: true,
22+
);
23+
} finally {
24+
HttpClientRequestProfile.profilingEnabled = profile;
25+
}
26+
});
27+
group('profile disabled', () {
28+
final profile = HttpClientRequestProfile.profilingEnabled;
29+
HttpClientRequestProfile.profilingEnabled = false;
30+
try {
31+
testAll(
32+
CronetClient.defaultCronetEngine,
33+
canStreamRequestBody: false,
34+
canReceiveSetCookieHeaders: true,
35+
canSendCookieHeaders: true,
36+
);
37+
} finally {
38+
HttpClientRequestProfile.profilingEnabled = profile;
39+
}
40+
});
41+
});
1942

2043
group('from cronet engine', () {
2144
testAll(

pkgs/cronet_http/example/pubspec.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ description: Demonstrates how to use the cronet_http plugin.
44
publish_to: 'none'
55

66
environment:
7-
sdk: ^3.2.0
7+
sdk: ^3.4.0
88

99
dependencies:
1010
cronet_http:
@@ -22,6 +22,7 @@ dev_dependencies:
2222
sdk: flutter
2323
http_client_conformance_tests:
2424
path: ../../http_client_conformance_tests/
25+
http_profile: ^0.1.0
2526
integration_test:
2627
sdk: flutter
2728
test: ^1.23.1

0 commit comments

Comments
 (0)