lib/plug_probe.ex

defmodule PlugProbe do
  @default_path "/probe"

  @moduledoc """
  A plug for responding to HTTP probe requests.

  This plug responds to `GET` or `HEAD` requests at a specific path
  (`#{@default_path}` by default), with:

    * status code: `200`
    * body: `OK` or `{}` (when `json: true` option is set)

  ## Options

  The following options can be used when calling `plug PlugProbe`.

    * `:path` (string) - specify the path on which `PlugProbe` will be mounted
      to respond to probe requests. Default to `#{@default_path}`.
    * `:json` (boolean) - specify whether the response will be an
      `application/json` response. Default to `false`.

  ## Examples

  In general, probe requests are separated from the functionality of the
  application, because of that, it's better to handle them as light as
  possible.

  In order to do that, this plug should be placed near the top of a plug
  pipeline, then it can match requests early so that subsequent plugs don't
  have the chance to tamper the connection.

  For a simple plug pipeline:

      defmodule DemoServer do
        use Plug.Builder

        # Put it before any other plugs
        plug PlugProbe

        # ...
      end

  For a Phoenix endpoint:

      defmodule DemoWeb.Endpoint do
        use Phoenix.Endpoint, otp_app: :demo

        # Put it before any other plugs
        plug PlugProbe

        # ...
      end

  Using a custom probe path is easy:

      defmodule DemoServer do
        use Plug.Builder

        # Put it before any other plugs
        plug PlugProbe, path: "/heartbeat"

        # ...
      end

  """

  @behaviour Plug
  import Plug.Conn

  def init(opts),
    do: Keyword.merge([path: @default_path, json: false], opts)

  def call(%Plug.Conn{} = conn, opts) do
    expected_path_info = String.split(opts[:path], "/", trim: true)

    if conn.path_info == expected_path_info and conn.method in ~w(GET HEAD) do
      conn |> halt() |> response(opts[:json])
    else
      conn
    end
  end

  defp response(conn, false = _json),
    do: send_resp(conn, 200, "OK")

  defp response(conn, true = _json),
    do: conn |> put_resp_content_type("application/json") |> send_resp(200, "{}")
end