moko-network
moko-network copied to clipboard
RefreshTokenPlugin: excecution is locked when updateTokenHandler gets 401 error
updateTokenHandler usually contains a request for refreshing accessToken, but in some APIs this request can get 401 Unauthorized error (for example, when refreshToken is outdated as well). In this case RefreshTokenPlugin gets locked by mutex, and no other requests can be executed
Possible solution - add needSkipRequest lambda as another parameter in RefreshTokenPlugin:
class RefreshTokenPlugin(
private val updateTokenHandler: suspend () -> Boolean,
private val isCredentialsActual: (HttpRequest) -> Boolean,
private val needSkipRequest: (HttpRequest) -> Boolean,
) {
class Config {
var updateTokenHandler: (suspend () -> Boolean)? = null
var isCredentialsActual: ((HttpRequest) -> Boolean)? = null
var needSkipRequest: (HttpRequest) -> Boolean = { false }
fun build() = RefreshTokenPlugin(
updateTokenHandler = updateTokenHandler
?: throw IllegalArgumentException("updateTokenHandler should be passed"),
isCredentialsActual = isCredentialsActual
?: throw IllegalArgumentException("isCredentialsActual should be passed"),
needSkipRequest = needSkipRequest,
)
}
companion object Plugin : HttpClientPlugin<Config, RefreshTokenPlugin> {
private val refreshTokenHttpPluginMutex = Mutex()
override val key = AttributeKey<RefreshTokenPlugin>("RefreshTokenPlugin")
override fun prepare(block: Config.() -> Unit) = Config().apply(block).build()
override fun install(plugin: RefreshTokenPlugin, scope: HttpClient) {
scope.receivePipeline.intercept(HttpReceivePipeline.After) {
if (subject.status != HttpStatusCode.Unauthorized || plugin.needSkipRequest(subject.request)) {
proceedWith(subject)
return@intercept
}
refreshTokenHttpPluginMutex.withLock {
// If token of the request isn't actual, then token has already been updated and
// let's just to try repeat request
if (!plugin.isCredentialsActual(subject.request)) {
val requestBuilder = HttpRequestBuilder().takeFrom(subject.request)
val result: HttpResponse = scope.request(requestBuilder)
proceedWith(result)
return@intercept
}
// Else if token of the request is actual (same as in the storage), then need to send
// refresh request.
if (plugin.updateTokenHandler.invoke()) {
// If the request refresh was successful, then let's just to try repeat request
val requestBuilder = HttpRequestBuilder().takeFrom(subject.request)
val result: HttpResponse = scope.request(requestBuilder)
proceedWith(result)
} else {
// If the request refresh was unsuccessful
proceedWith(subject)
}
}
}
}
}
}
Example usage:
needSkipRequest = { request ->
request.call.request.url.encodedPath.endsWith("/auth/refresh")
}