插頭是:
換句話說,Plug 允許您從小塊建立 Web 應用程式並在不同的 Web 伺服器上執行它們。 Phoenix 等 Web 框架使用 Plug 來管理請求、回應和 Websocket。本文檔將展示一些進階範例並介紹該外掛程式的主要構建塊。
為了使用 Plug,您需要一個 Web 伺服器及其與 Plug 的綁定。目前有兩種選擇:
透過將plug_cowboy
套件新增至mix.exs
來使用 Cowboy Web 伺服器(基於 Erlang):
def deps do
[
{ :plug_cowboy , "~> 2.0" }
]
end
透過將bandit
套件加入mix.exs
來使用 Bandit 網路伺服器(基於 Elixir):
def deps do
[
{ :bandit , "~> 1.0" }
]
end
這是一個最小的 hello world 範例,使用 Cowboy Web 伺服器:
Mix . install ( [ :plug , :plug_cowboy ] )
defmodule MyPlug do
import Plug.Conn
def init ( options ) do
# initialize options
options
end
def call ( conn , _opts ) do
conn
|> put_resp_content_type ( "text/plain" )
|> send_resp ( 200 , "Hello world" )
end
end
require Logger
webserver = { Plug.Cowboy , plug: MyPlug , scheme: :http , options: [ port: 4000 ] }
{ :ok , _ } = Supervisor . start_link ( [ webserver ] , strategy: :one_for_one )
Logger . info ( "Plug now running on localhost:4000" )
Process . sleep ( :infinity )
將該片段儲存到檔案中並將其作為elixir hello_world.exs
執行。參觀 http://localhost:4000/ 你應該會受到歡迎!
在上面的範例中,我們編寫了第一個模組plug ,稱為MyPlug
。模組插件必須定義init/1
函數和call/2
函數。 call/2
透過連線和init/1
回傳的選項來呼叫。
Plug v1.14 包含連線upgrade
API,這表示它提供開箱即用的 WebSocket 支援。讓我們來看一個範例,這次使用 Bandit Web 伺服器和 WebSocket 位元的websocket_adapter
專案。由於我們需要不同的路由,因此我們將使用內建的Plug.Router
:
Mix . install ( [ :bandit , :websock_adapter ] )
defmodule EchoServer do
def init ( options ) do
{ :ok , options }
end
def handle_in ( { "ping" , [ opcode: :text ] } , state ) do
{ :reply , :ok , { :text , "pong" } , state }
end
def terminate ( :timeout , state ) do
{ :ok , state }
end
end
defmodule Router do
use Plug.Router
plug Plug.Logger
plug :match
plug :dispatch
get "/" do
send_resp ( conn , 200 , """
Use the JavaScript console to interact using websockets
sock = new WebSocket("ws://localhost:4000/websocket")
sock.addEventListener("message", console.log)
sock.addEventListener("open", () => sock.send("ping"))
""" )
end
get "/websocket" do
conn
|> WebSockAdapter . upgrade ( EchoServer , [ ] , timeout: 60_000 )
|> halt ( )
end
match _ do
send_resp ( conn , 404 , "not found" )
end
end
require Logger
webserver = { Bandit , plug: Router , scheme: :http , port: 4000 }
{ :ok , _ } = Supervisor . start_link ( [ webserver ] , strategy: :one_for_one )
Logger . info ( "Plug now running on localhost:4000" )
Process . sleep ( :infinity )
將該程式碼片段儲存到檔案中並將其作為elixir websockets.exs
執行。造訪 http://localhost:4000/,您應該會在瀏覽器控制台中看到訊息。
這次,我們使用了Plug.Router
,它允許我們定義 Web 應用程式使用的路由以及要在每個請求上執行的一系列步驟/插件,例如plug Plug.Logger
。
此外,正如您所看到的,Plug 抽象化了不同的網頁伺服器。啟動應用程式時,區別在於選擇Plug.Cowboy
或Bandit
。
目前,我們直接在一次性管理程式中啟動伺服器,但對於生產部署,您希望在應用程式管理樹中啟動它們。請參閱接下來的受監督處理程序部分。
在生產系統上,您可能希望在應用程式的監督樹下啟動 Plug 管道。使用--sup
標誌啟動一個新的 Elixir 專案:
$ mix new my_app --sup
新增:plug_cowboy
(或:bandit
)作為mix.exs
的依賴項:
def deps do
[
{ :plug_cowboy , "~> 2.0" }
]
end
現在更新lib/my_app/application.ex
如下:
defmodule MyApp.Application do
# See https://hexdocs.pm/elixir/Application.html
# for more information on OTP Applications
@ moduledoc false
use Application
def start ( _type , _args ) do
# List all child processes to be supervised
children = [
{ Plug.Cowboy , scheme: :http , plug: MyPlug , options: [ port: 4001 ] }
]
# See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options
opts = [ strategy: :one_for_one , name: MyApp.Supervisor ]
Supervisor . start_link ( children , opts )
end
end
最後使用MyPlug
模組建立lib/my_app/my_plug.ex
。
現在運行mix run --no-halt
,它將透過在 http://localhost:4001 上執行的 Web 伺服器啟動您的應用程式。
Plug.Conn
結構在 hello world 範例中,我們定義了第一個名為MyPlug
的插件。插頭有兩種類型,模組插頭和功能插頭。
模組外掛實作init/1
函數來初始化選項和call/2
函數,該函數接收連接和初始化選項並傳回連接:
defmodule MyPlug do
def init ( [ ] ) , do: false
def call ( conn , _opts ) , do: conn
end
函數插頭接受連接、一組選項作為參數,並傳回連接:
def hello_world_plug ( conn , _opts ) do
conn
|> put_resp_content_type ( "text/plain" )
|> send_resp ( 200 , "Hello world" )
end
連接由%Plug.Conn{}
結構表示:
% Plug.Conn {
host: "www.example.com" ,
path_info: [ "bar" , "baz" ] ,
...
}
資料可以直接從連線讀取,也可以進行模式匹配。操作連接通常是透過使用Plug.Conn
模組中定義的函數來進行的。在我們的範例中, put_resp_content_type/2
和send_resp/3
皆在Plug.Conn
中定義。
請記住,與 Elixir 中的其他所有內容一樣,連接是不可變的,因此每次操作都會傳回連接的新副本:
conn = put_resp_content_type ( conn , "text/plain" )
conn = send_resp ( conn , 200 , "ok" )
conn
最後,請記住,連線是到底層 Web 伺服器的直接介面。當你呼叫上面的send_resp/3
時,它會立即將給定的狀態和正文傳回客戶端。這使得串流媒體等功能的使用變得輕而易舉。
Plug.Router
為了編寫一個根據傳入請求的路徑和方法進行調度的「路由器」插件,Plug 提供了Plug.Router
:
defmodule MyRouter do
use Plug.Router
plug :match
plug :dispatch
get "/hello" do
send_resp ( conn , 200 , "world" )
end
forward "/users" , to: UsersRouter
match _ do
send_resp ( conn , 404 , "oops" )
end
end
路由器是一個插頭。不僅如此:它還包含自己的插頭管道。上面的例子說,當呼叫路由器時,它將呼叫:match
插件,由本地(導入的) match/2
函數表示,然後呼叫:dispatch
插件來執行匹配的程式碼。
Plug 附帶了許多插件,您可以將它們添加到路由器插件管道中,從而允許您在路由匹配之前或將路由分派到之前插入某些內容。例如,如果您想要為路由器新增日誌記錄,只需執行以下操作:
plug Plug.Logger
plug :match
plug :dispatch
注意Plug.Router
將所有路由編譯為單一函數,並依賴 Erlang VM 將底層路由最佳化為樹查找,而不是匹配每條路由的線性查找。這意味著 Plug! 中的路由查找速度非常快!
這也意味著建議像上面的範例一樣定義一個 catch all match
區塊,否則路由會失敗並出現函數子句錯誤(就像在任何常規 Elixir 函數中一樣)。
每個路由都需要根據插頭規範返回連接。有關更多信息,請參閱Plug.Router
文件。
Plug 隨附一個Plug.Test
模組,可以輕鬆測試您的插頭。以下是我們如何從上面測試路由器(或任何其他插頭):
defmodule MyPlugTest do
use ExUnit.Case , async: true
use Plug.Test
@ opts MyRouter . init ( [ ] )
test "returns hello world" do
# Create a test connection
conn = conn ( :get , "/hello" )
# Invoke the plug
conn = MyRouter . call ( conn , @ opts )
# Assert the response and status
assert conn . state == :sent
assert conn . status == 200
assert conn . resp_body == "world"
end
end
該專案旨在提供可以跨應用程式重複使用的不同插頭:
Plug.BasicAuth
- 提供基本 HTTP 驗證;Plug.CSRFProtection
- 為您的應用程式新增跨網站請求偽造保護。如果您使用Plug.Session
通常需要;Plug.Head
- 將 HEAD 請求轉換為 GET 請求;Plug.Logger
- 記錄請求;Plug.MethodOverride
- 使用請求參數中指定的方法覆寫請求方法;Plug.Parsers
- 負責解析給定內容類型的請求主體;Plug.RequestId
- 設定要在日誌中使用的請求 ID;Plug.RewriteOn
- 從x-forwarded-*
標頭重寫請求的主機/連接埠/協定;Plug.Session
- 處理會話管理和儲存;Plug.SSL
- 透過 SSL 強制執行請求;Plug.Static
- 提供靜態檔案;Plug.Telemetry
- 使用:telemetry
事件來偵測插頭管道;您可以在我們的文件中詳細了解它們。
使用Plug.Router
或Plug.Builder
幫助開發後可以使用的模組:
Plug.Debugger
- 每次請求失敗時都會顯示一個有用的偵錯頁面;Plug.ErrorHandler
- 允許開發人員在崩潰時自訂錯誤頁面,而不是發送空白頁面; 我們歡迎大家為 Plug 做出貢獻並幫助我們解決現有問題!
使用問題追蹤器來報告錯誤或功能請求。當您準備好做出貢獻時,請打開拉取請求。提交拉取請求時,您不應更新CHANGELOG.md
。
如果您打算貢獻文檔,請查看我們編寫文檔的最佳實務。
最後,請記住我們官方空間中的所有互動均遵循我們的行為準則。
分支 | 支援 |
---|---|
v1.15 | 錯誤修復 |
v1.14 | 僅安全補丁 |
v1.13 | 僅安全補丁 |
v1.12 | 僅安全補丁 |
v1.11 | 僅安全補丁 |
v1.10 | 僅安全補丁 |
v1.9 | 從 10/2023 起不再支持 |
v1.8 | 從 01/2023 起不再支持 |
v1.7 | 從 01/2022 起不再支持 |
v1.6 | 從 01/2022 起不再支持 |
v1.5 | 從 03/2021 起不再支持 |
v1.4 | 從 12/2018 起不再支持 |
v1.3 | 從 12/2018 起不再支持 |
v1.2 | 從 06/2018 起不再支持 |
v1.1 | 從 01/2018 起不再支持 |
v1.0 | 從 05/2017 起不再支持 |
插件原始碼在 Apache License 2.0 下發布。檢查許可證文件以獲取更多資訊。