Plug เป็น Elixir library ที่ช่วยให้เราเขียนโค้ดจัดการ HTTP request แล้วส่ง response กลับไปให้ client ที่เรียกมาได้
สิ่งที่ชอบใน Plug คือการออกแบบที่แยกส่วนที่จัดการ HTTP connection ออกไปโดยกำหนด spec Adapter behavior ขึ้นมาให้มี function callback ตามที่ต้องการ แล้วค่อยสร้าง Adapter module มา implements callback ให้ต่อ HTTP connection จริงๆได้ ซึ่ง Adapter ที่ใช้กันส่วนใหญ่ก็คือ Plug.Cowboy ที่ด้านล่างใช้ cowboy library นั่นเอง
ส่วนที่สำคัญและทำให้การจัดการ request/response ง่ายมากเวลาใช้ Plug คือ ส่วนจัดการ request/response module ที่มี spec แบบตามนี้ ตัวอย่างเช่น
defmodule MyPlug do
def init(opts) do
opts
end
def call(conn, _opts) do
conn
|> Plug.Conn.put_resp_content_type("text/plain")
|> Plug.Conn.send_resp(200, "Hello")
end
end
คือเป็น module ที่ต้องมี function init/1
ที่รับค่า option แล้วสามารถ process options แล้ว return options ที่ process แล้วออกไป
และต้องมี function call/2
ที่ค่าแรกจะรับ conn ซึ่งเป็นค่า Plug.Conn
กับ options ที่ได้จาก init/1
Plug.Conn
Plug.Conn เป็น struct ที่สำคัญในกลไกการทำงานของ Plug เพราะเป็น struct ที่รวบรวม fields ต่างๆสำหรับเก็บข้อมูล request/response เอาไว้ และ Plug.Conn ก็ยังเป็น module ที่รวบรวม function ที่เอาไว้จัดการ fields ต่างๆของ Plug.Conn เอาไว้ หลักๆแล้วเราจัดการกับ HTTP ผ่าน function ของ Plug.Conn นั่นเอง เช่นตามตัวอย่างเราสามารถกำหนด response content type header ผ่านทาง Plug.Conn.put_resp_content_type/2
และส่ง response body ผ่านทาง Plug.Conn.send_resp(200, "Hello")
นั่นเอง
Plug pipeline
อีก concept คือ Plug pipeline คือการที่เราสร้าง Plug ย่อยๆแล้วเอามาร้อยเรียงกัน ภาษาหรือ library อื่นอาจจะเรียกแบบนี้ว่า middleware
ตัว plug pipeline เองมันก็คือ Plug module เหมือนกันนี่แหละแต่เอา Plug ที่มีอยู่แล้วมาประกอบกัน สำหรับ library Plug มี module Plug.Builder ช่วยให้เราประกอบ pipeline ได้ง่ายๆด้วย macro plug/2
เช่น
defmodule MyHelloPlug do
def init(opts) do
opts
end
def call(conn, _opts) do
conn
|> Plug.Conn.put_resp_content_type("text/plain")
|> Plug.Conn.send_resp(200, "Hello")
end
end
defmodule LogPlug do
def init(opts) do
opts
end
def call(conn, _opts) do
Time.utc_now()
|> IO.puts()
conn
end
end
defmodule MyPlug do
use Plug.Builder
plug(LogPlug)
plug(MyHelloPlug)
end
เวลาทำงานก็เรียก แต่ละ Plug ตามลำดับที่เราเรียก plug/2
นั่นเอง
นอกจากนั้นถ้าสร้าง Plug ผ่าน Plug.Builder เรายัง implements plug ผ่าน function ได้เลยไม่ต้องใช้ Module คือขอให้เป็น function ที่รับค่า conn กับ options แบบเดียวกับ call
function นั่นเอง ดังนั้นโค้ดเมื่อกี้เขียนใหม่ได้เป็น
defmodule MyPlug do
use Plug.Builder
plug(:hello_plug)
plug(:log_plug)
def hello_plug(conn, _opts) do
conn
|> Plug.Conn.put_resp_content_type("text/plain")
|> Plug.Conn.send_resp(200, "Hello")
end
def log_plug(conn, _opts) do
Time.utc_now()
|> IO.puts()
conn
end
end
สิ่งที่ได้เรียนรู้ก็คือเราจะเห็นการออกแบบ แบบนี้ของ Elixir อีกครั้งใน library Plug คือการพยายามผลักกลไก low level ออกไปเป็น module ใหม่แล้วทำให้ส่วนหลักที่เป็น logic ที่เราต้อง implements นั้นใช้งานง่ายๆ การจัดการ HTTP ก็แค่การพยายาม transforms ข้อมูลใน Plug.Conn ต่อกันไปเรื่อยๆใน Plug pipeline
การทดสอบก็จะง่ายเพราะก็เป็นการเช็คของที่อยู่ใน Plug.Conn นั่นเอง หรือแม้แต่การสร้าง Test Adapter เอาไว้ทดสอบก็ยังได้
Top comments (0)