Skip to content

Commit

Permalink
[pkgs/ok_http] Add functionality to accept and configure redirects. (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
Anikate-De authored Jun 11, 2024
1 parent 93ff4a9 commit f8d5bf8
Show file tree
Hide file tree
Showing 6 changed files with 267 additions and 1 deletion.
9 changes: 9 additions & 0 deletions pkgs/ok_http/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,20 @@ rootProject.allprojects {
}

apply plugin: "com.android.library"
apply plugin: 'kotlin-android'

android {
if (project.android.hasProperty("namespace")) {
namespace = "com.example.ok_http"
}

kotlinOptions {
jvmTarget = '1.8'
}

sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}

// Bumping the plugin compileSdk version requires all clients of this plugin
// to bump the version in their app.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// 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.

// To cause a request failure [with a suitable message] due to too many redirects,
// we need to throw an IOException. This cannot be done using Dart JNI bindings,
// which lead to a deadlock and eventually a `java.net.SocketTimeoutException`.
// https://github.com/dart-lang/native/issues/561

package com.example.ok_http

import okhttp3.Interceptor
import okhttp3.OkHttpClient
import java.io.IOException

class RedirectInterceptor {
companion object {

/**
* Adds a redirect interceptor to the OkHttpClient.Builder
*
* @param clientBuilder The `OkHttpClient.Builder` to add the interceptor to
* @param maxRedirects The maximum number of redirects to follow
* @param followRedirects Whether to follow redirects
*
* @return OkHttpClient.Builder
*/
fun addRedirectInterceptor(
clientBuilder: OkHttpClient.Builder, maxRedirects: Int, followRedirects: Boolean
): OkHttpClient.Builder {
return clientBuilder.addInterceptor(Interceptor { chain ->
var req = chain.request()
var response = chain.proceed(req)
var redirectCount = 0

while (response.isRedirect && followRedirects) {
if (redirectCount >= maxRedirects) {
throw IOException("Redirect limit exceeded")
}

val location = response.header("location") ?: break
req = req.newBuilder().url(location).build()
response.close()
response = chain.proceed(req)
redirectCount++
}

response
})
}
}
}
2 changes: 2 additions & 0 deletions pkgs/ok_http/example/integration_test/client_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@ Future<void> testConformance() async {
testResponseHeaders(OkHttpClient(), supportsFoldedHeaders: false);
testResponseStatusLine(OkHttpClient());
testCompressedResponseBody(OkHttpClient());
testRedirect(OkHttpClient());
testServerErrors(OkHttpClient());
testClose(OkHttpClient.new);
testIsolate(OkHttpClient.new);
testRequestCookies(OkHttpClient(), canSendCookieHeaders: true);
testResponseCookies(OkHttpClient(), canReceiveSetCookieHeaders: true);
});
}
1 change: 1 addition & 0 deletions pkgs/ok_http/jnigen.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ classes:
- "okhttp3.ConnectionPool"
- "okhttp3.Dispatcher"
- "okhttp3.Cache"
- "com.example.ok_http.RedirectInterceptor"

# Exclude the deprecated methods listed below
# They cause syntax errors during the `dart format` step of JNIGen.
Expand Down
181 changes: 181 additions & 0 deletions pkgs/ok_http/lib/src/jni/bindings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9437,3 +9437,184 @@ final class $CacheType extends jni.JObjType<Cache> {
return other.runtimeType == ($CacheType) && other is $CacheType;
}
}

