ElixirのPlug
今日はPlugです。
Plugとは
まずPlugとはなんぞやというところですが
だそうです。イメージはRackです。今はCowboyというサーバにのみ対応してます。
導入
$ mix new hoge
# mix.exs (一部略) def application do [applications: [:logger, :cowboy, :plug]] end defp deps do [{:cowboy, "~> 1.0"}, {:plug, "~> 0.6"}] end
$ mix do deps.get, compile
このへんの詳しい情報は公式見るなりググってください
基本
まずはmodule plugと呼ばれるものを見てみます。init/1
とcall/2
が必要です。
# lib/hello_plug.ex defmodule HelloPlug do import Plug.Conn def init(opts), do: opts def call(conn, opts) do name = Keyword.get(opts, :name, "") conn |> put_resp_content_type("text/plain") |> send_resp(200, "Hello #{name}") end end Plug.Adapters.Cowboy.http HelloPlug, [name: "John"]
$ mix run --no-halt lib/hello_plug.ex
localhost:4000にアクセスすると、"Hello John"が返ってきます。init/1
はオプションを受け取って初期化を担当します。こいつが返したものはcall/2
の第2引数になります。call/2
が受け取っているconn
はPlug.Conn
構造体の変数で、リクエストメソッドとかヘッダといったリクエストフィールドを読んだり、クッキーやらセッションやら読み書きして、レスポンスヘッダとかボディとか200とか404とかセットしてこれを返すのですね。もう何でもできそうですね。WAFとか要らないですよね(嘘)
とはいえコレだけでは何かと限界があるので、Plug.Builder
を使ってみましょう。
プラグスタック
defmodule StackedPlug do import Plug.Conn use Plug.Builder plug :append, "Hello" plug :append, "World" plug :respond def append(conn, word) do msg = (conn.assigns[:msg] || "") <> word assign(conn, :msg, msg) end def respond(conn, word) do conn |> put_resp_content_type("text/plain") |> send_resp(200, conn.assigns[:msg]) end end Plug.Adapters.Cowboy.http StackedPlug, []
きっと"HelloWorld"が返ってくるかと思いますです。5~7行目に書いたplug :関数名 引数
が上から順に実行されてる感じです。この複数のplugを順に適用する仕組みをプラグスタックと呼びます。append
とかrespond
はfunction plugと呼ぶらしいです。また次のように書いてmodule plugをスタックに組み込むこともできます。
plug :my_func01 plug MyPlug plug :my_func02
書いた順に処理されるなら、果たして本当にスタックなのか。ここには触れないでおきましょう。
便利なPlug
Plug.Router
ここまで来ると、こんなことを考え始める人もいるかと思います。
plug :match, path: "/", msg; "Welcome" plug :match, path: "/hello", msg: "Hello" plug :not_found
"/"へのアクセスには"Welcome"を、"/hello"へのアクセスには"Hello"を返し、それ以外は404っていうルータみたいなこと。リクエストメソッドでも振り分ければ夢が広がりますね。こういう用途のために、Plug.Router
ってのが用意されてます。
defmodule ExampleRouter do use Plug.Router import Plug.Conn plug :match plug :dispatch get "/" do send_resp(conn, 200, "Welcome") end get "/users/:id" do send_resp(conn, 200, "Hello, I am No.#{id}") end post "/users" do ... end match _ do send_resp(conn, 404, "Not Found") end end
2, 5, 6行目が必須。特に5, 6行目の順番は大切。use Plug.Builder
はuse Plug.Router
がやってくれるので要りません。各ブロック内に突然conn
という変数が出現しているのですが、こいつはマクロが上手く処理してくれるのでエラーにはなりません。ただしconn
という変数名を前提に作られているので、con
とかconnection
とかにすると怒られます。あとは雰囲気でいけるでしょうかね。
Plug.Static
静的ファイルを配信するためのplugとしてPlug.Static
モジュールが用意されています。さっきのルータの例を
plug Plug.Static, at: "/images", from: "assets/imgs" plug :match plug :dispatch
こんな風にすると、assets/imgs
以下にあるファイルを/images/hogehoge.png
みたいにサーブできます。
他にもPlug.Session
とかあるっぽいんですが、疲れたんで。
まとめ
書いたことをまとめると、
Rackとの違いとしては、Rackアプリがリクエストを受けてレスポンスを返す構成であるのに対して、Plugはコネクションを受けてコネクションを返す構成であるという点が挙げられるらしいです。スタックのどの位置からもコネクションを通してレスポンスを返せるようにしてあるとか何とか。このへん自信無いので誰か教えてください。