Skip to content

Commit 85ad8a0

Browse files
authored
feat: OkHttpEngine BYOC (#1437)
1 parent 24a17b7 commit 85ad8a0

File tree

5 files changed

+82
-8
lines changed

5 files changed

+82
-8
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"id": "4d3a104a-3225-4dcb-be05-f5155d320952",
3+
"type": "feature",
4+
"description": "Allow configuring a custom OkHttpClient in OkHttpEngine",
5+
"issues": [
6+
"https://github.com/aws/aws-sdk-kotlin/issues/1707"
7+
]
8+
}

runtime/build.gradle.kts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,8 +143,6 @@ subprojects {
143143
}
144144
}
145145

146-
// configureIosSimulatorTasks()
147-
148146
val excludeFromDocumentation = listOf(
149147
":runtime:testing",
150148
":runtime:smithy-test",

runtime/protocol/http-client-engines/http-client-engine-okhttp/api/http-client-engine-okhttp.api

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ public final class aws/smithy/kotlin/runtime/http/engine/okhttp/OkHttpEngine : a
5353
public static final field Companion Laws/smithy/kotlin/runtime/http/engine/okhttp/OkHttpEngine$Companion;
5454
public fun <init> ()V
5555
public fun <init> (Laws/smithy/kotlin/runtime/http/engine/okhttp/OkHttpEngineConfig;)V
56+
public synthetic fun <init> (Laws/smithy/kotlin/runtime/http/engine/okhttp/OkHttpEngineConfig;Lokhttp3/OkHttpClient;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
57+
public fun <init> (Lokhttp3/OkHttpClient;)V
5658
public synthetic fun getConfig ()Laws/smithy/kotlin/runtime/http/engine/HttpClientEngineConfig;
5759
public fun getConfig ()Laws/smithy/kotlin/runtime/http/engine/okhttp/OkHttpEngineConfig;
5860
public fun roundTrip (Laws/smithy/kotlin/runtime/operation/ExecutionContext;Laws/smithy/kotlin/runtime/http/request/HttpRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;

runtime/protocol/http-client-engines/http-client-engine-okhttp/jvm/src/aws/smithy/kotlin/runtime/http/engine/okhttp/OkHttpEngine.kt

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import aws.smithy.kotlin.runtime.InternalApi
99
import aws.smithy.kotlin.runtime.http.HttpCall
1010
import aws.smithy.kotlin.runtime.http.config.EngineFactory
1111
import aws.smithy.kotlin.runtime.http.engine.AlpnId
12+
import aws.smithy.kotlin.runtime.http.engine.HttpClientEngine
1213
import aws.smithy.kotlin.runtime.http.engine.HttpClientEngineBase
1314
import aws.smithy.kotlin.runtime.http.engine.TlsContext
1415
import aws.smithy.kotlin.runtime.http.engine.callContext
@@ -34,18 +35,23 @@ import okhttp3.TlsVersion as OkHttpTlsVersion
3435
/**
3536
* [aws.smithy.kotlin.runtime.http.engine.HttpClientEngine] based on OkHttp.
3637
*/
37-
public class OkHttpEngine(
38+
public class OkHttpEngine private constructor(
3839
override val config: OkHttpEngineConfig,
40+
private val userProvidedClient: OkHttpClient?,
3941
) : HttpClientEngineBase("OkHttp") {
40-
public constructor() : this(OkHttpEngineConfig.Default)
42+
public constructor() : this(OkHttpEngineConfig.Default, null)
43+
44+
public constructor(config: OkHttpEngineConfig) : this(config, null)
45+
46+
public constructor(client: OkHttpClient) : this(OkHttpEngineConfig.Default, client)
4147

4248
public companion object : EngineFactory<OkHttpEngineConfig.Builder, OkHttpEngine> {
4349
/**
4450
* Initializes a new [OkHttpEngine] via a DSL builder block
4551
* @param block A receiver lambda which sets the properties of the config to be built
4652
*/
4753
public operator fun invoke(block: OkHttpEngineConfig.Builder.() -> Unit): OkHttpEngine =
48-
OkHttpEngine(OkHttpEngineConfig(block))
54+
OkHttpEngine(OkHttpEngineConfig(block), null)
4955

5056
override val engineConstructor: (OkHttpEngineConfig.Builder.() -> Unit) -> OkHttpEngine = ::invoke
5157
}
@@ -57,7 +63,10 @@ public class OkHttpEngine(
5763
}
5864

5965
private val metrics = HttpClientMetrics(TELEMETRY_SCOPE, config.telemetryProvider)
60-
private val client = config.buildClient(metrics, connectionMonitoringListener)
66+
private val client: OkHttpClient by lazy {
67+
userProvidedClient?.withMetrics(metrics, config)
68+
?: config.buildClient(metrics, connectionMonitoringListener)
69+
}
6170

6271
@OptIn(ExperimentalCoroutinesApi::class)
6372
override suspend fun roundTrip(context: ExecutionContext, request: HttpRequest): HttpCall {
@@ -85,9 +94,11 @@ public class OkHttpEngine(
8594

8695
override fun shutdown() {
8796
connectionMonitoringListener?.closeIfCloseable()
88-
client.connectionPool.evictAll()
89-
client.dispatcher.executorService.shutdown()
9097
metrics.close()
98+
if (userProvidedClient == null) {
99+
client.connectionPool.evictAll()
100+
client.dispatcher.executorService.shutdown()
101+
}
91102
}
92103
}
93104

@@ -170,6 +181,18 @@ public fun OkHttpEngineConfig.buildClient(
170181
}.build()
171182
}
172183

184+
// Configure a user-provided client to collect SDK metrics
185+
private fun OkHttpClient.withMetrics(metrics: HttpClientMetrics, config: OkHttpEngineConfig) = newBuilder().apply {
186+
eventListenerFactory { call ->
187+
EventListenerChain(
188+
listOf(
189+
HttpEngineEventListener(connectionPool, config.hostResolver, dispatcher, metrics, call),
190+
),
191+
)
192+
}
193+
addInterceptor(MetricsInterceptor)
194+
}.build()
195+
173196
private fun tlsConnectionSpec(tlsContext: TlsContext, cipherSuites: List<String>?): ConnectionSpec {
174197
val minVersion = tlsContext.minVersion ?: TlsVersion.TLS_1_2
175198
val okHttpTlsVersions = SdkTlsVersion
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
package aws.smithy.kotlin.runtime.http.engine.okhttp
6+
7+
import aws.smithy.kotlin.runtime.content.ByteStream
8+
import aws.smithy.kotlin.runtime.http.Headers
9+
import aws.smithy.kotlin.runtime.http.HttpException
10+
import aws.smithy.kotlin.runtime.http.HttpMethod
11+
import aws.smithy.kotlin.runtime.http.SdkHttpClient
12+
import aws.smithy.kotlin.runtime.http.request.HttpRequest
13+
import aws.smithy.kotlin.runtime.http.toHttpBody
14+
import aws.smithy.kotlin.runtime.net.url.Url
15+
import kotlinx.coroutines.test.runTest
16+
import okhttp3.OkHttpClient
17+
import java.io.IOException
18+
import kotlin.test.Test
19+
import kotlin.test.assertFailsWith
20+
import kotlin.test.assertTrue
21+
22+
class OkHttpEngineConfigTest {
23+
@Test
24+
fun testUserClient() = runTest {
25+
val userClient = OkHttpClient.Builder().apply {
26+
addInterceptor { throw DummyOkHttpClientException() }
27+
}.build()
28+
29+
val engine = OkHttpEngine(userClient)
30+
val sdkClient = SdkHttpClient(engine)
31+
32+
val data = "a".repeat(100)
33+
val url = Url.parse("https://aws.amazon.com")
34+
val request = HttpRequest(HttpMethod.POST, url, Headers.Empty, ByteStream.fromString(data).toHttpBody())
35+
36+
val ex = assertFailsWith<HttpException> {
37+
sdkClient.call(request)
38+
}
39+
assertTrue(ex.cause is DummyOkHttpClientException)
40+
}
41+
42+
private class DummyOkHttpClientException : IOException("Custom OkHttpClient interceptor was called")
43+
}

0 commit comments

Comments
 (0)