/// from: com.example.ok_http.RedirectInterceptor$Companion
class RedirectInterceptor_Companion extends jni.JObject {
@override
late final jni.JObjType<RedirectInterceptor_Companion> $type = type;

RedirectInterceptor_Companion.fromReference(
jni.JReference reference,
) : super.fromReference(reference);

static final _class =
jni.JClass.forName(r"com/example/ok_http/RedirectInterceptor$Companion");

/// The type which includes information such as the signature of this class.
static const type = $RedirectInterceptor_CompanionType();
static final _id_addRedirectInterceptor = _class.instanceMethodId(
r"addRedirectInterceptor",
r"(Lokhttp3/OkHttpClient$Builder;IZ)Lokhttp3/OkHttpClient$Builder;",
);

static final _addRedirectInterceptor = ProtectedJniExtensions.lookup<
ffi.NativeFunction<
jni.JniResult Function(
ffi.Pointer<ffi.Void>,
jni.JMethodIDPtr,
ffi.VarArgs<
(
ffi.Pointer<ffi.Void>,
ffi.Int64,
ffi.Int64
)>)>>("globalEnv_CallObjectMethod")
.asFunction<
jni.JniResult Function(ffi.Pointer<ffi.Void>, jni.JMethodIDPtr,
ffi.Pointer<ffi.Void>, int, int)>();

/// from: public final okhttp3.OkHttpClient$Builder addRedirectInterceptor(okhttp3.OkHttpClient$Builder builder, int i, boolean z)
/// The returned object must be released after use, by calling the [release] method.
OkHttpClient_Builder addRedirectInterceptor(
OkHttpClient_Builder builder,
int i,
bool z,
) {
return _addRedirectInterceptor(
reference.pointer,
_id_addRedirectInterceptor as jni.JMethodIDPtr,
builder.reference.pointer,
i,
z ? 1 : 0)
.object(const $OkHttpClient_BuilderType());
}

static final _id_new0 = _class.constructorId(
r"(Lkotlin/jvm/internal/DefaultConstructorMarker;)V",
);

static final _new0 = ProtectedJniExtensions.lookup<
ffi.NativeFunction<
jni.JniResult Function(
ffi.Pointer<ffi.Void>,
jni.JMethodIDPtr,
ffi.VarArgs<(ffi.Pointer<ffi.Void>,)>)>>(
"globalEnv_NewObject")
.asFunction<
jni.JniResult Function(ffi.Pointer<ffi.Void>, jni.JMethodIDPtr,
ffi.Pointer<ffi.Void>)>();

/// from: public void <init>(kotlin.jvm.internal.DefaultConstructorMarker defaultConstructorMarker)
/// The returned object must be released after use, by calling the [release] method.
factory RedirectInterceptor_Companion(
jni.JObject defaultConstructorMarker,
) {
return RedirectInterceptor_Companion.fromReference(_new0(
_class.reference.pointer,
_id_new0 as jni.JMethodIDPtr,
defaultConstructorMarker.reference.pointer)
.reference);
}
}

final class $RedirectInterceptor_CompanionType
extends jni.JObjType<RedirectInterceptor_Companion> {
const $RedirectInterceptor_CompanionType();

@override
String get signature =>
r"Lcom/example/ok_http/RedirectInterceptor$Companion;";

@override
RedirectInterceptor_Companion fromReference(jni.JReference reference) =>
RedirectInterceptor_Companion.fromReference(reference);

@override
jni.JObjType get superType => const jni.JObjectType();

@override
final superCount = 1;

@override
int get hashCode => ($RedirectInterceptor_CompanionType).hashCode;

@override
bool operator ==(Object other) {
return other.runtimeType == ($RedirectInterceptor_CompanionType) &&
other is $RedirectInterceptor_CompanionType;
}
}

