Request Handler
A Handler is responsible for processing the matched incoming request and generating an appropriate response. It is a function that takes a Request and produces a Response. Thus, it is a crucial component of the ZIO HTTP that determines how the server should respond to a request matched by the corresponding RoutePattern.
In ZIO HTTP, each Route consists of a RoutePattern and a Handler. The RoutePattern is responsible for matching the method and path of the incoming request, while the Handler specifies the corresponding action to be taken when the request is matched.
Definition
The Handler trait is defined as follows:
sealed trait Handler[-R, +Err, -In, +Out] {
def apply(in: In): ZIO[R, Err, Out]
}
It has four type parameters. The first two parameters R and Err are the environment and error type of the underlying effect that the handler represents. The third and fourth parameters In and Out are the input and output types of the handler.
If the input type of the handler is Request and the output type is Response, we call that handler a request handler:
type RequestHandler[-R, +Err] = Handler[R, Err, Request, Response]
Creating a Handler
The Handler trait comes with a companion object that has different methods to create handlers for various needs.
Additionally, there's a smart constructor called handler in the zio.http package. It automatically picks the right handler constructor based on the input type. Usually, using handler is enough, but if we need more control and specificity, we can also use the methods in the Handler companion object.
Let's look at some examples of creating handlers, using the handler smart constructor:
import zio._
import zio.http._
Routes(
// 1. A simple handler that returns a "Hello, World!" response
Method.GET / "hello" ->
handler(Response.text("Hello, World!")),
// 2. A handler that echoes the request body
Method.POST / "echo" ->
handler { (req: Request) => req.body.asString(Charsets.Utf8).map(Response.text(_)).orDie },
// 3. A handler that generates a random UUID
Method.GET / "uuid" ->
handler(Random.nextUUID.map(u => Response.text(u.toString))),
// 4. A handler that takes the name from the path and returns a greeting message
Method.GET / "name" / string("name") ->
handler{ (name: String, _: Request) => Response.text(s"Hello, $name!") },
// 5. A handler that takes the name and age from the path and returns birthday greetings
Method.GET / "name" / string("name") / "age" / int("age") ->
handler{ (name: String, age: Int, _: Request) => Response.text(s"Happy $age-th birthday, $name!") }
)
// res0: Routes[Any, Nothing] = Routes(
// routes = IndexedSeq(
// Handled(
// routePattern = RoutePattern(
// method = GET,
// pathCodec = Segment(segment = Literal(value = "hello"))
// ),
// handler = zio.http.Handler$FromFunction$$anon$16@44f3aa63,
// location = ""
// ),
// Handled(
// routePattern = RoutePattern(
// method = POST,
// pathCodec = Segment(segment = Literal(value = "echo"))
// ),
// handler = zio.http.Handler$FromFunction$$anon$16@366f37d6,
// location = ""
// ),
// Handled(
// routePattern = RoutePattern(
// method = GET,
// pathCodec = Segment(segment = Literal(value = "uuid"))
// ),
// handler = zio.http.Handler$FromFunction$$anon$16@f82cda1,
// location = ""
// ),
// Unhandled(
// routePattern = RoutePattern(
// method = GET,
// pathCodec = Concat(
// left = Segment(segment = Literal(value = "name")),
// right = Segment(segment = Text(name = "name")),
// combiner = zio.http.codec.Combiner$$anon$1@5f6b5cbf
// )
// ),
// handler = zio.http.Handler$FromFunction$$anon$16@6924583,
// zippable = zio.ZippableLowPriority3$$anon$23@490c1300,
// location = "repl.MdocSession.MdocApp.res0(handler.md:29)"
// ),
// Unhandled(
// routePattern = RoutePattern(
// method = GET,
// pathCodec = Concat(
// left = Concat(
// left = Concat(
// left = Segment(segment = Literal(value = "name")),
// right = Segment(segment = Text(name = "name")),
// combiner = zio.http.codec.Combiner$$anon$1@507a3045
// ),
// ...
Please be aware that this page primarily concentrates on the Handler data type and its constructors. However, to provide a more comprehensive understanding within the context of routes, we also integrate examples with the Routes and Method data types. Detailed exploration of the Routes and Method data types is discussed in a separate section.
As we can see, the handler constructor is quite versatile and can be used to create handlers for different use cases. It automatically infers proper handler constructors based on the input we pass to it.
-
The first example shows a simple handler that only returns a "Hello, World!" response. It doesn't need any input, so we can directly pass the
Responseto thehandlerconstructor. -
The second example shows a handler that echoes the request body. Since it needs the request body, we pass a function that takes a
Requestand returns aResponse.notePlease note that this handler employs the
orDiemethod to transform any failures in the effect into defects. In real-world applications, it's advisable to handle failures more gracefully, such as returning a descriptive error message in the response body. This approach provides clients with a clear understanding of what went wrong. We will delve into error handling in a separate section. -
The third example shows a handler that generates a random UUID. It doesn't need any input, but it requires an effect that produces a
UUID. So, we pass aZIOeffect that generates a randomUUIDand returns aResponse. -
The fourth example shows a handler that takes the name from the path and returns a greeting message. It needs the name from the path, so we pass a function that takes a
String, (and also theRequestwhich we ignore using_), and returns aResponse. Please note that whenever we need to access path parameters, we need also to pass theRequestas an argument to the handler function, even if we don't use it. -
The fifth example is similar to the previous one, but it takes two path parameters.
Handler Constructors
As mentioned earlier, it is advisable to use the handler smart constructor for convenience. However, in some cases, we might use lower-level handler constructors. Let's look at some of the most commonly used handlers: