ได้เขียนไว้ที่นี่อีกที่นึง
วันนี้จะมาลองอธิบายการสร้างเว็บด้วย 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
- เพิ่ม
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
ใน 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)})
จะเห็นได้ว่า ตัวอย่างนั้นใช้การดึงค่าออกมาจาก 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))))
จะเห็นว่า 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
เอาล่ะคงเท่านี้ก่อน เพราะน่าจะยาวแล้ว
Top comments (0)