Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrated cupertino_http with package_http_profile #1079

Merged
merged 34 commits into from
May 23, 2024
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
206a7ff
Populate package:http_profile
derekxu16 Nov 14, 2023
6618444
Address comments
derekxu16 Dec 4, 2023
5d8b3b8
Add examples showing how to use the `cookies` setters
derekxu16 Dec 5, 2023
359a156
Update
derekxu16 Dec 12, 2023
d7f3d95
Integrate cupertino_http with http_profile
brianquinlan Dec 14, 2023
1fd25f0
Add note
brianquinlan Dec 14, 2023
a1feec6
Merge branch 'master' into cupertino_profile
brianquinlan Feb 23, 2024
603d783
Update
brianquinlan Feb 23, 2024
c8ab149
Changes
brianquinlan Feb 26, 2024
4af722d
Update
brianquinlan Mar 21, 2024
b80eaf9
Merge remote-tracking branch 'upstream/master' into cupertino_profile
brianquinlan Mar 21, 2024
57e367f
Changelog/Pubspec
brianquinlan Mar 21, 2024
618f47b
Update CHANGELOG.md
brianquinlan Mar 21, 2024
82590dd
Fix lints
brianquinlan Mar 21, 2024
22c184f
Update cupertino_client.dart
brianquinlan Mar 21, 2024
779f9ac
Add tests
brianquinlan Mar 22, 2024
9f717d1
Update client_profile_test.dart
brianquinlan Mar 22, 2024
671b781
Set content-length request header
brianquinlan Mar 25, 2024
fd44043
Merge branch 'master' into cupertino_profile
brianquinlan Mar 27, 2024
173238b
Update profile
brianquinlan Mar 27, 2024
f6f79be
Merge branch 'master' into cupertino_profile
brianquinlan Mar 27, 2024
969c0f4
Add tests
brianquinlan Mar 27, 2024
61a2d58
Merge branch 'master' into cupertino_profile
brianquinlan Mar 28, 2024
03e8ffc
Add redirect tests
brianquinlan Mar 28, 2024
70f6f77
Merge branch 'master' into cupertino_profile
brianquinlan Mar 28, 2024
14b4e32
Lots of reverts
brianquinlan Mar 28, 2024
ca72168
Update pubspec.yaml
brianquinlan Mar 28, 2024
d3a48a3
Update cupertino_client.dart
brianquinlan Mar 28, 2024
81deb02
Update cupertino_client.dart
brianquinlan Mar 29, 2024
5df70a7
Merge remote-tracking branch 'upstream/master' into cupertino_profile
brianquinlan May 9, 2024
26d048c
Fix versions
brianquinlan May 9, 2024
f1ae74e
Release prep
brianquinlan May 9, 2024
3213eb5
Merge remote-tracking branch 'upstream/master' into cupertino_profile
brianquinlan May 23, 2024
f956003
Review fixes
brianquinlan May 23, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/cupertino.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:
matrix:
# Test on the minimum supported flutter version and the latest
# version.
flutter-version: ["3.19.0", "any"]
flutter-version: ["3.22.0", "any"]
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
- uses: subosito/flutter-action@2783a3f08e1baf891508463f8c6653c258246225
Expand Down
4 changes: 3 additions & 1 deletion pkgs/cupertino_http/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
## 1.4.1-wip
## 1.5.0-wip

