Frugality

A toolkit for handling conditional HTTP requests in Plug-based services.

Conditional HTTP requests are closely related to HTTP caching and are useful in situations where one wants to:

Usage

There are two modes of operation - automatic and manual.

Automatic mode

The automatic mode can be activated by simply placing a plug in the pipeline.

plug Frugality.ConditionalGET

The plug generates ETag and last-modified headers if needed and evaluates the request preconditions against the headers.

As you can see the automatic mode is pretty easy to use, easier than the manual one, but the latter is more flexible.

Manual mode

In manual mode, one is expected to define at least one additional module - a metadata generator.

Let's say you have an endpoint serving individual orders and you want to support conditional requests.

defmodule OrderMetadata do
  @behaviour Frugality.Generator

  @impl true
  def etag(%{order: order}) do
    # For the purpose of the example, `order` is an Ecto schema struct.
    {:source, ["order", to_string(order.id), DateTime.to_iso8601(order.updated_at)]}
  end

  @impl true
  def last_modified(%{order: order}),
    do: order.updated_at
end

After we've defined a metadata generator, we can evaluate the request preconditions in the controller.

import Frugality,
  only: [put_generator: 2, evaluate_preconditions: 2]

plug :put_generator, OrderMetadata

def show(conn, %{"id" => order_id}) do
  order = Orders.get_order!(order_id)
  assigns = [order: order]

  with {:ok, conn} <- evaluate_preconditions(conn, assigns) do
    render(conn, :show, assigns)
  end
end

So far we've seen only Frugality.evaluate_preconditions/2 being used but there's another function which is applicable in many cases - Frugality.short_circuit/3. Let's rewrite the previous example using it.

import Frugality,
  only: [put_generator: 2, short_circuit: 2]

plug :put_generator, OrderMetadata

def show(conn, %{"id" => order_id}) do
  order = Orders.get_order!(order_id)
  assigns = [order: order]

  short_circuit(conn, assigns, fn conn ->
    render(conn, :show, assigns)
  end)
end

Installation

If available in Hex, the package can be installed by adding frugality to your list of dependencies in mix.exs:

def deps do
  [
    {:frugality, "~> 0.1.0"}
  ]
end

Documentation can be generated with ExDoc and published on HexDocs. Once published, the docs can be found at https://hexdocs.pm/frugality.