ปลั๊กคือ:
กล่าวอีกนัยหนึ่ง Plug ช่วยให้คุณสร้างเว็บแอปพลิเคชันจากชิ้นเล็กๆ และรันบนเว็บเซิร์ฟเวอร์ต่างๆ Plug ถูกใช้โดยเว็บเฟรมเวิร์ก เช่น Phoenix เพื่อจัดการคำขอ การตอบกลับ และเว็บซ็อกเก็ต เอกสารนี้จะแสดงตัวอย่างระดับสูงและแนะนำโครงสร้างหลักของ Plug
ในการใช้ Plug คุณต้องมีเว็บเซิร์ฟเวอร์และการเชื่อมโยงสำหรับ Plug มีสองตัวเลือกในขณะนี้:
ใช้เว็บเซิร์ฟเวอร์ Cowboy (แบบ Erlang) โดยเพิ่มแพ็คเกจ plug_cowboy
ให้กับ mix.exs
ของคุณ :
def deps do
[
{ :plug_cowboy , "~> 2.0" }
]
end
ใช้เว็บเซิร์ฟเวอร์ Bandit (แบบ Elixir) โดยเพิ่มแพ็คเกจ bandit
ให้กับ mix.exs
ของคุณ :
def deps do
[
{ :bandit , "~> 1.0" }
]
end
นี่เป็นตัวอย่างเล็กๆ น้อยๆ ของ Hello World โดยใช้เว็บเซิร์ฟเวอร์ Cowboy:
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 มี API upgrade
การเชื่อมต่อ ซึ่งหมายความว่าจะให้การสนับสนุน WebSocket ทันที มาดูตัวอย่าง คราวนี้ใช้เว็บเซิร์ฟเวอร์ Bandit และโปรเจ็กต์ websocket_adapter
สำหรับบิต WebSocket เนื่องจากเราต้องการเส้นทางที่แตกต่างกัน เราจะใช้ 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
ซึ่งช่วยให้เราสามารถกำหนดเส้นทางที่ใช้โดยเว็บแอปพลิเคชันของเราและชุดของขั้นตอน/ปลั๊ก เช่น plug Plug.Logger
ที่จะดำเนินการกับทุกคำขอ
นอกจากนี้ อย่างที่คุณเห็น เสียบบทคัดย่อเว็บเซิร์ฟเวอร์ต่างๆ เมื่อทำการบูทแอพพลิเคชั่น ความแตกต่างระหว่างการเลือก Plug.Cowboy
หรือ Bandit
ในตอนนี้ เราได้เริ่มต้นเซิร์ฟเวอร์โดยตรงในผู้ดูแลแบบทิ้ง แต่สำหรับการปรับใช้ที่ใช้งานจริง คุณต้องการเริ่มต้นเซิร์ฟเวอร์ในแผนผังการควบคุมดูแลแอปพลิเคชัน ดูส่วนตัวจัดการภายใต้การดูแลต่อไป
บนระบบที่ใช้งานจริง คุณอาจต้องการเริ่มไปป์ไลน์ Plug ของคุณภายใต้แผนผังการดูแลของแอปพลิเคชันของคุณ เริ่มโปรเจ็กต์ Elixir ใหม่ด้วยแฟล็ก --sup
:
$ 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
ในที่สุดก็สร้าง lib/my_app/my_plug.ex
ด้วยโมดูล MyPlug
ตอนนี้ให้รัน mix run --no-halt
และมันจะเริ่มต้นแอปพลิเคชันของคุณด้วยเว็บเซิร์ฟเวอร์ที่ทำงานที่ http://localhost:4001
Plug.Conn
ในตัวอย่างสวัสดีชาวโลก เราได้กำหนดปลั๊กตัวแรกของเราชื่อ 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
สุดท้ายนี้ โปรดทราบว่าการเชื่อมต่อเป็น อินเทอร์เฟซโดยตรงกับเว็บเซิร์ฟเวอร์ที่เกี่ยวข้อง เมื่อคุณโทรไปที่ 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
plug ซึ่งแสดงโดยฟังก์ชัน match/2
ในเครื่อง (นำเข้า) จากนั้นจึงเรียกปลั๊กอิน :dispatch
ซึ่งจะรันโค้ดที่ตรงกัน
Plug มาพร้อมกับปลั๊กหลายตัวที่คุณสามารถเพิ่มลงในไปป์ไลน์ของปลั๊กเราเตอร์ได้ ซึ่งช่วยให้คุณสามารถเสียบปลั๊กบางอย่างก่อนที่เส้นทางจะตรงกันหรือก่อนที่จะส่งเส้นทางไป ตัวอย่างเช่น หากคุณต้องการเพิ่มการบันทึกลงในเราเตอร์ ให้ทำดังนี้
plug Plug.Logger
plug :match
plug :dispatch
หมายเหตุ Plug.Router
รวบรวมเส้นทางทั้งหมดของคุณให้เป็นฟังก์ชันเดียว และใช้ Erlang VM เพื่อปรับเส้นทางพื้นฐานให้เหมาะสมในการค้นหาแบบต้นไม้ แทนที่จะใช้การค้นหาเชิงเส้นที่จะจับคู่เส้นทางต่อเส้นทางแทน ซึ่งหมายความว่าการค้นหาเส้นทางนั้นรวดเร็วมากใน Plug!
นอกจากนี้ยังหมายความว่าแนะนำให้กำหนดบล็อก catch all match
ดังตัวอย่างด้านบน ไม่เช่นนั้นการกำหนดเส้นทางจะล้มเหลวโดยมีข้อผิดพลาดของคำสั่งฟังก์ชัน (เช่นเดียวกับในฟังก์ชัน Elixir ทั่วไป)
แต่ละเส้นทางจำเป็นต้องคืนการเชื่อมต่อตามข้อกำหนดของปลั๊ก ดูเอกสาร Plug.Router
สำหรับข้อมูลเพิ่มเติม
ปลั๊กมาพร้อมกับโมดูล 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 เป็นคำขอ GETPlug.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
หากคุณวางแผนที่จะบริจาคเอกสาร โปรดตรวจสอบแนวทางปฏิบัติที่ดีที่สุดในการเขียนเอกสาร
สุดท้ายนี้ จำไว้ว่าการโต้ตอบทั้งหมดในพื้นที่อย่างเป็นทางการของเราเป็นไปตามหลักจรรยาบรรณของเรา
สาขา | สนับสนุน |
---|---|
เวอร์ชัน 1.15 | แก้ไขข้อบกพร่อง |
เวอร์ชัน 1.14 | แพทช์รักษาความปลอดภัยเท่านั้น |
เวอร์ชัน 1.13 | แพทช์รักษาความปลอดภัยเท่านั้น |
เวอร์ชัน 1.12 | แพทช์รักษาความปลอดภัยเท่านั้น |
เวอร์ชัน 1.11 | แพทช์รักษาความปลอดภัยเท่านั้น |
เวอร์ชัน 1.10 | แพทช์รักษาความปลอดภัยเท่านั้น |
เวอร์ชัน 1.9 | ไม่รองรับตั้งแต่วันที่ 10/2023 |
เวอร์ชัน 1.8 | ไม่รองรับตั้งแต่ 01/2023 |
เวอร์ชัน 1.7 | ไม่รองรับตั้งแต่ 01/2022 |
เวอร์ชัน 1.6 | ไม่รองรับตั้งแต่ 01/2022 |
เวอร์ชัน 1.5 | ไม่รองรับตั้งแต่ 03/2021 |
เวอร์ชัน 1.4 | ไม่รองรับตั้งแต่วันที่ 12/2018 |
เวอร์ชัน 1.3 | ไม่รองรับตั้งแต่วันที่ 12/2018 |
เวอร์ชัน 1.2 | ไม่รองรับตั้งแต่ 06/2018 |
เวอร์ชัน 1.1 | ไม่รองรับตั้งแต่ 01/2018 |
เวอร์ชัน 1.0 | ไม่รองรับตั้งแต่ 05/2017 |
ซอร์สโค้ด Plug ได้รับการเผยแพร่ภายใต้ Apache License 2.0 ตรวจสอบไฟล์ใบอนุญาตเพื่อดูข้อมูลเพิ่มเติม