Entity handling
Why Entity*
Http4s handles HTTP requests and responses in a streaming fashion. Your service
will receive a request after the header has been parsed (ok, not 100%
streaming), but before the body has been fully received. The same applies to the
http client usage, where you can start a connection before the body is fully
materialized. You don’t have to load the full body into memory to submit the
request either. Taking a look at Request
and Response
, both have a body of
type EntityBody
, which is simply an alias to Stream[Task, ByteVector]
. To
understand Stream
, take a look at the streams-tutorial.
The EntityDecoder
and EntityEncoder
help with the streaming nature of the
data in a http body, and they also have additional logic to deal with media
types. Not all decoders are streaming, depending on the implementation.
Construction and Media Types
Entity*
s also encode which media types they correspond to. The
EntityDecoder
s for json expect application/json
. To implement this
functionality, the constructor EntityDecoder.decodeBy
uses MediaRange
s. You
can pass multiple as needed. You can also append functionality to an existing
one via EntityDecoder[T].map
- however, you can’t change the media
type in that case.
When you encode a body with the EntityEncoder
for json, it appends the
Content-Type: application/json
header. You can construct new encoders via
EntityEncoder.encodeBy
or reuse an already existing one via
EntityEncoder[T].contramap
and withContentType
.
See the MediaRange companion object for ranges, and MediaType for specific
types. Because of the implicit conversions, you can also use (String, String)
for a MediaType
.
By default, decoders content types are ignored since it could lead to unexpected runtime errors.
Chaining Decoders
Decoders’ content types are used when chaining decoders with orElse
in order to
determine which of the chained decoders are to be used.
scala> import org.http4s._
import org.http4s._
scala> import org.http4s.dsl._
import org.http4s.dsl._
scala> import cats._
import cats._
scala> import cats.implicits._
import cats.implicits._
scala> import cats.data._
import cats.data._
scala> sealed trait Resp
defined trait Resp
scala> case class Audio(body: String) extends Resp
defined class Audio
scala> case class Video(body: String) extends Resp
defined class Video
scala> val response = Ok().withBody("").withContentType(Some(MediaType.`audio/ogg`))
response: fs2.Task[org.http4s.Response] = Task
scala> val audioDec = EntityDecoder.decodeBy(MediaType.`audio/ogg`) { msg =>
| EitherT {
| msg.as[String].map(s => Audio(s).asRight[DecodeFailure])
| }
| }
audioDec: org.http4s.EntityDecoder[Audio] = org.http4s.EntityDecoder$$anon$3@6ca0214c
scala> val videoDec = EntityDecoder.decodeBy(MediaType.`video/ogg`) { msg =>
| EitherT {
| msg.as[String].map(s => Video(s).asRight[DecodeFailure])
| }
| }
videoDec: org.http4s.EntityDecoder[Video] = org.http4s.EntityDecoder$$anon$3@1a650845
scala> val bothDec = audioDec.widen[Resp] orElse videoDec.widen[Resp]
bothDec: org.http4s.EntityDecoder[Resp] = org.http4s.EntityDecoder$OrDec@2e27f779
scala> println(response.as(bothDec).unsafeRun)
Audio()
Presupplied Encoders/Decoders
The EntityEncoder
/EntityDecoder
s shipped with http4s.
Raw Data Types
These are already in implicit scope by default, e.g. String
, File
,
Future[_]
, and InputStream
. Consult EntityEncoder and EntityDecoder for
a full list.
JSON
With jsonOf
for the EntityDecoder
, and jsonEncoderOf
for the EntityEncoder
:
- argonaut:
"org.http4s" %% "http4s-argonaut" % Http4sVersion
- circe:
"org.http4s" %% "http4s-circe" % Http4sVersion
- json4s-native:
"org.http4s" %% "http4s-json4s-native" % Http4sVersion
- json4s-jackson:
"org.http4s" %% "http4s-json4s-jackson" % Http4sVersion
XML
For scala-xml (xml literals), import org.http4s.scalaxml
. No direct naming
required here, because there is no Decoder instance for String
that would
cause conflicts with the builtin Decoders.
- scala-xml:
"org.http4s" %% "http4s-scala-xml" % Http4sVersion
Twirl
If you’re working with twirl templates, there’s a bridge for that too:
- scala-twirl:
"org.http4s" %% "http4s-twirl" % Http4sVersion