https://kotlinlang.org logo
Title
a

Angad

08/17/2021, 6:20 AM
I have created a Ktor feature to retry network call in case of Unauthorized error with an updated access token. In receive pipeline, I am intercepting the before phase to change the response in case of the error. I have validated that the retried call with the new access token is being success, however the status code that I am receiving in the client side is still Unauthorized. The feature implementation looks like below:
private fun handle(config: Config, scope: HttpClient) {
    scope.receivePipeline.intercept(HttpReceivePipeline.Before) { response ->
        if (context.response.status == HttpStatusCode.Unauthorized) {
            val client = HttpClient(scope.engine)
            var newResponse: HttpResponse = context.response
            runCatching {
                val accessToken = config.tokenRefreshService?.getAccessToken()
                val requestBuilder = request {
                    url(context.request.url)
                    headers {
                        for (header in context.request.headers.names()) {
                            if (header != AUTH_TOKEN) {
                                append(header, context.request.headers[header] ?: "")
                            } else {
                                append(header, NetworkModule.authToken(accessToken ?: ""))
                            }
                        }
                    }
                    method = context.request.method
                    body = context.request.content
                }
                client.request(requestBuilder) as HttpResponse
            }.onSuccess {
                newResponse = it
            }.onFailure {
                newResponse = response
            }
            client.close()
            if (newResponse == context.response) {
                proceed()
            } else {
                proceedWith(newResponse)
            }
        }
    }
}
Please can someone help with this? I was expecting that by updating the subject in
proceedWith
it will work.
a

Aleksei Tirman [JB]

08/17/2021, 7:48 AM
It works as expected with a bit simplified code:
val scope = HttpClient(CIO)

scope.receivePipeline.intercept(HttpReceivePipeline.Before) { response ->
    if (context.response.status == HttpStatusCode.Unauthorized) {
        val client = HttpClient(scope.engine)

        var newResponse: HttpResponse = context.response
        runCatching {
            val requestBuilder = request {
                url(context.request.url)
                url {
                    takeFrom(context.request.url)
                    encodedPath = "/get"
                }
                headers {
                    append("header", "value")
                }
                method = context.request.method
                body = context.request.content
            }

            client.request(requestBuilder) as HttpResponse
        }.onSuccess {
            newResponse = it
        }.onFailure {
            newResponse = response
        }
        client.close()

        if (newResponse == context.response) {
            proceed()
        } else {
            proceedWith(newResponse)
        }
    }
}

val r = scope.get<HttpResponse>("<https://httpbin.org/status/401>")
println(r.status)
println(r.receive() as String)
I suspect that a server for the second request still responds with 401 status code or some other interceptor interferes.
a

Angad

08/17/2021, 11:19 AM
The responsePipeline is continuing with the 401 response, although one I proceed with in receivePipeline was success.
Surprisingly, the UT that I have written for this passing
a

Aleksei Tirman [JB]

08/17/2021, 12:51 PM
Could you please share a code for your interceptor of the response pipeline?
a

Angad

08/17/2021, 4:16 PM
Issue is resolved, All I have done is to intercept the after phase and do the above retrying in it.
@Aleksei Tirman [JB] The request is failing in this case if the request is retried using the Feature. In regular case it doesn’t happen.
No transformation found: class io.ktor.utils.io.ByteBufferChannel (Kotlin reflection is not available) -> class <class name> (Kotlin reflection is not available)
with response from <endpoint>:
status: 200 OK
response headers: 
connection: keep-alive
, content-length: 65
, content-type: application/json;charset=UTF-8
, date: Wed, 18 Aug 2021 07:09:31 GMT
, x-android-received-millis: 1629270570413
, x-android-response-source: NETWORK 200
, x-android-selected-protocol: http/1.1
, x-android-sent-millis: 1629270568777