Hi everyone, I’m using Ktor with interceptors and...
# ktor
s
Hi everyone, I’m using Ktor with interceptors and facing an issue with the response pipeline. My setup is as follows:
Copy code
override fun install(
        plugin: InterceptorPlugin,
        scope: HttpClient,
    ) {
    scope.requestPipeline.intercept(HttpRequestPipeline.Transform) { request ->
        userInterceptors.forEach {
            it.interceptor.encryptContent(this, request)
        }
    }

    scope.responsePipeline.intercept(HttpResponsePipeline.Receive) { (_, _) ->
            userResponseInterceptors.forEach { 
                it.decrypt(this, plugin)
            }
    }
}
For end to end encryption, I am using "HttpRequestPipeline.Transform" and ecrypting the request and for decryption i am using "HttpResponsePipeline.Receive" it is working fine when api succeeds. The problem I’m encountering is that for every API success, HttpResponsePipeline.Receive is called once, but for every failure, it gets called twice. when this pipeline is called second time then it has empty response and it caused exception because body is already consumed. Has anyone experienced a similar issue or have any insights on why this might be happening?
a
For what kind of failure the interceptor is called twice?
s
400 bad request
@Aleksei Tirman [JB] below is my custom interceptor
Copy code
class ResponseDecryptionInterceptor : ApiResponseInterceptor {

    override suspend fun decrypt(
        response: PipelineContext<HttpResponseContainer, HttpClientCall>,
        plugin: InterceptorPlugin
    ) {
        val (type, content) = response.subject
            val method = response.context.request.method
            val status = response.context.response.status.value
            val contentLength = response.context.response.contentLength()
            if (contentLength == 0L) return@intercept
            if (contentLength == null && method == HttpMethod.Head) return@intercept
            if (content !is ByteReadChannel) return@intercept
            val decryptedResponse = decryptContent(response)
            val newResponse = with(plugin) {
                if (response.context.response.status.isSuccess()) {
                    HttpResponseContainer(type, decryptedResponse)
                }else{
                    HttpResponseContainer(type, DbxpMultiPartUtils.convertByteArrayToInput(decryptedResponse.toByteArray()))
                }
            }
    }

    @OptIn(InternalAPI::class)
    private suspend fun decryptContent(pipelineContext: PipelineContext<HttpResponseContainer, HttpClientCall>): String {
        pipelineContext.context.request.url.toString().contains("<http://10.0.2.2:8082>").not()) {
            val jsonResponseString = pipelineContext.context.response.content.readUTF8Line().orEmpty()
            val encryptedResponse = jsonResponseString?.fromStringJsonToObject<EncryptedResponse>(ignoreKeysUnknown = true)
            val result = encryptedResponse?.let {
                EncryptionUtil.decode(it.encryptedResponse)?.let { decoded ->
                    EncryptionUtil.decrypt(decoded)
                }
            }
            println("decrypted body : $result")
            if (!result.isNullOrEmpty()) {
                return result
            } else {
                throw NetworkExceptionHandler.ParseException()
            }
        }

}
so in 400 case, when first time decrypt method is called, it successfully decrypts the body. after that it is getting called again from responsepipeline and then it throws exception.
a
So do you have the
expectedSuccess = true
to make the 400 Bad Request a failure?
s
yes
a
Can you share a simplified code snippet to reproduce the multiple calls of the interceptor like so:
Copy code
val client = HttpClient(OkHttp) {
    expectSuccess = true
}

client.responsePipeline.intercept(HttpResponsePipeline.Receive) { (type, body) ->
    proceedWith(HttpResponseContainer(type, body))
    println("interceptor") // Called once
}

val response = client.get("<https://httpbin.org/status/400>")
println(response.bodyAsText())
309 Views