相同阶段的 Ktor 拦截器排序和覆盖

Ktor Interceptor Ordering for same Phase and overriding

我目前正在尝试使用功能、管道和拦截器实现以下模式:

auth {
    limit(1) {
        get {
            call.respond("Cake1")
        }
    }
}

在这两个功能中,我使用这个:

pipeline.insertPhaseAfter(ApplicationCallPipeline.Features, myCustomPhase)
pipeline.intercept(appTokenAuthPhase)

我遇到以下两个问题:

  1. 使用PhaseAfter时,顺序错误。应该先调用 Auth,然后再调用 Limit。为什么 Limit 排在第一位?我怎样才能防止这种情况发生?注意:它应该始终取决于代码顺序,第一个块应该首先执行。由于某些未知原因,它与 PhaseBefore 一起按预期工作。但这似乎不一致。这些方法将阶段放置到其他阶段,但不对合并执行排序。这是怎么做到的?

insertPhaseAfter

[Phase('Setup'), Phase('Monitoring'), Phase('Features'), Phase('Limit'), Phase('Call'), Phase('Fallback')]

[Phase('Setup'), Phase('Monitoring'), Phase('Features'), Phase('Auth'), Phase('Call'), Phase('Fallback')]

insertPhaseBefore

[Phase('Setup'), Phase('Monitoring'), Phase('Auth'), Phase('Features'), Phase('Call'), Phase('Fallback')]

[Phase('Setup'), Phase('Monitoring'), Phase('Limit'), Phase('Features'), Phase('Call'), Phase('Fallback')]

  1. 我想删除相位拦截器。
rate(100) {
 route("/sub") {
   rate(5) {
     get("/critical") {
       call.respondText("Hello, World!")
     }
   }
   get("/profile/{id}") { TODO("...") }
 }
}

所以对于 /sub/critical 只应调用 Rate(5) 的拦截器,并应跳过 Rate(100)。这在当前架构中可能吗?我看不出有什么方法可以覆盖合并并删除除阶段 "Limit" 的 "last" 拦截器之外的所有内容。另一个 "Limit" 应该保留在没有 "Limit" 的所有管道中。其他拦截器(如 Auth)应照常执行。

我会把我为第 2 部分所做的写给你。我不确定它是否性感。基本上,当我用自定义的东西向路由添加新的 child 时,我会计算路由中已经有多少 parents 具有相同的选择器。然后我将那个数量传递给拦截器,所以我知道当前 child 的嵌套位置是什么。

 fun Route.test(
        message: String,
        build: Route.() -> Unit,
    ): Route {
        val testRoute = createChild(SimpleSelector("message"))
        application.feature(SimpleInterceptor).apply {
            interceptPipeline(testRoute, message, testRoute.countSimpleSelectors())
        }
        testRoute.build()
        return testRoute
    }
    
    fun Route.countSimpleSelectors(): Int = 
        (parent?.countSimpleSelectors() ?: 0) +
            if (parent?.selector is SimpleSelector) {
                1
            } else {
                0
            }

然后在拦截器中,我将添加与嵌套拦截器一样多的阶段。但是我是倒序添加的,也就是说最后添加的拦截器会运行先

fun interceptPipeline(
    pipeline: ApplicationCallPipeline,
    message: String,
    nestingCounter: Int,
) {
    pipeline.insertPhaseAfter(ApplicationCallPipeline.Features, Authentication.ChallengePhase)
    val phases = (nestingCounter downTo 0).map {
        simplePhase(it)
    }
    phases.fold(Authentication.ChallengePhase) { old, new ->
        pipeline.insertPhaseAfter(old, new)
        new
    }
    pipeline.intercept(phases.first()) {
        val call = call
        val simpleContext = SimpleContext.from(call)
        TestPipeline().apply {
            val subject = SimpleContext.from(call)
            println("original subject: $message, new subject: ${subject.someMessage}")
            subject.someMessage = message
        }.execute(call, simpleContext)
    }
}

现在链中最新添加的拦截器将 运行 首先。剩下的就是拦截器向管道添加上下文。此上下文可以是您想要的任何内容,因此您可以向上下文添加一个布尔值:isAlreadyHandled。第一个拦截器完成后可以翻转它,后面的拦截器可以忽略管道。

我基于:https://www.ximedes.com/2020-09-17/role-based-authorization-in-ktor/

和随附的 github 回购:https://github.com/ximedes/ktor-authorization

我使用了与那里相同的结构,只是添加了计数和上下文。希望对您有所帮助!