DEV Community

Phondanai
Phondanai

Posted on • Edited on

Clojure Ring เบื้องต้น

ได้เขียนไว้ที่นี่อีกที่นึง

วันนี้จะมาลองอธิบายการสร้างเว็บด้วย Clojure โดยใช้ library ที่ชื่อว่า ring กัน
โดยตัวอย่างที่ยกมา เอามาจากหน้า wiki ของ ring ใน github

เกี่ยวกับ Ring

ring คือ

  • Library ที่ใช้กันแพร่หลาย สำหรับพัฒนาเว็บโดยใช้ภาษา Clojure
  • สามารถสร้างเว็บและคอมไพล์ให้เป็น Java servlet ได้
  • สร้าง package Java war เพื่อเอาไป deploy ต่อได้ง่าย

สิ่งที่ต้องมี

hello ring

ต่อไปเราจะเริ่มสร้างเว็บแบบง่ายๆ กัน

  • สร้าง project ใหม่ด้วย Leiningen:
$ lein new hello-world
$ cd hello-world
Enter fullscreen mode Exit fullscreen mode
  • เพิ่ม ring-core และ ring-jetty-adapter ลงใน dependencies ในไฟล์ project.clj
(defproject hello-world "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0"
            :url "https://www.eclipse.org/legal/epl-2.0/"}
  :dependencies [[org.clojure/clojure "1.10.0"]
                 [ring/ring-core "1.8.2"]
                 [ring/ring-jetty-adapter "1.8.2"]]
  :repl-options {:init-ns hello-world.core})
Enter fullscreen mode Exit fullscreen mode

ดาวน์โหลด dependencies:

$ lein deps
Enter fullscreen mode Exit fullscreen mode

ในไฟล์ src/hello-world/core.clj ให้สร้าง handler อย่างง่ายขึ้นมา (handler ก็คือฟังก์ชัน)

(ns hello-world.core)

(defn handler [request]
  {:status 200
   :headers {"Content-Type" "text/html"}
   :body "Hello World"})
Enter fullscreen mode Exit fullscreen mode

จากนั้นให้ start REPL ขึ้นมาโดยใช้ Leiningen

$ lein repl
Enter fullscreen mode Exit fullscreen mode

หลังจาก REPL รันขึ้นมาแล้ว, รัน Jetty ขึ้นมาเพื่อใช้ handler ที่เราสร้างขึ้น (Jetty คือ web server ตัวนึง)

