cat-bookmarker/deps/plug/lib/plug.ex

176 lines
5.0 KiB
Elixir
Raw Normal View History

2024-03-10 18:52:04 +00:00
defmodule Plug do
@moduledoc """
The plug specification.
## Types of plugs
There are two kind of plugs: function plugs and module plugs.
### Function plugs
A function plug is by definition any function that receives a connection
and a set of options and returns a connection. Function plugs must have
the following type signature:
(Plug.Conn.t, Plug.opts) :: Plug.Conn.t
### Module plugs
A module plug is an extension of the function plug. It is a module that must
export:
* a `c:call/2` function with the signature defined above
* an `c:init/1` function which takes a set of options and initializes it.
The result returned by `c:init/1` is passed as second argument to `c:call/2`. Note
that `c:init/1` may be called during compilation and as such it must not return
pids, ports or values that are specific to the runtime.
The API expected by a module plug is defined as a behaviour by the
`Plug` module (this module).
## Examples
Here's an example of a function plug:
def json_header_plug(conn, _opts) do
Plug.Conn.put_resp_content_type(conn, "application/json")
end
Here's an example of a module plug:
defmodule JSONHeaderPlug do
import Plug.Conn
def init(opts) do
opts
end
def call(conn, _opts) do
put_resp_content_type(conn, "application/json")
end
end
## The Plug pipeline
The `Plug.Builder` module provides conveniences for building plug pipelines.
"""
@type opts ::
binary
| tuple
| atom
| integer
| float
| [opts]
| %{optional(opts) => opts}
| MapSet.t()
@callback init(opts) :: opts
@callback call(conn :: Plug.Conn.t(), opts) :: Plug.Conn.t()
require Logger
@doc """
Run a series of plugs at runtime.
The plugs given here can be either a tuple, representing a module plug
and their options, or a simple function that receives a connection and
returns a connection.
If any plug halts, the connection won't invoke the remaining plugs. If the
given connection was already halted, none of the plugs are invoked either.
While `Plug.Builder` is designed to operate at compile-time, the `run` function
serves as a straightforward alternative for runtime executions.
## Examples
Plug.run(conn, [{Plug.Head, []}, &IO.inspect/1])
## Options
* `:log_on_halt` - a log level to be used if a plug halts
"""
@spec run(Plug.Conn.t(), [{module, opts} | (Plug.Conn.t() -> Plug.Conn.t())], Keyword.t()) ::
Plug.Conn.t()
def run(conn, plugs, opts \\ [])
def run(%Plug.Conn{halted: true} = conn, _plugs, _opts),
do: conn
def run(%Plug.Conn{} = conn, plugs, opts),
do: do_run(conn, plugs, Keyword.get(opts, :log_on_halt))
defp do_run(conn, [{mod, opts} | plugs], level) when is_atom(mod) do
case mod.call(conn, mod.init(opts)) do
%Plug.Conn{halted: true} = conn ->
level && Logger.log(level, "Plug halted in #{inspect(mod)}.call/2")
conn
%Plug.Conn{} = conn ->
do_run(conn, plugs, level)
other ->
raise "expected #{inspect(mod)} to return Plug.Conn, got: #{inspect(other)}"
end
end
defp do_run(conn, [fun | plugs], level) when is_function(fun, 1) do
case fun.(conn) do
%Plug.Conn{halted: true} = conn ->
level && Logger.log(level, "Plug halted in #{inspect(fun)}")
conn
%Plug.Conn{} = conn ->
do_run(conn, plugs, level)
other ->
raise "expected #{inspect(fun)} to return Plug.Conn, got: #{inspect(other)}"
end
end
defp do_run(conn, [], _level), do: conn
@doc """
Forwards requests to another plug while setting the connection to a trailing subpath of the request.
The `path_info` on the forwarded connection will only include the request path trailing segments
supplied to the `forward` function. The `conn.script_name` attribute retains the correct base path,
e.g., url generation.
## Example
defmodule Router do
def init(opts), do: opts
def call(conn, opts) do
case conn do
# Match subdomain
%{host: "admin." <> _} ->
AdminRouter.call(conn, opts)
# Match path on localhost
%{host: "localhost", path_info: ["admin" | rest]} ->
Plug.forward(conn, rest, AdminRouter, opts)
_ ->
MainRouter.call(conn, opts)
end
end
end
"""
@spec forward(Plug.Conn.t(), [String.t()], atom, Plug.opts()) :: Plug.Conn.t()
def forward(%Plug.Conn{path_info: path, script_name: script} = conn, new_path, target, opts) do
{base, split_path} = Enum.split(path, length(path) - length(new_path))
conn = do_forward(target, %{conn | path_info: split_path, script_name: script ++ base}, opts)
%{conn | path_info: path, script_name: script}
end
defp do_forward({mod, fun}, conn, opts), do: apply(mod, fun, [conn, opts])
defp do_forward(mod, conn, opts), do: mod.call(conn, opts)
end