https://kotlinlang.org logo
Title
o

Osman Saral

01/25/2022, 9:48 PM
Can I intercept a request before it's even sent and simulate the response? The use case is, our token is killed after a while and we want to refresh the token, without getting an unauthorized response from server. If the mentioned time is elapsed, client should know it and refresh the token automatically. I've added what I've tried below:
@OptIn(ExperimentalTime::class)
class TokenKiller {
    companion object Feature: HttpClientFeature<TokenKiller, TokenKiller> {
        override val key: AttributeKey<TokenKiller> = AttributeKey("TokenKiller")

        override fun prepare(block: TokenKiller.() -> Unit): TokenKiller {
            return TokenKiller().apply(block)
        }
        override fun install(feature: TokenKiller, scope: HttpClient) {
            var mark: TimeMark? = null
            scope.requestPipeline.intercept(HttpRequestPipeline.Before) {
                val duration = mark?.elapsedNow() ?: Duration.ZERO

                if (duration.inWholeMinutes > 10) {
                    //cancel the request and with Unauthorized here, so my AuthProvider can do it's job
                } else {
                    //continue
                }
            }

            scope.feature(HttpSend)!!.intercept { origin, context ->
                if (origin.response.status == HttpStatusCode.OK) {
                    mark = TimeSource.Monotonic.markNow() //I mark the last successful response here.I know i should store it
                }
                return@intercept origin
            }
        }
    }
}
a

Aleksei Tirman [JB]

01/26/2022, 10:43 AM
If the time is over you can refresh a token, finish the execution of the whole pipeline and execute it again. Here is an example:
val needRefresh = AtomicBoolean(true)

suspend fun main(): Unit = coroutineScope {
    val client = HttpClient(Apache) {}

    val phase = PipelinePhase("")
    client.requestPipeline.insertPhaseBefore(HttpRequestPipeline.Before, phase)
    client.requestPipeline.intercept(phase) {
        if (needRefresh.get()) {
            client.refreshToken()
            // Finishes execution of the whole pipeline so a request isn't sent
            finish()
            // Since a token is already refreshed we can execute the whole pipeline from the beginning again
            proceedWith(client.requestPipeline.execute(context, subject))
        }
    }

    val r = client.get<String>("<https://httpbin.org/get>")
    println(r)
}

suspend fun HttpClient.refreshToken() {
    println("Token refreshed")
    needRefresh.set(false)
}
The bigger question is how to properly track the elapsed time.
o

Osman Saral

01/26/2022, 11:01 AM
Can't I intercept HttpSend and save the time when status is 200 like I did above?
a

Aleksei Tirman [JB]

01/26/2022, 11:03 AM
In this case, the elapsed time doesn't accumulate.
o

Osman Saral

01/26/2022, 11:07 AM
well, It's not needed. I need the time between Last OK response and now. But there is another thing, I use
refreshTokens
block in
install(Auth)
the only way to call it is respond with Unauthorized. I should add another interceptor then? Like you did.
a

Aleksei Tirman [JB]

01/26/2022, 11:14 AM
So you want to artificially respond with Unauthorized to make the Auth plugin call
refreshTokens
's block?
o

Osman Saral

01/26/2022, 11:15 AM
yes exactly. without going to the server.
a

Aleksei Tirman [JB]

01/26/2022, 11:49 AM
Unfortunately, this isn't possible because the
Auth
plugin works with the
HttpSend
plugin that executes callbacks only after an actual request is made. So in a crude way, it's possible to replace the response but then the
HttpSend
plugin won't intercept a pipeline.
o

Osman Saral

01/26/2022, 11:53 AM
okay then, I need to cancel and manually refresh token without refreshTokens block.