LoginSignup
34
33

More than 5 years have passed since last update.

[Elixir] PhoenixでPlugの自作

Posted at

やりたいこと

Plugはとても便利。特にNode.jsからやってきた自分にとっては、Plug = Middlewareということで非常にとっつきやすい。こいつを使いこなすことで幸せになれるんじゃないかと本能的に感じた。

(公式)Plug Guide

RackやConnectを触っていた人にはすぐとっつける内容だと思うが、今回は違ったバックグラウンドの方でもわかるように書く。

Plugって何?

HTTP Request/Responseをまとめた"connection"をリレーする、プラグインのスタック。

Phoenixが提供するPlug

Phoenix.Contoller配下にpluggableな実装がまとめられている。

(公式)Phoenix.Controller

必要なものは一通り揃っている印象。

今回作ってみるPlug

APIリクエストの送信元が、localhostか、ローカルネットワーク内からのものか、グローバルからのものかを判断し、必要なアクセス制限を加える、allow_accessというPlugを作ってみる。

実装

Plug本体は以下のような実装。

web/plugs/allow_access.ex
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は以下の様な感じ。

web/router.ex
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がたくさん公開されるようになると良いのだが。

34
33
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
34
33