Skip to content

Commit 1bfa894

Browse files
committed
chore: make ktor http client config multiplatform configurable engine settings
1 parent a5dbc9e commit 1bfa894

File tree

16 files changed

+169
-106
lines changed

16 files changed

+169
-106
lines changed

backend/jvm/src/main/kotlin/dev/suresh/plugins/Serialization.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import io.ktor.server.plugins.contentnegotiation.*
66
import io.ktor.server.resources.*
77

88
fun Application.configureSerialization() {
9-
install(ContentNegotiation) { json(dev.suresh.json) }
9+
install(ContentNegotiation) { json(dev.suresh.http.json) }
1010

1111
install(Resources)
1212
}

backend/native/src/nativeMain/kotlin/Main.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import dev.suresh.flow.timerComposeFlow
22
import dev.suresh.http.MediaApiClient
3-
import dev.suresh.json
3+
import dev.suresh.http.json
44
import dev.suresh.platform
55
import kotlin.reflect.typeOf
66
import kotlin.time.Duration

gradle/kotlin-js-store/package-lock.json

Lines changed: 3 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

shared/src/commonMain/kotlin/dev/suresh/Greeting.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package dev.suresh
22

3+
import dev.suresh.http.json
34
import dev.zacsweers.redacted.annotations.Redacted
45
import kotlinx.atomicfu.atomic
56
import kotlinx.atomicfu.locks.reentrantLock

shared/src/commonMain/kotlin/dev/suresh/Platform.kt

Lines changed: 5 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@ import kotlinx.datetime.Clock
1111
import kotlinx.datetime.Instant
1212
import kotlinx.datetime.TimeZone
1313
import kotlinx.datetime.toLocalDateTime
14-
import kotlinx.serialization.json.ClassDiscriminatorMode.POLYMORPHIC
15-
import kotlinx.serialization.json.Json
1614

1715
expect val platform: Platform
1816

@@ -82,34 +80,16 @@ interface Platform {
8280
"${Instant.fromEpochSeconds(epochSeconds).toLocalDateTime(TimeZone.currentSystemDefault())} $tzShortId"
8381
}
8482

85-
// Expect classes are not stable
86-
// expect class Platform {
87-
// val name: String
88-
// }
89-
90-
/** Common JSON instance for serde of JSON data. */
91-
val json by lazy {
92-
Json {
93-
prettyPrint = true
94-
isLenient = true
95-
ignoreUnknownKeys = true
96-
encodeDefaults = true
97-
explicitNulls = false
98-
decodeEnumsCaseInsensitive = true
99-
allowTrailingComma = true
100-
allowSpecialFloatingPointValues = true
101-
allowStructuredMapKeys = true
102-
allowComments = true
103-
classDiscriminatorMode = POLYMORPHIC
104-
}
105-
}
106-
10783
val log = KotlinLogging.logger {}
108-
10984
/** Gets the current date and time in UTC timezone. */
11085
val utcDateTimeNow
11186
get() = Clock.System.now().toLocalDateTime(TimeZone.UTC)
11287

