View Source PlugHTTPCache (plug_http_cache v0.3.1)

A Plug that caches HTTP responses

This plug library relies on the http_cache library. It supports all caching features of RFC9111 and more (such as conditional requests and range requests).

See http_cache documentation for more information.

configuration

Configuration

In your plug pipeline, set the Plug for routes on which you want to enable caching:

router.ex

  pipeline :cache do
    plug PlugHTTPCache, @caching_options
  end

  ...

  scope "/", PlugHTTPCacheDemoWeb do
    pipe_through :browser

    scope "/some_route" do
      pipe_through :cache

      ...
    end
  end

You can also configure it for all requests by setting it in Phoenix's endpoint file:

endpoint.ex

defmodule MyApp.Endpoint do
  use Phoenix.Endpoint, otp_app: :plug_http_cache_demo

  % some other plugs

  plug Plug.RequestId
  plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint]
  plug Plug.Parsers,
    parsers: [:urlencoded, :multipart, :json],
    pass: ["*/*"],
    json_decoder: Phoenix.json_library()
  plug Plug.Head
  plug Plug.Session, @session_options

  plug PlugHTTPCache, @caching_options

  plug PlugHTTPCacheDemoWeb.Router
end

Note that:

  • caching chunked responses is not supported
  • some responses (called "cacheable by default") can be cached even when no cache-control header is set. For instance, a 200 response to a get request is cached 2 minutes by default, unless cache-control headers prohibit it
  • Phoenix automatically sets the "cache-control" header to "max-age=0, private, must-revalidate", so by default no response will ever be cached unless you override this header

You can also configure PlugHTTPCache.StaleIfError to return expired cached responses. This is useful to continue returning something when the backend experiences failures (for example if the DB crashed and while it's rebooting).

plug-options

Plug options

Plug options are those documented by :http_cache.opts/0.

The only required option is :store.

This plug sets the following default options:

  • :type: :shared,
  • :auto_compress: true,
  • :auto_accept_encoding: true

stores

Stores

Responses have to be stored in a separate store backend (this library does not come with one), such as:

Both are cluster-aware.

To use it along with this library, just add it to your mix.exs file:

mix.exs

{:plug_http_cache, "~> ..."},
{:http_cache_store_memory, "~> ..."},

security-considerations

Security considerations

Unlike many HTTP caches, http_cache allows caching:

  • responses to authorized request (with an "authorization" header)
  • responses with cookies

In the first case, beware of authenticating before handling caching. In other words, don't:

PlugHTTPCache, @caching_options
MyPlug.AuthorizeUser

which would return a cached response to unauthorized users, but do instead:

MyPlug.AuthorizeUser
PlugHTTPCache, @caching_options

Beware of not setting caching headers on private responses containing cookies.

useful-libraries

Useful libraries

  • PlugCacheControl can be used to set cache-control headers in your Plug pipelines, or manually in your controllers
  • PlugHTTPValidator should be used to set HTTP validators as soon as cacheable content is returned. See project documentation to figure out why

telemetry-events

Telemetry events

The following events are emitted:

  • [:plug_http_cache, :hit] when a cached response is returned.
  • [:plug_http_cache, :miss] when no cached response was found
  • [:plug_http_cache, :stale_if_error] when a response was returned because an error occurred downstream (see PlugHTTPCache.StaleIfError)

Neither measurements nor metadata are added to these events.

The http_cache, http_cache_store_memory and http_cache_store_disk emit other events about the caching subsystems, including some helping with detecting normalization issues.

normalization

Normalization

The underlying http caching library may store different responses for the same URL, following the directives of the "vary" header. For instance, if a response can be returned in English or in French, both versions can be cached as long as the "vary" header is correctly used.

This can unfortunately result in an explosion of stored responses if the headers are not normalized. For instance, in this scenario where a site handles both these languages, a response will be stored for any of these requests that include an "accept-language" header:

  • fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5
  • fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7,*;q=0.5
  • en
  • de
  • en, de
  • en, de, fr
  • en;q=1, de
  • en;q=1, de;q=0.9
  • en;q=1, de;q=0.8
  • en;q=1, de;q=0.7
  • en;q=1, de;q=0.6
  • en;q=1, de;q=0.5

and so on, so potentially hundreds of stored responses for only 2 available responses (English and French versions).

In this case, you probably want to apply normalization before caching. This could be done by a plug set before the PlugHTTPCache plug.

See Best practices for using the Vary header for more guidance regarding this issue.

Link to this section Summary

Functions

Adds one or more alternate keys to the cached response

Link to this section Functions

Link to this function

set_alternate_keys(conn, alt_keys)

View Source
@spec set_alternate_keys(
  Plug.Conn.t(),
  :http_cache.alternate_key() | [:http_cache.alternate_key()]
) :: Plug.Conn.t()

Adds one or more alternate keys to the cached response

Request with alternate keys can be later be invalidated with the :http_cache.invalidate_by_alternate_key/2 function.