Routing API (Advanced)
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 aRoute
. Routes
can be combined together into aRoutingHttpHandler
, which is both anHttpHandler
and aRouter
.- A
Router
is a selective request handler, which attempts to match a request. If it cannot, processing falls through to the nextRouter
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 underlyingResourceLoader
or fallback to/
(and passed to the SPA code)
Dynamic Paths / Path Variables
As you would expect, http4k allows routes to include dynamic or variable elements in the matching path, and allows you to reference the variable in the Handler. For example:
"/book/{title}" bind GET to { req ->
Response.invoke(Status.OK).body(GetBookDetails(req.path("title"))
}
"/author/{name}/latest" bind GET to { req ->
Response.invoke(Status.OK).body(GetAllBooks(author = req.path("name")).last())
}
By default, the variable(s) will match anything. However you can append the variable name with a RegEx expression to limit the matches.
// will match /book/978-3-16-148410-0 (i.e. only digits and dashes)
// /book/A%20Confederacy%20Of%20Dunces would return a 404 (Not Found)
"/book/{isbn:[\\d-]+}"
// will NOT match /sales/south or /sales/usa
"/sales/{region:(?:northeast|southeast|west|international)}"
There are no pre-defined types such as int
or path
for matching but these are easy to replicate with RegEx’s:
- string (excluding slashes) :
[^\\/]+
(note that Kotlin requires backslashes to be escaped, so\w
in RegEx is expressed as\\w
in Kotlin) - int :
\\d+
- float :
[+-]?([0-9]*[.])?[0-9]+
(this will match basic floats. Does not match exponents, or scientific notation) - path :
.*
Note that paths, not strings, will match by default. "/news/{date}"
will match www.example.com/news/2018/05/26
, making request.path("date")
equal to 2018/05/26
. This may be exactly what you want, or it may produce unexpected results, depending on how your URLs are structured.
Gradle setup
dependencies {
implementation(platform("org.http4k:http4k-bom:5.34.0.0"))
implementation("org.http4k:http4k-core")
}
Code
package content.howto.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.and
import org.http4k.routing.bind
import org.http4k.routing.header
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 { Response(OK) }
)
)
println(routesWithFilter(Request(GET, "/get/value")))
val staticWithFilter = PrintRequestAndResponse()
.then(static(Classpath("guide/howto/nestable_routes")))
val app = routes(
"/bob" bind routesWithFilter,
"/static" bind staticWithFilter,
"/pattern/{rest:.*}" bind { req: Request ->
Response(OK).body(req.path("rest").orEmpty())
},
"/rita" bind routes(
"/delete/{name}" bind DELETE to { Response(OK) },
"/post/{name}" bind POST to { Response(OK) }
),
"/matching" bind GET to routes(
header("requiredheader", "somevalue")
.and(queries("requiredquery")) bind {
Response(OK).body("matched 2 parameters")
},
headers("requiredheader") bind { Response(OK).body("matched 1 parameters") }
),
singlePageApp(Classpath("guide/howto/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,