Reputation: 2301
I've just created a server API with KTOR. Here's some simplified code:
fun Application.configureRouting() {
routing {
route("/MyPage", HttpMethod.Get) {
handle {
call.respondText(text = "Blah", status = HttpStatusCode.OK )
}
}
}
}
If I then request /mypage
instead of /MyPage
, I get a 404.
Now I understand that technically, URL paths are case-sensitive but I don't want the URLs in my API to be.
How can I set up routing so that "MYPAGE", "mypage", "MyPaGe" and every other permutation of mixed case will all fire the same handler?
I've looked through the docs and poked thorugh some of the code, all to no avail. I'm prmarily a .NET developer so go easy on me!
Thanks 🙂
Update - A partial solution which uses the same handler on multiple specified paths:
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)
}
}
}
Upvotes: 0
Views: 403
Reputation: 6999
You can write your own routes' selectors (RouteSelector) that will compare path segments ignoring the case. On top of that, you can write builder methods for creating routes with that selector. Here is an example of a get
builder method that conditionally uses a custom selector which compares constant (without parameters, wildcards, etc) path segments ignoring the case:
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
}
}
Upvotes: 2