Clojure Ring เบื้องต้น
Tags: clojure
Published: 2021-06-29 09:16:43

---
title: Clojure Ring เบื้องต้น
published: true
description: ทดลองอธิบายการใช้ Ring
tags: clojure
//cover_image: https://upload.wikimedia.org/wikipedia/commons/thumb/3/3b/Function_machine2.svg/191px-Function_machine2.svg.png
---

>ได้เขียนไว้[ที่นี่](https://blog.badbugs.cc/posts-output/2021-06-01-clojure-ring-1/)อีกที่นึง

วันนี้จะมาลองอธิบายการสร้างเว็บด้วย Clojure โดยใช้ library ที่ชื่อว่า `ring` กัน
โดยตัวอย่างที่ยกมา เอามาจากหน้า [wiki ของ ring ใน github](https://github.com/ring-clojure/ring/wiki)

## เกี่ยวกับ Ring
[ring](https://github.com/ring-clojure/ring) คือ
 - Library ที่ใช้กันแพร่หลาย สำหรับพัฒนาเว็บโดยใช้ภาษา Clojure
 - สามารถสร้างเว็บและคอมไพล์ให้เป็น Java servlet ได้
 - สร้าง package Java war เพื่อเอาไป deploy ต่อได้ง่าย

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

- [Clojure][1] 
- [Leiningen][2]

[1]: https://clojure.org
[2]: https://github.com/technomancy/leiningen

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

- สร้าง project ใหม่ด้วย Leiningen:

```bash
$ lein new hello-world
$ cd hello-world
```

- เพิ่ม `ring-core` และ `ring-jetty-adapter` ลงใน dependencies ในไฟล์ `project.clj`


```clojure
(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:

```bash
$ lein deps
```

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

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

จากนั้นให้ start REPL ขึ้นมาโดยใช้ Leiningen
```bash
$ lein repl
```

หลังจาก REPL รันขึ้นมาแล้ว, รัน Jetty ขึ้นมาเพื่อใช้ handler ที่เราสร้างขึ้น ([Jetty](https://www.eclipse.org/jetty/) คือ web server ตัวนึง)
```clojure
=> (use 'ring.adapter.jetty)
=> (use 'hello-world.core)
=> (run-jetty handler {:port 3000
                       :join? false)
```

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

![result](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6q137voj2ic1xcscx4tj.png)


เอาล่ะ ตอนนี้เราก็ได้เว็บง่ายๆ โง่ๆ มาอันนึงที่ 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 เข้ามา

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

![remote address](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g80sefp0e6194299teh2.png)


จะเห็นได้ว่า ตัวอย่างนั้นใช้การดึงค่าออกมาจาก 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

```clojure
(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

```clojure
(def app
  (wrap-content-type handler "text/html"))
```

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

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

```clojure
(def app
  (-> handler
      (wrap-content-type "text/html")
      (wrap-keyword-params)
      (wrap-params)))
```

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


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