=> (use 'ring.adapter.jetty)
=> (use 'hello-world.core)
=> (run-jetty handler {:port 3000
                       :join? false)
Enter fullscreen mode Exit fullscreen mode

web server จะถูกรันขึ้นมา โดยตอนนี้สามารถเปิดเว็บเบราว์เซอร์ขึ้นมาแล้วเปิดหน้าเว็บ http://localhost:3000/ เพื่อดูผลลัพธ์

result

เอาล่ะ ตอนนี้เราก็ได้เว็บง่ายๆ โง่ๆ มาอันนึงที่ response กลับมาทุกครั้งว่า Hello World
เรามาดู concept ที่ใช้ใน ring กัน ว่ามีไรบ้าง

Ring concepts

ใน ring จะมี concepts หลักๆ อยู่ 4 อย่าง

  • Handler
  • Request
  • Response
  • Middleware

Handlers

Handlers ก็คือฟังก์ชันนี่แหละ ที่คอยรับ HTTP request --> process request --> ส่ง response กลับมา
, โดย request กับ response ใน handler เนี่ย จะอยู่ในรูปแบบ Clojure map และ ring จะจัดการที่เหลือให้ (เช่น แปลงเป็น HTTP reponse string)

ตัวอย่าง handler ที่โชว์ ip address ของ client ที่ส่ง request เข้ามา

(defn what-is-my-ip [request]
  {:status 200
   :headers {"Content-Type" "text/plain"}
   :body (:remote-addr request)})
Enter fullscreen mode Exit fullscreen mode

remote address

จะเห็นได้ว่า ตัวอย่างนั้นใช้การดึงค่าออกมาจาก request map โดยใช้คีย์ :remote-addr

Requests

HTTP request จะอยู่ในรูปแบบ Clojure map ซึ่งก็จะมี keys มาตรฐานติดมา และก็ยังสามารถมี key ที่ custom เพิ่มเองได้
เช่น สร้างมาจาก middleware

key มาตรฐาน

  • :server-port - พอร์ตของ server ที่ request เรียกเข้ามา
  • :server-name - ชื่อของ server ที่ request เรียกเข้ามา
  • :remote-addr - Ip address ของ client request ที่เรียกเข้ามา <-- ที่ใช้ในตัวอย่างข้างบน
  • :uri - URI หรือ path ที่ request เรียกเข้ามา
  • :query-string - ตรงตัวเลยคือ query-string
  • :scheme protocol - ที่ใช้ request เช่น :http หรือ :https
  • :request-method - request method ที่เรียกมา เช่น :get, :head, :options, :put, :post หรือ :delete
  • :headers - Clojure map ของชื่อ header ต่างๆ
  • :body - InputStream จาก request body

Responses

Response map นั้น จะถูกสร้างขึ้นมาโดย handler ซึ่งมีทั้งหมด 3 key หลัก:

  • :status HTTP status code เช่น 200, 302, 404
  • :headers Clojure map ของ ชื่อ HTTP header
  • :body ตรงนี้จะเป็น response ที่จะตอบกลับไปยัง request, body ใน ring จะรองรับข้อมูลอยู่ 4 ประเภท คือ
    • string ส่ง string ตรงๆ ไปยัง client
    • ISeq แต่ละสมาชิกใน seq จะถูกส่งไปเป็น string
    • File เนื้อหาในไฟล์จะถูกส่งไปยัง client
    • InputStream เนื้อหาใน stream จะถูกส่งไปยัง client จนกว่า stream จะปิด

Middleware

middleware คือส่วนที่ทำให้เพิ่มความสามารถหรือทำอะไรบางอย่างเพิ่มเติมกับ handler ได้ (ในเอกสารเรียกว่าเป็น higher-level function)
middleware นั้น รับ handler เป็น input และ output ก็เป็น handler เช่นกัน
แต่ข้างในจะมีการเพิ่มเติมข้อมูลลงไป เช่น เปลี่ยน header หรืออะไรก็แล้วแต่

ตัวอย่าง middleware

(defn wrap-content-type [handler content-type]
  (fn [request]
    (let [response (handler request)]
      (assoc-in response [:headers "Content-Type"] content-type))))
Enter fullscreen mode Exit fullscreen mode

จะเห็นว่า middleware นั้น return ออกมาเป็นฟังก์ชัน หรือ handler นี่แหละ
ข้างในจะมีการเปลี่ยน header "Content-Type" ให้เป็นตามตัวแปร content-type

ตัวอย่างการใช้ middleware

(def app
  (wrap-content-type handler "text/html"))
Enter fullscreen mode Exit fullscreen mode

จากตัวอย่างแปลว่า มีการเปลี่ยน header "Content-Type" ให้กลายเป็นประเภท "text/html"

โดยปกติแล้ว เราสามารถซ้อน middleware ต่อกันไปได้เรื่อยๆ (เค้าเรียกว่า chain อ่ะนะ) กี่ชั้นก็ว่าไป
Clojure ก็จะมี threading macro (->) syntax เพื่อช่วยในการ chain middleware ต่างๆ เช่น

(def app
  (-> handler
      (wrap-content-type "text/html")
      (wrap-keyword-params)
      (wrap-params)))
Enter fullscreen mode Exit fullscreen mode

middleware นั้นถูกใช้เป็นเรื่องปกติใน Ring, ไม่ว่าจะช่วยในเรื่องการจัดการ parameters, sessions หรือ อัพโหลดไฟล์
ทั้งหมดทั้งมวลนี้ถูกจัดการโดยใช้ middleware ของ Ring

เอาล่ะคงเท่านี้ก่อน เพราะน่าจะยาวแล้ว

Top comments (0)