Skip to content

Latest commit

 

History

History
131 lines (109 loc) · 5.17 KB

README.md

File metadata and controls

131 lines (109 loc) · 5.17 KB

PlugRequireHeader

Build Status Inline docs Hex.pm Documentation

An Elixir Plug for requiring and extracting a given header.

Usage

Update your mix.exs file and run mix deps.get.

defp deps do
  [{:plug_require_header, "~> 0.8"}]
end

Add the plug to e.g. a pipeline in a Phoenix controller. In this case we will require the request header x-api-key to be set, extract its first value and assign it the connection (a Plug.Conn) for later use in another plug or action.

defmodule MyPhoenixApp.MyController do
  use MyPhoenixApp.Web, :controller
  alias Plug.Conn.Status

  plug PlugRequireHeader, headers: [api_key: "x-api-key"]
  plug :action

  def index(conn, _params) do
    conn
    |> put_status(Status.code :ok)
    |> text "The API key used is: #{conn.assigns[:api_key]}"
  end
end

Notice how the first value required header "x-api-key" has been extracted and can be retrieved using conn.assigns[:api_key]. An alternative is to use Plug.Conn.get_req_header/2 to get all the values associated with a given header.

By default, a missing header will return a status code of 403 (forbidden) and halt the plug pipeline, i.e. no subsequent plugs will be executed. The same is true if the required header is explicitly set to nil as the underlying HTTP server will not include the header. This behaviour however is configurable.

defmodule MyPhoenixApp.MyOtherController do
  use MyPhoenixApp.Web, :controller
  alias Plug.Conn.Status

  plug PlugRequireHeader, headers: [api_key: "x-api-key"],
    on_missing: [status: 418, message: %{error: "I'm a teapot!"}, as: :json]
  plug :action

  def index(conn, _params) do
    conn
    |> put_status(Status.code :ok)
    |> text "The API key used is: #{conn.assigns[:api_key]}"
  end

The :on_missing handling can be given a keyword list of options on how to handle a missing header.

  • :status - an integer or atom to specify the status code. If it's an atom, it'll be looked up using the Plug.Status.code function. Default is :forbidden.
  • :message - a binary sent as the response body. Default is an empty string.
  • :as - an atom describing the content type and encoding. Currently supported alternatives are :text for plain text and :json for JSON. Default is :text.

You can also provide a function that handles the missing header by specifying a module/function pair in a tuple as the :on_missing value.

defmodule MyPhoenixApp.MyOtherController do
  use MyPhoenixApp.Web, :controller
  alias Plug.Conn.Status

  plug PlugRequireHeader, headers: [api_key: "x-api-key"],
    on_missing: {__MODULE__, :handle_missing_header}
  plug :action

  def index(conn, _params) do
    conn
    |> put_status(Status.code :ok)
    |> text("The API key used is: #{conn.assigns[:api_key]}")
  end

  def handle_missing_header(conn, {_connection_assignment_key, missing_header_key}) do
    conn
    |> send_resp(Status.code(:bad_request), "Missing header: #{missing_header_key}")
    |> halt
  end
end

If the header is missing or set to nil the status code, a status code of 400 (bad request) will be returned before the plug pipeline is halted. Notice that the function specified as a callback needs to be a public function as it'll be invoked from another module. Also notice that the callback must return a Plug.Conn struct.

Lastly, it's possible to extract multiple headers at the same time.

  plug PlugRequireHeader, headers: [api_key: "x-api-key", magic: "x-magic"]

If extracting multiple headers and specifying an :on_missing callback, be aware that the callback will be invoked once for each missing header. Be careful to not send a response as you can easily run into raising a Plug.Conn.AlreadySentError. A way of avoiding this is to have your callback function pattern match on the state of the conn.

  plug PlugRequireHeader, headers: [api_key: "x-api-key", secret: "x-secret"],
    on_missing: {__MODULE__, :handle_missing_header}

  def handle_missing_header(%Plug.Conn{state: :sent} = conn, _), do: conn
  def handle_missing_header(conn, {_connection_assignment_key, missing_header_key}) do
    conn
    |> send_resp(Status.code(:bad_request), "Missing header: #{missing_header_key}")
    |> halt
  end

This example will only send a response for the first missing header.

Online documentation

For more information, see the full documentation.

Contributing

  1. Fork this repository
  2. Create your feature branch (git checkout -b I-say-we-take-off-and-nuke-it-from-orbit)
  3. Commit your changes (git commit -am 'It is the only way to be sure!')
  4. Push to the branch (git push origin I-say-we-take-off-and-nuke-it-from-orbit)
  5. Create a new Pull Request