Kotlin 访问对象中的ApplicationCall而不传播
Ktor中是否有线程安全的方法可以静态访问当前应用程序调用?我试图让下面这个简单的例子起作用Kotlin 访问对象中的ApplicationCall而不传播,kotlin,kotlin-coroutines,ktor,Kotlin,Kotlin Coroutines,Ktor,Ktor中是否有线程安全的方法可以静态访问当前应用程序调用?我试图让下面这个简单的例子起作用 object Main { fun start() { val server = embeddedServer(Jetty, 8081) { intercept(ApplicationCallPipeline.Call) { // START: this will be more dynamic in the future
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
}
}
objectmain{
有趣的开始{
val服务器=嵌入式服务器(Jetty,8081){
拦截(ApplicationCallPipeline.Call){
//开始:这将在未来更加动态,我们不想通过ApplicationCall
Addon.processRequest()
//结束:这将在未来更加动态,我们不希望通过ApplicationCall
call.respondText(输出,ContentType.Text.Html,HttpStatusCode.OK)
return@intercept完成()
}
}
server.start(wait=true)
}
}
趣味主线(args:Array){
Main.start();
}
对象插件{
fun processRequest(){
val call=RequestUtils.getCurrentApplicationCall()
//call.request.queryParameters的处理
// ...
}
}
对象请求程序{
趣味getCurrentApplicationCall():ApplicationCall{
//这就是我迷路的地方。。
返回空
}
}
我希望能够从RequestUtils静态地获得当前上下文的ApplicationCall,这样我就可以在任何地方访问有关请求的信息。当然,这需要扩展,以便能够同时处理多个请求
我已经用依赖注入和ThreadLocal做了一些实验,但没有成功。好吧,应用程序调用被传递给一个协同程序,因此尝试“静态”获取它是非常危险的,因为所有请求都在并发上下文中处理 科特林的官方文件谈到。它使用CoroutineContext的概念来恢复特定/自定义coroutine上下文中的线程本地值 但是,如果您能够设计一个完全异步的API,那么您将能够通过直接创建定制的CoroutineContext(嵌入请求调用)绕过线程局部变量 编辑:我已经更新了示例代码以测试2种口味:
- 异步端点:完全基于协同路由上下文和挂起函数的解决方案
- 阻塞端点:使用线程本地存储应用程序调用,如中所述
您希望用于此的关键机制是
CoroutineContext
。您可以在此处设置要在任何子协同程序或挂起函数调用中使用的键值对
我将试着举出一个例子
首先,让我们定义一个coroutinextelement
,它将允许我们向coroutinextext
添加一个ApplicationCall
class ApplicationCallElement(var call: ApplicationCall?) : AbstractCoroutineContextElement(ApplicationCallElement) {
companion object Key : CoroutineContext.Key<ApplicationCallElement>
}
然后我们可以一起使用它,就像下面这个测试用例中,我们可以从嵌套的挂起函数获得调用:
suspend fun getSomethingFromCall(): String {
val call = coroutineContext[ApplicationCallElement.Key]?.call ?: throw Exception("Element not set")
return call.parameters["key"] ?: throw Exception("Parameter not set")
}
fun Application.myApp() {
routing {
route("/foo") {
get {
withCall {
call.respondText(getSomethingFromCall())
}
}
}
}
}
class ApplicationCallTest {
@Test
fun `we can get the application call in a nested function`() {
withTestApplication({ myApp() }) {
with(handleRequest(HttpMethod.Get, "/foo?key=bar")) {
assertEquals(HttpStatusCode.OK, response.status())
assertEquals("bar", response.content)
}
}
}
}
啊,达姆-我看我的答案和@amanin非常相似。啊,好吧,我把它留在这里,因为可能帮助函数添加了一些实现思想。
class ApplicationCallElement(var call: ApplicationCall?) : AbstractCoroutineContextElement(ApplicationCallElement) {
companion object Key : CoroutineContext.Key<ApplicationCallElement>
}
suspend fun PipelineContext<Unit, ApplicationCall>.withCall(
bodyOfCall: suspend PipelineContext<Unit, ApplicationCall>.() -> Unit
) {
val pipeline = this
val appCallContext = buildAppCallContext(this.call)
withContext(appCallContext) {
pipeline.bodyOfCall()
}
}
internal suspend fun buildAppCallContext(call: ApplicationCall): CoroutineContext {
var context = coroutineContext
val callElement = ApplicationCallElement(call)
context = context.plus(callElement)
return context
}
suspend fun getSomethingFromCall(): String {
val call = coroutineContext[ApplicationCallElement.Key]?.call ?: throw Exception("Element not set")
return call.parameters["key"] ?: throw Exception("Parameter not set")
}
fun Application.myApp() {
routing {
route("/foo") {
get {
withCall {
call.respondText(getSomethingFromCall())
}
}
}
}
}
class ApplicationCallTest {
@Test
fun `we can get the application call in a nested function`() {
withTestApplication({ myApp() }) {
with(handleRequest(HttpMethod.Get, "/foo?key=bar")) {
assertEquals(HttpStatusCode.OK, response.status())
assertEquals("bar", response.content)
}
}
}
}