Coding Directives
Coding directives compress responses and decompress requests with gzip and deflate through fs2's
Compression, on every platform.
encodeResponse
Encodes the response with the coding the client prefers in Accept-Encoding, choosing between identity,
gzip and deflate (in that order on ties). Without the header, or when the client lists neither gzip nor
deflate, the response is left alone; it is rejected with an UnacceptedResponseEncodingRejection (406 when
sealed) only when the client explicitly refuses identity as well. Responses that already carry
Content-Encoding, have no entity, or have a non-compressible media type are never touched. Content-Length is
removed from an encoded response.
Each chunk of the response is compressed and flushed as it is produced, as Pekko's encoders do, so a streamed
response (server-sent events, a slow producer) reaches the client without waiting for the compressor to fill its
buffer. Every flush costs a few bytes and resets the compressor's look-ahead, so a stream of small chunks compresses
much worse: one chunk per line of JSON (about 50 bytes) came out nearly three times larger than one chunk, 16-byte
chunks six times. Regroup small chunks before completing when size matters more than latency (chunkMin, or
groupWithin to bound the delay). On Scala Native the flush is not applied (scala-native's Deflater ignores the
flush mode), so the output is buffered as before.
val route: Route = encodeResponse {
complete("a large text body")
}
encodeResponseWith
Like encodeResponse but with an explicit list of codings. Without Accept-Encoding the first one is used;
when the client accepts none of them the request is rejected with an UnacceptedResponseEncodingRejection
(406 when sealed).
import org.http4s.ContentCoding
val route: Route = encodeResponseWith(ContentCoding.gzip) {
complete("always gzipped unless refused")
}
responseEncodingAccepted
Passes when the client lists the given coding (or *) with a positive q-value in Accept-Encoding, or sends no
Accept-Encoding at all; rejects with an UnacceptedResponseEncodingRejection otherwise.
decodeRequest
Decompresses a gzip or deflate encoded request body before the inner route reads it; unencoded requests and
requests known to carry no body (Content-Length: 0, or HTTP/1.x with neither a length nor chunking) pass through.
Any other Content-Encoding is rejected with UnsupportedRequestEncodingRejections (400 when sealed). A corrupt
compressed body fails the entity stream with a 400 Bad Request ("The request's encoding is corrupt"), whoever reads
it. The decoded request carries Transfer-Encoding: chunked in place of its Content-Length, so
requestEntityPresent still sees a body.
The decoded body is bounded to CodingDirectives.DefaultDecodeMaxBytes (8 MiB, Pekko's decode-max-size), so a
small compressed body cannot expand without limit; exceeding it fails with 413 Payload Too Large. A
withSizeLimit or withoutSizeLimit inside decodeRequest replaces that bound for the decompressed bytes; outside
it only bounds the compressed bytes.
val route: Route = decodeRequest {
entity(as[String]) { body => complete(s"${body.length} chars") }
}
decodeRequestWith
decodeRequestWith(coding) accepts only requests encoded with that coding (or any request when the coding is
identity). decodeRequestWith(codings: _*) tries each in turn.
requestEncodedWith
Rejects with an UnsupportedRequestEncodingRejection unless the request's Content-Encoding (identity when
absent) matches the given coding.
withPrecompressedMediaTypeSupport
Adds Content-Encoding: gzip to a response that one of the static file directives served from a .svgz or
name.ext.gz file (Pekko's Gzipped media types) when the response's content type is not an archive
(application/gzip) and no Content-Encoding is set yet. http4s media types carry no "gzipped" flag, so the
directive looks at the served file name and the final content type instead. The static file directives already add
the header when their ContentTypeResolver typed the file, so the wrapper is only needed around a file served with
an explicit content type. encodeResponse never compresses such a response, so the two directives can be nested
in either order.
val route: Route = path("logo.svg") {
withPrecompressedMediaTypeSupport {
getFromFile("/var/www/static/logo.svgz", `Content-Type`(MediaType.image.`svg+xml`))
}
}