Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pkgs/cronet_http/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* Add a new `CronetStreamedResponse` class that provides additional information
about the HTTP response.
* Fix a Flutter warning by upgrading to Kotlin 1.18.10.
* Upgrade `package:jni` and `package:jnigen` to 0.14.2.

## 1.3.4

Expand Down
6 changes: 3 additions & 3 deletions pkgs/cronet_http/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ android {
compileSdkVersion 31

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}

kotlinOptions {
jvmTarget = '1.8'
jvmTarget = '11'
}

sourceSets {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,58 +19,69 @@ import org.chromium.net.UrlRequest
import org.chromium.net.UrlResponseInfo
import java.nio.ByteBuffer

// Due to a bug (https://github.com/dart-lang/native/issues/2421) where JNIgen
// does not synchronize the nullabilities across the class hierarchy and the
// fact that UrlRequest.Callback is a Java class with no nullability
// annotations, generating both `UrlRequestCallbackProxy` and
// `UrlRequest.Callback` together with different nullabilities causes the
// super method to have a looser type for parameters which is a Dart compilation
// error.
// That is why all of the parameters of this class are defined as nullable to
// match `UrlRequest.Callback` while in reality only `onFailed`'s `info`
// parameter is nullable as specified in the cronet source code:
// https://source.chromium.org/chromium/chromium/src/+/main:components/cronet/android/api/src/org/chromium/net/UrlRequest.java;l=232

class UrlRequestCallbackProxy(val callback: UrlRequestCallbackInterface) : UrlRequest.Callback() {
public interface UrlRequestCallbackInterface {
fun onRedirectReceived(
request: UrlRequest,
info: UrlResponseInfo,
newLocationUrl: String
request: UrlRequest?,
info: UrlResponseInfo?,
newLocationUrl: String?
)

fun onResponseStarted(request: UrlRequest?, info: UrlResponseInfo)
fun onResponseStarted(request: UrlRequest?, info: UrlResponseInfo?)
fun onReadCompleted(
request: UrlRequest,
info: UrlResponseInfo,
byteBuffer: ByteBuffer
request: UrlRequest?,
info: UrlResponseInfo?,
byteBuffer: ByteBuffer?
)

fun onSucceeded(request: UrlRequest, info: UrlResponseInfo?)
fun onSucceeded(request: UrlRequest?, info: UrlResponseInfo?)
fun onFailed(
request: UrlRequest,
request: UrlRequest?,
info: UrlResponseInfo?,
error: CronetException
error: CronetException?
)
}

override fun onRedirectReceived(
request: UrlRequest,
info: UrlResponseInfo,
newLocationUrl: String
request: UrlRequest?,
info: UrlResponseInfo?,
newLocationUrl: String?
) {
callback.onRedirectReceived(request, info, newLocationUrl);
}

override fun onResponseStarted(request: UrlRequest?, info: UrlResponseInfo) {
override fun onResponseStarted(request: UrlRequest?, info: UrlResponseInfo?) {
callback.onResponseStarted(request, info);
}

override fun onReadCompleted(
request: UrlRequest,
info: UrlResponseInfo,
byteBuffer: ByteBuffer
request: UrlRequest?,
info: UrlResponseInfo?,
byteBuffer: ByteBuffer?
) {
callback.onReadCompleted(request, info, byteBuffer);
}

override fun onSucceeded(request: UrlRequest, info: UrlResponseInfo?) {
override fun onSucceeded(request: UrlRequest?, info: UrlResponseInfo?) {
callback.onSucceeded(request, info);
}

override fun onFailed(
request: UrlRequest,
request: UrlRequest?,
info: UrlResponseInfo?,
error: CronetException
error: CronetException?
) {
callback.onFailed(request, info, error);
}
Expand Down
35 changes: 31 additions & 4 deletions pkgs/cronet_http/example/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ android {
namespace 'io.flutter.cronet_http_example'

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}

kotlinOptions {
jvmTarget = '1.8'
jvmTarget = '11'
}

sourceSets {
Expand Down Expand Up @@ -68,9 +68,36 @@ flutter {
dependencies {
// TODO(#1112): org.jetbrains.kotlin:kotlin-bom artifact purpose is to align kotlin stdlib and related code versions.
// This should be removed when https://github.com/flutter/flutter/issues/125062 is fixed.
implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.8.0"))
// implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.8.0"))
// ""com.google.android.gms:play-services-cronet" is only present so that
// `jnigen` will work. Applications should not include this line.
// The version should be synced with `pkgs/cronet_http/android/build.gradle`.
implementation "com.google.android.gms:play-services-cronet:18.0.1"
}

// // Gradle stub for listing dependencies in jnigen. If found in
// // android/build.gradle, please delete the following function.
task getReleaseCompileClasspath(type: Copy) {
project.afterEvaluate {
try {
def app = project(':app')
println "ok so far 1"
def android = app.android
def cp = [android.getBootClasspath()[0]]
println "ok so far 2"
android.applicationVariants.each { variant ->
if (variant.name.equals('release')) {
println "ok so far 3"
println variant.javaCompile.classpath
cp += variant.javaCompile.classpath.getFiles()
println "ok so far 4"
}
}
cp.each { println it }
} catch (Exception e) {
System.err.println("Gradle stub cannot find JAR libraries. This might be because no APK build has happened yet.")
System.err.println("If you are seeing this error in `flutter build` output, it is likely that `jnigen` left some stubs in the build.gradle file. Please restore that file from your version control system or manually remove the stub functions named getReleaseCompileClasspath and / or getSources.")
throw e
}
}
}
8 changes: 8 additions & 0 deletions pkgs/cronet_http/example/android/build.gradle
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
allprojects {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}

rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
Expand Down
70 changes: 38 additions & 32 deletions pkgs/cronet_http/lib/src/cronet_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,9 @@ class CronetStreamedResponse extends _StreamedResponseWithUrl {
///
/// It will be the empty string or `'unknown'` if no protocol was negotiated,
/// the protocol is not known, or when using plain HTTP or HTTPS.
String get negotiatedProtocol =>
_responseInfo.getNegotiatedProtocol().toDartString(releaseOriginal: true);
String get negotiatedProtocol => _responseInfo
.getNegotiatedProtocol()!
.toDartString(releaseOriginal: true);

/// The minimum count of bytes received from the network to process this
/// request.
Expand Down Expand Up @@ -124,7 +125,7 @@ class CronetEngine {
bool? enableQuic,
String? storagePath,
String? userAgent}) {
final builder = jb.CronetEngine_Builder(
final builder = jb.CronetEngine$Builder(
JObject.fromReference(Jni.getCachedApplicationContext()));

try {
Expand Down Expand Up @@ -159,7 +160,7 @@ class CronetEngine {
builder.setUserAgent(userAgent.toJString());
}

return CronetEngine._(builder.build());
return CronetEngine._(builder.build()!);
} on JniException catch (e) {
// TODO: Decode this exception in a better way when
// https://github.com/dart-lang/jnigen/issues/239 is fixed.
Expand All @@ -182,12 +183,12 @@ class CronetEngine {
}

Map<String, String> _cronetToClientHeaders(
JMap<JString, JList<JString>> cronetHeaders) =>
JMap<JString?, JList<JString?>?> cronetHeaders) =>
cronetHeaders.map((key, value) => MapEntry(
key.toDartString(releaseOriginal: true).toLowerCase(),
value.join(',')));
key!.toDartString(releaseOriginal: true).toLowerCase(),
value!.join(',')));

jb.UrlRequestCallbackProxy_UrlRequestCallbackInterface _urlRequestCallbacks(
jb.UrlRequestCallbackProxy$UrlRequestCallbackInterface _urlRequestCallbacks(
BaseRequest request,
Completer<CronetStreamedResponse> responseCompleter,
HttpClientRequestProfile? profile) {
Expand All @@ -198,21 +199,24 @@ jb.UrlRequestCallbackProxy_UrlRequestCallbackInterface _urlRequestCallbacks(

// The order of callbacks generated by Cronet is documented here:
// https://developer.android.com/guide/topics/connectivity/cronet/lifecycle
return jb.UrlRequestCallbackProxy_UrlRequestCallbackInterface.implement(
jb.$UrlRequestCallbackProxy_UrlRequestCallbackInterface(
return jb.UrlRequestCallbackProxy$UrlRequestCallbackInterface.implement(
// All of the variables in the interface are non-nullable with the
// exception of onFailed's UrlResponseInfo as specified in:
// https://source.chromium.org/chromium/chromium/src/+/main:components/cronet/android/api/src/org/chromium/net/UrlRequest.java;l=232
jb.$UrlRequestCallbackProxy$UrlRequestCallbackInterface(
onResponseStarted: (urlRequest, responseInfo) {
responseStream = StreamController(onCancel: () {
// The user did `response.stream.cancel()`. We can just pretend that
// the response completed normally.
if (done) return;
done = true;
urlRequest.cancel();
urlRequest!.cancel();
responseStream!.sink.close();
jByteBuffer?.release();
profile?.responseData.close();
});
final responseHeaders =
_cronetToClientHeaders(responseInfo.getAllHeaders());
_cronetToClientHeaders(responseInfo!.getAllHeaders()!);
int? contentLength;

switch (responseHeaders['content-length']) {
Expand All @@ -222,7 +226,7 @@ jb.UrlRequestCallbackProxy_UrlRequestCallbackInterface _urlRequestCallbacks(
'Invalid content-length header [$contentLengthHeader].',
request.url,
));
urlRequest.cancel();
urlRequest?.cancel();
return;
case final contentLengthHeader?:
contentLength = int.parse(contentLengthHeader);
Expand All @@ -232,10 +236,10 @@ jb.UrlRequestCallbackProxy_UrlRequestCallbackInterface _urlRequestCallbacks(
responseInfo.getHttpStatusCode(),
responseInfo: responseInfo,
url: Uri.parse(
responseInfo.getUrl().toDartString(releaseOriginal: true)),
responseInfo.getUrl()!.toDartString(releaseOriginal: true)),
contentLength: contentLength,
reasonPhrase: responseInfo
.getHttpStatusText()
.getHttpStatusText()!
.toDartString(releaseOriginal: true),
request: request,
isRedirect: false,
Expand All @@ -247,39 +251,40 @@ jb.UrlRequestCallbackProxy_UrlRequestCallbackInterface _urlRequestCallbacks(
?..contentLength = contentLength
..headersCommaValues = responseHeaders
..isRedirect = false
..reasonPhrase =
responseInfo.getHttpStatusText().toDartString(releaseOriginal: true)
..reasonPhrase = responseInfo
.getHttpStatusText()!
.toDartString(releaseOriginal: true)
..startTime = DateTime.now()
..statusCode = responseInfo.getHttpStatusCode();
jByteBuffer = JByteBuffer.allocateDirect(_bufferSize);
urlRequest.read(jByteBuffer!);
urlRequest?.read(jByteBuffer!);
},
onRedirectReceived: (urlRequest, responseInfo, newLocationUrl) {
if (done) return;
final responseHeaders =
_cronetToClientHeaders(responseInfo.getAllHeaders());
_cronetToClientHeaders(responseInfo!.getAllHeaders()!);

if (!request.followRedirects) {
urlRequest.cancel();
urlRequest!.cancel();
responseCompleter.complete(CronetStreamedResponse._(
const Stream.empty(), // Cronet provides no body for redirects.
responseInfo.getHttpStatusCode(),
responseInfo: responseInfo,
url: Uri.parse(
responseInfo.getUrl().toDartString(releaseOriginal: true)),
responseInfo.getUrl()!.toDartString(releaseOriginal: true)),
contentLength: 0,
reasonPhrase: responseInfo
.getHttpStatusText()
.getHttpStatusText()!
.toDartString(releaseOriginal: true),
request: request,
isRedirect: true,
headers: _cronetToClientHeaders(responseInfo.getAllHeaders())));
headers: _cronetToClientHeaders(responseInfo.getAllHeaders()!)));

profile?.responseData
?..headersCommaValues = responseHeaders
..isRedirect = true
..reasonPhrase = responseInfo
.getHttpStatusText()
.getHttpStatusText()!
.toDartString(releaseOriginal: true)
..startTime = DateTime.now()
..statusCode = responseInfo.getHttpStatusCode();
Expand All @@ -294,23 +299,23 @@ jb.UrlRequestCallbackProxy_UrlRequestCallbackInterface _urlRequestCallbacks(
// does not seem to have a way to get the method so we'd have to
// calculate it according to the rules in RFC-7231.
method: 'GET',
location: newLocationUrl.toDartString(releaseOriginal: true)));
urlRequest.followRedirect();
location: newLocationUrl!.toDartString(releaseOriginal: true)));
urlRequest!.followRedirect();
} else {
urlRequest.cancel();
urlRequest!.cancel();
responseCompleter.completeError(
ClientException('Redirect limit exceeded', request.url));
}
},
onReadCompleted: (urlRequest, responseInfo, byteBuffer) {
if (done) return;
byteBuffer.flip();
byteBuffer!.flip();
final data = jByteBuffer!.asUint8List().sublist(0, byteBuffer.remaining);
responseStream!.add(data);
profile?.responseData.bodySink.add(data);

byteBuffer.clear();
urlRequest.read(byteBuffer);
urlRequest!.read(byteBuffer);
},
onSucceeded: (urlRequest, responseInfo) {
if (done) return;
Expand All @@ -319,7 +324,7 @@ jb.UrlRequestCallbackProxy_UrlRequestCallbackInterface _urlRequestCallbacks(
jByteBuffer?.release();
profile?.responseData.close();
},
onFailed: (urlRequest, responseInfo, cronetException) {
onFailed: (urlRequest, responseInfo /* can be null */, cronetException) {
if (done) return;
done = true;
final error = ClientException(
Expand Down Expand Up @@ -442,7 +447,8 @@ class CronetClient extends BaseClient {
jb.UrlRequestCallbackProxy(
_urlRequestCallbacks(request, responseCompleter, profile)),
_executor,
)..setHttpMethod(request.method.toJString());
)!
..setHttpMethod(request.method.toJString());

var headers = request.headers;
if (body.isNotEmpty &&
Expand Down Expand Up @@ -471,7 +477,7 @@ class CronetClient extends BaseClient {
builder.setUploadDataProvider(
jb.UploadDataProviders.create$2(data), _executor);
}
builder.build().start();
builder.build()!.start();
return responseCompleter.future;
}
}
Expand Down
Loading
Loading