30
Clojure Ring เบื้องต้น
ได้เขียนไว้ที่นี่อีกที่นึง
วันนี้จะมาลองอธิบายการสร้างเว็บด้วย Clojure โดยใช้ library ที่ชื่อว่า
โดยตัวอย่างที่ยกมา เอามาจากหน้า wiki ของ ring ใน github
ring
กันโดยตัวอย่างที่ยกมา เอามาจากหน้า wiki ของ ring ใน github
ต่อไปเราจะเริ่มสร้างเว็บแบบง่ายๆ กัน
$ lein new hello-world
$ cd hello-world
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})
ดาวน์โหลด dependencies:
$ lein deps
ในไฟล์
src/hello-world/core.clj
ให้สร้าง handler อย่างง่ายขึ้นมา (handler ก็คือฟังก์ชัน)(ns hello-world.core)
(defn handler [request]
{:status 200
:headers {"Content-Type" "text/html"}
:body "Hello World"})
จากนั้นให้ start REPL ขึ้นมาโดยใช้ Leiningen
$ lein repl
หลังจาก REPL รันขึ้นมาแล้ว, รัน Jetty ขึ้นมาเพื่อใช้ handler ที่เราสร้างขึ้น (Jetty คือ web server ตัวนึง)
=> (use 'ring.adapter.jetty)
=> (use 'hello-world.core)
=> (run-jetty handler {:port 3000
:join? false)
web server จะถูกรันขึ้นมา โดยตอนนี้สามารถเปิดเว็บเบราว์เซอร์ขึ้นมาแล้วเปิดหน้าเว็บ http://localhost:3000/ เพื่อดูผลลัพธ์

เอาล่ะ ตอนนี้เราก็ได้เว็บง่ายๆ โง่ๆ มาอันนึงที่ response กลับมาทุกครั้งว่า Hello World
เรามาดู concept ที่ใช้ใน
เรามาดู concept ที่ใช้ใน
ring
กัน ว่ามีไรบ้างใน ring จะมี concepts หลักๆ อยู่ 4 อย่าง
Handlers ก็คือฟังก์ชันนี่แหละ ที่คอยรับ HTTP request --> process request --> ส่ง response กลับมา
, โดย request กับ response ใน handler เนี่ย จะอยู่ในรูปแบบ
, โดย 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)})

จะเห็นได้ว่า ตัวอย่างนั้นใช้การดึงค่าออกมาจาก request map โดยใช้คีย์
:remote-addr
HTTP request จะอยู่ในรูปแบบ
เช่น สร้างมาจาก middleware
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 bodyResponse 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 คือส่วนที่ทำให้เพิ่มความสามารถหรือทำอะไรบางอย่างเพิ่มเติมกับ handler ได้ (ในเอกสารเรียกว่าเป็น higher-level function)
middleware นั้น รับ handler เป็น input และ output ก็เป็น handler เช่นกัน
แต่ข้างในจะมีการเพิ่มเติมข้อมูลลงไป เช่น เปลี่ยน header หรืออะไรก็แล้วแต่
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))))
จะเห็นว่า middleware นั้น return ออกมาเป็นฟังก์ชัน หรือ handler นี่แหละ
ข้างในจะมีการเปลี่ยน header
ข้างในจะมีการเปลี่ยน header
"Content-Type"
ให้เป็นตามตัวแปร content-type
ตัวอย่างการใช้ middleware
(def app
(wrap-content-type handler "text/html"))
จากตัวอย่างแปลว่า มีการเปลี่ยน header
"Content-Type"
ให้กลายเป็นประเภท "text/html"
โดยปกติแล้ว เราสามารถซ้อน middleware ต่อกันไปได้เรื่อยๆ (เค้าเรียกว่า chain อ่ะนะ) กี่ชั้นก็ว่าไป
Clojure ก็จะมี threading macro (
Clojure ก็จะมี threading macro (
->
) syntax เพื่อช่วยในการ chain middleware ต่างๆ เช่น(def app
(-> handler
(wrap-content-type "text/html")
(wrap-keyword-params)
(wrap-params)))
middleware นั้นถูกใช้เป็นเรื่องปกติใน Ring, ไม่ว่าจะช่วยในเรื่องการจัดการ parameters, sessions หรือ อัพโหลดไฟล์
ทั้งหมดทั้งมวลนี้ถูกจัดการโดยใช้ middleware ของ Ring
ทั้งหมดทั้งมวลนี้ถูกจัดการโดยใช้ middleware ของ Ring
เอาล่ะคงเท่านี้ก่อน เพราะน่าจะยาวแล้ว