From c148a3acbef72a4a507be46c0e62894c616e8702 Mon Sep 17 00:00:00 2001 From: Alex James Date: Fri, 7 Jul 2023 18:15:48 +0100 Subject: [PATCH] Java http BaseClient implementation (#980) --- .github/workflows/java.yml | 67 +++ pkgs/java_http/.gitignore | 1 + pkgs/java_http/analysis_options.yaml | 5 + pkgs/java_http/jnigen.yaml | 15 + pkgs/java_http/lib/java_http.dart | 3 +- pkgs/java_http/lib/src/java_client.dart | 43 ++ .../lib/src/third_party/java/net/URL.dart | 399 ++++++++++++++++++ .../src/third_party/java/net/_package.dart | 1 + pkgs/java_http/pubspec.yaml | 7 + pkgs/java_http/test/java_client_test.dart | 13 + 10 files changed, 552 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/java.yml create mode 100644 pkgs/java_http/.gitignore create mode 100644 pkgs/java_http/analysis_options.yaml create mode 100644 pkgs/java_http/jnigen.yaml create mode 100644 pkgs/java_http/lib/src/java_client.dart create mode 100644 pkgs/java_http/lib/src/third_party/java/net/URL.dart create mode 100644 pkgs/java_http/lib/src/third_party/java/net/_package.dart create mode 100644 pkgs/java_http/test/java_client_test.dart diff --git a/.github/workflows/java.yml b/.github/workflows/java.yml new file mode 100644 index 0000000000..498226f7a5 --- /dev/null +++ b/.github/workflows/java.yml @@ -0,0 +1,67 @@ +name: package:java_http CI + +on: + push: + branches: + - main + - master + paths: + - 'pkgs/java_http/**' + - 'pkgs/http_client_conformance_tests/**' + - '.github/workflows/java.yml' + pull_request: + paths: + - 'pkgs/java_http/**' + - 'pkgs/http_client_conformance_tests/**' + - '.github/workflows/java.yml' + schedule: + # Runs every Sunday at midnight (00:00 UTC). + - cron: "0 0 * * 0" + +env: + PUB_ENVIRONMENT: bot.github + +jobs: + analyze: + name: Lint and static analysis + runs-on: ubuntu-latest + defaults: + run: + working-directory: pkgs/java_http + steps: + - uses: actions/checkout@v3 + - uses: subosito/flutter-action@v2 + with: + channel: 'stable' + + - id: install + name: Install dependencies + run: dart pub get + + - name: Check formatting + run: dart format --output=none --set-exit-if-changed . + if: always() && steps.install.outcome == 'success' + + - name: Analyze code + run: dart analyze --fatal-infos + if: always() && steps.install.outcome == 'success' + + test: + needs: analyze + name: Build and test + runs-on: ubuntu-latest + defaults: + run: + working-directory: pkgs/java_http + + steps: + - uses: actions/checkout@v3 + - uses: subosito/flutter-action@v2 + with: + channel: 'stable' + + - name: Build jni dynamic libraries + run: dart run jni:setup + + - name: Run tests + run: dart test diff --git a/pkgs/java_http/.gitignore b/pkgs/java_http/.gitignore new file mode 100644 index 0000000000..567609b123 --- /dev/null +++ b/pkgs/java_http/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/pkgs/java_http/analysis_options.yaml b/pkgs/java_http/analysis_options.yaml new file mode 100644 index 0000000000..1bff4c9f23 --- /dev/null +++ b/pkgs/java_http/analysis_options.yaml @@ -0,0 +1,5 @@ +include: ../../analysis_options.yaml + +analyzer: + exclude: + - lib/src/third_party/** diff --git a/pkgs/java_http/jnigen.yaml b/pkgs/java_http/jnigen.yaml new file mode 100644 index 0000000000..6e9ffcb143 --- /dev/null +++ b/pkgs/java_http/jnigen.yaml @@ -0,0 +1,15 @@ +# Regenerate bindings with `dart run jnigen --config jnigen.yaml`. + +summarizer: + backend: asm + +output: + bindings_type: dart_only + dart: + path: 'lib/src/third_party/' + +class_path: + - 'classes.jar' + +classes: + - 'java.net.URL' diff --git a/pkgs/java_http/lib/java_http.dart b/pkgs/java_http/lib/java_http.dart index 6498d22ead..743aa40223 100644 --- a/pkgs/java_http/lib/java_http.dart +++ b/pkgs/java_http/lib/java_http.dart @@ -2,6 +2,5 @@ // 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. -library; - +export 'src/java_client.dart'; export 'src/java_http_base.dart'; diff --git a/pkgs/java_http/lib/src/java_client.dart b/pkgs/java_http/lib/src/java_client.dart new file mode 100644 index 0000000000..0c00c3388a --- /dev/null +++ b/pkgs/java_http/lib/src/java_client.dart @@ -0,0 +1,43 @@ +// Copyright (c) 2023, 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:convert'; +import 'dart:io'; + +import 'package:http/http.dart'; +import 'package:jni/jni.dart'; +import 'package:path/path.dart'; + +import 'third_party/java/net/URL.dart'; + +// TODO: Add a description of the implementation. +// Look at the description of cronet_client.dart and cupertino_client.dart for +// examples. +// See https://github.com/dart-lang/http/pull/980#discussion_r1253697461. +class JavaClient extends BaseClient { + void _initJVM() { + if (!Platform.isAndroid) { + Jni.spawnIfNotExists(dylibDir: join('build', 'jni_libs')); + } + } + + @override + Future send(BaseRequest request) async { + // TODO: Move the call to _initJVM() to the JavaClient constructor. + // See https://github.com/dart-lang/http/pull/980#discussion_r1253700470. + _initJVM(); + + final javaUrl = URL.ctor3(request.url.toString().toJString()); + final dartUrl = Uri.parse(javaUrl.toString1().toDartString()); + + const result = 'Hello World!'; + final stream = Stream.value(latin1.encode(result)); + + return StreamedResponse(stream, 200, + contentLength: 12, + request: Request(request.method, dartUrl), + headers: {'content-type': 'text/plain'}, + reasonPhrase: 'OK'); + } +} diff --git a/pkgs/java_http/lib/src/third_party/java/net/URL.dart b/pkgs/java_http/lib/src/third_party/java/net/URL.dart new file mode 100644 index 0000000000..8c420875f4 --- /dev/null +++ b/pkgs/java_http/lib/src/third_party/java/net/URL.dart @@ -0,0 +1,399 @@ +// Autogenerated by jnigen. DO NOT EDIT! + +// ignore_for_file: annotate_overrides +// ignore_for_file: camel_case_extensions +// ignore_for_file: camel_case_types +// ignore_for_file: constant_identifier_names +// ignore_for_file: file_names +// ignore_for_file: no_leading_underscores_for_local_identifiers +// ignore_for_file: non_constant_identifier_names +// ignore_for_file: overridden_fields +// ignore_for_file: unnecessary_cast +// ignore_for_file: unused_element +// ignore_for_file: unused_field +// ignore_for_file: unused_import +// ignore_for_file: unused_shown_name + +import "dart:isolate" show ReceivePort; +import "dart:ffi" as ffi; +import "package:jni/internal_helpers_for_jnigen.dart"; +import "package:jni/jni.dart" as jni; + +/// from: java.net.URL +class URL extends jni.JObject { + @override + late final jni.JObjType $type = type; + + URL.fromRef( + jni.JObjectPtr ref, + ) : super.fromRef(ref); + + static final _class = jni.Jni.findJClass(r"java/net/URL"); + + /// The type which includes information such as the signature of this class. + static const type = $URLType(); + static final _id_ctor = jni.Jni.accessors.getMethodIDOf(_class.reference, + r"", r"(Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;)V"); + + /// from: public void (java.lang.String string, java.lang.String string1, int i, java.lang.String string2) + /// The returned object must be deleted after use, by calling the `delete` method. + factory URL( + jni.JString string, + jni.JString string1, + int i, + jni.JString string2, + ) { + return URL.fromRef(jni.Jni.accessors.newObjectWithArgs( + _class.reference, _id_ctor, [ + string.reference, + string1.reference, + jni.JValueInt(i), + string2.reference + ]).object); + } + + static final _id_ctor1 = jni.Jni.accessors.getMethodIDOf(_class.reference, + r"", r"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); + + /// from: public void (java.lang.String string, java.lang.String string1, java.lang.String string2) + /// The returned object must be deleted after use, by calling the `delete` method. + factory URL.ctor1( + jni.JString string, + jni.JString string1, + jni.JString string2, + ) { + return URL.fromRef(jni.Jni.accessors.newObjectWithArgs( + _class.reference, + _id_ctor1, + [string.reference, string1.reference, string2.reference]).object); + } + + static final _id_ctor2 = jni.Jni.accessors.getMethodIDOf( + _class.reference, + r"", + r"(Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/net/URLStreamHandler;)V"); + + /// from: public void (java.lang.String string, java.lang.String string1, int i, java.lang.String string2, java.net.URLStreamHandler uRLStreamHandler) + /// The returned object must be deleted after use, by calling the `delete` method. + factory URL.ctor2( + jni.JString string, + jni.JString string1, + int i, + jni.JString string2, + jni.JObject uRLStreamHandler, + ) { + return URL.fromRef( + jni.Jni.accessors.newObjectWithArgs(_class.reference, _id_ctor2, [ + string.reference, + string1.reference, + jni.JValueInt(i), + string2.reference, + uRLStreamHandler.reference + ]).object); + } + + static final _id_ctor3 = jni.Jni.accessors + .getMethodIDOf(_class.reference, r"", r"(Ljava/lang/String;)V"); + + /// from: public void (java.lang.String string) + /// The returned object must be deleted after use, by calling the `delete` method. + factory URL.ctor3( + jni.JString string, + ) { + return URL.fromRef(jni.Jni.accessors.newObjectWithArgs( + _class.reference, _id_ctor3, [string.reference]).object); + } + + static final _id_ctor4 = jni.Jni.accessors.getMethodIDOf( + _class.reference, r"", r"(Ljava/net/URL;Ljava/lang/String;)V"); + + /// from: public void (java.net.URL uRL, java.lang.String string) + /// The returned object must be deleted after use, by calling the `delete` method. + factory URL.ctor4( + URL uRL, + jni.JString string, + ) { + return URL.fromRef(jni.Jni.accessors.newObjectWithArgs( + _class.reference, _id_ctor4, [uRL.reference, string.reference]).object); + } + + static final _id_ctor5 = jni.Jni.accessors.getMethodIDOf( + _class.reference, + r"", + r"(Ljava/net/URL;Ljava/lang/String;Ljava/net/URLStreamHandler;)V"); + + /// from: public void (java.net.URL uRL, java.lang.String string, java.net.URLStreamHandler uRLStreamHandler) + /// The returned object must be deleted after use, by calling the `delete` method. + factory URL.ctor5( + URL uRL, + jni.JString string, + jni.JObject uRLStreamHandler, + ) { + return URL.fromRef(jni.Jni.accessors.newObjectWithArgs( + _class.reference, + _id_ctor5, + [uRL.reference, string.reference, uRLStreamHandler.reference]).object); + } + + static final _id_getQuery = jni.Jni.accessors + .getMethodIDOf(_class.reference, r"getQuery", r"()Ljava/lang/String;"); + + /// from: public java.lang.String getQuery() + /// The returned object must be deleted after use, by calling the `delete` method. + jni.JString getQuery() { + return const jni.JStringType().fromRef(jni.Jni.accessors.callMethodWithArgs( + reference, _id_getQuery, jni.JniCallType.objectType, []).object); + } + + static final _id_getPath = jni.Jni.accessors + .getMethodIDOf(_class.reference, r"getPath", r"()Ljava/lang/String;"); + + /// from: public java.lang.String getPath() + /// The returned object must be deleted after use, by calling the `delete` method. + jni.JString getPath() { + return const jni.JStringType().fromRef(jni.Jni.accessors.callMethodWithArgs( + reference, _id_getPath, jni.JniCallType.objectType, []).object); + } + + static final _id_getUserInfo = jni.Jni.accessors + .getMethodIDOf(_class.reference, r"getUserInfo", r"()Ljava/lang/String;"); + + /// from: public java.lang.String getUserInfo() + /// The returned object must be deleted after use, by calling the `delete` method. + jni.JString getUserInfo() { + return const jni.JStringType().fromRef(jni.Jni.accessors.callMethodWithArgs( + reference, _id_getUserInfo, jni.JniCallType.objectType, []).object); + } + + static final _id_getAuthority = jni.Jni.accessors.getMethodIDOf( + _class.reference, r"getAuthority", r"()Ljava/lang/String;"); + + /// from: public java.lang.String getAuthority() + /// The returned object must be deleted after use, by calling the `delete` method. + jni.JString getAuthority() { + return const jni.JStringType().fromRef(jni.Jni.accessors.callMethodWithArgs( + reference, _id_getAuthority, jni.JniCallType.objectType, []).object); + } + + static final _id_getPort = + jni.Jni.accessors.getMethodIDOf(_class.reference, r"getPort", r"()I"); + + /// from: public int getPort() + int getPort() { + return jni.Jni.accessors.callMethodWithArgs( + reference, _id_getPort, jni.JniCallType.intType, []).integer; + } + + static final _id_getDefaultPort = jni.Jni.accessors + .getMethodIDOf(_class.reference, r"getDefaultPort", r"()I"); + + /// from: public int getDefaultPort() + int getDefaultPort() { + return jni.Jni.accessors.callMethodWithArgs( + reference, _id_getDefaultPort, jni.JniCallType.intType, []).integer; + } + + static final _id_getProtocol = jni.Jni.accessors + .getMethodIDOf(_class.reference, r"getProtocol", r"()Ljava/lang/String;"); + + /// from: public java.lang.String getProtocol() + /// The returned object must be deleted after use, by calling the `delete` method. + jni.JString getProtocol() { + return const jni.JStringType().fromRef(jni.Jni.accessors.callMethodWithArgs( + reference, _id_getProtocol, jni.JniCallType.objectType, []).object); + } + + static final _id_getHost = jni.Jni.accessors + .getMethodIDOf(_class.reference, r"getHost", r"()Ljava/lang/String;"); + + /// from: public java.lang.String getHost() + /// The returned object must be deleted after use, by calling the `delete` method. + jni.JString getHost() { + return const jni.JStringType().fromRef(jni.Jni.accessors.callMethodWithArgs( + reference, _id_getHost, jni.JniCallType.objectType, []).object); + } + + static final _id_getFile = jni.Jni.accessors + .getMethodIDOf(_class.reference, r"getFile", r"()Ljava/lang/String;"); + + /// from: public java.lang.String getFile() + /// The returned object must be deleted after use, by calling the `delete` method. + jni.JString getFile() { + return const jni.JStringType().fromRef(jni.Jni.accessors.callMethodWithArgs( + reference, _id_getFile, jni.JniCallType.objectType, []).object); + } + + static final _id_getRef = jni.Jni.accessors + .getMethodIDOf(_class.reference, r"getRef", r"()Ljava/lang/String;"); + + /// from: public java.lang.String getRef() + /// The returned object must be deleted after use, by calling the `delete` method. + jni.JString getRef() { + return const jni.JStringType().fromRef(jni.Jni.accessors.callMethodWithArgs( + reference, _id_getRef, jni.JniCallType.objectType, []).object); + } + + static final _id_equals1 = jni.Jni.accessors + .getMethodIDOf(_class.reference, r"equals", r"(Ljava/lang/Object;)Z"); + + /// from: public boolean equals(java.lang.Object object) + bool equals1( + jni.JObject object, + ) { + return jni.Jni.accessors.callMethodWithArgs(reference, _id_equals1, + jni.JniCallType.booleanType, [object.reference]).boolean; + } + + static final _id_hashCode1 = + jni.Jni.accessors.getMethodIDOf(_class.reference, r"hashCode", r"()I"); + + /// from: public int hashCode() + int hashCode1() { + return jni.Jni.accessors.callMethodWithArgs( + reference, _id_hashCode1, jni.JniCallType.intType, []).integer; + } + + static final _id_sameFile = jni.Jni.accessors + .getMethodIDOf(_class.reference, r"sameFile", r"(Ljava/net/URL;)Z"); + + /// from: public boolean sameFile(java.net.URL uRL) + bool sameFile( + URL uRL, + ) { + return jni.Jni.accessors.callMethodWithArgs(reference, _id_sameFile, + jni.JniCallType.booleanType, [uRL.reference]).boolean; + } + + static final _id_toString1 = jni.Jni.accessors + .getMethodIDOf(_class.reference, r"toString", r"()Ljava/lang/String;"); + + /// from: public java.lang.String toString() + /// The returned object must be deleted after use, by calling the `delete` method. + jni.JString toString1() { + return const jni.JStringType().fromRef(jni.Jni.accessors.callMethodWithArgs( + reference, _id_toString1, jni.JniCallType.objectType, []).object); + } + + static final _id_toExternalForm = jni.Jni.accessors.getMethodIDOf( + _class.reference, r"toExternalForm", r"()Ljava/lang/String;"); + + /// from: public java.lang.String toExternalForm() + /// The returned object must be deleted after use, by calling the `delete` method. + jni.JString toExternalForm() { + return const jni.JStringType().fromRef(jni.Jni.accessors.callMethodWithArgs( + reference, _id_toExternalForm, jni.JniCallType.objectType, []).object); + } + + static final _id_toURI = jni.Jni.accessors + .getMethodIDOf(_class.reference, r"toURI", r"()Ljava/net/URI;"); + + /// from: public java.net.URI toURI() + /// The returned object must be deleted after use, by calling the `delete` method. + jni.JObject toURI() { + return const jni.JObjectType().fromRef(jni.Jni.accessors.callMethodWithArgs( + reference, _id_toURI, jni.JniCallType.objectType, []).object); + } + + static final _id_openConnection = jni.Jni.accessors.getMethodIDOf( + _class.reference, r"openConnection", r"()Ljava/net/URLConnection;"); + + /// from: public java.net.URLConnection openConnection() + /// The returned object must be deleted after use, by calling the `delete` method. + jni.JObject openConnection() { + return const jni.JObjectType().fromRef(jni.Jni.accessors.callMethodWithArgs( + reference, _id_openConnection, jni.JniCallType.objectType, []).object); + } + + static final _id_openConnection1 = jni.Jni.accessors.getMethodIDOf( + _class.reference, + r"openConnection", + r"(Ljava/net/Proxy;)Ljava/net/URLConnection;"); + + /// from: public java.net.URLConnection openConnection(java.net.Proxy proxy) + /// The returned object must be deleted after use, by calling the `delete` method. + jni.JObject openConnection1( + jni.JObject proxy, + ) { + return const jni.JObjectType().fromRef(jni.Jni.accessors.callMethodWithArgs( + reference, + _id_openConnection1, + jni.JniCallType.objectType, + [proxy.reference]).object); + } + + static final _id_openStream = jni.Jni.accessors.getMethodIDOf( + _class.reference, r"openStream", r"()Ljava/io/InputStream;"); + + /// from: public final java.io.InputStream openStream() + /// The returned object must be deleted after use, by calling the `delete` method. + jni.JObject openStream() { + return const jni.JObjectType().fromRef(jni.Jni.accessors.callMethodWithArgs( + reference, _id_openStream, jni.JniCallType.objectType, []).object); + } + + static final _id_getContent = jni.Jni.accessors + .getMethodIDOf(_class.reference, r"getContent", r"()Ljava/lang/Object;"); + + /// from: public final java.lang.Object getContent() + /// The returned object must be deleted after use, by calling the `delete` method. + jni.JObject getContent() { + return const jni.JObjectType().fromRef(jni.Jni.accessors.callMethodWithArgs( + reference, _id_getContent, jni.JniCallType.objectType, []).object); + } + + static final _id_getContent1 = jni.Jni.accessors.getMethodIDOf( + _class.reference, + r"getContent", + r"([Ljava/lang/Class;)Ljava/lang/Object;"); + + /// from: public final java.lang.Object getContent(java.lang.Object[] classs) + /// The returned object must be deleted after use, by calling the `delete` method. + jni.JObject getContent1( + jni.JArray classs, + ) { + return const jni.JObjectType().fromRef(jni.Jni.accessors.callMethodWithArgs( + reference, + _id_getContent1, + jni.JniCallType.objectType, + [classs.reference]).object); + } + + static final _id_setURLStreamHandlerFactory = jni.Jni.accessors + .getStaticMethodIDOf(_class.reference, r"setURLStreamHandlerFactory", + r"(Ljava/net/URLStreamHandlerFactory;)V"); + + /// from: static public void setURLStreamHandlerFactory(java.net.URLStreamHandlerFactory uRLStreamHandlerFactory) + static void setURLStreamHandlerFactory( + jni.JObject uRLStreamHandlerFactory, + ) { + return jni.Jni.accessors.callStaticMethodWithArgs( + _class.reference, + _id_setURLStreamHandlerFactory, + jni.JniCallType.voidType, + [uRLStreamHandlerFactory.reference]).check(); + } +} + +class $URLType extends jni.JObjType { + const $URLType(); + + @override + String get signature => r"Ljava/net/URL;"; + + @override + URL fromRef(jni.JObjectPtr ref) => URL.fromRef(ref); + + @override + jni.JObjType get superType => const jni.JObjectType(); + + @override + final superCount = 1; + + @override + int get hashCode => ($URLType).hashCode; + + @override + bool operator ==(Object other) { + return other.runtimeType == ($URLType) && other is $URLType; + } +} diff --git a/pkgs/java_http/lib/src/third_party/java/net/_package.dart b/pkgs/java_http/lib/src/third_party/java/net/_package.dart new file mode 100644 index 0000000000..4cfea9a1d2 --- /dev/null +++ b/pkgs/java_http/lib/src/third_party/java/net/_package.dart @@ -0,0 +1 @@ +export "URL.dart"; diff --git a/pkgs/java_http/pubspec.yaml b/pkgs/java_http/pubspec.yaml index 20cb429e54..a11cdf912a 100644 --- a/pkgs/java_http/pubspec.yaml +++ b/pkgs/java_http/pubspec.yaml @@ -8,7 +8,14 @@ environment: sdk: ^3.0.0 dependencies: + http: '>=0.13.4 <2.0.0' + jni: ^0.5.0 + path: ^1.8.0 dev_dependencies: + dart_flutter_team_lints: ^1.0.0 + http_client_conformance_tests: + path: ../http_client_conformance_tests/ + jnigen: ^0.5.0 lints: ^2.0.0 test: ^1.21.0 diff --git a/pkgs/java_http/test/java_client_test.dart b/pkgs/java_http/test/java_client_test.dart new file mode 100644 index 0000000000..f3afbb07c0 --- /dev/null +++ b/pkgs/java_http/test/java_client_test.dart @@ -0,0 +1,13 @@ +// Copyright (c) 2023, 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 'package:http_client_conformance_tests/http_client_conformance_tests.dart'; +import 'package:java_http/java_http.dart'; +import 'package:test/test.dart'; + +void main() { + group('java_http client conformance tests', () { + testResponseBody(JavaClient(), canStreamResponseBody: false); + }); +}