Clojure Ring เบื้องต้น

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

วันนี้จะมาลองอธิบายการสร้างเว็บด้วย Clojure โดยใช้ library ที่ชื่อว่า ring กัน
โดยตัวอย่างที่ยกมา เอามาจากหน้า wiki ของ ring ใน github
เกี่ยวกับ 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
    เอาล่ะคงเท่านี้ก่อน เพราะน่าจะยาวแล้ว

    30

    This website collects cookies to deliver better user experience

    Clojure Ring เบื้องต้น