Nestable routes

This is a fairly comprehensive example of the core-routing logic available:

  • Individual HTTP endpoints are represented as HttpHandlers.
  • Binding an HttpHandler to a path and HTTP verb yields a Route.
  • Routes can be combined together into a RoutingHttpHandler, which is both an HttpHandler and aRouter.
  • A Router is a selective request handler, which attempts to match a request. If it cannot, processing falls through to the next Router in the list.
  • Routers can be combined together to form another HttpHandler
  • Usage of supplied core library Filters
  • Serving of static content using a Classpath resource loader
  • Support for Single Page Applications using a singlePageApp() block - resources loaded from here are loaded from the underlying ResourceLoader or fallback to / (and passed to the SPA code)

Gradle setup

implementation group: "org.http4k", name: "http4k-core", version: "3.270.0"

Code

package cookbook.nestable_routes

import org.http4k.core.Method.DELETE
import org.http4k.core.Method.GET
import org.http4k.core.Method.POST
import org.http4k.core.Request
import org.http4k.core.Response
import org.http4k.core.Status.Companion.OK
import org.http4k.core.then
import org.http4k.filter.DebuggingFilters.PrintRequestAndResponse
import org.http4k.routing.ResourceLoader.Companion.Classpath
import org.http4k.routing.bind
import org.http4k.routing.headers
import org.http4k.routing.path
import org.http4k.routing.queries
import org.http4k.routing.routes
import org.http4k.routing.singlePageApp
import org.http4k.routing.static

fun main() {
    val routesWithFilter =
        PrintRequestAndResponse().then(
            routes(
                "/get/{name}" bind GET to { req: Request -> Response(OK).body(req.path("name")!!) },
                "/post/{name}" bind POST to { _: Request -> Response(OK) }
            )
        )
    println(routesWithFilter(Request(GET, "/get/value")))

    val staticWithFilter = PrintRequestAndResponse().then(static(Classpath("cookbook/nestable_routes")))
    val app = routes(
        "/bob" bind routesWithFilter,
        "/static" bind staticWithFilter,
        "/pattern/{rest:.*}" bind { req: Request -> Response(OK).body(req.path("rest") ?: "") },
        "/rita" bind routes(
            "/delete/{name}" bind DELETE to { _: Request -> Response(OK) },
            "/post/{name}" bind POST to { _: Request -> Response(OK) }
        ),
        "/matching" bind GET to routes(
            headers("requiredheader").and(queries("requiredquery")) bind { Response(OK).body("matched 2 parameters") },
            headers("requiredheader") bind { Response(OK).body("matched 1 parameters") }
        ),
        singlePageApp(Classpath("cookbook/nestable_routes"))
    )

    println(app(Request(GET, "/bob/get/value")))
    println(app(Request(GET, "/static/someStaticFile.txt")))
    println(app(Request(GET, "/pattern/some/entire/pattern/we/want/to/capture")))
    println(app(Request(GET, "/matching").header("requiredheader", "somevalue").query("requiredquery", "somevalue")))
    println(app(Request(GET, "/matching").header("requiredheader", "somevalue")))
    println(app(Request(GET, "/someSpaResource")))
}

For the typesafe contract-style routing, refer to this recipe instead,