From 17bcb4585026cebd9d719be4d288a55829cb6aee Mon Sep 17 00:00:00 2001 From: Alexander Minayev Date: Mon, 20 Jul 2020 00:17:28 +0700 Subject: [PATCH 1/3] new feature HttpRetrySendFeature for retrying failed request --- .../network/features/HttpRetrySendFeature.kt | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 network/src/commonMain/kotlin/dev/icerock/moko/network/features/HttpRetrySendFeature.kt diff --git a/network/src/commonMain/kotlin/dev/icerock/moko/network/features/HttpRetrySendFeature.kt b/network/src/commonMain/kotlin/dev/icerock/moko/network/features/HttpRetrySendFeature.kt new file mode 100644 index 0000000..7691242 --- /dev/null +++ b/network/src/commonMain/kotlin/dev/icerock/moko/network/features/HttpRetrySendFeature.kt @@ -0,0 +1,76 @@ +package dev.icerock.moko.network.features + +import io.ktor.client.HttpClient +import io.ktor.client.features.HttpClientFeature +import io.ktor.client.request.HttpRequestBuilder +import io.ktor.client.request.HttpSendPipeline +import io.ktor.client.statement.HttpStatement +import io.ktor.util.AttributeKey +import io.ktor.utils.io.errors.IOException +import kotlinx.coroutines.delay + +/** +@param onGetDelay the callback for calculation a delay between failed request and next request +@param maxAmountRetrying the max amounts retrying requests +@param onConditionRetrying the callback for condition a retrying request. By default it check `e is IOException ` + */ +class HttpRetrySendFeature( + private val onGetDelay: (lastDelayInMillisecond: Long, timeRetrying: Int) -> Long, + private val maxAmountsRetrying: Int, + private val onConditionRetrying: (e: Throwable) -> Boolean +) { + + class Config { + var onGetDelay: (lastDelayInMillisecond: Long, timeRetrying: Int) -> Long = + { _, _ -> 2_000L } + var maxAmountRetrying: Int = 3 // first request + three retrying requests, + var onConditionRetrying: (e: Throwable) -> Boolean = { e -> e is IOException } + fun build() = HttpRetrySendFeature(onGetDelay, maxAmountRetrying, onConditionRetrying) + } + + companion object Feature : HttpClientFeature { + private const val LAST_DELAY_HEADER = "HttpRetrySendFeature-Last-Delay" + private const val RETRY_COUNTER_HEADER = "HttpRetrySendFeature-Retry-Counter" + + override val key: AttributeKey = AttributeKey("HttpRetrySendFeature") + + override fun install(feature: HttpRetrySendFeature, scope: HttpClient) { + scope.sendPipeline.intercept(HttpSendPipeline.Before) { + val counter = context.headers[RETRY_COUNTER_HEADER]?.toInt() ?: 0 + val lastDelay = context.headers[LAST_DELAY_HEADER]?.toLong() + ?: feature.onGetDelay(0L, counter) + if (counter < feature.maxAmountsRetrying) { + try { + context.headers.remove(LAST_DELAY_HEADER) + context.headers.remove(RETRY_COUNTER_HEADER) + proceed() + } catch (e: Throwable) { + if (feature.onConditionRetrying(e)) { + val requestBuilder = HttpRequestBuilder().takeFrom(context) + val indexRetrying = counter + 1 + val nextDelay = feature.onGetDelay(lastDelay, counter) + requestBuilder.headers[RETRY_COUNTER_HEADER] = indexRetrying.toString() + requestBuilder.headers[LAST_DELAY_HEADER] = nextDelay.toString() + delay(nextDelay) + finish() + + proceedWith(HttpStatement(requestBuilder, scope).execute().call) + } else { + throw e + } + } + } else { + context.headers.remove(LAST_DELAY_HEADER) + context.headers.remove(RETRY_COUNTER_HEADER) + proceed() + } + + } + } + + override fun prepare(block: Config.() -> Unit): HttpRetrySendFeature { + return Config().apply(block).build() + } + + } +} From 605ea5e36ae5ad2d9bf836fffcd5694283b2168e Mon Sep 17 00:00:00 2001 From: Alexander Minayev Date: Mon, 20 Jul 2020 00:19:09 +0700 Subject: [PATCH 2/3] fix excess operation --- .../kotlin/dev/icerock/moko/network/features/TokenFeature.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/network/src/commonMain/kotlin/dev/icerock/moko/network/features/TokenFeature.kt b/network/src/commonMain/kotlin/dev/icerock/moko/network/features/TokenFeature.kt index 8d7abc5..187cfb5 100644 --- a/network/src/commonMain/kotlin/dev/icerock/moko/network/features/TokenFeature.kt +++ b/network/src/commonMain/kotlin/dev/icerock/moko/network/features/TokenFeature.kt @@ -33,8 +33,7 @@ class TokenFeature private constructor( override fun install(feature: TokenFeature, scope: HttpClient) { scope.requestPipeline.intercept(HttpRequestPipeline.State) { feature.tokenProvider.getToken()?.apply { - context.headers.remove(feature.tokenHeaderName) - context.header(feature.tokenHeaderName, this) + context.headers[feature.tokenHeaderName] = this } } } From 5ce396497161b352ed66c16be05c57039af39c60 Mon Sep 17 00:00:00 2001 From: Alexander Minayev Date: Mon, 20 Jul 2020 18:03:06 +0700 Subject: [PATCH 3/3] fix review: param naming and add license --- .../network/features/HttpRetrySendFeature.kt | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/network/src/commonMain/kotlin/dev/icerock/moko/network/features/HttpRetrySendFeature.kt b/network/src/commonMain/kotlin/dev/icerock/moko/network/features/HttpRetrySendFeature.kt index 7691242..e2759b4 100644 --- a/network/src/commonMain/kotlin/dev/icerock/moko/network/features/HttpRetrySendFeature.kt +++ b/network/src/commonMain/kotlin/dev/icerock/moko/network/features/HttpRetrySendFeature.kt @@ -1,3 +1,7 @@ +/* + * Copyright 2020 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. + */ + package dev.icerock.moko.network.features import io.ktor.client.HttpClient @@ -10,14 +14,14 @@ import io.ktor.utils.io.errors.IOException import kotlinx.coroutines.delay /** -@param onGetDelay the callback for calculation a delay between failed request and next request +@param delayGetter the function for calculation a delay between failed request and next request @param maxAmountRetrying the max amounts retrying requests -@param onConditionRetrying the callback for condition a retrying request. By default it check `e is IOException ` +@param isShouldRetryRequest the function for condition a retrying request. By default it check `e is IOException ` */ class HttpRetrySendFeature( - private val onGetDelay: (lastDelayInMillisecond: Long, timeRetrying: Int) -> Long, + private val delayGetter: (lastDelayInMillisecond: Long, timeRetrying: Int) -> Long, private val maxAmountsRetrying: Int, - private val onConditionRetrying: (e: Throwable) -> Boolean + private val isShouldRetryRequest: (e: Throwable) -> Boolean ) { class Config { @@ -38,17 +42,17 @@ class HttpRetrySendFeature( scope.sendPipeline.intercept(HttpSendPipeline.Before) { val counter = context.headers[RETRY_COUNTER_HEADER]?.toInt() ?: 0 val lastDelay = context.headers[LAST_DELAY_HEADER]?.toLong() - ?: feature.onGetDelay(0L, counter) + ?: feature.delayGetter(0L, counter) if (counter < feature.maxAmountsRetrying) { try { context.headers.remove(LAST_DELAY_HEADER) context.headers.remove(RETRY_COUNTER_HEADER) proceed() } catch (e: Throwable) { - if (feature.onConditionRetrying(e)) { + if (feature.isShouldRetryRequest(e)) { val requestBuilder = HttpRequestBuilder().takeFrom(context) val indexRetrying = counter + 1 - val nextDelay = feature.onGetDelay(lastDelay, counter) + val nextDelay = feature.delayGetter(lastDelay, counter) requestBuilder.headers[RETRY_COUNTER_HEADER] = indexRetrying.toString() requestBuilder.headers[LAST_DELAY_HEADER] = nextDelay.toString() delay(nextDelay)