February 13, 2025
Preflight Requests
ผมเคยเขียน microservice อยู่ครั้งนึงที่เกิดปัญหาติด CORS ว่าใน headers ไม่รองรับ method options กว่าจะเข้าใจเรื่องนี้ใช้เวลาอยู่นานเหมือนกันครับ เลยอยากมาเขียนอธิบายทิ้งไว้สักหน่อย
CORS แบบสั้นๆ
CORS หรือ Cross origin resource sharing เป็นกลไกของ browser ที่จะตัดสินใจว่า resource ที่ถูกส่งกลับมาจาก server ต่าง origin (เช่น เราใช้เว็บ palmmmy.com อยู่แล้ว frontend ไปเรียก API จาก hellopalm.com) จะได้รับการอนุญาตให้ web อ่านได้หรือไม่
Preflight?
บางครั้งก่อนที่เราจะส่ง Request ที่เขียนฝั่ง Frontend จะมีแอบมี Request ที่ใช้ METHOD เป็น OPTIONS แอบส่งไปก่อนโดย browser เพื่อถามว่ามี METHOD อะไรบ้างที่สามารถใช้ได้ เราเรียกว่า “Preflight”
โดยเราสามารถตรวจสอบเรื่องนี้ได้ไม่ยาก โดยการเปิด Network ใน Developer Tools ครับ
กลไกนี้ browser เป็นคนจัดการเองนะครับ เราไม่ต้องไปทำอะไรกับมัน
แม้ว่าเราจะมี browser extension ที่ช่วยให้ข้ามการ trigger preflight ก็ตาม แต่มันไม่สะดวกและปลอดภัยเลยที่ต้องให้ User มาโหลดโปรแกรมเข้าเครื่องเพื่อใช้ web app ของเรา
ทำไมบาง Request ถึงไม่มี Preflight
เนื่องจาก browser มีเงื่อนไขในการส่ง Preflight อยู่ครับ โดยแบ่งได้ 2 ประเภท คือ
- Simple Requests (ไม่ส่ง Preflight)
- Preflighted Requests
เงื่อนไขในการเป็น Preflighted Requests
- HTTP methods คือ PUT, DELETE, PATCH, OPTIONS หรือ Custom Method
- Custom headers เช่น Authorization, Content-Type ที่ไม่ใช่ application/x-www-form-urlencoded, multipart/form-data หรือ text/plain
- Credentials (e.g. cookies หรือ Authorization headers)
ลำดับขั้นตอนของ Preflighted Requests
[Client -> (Request) -> Server] ส่ง Preflight ให้ server
METHOD: OPTIONS
Access-Control-Request-Method: POST
Origin: https://example.com
Access-Control-Request-Methodจะบอกว่าจริงๆ แล้ว Request ที่ต้องการส่งจริง ต้องใช้METHODอะไร (ในตัวอย่างนี้เป็นPOST)Originชื่อของเว็บที่ส่ง
[Client <- (Response) <- Server] Response preflight
เราจะได้ response กลับมาหน้าตาประมาณนี้
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: ...
Access-Control-Max-Age: 86400
Access-Control-Allow-Methodsตอบกลับว่าMethodอะไรใช่้ได้บ้างAccess-Control-Max-Ageคือ เวลา cached ที่บอกว่าไม่ต้อง preflight อีกนานแค่ไหนในหน่วยวินาที (86400s ~ 24hr) หากอ้างอิงจาก (https://developer.mozilla.org/) จะมี default เป็น 5s
SUCCESS: [Client -> (Request) -> Server] Actual Requests
จากนี้เราก็สามารถส่ง Request ได้ตามปกติแล้วครับ
ข้อสังเกต
อีกกรณีที่ development แล้วเจอเรื่องนี้ คือ ใช้ Postman หรือ curl แล้วไม่เจอปัญหานี้ มาจากการที่ tools พวกนี้ไม่มีกลไกของ CORS เหมือนใน browser นั้นเอง
Note to me
เคย custom response header ใน Spring Cloud Gateway เอง แล้วไม่เข้าใจเรื่องนี้ ทำให้ไม่มี OPTIONS ใน Access-Control-Allow-Methods