Can I intercept a request before it's even sent an...
# ktor
o
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:
Copy code
@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
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:
Copy code
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
Can't I intercept HttpSend and save the time when status is 200 like I did above?
a
In this case, the elapsed time doesn't accumulate.
o
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
So you want to artificially respond with Unauthorized to make the Auth plugin call
refreshTokens
's block?
o
yes exactly. without going to the server.
a
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
okay then, I need to cancel and manually refresh token without refreshTokens block.