我可以让 KTOR 路由不区分大小写吗?

Can I make KTOR routing case insensitive?

我刚刚用 KTOR 创建了一个服务器 API。这是一些简化的代码:

fun Application.configureRouting() {
    routing {
        route("/MyPage", HttpMethod.Get) {
            handle {
                call.respondText(text = "Blah", status = HttpStatusCode.OK )
            }
        }
    }
}

如果我随后请求 /mypage 而不是 /MyPage,我会收到 404。

现在我明白 技术上,URL 路径 区分大小写,但我不想要 URL 在我的 API 中。

如何设置路由以便“MYPAGE”、“mypage”、“MyPaGe”和所有其他大小写混合排列都将触发同一个处理程序?

我查看了文档并查看了一些代码,但都无济于事。我主要是一名 .NET 开发人员,请放轻松!

谢谢

更新 - 部分解决方案,在多个指定路径上使用相同的处理程序:

fun Application.configureRouting() {

    val handler: PipelineInterceptor<Unit, ApplicationCall> = {
        call.respondText(text = "Blah", status = HttpStatusCode.OK)
    }
    
    routing {
        route("/mypage", HttpMethod.Get) {
            handle(handler)
        }
        route("/MyPage", HttpMethod.Get) {
            handle(handler)
        }
    }
}

您可以编写自己的路由选择器 (RouteSelector) 来比较忽略大小写的路径段。最重要的是,您可以编写构建器方法来使用该选择器创建路由。下面是一个 get 构建器方法的示例,它有条件地使用自定义选择器来比较常量(没有参数、通配符等)忽略大小写的路径段:

import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import io.ktor.util.pipeline.*

fun main() {
    embeddedServer(Netty, port = 2020) {
        routing {
            get("/some") {
                call.respondText { "Case sensitive" }
            }

            get("/foo", ignoreCase = true) {
                call.respondText { "Case insensitive" }
            }
        }
    }.start()
}


fun Route.get(path: String, ignoreCase: Boolean, body: PipelineInterceptor<Unit, ApplicationCall>): Route {
    return route(path, HttpMethod.Get, ignoreCase) { handle(body) }
}

fun Route.route(path: String, method: HttpMethod, ignoreCase: Boolean, build: Route.() -> Unit): Route {
    val selector = HttpMethodRouteSelector(method)
    return routeFromPath(path, ignoreCase).createChild(selector).apply(build)
}

fun Route.routeFromPath(path: String, ignoreCase: Boolean): Route {
    val parts = RoutingPath.parse(path).parts
    var current: Route = this
    for (index in parts.indices) {
        val (value, kind) = parts[index]
        val selector = when (kind) {
            RoutingPathSegmentKind.Parameter -> PathSegmentSelectorBuilder.parseParameter(value)
            RoutingPathSegmentKind.Constant -> if (ignoreCase) {
                CaseInsensitivePathSelector(value)
            } else {
                PathSegmentSelectorBuilder.parseConstant(value)
            }
        }
        // there may already be entry with same selector, so join them
        current = current.createChild(selector)
    }
    if (path.endsWith("/")) {
        current = current.createChild(TrailingSlashRouteSelector)
    }
    return current
}

class CaseInsensitivePathSelector(val value: String): RouteSelector() {
    override fun evaluate(context: RoutingResolveContext, segmentIndex: Int): RouteSelectorEvaluation = when {
        segmentIndex < context.segments.size && context.segments[segmentIndex].equals(value, ignoreCase = true) ->
            RouteSelectorEvaluation.ConstantPath
        else -> RouteSelectorEvaluation.FailedPath
    }
}