Elixir: จัดการ HTTP request ด้วย Plug
Tags: elixir
Published: 2020-11-30 16:21:53

[Plug](https://hexdocs.pm/plug) เป็น Elixir library ที่ช่วยให้เราเขียนโค้ดจัดการ HTTP request แล้วส่ง response กลับไปให้ client ที่เรียกมาได้

สิ่งที่ชอบใน Plug คือการออกแบบที่แยกส่วนที่จัดการ HTTP connection ออกไปโดยกำหนด spec Adapter behavior ขึ้นมาให้มี function callback ตามที่ต้องการ แล้วค่อยสร้าง Adapter module มา implements callback ให้ต่อ HTTP connection จริงๆได้ ซึ่ง Adapter ที่ใช้กันส่วนใหญ่ก็คือ [Plug.Cowboy](https://hexdocs.pm/plug_cowboy/Plug.Cowboy.html) ที่ด้านล่างใช้ [cowboy](https://github.com/ninenines/cowboy) library นั่นเอง

ส่วนที่สำคัญและทำให้การจัดการ request/response ง่ายมากเวลาใช้ Plug คือ ส่วนจัดการ request/response module ที่มี spec แบบตามนี้ ตัวอย่างเช่น

```elixir
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](https://hexdocs.pm/plug/Plug.Conn.html) เป็น 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](https://hexdocs.pm/plug/Plug.Builder.html) ช่วยให้เราประกอบ pipeline ได้ง่ายๆด้วย macro `plug/2` เช่น

```elixir
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 นั่นเอง ดังนั้นโค้ดเมื่อกี้เขียนใหม่ได้เป็น

```elixir
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 เอาไว้ทดสอบก็ยังได้