From 36a8eb8d8e998f3f9cefe50ec7d83117edc74552 Mon Sep 17 00:00:00 2001 From: Alex James Date: Mon, 24 Jul 2023 16:35:59 +0100 Subject: [PATCH 01/10] Generate bindings for java.io.OutputStream --- pkgs/java_http/jnigen.yaml | 1 + .../src/third_party/java/io/InputStream.dart | 4 +- .../src/third_party/java/io/OutputStream.dart | 136 ++++++++++++++++++ .../lib/src/third_party/java/io/_package.dart | 1 + .../third_party/java/net/URLConnection.dart | 9 +- 5 files changed, 147 insertions(+), 4 deletions(-) create mode 100644 pkgs/java_http/lib/src/third_party/java/io/OutputStream.dart diff --git a/pkgs/java_http/jnigen.yaml b/pkgs/java_http/jnigen.yaml index 6f470e196d..2a8302685c 100644 --- a/pkgs/java_http/jnigen.yaml +++ b/pkgs/java_http/jnigen.yaml @@ -13,6 +13,7 @@ class_path: classes: - 'java.io.InputStream' + - 'java.io.OutputStream' - 'java.lang.System' - 'java.net.HttpURLConnection' - 'java.net.URL' diff --git a/pkgs/java_http/lib/src/third_party/java/io/InputStream.dart b/pkgs/java_http/lib/src/third_party/java/io/InputStream.dart index ec99ebc1a1..3b930d49cd 100644 --- a/pkgs/java_http/lib/src/third_party/java/io/InputStream.dart +++ b/pkgs/java_http/lib/src/third_party/java/io/InputStream.dart @@ -19,6 +19,8 @@ import "dart:ffi" as ffi; import "package:jni/internal_helpers_for_jnigen.dart"; import "package:jni/jni.dart" as jni; +import "OutputStream.dart" as outputstream_; + /// from: java.io.InputStream class InputStream extends jni.JObject { @override @@ -192,7 +194,7 @@ class InputStream extends jni.JObject { /// from: public long transferTo(java.io.OutputStream outputStream) int transferTo( - jni.JObject outputStream, + outputstream_.OutputStream outputStream, ) { return jni.Jni.accessors.callMethodWithArgs(reference, _id_transferTo, jni.JniCallType.longType, [outputStream.reference]).long; diff --git a/pkgs/java_http/lib/src/third_party/java/io/OutputStream.dart b/pkgs/java_http/lib/src/third_party/java/io/OutputStream.dart new file mode 100644 index 0000000000..ce4966c0aa --- /dev/null +++ b/pkgs/java_http/lib/src/third_party/java/io/OutputStream.dart @@ -0,0 +1,136 @@ +// 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.io.OutputStream +class OutputStream extends jni.JObject { + @override + late final jni.JObjType $type = type; + + OutputStream.fromRef( + jni.JObjectPtr ref, + ) : super.fromRef(ref); + + static final _class = jni.Jni.findJClass(r"java/io/OutputStream"); + + /// The type which includes information such as the signature of this class. + static const type = $OutputStreamType(); + static final _id_ctor = + jni.Jni.accessors.getMethodIDOf(_class.reference, r"", r"()V"); + + /// from: public void () + /// The returned object must be deleted after use, by calling the `delete` method. + factory OutputStream() { + return OutputStream.fromRef(jni.Jni.accessors + .newObjectWithArgs(_class.reference, _id_ctor, []).object); + } + + static final _id_nullOutputStream = jni.Jni.accessors.getStaticMethodIDOf( + _class.reference, r"nullOutputStream", r"()Ljava/io/OutputStream;"); + + /// from: static public java.io.OutputStream nullOutputStream() + /// The returned object must be deleted after use, by calling the `delete` method. + static OutputStream nullOutputStream() { + return const $OutputStreamType().fromRef(jni.Jni.accessors + .callStaticMethodWithArgs(_class.reference, _id_nullOutputStream, + jni.JniCallType.objectType, []).object); + } + + static final _id_write = + jni.Jni.accessors.getMethodIDOf(_class.reference, r"write", r"(I)V"); + + /// from: public abstract void write(int i) + void write( + int i, + ) { + return jni.Jni.accessors.callMethodWithArgs(reference, _id_write, + jni.JniCallType.voidType, [jni.JValueInt(i)]).check(); + } + + static final _id_write1 = + jni.Jni.accessors.getMethodIDOf(_class.reference, r"write", r"([B)V"); + + /// from: public void write(byte[] bs) + void write1( + jni.JArray bs, + ) { + return jni.Jni.accessors.callMethodWithArgs(reference, _id_write1, + jni.JniCallType.voidType, [bs.reference]).check(); + } + + static final _id_write2 = + jni.Jni.accessors.getMethodIDOf(_class.reference, r"write", r"([BII)V"); + + /// from: public void write(byte[] bs, int i, int i1) + void write2( + jni.JArray bs, + int i, + int i1, + ) { + return jni.Jni.accessors.callMethodWithArgs( + reference, + _id_write2, + jni.JniCallType.voidType, + [bs.reference, jni.JValueInt(i), jni.JValueInt(i1)]).check(); + } + + static final _id_flush = + jni.Jni.accessors.getMethodIDOf(_class.reference, r"flush", r"()V"); + + /// from: public void flush() + void flush() { + return jni.Jni.accessors.callMethodWithArgs( + reference, _id_flush, jni.JniCallType.voidType, []).check(); + } + + static final _id_close = + jni.Jni.accessors.getMethodIDOf(_class.reference, r"close", r"()V"); + + /// from: public void close() + void close() { + return jni.Jni.accessors.callMethodWithArgs( + reference, _id_close, jni.JniCallType.voidType, []).check(); + } +} + +class $OutputStreamType extends jni.JObjType { + const $OutputStreamType(); + + @override + String get signature => r"Ljava/io/OutputStream;"; + + @override + OutputStream fromRef(jni.JObjectPtr ref) => OutputStream.fromRef(ref); + + @override + jni.JObjType get superType => const jni.JObjectType(); + + @override + final superCount = 1; + + @override + int get hashCode => ($OutputStreamType).hashCode; + + @override + bool operator ==(Object other) { + return other.runtimeType == ($OutputStreamType) && + other is $OutputStreamType; + } +} diff --git a/pkgs/java_http/lib/src/third_party/java/io/_package.dart b/pkgs/java_http/lib/src/third_party/java/io/_package.dart index 19b2828809..ef72615333 100644 --- a/pkgs/java_http/lib/src/third_party/java/io/_package.dart +++ b/pkgs/java_http/lib/src/third_party/java/io/_package.dart @@ -1 +1,2 @@ export "InputStream.dart"; +export "OutputStream.dart"; diff --git a/pkgs/java_http/lib/src/third_party/java/net/URLConnection.dart b/pkgs/java_http/lib/src/third_party/java/net/URLConnection.dart index 28c3f4697a..6dd56e1df9 100644 --- a/pkgs/java_http/lib/src/third_party/java/net/URLConnection.dart +++ b/pkgs/java_http/lib/src/third_party/java/net/URLConnection.dart @@ -23,6 +23,8 @@ import "URL.dart" as url_; import "../io/InputStream.dart" as inputstream_; +import "../io/OutputStream.dart" as outputstream_; + /// from: java.net.URLConnection class URLConnection extends jni.JObject { @override @@ -467,9 +469,10 @@ class URLConnection extends jni.JObject { /// from: public java.io.OutputStream getOutputStream() /// The returned object must be deleted after use, by calling the `delete` method. - jni.JObject getOutputStream() { - return const jni.JObjectType().fromRef(jni.Jni.accessors.callMethodWithArgs( - reference, _id_getOutputStream, jni.JniCallType.objectType, []).object); + outputstream_.OutputStream getOutputStream() { + return const outputstream_.$OutputStreamType().fromRef(jni.Jni.accessors + .callMethodWithArgs(reference, _id_getOutputStream, + jni.JniCallType.objectType, []).object); } static final _id_toString1 = jni.Jni.accessors From b19c7c0c56e55441dc0fc5790924bdbc20275a0f Mon Sep 17 00:00:00 2001 From: Alex James Date: Mon, 24 Jul 2023 16:43:45 +0100 Subject: [PATCH 02/10] Get the request body --- pkgs/java_http/lib/src/java_client.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/java_http/lib/src/java_client.dart b/pkgs/java_http/lib/src/java_client.dart index f652084507..19e0b41515 100644 --- a/pkgs/java_http/lib/src/java_client.dart +++ b/pkgs/java_http/lib/src/java_client.dart @@ -40,8 +40,8 @@ class JavaClient extends BaseClient { _initJVM(); final (statusCode, reasonPhrase, responseHeaders, responseBody) = - await Isolate.run(() { - request.finalize(); + await Isolate.run(() async { + final requestBody = await request.finalize().toBytes(); final httpUrlConnection = URL .ctor3(request.url.toString().toJString()) From 04241b31d380f3e432989dd33f23fc9f04b4ad79 Mon Sep 17 00:00:00 2001 From: Alex James Date: Mon, 24 Jul 2023 16:53:35 +0100 Subject: [PATCH 03/10] Send request body --- pkgs/java_http/lib/src/java_client.dart | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/pkgs/java_http/lib/src/java_client.dart b/pkgs/java_http/lib/src/java_client.dart index 19e0b41515..856104e699 100644 --- a/pkgs/java_http/lib/src/java_client.dart +++ b/pkgs/java_http/lib/src/java_client.dart @@ -54,6 +54,7 @@ class JavaClient extends BaseClient { }); httpUrlConnection.setRequestMethod(request.method.toJString()); + _setRequestBody(httpUrlConnection, requestBody); final statusCode = _statusCode(request, httpUrlConnection); final reasonPhrase = _reasonPhrase(httpUrlConnection); @@ -78,6 +79,22 @@ class JavaClient extends BaseClient { reasonPhrase: reasonPhrase); } + void _setRequestBody( + HttpURLConnection httpUrlConnection, + Uint8List requestBody, + ) { + if (requestBody.isEmpty) return; + + httpUrlConnection.setDoOutput(true); + + final outputStream = httpUrlConnection.getOutputStream(); + requestBody.forEach(outputStream.write); + + outputStream + ..flush() + ..close(); + } + int _statusCode(BaseRequest request, HttpURLConnection httpUrlConnection) { final statusCode = httpUrlConnection.getResponseCode(); From 1c8867c631860a01af98dd08ba31e8368500d01b Mon Sep 17 00:00:00 2001 From: Alex James Date: Mon, 24 Jul 2023 16:55:15 +0100 Subject: [PATCH 04/10] Test request body --- pkgs/java_http/test/java_client_test.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/pkgs/java_http/test/java_client_test.dart b/pkgs/java_http/test/java_client_test.dart index 0f48c0b7b7..a3b368c8ba 100644 --- a/pkgs/java_http/test/java_client_test.dart +++ b/pkgs/java_http/test/java_client_test.dart @@ -10,6 +10,7 @@ void main() { group('java_http client conformance tests', () { testResponseBody(JavaClient(), canStreamResponseBody: false); testResponseHeaders(JavaClient()); + testRequestBody(JavaClient()); testRequestHeaders(JavaClient()); testMultipleClients(JavaClient.new); }); From fc126265e3a5cab3d74fa619572196af7e1ccc38 Mon Sep 17 00:00:00 2001 From: Alex James Date: Wed, 26 Jul 2023 23:52:25 +0100 Subject: [PATCH 05/10] Pass failing request body tests --- pkgs/java_http/lib/src/java_client.dart | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/pkgs/java_http/lib/src/java_client.dart b/pkgs/java_http/lib/src/java_client.dart index 856104e699..a7bb16179c 100644 --- a/pkgs/java_http/lib/src/java_client.dart +++ b/pkgs/java_http/lib/src/java_client.dart @@ -39,24 +39,29 @@ class JavaClient extends BaseClient { // See https://github.com/dart-lang/http/pull/980#discussion_r1253700470. _initJVM(); + // We can't send a StreamedRequest to another Isolate. + // But we can send Map, String, UInt8List, Uri. + final requestBody = await request.finalize().toBytes(); + final requestHeaders = request.headers; + final requestMethod = request.method; + final requestUrl = request.url; + final (statusCode, reasonPhrase, responseHeaders, responseBody) = await Isolate.run(() async { - final requestBody = await request.finalize().toBytes(); - final httpUrlConnection = URL - .ctor3(request.url.toString().toJString()) + .ctor3(requestUrl.toString().toJString()) .openConnection() .castTo(HttpURLConnection.type, deleteOriginal: true); - request.headers.forEach((headerName, headerValue) { + requestHeaders.forEach((headerName, headerValue) { httpUrlConnection.setRequestProperty( headerName.toJString(), headerValue.toJString()); }); - httpUrlConnection.setRequestMethod(request.method.toJString()); + httpUrlConnection.setRequestMethod(requestMethod.toJString()); _setRequestBody(httpUrlConnection, requestBody); - final statusCode = _statusCode(request, httpUrlConnection); + final statusCode = _statusCode(requestUrl, httpUrlConnection); final reasonPhrase = _reasonPhrase(httpUrlConnection); final responseHeaders = _responseHeaders(httpUrlConnection); final responseBody = _responseBody(httpUrlConnection); @@ -86,7 +91,6 @@ class JavaClient extends BaseClient { if (requestBody.isEmpty) return; httpUrlConnection.setDoOutput(true); - final outputStream = httpUrlConnection.getOutputStream(); requestBody.forEach(outputStream.write); @@ -95,12 +99,12 @@ class JavaClient extends BaseClient { ..close(); } - int _statusCode(BaseRequest request, HttpURLConnection httpUrlConnection) { + int _statusCode(Uri requestUrl, HttpURLConnection httpUrlConnection) { final statusCode = httpUrlConnection.getResponseCode(); if (statusCode == -1) { throw ClientException( - 'Status code can not be discerned from the response.', request.url); + 'Status code can not be discerned from the response.', requestUrl); } return statusCode; From fa243dc4e60afb27ec5252fd7df65048fba4bbcb Mon Sep 17 00:00:00 2001 From: Alex James Date: Thu, 27 Jul 2023 00:14:31 +0100 Subject: [PATCH 06/10] Add testIsolate to java_http's tests --- pkgs/java_http/test/java_client_test.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/pkgs/java_http/test/java_client_test.dart b/pkgs/java_http/test/java_client_test.dart index a3b368c8ba..5ad0120099 100644 --- a/pkgs/java_http/test/java_client_test.dart +++ b/pkgs/java_http/test/java_client_test.dart @@ -8,6 +8,7 @@ import 'package:test/test.dart'; void main() { group('java_http client conformance tests', () { + testIsolate(JavaClient.new); testResponseBody(JavaClient(), canStreamResponseBody: false); testResponseHeaders(JavaClient()); testRequestBody(JavaClient()); From 788d8c1eeab28b1b0c811d1c8adeccd631e469a6 Mon Sep 17 00:00:00 2001 From: Alex James Date: Fri, 28 Jul 2023 01:19:32 +0100 Subject: [PATCH 07/10] Write the byte array to the OutputStream --- pkgs/java_http/lib/src/java_client.dart | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pkgs/java_http/lib/src/java_client.dart b/pkgs/java_http/lib/src/java_client.dart index a7bb16179c..fd50bf1790 100644 --- a/pkgs/java_http/lib/src/java_client.dart +++ b/pkgs/java_http/lib/src/java_client.dart @@ -91,10 +91,12 @@ class JavaClient extends BaseClient { if (requestBody.isEmpty) return; httpUrlConnection.setDoOutput(true); - final outputStream = httpUrlConnection.getOutputStream(); - requestBody.forEach(outputStream.write); - outputStream + final bodyArray = JArray(jbyte.type, requestBody.length) + ..setRange(0, requestBody.length, requestBody); + + httpUrlConnection.getOutputStream() + ..write1(bodyArray) ..flush() ..close(); } From 140f638521129fd78bab60543747c5d789528043 Mon Sep 17 00:00:00 2001 From: Alex James Date: Fri, 28 Jul 2023 23:45:33 +0100 Subject: [PATCH 08/10] Add extension method to Uint8List --- pkgs/java_http/lib/src/extensions/uint8_list.dart | 12 ++++++++++++ pkgs/java_http/lib/src/java_client.dart | 7 +++---- 2 files changed, 15 insertions(+), 4 deletions(-) create mode 100644 pkgs/java_http/lib/src/extensions/uint8_list.dart diff --git a/pkgs/java_http/lib/src/extensions/uint8_list.dart b/pkgs/java_http/lib/src/extensions/uint8_list.dart new file mode 100644 index 0000000000..38e3a8e9ce --- /dev/null +++ b/pkgs/java_http/lib/src/extensions/uint8_list.dart @@ -0,0 +1,12 @@ +// 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:typed_data'; + +import 'package:jni/jni.dart'; + +extension ToJavaArray on Uint8List { + JArray toJArray() => + JArray(jbyte.type, length)..setRange(0, length, this); +} diff --git a/pkgs/java_http/lib/src/java_client.dart b/pkgs/java_http/lib/src/java_client.dart index fd50bf1790..d3a8540e21 100644 --- a/pkgs/java_http/lib/src/java_client.dart +++ b/pkgs/java_http/lib/src/java_client.dart @@ -10,6 +10,8 @@ import 'package:http/http.dart'; import 'package:jni/jni.dart'; import 'package:path/path.dart'; +import 'extensions/uint8_list.dart'; + import 'third_party/java/lang/System.dart'; import 'third_party/java/net/HttpURLConnection.dart'; import 'third_party/java/net/URL.dart'; @@ -92,11 +94,8 @@ class JavaClient extends BaseClient { httpUrlConnection.setDoOutput(true); - final bodyArray = JArray(jbyte.type, requestBody.length) - ..setRange(0, requestBody.length, requestBody); - httpUrlConnection.getOutputStream() - ..write1(bodyArray) + ..write1(requestBody.toJArray()) ..flush() ..close(); } From 5335c7574fd5ee958f2c860c7d6245a44557d7ca Mon Sep 17 00:00:00 2001 From: Alex James Date: Sat, 29 Jul 2023 00:13:44 +0100 Subject: [PATCH 09/10] Revert "Add extension method to Uint8List" This reverts commit 140f638521129fd78bab60543747c5d789528043. --- pkgs/java_http/lib/src/extensions/uint8_list.dart | 12 ------------ pkgs/java_http/lib/src/java_client.dart | 7 ++++--- 2 files changed, 4 insertions(+), 15 deletions(-) delete mode 100644 pkgs/java_http/lib/src/extensions/uint8_list.dart diff --git a/pkgs/java_http/lib/src/extensions/uint8_list.dart b/pkgs/java_http/lib/src/extensions/uint8_list.dart deleted file mode 100644 index 38e3a8e9ce..0000000000 --- a/pkgs/java_http/lib/src/extensions/uint8_list.dart +++ /dev/null @@ -1,12 +0,0 @@ -// 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:typed_data'; - -import 'package:jni/jni.dart'; - -extension ToJavaArray on Uint8List { - JArray toJArray() => - JArray(jbyte.type, length)..setRange(0, length, this); -} diff --git a/pkgs/java_http/lib/src/java_client.dart b/pkgs/java_http/lib/src/java_client.dart index d3a8540e21..fd50bf1790 100644 --- a/pkgs/java_http/lib/src/java_client.dart +++ b/pkgs/java_http/lib/src/java_client.dart @@ -10,8 +10,6 @@ import 'package:http/http.dart'; import 'package:jni/jni.dart'; import 'package:path/path.dart'; -import 'extensions/uint8_list.dart'; - import 'third_party/java/lang/System.dart'; import 'third_party/java/net/HttpURLConnection.dart'; import 'third_party/java/net/URL.dart'; @@ -94,8 +92,11 @@ class JavaClient extends BaseClient { httpUrlConnection.setDoOutput(true); + final bodyArray = JArray(jbyte.type, requestBody.length) + ..setRange(0, requestBody.length, requestBody); + httpUrlConnection.getOutputStream() - ..write1(requestBody.toJArray()) + ..write1(bodyArray) ..flush() ..close(); } From 0e921cf63228a531da74ceacffa3a7e302fc280a Mon Sep 17 00:00:00 2001 From: Alex James Date: Sat, 29 Jul 2023 00:32:10 +0100 Subject: [PATCH 10/10] Add private extension method to Uint8List --- pkgs/java_http/lib/src/java_client.dart | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pkgs/java_http/lib/src/java_client.dart b/pkgs/java_http/lib/src/java_client.dart index fd50bf1790..51847a50f6 100644 --- a/pkgs/java_http/lib/src/java_client.dart +++ b/pkgs/java_http/lib/src/java_client.dart @@ -92,11 +92,8 @@ class JavaClient extends BaseClient { httpUrlConnection.setDoOutput(true); - final bodyArray = JArray(jbyte.type, requestBody.length) - ..setRange(0, requestBody.length, requestBody); - httpUrlConnection.getOutputStream() - ..write1(bodyArray) + ..write1(requestBody.toJArray()) ..flush() ..close(); } @@ -183,3 +180,8 @@ class JavaClient extends BaseClient { return Uint8List.fromList(bytes); } } + +extension on Uint8List { + JArray toJArray() => + JArray(jbyte.type, length)..setRange(0, length, this); +}