defmodule Frugality.ConditionalGET do
@moduledoc """
Automatically computes and sets a shallow entity tag and evaluates
the request preconditions against it.
This plug generates shallow entity tags. Shallow means that the
generated entity-tag is not context sensitive and the whole response
body is used as a source for the generation.
## Usage
plug Frugality.ConditionalGET,
encoder: Frugality.Encoder.MD5
"""
@behaviour Plug
alias Frugality.Core.Utils
alias Frugality.Generator
alias Frugality.Generator.Conn
alias Frugality.Generator.Response
import Plug.Conn, only: [register_before_send: 2, get_resp_header: 2]
@default_opts %{
encoder: MD5
}
@impl true
def init(opts) do
opts
|> Map.new()
|> Map.merge(@default_opts)
end
@impl true
def call(conn, opts) do
register_before_send(conn, &do_call(&1, opts))
end
# Skip evaluation when the preconditions has already been manually
# evaluated by `evaluate_preconditions[!]/2`.
defp do_call(%Plug.Conn{private: %{frugality_evaluated: true}} = conn, _),
do: conn
defp do_call(conn, opts) do
conn
|> generate_metadata(opts)
|> evaluate_preconditions(opts)
end
defp generate_metadata(%Plug.Conn{status: status} = conn, %{encoder: encoder})
when status in [200, 201] do
cond do
has_metadata?(conn) ->
conn
metadata = response_metadata(conn, encoder) ->
Utils.put_metadata(conn, metadata)
true ->
conn
end
end
defp generate_metadata(conn, _), do: conn
defp has_metadata?(conn) do
["etag", "last-modified"]
|> Enum.map(&get_resp_header(conn, &1))
|> Enum.all?(&Enum.empty?/1)
|> Kernel.not()
end
# It's usually the static file server's job to generate metadata
# when files are being sent.
defp response_metadata(%Plug.Conn{state: :file}, _), do: nil
defp response_metadata(%Plug.Conn{resp_body: ""}, _), do: nil
defp response_metadata(%Plug.Conn{resp_body: response}, encoder) do
Generator.generate(Response, %{response: response}, encoder)
end
defp evaluate_preconditions(%Plug.Conn{method: method} = conn, _)
when method in ["GET", "HEAD"] do
conn
|> Frugality.put_generator(Conn)
|> Frugality.evaluate_preconditions(%{})
end
defp evaluate_preconditions(conn, _), do: conn
end