* Add integration to the
[DevTools "Network" tab](https://docs.flutter.dev/tools/devtools/network).
* Upgrade to `package:ffigen` 11.0.0.
Copy link
Member

@derekxu16 derekxu16 May 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
[DevTools "Network" tab](https://docs.flutter.dev/tools/devtools/network).
[DevTools Network View](https://docs.flutter.dev/tools/devtools/network).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

* Bring `WebSocket` behavior in line with the documentation by throwing
`WebSocketConnectionClosed` rather than `StateError` when attempting to send
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,39 @@
import 'package:cupertino_http/cupertino_http.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:http_client_conformance_tests/http_client_conformance_tests.dart';
import 'package:http_profile/http_profile.dart';
import 'package:integration_test/integration_test.dart';

void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();

group('defaultSessionConfiguration', () {
testAll(
CupertinoClient.defaultSessionConfiguration,
canReceiveSetCookieHeaders: true,
canSendCookieHeaders: true,
);
group('profile enabled', () {
final profile = HttpClientRequestProfile.profilingEnabled;
HttpClientRequestProfile.profilingEnabled = true;
try {
testAll(
CupertinoClient.defaultSessionConfiguration,
canReceiveSetCookieHeaders: true,
canSendCookieHeaders: true,
);
} finally {
HttpClientRequestProfile.profilingEnabled = profile;
}
});
group('profile disabled', () {
final profile = HttpClientRequestProfile.profilingEnabled;
HttpClientRequestProfile.profilingEnabled = false;
try {
testAll(
CupertinoClient.defaultSessionConfiguration,
canReceiveSetCookieHeaders: true,
canSendCookieHeaders: true,
);
} finally {
HttpClientRequestProfile.profilingEnabled = profile;
}
});
});
group('fromSessionConfiguration', () {
final config = URLSessionConfiguration.ephemeralSessionConfiguration();
Expand Down
335 changes: 335 additions & 0 deletions pkgs/cupertino_http/example/integration_test/client_profile_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,335 @@
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:async';
import 'dart:io';

import 'package:cupertino_http/src/cupertino_client.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart';
import 'package:http_profile/http_profile.dart';
import 'package:integration_test/integration_test.dart';

void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();

group('profile', () {
final profilingEnabled = HttpClientRequestProfile.profilingEnabled;

setUpAll(() {
HttpClientRequestProfile.profilingEnabled = true;
});

tearDownAll(() {
HttpClientRequestProfile.profilingEnabled = profilingEnabled;
});

group('non-streamed POST', () {
late HttpServer successServer;
late Uri successServerUri;
late HttpClientRequestProfile profile;

setUpAll(() async {
successServer = (await HttpServer.bind('localhost', 0))
..listen((request) async {
await request.drain<void>();
request.response.headers.set('Content-Type', 'text/plain');
request.response.headers.set('Content-Length', '11');
request.response.write('Hello World');
await request.response.close();
});
successServerUri = Uri.http('localhost:${successServer.port}');
final client = CupertinoClientWithProfile.defaultSessionConfiguration();
await client.post(successServerUri,
headers: {'Content-Type': 'text/plain'}, body: 'Hi');
profile = client.profile!;
});
tearDownAll(() {
successServer.close();
});

test('profile attributes', () {
expect(profile.events, isEmpty);
expect(profile.requestMethod, 'POST');
expect(profile.requestUri, successServerUri.toString());
expect(profile.connectionInfo,
containsPair('package', 'package:cupertino_http'));
});

test('request attributes', () {
expect(profile.requestData.bodyBytes, 'Hi'.codeUnits);
expect(profile.requestData.contentLength, 2);
expect(profile.requestData.endTime, isNotNull);
expect(profile.requestData.error, isNull);
expect(
profile.requestData.headers, containsPair('Content-Length', ['2']));
expect(profile.requestData.headers,
containsPair('Content-Type', ['text/plain; charset=utf-8']));
expect(profile.requestData.persistentConnection, isNull);
expect(profile.requestData.proxyDetails, isNull);
expect(profile.requestData.startTime, isNotNull);
});

test('response attributes', () {
expect(profile.responseData.bodyBytes, 'Hello World'.codeUnits);
expect(profile.responseData.compressionState, isNull);
expect(profile.responseData.contentLength, 11);
expect(profile.responseData.endTime, isNotNull);
expect(profile.responseData.error, isNull);
expect(profile.responseData.headers,
containsPair('content-type', ['text/plain']));
expect(profile.responseData.headers,
containsPair('content-length', ['11']));
expect(profile.responseData.isRedirect, false);
expect(profile.responseData.persistentConnection, isNull);
expect(profile.responseData.reasonPhrase, 'OK');
expect(profile.responseData.redirects, isEmpty);
expect(profile.responseData.startTime, isNotNull);
expect(profile.responseData.statusCode, 200);
});
});

group('streaming POST request', () {
late HttpServer successServer;
late Uri successServerUri;
late HttpClientRequestProfile profile;

setUpAll(() async {
successServer = (await HttpServer.bind('localhost', 0))
..listen((request) async {
await request.drain<void>();
request.response.headers.set('Content-Type', 'text/plain');
request.response.headers.set('Content-Length', '11');
request.response.write('Hello World');
await request.response.close();
});
successServerUri = Uri.http('localhost:${successServer.port}');
final client = CupertinoClientWithProfile.defaultSessionConfiguration();
final request = StreamedRequest('POST', successServerUri);
final stream = () async* {
for (var i = 0; i < 1000; ++i) {
await Future<void>.delayed(const Duration());
// The request has started but not finished.
expect(client.profile!.requestData.startTime, isNotNull);
expect(client.profile!.requestData.endTime, isNull);
expect(client.profile!.responseData.startTime, isNull);
expect(client.profile!.responseData.endTime, isNull);
yield 'Hello'.codeUnits;
}
}();
unawaited(
request.sink.addStream(stream).then((_) => request.sink.close()));

await client.send(request);
profile = client.profile!;
});
tearDownAll(() {
successServer.close();
});

test('request attributes', () async {
expect(profile.requestData.bodyBytes, ('Hello' * 1000).codeUnits);
expect(profile.requestData.contentLength, isNull);
expect(profile.requestData.endTime, isNotNull);
expect(profile.requestData.startTime, isNotNull);
expect(profile.requestData.headers, isNot(contains('Content-Length')));
});
});

group('failed POST request', () {
late HttpClientRequestProfile profile;

setUpAll(() async {
final client = CupertinoClientWithProfile.defaultSessionConfiguration();
try {
await client.post(Uri.http('thisisnotahost'),
headers: {'Content-Type': 'text/plain'}, body: 'Hi');
fail('expected exception');
} on ClientException {
// Expected exception.
}
profile = client.profile!;
});

test('profile attributes', () {
expect(profile.events, isEmpty);
expect(profile.requestMethod, 'POST');
expect(profile.requestUri, 'http://thisisnotahost');
expect(profile.connectionInfo,
containsPair('package', 'package:cupertino_http'));
});

test('request attributes', () {
expect(profile.requestData.bodyBytes, 'Hi'.codeUnits);
expect(profile.requestData.contentLength, 2);
expect(profile.requestData.endTime, isNotNull);
expect(profile.requestData.error, startsWith('ClientException:'));
expect(
profile.requestData.headers, containsPair('Content-Length', ['2']));
expect(profile.requestData.headers,
containsPair('Content-Type', ['text/plain; charset=utf-8']));
expect(profile.requestData.persistentConnection, isNull);
expect(profile.requestData.proxyDetails, isNull);
expect(profile.requestData.startTime, isNotNull);
});

test('response attributes', () {
expect(profile.responseData.bodyBytes, isEmpty);
expect(profile.responseData.compressionState, isNull);
expect(profile.responseData.contentLength, isNull);
expect(profile.responseData.endTime, isNull);
expect(profile.responseData.error, isNull);
expect(profile.responseData.headers, isNull);
expect(profile.responseData.isRedirect, isNull);
expect(profile.responseData.persistentConnection, isNull);
expect(profile.responseData.reasonPhrase, isNull);
expect(profile.responseData.redirects, isEmpty);
expect(profile.responseData.startTime, isNull);
expect(profile.responseData.statusCode, isNull);
});
});

group('failed POST response', () {
late HttpServer successServer;
late Uri successServerUri;
late HttpClientRequestProfile profile;

setUpAll(() async {
successServer = (await HttpServer.bind('localhost', 0))
..listen((request) async {
await request.drain<void>();
request.response.headers.set('Content-Type', 'text/plain');
request.response.headers.set('Content-Length', '11');
final socket = await request.response.detachSocket();
await socket.close();
});
successServerUri = Uri.http('localhost:${successServer.port}');
final client = CupertinoClientWithProfile.defaultSessionConfiguration();

try {
await client.post(successServerUri,
headers: {'Content-Type': 'text/plain'}, body: 'Hi');
fail('expected exception');
} on ClientException {
// Expected exception.
}
profile = client.profile!;
});
tearDownAll(() {
successServer.close();
});

test('profile attributes', () {
expect(profile.events, isEmpty);
expect(profile.requestMethod, 'POST');
expect(profile.requestUri, successServerUri.toString());
expect(profile.connectionInfo,
containsPair('package', 'package:cupertino_http'));
});

test('request attributes', () {
expect(profile.requestData.bodyBytes, 'Hi'.codeUnits);
expect(profile.requestData.contentLength, 2);
expect(profile.requestData.endTime, isNotNull);
expect(profile.requestData.error, isNull);
expect(
profile.requestData.headers, containsPair('Content-Length', ['2']));
expect(profile.requestData.headers,
containsPair('Content-Type', ['text/plain; charset=utf-8']));
expect(profile.requestData.persistentConnection, isNull);
expect(profile.requestData.proxyDetails, isNull);
expect(profile.requestData.startTime, isNotNull);
});

test('response attributes', () {
expect(profile.responseData.bodyBytes, isEmpty);
expect(profile.responseData.compressionState, isNull);
expect(profile.responseData.contentLength, 11);
expect(profile.responseData.endTime, isNotNull);
expect(profile.responseData.error, startsWith('ClientException:'));
expect(profile.responseData.headers,
containsPair('content-type', ['text/plain']));
expect(profile.responseData.headers,
containsPair('content-length', ['11']));
expect(profile.responseData.isRedirect, false);
expect(profile.responseData.persistentConnection, isNull);
expect(profile.responseData.reasonPhrase, 'OK');
expect(profile.responseData.redirects, isEmpty);
expect(profile.responseData.startTime, isNotNull);
expect(profile.responseData.statusCode, 200);
});
});

group('redirects', () {
late HttpServer successServer;
late Uri successServerUri;
late HttpClientRequestProfile profile;

setUpAll(() async {
successServer = (await HttpServer.bind('localhost', 0))
..listen((request) async {
if (request.requestedUri.pathSegments.isEmpty) {
unawaited(request.response.close());
} else {
final n = int.parse(request.requestedUri.pathSegments.last);
final nextPath = n - 1 == 0 ? '' : '${n - 1}';
unawaited(request.response
.redirect(successServerUri.replace(path: '/$nextPath')));
}
});
successServerUri = Uri.http('localhost:${successServer.port}');
});
tearDownAll(() {
successServer.close();
});

test('no redirects', () async {
final client = CupertinoClientWithProfile.defaultSessionConfiguration();
await client.get(successServerUri);
profile = client.profile!;

expect(profile.responseData.redirects, isEmpty);
});

test('follow redirects', () async {
final client = CupertinoClientWithProfile.defaultSessionConfiguration();
await client.send(Request('GET', successServerUri.replace(path: '/3'))
..followRedirects = true
..maxRedirects = 4);
profile = client.profile!;

expect(profile.requestData.followRedirects, true);
expect(profile.requestData.maxRedirects, 4);
expect(profile.responseData.isRedirect, false);

expect(profile.responseData.redirects, [
HttpProfileRedirectData(
statusCode: 302,
method: 'GET',
location: successServerUri.replace(path: '/2').toString()),
HttpProfileRedirectData(
statusCode: 302,
method: 'GET',
location: successServerUri.replace(path: '/1').toString()),
HttpProfileRedirectData(
statusCode: 302,
method: 'GET',
location: successServerUri.replace(path: '/').toString(),
)
]);
});

test('no follow redirects', () async {
final client = CupertinoClientWithProfile.defaultSessionConfiguration();
await client.send(Request('GET', successServerUri.replace(path: '/3'))
..followRedirects = false);
profile = client.profile!;

expect(profile.requestData.followRedirects, false);
expect(profile.responseData.isRedirect, true);
expect(profile.responseData.redirects, isEmpty);
});
});
});
}