วันนี้จะมาลองอธิบายการสร้างเว็บด้วย Clojure โดยใช้ library ที่ชื่อว่า ring
กัน
โดยตัวอย่างที่ยกมา เอามาจากหน้า wiki ของ ring ใน github
ring คือ
ต่อไปเราจะเริ่มสร้างเว็บแบบง่ายๆ กัน
$ 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 ที่ใช้ใน ring
กัน ว่ามีไรบ้าง
ใน ring จะมี concepts หลักๆ อยู่ 4 อย่าง
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)})
จะเห็นได้ว่า ตัวอย่างนั้นใช้การดึงค่าออกมาจาก request map โดยใช้คีย์ :remote-addr
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 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 ตรงๆ ไปยัง clientISeq
แต่ละสมาชิกใน seq จะถูกส่งไปเป็น stringFile
เนื้อหาในไฟล์จะถูกส่งไปยัง clientInputStream
เนื้อหาใน stream จะถูกส่งไปยัง client จนกว่า stream จะปิด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))))
จะเห็นว่า middleware นั้น return ออกมาเป็นฟังก์ชัน หรือ handler นี่แหละ
ข้างในจะมีการเปลี่ยน header "Content-Type"
ให้เป็นตามตัวแปร content-type
ตัวอย่างการใช้ middleware
(def app
(wrap-content-type handler "text/html"))
จากตัวอย่างแปลว่า มีการเปลี่ยน 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)))
middleware นั้นถูกใช้เป็นเรื่องปกติใน Ring, ไม่ว่าจะช่วยในเรื่องการจัดการ parameters, sessions หรือ อัพโหลดไฟล์ ทั้งหมดทั้งมวลนี้ถูกจัดการโดยใช้ middleware ของ Ring
เอาล่ะคงเท่านี้ก่อน เพราะน่าจะยาวแล้ว
New comment
Reply {{ formatEllipsisAuthor(replyComment.author) }}
Write the first comment
{{ comments.length }} comments