/var/log/messages

Apr 3, 2019 - 2 minute read - Comments - programming

phoenix excersize (4)

今日は以下あたりを機械翻訳したのでそれを見つつポイント列挙の方向。

  • Plug は Phoenix の HTTP レイヤの中心におり、Phoenix は Plug を front と center に配置する
  • connection lifecycle の全ての step で Plug と対話
  • Endpoint、Router、Controller などの Phoenix の中核的なコンポーネントはすべて内部的に Plug
  • Plug は Web アプリケーション間の構成可能なモジュールの仕様
  • 異る Web サーバの接続アダプタの抽象化レイヤでもある
  • Plug の基本的な考えかたは我々が操作する “接続” の概念を統一すること
  • 要求と応答がミドルウェアスタックで分離されている Rack などの他の HTTP ミドルウェアレイヤとは異なる
  • 最も単純なレベルでは Plug の仕様には二つの種類がある、function plugs と module plugs

Function Plugs

  • Plug として機能するためには手続きは接続構造体 (%Plug.Conn{}) とオプションを受け取る必要がある
  • 接続構造体も戻す必要がある
  • これらの基準を満たす機能はすべて実行される

以下は pub_headers の例。

def put_headers(conn, key_values) do
  Enum.reduce key_values, conn, fn {k, v}, conn ->
    Plug.Conn.put_resp_header(conn, to_string(k), v)
  end
end

以下はこれを使う方法。

defmodule HelloWeb.MessageController do
  use HelloWeb, :controller

  plug :put_headers, %{content_encoding: "gzip", cache_control: "max-age=3600"}
  plug :put_layout, "bare.html"

  ...
end

例えば以下な Plug が定義されていたとして

  defp authenticate(conn, _) do
    case Authenticator.find_user(conn) do
      {:ok, user} ->
        assign(conn, :user, user)
      :error ->
        conn |> put_flash(:info, "You must be logged in") |> redirect(to: "/") |> halt()
    end
  end

  defp fetch_message(conn, _) do
    case find_message(conn.params["id"]) do
      nil ->
        conn |> put_flash(:info, "That message wasn't found") |> redirect(to: "/") |> halt()
      message ->
        assign(conn, :message, message)
    end
  end

  defp authorize_message(conn, _) do
    if Authorizer.can_access?(conn.assigns[:user], conn.assigns[:message]) do
      conn
    else
      conn |> put_flash(:info, "You can't access that page") |> redirect(to: "/") |> halt()
    end
  end

以下なコントローラの実装で

defmodule HelloWeb.MessageController do
  use HelloWeb, :controller

  plug :authenticate
  plug :fetch_message
  plug :authorize_message

  def show(conn, params) do
    render(conn, :show, page: find_message(params["id"]))
  end

Plug が (例えば authenticate) :error の状態となった場合、halt() が呼び出されて以降の Plug は実行されない模様。これは良いですね。

Module Plugs

  • Module Plug はモジュール内で接続変換を定義できるようにするもう一つのタイプの Plug
  • 二つの機能を実装する必要がある
    • call/2 に渡す引数やオプションを初期化する init/1
    • 接続変換を実行する call/2 は先に見た function plug

以下が Module Plug の例。

defmodule HelloWeb.Plugs.Locale do
  import Plug.Conn

  @locales ["en", "fr", "de"]

  def init(default), do: default

  def call(%Plug.Conn{params: %{"locale" => loc}} = conn, _default) when loc in @locales do
    assign(conn, :locale, loc)
  end
  def call(conn, default), do: assign(conn, :locale, default)
end

defmodule HelloWeb.Router do
  use HelloWeb, :router

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_flash
    plug :protect_from_forgery
    plug :put_secure_browser_headers
    plug HelloWeb.Plugs.Locale, "en"
  end
  ...

引数のパタンマッチ使っています。Elixir では = はパタンマッチなので最初の call は

  • @locales にあるいずれかの要素を “locale” な params として持っている conn

であれば呼び出されてそれ以外なら下の call が呼び出される、という理解で良いのかな。

phoenix excersize (3) phoenix excersize (5)

comments powered by Disqus