zf

zenflows testing
git clone https://s.sonu.ch/~srfsh/zf.git
Log | Files | Refs | Submodules | README | LICENSE

plug.ex (4989B)


      1 defmodule Plug do
      2   @moduledoc """
      3   The plug specification.
      4 
      5   There are two kind of plugs: function plugs and module plugs.
      6 
      7   #### Function plugs
      8 
      9   A function plug is any function that receives a connection and a set of
     10   options and returns a connection. Its type signature must be:
     11 
     12       (Plug.Conn.t, Plug.opts) :: Plug.Conn.t
     13 
     14   #### Module plugs
     15 
     16   A module plug is an extension of the function plug. It is a module that must
     17   export:
     18 
     19     * a `c:call/2` function with the signature defined above
     20     * an `c:init/1` function which takes a set of options and initializes it.
     21 
     22   The result returned by `c:init/1` is passed as second argument to `c:call/2`. Note
     23   that `c:init/1` may be called during compilation and as such it must not return
     24   pids, ports or values that are specific to the runtime.
     25 
     26   The API expected by a module plug is defined as a behaviour by the
     27   `Plug` module (this module).
     28 
     29   ## Examples
     30 
     31   Here's an example of a function plug:
     32 
     33       def json_header_plug(conn, _opts) do
     34         Plug.Conn.put_resp_content_type(conn, "application/json")
     35       end
     36 
     37   Here's an example of a module plug:
     38 
     39       defmodule JSONHeaderPlug do
     40         import Plug.Conn
     41 
     42         def init(opts) do
     43           opts
     44         end
     45 
     46         def call(conn, _opts) do
     47           put_resp_content_type(conn, "application/json")
     48         end
     49       end
     50 
     51   ## The Plug pipeline
     52 
     53   The `Plug.Builder` module provides conveniences for building plug
     54   pipelines.
     55   """
     56 
     57   @type opts ::
     58           binary
     59           | tuple
     60           | atom
     61           | integer
     62           | float
     63           | [opts]
     64           | %{optional(opts) => opts}
     65           | MapSet.t()
     66 
     67   @callback init(opts) :: opts
     68   @callback call(conn :: Plug.Conn.t(), opts) :: Plug.Conn.t()
     69 
     70   require Logger
     71 
     72   @doc """
     73   Run a series of Plugs at runtime.
     74 
     75   The plugs given here can be either a tuple, representing a module plug
     76   and their options, or a simple function that receives a connection and
     77   returns a connection.
     78 
     79   If any of the plugs halt, the remaining plugs are not invoked. If the
     80   given connection was already halted, none of the plugs are invoked
     81   either.
     82 
     83   While `Plug.Builder` works at compile-time, this is a straight-forward
     84   alternative that works at runtime.
     85 
     86   ## Examples
     87 
     88       Plug.run(conn, [{Plug.Head, []}, &IO.inspect/1])
     89 
     90   ## Options
     91 
     92     * `:log_on_halt` - a log level to be used if a Plug halts
     93 
     94   """
     95   @spec run(Plug.Conn.t(), [{module, opts} | (Plug.Conn.t() -> Plug.Conn.t())], Keyword.t()) ::
     96           Plug.Conn.t()
     97   def run(conn, plugs, opts \\ [])
     98 
     99   def run(%Plug.Conn{halted: true} = conn, _plugs, _opts),
    100     do: conn
    101 
    102   def run(%Plug.Conn{} = conn, plugs, opts),
    103     do: do_run(conn, plugs, Keyword.get(opts, :log_on_halt))
    104 
    105   defp do_run(conn, [{mod, opts} | plugs], level) when is_atom(mod) do
    106     case mod.call(conn, mod.init(opts)) do
    107       %Plug.Conn{halted: true} = conn ->
    108         level && Logger.log(level, "Plug halted in #{inspect(mod)}.call/2")
    109         conn
    110 
    111       %Plug.Conn{} = conn ->
    112         do_run(conn, plugs, level)
    113 
    114       other ->
    115         raise "expected #{inspect(mod)} to return Plug.Conn, got: #{inspect(other)}"
    116     end
    117   end
    118 
    119   defp do_run(conn, [fun | plugs], level) when is_function(fun, 1) do
    120     case fun.(conn) do
    121       %Plug.Conn{halted: true} = conn ->
    122         level && Logger.log(level, "Plug halted in #{inspect(fun)}")
    123         conn
    124 
    125       %Plug.Conn{} = conn ->
    126         do_run(conn, plugs, level)
    127 
    128       other ->
    129         raise "expected #{inspect(fun)} to return Plug.Conn, got: #{inspect(other)}"
    130     end
    131   end
    132 
    133   defp do_run(conn, [], _level), do: conn
    134 
    135   @doc """
    136   Forwards requests to another Plug setting the connection to a trailing subpath of the request.
    137 
    138   The `path_info` on the forwarded connection will only include the trailing segments
    139   of the request path supplied to forward, while `conn.script_name` will
    140   retain the correct base path for e.g. url generation.
    141 
    142   ## Example
    143 
    144       defmodule Router do
    145         def init(opts), do: opts
    146 
    147         def call(conn, opts) do
    148           case conn do
    149             # Match subdomain
    150             %{host: "admin." <> _} ->
    151               AdminRouter.call(conn, opts)
    152 
    153             # Match path on localhost
    154             %{host: "localhost", path_info: ["admin" | rest]} ->
    155               Plug.forward(conn, rest, AdminRouter, opts)
    156 
    157             _ ->
    158               MainRouter.call(conn, opts)
    159           end
    160         end
    161       end
    162 
    163   """
    164   @spec forward(Plug.Conn.t(), [String.t()], atom, Plug.opts()) :: Plug.Conn.t()
    165   def forward(%Plug.Conn{path_info: path, script_name: script} = conn, new_path, target, opts) do
    166     {base, split_path} = Enum.split(path, length(path) - length(new_path))
    167 
    168     conn = do_forward(target, %{conn | path_info: split_path, script_name: script ++ base}, opts)
    169     %{conn | path_info: path, script_name: script}
    170   end
    171 
    172   defp do_forward({mod, fun}, conn, opts), do: apply(mod, fun, [conn, opts])
    173   defp do_forward(mod, conn, opts), do: mod.call(conn, opts)
    174 end