View Source 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:

  • Prevent sending a response payload if the client has a cached representation that's still the same as on the server.

  • Prevent an update if the client has an older version than the server.

usage

Usage

There are two modes of operation - automatic and manual.

automatic-mode

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

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

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.