บทความ

HTTP QUERY method คืออะไร ต่างจาก GET และ POST ยังไง

QUERY เป็น HTTP method ใหม่จาก RFC 10008 ส่งเงื่อนไขค้นหาไปใน body ได้เหมือน POST แต่ server และระบบกลางทางยังรู้ว่าคำขอนี้แค่อ่านข้อมูล ส่งซ้ำได้ และ cache ได้เหมือน GET

เผยแพร่
เวลาอ่าน
3 นาที
แท็ก
#HTTP#Laravel

GET ส่งเงื่อนไขได้แค่ใน URL

ลองนึกถึงหน้าค้นหาสินค้าที่มีตัวกรองเยอะ ๆ เลือกได้หลายหมวด หลายแบรนด์ ช่วงราคา และคำค้นภาษาไทย ถ้าใช้ GET ทุกอย่างต้องไปต่อกันใน query string

/products?category[]=coffee&category[]=tea&brand[]=12&brand[]=31&price[min]=100&price[max]=500&q=%E0%B8%81%E0%B8%B2%E0%B9%81%E0%B8%9F

ใช้ได้ตอนตัวกรองยังน้อย พอเงื่อนไขเยอะขึ้นจะเริ่มเจอปัญหาสามเรื่อง

  • URL ยาวได้ไม่เท่ากันในแต่ละที่ คำขอหนึ่งวิ่งผ่านเบราว์เซอร์ CDN load balancer และ nginx แต่ละตัวตั้งขีดจำกัดไว้ไม่เท่ากัน และเราไม่รู้ล่วงหน้าว่าตัวไหนจะตัดก่อน ถ้าเกิน nginx จะตอบ 414 Request-URI Too Large
  • ภาษาไทยกินที่มาก ตัวอักษรไทยหนึ่งตัวเป็น 3 byte ใน UTF-8 พอเข้ารหัสใส่ URL แต่ละ byte กลายเป็น %E0 จึงยาวเป็น 9 ตัวอักษรต่อหนึ่งตัวไทย คำว่า "กาแฟ" ข้างบนเลยยาว 36 ตัว
  • URL ไปอยู่ใน log nginx เก็บ URL ของทุกคำขอลง access log แต่ไม่เก็บ body ถ้าเงื่อนไขค้นหามีข้อมูลส่วนตัว เช่น เลขบัตรประชาชนหรือเบอร์โทร ก็จะไปค้างอยู่ใน log ด้วย

ใช้ POST แทนได้ แต่ระบบรอบข้างไม่รู้ว่าแค่อ่าน

ที่ผ่านมาเราก็มักแก้ด้วยการทำ POST /products/search แล้วส่งเงื่อนไขเป็น JSON ก็ใช้ได้นะ แต่ POST บอกระบบรอบข้างว่าคำขอนี้อาจแก้ข้อมูล ผลที่ตามมาคือ

  • HTTP client และ proxy จะไม่ลองส่งซ้ำให้เองเมื่อเน็ตหลุดกลางทาง เพราะกลัวสั่งซื้อซ้ำหรือตัดเงินซ้ำ
  • cache ทั่วไปไม่เก็บผลของ POST
  • คนที่อ่านโค้ดหรือ log ต้องเปิด controller ดูเองว่า route นี้แค่ค้นหา

QUERY อยู่ตรงกลางระหว่าง GET กับ POST

RFC ใช้สองคำที่ต้องรู้ก่อน

  • safe คือคำขอที่ไม่เปลี่ยนข้อมูลฝั่ง server (MDN)
  • idempotent คือส่งซ้ำกี่ครั้ง ผลบน server ก็เหมือนส่งครั้งเดียว (MDN)
GET QUERY POST
safe ใช่ ใช่ อาจไม่ใช่
idempotent ใช่ ใช่ อาจไม่ใช่
body มีความหมาย ไม่ ใช่ ใช่
cache ได้ ได้ ได้ ได้แบบจำกัด

ตารางนี้สรุปมาจาก RFC 10008 สรุปคือ QUERY ส่ง body ได้เหมือน POST แต่สัญญากับทุกคนว่าแค่อ่าน ส่งซ้ำได้เหมือน GET

หน้าตาคำขอ QUERY

ตัวอย่างจาก RFC ส่งเงื่อนไขแบบเดียวกับ query string แต่ย้ายไปไว้ใน body

QUERY /feed HTTP/1.1
Host: example.org
Content-Type: application/x-www-form-urlencoded

q=foo&limit=10&sort=-published

server ตอบผลค้นหากลับมาใน body ด้วย 200 OK ตามปกติ RFC มีส่วนเสริมอีกสองอย่างที่น่ารู้

  • server บอกได้ว่ารับเงื่อนไขรูปแบบไหน ผ่าน header Accept-Query เช่น Accept-Query: application/json
  • server ตอบ 303 See Other พร้อม Location ได้ เพื่อบอกว่าผลลัพธ์อยู่ที่ URL นี้ ครั้งหน้า client ก็ใช้ GET ดึงผลเดิมได้โดยไม่ต้องส่ง body ซ้ำ

ส่วน cache ที่รองรับ QUERY ต้องเอา body มาเป็นส่วนหนึ่งของ cache key ด้วย ไม่อย่างนั้นการค้นหาสองแบบที่ยิงไป URL เดียวกันจะได้ผลปนกัน

ใช้ใน Laravel ได้แล้ว

Laravel ทยอยเพิ่มการรองรับมาสองรอบ Laravel 13.19 เพิ่มฝั่ง HTTP client กับ test ส่วน Laravel 13.35 เพิ่ม Route::query() (อ่านเพิ่มในสรุป Laravel 13.35)

// Laravel 13.35 ขึ้นไป
Route::query('/products/search', SearchProductsController::class);

ส่ง body เป็น JSON แล้วอ่านด้วย $request->input() ได้เหมือน POST ทุกอย่าง

เช็คก่อนใช้จริง

  • ฟอร์ม HTML ส่งได้แค่ GET กับ POST QUERY จึงใช้ผ่าน fetch() หรือ HTTP client อย่าง Http::query() เท่านั้น
  • ข้ามโดเมนต้องผ่าน preflight QUERY ไม่อยู่ในรายการ method ที่ CORS ปล่อยผ่านเลย เบราว์เซอร์จะส่ง OPTIONS มาถามก่อน ถ้าแก้ allowed_methods ใน config/cors.php ไว้ อย่าลืมเพิ่ม QUERY
  • ระบบกลางทางอาจยังไม่รู้จัก method นี้เพิ่งเป็นมาตรฐาน WAF, proxy หรือ CDN บางตัวอาจยังตอบ error หรือไม่ cache ให้ ลองยิงผ่านเส้นทางจริงบน server ก่อนเปลี่ยนหน้าค้นหาหลัก
  • GET ยังใช้ได้ดีอยู่ ถ้าเงื่อนไขสั้นและอยากให้ผู้ใช้ copy URL ไปแชร์ได้ GET ยังเหมาะกว่า QUERY เหมาะกับการค้นหาที่เงื่อนไขยาวหรือเป็นข้อมูลส่วนตัว

รายละเอียดทั้งหมดอยู่ใน RFC 10008 ซึ่งมีตัวอย่าง QUERY แบบ JSONPath และ SQL ให้ดูด้วย

อ่านต่อ

ล่าสุด

ดูทั้งหมด →