Response
A Response describes how to create a HTTP response from a Scala value.
For example, the following Response will produce an HTTP OK response with a text/plain entity.
val ok: Response[String] = Response.ok(Entity.text)
The entity will be constructed from a String that is passed to the respond method on the Response.
You usually won't do this yourself; it is handled by the Route the Response is part of.
Entities
Entities (response bodies) are handled in the same way as requests: by specifying an Entity. In this case the Entity is responsible for encoding Scala values as data in the HTTP response.
Use Entity.unit to indicate that your response has no entity. For example:
val noBody: Response[Unit] = Response.ok(Entity.unit)
Headers
Headers can be added using the withHeader method. This method accepts one of more values in any of the following forms:
- A value of type
Awhich has aHeader[A]in scope - A
(name, value)pair ofString, which is treated as aRecurringheader - A
Header.Raw - A
Foldable(List,Option, etc) of the above.
In the example below we add a header using the (name, value) form, and a value with a Header[A] in scope.
import org.http4s.headers.Allow
val headers =
Response.ok(Entity.html).withHeader("X-Awesomeness" -> "10.0", Allow(Method.GET))
Error Handling
In many cases you'll need to generate an error response despite a valid request. For example, you could generate a 404 Not Found if the client has sent a well-formed request for a user but that user does not exist. This situation can be handled using the orNotFound method, which converts a Response[A] to a Response[Option[A]]. When passed a None the Response responds with a 404. This is shown in the example below. If the user ID is not 1 (the only valid ID) a 404 will be returned.
val getUser =
Route(
Request.get(Path / "user" / Param.int),
Response.ok(Entity.text).orNotFound
).handle(id =>
if id == 1 then Some("Found the user!") else None
)
For more complex cases you can use orElse, which allows you to handle an Either[A, B] and introduce custom error handling. The example below shows complex error handling combining orElse and orNotFound. A 404 Not Found is returned if the user id does not correspond to an existing user, and a 400 Bad Request is returned if the Name entity is not valid.
import io.circe.{Decoder, Encoder}
final case class Name(name: String) derives Decoder, Encoder
final case class User(id: Int, name: String) derives Decoder, Encoder
val postUser =
Route(
Request.post(Path / "user" / Param.int).withEntity(Entity.jsonOf[Name]),
Response.ok(Entity.jsonOf[User])
.orElse(Response.status(Status.BadRequest, Entity.text))
.orNotFound
).handle((id, name) =>
// Check ID is valid
if id == 1 then
// Check name is valid
if name.name == "Hieronymus Bosch" then Some(Right(User(1, name.name)))
else Some(Left("$name is not an allowed name"))
else None
)
Not that when using orElse the first Response is the successful one, and the second is the error case. This follows the usual convention in English, but means that when written the Response on the left-hand side corresponds to a Right value. In other words, we write
success.orElse(error)
not
error.orElse(success)