Packages

35 built-in plugins for the Hibana web framework: JWT, OAuth, CORS, rate limiting, GraphQL, admin dashboard, i18n, and more.

Current section

Files

Jump to
hibana_plugins lib hibana plugins compression.ex
Raw

lib/hibana/plugins/compression.ex

defmodule Hibana.Plugins.Compression do
@moduledoc """
Response compression plugin supporting gzip and deflate.
## Usage
plug Hibana.Plugins.Compression
plug Hibana.Plugins.Compression, level: 6, min_size: 1024
## Options
- `:level` - Compression level from 0 (no compression) to 9 (maximum compression), used for deflate encoding (default: `6`)
- `:min_size` - Minimum response body size in bytes before compression is applied (default: `860`)
"""
use Hibana.Plugin
import Plug.Conn
@impl true
def init(opts) do
%{
level: Keyword.get(opts, :level, 6),
min_size: Keyword.get(opts, :min_size, 860)
}
end
@impl true
def call(conn, opts) do
register_before_send(conn, fn conn ->
if should_compress?(conn, opts) do
compress(conn, opts)
else
conn
end
end)
end
defp should_compress?(conn, opts) do
body = conn.resp_body
has_body =
if body do
byte_size(IO.iodata_to_binary(body)) >= opts.min_size
else
false
end
has_body and not compressed?(conn) and accepts_encoding?(conn)
end
defp compress(conn, opts) do
encoding = preferred_encoding(conn)
body = IO.iodata_to_binary(conn.resp_body)
case encoding do
"gzip" ->
compressed = :zlib.gzip(body)
conn
|> put_resp_header("content-encoding", "gzip")
|> put_resp_header("vary", "Accept-Encoding")
|> delete_resp_header("content-length")
|> Map.put(:resp_body, compressed)
"deflate" ->
z = :zlib.open()
:zlib.deflateInit(z, opts.level)
compressed = :zlib.deflate(z, body, :finish) |> IO.iodata_to_binary()
:zlib.deflateEnd(z)
:zlib.close(z)
conn
|> put_resp_header("content-encoding", "deflate")
|> put_resp_header("vary", "Accept-Encoding")
|> delete_resp_header("content-length")
|> Map.put(:resp_body, compressed)
_ ->
conn
end
end
defp accepts_encoding?(conn) do
case get_req_header(conn, "accept-encoding") do
[header | _] -> String.contains?(header, "gzip") or String.contains?(header, "deflate")
_ -> false
end
end
defp preferred_encoding(conn) do
case get_req_header(conn, "accept-encoding") do
[header | _] ->
cond do
String.contains?(header, "gzip") -> "gzip"
String.contains?(header, "deflate") -> "deflate"
true -> nil
end
_ ->
nil
end
end
defp compressed?(conn) do
case get_resp_header(conn, "content-encoding") do
[_ | _] -> true
_ -> false
end
end
end