I'm working on an API wrapper with Ktor for an ext...
# ktor
s
I'm working on an API wrapper with Ktor for an external API that has oauth support, so the SDK can take actions on a user's behalf by passing a different API key in the request headers. Is there something I can do to make this easier for users to work with, other than adding an optional
token
parameter to every single request? I have a lot of endpoints to do and want something more scaleable. Ideally looking for something like
Copy code
val client = MyClient(token = "DEFAULT_APP_TOKEN") // Token to use for all requests unless specified otherwise
client.someRequest() // Uses default token

// ...

client.withToken("SOME_USER_OAUTH_TOKEN") {
    client.someRequest() // Uses user-specific token
}
Best I can think of is storing the token in a variable in the
client
and temporarily modifying it, but I don't feel like that would be safe in a coroutine context where multiple requests can run at the same time
a
So the token is sent in the
Autorization
header?
s
Yes
I currently use DefaultRequest to add the token, and I can easily modify it manually per request by passing in another token, but adding an extra parameter for this in every single method feels a bit repetitive given that I have multiple hundreds of API calls
Was hoping I can cleanly work around it
a
You can use the coroutine context to pass the token to the request interceptor. Here is an example:
Copy code
suspend fun main() {
    val client = HttpClient(CIO) {}

    client.requestPipeline.intercept(HttpRequestPipeline.Transform) {
        val tokenElement = coroutineContext[TokenContextElement]

        if (tokenElement != null) {
            context.header(HttpHeaders.Authorization, "Bearer ${tokenElement.token}")
        }
    }

    val response = client.withToken("123") {
        client.get("<https://httpbin.org/get>")
    }

    println(response.bodyAsText())

}

suspend fun HttpClient.withToken(token: String, request: suspend HttpClient.() -> HttpResponse): HttpResponse {
    return withContext(TokenContextElement(token)) {
        request()
    }
}

class TokenContextElement(val token: String) : CoroutineContext.Element {
    override val key: CoroutineContext.Key<*> get() = TokenContextElement
    companion object : CoroutineContext.Key<TokenContextElement>
}
s
That's actually an interesting solution, thanks!