PlugCaisson behaviour (plug_caisson v0.2.0)

Body reader for supporting compressed Plug requests.

Implementing algorithm

In addition to the built in algorithms (see read_body/2) it is possible to implement custom algorithms by implementing behaviour defined in this module.

Example

defmodule BobbyCompression do
  @behaviour PlugCaisson

  # Initialise decompression state with whatever is needed
  @impl true
  def init(opts), do: {:ok, Bobby.open()}

  # Gracefully close the state
  @impl true
  def deinit(state), do: Bobby.finish(state)

  # Process read data to decompress them
  @impl true
  def process(state, data, _opts) do
    case Bobby.decompress(state, data) do
      {:finished, decompressed, new_state} ->
        # All data was decompressed and there is no more data to be
        # decompressed in stream
        {:ok, decompressed, new_state}

      {:more, decompressed, new_state} ->
        # It can happen that `decompressed == ""` and this is perfectly fine
        # as long as there is possibility that there will be more data in
        # future
        {:more, decompressed, new_state}

      {:error, _} = error -> error
    end
  end
end

Summary

Callbacks

Cleanup for the state. This will be called in Plug.Conn.register_before_send/2 callback, so the same conditions as with these apply. It is guaranteed that it will be called only once for each connection.

Initialise state for the decompression algorithm.

Process chunk of data.

Functions

Read Plug.Conn request body and decompress it if needed.

Callbacks

@callback deinit(state :: term()) :: term()

Cleanup for the state. This will be called in Plug.Conn.register_before_send/2 callback, so the same conditions as with these apply. It is guaranteed that it will be called only once for each connection.

@callback init(opts :: term()) :: {:ok, state :: term()} | {:error, term()}

Initialise state for the decompression algorithm.

This callback will be called if and only if given algorithm was picked as a suitable option for decompression. The returned state will be stored in the Plug.Conn.t(). It is guaranteed that it will be called only on first call to read_body/2 and all subsequent calls will not call this function again.

It will receive data passed as a second value in tuple declared in the algorithm map.

Link to this callback

process(state, data, opts)

@callback process(state :: term(), data :: binary(), opts :: keyword()) ::
  {:ok, binary(), new_state :: term()}
  | {:more, binary(), new_state :: term()}
  | {:error, term()}

Process chunk of data.

It receives current state, binary read from the request and list of options passed to the read_body/2 as a 2nd argument.

Return value

In case of success it should return 3-ary tuple:

  • {:ok, binary(), new_state :: term()} - wich mean that all data was read and there is no more data left in the internal buffer.
  • {:more, binary(), new_state :: term()} - which mean that data was processed, but there is more data left to be read in future calls.

If error occured during processing {:error, term()} tuple should be returned.

Functions

Link to this function

read_body(conn, opts \\ [])

Read Plug.Conn request body and decompress it if needed.

Options

Accepts the same set of options as Plug.Conn.read_body/2 with one option extra: :algorithms which is map containing algorithm identifier as key and tuple containing module name for module that implements PlugCaisson behaviour and value that will be passed as 2nd argument to the init/1 callback.

By default the value is set to:

%{
  "br" => {PlugCaisson.Brotli, []},
  "deflate" => {PlugCaisson.Zlib, [type: :deflate]},
  "gzip" => {PlugCaisson.Zlib, [type: :gzip]},
  "zstd" => {PlugCaisson.Zstandard, []}
}

Supported algorithms

  • gzip
  • deflate
  • br (Brotli) - only if :brotli dependency is available
  • zstd (Zstandard) - only if :ezstd dependency is available

Options

All passed opts will be passed to Plug.Conn.read_body/2 and to used decompression handlers. Decompressors by default will use :length to limit amount of returned data to prevent zipbombs. Returned data can be longer than :length if the internal decompression buffer was larger. As it is described in Plug.Conn.read_body/2 docs. By default length: 8_000_000.