kotlin - 在不传播的情况下访问对象中的 ApplicationCall
问题描述
Ktor 中是否有可以静态访问当前 ApplicationCall 的线程安全方法?我正在尝试使以下简单示例起作用;
object Main {
fun start() {
val server = embeddedServer(Jetty, 8081) {
intercept(ApplicationCallPipeline.Call) {
// START: this will be more dynamic in the future, we don't want to pass ApplicationCall
Addon.processRequest()
// END: this will be more dynamic in the future, we don't want to pass ApplicationCall
call.respondText(output, ContentType.Text.Html, HttpStatusCode.OK)
return@intercept finish()
}
}
server.start(wait = true)
}
}
fun main(args: Array<String>) {
Main.start();
}
object Addon {
fun processRequest() {
val call = RequestUtils.getCurrentApplicationCall()
// processing of call.request.queryParameters
// ...
}
}
object RequestUtils {
fun getCurrentApplicationCall(): ApplicationCall {
// Here is where I am getting lost..
return null
}
}
我希望能够从 RequestUtils 中静态获取当前上下文的 ApplicationCall,以便我可以在任何地方访问有关请求的信息。这当然需要扩展以能够同时处理多个请求。
我已经用依赖注入和 ThreadLocal 做了一些实验,但没有成功。
解决方案
好吧,应用程序调用被传递给协程,因此尝试“静态”获取它真的很危险,因为所有请求都在并发上下文中处理。
Kotlin 官方文档在协程执行的上下文中讨论了Thread-local 。它使用 CoroutineContext 的概念来恢复特定/自定义协程上下文中的 Thread-Local 值。
但是,如果您能够设计一个完全异步的 API,您将能够通过直接创建自定义 CoroutineContext、嵌入请求调用来绕过线程本地。
编辑:我更新了我的示例代码以测试 2 种风格:
- 异步端点:完全基于协程上下文和挂起函数的解决方案
- 阻塞端点:使用线程本地来存储应用程序调用,如kotlin doc中所述。
import io.ktor.server.engine.embeddedServer
import io.ktor.server.jetty.Jetty
import io.ktor.application.*
import io.ktor.http.ContentType
import io.ktor.http.HttpStatusCode
import io.ktor.response.respondText
import io.ktor.routing.get
import io.ktor.routing.routing
import kotlinx.coroutines.asContextElement
import kotlinx.coroutines.launch
import kotlin.coroutines.AbstractCoroutineContextElement
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.coroutineContext
/**
* Thread local in which you'll inject application call.
*/
private val localCall : ThreadLocal<ApplicationCall> = ThreadLocal();
object Main {
fun start() {
val server = embeddedServer(Jetty, 8081) {
routing {
// Solution requiring full coroutine/ supendable execution.
get("/async") {
// Ktor will launch this block of code in a coroutine, so you can create a subroutine with
// an overloaded context providing needed information.
launch(coroutineContext + ApplicationCallContext(call)) {
PrintQuery.processAsync()
}
}
// Solution based on Thread-Local, not requiring suspending functions
get("/blocking") {
launch (coroutineContext + localCall.asContextElement(value = call)) {
PrintQuery.processBlocking()
}
}
}
intercept(ApplicationCallPipeline.ApplicationPhase.Call) {
call.respondText("Hé ho", ContentType.Text.Plain, HttpStatusCode.OK)
}
}
server.start(wait = true)
}
}
fun main() {
Main.start();
}
interface AsyncAddon {
/**
* Asynchronicity propagates in order to properly access coroutine execution information
*/
suspend fun processAsync();
}
interface BlockingAddon {
fun processBlocking();
}
object PrintQuery : AsyncAddon, BlockingAddon {
override suspend fun processAsync() = processRequest("async", fetchCurrentCallFromCoroutineContext())
override fun processBlocking() = processRequest("blocking", fetchCurrentCallFromThreadLocal())
private fun processRequest(prefix : String, call : ApplicationCall?) {
println("$prefix -> Query parameter: ${call?.parameters?.get("q") ?: "NONE"}")
}
}
/**
* Custom coroutine context allow to provide information about request execution.
*/
private class ApplicationCallContext(val call : ApplicationCall) : AbstractCoroutineContextElement(Key) {
companion object Key : CoroutineContext.Key<ApplicationCallContext>
}
/**
* This is your RequestUtils rewritten as a first-order function. It defines as asynchronous.
* If not, you won't be able to access coroutineContext.
*/
suspend fun fetchCurrentCallFromCoroutineContext(): ApplicationCall? {
// Here is where I am getting lost..
return coroutineContext.get(ApplicationCallContext.Key)?.call
}
fun fetchCurrentCallFromThreadLocal() : ApplicationCall? {
return localCall.get()
}
您可以在导航器中对其进行测试:
http://localhost:8081/blocking?q=test1
http://localhost:8081/blocking?q=test2
http://localhost:8081/async?q=test3
服务器日志输出:
blocking -> Query parameter: test1
blocking -> Query parameter: test2
async -> Query parameter: test3
推荐阅读
- c - 为什么我的循环不检查 argv 中的字母并打印出错误?
- javascript - 可以使用 useRef 挂钩清空输入值吗?
- java - CORS 和错误以及 Access-Control-Allow-Origin 标头问题
- php - 重复 ajax 调用以获取表的最新数据
- google-cloud-dataflow - 如何在 GCS 上自定义从 PubSub 到文本文件的 GCP 数据流
- symfony - 如何使用 Symfony 处理无效表单?
- javascript - 如何通过许多不同的类别列表对数据项列表进行分类,其中每个列表包含多个不同的类别值?
- php - 用 url 预先填写复选框
- php - 在 Windows 上使用 Deployer 部署 laravel 应用
- blazor-server-side - 了解和正确处理 Blazor Server 水合