/// from: com.example.ok_http.RedirectInterceptor
class RedirectInterceptor extends jni.JObject {
@override
late final jni.JObjType<RedirectInterceptor> $type = type;

RedirectInterceptor.fromReference(
jni.JReference reference,
) : super.fromReference(reference);

static final _class =
jni.JClass.forName(r"com/example/ok_http/RedirectInterceptor");

/// The type which includes information such as the signature of this class.
static const type = $RedirectInterceptorType();
static final _id_Companion = _class.staticFieldId(
r"Companion",
r"Lcom/example/ok_http/RedirectInterceptor$Companion;",
);

/// from: static public final com.example.ok_http.RedirectInterceptor$Companion Companion
/// The returned object must be released after use, by calling the [release] method.
static RedirectInterceptor_Companion get Companion =>
_id_Companion.get(_class, const $RedirectInterceptor_CompanionType());

static final _id_new0 = _class.constructorId(
r"()V",
);

static final _new0 = ProtectedJniExtensions.lookup<
ffi.NativeFunction<
jni.JniResult Function(
ffi.Pointer<ffi.Void>,
jni.JMethodIDPtr,
)>>("globalEnv_NewObject")
.asFunction<
jni.JniResult Function(
ffi.Pointer<ffi.Void>,
jni.JMethodIDPtr,
)>();

/// from: public void <init>()
/// The returned object must be released after use, by calling the [release] method.
factory RedirectInterceptor() {
return RedirectInterceptor.fromReference(
_new0(_class.reference.pointer, _id_new0 as jni.JMethodIDPtr)
.reference);
}
}

final class $RedirectInterceptorType extends jni.JObjType<RedirectInterceptor> {
const $RedirectInterceptorType();

@override
String get signature => r"Lcom/example/ok_http/RedirectInterceptor;";

@override
RedirectInterceptor fromReference(jni.JReference reference) =>
RedirectInterceptor.fromReference(reference);

@override
jni.JObjType get superType => const jni.JObjectType();

@override
final superCount = 1;

@override
int get hashCode => ($RedirectInterceptorType).hashCode;

@override
bool operator ==(Object other) {
return other.runtimeType == ($RedirectInterceptorType) &&
other is $RedirectInterceptorType;
}
}
23 changes: 22 additions & 1 deletion pkgs/ok_http/lib/src/ok_http_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ class OkHttpClient extends BaseClient {
var requestHeaders = request.headers;
var requestMethod = request.method;
var requestBody = await request.finalize().toBytes();
var maxRedirects = request.maxRedirects;
var followRedirects = request.followRedirects;

final responseCompleter = Completer<StreamedResponse>();

Expand All @@ -112,9 +114,22 @@ class OkHttpClient extends BaseClient {
okReqBody,
);

// To configure the client per-request, we create a new client with the
// builder associated with `_client`.
// They share the same connection pool and dispatcher.
// https://square.github.io/okhttp/recipes/#per-call-configuration-kt-java
//
// `followRedirects` is set to `false` to handle redirects manually.
// (Since OkHttp sets a hard limit of 20 redirects.)
// https://github.com/square/okhttp/blob/54238b4c713080c3fd32fb1a070fb5d6814c9a09/okhttp/src/main/kotlin/okhttp3/internal/http/RetryAndFollowUpInterceptor.kt#L350
final reqConfiguredClient = bindings.RedirectInterceptor.Companion
.addRedirectInterceptor(_client.newBuilder().followRedirects(false),
maxRedirects, followRedirects)
.build();

// `enqueue()` schedules the request to be executed in the future.
// https://square.github.io/okhttp/5.x/okhttp/okhttp3/-call/enqueue.html
_client
reqConfiguredClient
.newCall(reqBuilder.build())
.enqueue(bindings.Callback.implement(bindings.$CallbackImpl(
onResponse: (bindings.Call call, bindings.Response response) {
Expand Down Expand Up @@ -159,9 +174,15 @@ class OkHttpClient extends BaseClient {
headers: responseHeaders,
request: request,
contentLength: contentLength,
isRedirect: response.isRedirect(),
));
},
onFailure: (bindings.Call call, JObject ioException) {
if (ioException.toString().contains('Redirect limit exceeded')) {
responseCompleter.completeError(
ClientException('Redirect limit exceeded', request.url));
return;
}
responseCompleter.completeError(
ClientException(ioException.toString(), request.url));
},
Expand Down

0 comments on commit f8d5bf8

Please sign in to comment.