やりたいこと
Plugはとても便利。特にNode.jsからやってきた自分にとっては、Plug = Middlewareということで非常にとっつきやすい。こいつを使いこなすことで幸せになれるんじゃないかと本能的に感じた。
RackやConnectを触っていた人にはすぐとっつける内容だと思うが、今回は違ったバックグラウンドの方でもわかるように書く。
Plugって何?
HTTP Request/Responseをまとめた"connection"をリレーする、プラグインのスタック。
Phoenixが提供するPlug
Phoenix.Contoller配下にpluggableな実装がまとめられている。
必要なものは一通り揃っている印象。
今回作ってみるPlug
APIリクエストの送信元が、localhostか、ローカルネットワーク内からのものか、グローバルからのものかを判断し、必要なアクセス制限を加える、allow_accessというPlugを作ってみる。
実装
Plug本体は以下のような実装。
defmodule Demo.Plug.AllowAccess do
import Plug.Conn
def init(default), do: default
def call(conn, params) do
case conn.peer do
{{127, 0, 0, 1}, _} ->
conn
{{192, 168, _, _}, _} ->
if params == [:localhost] do
send400 conn
else
conn
end
_ ->
if params == [:localhost] or params == [:internal] do
send400 conn
else
conn
end
end
end
defp send400(conn) do
conn
|> put_resp_content_type("text/plain")
|> send_resp(400, "Bad Request")
end
end
init/1でPlugそのものへの初期化を行う。当然これは一回しか呼ばれない。今回は特に何も行わない。
call/2で実際の処理を行う。
paramsには:localhost
、:internal
、:all
の3種類が渡される想定。
これを実際に利用するRouterは以下の様な感じ。
defmodule Demo.Router do
use Demo.Web, :router
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_flash
plug :protect_from_forgery
plug :put_secure_browser_headers
plug Demo.Plug.AllowAccess, [:all]
end
pipeline :admin do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_flash
plug :protect_from_forgery
plug :put_secure_browser_headers
plug Demo.Plug.AllowAccess, [:internal]
end
pipeline :api do
plug :accepts, ["json"]
plug Demo.Plug.AllowAccess, [:all]
end
pipeline :api_restricted do
plug :accepts, ["json"]
plug Demo.Plug.AllowAccess, [:localhost]
end
scope "/", Demo do
pipe_through :browser
get "/", PageController, :index
end
scope "/admin", Demo do
pipe_through :admin
get "/", AdminController, :index
resources "/users", UserController
end
scope "/api", Demo do
pipe_through :api
resources "/users", UserController, only: [:index, :show]
end
scope "/api_restricted", Demo do
pipe_through :api_restricted
resources "/users", UserController, only: [:delete]
end
end
ユーザー登録ができるアプリケーションを想定しており、/
と、/api
へのアクセスはIP制限を行わず、Adminページへのアクセスはローカルネットワーク内からのみアクセスを許可、ユーザーの削除だけはAPI経由でlocalhostから行うよう強制させた。
あとがき
Plugはあらゆる局面で活用できる優れた仕組みだが、まだまだElixirのエコシステムが成熟していないため、細かいものも含めて自作する機会が多い。
Elixirが言語として成熟してきて、かゆいところに手が届くPlugがたくさん公開されるようになると良いのだが。