GZip Compression
Http4s provides Middleware, named GZip
, for allowing for the compression of the Response
body using GZip.
Examples in this document have the following dependencies.
libraryDependencies ++= Seq(
"org.http4s" %% "http4s-dsl" % http4sVersion,
"org.http4s" %% "http4s-server" % http4sVersion
)
And we need some imports.
import cats.effect._
import org.http4s._
import org.http4s.dsl.io._
Let’s start by making a simple service that returns a (relatively) large string
in its body. We’ll use as[String]
to examine the body.
val service = HttpService[IO] {
case _ =>
Ok("I repeat myself when I'm under stress. " * 3)
}
// service: HttpService[IO] = Kleisli(
// org.http4s.HttpService$$$Lambda$12903/708292025@1cf22a2e
// )
val request = Request[IO](Method.GET, uri("/"))
// request: Request[IO] = Request(
// Method("GET"),
// Uri(None, None, "/", Query(), None),
// HttpVersion(1, 1),
// Headers(),
// Stream(..),
// org.http4s.AttributeMap@8cf85da
// )
// Do not call 'unsafeRun' in your code - see note at bottom.
val response1 = service.orNotFound(request).unsafeRunSync
// response1: Response[IO] = Response(
// Status(200),
// HttpVersion(1, 1),
// Headers(
// Content-Type(MediaType(text/plain), Some(Charset(UTF-8))),
// Content-Length(117L)
// ),
// Stream(..),
// org.http4s.AttributeMap@8cf85da
// )
val body1 = response1.as[String].unsafeRunSync
// body1: String = "I repeat myself when I'm under stress. I repeat myself when I'm under stress. I repeat myself when I'm under stress. "
body1.length
// res0: Int = 117
Now we can wrap the service in the GZip
middleware.
import org.http4s.server.middleware._
val zipService = GZip(service)
// zipService: HttpService[IO] = Kleisli(
// org.http4s.server.middleware.GZip$$$Lambda$13766/971208035@41adbf1e
// )
// Do not call 'unsafeRun' in your code - see note at bottom.
val response2 = zipService.orNotFound(request).unsafeRunSync
// response2: Response[IO] = Response(
// Status(200),
// HttpVersion(1, 1),
// Headers(
// Content-Type(MediaType(text/plain), Some(Charset(UTF-8))),
// Content-Length(117L)
// ),
// Stream(..),
// org.http4s.AttributeMap@8cf85da
// )
val body2 = response2.as[String].unsafeRunSync
// body2: String = "I repeat myself when I'm under stress. I repeat myself when I'm under stress. I repeat myself when I'm under stress. "
body2.length
// res1: Int = 117
So far, there was no change. That’s because the caller needs to inform us that
they will accept GZipped responses via an Accept-Encoding
header. Acceptable
values for the Accept-Encoding
header are “gzip”, “x-gzip”, and “*”.
import org.http4s.Header
val acceptHeader = Header("Accept-Encoding", "gzip")
// acceptHeader: Header.Raw = Raw(Accept-Encoding, "gzip")
val zipRequest = request.putHeaders(acceptHeader)
// zipRequest: request.Self = Request(
// Method("GET"),
// Uri(None, None, "/", Query(), None),
// HttpVersion(1, 1),
// Headers(Raw(Accept-Encoding, "gzip")),
// Stream(..),
// org.http4s.AttributeMap@8cf85da
// )
// Do not call 'unsafeRun' in your code - see note at bottom.
val response3 = zipService.orNotFound(zipRequest).unsafeRunSync
// response3: Response[IO] = Response(
// Status(200),
// HttpVersion(1, 1),
// Headers(
// Content-Type(MediaType(text/plain), Some(Charset(UTF-8))),
// Content-Encoding(ContentCoding(gzip, QValue(1.0)))
// ),
// Stream(..),
// org.http4s.AttributeMap@8cf85da
// )
val body3 = response3.as[String].unsafeRunSync
// body3: String = "\u001f\ufffd\b\u0000\u0000\u0000\u0000\u0000\u0000\u0000\ufffdT(J-HM,Q\u022d,N\ufffdIS(\ufffdH\ufffdS\ufffdT\ufffdU(\ufffdKI-R(.)J-.\ufffdS\ufffd2\u0000UX\ufffd<u\u0000\u0000\u0000"
body3.length
// res2: Int = 59
Notice how the response no longer looks very String-like and it’s shorter in
length. Also, there is a Content-Encoding
header in the response with a value
of “gzip”.
As described in Middleware, services and middleware can be composed such
that only some of your endpoints are GZip enabled.
NOTE: In this documentation, we are calling unsafeRunSync
to extract values out
of a service or middleware code. You can work with values while keeping them inside the
F
using map
, flatMap
and/or for
. Remember, your service returns an
F[Response]
.