TWiStErRob
TWiStErRob

Reputation: 46480

How to redirect internally in Ktor?

I'm wondering if there's a way to do an internal redirect, re-route, or response forwarding inside Ktor.

call.respondRedirect("relative/url")

sends a HTTP 302 or 301 depending on the permantent: Boolean flag. I'm looking for something that would do the same without using HTTP, just internally in Ktor. Here's some pseudo-routing of what I want to achieve:

get("/foo") {
    if (call.parameters["something"] != null) {
        call.respondText("Yay, something!")
    } else {
        call.respondRedirect("/bar") // except without relying on client to handle HTTP redirects.
    }
}
get("/bar") {
    call.respondText("No thing :(")
}

The goal is that the client shouldn't make 2 requests, and shouldn't be aware of the redirection happening.

NB: I'm aware I can extract a function for /bar's body and invoke it, instead of responsdRedirect. However, I want to make Ktor handle it so that it goes through all the necessary lifecycle and pipeline with all the interceptors. This is to make sure it is handled as if it was an external request, except the network roundtrip.

I'm looking for something like Express.js' req.app.handle(req, res) as shown in the first half of this answer: https://stackoverflow.com/a/48790319/253468. A potential solution I couldn't understand yet is something like TestApplicationEngine.handleRequest (in io.ktor:ktor-server-test-host) is doing with pipeline.execute. I guess I could invoke call.application.execute(), the question is how to construct the ApplicationCall object then. Note this is for production use, so no TestApplicationCall.

Upvotes: 5

Views: 2798

Answers (1)

Aleksei Tirman
Aleksei Tirman

Reputation: 7079

You can do similar thing in Ktor by using call.application.execute function with cloned call object. For convenience let's define extension function for doing internal redirects:

suspend fun ApplicationCall.redirectInternally(path: String) {
    val cp = object: RequestConnectionPoint by this.request.local {
        override val uri: String = path
    }
    val req = object: ApplicationRequest by this.request {
        override val local: RequestConnectionPoint = cp
    }
    val call = object: ApplicationCall by this {
        override val request: ApplicationRequest = req
    }

    this.application.execute(call)
}

Here it creates a copy of an ApplicationCall object with the replaced path for a request. I use delegates to avoid boilerplate code. You can use redirectInternally function like this:

get("/foo") {
    call.redirectInternally("/bar")
}

Upvotes: 2

Related Questions