11388
/** Gets the current date and time in the system's default time zone. */
11489
val localDateTimeNow
11590
get() = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault())
91+
92+
// Expect classes are not stable
93+
// expect class Platform {
94+
// val name: String
95+
// }
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package dev.suresh.http
2+
3+
import io.ktor.client.*
4+
import io.ktor.client.plugins.*
5+
import io.ktor.client.plugins.compression.*
6+
import io.ktor.client.plugins.contentnegotiation.*
7+
import io.ktor.client.plugins.cookies.*
8+
import io.ktor.client.plugins.logging.*
9+
import io.ktor.client.plugins.resources.*
10+
import io.ktor.http.*
11+
import io.ktor.serialization.kotlinx.json.*
12+
import io.ktor.util.*
13+
import kotlinx.serialization.json.ClassDiscriminatorMode.POLYMORPHIC
14+
import kotlinx.serialization.json.Json
15+
16+
/** Common JSON instance for serde of JSON data. */
17+
val json by lazy {
18+
Json {
19+
prettyPrint = true
20+
isLenient = true
21+
ignoreUnknownKeys = true
22+
encodeDefaults = true
23+
explicitNulls = false
24+
decodeEnumsCaseInsensitive = true
25+
allowTrailingComma = true
26+
allowSpecialFloatingPointValues = true
27+
allowStructuredMapKeys = true
28+
allowComments = true
29+
classDiscriminatorMode = POLYMORPHIC
30+
}
31+
}
32+
33+
/** Multiplatform HTTP client factory function. */
34+
expect fun httpClient(
35+
name: String = "Api Client",
36+
timeout: Timeout = Timeout.DEFAULT,
37+
retry: Retry = Retry.DEFAULT,
38+
config: HttpClientConfig<*>.() -> Unit = {
39+
install(Resources)
40+
41+
install(ContentNegotiation) { json(json) }
42+
43+
install(ContentEncoding) {
44+
deflate(1.0F)
45+
gzip(0.9F)
46+
}
47+
48+
install(HttpRequestRetry) {
49+
maxRetries = retry.attempts
50+
retryOnException(retryOnTimeout = true)
51+
retryOnServerErrors()
52+
exponentialDelay(maxDelayMs = retry.maxDelay.inWholeMilliseconds)
53+
}
54+
55+
install(HttpTimeout) {
56+
connectTimeoutMillis = timeout.connection.inWholeMilliseconds
57+
requestTimeoutMillis = timeout.read.inWholeMilliseconds
58+
socketTimeoutMillis = timeout.write.inWholeMilliseconds
59+
}
60+
61+
install(HttpCookies)
62+
63+
install(Logging) {
64+
logger = Logger.DEFAULT
65+
level = LogLevel.INFO
66+
sanitizeHeader { header -> header == HttpHeaders.Authorization }
67+
}
68+
69+
engine { pipelining = true }
70+
71+
followRedirects = true
72+
73+
install(UserAgent) { agent = name }
74+
75+
install(DefaultRequest) {
76+
headers.appendIfNameAndValueAbsent(
77+
HttpHeaders.ContentType, ContentType.Application.Json.toString())
78+
}
79+
80+
// install(Auth) {
81+
// basic {
82+
// credentials {
83+
// sendWithoutRequest { true }
84+
// BasicAuthCredentials(username = "", password = "")
85+
// }
86+
// }
87+
// }
88+
//
89+
// expectSuccess = false
90+
//
91+
// HttpResponseValidator {
92+
// validateResponse {
93+
// when (it.status.value) {
94+
// in 300..399 -> throw RedirectResponseException(it, "Redirect error")
95+
// in 400..499 -> throw ClientRequestException(it, "Client error")
96+
// in 500..599 -> throw ServerResponseException(it, "Server error")
97+
// }
98+
// }
99+
// }
100+
}
101+
): HttpClient

shared/src/commonMain/kotlin/dev/suresh/http/MediaApiClient.kt

Lines changed: 10 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,15 @@
11
package dev.suresh.http
22

33
import io.github.oshai.kotlinlogging.KotlinLogging
4-
import io.ktor.client.*
54
import io.ktor.client.call.*
65
import io.ktor.client.plugins.*
7-
import io.ktor.client.plugins.compression.*
8-
import io.ktor.client.plugins.contentnegotiation.*
9-
import io.ktor.client.plugins.cookies.*
10-
import io.ktor.client.plugins.logging.*
116
import io.ktor.client.plugins.resources.*
12-
import io.ktor.client.plugins.resources.Resources
13-
import io.ktor.http.*
147
import io.ktor.resources.*
15-
import io.ktor.serialization.kotlinx.json.*
16-
import io.ktor.util.*
178
import kotlinx.serialization.Serializable
189

19-
@Resource("/media-api/images.json") class ImgRes()
10+
@Resource("/media-api/images.json") class ImgRes
2011

21-
@Resource("/media-api/videos.json") class VideoRes()
12+
@Resource("/media-api/videos.json") class VideoRes
2213

