どうも、靖宗です。
Plugの説明の記事を挟みましたが、引きつづぎRoutingの章を進めて行きます。
Pipelines
スルーしてきたpipe_through :browser
の説明です。
Plugのスタックってパイプラインに似てると思わへんか?とのこと。確かにrouter.ex
の
pipeline :browser do plug :accepts, ["html"] plug :fetch_session plug :fetch_flash plug :protect_from_forgery plug :put_secure_browser_headers end
は、接続情報をリレーして行ってる感じなので
conn |> plug :accepts, ["html"] |> plug :fetch_session |> plug :fetch_flash |> plug :protect_from_forgery |> plug :put_secure_browser_headers
みたいに見えなくも無いです。
名前からしてもイメージ的にはパイプラインなのでしょう。
作成したファイルにデフォルトで記載されているpipe_through
は:browser
と:api
があります。が、この説明をしていくまえにEndpoint plugsの解説が必要なようです。
The Endpoint Plugs
Endpointsは全Plugに放り込まれる共通の処理を担当しており、ルーティングされる前にこの共通処理を行うPlugのようです。
主に担当している機能は下記の通り。
- Plug.Static
- Phoenix.CodeReloader
- コードをリロードするやつ。ホットリロードとかその辺?
- Plug.RequestId
- 来たリクエストそれぞれにユニークなIDを割り振るやつ
- Plug.Logger
- リクエストのログを記録?するやつ
- Plug.Parsers
- Plug.MethodOverride
method
のパラメータからPOSTのリクエストをPUT、PATCH、DELETEに書き換えるやつ
- Plug.Head
- Plug.Session
- セッションをセットアップするやつ。Cookiesとかかな?
- Plug.Router
- ルーティング?
結構な量がありますが、確かにHTTPリクエストの度にこれだけの処理は要る気はします。
その辺をEndpointsがよしなにしてくれているのでしょう。ありがたや。
The :browser
and :api
Pipelines
さて、ここに来てようやく:browser
と:api
のパイプラインの説明です。
先ほどのEndpointsに加えて、ブラウザで表示する場合に必要なPlugとAPIとして活用するときに必要なPlugが別れるのでしょう。
pipeline :browser do plug :accepts, ["html"] plug :fetch_session plug :fetch_flash plug :protect_from_forgery plug :put_secure_browser_headers end pipeline :api do plug :accepts, ["json"] end
pipeline
というマクロを使ってパイプラインを定義していくようです。
その後、利用するスコープ(scope "/", HelloPhoenixWeb ...
など)でpipe_through パイプライン名
という形でパイプラインを呼び出しているようです。
結局この辺の処理もcase
などでネストしないで済むような工夫な気はしますね。
:browser
パイプラインは下記のPlugの処理が走ります。
plug :accepts, ["html"]
リクエストのフォーマットがHTMLのやつのみ受入plug :fetch_session
セッションデータ作成(EndpointのPlug.Sessionで準備したやつ)plug :fetch_flash
Flashメッセージの取り出しplug :protect_from_forgery
下の奴とセットでCSRF対策?plug :put_secure_browser_headers
一方:api
パイプラインは一個だけです。
plug :accepts, ["json"]
JSON形式のやつだけ受入
パイプラインはscope
マクロでスコープされている中だけに適応されますが、そもそもscope
マクロがない場合は全てのリクエスト処理に適応されるそうです。
また、ネストしたスコープの中で使う場合は、親スコープでpipe_through
にて適応されたパイプラインはネストした中にも影響を及ぼすそうです。これは直感的なので「せやな」ってところでしょう。
例
そんなことよりコードで理解しろ!ってことでサンプルを載せてくれてます。Phoenixプロジェクトを作成した直後のrouter.ex
に:api
のスコープを追記した感じです。
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 end pipeline :api do plug :accepts, ["json"] end scope "/", HelloWeb do pipe_through :browser get "/", PageController, :index end # Other scopes may use custom stacks. scope "/api", HelloWeb do pipe_through :api resources "/reviews", ReviewController end end
Phoenixはリクエストを受け取るとまずEndpointにリクエストを送るそうです。これは上記でも説明してますね。
次にどのPlugやパイプラインに渡すかはscope
によって判定されているようです。複数scope
がある場合は上からチェック。
ということで一番最初のscope
で判定されますが、http://localhost:400/
にアクセスすれば/
がマッチします。そのscope
ではpipe_through :browser
と書いてあるので:browser
パイプラインを通過します。
処理の内容は上記で説明したとおり、pipeline :browser
で書いたPlugが実行されます。そのあとで、PageController
の:index
が実行される、という流れです。
同様にhttp://localhost:400/api/hogehoge
など、scope "/api"
にマッチするリクエストはpipe_through :api
の記載がありますので:api
パイプラインを通過します。
scope
無しのパイプライン
もしAPIの挙動が要らなく、ブラウザでの表示のみを想定している場合は、scope
無しで下記のように書けます。
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 end pipe_through :browser get "/", HelloWeb.PageController, :index resources "/reviews", HelloWeb.ReviewController end
scope
がない場合は地に書かれたマクロが適応されるので、せやな。といったところです。
ただし、Phoenixはどちらかと言えばビューのみを担当することが無さそうなのであまりこの書き方はしない気がします。
複数のパイプライン
上記の例では:browser
か:api
の二者択一でしたが、両方適応したいときとかはどうするんでしょうか?並べる?
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 end ... scope "/reviews" do pipe_through [:browser, :review_checks, :other_great_stuff] resources "/", HelloWeb.ReviewController end end
pipe_through
にパイプライン名のリストを投げれば良いようです。
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 end ... scope "/", HelloWeb do pipe_through :browser resources "/posts", PostController end scope "/reviews", HelloWeb do pipe_through [:browser, :review_checks] resources "/", ReviewController end end
同じコントローラー使ってても認証とかチェックとか挟むときはscope
かき分けた方がいいかも。
Creating New Pipelines
パイプラインの作り方。もうサンプルで散々出てきてるので大丈夫そう。
pipeline
マクロを使います。
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 end pipeline :review_checks do plug :ensure_authenticated_user plug :ensure_user_owns_review end scope "/reviews", HelloWeb do pipe_through :review_checks resources "/", ReviewController end end
Channel Routes
Channelというものがあります。おそらくリアルタイムのチャットなどwebsocketを使う手段でしょう。
Channelの設定はlib/hello_phoenix_web/endpoint.ex
に記載があります。この箇所でwebsocketのハンドラをマウントしているようです。
defmodule HelloPhoenixWeb.Endpoint do use Phoenix.Endpoint, otp_app: :hello_phoenix socket "/socket", HelloPhoenixWeb.UserSocket, websocket: true, longpoll: false ... end
websocketを有効にするかロングポーリングを有効にするかの設定があります。webscoket使えるならロングポーリングは要らないでしょう。
先ほどマウントしたハンドラを見てみます。lib/hello_phoenix_web/channels/user_socket.ex
にファイルがあります。
defmodule HelloPhoenixWeb.UserSocket do use Phoenix.Socket ## Channels # channel "room:*", HelloPhoenixWeb.RoomChannel # Socket params are passed from the client and can # be used to verify and authenticate a user. After # verification, you can put default assigns into # the socket that will be set for all channels, ie # # {:ok, assign(socket, :user_id, verified_user_id)} # # To deny connection, return `:error`. # # See `Phoenix.Token` documentation for examples in # performing token verification on connect. def connect(_params, socket, _connect_info) do {:ok, socket} end # Socket id's are topics that allow you to identify all sockets for a given user: # # def id(socket), do: "user_socket:#{socket.assigns.user_id}" # # Would allow you to broadcast a "disconnect" event and terminate # all active sockets and channels for a given user: # # HelloPhoenixWeb.Endpoint.broadcast("user_socket:#{user.id}", "disconnect", %{}) # # Returning `nil` makes this socket anonymous. def id(_socket), do: nil end
ほぼなんも書かれてません。
コメントアウトされている箇所の
# channel "room:*", HelloPhoenixWeb.RoomChannel
でチャンネルモジュールを呼び出すようです。上記の例ではroom:*
というトピック名の通信を呼び出しているようです。
room:*
と書かれているのはroom:subtopic
というサブトピックもHelloPhoenixWeb.RoomChannel
でハンドリングするという書き方なのでしょう。
詳しくはChannelsの章があるのでその際に見ていきたいと思います。
(まだwebsocket分かってないマンなのでPhoenixでwebsocketを理解できればとは思います。)
系統立ててなかなか学習できてませんが、とりあえず進める精神で他の項目もやっていきます┗(⌒)(╬´◓ω◔`╬)(⌒)┛