ยิง request แบบไม่มีสะดุดด้วย emacs REST client
ช่วงปีใหม่ผมมีเวลาไปเรียนคอร์ส The Ultimate Go Series ของพี่ยอดซึ่งจากใจคนที่ดู Go อยู่ห่างๆ ผมบอกได้เลยว่าคอร์สนี้สอนได้ครบเครื่องมากในเวลาไม่กี่ชั่วโมง แต่นั่นไม่ใช่ประเด็นครับ ประเด็นคือตอนคอร์ส 2 ที่กำลังเรียนเรื่อง API พี่ยอดใช้ HTTP Client ที่รันจาก Plain-text query เลยบน VSCode ซึ่ง ส่วนตัวผมเคยใช้อะไรคล้ายๆ กันบน Intellij เวอร์ชั่นหลังๆ แต่ก็ยังไม่ค่อยคลิก
แต่ก็จังหวะพอดีที่คอร์สนี้ผมเรียนอยู่บน emacs ผมที่ค่อยๆ ใช้งานหนักขึ้นเรื่อยๆ เพื่อให้ตอบโจทย์การใช้งานที่สุด โพสนี้เลยจะเล่าว่าผมใช้ Emacs REST cilent ยังไง แล้วก็จะเปรียบเทียบ use case เดียวกันเทียบกับ REST client บน Intellij กับ VSCode ด้วยครับ
Installing
สำหรับ emacs สามารถโหลดจาก MELPA ได้จาก package ชื่อ restclient ครับ แต่ถ้าใครใช้ Doom ก็เพียงแค่เปิด option rest ; Emacs as a REST client
ในไฟล์ init.el แล้วสั่ง doom reload
ก็พร้อมใช้งานแล้วครับ
Basic Usage
วิธีการใช้งานของ restclient ทั้งสามเจ้าที่ผมพูดถึงข้างต้นจะคล้ายๆ กันหมดครับ คือให้เราสร้างไฟล์นามสกุล http ขึ้นมาหลังจากนั้นเราก็เขียน Request ลงไปครับ เหมือนตัวอย่างนี้
POST http://localhost:8081/todos
Authorization: Bearer token
Content-Type: application/json
{
"text": "test request"
}
ซึ่งเราจะเห็นได้ว่ามันจะมีส่วนประกอบหลักๆ อยู่ 4 ส่วนคือ
- HTTP Method — ตรงนี้เราจะใส่เป็น GET, POST, PUT, PATCH, DELETE แล้วแต่เราเลย
- HTTP Endpoint — เป็น URL ที่เราจะไป consuming REST service ครับ
- Request Header (Optional) — ตรงนี้เป็นส่วนที่เราจะใส่ Request header เช่น Authorization หรือ Content-Type เป็นต้น ถ้าใส่ตัวใหม่ก็เคาะบรรทัดใหม่ แต่มีข้อแม้ว่าต้องติดกับบรรทัดที่เป็น Method + endpoint เสมอครับ
- Request Body (Optional) — ตัวนี้แล้วแต่ method ครับ แต่ถ้าเราต้องใช้ให้เคาะ เว้นบรรทัดมาจาก header 1 บรรทัด แล้วใส่ body ไปได้เลย แล้วแต่ type ที่เราตั้งไว้ ซึ่งในเคสข้างบนเป็น JSON ครับ
พอเราเตรียม Request query เรียบร้อยแล้ว เราสามารถสั่งให้ emacs เรายิง HTTP request ไปตามที่เราเขียนไว้โดยเลื่อน cursor ไปไว้ที่บรรทัด HTTP Method ครับ หลังจากนั้นกด C-c C-c
ก็จะเป็นการยิง request ออกไปครับ ซึ่งถ้าเราไม่เคยยิงมาก่อนมันจะเปิด buffer window ของ HTTP Response ให้อัตโนมัติครับ
ซึ่งมันจะโฟกัส cursor มาที่ window ของ response อัตโนมัติด้วยครับ โดยเราสามารถสลับกลับไปที่ window ของไฟล์ http เราได้โดยกด C-x o
ครับ
Tricks
จริงๆ รู้แค่ข้างบนก็พอใช้งานได้แล้ว 80% ครับ แต่เพื่อความสะดวกสบาย ผมมีทริก 3 อย่างที่ใช้บ่อยมาก หลังจากใช้ emacs REST client ตัวนี้มากซักพัก
เทคนิคแรกคือ เวลา cursor ของเราดีดมาที่หน้า response แล้วเราต้องคอย Switch กลับบ่อยๆ มันก็น่ารำคาญครับ ซึ่งเทคนิคนึงที่ช่วยได้คือ ให้เราสั่ง request โดยใช้ C-c C-v
แทนครับ วิธีนี้จะทำให้มันไม่โฟกัสไปที่ Buffer ของ HTTP Response ถ้าเราต้องการแค่ดูผลลัพธ์เฉยๆ
เทคนิคที่สองคือ หลายๆ ครั้งการเขียน Request ของเรามันจะมีชื่อซ้ำๆ หรือใช้ Header ซ้ำๆ ใช่มั้ยครับ พอเราไม่อยากเขียนซ้ำหรือต้องแก้หลายที่ ตัว emacs REST client รองรับการสร้าง variable ด้วยในตัวโดย เราสามารถเขียนเป็น
:base_url = http://localhost:8081
หลังจากนั้น เราก็เอาตัวแปรนี้ไปใช้ได้อย่างเช่นในเคสนี้ผมจะเอาไปใช้กับ endpoint ผมก็สามารถเขียนแบบนี้ได้เลย
GET :base_url/tokenz
ซึ่งนอกจาก การแทนที่ variable แบบง่ายๆ นี้แล้ว เราสามารถทำ multiline variable ได้ด้วย โดยเปลี่ยนจาก =
เป็น = <<
ซึ่งสามารถไปดูได้ใน GitHub ของ library นี้ได้เลยครับ
เทคนิคสุดท้ายคือ เป็นเรื่องธรรมดามากที่เราจะมีการป้องกัน API เราด้วย Authentication method แล้วเราต้องมาแปะ token ที่ยิงขอจาก API นึงมาใช้กับ API อื่นๆ ใน emacs REST client ตัวนี้เราสามารถทำได้เหมือนกันครับ โดยทำตามนี้
ใน Request ที่เราทำ Authentication แล้วเราได้ Token กลับคืนมาใน Response ให้เราใส่ Hook ตัวนี้ลงไปหลัง Request ที่เราทำการขอ Token ครับCOPY
-> run-hook (restclient-set-var ":newtoken" (cdr (assq 'token (json-read))))
จะมีจุดสังเกตุอยู่ 2 จุดคือ
“:newtoken”
ตัวนี้คือชื่อตัวแปรที่เราจะเซตให้กับ token ที่เราอ่านขึ้นมาได้ครับ
‘token
ตัวนี้เป็น key ใน response body ที่เราจะไปอ่านค่าของ token ขึ้นมาครับ
นอกจากนั้นจะเป็น function ที่ restclient.el เตรียมมาให้เราในการอ่านค่าจาก Response JSON ครับ
ใน Request ของ protected resource เราสามารถนำตัวแปรนั้นไปใช้ได้เหมือนกับวิธีการใช้ตัวแปรตัวอื่นเลย อย่างเช่นในเคสนี้ผมเอาไปใส่ไว้ใน Authorization header ครับ
POST http://localhost:8081/todos
Authorization: Bearer :newtoken
เพียงแค่นี้เราก็จะสามารถใช้ reuse authentication token กับ request อื่นๆ ได้แล้วครับ ซึ่งจริงๆ ตัว Library นี้ยังมีฟีเจอร์อื่นๆ อีกด้วยครับ สามารถลองกดคีย์ดูได้ ยังมีอีกหลายฟีเจอร์ที่ผมยังไม่ได้ใช้เลย
Comparison with Intellij REST client and VSCode REST client
ไหนๆ ก็พูดถึง Emacs แล้วก็ขอเทียบกับ REST client แบบเดียวกันของ Intellij กับ VSCode เลยละกันว่ามันมีข้อดีข้อเสียต่างกันยังไงบ้าง
เทียบกัน 3 เจ้า ถ้าไม่นับคีย์ emacs ที่กดง่ายมากในตัวอยู่แล้ว ผมยกให้ VSCode REST client นี่ UX ดีสุดเลยครับ ผมรู้สึกว่าการประกาศตัวแปร กับการใช้งานมันตรงไป ตรงมาดีแต่ที่ชอบที่สุดของตัว VSCode คือวิธีการที่มันดึงค่ามาจาก Response นี่ ง่ายที่สุดในทั้ง 3 เจ้า แล้วก็ตรงไปตรงมาที่สุดแล้วครับ โดยแค่บอกชื่อ request name แล้วก็แกะ body ออกมา bind ใส่ variable จบ ซึ่งมันควรจะแค่นี้จริงๆ ครับ
ในส่วนของ Intellij REST Client ผมว่าข้อดีคือมัน Integrate กับ ecosystem ของ Jetbrain ได้เนียนจนคนที่ใช้อยู่แล้ว จะไม่รู้สึกว่ามันขัดเลยอย่างเช่นปุ่มกด run request รวมถึง Response ที่หน้าตาไม่ต่างกับ test runner ที่คุ้นเคยกันอยู่แล้ว อาจจะมี function ที่เวิ่นเว้อหน่อยในการแกะ response ออกมา แต่ผมว่าก็ยังอ่านง่ายกว่าและตรงไปตรงมากว่า emacs ครับ
ข้อเสียหลักๆ ของ Intellij REST cilent คือการที่เราต้องประกาศ environment แยกไว้อีกไฟล์นี่แหละครับ ส่วนตัวผมรู้สึกว่ามันขัดใจมาก (หรือถ้ามันประกาศไว้ในไฟล์เดียวได้บอกผมหน่อยนะครับ) อีกข้อเสียนึงที่เป็นส่วนหนึ่งให้ผมเลิกใช้ตัวนี้ไปคือมันจะเก็บ Response ไว้ในไฟล์เป็น default ครับ ซึ่งพอเราเทสบ่อยๆ มันก็สร้างไฟล์ response ออกมาบานมา แล้วต้องมาคอยนั่งไล่ลบใน .idea เราที่บวมขึ้นเรื่อยๆ ครับ
Verdict
สุดท้ายแล้ว ผมรู้สึกว่าเรามาถึงยุคที่การเก็บ HTTP Request query เป็น Plain text นี้มันดูแลง่ายกว่าสมัยก่อนมากๆ ครับ เพียงแค่ยัดลง git เราก็จะ maintain ตัวนี้ได้ยาวๆ เป็น executable document ที่แท้จริง แล้วจะเห็นได้ว่าแทบจะทุก editor/IDE รองรับแล้ว แต่อาจจะแตกต่างกันไปตามสไตล์แต่ละ implementation ของตัวเองครับ ซึ่งในจุดนี้ถ้าทั้งทีมตกลงกันดีๆ ผมคิดว่าน่าจะไม่มีปัญหาอะไร ส่วนใครเอาไปปรับใช้กับตัวเองแล้วเจอเทคนิคอะไรดีๆ ก็แบ่งปันกันได้ครับ แล้วเจอกันใหม่ตอนหน้าครับ
ปล.นอกเรื่องนิดนึงตอนแรกซีรีย์ emacs diary นี้ผมว่าจะเขียนตั้งแต่ Basic ขึ้นไปเรื่อยๆ แต่คิดว่าทำแบบนั้นกว่าจะได้เขียนเรื่องที่อยากเขียน หรือพอมีเรื่องที่จะเขียนมันก็ต่อคิวยาว เพราะฉะนั้นผมเลยเปลี่ยนแนวทางใหม่เป็นลงตามอารมณ์เลยละกันครับ 555