PhoenixのPlugの説明を読んでいると、Plug自体の動きが少し気になったので読んでみました。
ここでは、以下の Hello World を追ってみることに。
https://github.com/elixir-lang/plug
API Document: http://hexdocs.pm/plug/
Plug自体、簡単なWebサーバの機構を提供するものですね。
ひとまず、
iex> {:ok, _} = Plug.Adapters.Cowboy.http MyPlug, [] {:ok, #PID<...>}
を実行してみる。
まずは以下が呼ばれる。
https://github.com/elixir-lang/plug/blob/master/lib/plug/adapters/cowboy.ex#L41
@spec http(module(), Keyword.t, Keyword.t) :: {:ok, pid} | {:error, :eaddrinuse} | {:error, term} def http(plug, opts, cowboy_options []) do run(:http, plug, opts, cowboy_options) end
この中を辿ると、 run(:http, plug, opts, cowboy_options)
にいきつく。
ここで何しているかを見てみる。
https://github.com/elixir-lang/plug/blob/master/lib/plug/adapters/cowboy.ex#L118
@http_cowboy_options [port: 4000] @https_cowboy_options [port: 4040] @not_cowboy_options [:acceptors, :dispatch, :ref, :otp_app, :compress] defp run(scheme, plug, opts, cowboy_options) do case Application.ensure_all_started(:cowboy) do {:ok, _} -> :ok {:error, {:cowboy, _}} -> raise "could not start the cowboy application. Please ensure it is listed " <> "as a dependency both in deps and application in your mix.exs" end apply(:cowboy, :"start_#{scheme}", args(scheme, plug, opts, cowboy_options)) end
なるほど。最終的に、Cowboyを実行しているのですね。これはErlang向けの軽量なWebサーバらしい。
Cowboy
https://github.com/ninenines/cowboy
返り値として、 {:ok, pid}
のタプルを返す。
試しに、もう一度実行すると以下のようにerrorを得られる
iex(2)> Plug.Adapters.Cowboy.http MyPlug, [] {:error, {:already_started, #PID<0.485.0>}}
今度は :error
のタプルですね。:already_started
が得られているので、pidを得たい場合、
{:error, {e_message, pid}} = Plug.Adapters.Cowboy.http MyPlug, []
とすれば得られます。なるほど。
ちなみに、停止するときは、
> Plug.Adapters.Cowboy.shutdown MyPlug.HTTP
とします。
これは、
https://github.com/elixir-lang/plug/blob/master/lib/plug/adapters/cowboy.ex#L96
を参考にすれば良いですね。ここで与える :ref
は、起動時に与えた Plug.Adapters.Cowboy.http MyPlug, []
の中の MyPlug
という名前。
これは、以下のコードを見てみると、確かに:refの引数( build_ref(plug, scheme)
)としてplug
を与えているのでわかります。
https://github.com/elixir-lang/plug/blob/master/lib/plug/adapters/cowboy.ex#L35
# Made public with @doc false for testing. @doc false def args(scheme, plug, opts, cowboy_options) do cowboy_options |> Keyword.put_new(:ref, build_ref(plug, scheme)) |> Keyword.put_new(:dispatch, cowboy_options[:dispatch] || dispatch_for(plug, opts)) |> normalize_cowboy_options(scheme) |> to_args() end
なるほど。Plugというか、Cawboyの理解が少し進んだぞ。
%Plug.Conn{}
の構造体が以下のような感じ。以下は、 conn.host
とかで取得できます。
%Plug.Conn{adapter: {Plug.Conn, :...}, assigns: %{}, before_send: [], body_params: %Plug.Conn.Unfetched{aspect: :body_params}, cookies: %Plug.Conn.Unfetched{aspect: :cookies}, halted: false, host: "www.example.com", method: "GET", owner: nil, params: %Plug.Conn.Unfetched{aspect: :params}, path_info: [], peer: nil, port: 0, private: %{}, query_params: %Plug.Conn.Unfetched{aspect: :query_params}, query_string: "", remote_ip: nil, req_cookies: %Plug.Conn.Unfetched{aspect: :cookies}, req_headers: [], resp_body: nil, resp_cookies: %{}, resp_headers: [{"cache-control", "max-age=0, private, must-revalidate"}], scheme: :http, script_name: [], secret_key_base: nil, state: :unset, status: nil}
他、
plug :match plug :dispatch
による、パスのマッチングと、そのマッチしたパスを実行する、というpipeline。
Plug自体、RouterやTestといったモジュールも提供し、中心的なツールになっているので、もう少し慣れていきたい。ともあれ、コードを追うことができるくらいにはElixirに馴染んできた模様だ。