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に馴染んできた模様だ。