diff --git a/.github/workflows/health.yaml b/.github/workflows/health.yaml new file mode 100644 index 0000000000..1c904405a2 --- /dev/null +++ b/.github/workflows/health.yaml @@ -0,0 +1,14 @@ +name: Health +on: + pull_request: + branches: [ master ] + types: [opened, synchronize, reopened, labeled, unlabeled] + +jobs: + health: + uses: dart-lang/ecosystem/.github/workflows/health.yaml@main + with: + ignore_license: "**.g.dart" + sdk: dev + permissions: + pull-requests: write diff --git a/.github/workflows/post_summaries.yaml b/.github/workflows/post_summaries.yaml new file mode 100644 index 0000000000..e082efe95a --- /dev/null +++ b/.github/workflows/post_summaries.yaml @@ -0,0 +1,17 @@ +name: Comment on the pull request + +on: + # Trigger this workflow after the Health workflow completes. This workflow will have permissions to + # do things like create comments on the PR, even if the original workflow couldn't. + workflow_run: + workflows: + - Health + - Publish + types: + - completed + +jobs: + upload: + uses: dart-lang/ecosystem/.github/workflows/post_summaries.yaml@main + permissions: + pull-requests: write diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index f0cc574ccc..151e5e4d16 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -12,6 +12,8 @@ jobs: publish: if: ${{ github.repository_owner == 'dart-lang' }} uses: dart-lang/ecosystem/.github/workflows/publish.yaml@main + with: + write-comments: false permissions: id-token: write # Required for authentication using OIDC pull-requests: write # Required for writing the pull request note diff --git a/pkgs/cronet_http/CHANGELOG.md b/pkgs/cronet_http/CHANGELOG.md index 0ce43fceb3..14dcf79b65 100644 --- a/pkgs/cronet_http/CHANGELOG.md +++ b/pkgs/cronet_http/CHANGELOG.md @@ -1,3 +1,8 @@ +## 1.3.3-wip + +* Throw `ClientException` if `CronetClient.send` runs out of Java heap while + allocating memory for the request body. + ## 1.3.2 * Upgrade `package:jni` to 0.10.1 and `package:jnigen` to 0.10.0 to fix method diff --git a/pkgs/cronet_http/lib/src/cronet_client.dart b/pkgs/cronet_http/lib/src/cronet_client.dart index f55617eeb1..440de8ae35 100644 --- a/pkgs/cronet_http/lib/src/cronet_client.dart +++ b/pkgs/cronet_http/lib/src/cronet_client.dart @@ -392,8 +392,22 @@ class CronetClient extends BaseClient { headers.forEach((k, v) => builder.addHeader(k.toJString(), v.toJString())); if (body.isNotEmpty) { + final JByteBuffer data; + try { + data = body.toJByteBuffer(); + } on JniException catch (e) { + // There are no unit tests for this code. You can verify this behavior + // manually by incrementally increasing the amount of body data in + // `CronetClient.post` until you get this exception. + if (e.message.contains('java.lang.OutOfMemoryError:')) { + throw ClientException( + 'Not enough memory for request body: ${e.message}', request.url); + } + rethrow; + } + builder.setUploadDataProvider( - jb.UploadDataProviders.create2(body.toJByteBuffer()), _executor); + jb.UploadDataProviders.create2(data), _executor); } builder.build().start(); return responseCompleter.future; diff --git a/pkgs/cronet_http/pubspec.yaml b/pkgs/cronet_http/pubspec.yaml index 19e5e8509f..ee14e309a9 100644 --- a/pkgs/cronet_http/pubspec.yaml +++ b/pkgs/cronet_http/pubspec.yaml @@ -1,5 +1,5 @@ name: cronet_http -version: 1.3.2 +version: 1.3.3-wip description: >- An Android Flutter plugin that provides access to the Cronet HTTP client. repository: https://github.com/dart-lang/http/tree/master/pkgs/cronet_http diff --git a/pkgs/cupertino_http/CHANGELOG.md b/pkgs/cupertino_http/CHANGELOG.md index 4cd4a5e8cd..4667f6ab1f 100644 --- a/pkgs/cupertino_http/CHANGELOG.md +++ b/pkgs/cupertino_http/CHANGELOG.md @@ -1,3 +1,5 @@ +## 1.5.2-wip + ## 1.5.1 * Allow `1000` as a `code` argument in `CupertinoWebSocket.close`. diff --git a/pkgs/cupertino_http/macos/Classes/CUPHTTPClientDelegate.m b/pkgs/cupertino_http/darwin/Classes/CUPHTTPClientDelegate.m similarity index 100% rename from pkgs/cupertino_http/macos/Classes/CUPHTTPClientDelegate.m rename to pkgs/cupertino_http/darwin/Classes/CUPHTTPClientDelegate.m diff --git a/pkgs/cupertino_http/macos/Classes/CUPHTTPCompletionHelper.m b/pkgs/cupertino_http/darwin/Classes/CUPHTTPCompletionHelper.m similarity index 100% rename from pkgs/cupertino_http/macos/Classes/CUPHTTPCompletionHelper.m rename to pkgs/cupertino_http/darwin/Classes/CUPHTTPCompletionHelper.m diff --git a/pkgs/cupertino_http/macos/Classes/CUPHTTPForwardedDelegate.m b/pkgs/cupertino_http/darwin/Classes/CUPHTTPForwardedDelegate.m similarity index 100% rename from pkgs/cupertino_http/macos/Classes/CUPHTTPForwardedDelegate.m rename to pkgs/cupertino_http/darwin/Classes/CUPHTTPForwardedDelegate.m diff --git a/pkgs/cupertino_http/macos/Classes/CUPHTTPStreamToNSInputStreamAdapter.m b/pkgs/cupertino_http/darwin/Classes/CUPHTTPStreamToNSInputStreamAdapter.m similarity index 100% rename from pkgs/cupertino_http/macos/Classes/CUPHTTPStreamToNSInputStreamAdapter.m rename to pkgs/cupertino_http/darwin/Classes/CUPHTTPStreamToNSInputStreamAdapter.m diff --git a/pkgs/cupertino_http/macos/Classes/dart_api_dl.c b/pkgs/cupertino_http/darwin/Classes/dart_api_dl.c similarity index 100% rename from pkgs/cupertino_http/macos/Classes/dart_api_dl.c rename to pkgs/cupertino_http/darwin/Classes/dart_api_dl.c diff --git a/pkgs/cupertino_http/macos/cupertino_http.podspec b/pkgs/cupertino_http/darwin/cupertino_http.podspec similarity index 84% rename from pkgs/cupertino_http/macos/cupertino_http.podspec rename to pkgs/cupertino_http/darwin/cupertino_http.podspec index 86b7201978..134c075c55 100644 --- a/pkgs/cupertino_http/macos/cupertino_http.podspec +++ b/pkgs/cupertino_http/darwin/cupertino_http.podspec @@ -18,11 +18,13 @@ Pod::Spec.new do |s| # paths, so Classes contains a forwarder C file that relatively imports # `../src/*` so that the C sources can be shared among all target platforms. s.source = { :path => '.' } - s.source_files = 'Classes/**/*' - s.dependency 'FlutterMacOS' + s.source_files = 'cupertino_http/Sources/cupertino_http/**/*' + s.ios.dependency 'Flutter' + s.osx.dependency 'FlutterMacOS' + s.ios.deployment_target = '12.0' + s.osx.deployment_target = '10.14' s.requires_arc = [] - s.platform = :osx, '10.14' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } s.swift_version = '5.0' end diff --git a/pkgs/cupertino_http/ios/cupertino_http.podspec b/pkgs/cupertino_http/ios/cupertino_http.podspec deleted file mode 100644 index fdfe0d960b..0000000000 --- a/pkgs/cupertino_http/ios/cupertino_http.podspec +++ /dev/null @@ -1,29 +0,0 @@ -# -# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. -# Run `pod lib lint cupertino_http.podspec` to validate before publishing. -# -Pod::Spec.new do |s| - s.name = 'cupertino_http' - s.version = '0.0.1' - s.summary = 'Flutter Foundation URL Loading System' - s.description = <<-DESC - A Flutter plugin for accessing the Foundation URL Loading System. - DESC - s.homepage = 'https://github.com/dart-lang/http/tree/master/pkgs/cupertino_http' - s.license = { :type => 'BSD', :file => '../LICENSE' } - s.author = { 'TODO' => 'use-valid-author' } - - # This will ensure the source files in Classes/ are included in the native - # builds of apps using this FFI plugin. Podspec does not support relative - # paths, so Classes contains a forwarder C file that relatively imports - # `../src/*` so that the C sources can be shared among all target platforms. - s.source = { :path => '.' } - s.source_files = 'cupertino_http/Sources/cupertino_http/**/*' - s.dependency 'Flutter' - s.platform = :ios, '12.0' - s.requires_arc = [] - - # Flutter.framework does not contain a i386 slice. - s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } - s.swift_version = '5.0' -end diff --git a/pkgs/cupertino_http/lib/src/cupertino_web_socket.dart b/pkgs/cupertino_http/lib/src/cupertino_web_socket.dart index b374dd9907..dc4d748c14 100644 --- a/pkgs/cupertino_http/lib/src/cupertino_web_socket.dart +++ b/pkgs/cupertino_http/lib/src/cupertino_web_socket.dart @@ -23,8 +23,9 @@ class ConnectionException extends WebSocketException { /// A [WebSocket] implemented using the /// [NSURLSessionWebSocketTask API](https://developer.apple.com/documentation/foundation/nsurlsessionwebsockettask). /// -/// NOTE: the [WebSocket] interface is currently experimental and may change in -/// the future. +/// > [!NOTE] +/// > The [WebSocket] interface is currently experimental and may change in the +/// > future. /// /// ```dart /// import 'package:cupertino_http/cupertino_http.dart'; @@ -47,6 +48,11 @@ class ConnectionException extends WebSocketException { /// }); /// } /// ``` +/// +/// > [!TIP] +/// > [`AdapterWebSocketChannel`](https://pub.dev/documentation/web_socket_channel/latest/adapter_web_socket_channel/AdapterWebSocketChannel-class.html) +/// > can be used to adapt a [CupertinoWebSocket] into a +/// > [`WebSocketChannel`](https://pub.dev/documentation/web_socket_channel/latest/web_socket_channel/WebSocketChannel-class.html). class CupertinoWebSocket implements WebSocket { /// Create a new WebSocket connection using the /// [NSURLSessionWebSocketTask API](https://developer.apple.com/documentation/foundation/nsurlsessionwebsockettask). diff --git a/pkgs/cupertino_http/pubspec.yaml b/pkgs/cupertino_http/pubspec.yaml index 94a9da8d8c..cff2546b3e 100644 --- a/pkgs/cupertino_http/pubspec.yaml +++ b/pkgs/cupertino_http/pubspec.yaml @@ -1,5 +1,5 @@ name: cupertino_http -version: 1.5.1 +version: 1.5.2-wip description: >- A macOS/iOS Flutter plugin that provides access to the Foundation URL Loading System. @@ -27,5 +27,7 @@ flutter: platforms: ios: ffiPlugin: true + sharedDarwinSource: true macos: ffiPlugin: true + sharedDarwinSource: true diff --git a/pkgs/http/CHANGELOG.md b/pkgs/http/CHANGELOG.md index 85dccd7313..18e71333b7 100644 --- a/pkgs/http/CHANGELOG.md +++ b/pkgs/http/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.2.3-wip + +* Fixed unintended HTML tags in doc comments. + ## 1.2.2 * Require package `web: '>=0.5.0 <2.0.0'`. diff --git a/pkgs/http/lib/http.dart b/pkgs/http/lib/http.dart index da35b23a87..317a2c17f3 100644 --- a/pkgs/http/lib/http.dart +++ b/pkgs/http/lib/http.dart @@ -50,15 +50,15 @@ Future get(Uri url, {Map? headers}) => /// Sends an HTTP POST request with the given headers and body to the given URL. /// -/// [body] sets the body of the request. It can be a [String], a [List] or -/// a [Map]. If it's a String, it's encoded using [encoding] and -/// used as the body of the request. The content-type of the request will +/// [body] sets the body of the request. It can be a `String`, a `List` or +/// a `Map`. If it's a `String`, it's encoded using [encoding] +/// and used as the body of the request. The content-type of the request will /// default to "text/plain". /// -/// If [body] is a List, it's used as a list of bytes for the body of the +/// If [body] is a `List`, it's used as a list of bytes for the body of the /// request. /// -/// If [body] is a Map, it's encoded as form fields using [encoding]. The +/// If [body] is a `Map`, it's encoded as form fields using [encoding]. The /// content-type of the request will be set to /// `"application/x-www-form-urlencoded"`; this cannot be overridden. /// @@ -73,15 +73,15 @@ Future post(Uri url, /// Sends an HTTP PUT request with the given headers and body to the given URL. /// -/// [body] sets the body of the request. It can be a [String], a [List] or -/// a [Map]. If it's a String, it's encoded using [encoding] and -/// used as the body of the request. The content-type of the request will +/// [body] sets the body of the request. It can be a `String`, a `List` or +/// a `Map`. If it's a `String`, it's encoded using [encoding] +/// and used as the body of the request. The content-type of the request will /// default to "text/plain". /// -/// If [body] is a List, it's used as a list of bytes for the body of the +/// If [body] is a `List`, it's used as a list of bytes for the body of the /// request. /// -/// If [body] is a Map, it's encoded as form fields using [encoding]. The +/// If [body] is a `Map`, it's encoded as form fields using [encoding]. The /// content-type of the request will be set to /// `"application/x-www-form-urlencoded"`; this cannot be overridden. /// @@ -97,15 +97,15 @@ Future put(Uri url, /// Sends an HTTP PATCH request with the given headers and body to the given /// URL. /// -/// [body] sets the body of the request. It can be a [String], a [List] or -/// a [Map]. If it's a String, it's encoded using [encoding] and -/// used as the body of the request. The content-type of the request will +/// [body] sets the body of the request. It can be a `String`, a `List` or +/// a `Map`. If it's a `String`, it's encoded using [encoding] +/// and used as the body of the request. The content-type of the request will /// default to "text/plain". /// -/// If [body] is a List, it's used as a list of bytes for the body of the +/// If [body] is a `List`, it's used as a list of bytes for the body of the /// request. /// -/// If [body] is a Map, it's encoded as form fields using [encoding]. The +/// If [body] is a `Map`, it's encoded as form fields using [encoding]. The /// content-type of the request will be set to /// `"application/x-www-form-urlencoded"`; this cannot be overridden. /// diff --git a/pkgs/http/lib/src/base_response.dart b/pkgs/http/lib/src/base_response.dart index 0527461dbb..39934ceea9 100644 --- a/pkgs/http/lib/src/base_response.dart +++ b/pkgs/http/lib/src/base_response.dart @@ -117,9 +117,9 @@ var _headerSplitter = RegExp(r'[ \t]*,[ \t]*'); /// /// Set-Cookie strings can contain commas. In particular, the following /// productions defined in RFC-6265, section 4.1.1: -/// - e.g. "Expires=Sun, 06 Nov 1994 08:49:37 GMT" -/// - e.g. "Path=somepath," -/// - e.g. "AnyString,Really," +/// - `` e.g. "Expires=Sun, 06 Nov 1994 08:49:37 GMT" +/// - `` e.g. "Path=somepath," +/// - `` e.g. "AnyString,Really," /// /// Some values are ambiguous e.g. /// "Set-Cookie: lang=en; Path=/foo/" @@ -128,8 +128,9 @@ var _headerSplitter = RegExp(r'[ \t]*,[ \t]*'); /// "Set-Cookie: lang=en; Path=/foo/,SID=x23" /// would both be result in `response.headers` => "lang=en; Path=/foo/,SID=x23" /// -/// The idea behind this regex is that ",=" is more likely to -/// start a new then be part of or . +/// The idea behind this regex is that `,=` is more likely to +/// start a new `` than be part of `` or +/// ``. /// /// See https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.1 var _setCookieSplitter = RegExp(r'[ \t]*,[ \t]*(?=[' + _tokenChars + r']+=)'); diff --git a/pkgs/http/lib/src/client.dart b/pkgs/http/lib/src/client.dart index 6de5698104..8ee554a241 100644 --- a/pkgs/http/lib/src/client.dart +++ b/pkgs/http/lib/src/client.dart @@ -54,17 +54,17 @@ abstract interface class Client { /// Sends an HTTP POST request with the given headers and body to the given /// URL. /// - /// [body] sets the body of the request. It can be a [String], a [List] - /// or a [Map]. + /// [body] sets the body of the request. It can be a `String`, a `List` + /// or a `Map`. /// - /// If [body] is a String, it's encoded using [encoding] and used as the body - /// of the request. The content-type of the request will default to + /// If [body] is a `String`, it's encoded using [encoding] and used as the + /// body of the request. The content-type of the request will default to /// "text/plain". /// - /// If [body] is a List, it's used as a list of bytes for the body of the + /// If [body] is a `List`, it's used as a list of bytes for the body of the /// request. /// - /// If [body] is a Map, it's encoded as form fields using [encoding]. The + /// If [body] is a `Map`, it's encoded as form fields using [encoding]. The /// content-type of the request will be set to /// `"application/x-www-form-urlencoded"`; this cannot be overridden. /// @@ -77,15 +77,15 @@ abstract interface class Client { /// Sends an HTTP PUT request with the given headers and body to the given /// URL. /// - /// [body] sets the body of the request. It can be a [String], a [List] - /// or a [Map]. If it's a String, it's encoded using + /// [body] sets the body of the request. It can be a `String`, a `List` + /// or a `Map`. If it's a `String`, it's encoded using /// [encoding] and used as the body of the request. The content-type of the /// request will default to "text/plain". /// - /// If [body] is a List, it's used as a list of bytes for the body of the + /// If [body] is a `List`, it's used as a list of bytes for the body of the /// request. /// - /// If [body] is a Map, it's encoded as form fields using [encoding]. The + /// If [body] is a `Map`, it's encoded as form fields using [encoding]. The /// content-type of the request will be set to /// `"application/x-www-form-urlencoded"`; this cannot be overridden. /// @@ -98,15 +98,15 @@ abstract interface class Client { /// Sends an HTTP PATCH request with the given headers and body to the given /// URL. /// - /// [body] sets the body of the request. It can be a [String], a [List] - /// or a [Map]. If it's a String, it's encoded using + /// [body] sets the body of the request. It can be a `String`, a `List` + /// or a `Map`. If it's a `String`, it's encoded using /// [encoding] and used as the body of the request. The content-type of the /// request will default to "text/plain". /// - /// If [body] is a List, it's used as a list of bytes for the body of the + /// If [body] is a `List`, it's used as a list of bytes for the body of the /// request. /// - /// If [body] is a Map, it's encoded as form fields using [encoding]. The + /// If [body] is a `Map`, it's encoded as form fields using [encoding]. The /// content-type of the request will be set to /// `"application/x-www-form-urlencoded"`; this cannot be overridden. /// diff --git a/pkgs/http/pubspec.yaml b/pkgs/http/pubspec.yaml index 7039e30838..8c37cff9b1 100644 --- a/pkgs/http/pubspec.yaml +++ b/pkgs/http/pubspec.yaml @@ -1,5 +1,5 @@ name: http -version: 1.2.2 +version: 1.2.3-wip description: A composable, multi-platform, Future-based API for HTTP requests. repository: https://github.com/dart-lang/http/tree/master/pkgs/http diff --git a/pkgs/http_client_conformance_tests/lib/src/compressed_response_body_server.dart b/pkgs/http_client_conformance_tests/lib/src/compressed_response_body_server.dart index 17f8897cc8..bb4b95d1e8 100644 --- a/pkgs/http_client_conformance_tests/lib/src/compressed_response_body_server.dart +++ b/pkgs/http_client_conformance_tests/lib/src/compressed_response_body_server.dart @@ -13,7 +13,7 @@ import 'package:stream_channel/stream_channel.dart'; /// On Startup: /// - send port /// On Request Received: -/// - send headers as Map> +/// - send headers as `Map>` /// When Receive Anything: /// - exit void hybridMain(StreamChannel channel) async { diff --git a/pkgs/http_client_conformance_tests/lib/src/multipart_server.dart b/pkgs/http_client_conformance_tests/lib/src/multipart_server.dart index 90e4d8b1a7..3072090dec 100644 --- a/pkgs/http_client_conformance_tests/lib/src/multipart_server.dart +++ b/pkgs/http_client_conformance_tests/lib/src/multipart_server.dart @@ -23,12 +23,17 @@ void hybridMain(StreamChannel channel) async { server = (await HttpServer.bind('localhost', 0)) ..listen((request) async { request.response.headers.set('Access-Control-Allow-Origin', '*'); + request.response + ..contentLength = 0 + ..statusCode = HttpStatus.ok; + if (request.method == 'OPTIONS') { // Handle a CORS preflight request: // https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#preflighted_requests request.response.headers ..set('Access-Control-Allow-Methods', '*') ..set('Access-Control-Allow-Headers', '*'); + await request.response.close(); } else { final headers = >{}; request.headers.forEach((field, value) { @@ -36,9 +41,9 @@ void hybridMain(StreamChannel channel) async { }); final body = await const Utf8Decoder().bind(request).fold('', (x, y) => '$x$y'); - channel.sink.add((headers, body)); + await request.response.close(); + channel.sink.add([headers, body]); } - unawaited(request.response.close()); }); channel.sink.add(server.port); diff --git a/pkgs/http_client_conformance_tests/lib/src/multipart_tests.dart b/pkgs/http_client_conformance_tests/lib/src/multipart_tests.dart index c9ecd90f82..0277e30935 100644 --- a/pkgs/http_client_conformance_tests/lib/src/multipart_tests.dart +++ b/pkgs/http_client_conformance_tests/lib/src/multipart_tests.dart @@ -34,8 +34,9 @@ void testMultipartRequests(Client client, request.files.add(MultipartFile.fromString('file1', 'Hello World')); await client.send(request); - final (headers, body) = - await httpServerQueue.next as (Map>, String); + final serverRequest = await httpServerQueue.next as List; + final headers = (serverRequest[0] as Map).cast>(); + final body = serverRequest[1] as String; expect(headers['content-length']!.single, '${request.contentLength}'); expect(headers['content-type']!.single, startsWith('multipart/form-data; boundary=')); diff --git a/pkgs/http_client_conformance_tests/lib/src/redirect_server.dart b/pkgs/http_client_conformance_tests/lib/src/redirect_server.dart index cf1fafddb8..2813e32239 100644 --- a/pkgs/http_client_conformance_tests/lib/src/redirect_server.dart +++ b/pkgs/http_client_conformance_tests/lib/src/redirect_server.dart @@ -18,7 +18,7 @@ import 'package:stream_channel/stream_channel.dart'; /// ".../9" | ".../8" /// ... | ... /// ".../1" | "/" -/// "/" | <200 return> +/// "/" | <200 return> void hybridMain(StreamChannel channel) async { late HttpServer server; diff --git a/pkgs/http_client_conformance_tests/lib/src/request_headers_server.dart b/pkgs/http_client_conformance_tests/lib/src/request_headers_server.dart index c443ad0034..ad3fdfe0cc 100644 --- a/pkgs/http_client_conformance_tests/lib/src/request_headers_server.dart +++ b/pkgs/http_client_conformance_tests/lib/src/request_headers_server.dart @@ -13,7 +13,7 @@ import 'package:stream_channel/stream_channel.dart'; /// On Startup: /// - send port /// On Request Received: -/// - send headers as Map> +/// - send headers as `Map>` /// When Receive Anything: /// - exit void hybridMain(StreamChannel channel) async { diff --git a/pkgs/http_profile/CHANGELOG.md b/pkgs/http_profile/CHANGELOG.md index b9455a4ed6..fdcad42253 100644 --- a/pkgs/http_profile/CHANGELOG.md +++ b/pkgs/http_profile/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.1-wip + +* Fixed unintended HTML tags in doc comments. + ## 0.1.0 * Initial **experimental** release. diff --git a/pkgs/http_profile/lib/src/utils.dart b/pkgs/http_profile/lib/src/utils.dart index 0225f87f39..780bb446b8 100644 --- a/pkgs/http_profile/lib/src/utils.dart +++ b/pkgs/http_profile/lib/src/utils.dart @@ -14,9 +14,9 @@ var _headerSplitter = RegExp(r'[ \t]*,[ \t]*'); /// /// Set-Cookie strings can contain commas. In particular, the following /// productions defined in RFC-6265, section 4.1.1: -/// - e.g. "Expires=Sun, 06 Nov 1994 08:49:37 GMT" -/// - e.g. "Path=somepath," -/// - e.g. "AnyString,Really," +/// - `` e.g. "Expires=Sun, 06 Nov 1994 08:49:37 GMT" +/// - `` e.g. "Path=somepath," +/// - `` e.g. "AnyString,Really," /// /// Some values are ambiguous e.g. /// "Set-Cookie: lang=en; Path=/foo/" @@ -25,8 +25,9 @@ var _headerSplitter = RegExp(r'[ \t]*,[ \t]*'); /// "Set-Cookie: lang=en; Path=/foo/,SID=x23" /// would both be result in `response.headers` => "lang=en; Path=/foo/,SID=x23" /// -/// The idea behind this regex is that ",=" is more likely to -/// start a new then be part of or . +/// The idea behind this regex is that `,=` is more likely to +/// start a new `` than be part of `` or +/// ``. /// /// See https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.1 var _setCookieSplitter = RegExp(r'[ \t]*,[ \t]*(?=[' + _tokenChars + r']+=)'); diff --git a/pkgs/http_profile/pubspec.yaml b/pkgs/http_profile/pubspec.yaml index d55dc32260..1184789c8a 100644 --- a/pkgs/http_profile/pubspec.yaml +++ b/pkgs/http_profile/pubspec.yaml @@ -3,7 +3,7 @@ description: >- A library used by HTTP client authors to integrate with the DevTools Network View. repository: https://github.com/dart-lang/http/tree/master/pkgs/http_profile -version: 0.1.0 +version: 0.1.1-wip environment: sdk: ^3.4.0 diff --git a/pkgs/ok_http/CHANGELOG.md b/pkgs/ok_http/CHANGELOG.md index 557f19db20..a6a2fd3f4e 100644 --- a/pkgs/ok_http/CHANGELOG.md +++ b/pkgs/ok_http/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.1.1-wip + +- `OkHttpClient` now receives an `OkHttpClientConfiguration` to configure the client on a per-call basis. +- `OkHttpClient` supports setting four types of timeouts: [`connectTimeout`](https://square.github.io/okhttp/5.x/okhttp/okhttp3/-ok-http-client/-builder/connect-timeout.html), [`readTimeout`](https://square.github.io/okhttp/5.x/okhttp/okhttp3/-ok-http-client/-builder/read-timeout.html), [`writeTimeout`](https://square.github.io/okhttp/5.x/okhttp/okhttp3/-ok-http-client/-builder/write-timeout.html), and [`callTimeout`](https://square.github.io/okhttp/5.x/okhttp/okhttp3/-ok-http-client/-builder/call-timeout.html), using the `OkHttpClientConfiguration`. + ## 0.1.0 - 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) diff --git a/pkgs/ok_http/example/integration_test/client_configuration_test.dart b/pkgs/ok_http/example/integration_test/client_configuration_test.dart new file mode 100644 index 0000000000..2cee14afb6 --- /dev/null +++ b/pkgs/ok_http/example/integration_test/client_configuration_test.dart @@ -0,0 +1,98 @@ +// 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:io'; + +import 'package:http/http.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:ok_http/ok_http.dart'; +import 'package:test/test.dart'; + +void testTimeouts() { + group('timeouts', () { + group('call timeout', () { + late HttpServer server; + + setUp(() async { + server = (await HttpServer.bind('localhost', 0)) + ..listen((request) async { + // Add a delay of `n` seconds for URI `http://localhost:port/n` + final delay = int.parse(request.requestedUri.pathSegments.last); + await Future.delayed(Duration(seconds: delay)); + + await request.drain(); + await request.response.close(); + }); + }); + tearDown(() { + server.close(); + }); + + test('exceeded', () { + final client = OkHttpClient( + configuration: const OkHttpClientConfiguration( + callTimeout: Duration(milliseconds: 500), + ), + ); + expect( + () async { + await client.get(Uri.parse('http://localhost:${server.port}/1')); + }, + throwsA( + isA().having( + (exception) => exception.message, + 'message', + startsWith('java.io.InterruptedIOException'), + ), + ), + ); + }); + + test('not exceeded', () async { + final client = OkHttpClient( + configuration: const OkHttpClientConfiguration( + callTimeout: Duration(milliseconds: 1500), + ), + ); + final response = await client.send( + Request( + 'GET', + Uri.http('localhost:${server.port}', '1'), + ), + ); + + expect(response.statusCode, 200); + expect(response.contentLength, 0); + }); + + test('not set', () async { + final client = OkHttpClient(); + + expect( + () async { + await client.send( + Request( + 'GET', + Uri.http('localhost:${server.port}', '11'), + ), + ); + }, + throwsA( + isA().having( + (exception) => exception.message, + 'message', + startsWith('java.net.SocketTimeoutException'), + ), + ), + ); + }); + }); + }); +} + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + testTimeouts(); +} diff --git a/pkgs/ok_http/jnigen.yaml b/pkgs/ok_http/jnigen.yaml index 7f63d6c2d9..2e7f87a45a 100644 --- a/pkgs/ok_http/jnigen.yaml +++ b/pkgs/ok_http/jnigen.yaml @@ -36,6 +36,7 @@ classes: - "com.example.ok_http.WebSocketListenerProxy" - "okio.ByteString" - "com.example.ok_http.WebSocketInterceptor" + - "java.util.concurrent.TimeUnit" # Exclude the deprecated methods listed below # They cause syntax errors during the `dart format` step of JNIGen. diff --git a/pkgs/ok_http/lib/src/jni/bindings.dart b/pkgs/ok_http/lib/src/jni/bindings.dart index 66f0078d9a..608384389a 100644 --- a/pkgs/ok_http/lib/src/jni/bindings.dart +++ b/pkgs/ok_http/lib/src/jni/bindings.dart @@ -5008,7 +5008,7 @@ class OkHttpClient_Builder extends jni.JObject { /// The returned object must be released after use, by calling the [release] method. OkHttpClient_Builder callTimeout( int j, - jni.JObject timeUnit, + TimeUnit timeUnit, ) { return _callTimeout(reference.pointer, _id_callTimeout as jni.JMethodIDPtr, j, timeUnit.reference.pointer) @@ -5061,7 +5061,7 @@ class OkHttpClient_Builder extends jni.JObject { /// The returned object must be released after use, by calling the [release] method. OkHttpClient_Builder connectTimeout( int j, - jni.JObject timeUnit, + TimeUnit timeUnit, ) { return _connectTimeout( reference.pointer, @@ -5117,7 +5117,7 @@ class OkHttpClient_Builder extends jni.JObject { /// The returned object must be released after use, by calling the [release] method. OkHttpClient_Builder readTimeout( int j, - jni.JObject timeUnit, + TimeUnit timeUnit, ) { return _readTimeout(reference.pointer, _id_readTimeout as jni.JMethodIDPtr, j, timeUnit.reference.pointer) @@ -5170,7 +5170,7 @@ class OkHttpClient_Builder extends jni.JObject { /// The returned object must be released after use, by calling the [release] method. OkHttpClient_Builder writeTimeout( int j, - jni.JObject timeUnit, + TimeUnit timeUnit, ) { return _writeTimeout(reference.pointer, _id_writeTimeout as jni.JMethodIDPtr, j, timeUnit.reference.pointer) @@ -5223,7 +5223,7 @@ class OkHttpClient_Builder extends jni.JObject { /// The returned object must be released after use, by calling the [release] method. OkHttpClient_Builder pingInterval( int j, - jni.JObject timeUnit, + TimeUnit timeUnit, ) { return _pingInterval(reference.pointer, _id_pingInterval as jni.JMethodIDPtr, j, timeUnit.reference.pointer) @@ -8204,7 +8204,7 @@ class ConnectionPool extends jni.JObject { factory ConnectionPool.new1( int i, int j, - jni.JObject timeUnit, + TimeUnit timeUnit, ) { return ConnectionPool.fromReference(_new1(_class.reference.pointer, _id_new1 as jni.JMethodIDPtr, i, j, timeUnit.reference.pointer) @@ -8836,7 +8836,7 @@ class ExecutorService extends jni.JObject { /// from: public abstract boolean awaitTermination(long j, java.util.concurrent.TimeUnit timeUnit) bool awaitTermination( int j, - jni.JObject timeUnit, + TimeUnit timeUnit, ) { return _awaitTermination( reference.pointer, @@ -8902,7 +8902,7 @@ class ExecutorService extends jni.JObject { $a[0] .castTo(const jni.JLongType(), releaseOriginal: true) .longValue(releaseOriginal: true), - $a[1].castTo(const jni.JObjectType(), releaseOriginal: true), + $a[1].castTo(const $TimeUnitType(), releaseOriginal: true), ); return jni.JBoolean($r).reference.toPointer(); } @@ -8945,14 +8945,14 @@ abstract interface class $ExecutorServiceImpl { required jni.JList Function() shutdownNow, required bool Function() isShutdown, required bool Function() isTerminated, - required bool Function(int j, jni.JObject timeUnit) awaitTermination, + required bool Function(int j, TimeUnit timeUnit) awaitTermination, }) = _$ExecutorServiceImpl; void shutdown(); jni.JList shutdownNow(); bool isShutdown(); bool isTerminated(); - bool awaitTermination(int j, jni.JObject timeUnit); + bool awaitTermination(int j, TimeUnit timeUnit); } class _$ExecutorServiceImpl implements $ExecutorServiceImpl { @@ -8961,7 +8961,7 @@ class _$ExecutorServiceImpl implements $ExecutorServiceImpl { required jni.JList Function() shutdownNow, required bool Function() isShutdown, required bool Function() isTerminated, - required bool Function(int j, jni.JObject timeUnit) awaitTermination, + required bool Function(int j, TimeUnit timeUnit) awaitTermination, }) : _shutdown = shutdown, _shutdownNow = shutdownNow, _isShutdown = isShutdown, @@ -8972,7 +8972,7 @@ class _$ExecutorServiceImpl implements $ExecutorServiceImpl { final jni.JList Function() _shutdownNow; final bool Function() _isShutdown; final bool Function() _isTerminated; - final bool Function(int j, jni.JObject timeUnit) _awaitTermination; + final bool Function(int j, TimeUnit timeUnit) _awaitTermination; void shutdown() { return _shutdown(); @@ -8990,7 +8990,7 @@ class _$ExecutorServiceImpl implements $ExecutorServiceImpl { return _isTerminated(); } - bool awaitTermination(int j, jni.JObject timeUnit) { + bool awaitTermination(int j, TimeUnit timeUnit) { return _awaitTermination(j, timeUnit); } } @@ -13393,3 +13393,478 @@ final class $WebSocketInterceptorType other is $WebSocketInterceptorType; } } + +/// from: java.util.concurrent.TimeUnit +class TimeUnit extends jni.JObject { + @override + late final jni.JObjType $type = type; + + TimeUnit.fromReference( + jni.JReference reference, + ) : super.fromReference(reference); + + static final _class = jni.JClass.forName(r'java/util/concurrent/TimeUnit'); + + /// The type which includes information such as the signature of this class. + static const type = $TimeUnitType(); + static final _id_NANOSECONDS = _class.staticFieldId( + r'NANOSECONDS', + r'Ljava/util/concurrent/TimeUnit;', + ); + + /// from: static public final java.util.concurrent.TimeUnit NANOSECONDS + /// The returned object must be released after use, by calling the [release] method. + static TimeUnit get NANOSECONDS => + _id_NANOSECONDS.get(_class, const $TimeUnitType()); + + static final _id_MICROSECONDS = _class.staticFieldId( + r'MICROSECONDS', + r'Ljava/util/concurrent/TimeUnit;', + ); + + /// from: static public final java.util.concurrent.TimeUnit MICROSECONDS + /// The returned object must be released after use, by calling the [release] method. + static TimeUnit get MICROSECONDS => + _id_MICROSECONDS.get(_class, const $TimeUnitType()); + + static final _id_MILLISECONDS = _class.staticFieldId( + r'MILLISECONDS', + r'Ljava/util/concurrent/TimeUnit;', + ); + + /// from: static public final java.util.concurrent.TimeUnit MILLISECONDS + /// The returned object must be released after use, by calling the [release] method. + static TimeUnit get MILLISECONDS => + _id_MILLISECONDS.get(_class, const $TimeUnitType()); + + static final _id_SECONDS = _class.staticFieldId( + r'SECONDS', + r'Ljava/util/concurrent/TimeUnit;', + ); + + /// from: static public final java.util.concurrent.TimeUnit SECONDS + /// The returned object must be released after use, by calling the [release] method. + static TimeUnit get SECONDS => _id_SECONDS.get(_class, const $TimeUnitType()); + + static final _id_MINUTES = _class.staticFieldId( + r'MINUTES', + r'Ljava/util/concurrent/TimeUnit;', + ); + + /// from: static public final java.util.concurrent.TimeUnit MINUTES + /// The returned object must be released after use, by calling the [release] method. + static TimeUnit get MINUTES => _id_MINUTES.get(_class, const $TimeUnitType()); + + static final _id_HOURS = _class.staticFieldId( + r'HOURS', + r'Ljava/util/concurrent/TimeUnit;', + ); + + /// from: static public final java.util.concurrent.TimeUnit HOURS + /// The returned object must be released after use, by calling the [release] method. + static TimeUnit get HOURS => _id_HOURS.get(_class, const $TimeUnitType()); + + static final _id_DAYS = _class.staticFieldId( + r'DAYS', + r'Ljava/util/concurrent/TimeUnit;', + ); + + /// from: static public final java.util.concurrent.TimeUnit DAYS + /// The returned object must be released after use, by calling the [release] method. + static TimeUnit get DAYS => _id_DAYS.get(_class, const $TimeUnitType()); + + static final _id_values = _class.staticMethodId( + r'values', + r'()[Ljava/util/concurrent/TimeUnit;', + ); + + static final _values = ProtectedJniExtensions.lookup< + ffi.NativeFunction< + jni.JniResult Function( + ffi.Pointer, + jni.JMethodIDPtr, + )>>('globalEnv_CallStaticObjectMethod') + .asFunction< + jni.JniResult Function( + ffi.Pointer, + jni.JMethodIDPtr, + )>(); + + /// from: static public java.util.concurrent.TimeUnit[] values() + /// The returned object must be released after use, by calling the [release] method. + static jni.JArray values() { + return _values(_class.reference.pointer, _id_values as jni.JMethodIDPtr) + .object(const jni.JArrayType($TimeUnitType())); + } + + static final _id_valueOf = _class.staticMethodId( + r'valueOf', + r'(Ljava/lang/String;)Ljava/util/concurrent/TimeUnit;', + ); + + static final _valueOf = ProtectedJniExtensions.lookup< + ffi.NativeFunction< + jni.JniResult Function( + ffi.Pointer, + jni.JMethodIDPtr, + ffi.VarArgs<(ffi.Pointer,)>)>>( + 'globalEnv_CallStaticObjectMethod') + .asFunction< + jni.JniResult Function(ffi.Pointer, jni.JMethodIDPtr, + ffi.Pointer)>(); + + /// from: static public java.util.concurrent.TimeUnit valueOf(java.lang.String string) + /// The returned object must be released after use, by calling the [release] method. + static TimeUnit valueOf( + jni.JString string, + ) { + return _valueOf(_class.reference.pointer, _id_valueOf as jni.JMethodIDPtr, + string.reference.pointer) + .object(const $TimeUnitType()); + } + + static final _id_convert = _class.instanceMethodId( + r'convert', + r'(JLjava/util/concurrent/TimeUnit;)J', + ); + + static final _convert = ProtectedJniExtensions.lookup< + ffi.NativeFunction< + jni.JniResult Function( + ffi.Pointer, + jni.JMethodIDPtr, + ffi.VarArgs<(ffi.Int64, ffi.Pointer)>)>>( + 'globalEnv_CallLongMethod') + .asFunction< + jni.JniResult Function(ffi.Pointer, jni.JMethodIDPtr, int, + ffi.Pointer)>(); + + /// from: public long convert(long j, java.util.concurrent.TimeUnit timeUnit) + int convert( + int j, + TimeUnit timeUnit, + ) { + return _convert(reference.pointer, _id_convert as jni.JMethodIDPtr, j, + timeUnit.reference.pointer) + .long; + } + + static final _id_convert1 = _class.instanceMethodId( + r'convert', + r'(Ljava/time/Duration;)J', + ); + + static final _convert1 = ProtectedJniExtensions.lookup< + ffi.NativeFunction< + jni.JniResult Function( + ffi.Pointer, + jni.JMethodIDPtr, + ffi.VarArgs<(ffi.Pointer,)>)>>( + 'globalEnv_CallLongMethod') + .asFunction< + jni.JniResult Function(ffi.Pointer, jni.JMethodIDPtr, + ffi.Pointer)>(); + + /// from: public long convert(java.time.Duration duration) + int convert1( + jni.JObject duration, + ) { + return _convert1(reference.pointer, _id_convert1 as jni.JMethodIDPtr, + duration.reference.pointer) + .long; + } + + static final _id_toNanos = _class.instanceMethodId( + r'toNanos', + r'(J)J', + ); + + static final _toNanos = ProtectedJniExtensions.lookup< + ffi.NativeFunction< + jni.JniResult Function(ffi.Pointer, jni.JMethodIDPtr, + ffi.VarArgs<(ffi.Int64,)>)>>('globalEnv_CallLongMethod') + .asFunction< + jni.JniResult Function( + ffi.Pointer, jni.JMethodIDPtr, int)>(); + + /// from: public long toNanos(long j) + int toNanos( + int j, + ) { + return _toNanos(reference.pointer, _id_toNanos as jni.JMethodIDPtr, j).long; + } + + static final _id_toMicros = _class.instanceMethodId( + r'toMicros', + r'(J)J', + ); + + static final _toMicros = ProtectedJniExtensions.lookup< + ffi.NativeFunction< + jni.JniResult Function(ffi.Pointer, jni.JMethodIDPtr, + ffi.VarArgs<(ffi.Int64,)>)>>('globalEnv_CallLongMethod') + .asFunction< + jni.JniResult Function( + ffi.Pointer, jni.JMethodIDPtr, int)>(); + + /// from: public long toMicros(long j) + int toMicros( + int j, + ) { + return _toMicros(reference.pointer, _id_toMicros as jni.JMethodIDPtr, j) + .long; + } + + static final _id_toMillis = _class.instanceMethodId( + r'toMillis', + r'(J)J', + ); + + static final _toMillis = ProtectedJniExtensions.lookup< + ffi.NativeFunction< + jni.JniResult Function(ffi.Pointer, jni.JMethodIDPtr, + ffi.VarArgs<(ffi.Int64,)>)>>('globalEnv_CallLongMethod') + .asFunction< + jni.JniResult Function( + ffi.Pointer, jni.JMethodIDPtr, int)>(); + + /// from: public long toMillis(long j) + int toMillis( + int j, + ) { + return _toMillis(reference.pointer, _id_toMillis as jni.JMethodIDPtr, j) + .long; + } + + static final _id_toSeconds = _class.instanceMethodId( + r'toSeconds', + r'(J)J', + ); + + static final _toSeconds = ProtectedJniExtensions.lookup< + ffi.NativeFunction< + jni.JniResult Function(ffi.Pointer, jni.JMethodIDPtr, + ffi.VarArgs<(ffi.Int64,)>)>>('globalEnv_CallLongMethod') + .asFunction< + jni.JniResult Function( + ffi.Pointer, jni.JMethodIDPtr, int)>(); + + /// from: public long toSeconds(long j) + int toSeconds( + int j, + ) { + return _toSeconds(reference.pointer, _id_toSeconds as jni.JMethodIDPtr, j) + .long; + } + + static final _id_toMinutes = _class.instanceMethodId( + r'toMinutes', + r'(J)J', + ); + + static final _toMinutes = ProtectedJniExtensions.lookup< + ffi.NativeFunction< + jni.JniResult Function(ffi.Pointer, jni.JMethodIDPtr, + ffi.VarArgs<(ffi.Int64,)>)>>('globalEnv_CallLongMethod') + .asFunction< + jni.JniResult Function( + ffi.Pointer, jni.JMethodIDPtr, int)>(); + + /// from: public long toMinutes(long j) + int toMinutes( + int j, + ) { + return _toMinutes(reference.pointer, _id_toMinutes as jni.JMethodIDPtr, j) + .long; + } + + static final _id_toHours = _class.instanceMethodId( + r'toHours', + r'(J)J', + ); + + static final _toHours = ProtectedJniExtensions.lookup< + ffi.NativeFunction< + jni.JniResult Function(ffi.Pointer, jni.JMethodIDPtr, + ffi.VarArgs<(ffi.Int64,)>)>>('globalEnv_CallLongMethod') + .asFunction< + jni.JniResult Function( + ffi.Pointer, jni.JMethodIDPtr, int)>(); + + /// from: public long toHours(long j) + int toHours( + int j, + ) { + return _toHours(reference.pointer, _id_toHours as jni.JMethodIDPtr, j).long; + } + + static final _id_toDays = _class.instanceMethodId( + r'toDays', + r'(J)J', + ); + + static final _toDays = ProtectedJniExtensions.lookup< + ffi.NativeFunction< + jni.JniResult Function(ffi.Pointer, jni.JMethodIDPtr, + ffi.VarArgs<(ffi.Int64,)>)>>('globalEnv_CallLongMethod') + .asFunction< + jni.JniResult Function( + ffi.Pointer, jni.JMethodIDPtr, int)>(); + + /// from: public long toDays(long j) + int toDays( + int j, + ) { + return _toDays(reference.pointer, _id_toDays as jni.JMethodIDPtr, j).long; + } + + static final _id_timedWait = _class.instanceMethodId( + r'timedWait', + r'(Ljava/lang/Object;J)V', + ); + + static final _timedWait = ProtectedJniExtensions.lookup< + ffi.NativeFunction< + jni.JThrowablePtr Function( + ffi.Pointer, + jni.JMethodIDPtr, + ffi.VarArgs<(ffi.Pointer, ffi.Int64)>)>>( + 'globalEnv_CallVoidMethod') + .asFunction< + jni.JThrowablePtr Function(ffi.Pointer, jni.JMethodIDPtr, + ffi.Pointer, int)>(); + + /// from: public void timedWait(java.lang.Object object, long j) + void timedWait( + jni.JObject object, + int j, + ) { + _timedWait(reference.pointer, _id_timedWait as jni.JMethodIDPtr, + object.reference.pointer, j) + .check(); + } + + static final _id_timedJoin = _class.instanceMethodId( + r'timedJoin', + r'(Ljava/lang/Thread;J)V', + ); + + static final _timedJoin = ProtectedJniExtensions.lookup< + ffi.NativeFunction< + jni.JThrowablePtr Function( + ffi.Pointer, + jni.JMethodIDPtr, + ffi.VarArgs<(ffi.Pointer, ffi.Int64)>)>>( + 'globalEnv_CallVoidMethod') + .asFunction< + jni.JThrowablePtr Function(ffi.Pointer, jni.JMethodIDPtr, + ffi.Pointer, int)>(); + + /// from: public void timedJoin(java.lang.Thread thread, long j) + void timedJoin( + jni.JObject thread, + int j, + ) { + _timedJoin(reference.pointer, _id_timedJoin as jni.JMethodIDPtr, + thread.reference.pointer, j) + .check(); + } + + static final _id_sleep = _class.instanceMethodId( + r'sleep', + r'(J)V', + ); + + static final _sleep = ProtectedJniExtensions.lookup< + ffi.NativeFunction< + jni.JThrowablePtr Function( + ffi.Pointer, + jni.JMethodIDPtr, + ffi.VarArgs<(ffi.Int64,)>)>>('globalEnv_CallVoidMethod') + .asFunction< + jni.JThrowablePtr Function( + ffi.Pointer, jni.JMethodIDPtr, int)>(); + + /// from: public void sleep(long j) + void sleep( + int j, + ) { + _sleep(reference.pointer, _id_sleep as jni.JMethodIDPtr, j).check(); + } + + static final _id_toChronoUnit = _class.instanceMethodId( + r'toChronoUnit', + r'()Ljava/time/temporal/ChronoUnit;', + ); + + static final _toChronoUnit = ProtectedJniExtensions.lookup< + ffi.NativeFunction< + jni.JniResult Function( + ffi.Pointer, + jni.JMethodIDPtr, + )>>('globalEnv_CallObjectMethod') + .asFunction< + jni.JniResult Function( + ffi.Pointer, + jni.JMethodIDPtr, + )>(); + + /// from: public java.time.temporal.ChronoUnit toChronoUnit() + /// The returned object must be released after use, by calling the [release] method. + jni.JObject toChronoUnit() { + return _toChronoUnit( + reference.pointer, _id_toChronoUnit as jni.JMethodIDPtr) + .object(const jni.JObjectType()); + } + + static final _id_of = _class.staticMethodId( + r'of', + r'(Ljava/time/temporal/ChronoUnit;)Ljava/util/concurrent/TimeUnit;', + ); + + static final _of = ProtectedJniExtensions.lookup< + ffi.NativeFunction< + jni.JniResult Function( + ffi.Pointer, + jni.JMethodIDPtr, + ffi.VarArgs<(ffi.Pointer,)>)>>( + 'globalEnv_CallStaticObjectMethod') + .asFunction< + jni.JniResult Function(ffi.Pointer, jni.JMethodIDPtr, + ffi.Pointer)>(); + + /// from: static public java.util.concurrent.TimeUnit of(java.time.temporal.ChronoUnit chronoUnit) + /// The returned object must be released after use, by calling the [release] method. + static TimeUnit of( + jni.JObject chronoUnit, + ) { + return _of(_class.reference.pointer, _id_of as jni.JMethodIDPtr, + chronoUnit.reference.pointer) + .object(const $TimeUnitType()); + } +} + +final class $TimeUnitType extends jni.JObjType { + const $TimeUnitType(); + + @override + String get signature => r'Ljava/util/concurrent/TimeUnit;'; + + @override + TimeUnit fromReference(jni.JReference reference) => + TimeUnit.fromReference(reference); + + @override + jni.JObjType get superType => const jni.JObjectType(); + + @override + final superCount = 1; + + @override + int get hashCode => ($TimeUnitType).hashCode; + + @override + bool operator ==(Object other) { + return other.runtimeType == ($TimeUnitType) && other is $TimeUnitType; + } +} diff --git a/pkgs/ok_http/lib/src/ok_http_client.dart b/pkgs/ok_http/lib/src/ok_http_client.dart index 752dbaa41a..27af5bf3d7 100644 --- a/pkgs/ok_http/lib/src/ok_http_client.dart +++ b/pkgs/ok_http/lib/src/ok_http_client.dart @@ -21,6 +21,43 @@ import 'package:jni/jni.dart'; import 'jni/bindings.dart' as bindings; +/// Configurations for the [OkHttpClient]. +class OkHttpClientConfiguration { + /// The maximum duration to wait for a call to complete. + /// + /// If a call does not finish within the specified time, it will throw a + /// [ClientException] with the message "java.io.InterruptedIOException...". + /// + /// [Duration.zero] indicates no timeout. + /// + /// See [OkHttpClient.Builder.callTimeout](https://square.github.io/okhttp/5.x/okhttp/okhttp3/-ok-http-client/-builder/call-timeout.html). + final Duration callTimeout; + + /// The maximum duration to wait while connecting a TCP Socket to the target + /// host. + /// + /// See [OkHttpClient.Builder.connectTimeout](https://square.github.io/okhttp/5.x/okhttp/okhttp3/-ok-http-client/-builder/connect-timeout.html). + final Duration connectTimeout; + + /// The maximum duration to wait for a TCP Socket and for individual read + /// IO operations. + /// + /// See [OkHttpClient.Builder.readTimeout](https://square.github.io/okhttp/5.x/okhttp/okhttp3/-ok-http-client/-builder/read-timeout.html). + final Duration readTimeout; + + /// The maximum duration to wait for individual write IO operations. + /// + /// See [OkHttpClient.Builder.writeTimeout](https://square.github.io/okhttp/5.x/okhttp/okhttp3/-ok-http-client/-builder/write-timeout.html). + final Duration writeTimeout; + + const OkHttpClientConfiguration({ + this.callTimeout = Duration.zero, + this.connectTimeout = const Duration(milliseconds: 10000), + this.readTimeout = const Duration(milliseconds: 10000), + this.writeTimeout = const Duration(milliseconds: 10000), + }); +} + /// An HTTP [Client] utilizing the [OkHttp](https://square.github.io/okhttp/) client. /// /// Example Usage: @@ -47,7 +84,14 @@ class OkHttpClient extends BaseClient { late bindings.OkHttpClient _client; bool _isClosed = false; - OkHttpClient() { + /// The configuration for this client, applied on a per-call basis. + /// It can be updated multiple times during the client's lifecycle. + OkHttpClientConfiguration configuration; + + /// Creates a new instance of [OkHttpClient] with the given [configuration]. + OkHttpClient({ + this.configuration = const OkHttpClientConfiguration(), + }) { _client = bindings.OkHttpClient.new1(); } @@ -160,15 +204,26 @@ class OkHttpClient extends BaseClient { maxRedirects, followRedirects, bindings.RedirectReceivedCallback.implement( bindings.$RedirectReceivedCallbackImpl( - onRedirectReceived: (response, newLocation) { - profile?.responseData.addRedirect(HttpProfileRedirectData( - statusCode: response.code(), - method: - response.request().method().toDartString(releaseOriginal: true), - location: newLocation.toDartString(releaseOriginal: true), - )); - }, - ))).build(); + onRedirectReceived: (response, newLocation) { + profile?.responseData.addRedirect(HttpProfileRedirectData( + statusCode: response.code(), + method: response + .request() + .method() + .toDartString(releaseOriginal: true), + location: newLocation.toDartString(releaseOriginal: true), + )); + }, + ))) + .callTimeout(configuration.callTimeout.inMilliseconds, + bindings.TimeUnit.MILLISECONDS) + .connectTimeout(configuration.connectTimeout.inMilliseconds, + bindings.TimeUnit.MILLISECONDS) + .readTimeout(configuration.readTimeout.inMilliseconds, + bindings.TimeUnit.MILLISECONDS) + .writeTimeout(configuration.writeTimeout.inMilliseconds, + bindings.TimeUnit.MILLISECONDS) + .build(); // `enqueue()` schedules the request to be executed in the future. // https://square.github.io/okhttp/5.x/okhttp/okhttp3/-call/enqueue.html diff --git a/pkgs/ok_http/lib/src/ok_http_web_socket.dart b/pkgs/ok_http/lib/src/ok_http_web_socket.dart index 4136a62852..16ed95ed1a 100644 --- a/pkgs/ok_http/lib/src/ok_http_web_socket.dart +++ b/pkgs/ok_http/lib/src/ok_http_web_socket.dart @@ -15,6 +15,10 @@ import 'jni/bindings.dart' as bindings; /// [WebSocket](https://square.github.io/okhttp/5.x/okhttp/okhttp3/-web-socket/index.html) /// API. /// +/// > [!NOTE] +/// > The [WebSocket] interface is currently experimental and may change in the +/// > future. +/// /// Example usage of [OkHttpWebSocket]: /// ```dart /// import 'package:ok_http/ok_http.dart'; @@ -37,6 +41,11 @@ import 'jni/bindings.dart' as bindings; /// }); /// } /// ``` +/// +/// > [!TIP] +/// > [`AdapterWebSocketChannel`](https://pub.dev/documentation/web_socket_channel/latest/adapter_web_socket_channel/AdapterWebSocketChannel-class.html) +/// > can be used to adapt a [OkHttpWebSocket] into a +/// > [`WebSocketChannel`](https://pub.dev/documentation/web_socket_channel/latest/web_socket_channel/WebSocketChannel-class.html). class OkHttpWebSocket implements WebSocket { late bindings.OkHttpClient _client; late final bindings.WebSocket _webSocket; diff --git a/pkgs/ok_http/pubspec.yaml b/pkgs/ok_http/pubspec.yaml index 121c6b314c..93759491e5 100644 --- a/pkgs/ok_http/pubspec.yaml +++ b/pkgs/ok_http/pubspec.yaml @@ -1,5 +1,5 @@ name: ok_http -version: 0.1.0 +version: 0.1.1-wip description: >- An Android Flutter plugin that provides access to the OkHttp HTTP client. repository: https://github.com/dart-lang/http/tree/master/pkgs/ok_http