-
Notifications
You must be signed in to change notification settings - Fork 358
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
pkgs/ok_http: DevTools Networking Support. #1242
Merged
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
8f16111
pkgs/ok_http: add http profile to provide network inspector integration
Anikate-De 83fdd02
add profile enable/disabled tests
Anikate-De 91cb06f
add redirect received callback to kotlin src and generate bindings
Anikate-De c95c2ca
create client_profile_test by reusing the profile test in pkgs/cronet…
Anikate-De 5ce8d60
create test only utility class and implement redirect reception callback
Anikate-De d147df0
update changelog
Anikate-De File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
## 0.1.0-wip | ||
|
||
- Implementation of [`BaseClient`](https://pub.dev/documentation/http/latest/http/BaseClient-class.html) and `send()` method using [`enqueue()` API](https://square.github.io/okhttp/5.x/okhttp/okhttp3/-call/enqueue.html) | ||
- `ok_http` can now send asynchronous requests | ||
- `ok_http` can now send asynchronous requests and stream response bodies. | ||
- Add [DevTools Network View](https://docs.flutter.dev/tools/devtools/network) support. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
288 changes: 288 additions & 0 deletions
288
pkgs/ok_http/example/integration_test/client_profile_test.dart
Anikate-De marked this conversation as resolved.
Show resolved
Hide resolved
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,288 @@ | ||
// 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:flutter_test/flutter_test.dart'; | ||
import 'package:http/http.dart'; | ||
import 'package:http_profile/http_profile.dart'; | ||
import 'package:integration_test/integration_test.dart'; | ||
import 'package:ok_http/ok_http.dart'; | ||
|
||
void main() { | ||
IntegrationTestWidgetsFlutterBinding.ensureInitialized(); | ||
|
||
group('profile', () { | ||
final profilingEnabled = HttpClientRequestProfile.profilingEnabled; | ||
|
||
setUpAll(() { | ||
HttpClientRequestProfile.profilingEnabled = true; | ||
}); | ||
|
||
tearDownAll(() { | ||
HttpClientRequestProfile.profilingEnabled = profilingEnabled; | ||
}); | ||
|
||
group('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 = OkHttpClientWithProfile(); | ||
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:ok_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('failed POST request', () { | ||
late HttpClientRequestProfile profile; | ||
|
||
setUpAll(() async { | ||
final client = OkHttpClientWithProfile(); | ||
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:ok_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 = OkHttpClientWithProfile(); | ||
|
||
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:ok_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 = OkHttpClientWithProfile(); | ||
await client.get(successServerUri); | ||
profile = client.profile!; | ||
|
||
expect(profile.responseData.redirects, isEmpty); | ||
}); | ||
|
||
test('follow redirects', () async { | ||
final client = OkHttpClientWithProfile(); | ||
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 = OkHttpClientWithProfile(); | ||
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); | ||
}); | ||
}); | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In a follow-up PR, would it make sense to migrate the redirect logic to Dart? Actually, would it make sense to make interceptors available in Dart?
Taking a step back, maybe you'd want to expose
OkHttpClient
or a wrapper around that to Dart?With that users can control caching, connection pools, DNS, etc.
I'm not sure what the best way to do that is though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
About migrating the redirection logic to Dart, there are issues with threading while implementing
Interceptor
.Hossein and I have been trying to work our way around this issue, but so far,
RedirectInterceptor
stands out as an example.About exposing configurations to users, I had planned to provide high-level functions (and constructor parameters) within
OkHttpClient
allowing users to tweak the client according to their requirements.For example, if a user wants to alter the timeout for a request, they can either pass the timeout duration in the constructor or in the function:
But yes, a wrapper sounds good! We can use the architecture that
CronetEngine
has.