ข้อผิดพลาดหน้าตาแบบนี้ ระบบเชื่อมต่อส่ง POST ไปยังเอนด์พอยต์ของคุณ เอนด์พอยต์ทำงานได้ เส้นทางก็มีอยู่ ทดสอบด้วยมือแล้วทุกอย่างปกติ แต่ในบันทึกกลับเห็น GET ยิงเข้าเส้นทางที่รับเฉพาะ POST และตอบกลับด้วย 404 หรือ 405
สาเหตุแทบจะเหมือนกันเสมอ: ระหว่างไคลเอนต์กับเอนด์พอยต์มีการรีไดเรกต์รหัส 301 หรือ 302 คั่นอยู่ และรหัสเหล่านี้ไม่ได้บังคับให้ไคลเอนต์รักษาเมธอดไว้
ตรวจสอบได้ในสิบวินาที:
curl -sS -o /dev/null -L -d 'test=1' \
-w 'เมธอดหลังรีไดเรกต์: %{method}\n' \
'https://httpbingo.org/redirect-to?url=/anything&status_code=301'
ผลลัพธ์: เมธอดหลังรีไดเรกต์: GET และเนื้อหาของคำขอก็หายไปพร้อมกัน
เปลี่ยน 301 เป็น 308 ในคำสั่งเดิม แล้วคุณจะได้ POST ต่างกันแค่รหัสสถานะเท่านั้น
ห้ารหัส สองคุณสมบัติ
การรีไดเรกต์แยกกันตามสองแกนที่เป็นอิสระต่อกัน: ถาวรหรือชั่วคราว และเมธอดของคำขอถูกรักษาไว้หรือไม่
| รหัส | ชื่อ | ความถาวร | รักษาเมธอด |
|---|---|---|---|
| 301 | Moved Permanently | ถาวร | ไม่ |
| 302 | Found | ชั่วคราว | ไม่ |
| 303 | See Other | — | ไม่ เป็น GET เสมอ |
| 307 | Temporary Redirect | ชั่วคราว | ใช่ |
| 308 | Permanent Redirect | ถาวร | ใช่ |
สังเกตความสมมาตร: 307 คือ 302 ที่รับประกันเมธอด ส่วน 308 คือ 301 ที่รับประกันเมธอด
ความสับสนนี้มาจากไหน
ในทางรูปแบบ ข้อกำหนดไม่เคยบังคับให้เปลี่ยนเมธอดใน 301 และ 302 แต่เบราว์เซอร์และไคลเอนต์ยุคแรกทำแบบนั้นพอดี และเมื่อสังเกตเห็นความไม่สอดคล้อง เว็บทั้งใบก็ทำงานแบบนั้นไปแล้ว
พฤติกรรมนี้จึงถูกบันทึกไว้ว่า «อนุญาต»: RFC 9110 ซึ่งเป็นข้อกำหนดปัจจุบันของความหมาย HTTP อนุญาตให้ไคลเอนต์เปลี่ยน POST เป็น GET เมื่อเจอ 301 และ 302 อนุญาตแต่ไม่บังคับ — นั่นหมายความว่าพฤติกรรมขึ้นอยู่กับดุลพินิจของไคลเอนต์ และพึ่งพาไม่ได้
เพื่อให้มีตัวเลือกที่ชัดเจน จึงมีการเพิ่ม 307 และ 308 สำหรับสองรหัสนี้ การเปลี่ยนเมธอดเป็นสิ่งต้องห้าม: ไคลเอนต์ต้องส่งคำขอซ้ำด้วยเมธอดเดิมและเนื้อหาเดิม รหัส 308 นิยามไว้ใน RFC 7538 ของตัวเอง และมีคำอธิบายอ้างอิงในเอกสาร MDN ด้วย
ข้อสรุปเชิงปฏิบัติ: 301 และ 302 ปลอดภัยเฉพาะในที่ซึ่งคำขอเป็น GET แน่นอนเท่านั้น ที่ใดที่ POST อาจมาถึง ต้องใช้ 307 หรือ 308
กับดักที่สอง: รีไดเรกต์ถาวรถูกแคช
เรื่องนี้ถูกลืมบ่อยกว่าปัญหาเรื่องเมธอดเสียอีก
301 และ 308 ประกาศว่าการย้ายเป็นแบบถาวร และเบราว์เซอร์มีสิทธิ์แคชไว้นาน — บางครั้งจนกว่าผู้ใช้จะล้างแคช หลังจากนั้นเซิร์ฟเวอร์ก็ไม่ถูกถามอีกเลย
จึงเกิดกฎข้อนี้: 301 ที่วางผิดแทบจะย้อนกลับไม่ได้ คุณแก้การตั้งค่าได้ แต่ผู้เข้าชมที่ได้รับคำตอบเดิมไปแล้วจะยังถูกรีไดเรกต์ต่อไป 302 และ 307 ไม่มีปัญหานี้ เพราะเป็นแบบชั่วคราวโดยนิยาม
คำแนะนำเชิงปฏิบัติ: ตราบใดที่แผนการย้ายยังไม่นิ่ง ให้ใช้รหัสชั่วคราว การยกระดับ 302 เป็น 301 ในภายหลังทำได้ง่าย ส่วนทางกลับกันไม่ง่าย
ควรเลือกอะไร
หน้าย้ายถาวรแล้ว และรับเฉพาะ GET → 301 กรณีคลาสสิก: URL บทความเก่า การเปลี่ยนโครงสร้างที่อยู่
เหมือนกัน แต่เอนด์พอยต์รับ POST → 308 ครอบคลุม API, เว็บฮุก และฟอร์ม
ปลายทางเปลี่ยนชั่วคราว มีแต่ GET → 302
ชั่วคราว และเมธอดต้องคงอยู่ → 307
หลัง POST ต้องส่งผู้ใช้ไปหน้าผลลัพธ์ → 303 นี่คือรูปแบบ POST/Redirect/GET: มันเปลี่ยนคำขอเป็น GET โดยตั้งใจ เพื่อให้การรีเฟรชหน้าไม่ส่งฟอร์มซ้ำ ตรงนี้การเปลี่ยนเมธอดคือเป้าหมาย ไม่ใช่ข้อบกพร่อง
ในทางปฏิบัติมันพังตรงไหน
รีไดเรกต์ http→https ที่วางไว้หน้า API เป็นกรณีที่พบบ่อยที่สุดและน่ารำคาญที่สุด ไคลเอนต์ตั้งค่าเป็น http:// เซิร์ฟเวอร์ตอบ 301 ชี้ไป https:// แล้วเมธอดก็หายไป อาการคือเห็น GET ในบันทึกบนเส้นทาง POST แก้ได้ด้วยการเปลี่ยน 301 เป็น 308
การทำโฮสต์ให้เป็นรูปแบบบัญญัติ การรีไดเรกต์จาก www ไปไม่มี www (หรือกลับกัน) มักตั้งเป็น 301 ซึ่งไม่มีปัญหาสำหรับหน้าเว็บ แต่ถ้ามี API อยู่บนโฮสต์เดียวกัน ปัญหาเดียวกันก็เกิดขึ้นทันที
การส่งฟอร์ม ฟอร์มถูกส่งไปยังที่อยู่ที่มีการรีไดเรกต์ ผู้ใช้เห็นว่า «ไม่มีอะไรเกิดขึ้น» เพราะข้อมูลไม่เคยไปถึงตัวจัดการ
เว็บฮุก บริการภายนอกส่ง POST ไปยังที่อยู่ที่คุณเคยให้ไว้ ถ้าที่อยู่นั้นย้ายไปหลัง 301 เว็บฮุกจะหยุดทำงานอย่างเงียบ ๆ ขณะที่บันทึกของฝั่งผู้ส่งแสดงการตอบกลับที่สำเร็จต่อการรีไดเรกต์
ทั้งสี่กรณีมีลายเซ็นร่วมกัน: เอนด์พอยต์ปกติดี แต่คำขอไปไม่ถึงในรูปแบบเดิม การวินิจฉัยจึงหลงทาง — คนไปตามหาบั๊กในตัวจัดการ ทั้งที่มันอยู่ในการตั้งค่าเว็บเซิร์ฟเวอร์
ทำไมบริการย่อลิงก์ใช้ 302
ลิงก์ย่อแทบจะคืนค่า 302 เสมอ และนั่นเป็นทางเลือกที่ตั้งใจ ไม่ใช่ความประมาท
เหตุผลอยู่ที่ความถาวร ไม่ใช่เมธอด: ปลายทางของลิงก์ย่อเปลี่ยนได้ การใช้ 301 เท่ากับอนุญาตให้เบราว์เซอร์แคชการกระโดดนั้นไว้ตลอดกาล และหลังจากเปลี่ยนปลายทางแล้ว ผู้เข้าชมส่วนหนึ่งจะยังไปตกที่อยู่เดิมต่อไป ส่วนเมธอดไม่เกี่ยวข้องตรงนี้ — ลิงก์ย่อถูกเข้าถึงด้วยคำขอ GET
สิ่งที่การเลือกรหัสไม่ได้เปลี่ยน
ความเข้าใจผิดที่แพร่หลายสองข้อ
น้ำหนักลิงก์ การเลือกระหว่าง 301 กับ 302 ไม่กระทบการส่งผ่านน้ำหนัก — Google ยืนยันตั้งแต่ปี 2016 รายละเอียดเต็มอยู่ในลิงก์ย่อส่งผ่านน้ำหนักลิงก์หรือไม่
การเลือก URL ตามรูปแบบบัญญัติ การรีไดเรกต์กับ rel="canonical" แก้ปัญหาคนละอย่าง: การรีไดเรกต์ถอนที่อยู่หนึ่งออกจากการใช้งาน ส่วน canonical ปล่อยให้ทั้งสองเข้าถึงได้และระบุว่าอันไหนเป็นหลัก จะใช้อะไรเมื่อไรอธิบายไว้ในคู่มือ canonical
เช็กลิสต์สั้น ๆ
- บนเส้นทางที่รับ
POSTให้ใช้ 307 หรือ 308 ไม่ใช่ 302 และ 301 - ตรวจรีไดเรกต์ http→https แยกต่างหาก เพราะมักตั้งด้วยกฎรวมและลืม API ไป
- ตราบใดที่แผนยังไม่นิ่ง ให้ใช้รหัสชั่วคราว 301 และ 308 ถูกแคชและแทบย้อนกลับไม่ได้
- หลังประมวลผลฟอร์มให้ใช้ 303 — นั่นคือพฤติกรรมที่ถูกต้อง ไม่ใช่ความผิดพลาด
- ลดห่วงโซ่ให้เหลือกระโดดเดียว ทุกกระโดดที่เพิ่มมาคือทั้งความหน่วงและความเสี่ยง