Dave Challis
Dave Challis

Reputation: 3906

Accessing routing path string within request in Ktor

Does the Ktor framework provide a way of accessing a route's path string within a request?

For example, if I set up a route such as:

routing {
    get("/user/{user_id}") {
        // possible to get the string "/user/{user_id}" here?
        val path = someFunction()
        assert(path == "/user/{user_id}")
    } 
}

To clarify, I'm looking for a way to access the unprocessed path string, i.e. "/user/{user_id}" in this case (accessing the path via call.request.path() gives me the path after the {user_id} has been filled in, e.g. "/user/123").

I can of course assign the path to a variable and pass it to both get and use it within the function body, but wondering if there's a way of getting at the route's path without doing that.

Upvotes: 6

Views: 4599

Answers (6)

Raman
Raman

Reputation: 19585

If for some reason you are unable to statically determine your routes as explained by the accepted answer, it is possible to determine the route at runtime via a plugin.

The plugin captures the route information and makees it available to the application as an attribute of the call context. Here is the plugin code:

import io.ktor.server.application.*
import io.ktor.server.application.hooks.*
import io.ktor.server.routing.*
import io.ktor.util.*

val routeKey = AttributeKey<Route>("RequestRoute")

val RequestRoutePlugin = createApplicationPlugin("RequestRoutePlugin") {
  on(MonitoringEvent(Routing.RoutingCallStarted)) { call ->
    call.attributes.put(routeKey, call.route)
  }
}

fun ApplicationCall.requestRoute() = attributes[routeKey]

Once that plugin is installed via install(RequestRoutePlugin), simply use call.requestRoute().parent to access the route you want. For example:

get("/foo/{bar}") {
  call.respondText(
    "bar=${call.parameters["bar"]} route=${call.requestRoute().parent.toString()}",
    status = HttpStatusCode.OK
  )
}

will respond to a request for /foo/abc as:

bar=abc route=/foo/{bar}

Upvotes: 2

Savely Shmelev
Savely Shmelev

Reputation: 11

fun Route.fullPath(): String {
    val parentPath = parent?.fullPath() ?: "/"

    return when (selector) {
        is TrailingSlashRouteSelector,
        is AuthenticationRouteSelector,
        is HttpMethodRouteSelector,
        is HttpHeaderRouteSelector,
        is HttpAcceptRouteSelector -> parentPath
        else -> parentPath.let { if (it.endsWith("/")) it else "$it/" } + selector.toString()
    }
}

route("/user/{id}", HttpMethod.Get) {
    handle {
        val path = [email protected]()
        println(path)
    }
}

Upvotes: 1

CHH
CHH

Reputation: 168

So all solutions so far miss the most obvious (and in my humble opinion - correct) way to extract the path variables:

routing {
   get("/user/{user_id}") {
       val userId = call.parameters["user_id"]
   } 
}

call.parameters["user_id"] will return a value of type String?

You can have multiple path variables and pull them out by this method.

Upvotes: -3

George Nady
George Nady

Reputation: 19

I found a solution for this problem

val uri = "foos/foo"

get("$uri/{foo_id}") {
    val path = call.request.path()
    val firstPart = path.length
    val secondPart = path.slice((firstPart+1) until path.length)
  
    call.respondText("$secondPart")
}

try this code it's simple and robust

Upvotes: -1

MisseMask
MisseMask

Reputation: 459

I solved it like this

// Application.kt

private object Paths {
    const val LOGIN = "/login"
    ...
}

fun Application.module(testing: Boolean = false) {
    ...
    routing {
       loginGet(Paths.LOGIN)
    }
}

And to structure my extension functions, I put them in other files like this

// Auth.kt

fun Route.loginGet(path: String) = get(path) {
    println("The path is: $path")
}

Upvotes: 1

Some random IT boy
Some random IT boy

Reputation: 8457

I don't think that is possible. What you could do instead is write such a class/object

object UserRoutes {

    const val userDetails = "/users/{user_id}"
    ...

}

And reference that field from your routing module:

import package.UserRoutes

get(UserRoutes.userDetails) {...}

By doing so you would need to just reference that string from the given singleton. Also no need for the object wrapper but I think it looks neat that you can group the paths by somewhat their module name

Upvotes: 2

Related Questions