Handlers
A Route extracts Scala values from an HTTP request, and converts Scala values into an HTTP response. A Handler adds the functionality that connects these two parts together, giving a complete controller that can be run as part of an application.
There are three ways to create a handler: using handle
, handleIO
, or passthrough
. Assume the request produces a value of type A
and the response needs a value of type B
. Then these three methods have the following meaning:
handle
requires a functionA => B
;handleIO
requires a functionA => IO[B]
; andpassthrough
, which can only be called whenA
is the same type asB
, means that the output of the request is connected directly to the input of the response. This is useful, for example, when the response is loading a static file from the file system and the request produces the name of the file to load.
Let's say we have the following Route
.
val route =
Route(
Request.get(Path / "user" / Param.int),
Response.ok(Entity.text)
)
It produces an Int
from the request, and requires a String
to create the response. We can create a Handler
in the following two ways:
route.handle(userId => s"You asked for user $userId")
route.handleIO(userId => IO.pure(s"You asked for user $userId"))
We cannot use passthrough
as the value produced from the request has a different type to the value required to create the response.
Type Transformations for Handlers
If you dig into the types produced by Request
you will notice a lot of tuple types are used. Here's an example, showing a Request
producing a Tuple2
.
val request = Request.get(Path / Param.int / Param.string)
// request: RequestMethodPath[*:[Int, *:[String, EmptyTuple]], EmptyTuple] = RequestMethodPath(
// method = GET,
// path = krop.route.Path@245c5366
// )
This Tuple2
arises because we extract two elements from the HTTP request's path: one Int
and one String
.
However, when you come to use a handler with such a request, you can use a normal function with two arguments not a function that accepts a single Tuple2
.
Route(request, Response.ok(Entity.text))
.handle((int, string) => s"${int.toString}: ${string}")
The conversion between tuples and functions is done by given instances of TupleApply, which allows a function (A, B, ..., N) => Z
to be applied to a tuple (A, B, ..., N)