[英]How do I properly use Kotlin Flow in Ktor streaming responses?
emphasized text I am trying to use Kotlin Flow to process some data asynchronously and in parallel, and stream the responses to the client as they occur, as opposed to waiting until all the jobs are complete.强调文本我正在尝试使用 Kotlin Flow 来异步并行处理一些数据,并使用 stream 对客户端的响应,而不是等到所有作业完成。
After unsuccessfully trying to just send the flow
itself to the response, like this: call.respond(HttpStatusCode.OK, flow.toList())
在尝试将flow
本身发送到响应失败后,如下所示: call.respond(HttpStatusCode.OK, flow.toList())
... I tinkered for hours trying to figure it out, and came up with the following. ...我花了几个小时试图弄清楚,并想出了以下内容。 Is this correct?这个对吗? It seems there should be a more idiomatic way of sending a Flow<MyData>
as a response, like one can with a Flux<MyData>
in Spring Boot.似乎应该有一种更惯用的方式来发送Flow<MyData>
作为响应,就像在 Spring Boot 中使用Flux<MyData>
一样。
Also, it seems that using the below method does not cancel the Flow when the HTTP request is cancelled, so how would one cancel it in Ktor?另外,当 HTTP 请求被取消时,使用以下方法似乎不会取消 Flow,那么如何在 Ktor 中取消它呢?
data class MyData(val number: Int)
class MyService {
fun updateAllJobs(): Flow<MyData> =
flow {
buildList { repeat(10) { add(MyData(Random.nextInt())) } }
// Docs recommend using `onEach` to "delay" elements.
// However, if I delay here instead of in `map`, all elements are held
// and emitted at once at the very end of the cumulative delay.
// .onEach { delay(500) }
.map {
// I want to emit elements in a "stream" as each is computed.
delay(500)
emit(it)
}
}
}
fun Route.jobRouter() {
val service: MyService by inject() // injected with Koin
put("/jobs") {
val flow = service.updateAllJobs()
// Just using the default Jackson mapper for this example.
val mapper = jsonMapper { }
// `respondOutputStream` seems to be the only way to send a Flow as a stream.
call.respondOutputStream(ContentType.Application.Json, HttpStatusCode.OK) {
flow.collect {
println(it)
// The data does not stream without the newline and `flush()` call.
write((mapper.writeValueAsString(it) + "\n").toByteArray())
flush()
}
}
}
}
The best solution I was able to find (although I don't like it) is to use respondBytesWriter
to write data to a response body channel.我能找到的最佳解决方案(尽管我不喜欢它)是使用respondBytesWriter
将数据写入响应主体通道。 In the handler, a new job to collect the flow is launched to be able to cancel it if the channel is closed for writing (HTTP request is canceled):在处理程序中,将启动一个收集流的新作业,以便在通道关闭写入时能够取消它(HTTP 请求被取消):
fun Route.jobRouter(service: MyService) {
put("/jobs") {
val flow = service.updateAllJobs()
val mapper = jsonMapper {}
call.respondBytesWriter(contentType = ContentType.Application.Json) {
val job = launch {
flow.collect {
println(it)
try {
writeStringUtf8(mapper.writeValueAsString(it))
flush()
} catch (_: ChannelWriteException) {
cancel()
}
}
}
job.join()
}
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.