Ulrich Winter
06/27/2023, 7:08 AMonResponse()
consumes that content and makes it unavailable for other plugins (like Logging
) and for the code using the http client.
I found the io.ktor.client.plugins.observer.ResponseObserver
class, which is also used by the client Logging
plugin. But there is not much documentation available for it.
Is there any hint how a client plugin can inspect the response body content?
From the docs of the onResponse()
hook I originally concluded, that this is supported out of the box:
There you can inspect the response in a way you want: save cookies, add logging, etc.
Aleksei Tirman [JB]
06/27/2023, 8:23 AMUlrich Winter
06/27/2023, 9:54 AMval DemoPlugin = createClientPlugin("Demo") {
// The test fails, if one of response.readBytes() or response.bodyAsText() is called in this hook.
onResponse { response ->
val body = String(response.readBytes(), response.charset() ?: Charsets.UTF_8)
// failure reason, if response.readBytes() is called:
// expected:<[Hello, world!]> but was:<[]>
// also, the body is missing in the client logging output:
// BODY START
//
// BODY END
// val body = response.bodyAsText(Charsets.UTF_8)
// failure reason, if response.bodyAsText() is called:
// Parent job is Completed
// kotlinx.coroutines.JobCancellationException: Parent job is Completed; job=JobImpl{Completed}@1782e8fc
// in this case, the client log mentions the body being omitted:
// BODY START
// [response body omitted]
// BODY END
}
}
class ApplicationTest {
@Test
fun testRoot() = testApplication {
val client = createClient {
install(Logging) {
logger = Logger.DEFAULT
level = LogLevel.ALL
}
install(DemoPlugin)
}
val response = client.get("/")
assertEquals(HttpStatusCode.OK, response.status)
assertEquals("Hello, world!", response.bodyAsText())
}
}
Ulrich Winter
06/27/2023, 10:00 AMresponse.readBytes()
or response.bodyAsText()
within an onResponse
hook of a ktor client plugin.Ulrich Winter
06/27/2023, 1:36 PM/**
* This ktor client plugin reads the response body but keeps it available for the application code using the client (and for other plugins).
* This is achieved by using the ktor internal ResponseObserver class.
*/
public class ResponseReading private constructor(
) {
public companion object : HttpClientPlugin<Unit, ResponseReading> {
override val key: AttributeKey<ResponseReading> = AttributeKey("ResponseReading")
override fun prepare(block: Unit.() -> Unit): ResponseReading {
return ResponseReading()
}
override fun install(plugin: ResponseReading, scope: HttpClient) {
plugin.setupReadResponse(scope)
}
}
private fun setupReadResponse(scope: HttpClient) {
val observer: ResponseHandler = observer@{ response ->
val body = String(response.readBytes(), response.charset() ?: Charsets.UTF_8)
println("body as read in plugin: $body")
}
ResponseObserver.install(ResponseObserver(observer), scope)
}
}
Aleksei Tirman [JB]
06/27/2023, 1:47 PMResponseObserver
plugin works and you want to incorporate it in your own plugin, you can take the code of the ResponseObserver
plugin as an example.Ulrich Winter
06/27/2023, 1:52 PMreceivePipeline
.
Would it really help to reimplement the observer, when it still relies on internal things?
What do you think: wouldn’t it be helpful for a broader ktor user base to support this response reading behaviour within the new ktor client plugin api, e.g. by providing a new hook type?