Routing
Routing handles the HTTP specific details of incoming requests and outgoing responses. The main uses of routes are to:
- match HTTP requests and extract Scala values;
- convert Scala values to an HTTP response; and
- reversing a route to create a link to the route or a client that calls the route.
A Route describes how to convert an incoming request into Scala values, and how to turn Scala values in an outgoing response.
A Handler is a Route
that also includes the code to convert the values parsed from the request into the values that are used to create the response. In other words, a Handler
includes business logic.
A Handlers is a collection of Handler
.
The Route Type
The Route
type is fairly complex, though you can ignore this in most uses.
Route[P <: Tuple, Q <: Tuple, I <: Tuple, O <: Tuple, R]
The types have the following meanings:
P
is the type of values extracted from the request's path by the Path.Q
is the type of query parameters extracted by the Path.I
is the type of all values extracted from the HTTP request.O
is the type of values to construct an HTTP request to thisRoute
. This is often, but not always, the same asI
.R
is the type of the value to construct an HTTP response.
Most of these types are tuples because they accumulate values extracted from smaller components of the HTTP request. This will become clearer in the examples below.
Constructing A Route
A Route is constructed from two components:
The idiomatic way to construct a Route
is by calling the Route.apply
method, passing a Request and Response.
Here is a small example.
val route =
Route(
Request.get(Path / "user" / Param.int),
Response.ok(Entity.text)
)
This route tells us two things:
- It will match an incoming GET request to a path like
/user/1234
and extract the number as anInt
. - It convert a
String
to an OK response. The response will include thatString
as the response's entity, and it will have a content typetext/plain
.
To actually use this route we need to add a handler. In this case a handler would be either a function with type Int => String
or with type Int => IO[String]
. Here's a very simple example using an Int => String
handler.
route.handle(userId => s"You asked for user $userId")
Notice that adding a handler produces a value with a different type, a Handler.
For more details see the separate pages for Request, Response and Handler.
Reverse Routing
There are three forms of reverse routing:
- constructing a
String
that corresponds to the path matched by theRoute
; - constructing a
String
corresponding to the path and query parameters matched by theRoute
; - constructing a HTTP request that will be matched by the
Route
.
Reverse Routing for Paths
Given a Route you can construct a String
containing the path to that route using the pathTo
method. This can be used, for example, to embed hyperlinks to routes in generated HTML. Here's an example.
We first create a Route.
val viewUser = Route(Request.get(Path / "user" / Param.int), Response.ok(Entity.text))
Now we can call pathTo
to construct a path to that route, which we could embed in an HTML form or a hyperlink.
viewUser.pathTo(1234)
// res0: String = "/user/1234"
Note that we pass to pathTo
the parameters for the Path component of the route.
If the route has no path parameters there is an overload with no parameters.
Here's an example with no parameters.
val users = Route(Request.get(Path / "users"), Response.ok(Entity.text))
Now we can call pathTo
without any parameters.
users.pathTo
// res1: String = "/users"
If there is more than one parameter we must collect them in a tuple. The route below has two parameters.
val twoParams = Route(Request.get(Path / "user" / Param.int / Param.string), Response.ok(Entity.text))
Notice when we call pathTo
we pass a Tuple2
.
twoParams.pathTo((1234, "McBoopy"))
// res2: String = "/user/1234/McBoopy"
Reverse Routing for Paths and Queries
You can use the pathAndQueryTo
method to construct a String
contains both the path and query parameters to a Route.
Here's an example of a Route that extracts elements from both the path and the query parameters.
val searchUsers = Route(
Request.get(
Path / "users" / "search" / Param.string :? Query("start", Param.int)
.and("stop", Param.int)
),
Response.ok(Entity.text)
)
searchUsers.pathAndQueryTo("scala", (1, 10))
// res3: String = "/users/search/scala?start=1&stop=10"
Combining Routes
Two or more routes can be combined using the orElse
method, creating Routes.
val routes = viewUser.orElse(users).orElse(twoParams)
A Route
or Routes
can also be combined with an Application
using overloads of the orElse
method, which produces an Application
.