Hi , I am finding it really hard to make `transfor...
# ktor
p
Hi , I am finding it really hard to make
transformResponseBody{ }
, work using
createClientPlugin
It works if put before
ContentNegotiation
plugin, but any plugin with
transformResponseBody
put after
ContentNegotiation
dosen’t get called at all. And in my other projects , so far i have been unsuccessful in making
transformResponseBody
work. Every other method provided by
createClientPlugin
works without problem. Is there something i am missing or there is some extra step needed to make it work?
a
Can you please describe the effect you're trying to achieve?
p
I basically need to decrypt the content before sending it to
ContentNegotiation
So was trying to achieve it with
transformResponseBody
, but i don’t get any callback in this method
So far from what i have tried ,
onResponse
get’s called for every single installed Custom plugin , but not the case with
transformResponseBody
,This should not be the case , Right? Or am i missing something
a
The
transformResponseBody
's block is called only when the response body is still the
ByteReadChannel
(it isn't transformed to the specified type). The order of the plugins determines which transformation come first since both use the same pipeline phase. You can insert the phase before the
HttpResponsePipeline.Transform
phase to make your transformation executed first. Here is an example:
Copy code
fun main(): Unit = runBlocking {
    val client = HttpClient(CIO) {
        install(ContentNegotiation) {
            json(Json {
                ignoreUnknownKeys = true
            })
        }
    }

    val beforeTransform = PipelinePhase("BeforeTransform")
    client.responsePipeline.insertPhaseBefore(HttpResponsePipeline.Transform, beforeTransform)
    client.responsePipeline.intercept(beforeTransform) { (typeInfo, body)->
        // Decrypt the body here and then pass it to proceedWith(...)
        println(body)
        proceedWith(HttpResponseContainer(typeInfo, body))
    }

    val response = client.get("<https://httpbin.org/get>")
    println(response.body<HttpBin>())
}

@Serializable
data class HttpBin(val origin: String)
p
I tested it a bit more , even if i put this plugin at the very top , I still do not get callback for
transformResponseBody
, But at the same time i am able to read content using
onResponse
very easily. What can be the reason for that,i tried to print
it.content.toString
inside onResponse, Output is
ByteBufferChannel(92329252, IDLE(with buffer))
I will use the implementation provided by you to make it work , Still wondering why
transformResponseBody
didn’t work
a
The reason is that
onResponse
is executed before the
HttpResponse.body
method is called.
a
Hi! I found this while searching because I'm trying to do something similar. I found an api that returns everything in jsonp, which basically means it's wrapped in parenthesis.
({ myJson })
I was hoping the easiest way to serialize this properly was to intercept it, look if it started and ended with parens, and remove them from the string. This solution seems pretty close, but I'm unclear how to manipulate the byte array here:
Copy code
// Decrypt the body here and then pass it to proceedWith(...)
println(body)
Do you have any examples of this?
a
You can use the following method to remove JSONP characters from the response body:
Copy code
@OptIn(DelicateCoroutinesApi::class)
suspend fun ByteReadChannel.transform(): ByteReadChannel {
    val body = this

    val first = body.readByte()
    val second = body.readByte()

    if (first != '('.code.toByte() || second != '{'.code.toByte()) {
        return GlobalScope.writer {
            channel.writeByte(first)
            channel.writeByte(second)
            body.copyTo(channel)
        }.channel
    }

    return GlobalScope.writer {
        while (!body.isClosedForRead) {
            val arr = body.readRemaining(8 * 1024).readByteArray()

            if (body.isClosedForRead && arr.size >= 2) {
                val first = arr[arr.size - 2]
                val second = arr[arr.size - 1]

                if (first == '}'.code.toByte() && second == ')'.code.toByte()) {
                    channel.writeFully(arr, 0, arr.size - 2)
                    break
                } else {
                    channel.writeFully(arr)
                    continue
                }
            }

            channel.writeFully(arr)
        }
    }.channel
}
Here is a usage example:
Copy code
client.responsePipeline.intercept(beforeTransform) { (typeInfo, body)->
    when (body) {
        is ByteReadChannel -> {
            proceedWith(HttpResponseContainer(typeInfo, body.transform()))
        }
        else -> TODO()
    }
}
a
Amazing, thank you a ton! That worked out great.
100 Views