iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 18
0

Plug

在繼續解釋其它的元件之前,要先來解釋一下 Plug。Plug 是 elixir 網路應用間溝通界面的規範,官方有管理一個通用的實作。如果你熟悉 Ruby 的話,你可以把它想成是跟 Rack 差不多的東西。

而跟 Rack 相比,Plug 規範的特色在於它把 HTTP request 及 HTTP response 包在一起,稱之為 connection。實作上就是一個叫 %Plug.Conn{} 的 struct 鍵值對。內容大概像是這樣:

%Plug.Conn{host: "www.example.com",
           path_info: ["bar", "baz"],
           ...}

而當我們在文件及溝通上說到「這只是一個 plug」(小寫)的時候,代表了兩種可能性: function plug 及 module plug。

function plug

function plug 顧名思義,就是一個單純的函式。這函式接收兩個參數,第一個參數就是 %Plug.Conn{},第二個參數是一個 keyword list,用來輸入其它需要的資訊。而回傳值,就是一個新的 %Plug.Conn{}

def hello_world_plug(conn, _opts) do
  conn
  |> pug_resp_content_type("text/plain")
  |> send_resp(200, "Hello world")
end

module plug

如果要處理的情況比較複雜,我們會用一整個模組來定義一個 plug。module plug 要求你實作兩個函式: init/1call/2

  • init/1 會處理接收到的參數並回傳一個鍵值對。之後每次呼叫 call/2 時,call/2 接收到的第二個參數就會是這個鍵值對。
  • call/2 就跟上面提到的 function plug 一樣,接收 connection 及 keyword list 參數,並回傳一個新的 connection。

Once again, journey of data transformation

我們之前說過的 immutability 跟資料的旅程又在這裡跟我們遇上了。既然 elixir 裡的資料都是 immutable 的,所以當 connection 進入一個 plug 後,回傳的是另一個 完全新的 connection

而整個網頁應用程式的流程,就是當我們的 server 接受了 HTTP request 後,每一步的 plug 從它接收到的 connection 裡取出它需要的資料,處理過後,把新的資料以及原本有的 connection 整合成一個新的 connection,再傳遞給下一個 plug,直到最後回傳含有完整 HTTP response 資料的 connection。


關於 plug,其實還有很多可以聊的。不過這些足夠讓我們往下走了,為了不要離題太遠,我們先回到 Phoenix 的主線上,來看看 Endpoint。

Endpoint

Endpoint 本身也是一顆 plug,而就像它的名字所代表的,是所有 HTTP request 進入我們應用程式的端點。這裡將會處理通用於所有 request 的設定及行為。來看看 lib/hello_phx_web/endpoint.ex 這張檔案:

defmodule HelloPhxWeb.Endpoint do
  use Phoenix.Endpoint, otp_app: :hello_phx

  socket "/socket", HelloPhxWeb.UserSocket

  # Serve at "/" the static files from "priv/static" directory.
  #
  # You should set gzip to true if you are running phoenix.digest
  # when deploying your static files in production.
  plug Plug.Static,
    at: "/", from: :hello_phx, gzip: false,
    only: ~w(css fonts images js favicon.ico robots.txt)

  # Code reloading can be explicitly enabled under the
  # :code_reloader configuration of your endpoint.
  if code_reloading? do
    socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket
    plug Phoenix.LiveReloader
    plug Phoenix.CodeReloader
  end

  plug Plug.RequestId
  plug Plug.Logger

  plug Plug.Parsers,
    parsers: [:urlencoded, :multipart, :json],
    pass: ["*/*"],
    json_decoder: Poison

  plug Plug.MethodOverride
  plug Plug.Head

  # The session will be stored in the cookie and signed,
  # this means its contents can be read but not tampered with.
  # Set :encryption_salt if you would also like to encrypt it.
  plug Plug.Session,
    store: :cookie,
    key: "_hello_phx_key",
    signing_salt: "2YDGYd32"

  plug HelloPhxWeb.Router

  @doc """
  Callback invoked for dynamically configuring the endpoint.

  It receives the endpoint configuration and checks if
  configuration should be loaded from the system environment.
  """
  def init(_key, config) do
    if config[:load_from_system_env] do
      port = System.get_env("PORT") || raise "expected the PORT environment variable to be set"
      {:ok, Keyword.put(config, :http, [:inet6, port: port])}
    else
      {:ok, config}
    end
  end
end

use

在模組宣告的下一行,寫了 use Phoenix.Endpoint。就是應用了我們之前提到的 use 機制,把 endpoint 需要的函式放進這個模組裡。一旦有了這行,我們就會說這個模組是一個 endpoint。另外可以看到這個 use 的模組名稱是 Phoenix.Endpoint,意思是該模組是 Phoenix 內建的,比較沒有我們下手亂改的空間。所以這個 use 在幫我們把寫好的 init/1call/2 嵌到此模組裡後,還在此模組最下方寫好一個 init/2,當我們需要調整 endpoint 的行為時,可以自行修改此函式。

socket

再接下來是 socket,表示從這裡開始,所有以 /socket 開頭的 HTTP request,都會被 HelloPhxWeb.UserSocket 這個模組接管。用於處理與 web socket 相關的行為。

plug

接下來看到的是一系列的 plug Module。與 socket 無關的 HTTP request, 將會依序流過這些指定的 plug,並進行相應的處理,將結果放到 connection 裡向下傳遞。

例如我們可以看到 plug Plug.Static 裡是否要 gzip 靜態檔案,plug Plug.Parsers 裡設定 JSON decoder,及 plug Plug.Session 裡設定用 cookie 來儲存 cookie 等等。

而這一長串的 plug 的最後一個,就是 plug HelloPhxWeb.Router。當 HTTP request 走完第一關 endpoint 後,接下來的事將會交給我們的 HelloPhxWeb.Router 模組,而這就是我們下一篇的主題了。

重點回顧

  • Plug 是 elixir 泛用的網路應用溝通界面的規範。官方有管理一個目前大家廣泛採用的實作
  • plug 分為 function plug 及 module plug,前者就是一個函式,後者是需要實做兩個函式的模組
  • plug 接收 connection,也就是 %Plug.Conn{} 及其它參數,並回傳一個新的 connection
  • Endpoint 是一個 plug, 是所有 HTTP request 進入我們程式的端點。會處理與每一個 request 都適用的設定及行為
  • 下一步會交給 router

Happy hacking! 明天見


上一篇
資料夾結構與 CRUD
下一篇
Router.part_1
系列文
函數式編程: 從 Elixir & Phoenix 入門。31

尚未有邦友留言

立即登入留言