2314
@Serializable
2415
data class Image(
@@ -37,73 +28,17 @@ data class Video(
3728
val poster: String? = null,
3829
)
3930

40-
class MediaApiClient(val timeout: Timeout = Timeout.DEFAULT, val retry: Retry = Retry.DEFAULT) {
31+
class MediaApiClient(timeout: Timeout = Timeout.DEFAULT, retry: Retry = Retry.DEFAULT) {
4132

4233
private val log = KotlinLogging.logger {}
4334

44-
private val client = HttpClient {
45-
install(Resources)
46-
install(ContentNegotiation) { json(dev.suresh.json) }
47-
48-
install(ContentEncoding) {
49-
deflate(1.0F)
50-
gzip(0.9F)
51-
}
52-
53-
install(HttpRequestRetry) {
54-
maxRetries = retry.attempts
55-
retryOnException(retryOnTimeout = true)
56-
retryOnServerErrors()
57-
constantDelay(millis = retry.maxDelay.inWholeMilliseconds)
58-
}
59-
60-
install(HttpTimeout) {
61-
connectTimeoutMillis = timeout.connection.inWholeMilliseconds
62-
requestTimeoutMillis = timeout.read.inWholeMilliseconds
63-
socketTimeoutMillis = timeout.write.inWholeMilliseconds
64-
}
65-
66-
install(HttpCookies)
67-
68-
install(Logging) {
69-
logger = Logger.DEFAULT
70-
level = LogLevel.INFO
71-
sanitizeHeader { header -> header == HttpHeaders.Authorization }
72-
}
73-
74-
engine { pipelining = true }
75-
76-
followRedirects = true
77-
78-
install(UserAgent) { agent = "Image API Client" }
79-
80-
install(DefaultRequest) {
81-
url("https://suresh.dev/")
82-
headers.appendIfNameAndValueAbsent(
83-
HttpHeaders.ContentType, ContentType.Application.Json.toString())
84-
}
85-
86-
// install(Auth) {
87-
// basic {
88-
// credentials {
89-
// sendWithoutRequest { true }
90-
// BasicAuthCredentials(username = "", password = "")
91-
// }
92-
// }
93-
// }
94-
//
95-
// expectSuccess = false
96-
//
97-
// HttpResponseValidator {
98-
// validateResponse {
99-
// when (it.status.value) {
100-
// in 300..399 -> throw RedirectResponseException(it, "Redirect error")
101-
// in 400..499 -> throw ClientRequestException(it, "Client error")
102-
// in 500..599 -> throw ServerResponseException(it, "Server error")
103-
// }
104-
// }
105-
// }
106-
}
35+
private val client =
36+
httpClient(
37+
name = "Media API Client",
38+
timeout = timeout,
39+
retry = retry,
40+
)
41+
.config { defaultRequest { url("https://suresh.dev/") } }
10742

10843
suspend fun images() = client.get(ImgRes()).body<List<Image>>()
10944

shared/src/commonTest/kotlin/dev/suresh/CommonTest.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package dev.suresh
22

3+
import dev.suresh.http.json
34
import io.ktor.client.*
45
import io.ktor.client.engine.mock.*
56
import io.ktor.client.plugins.*
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package dev.suresh.http
2+
3+
import io.ktor.client.*
4+
import io.ktor.client.engine.js.*
5+
6+
actual fun httpClient(
7+
name: String,
8+
timeout: Timeout,
9+
retry: Retry,
10+
config: HttpClientConfig<*>.() -> Unit
11+
) = HttpClient(Js) { config(this) }

shared/src/jsMain/kotlin/dev/suresh/JsJodaTZ.kt renamed to shared/src/jsMain/kotlin/dev/suresh/tz/JsJodaTZ.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package dev.suresh
1+
package dev.suresh.tz
22

33
// @JsModule("@js-joda/timezone") @JsNonModule external object JsJodaTimeZoneModule
44
//

0 commit comments

Comments
 (0)