สามารถพบได้ทั่วๆ ที่ใครก็จะแนะนำให้ใส่ config ประมาณนี้ เพื่อทำ reversed proxy ไปยัง socket.io ด้านหลัง
location ^~ /socket.io/ {
proxy_pass https://127.0.0.1:8089;
proxy_redirect off;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
}
พอเราทำตามนี้กลับเจอ error WebSocket connection to 'wss://your-domain.com/socket.io/' failed: Error during WebSocket handshake: 'Upgrade' header is missing
นี้ใน Console ของ Browser
ซึ่งถ้าไม่สังเกต เราจะไม่รู้ตัวด้วย เพราะว่า Front-end Application ของเรามันก็สามารถทำงานได้อยู่ ส่วนที่มันเป็นเช่นนี้ เป็นเพราะว่า library socket.io ที่ใช้อยู่ที่ Front-end มันมี features ที่จะ fallback ไปใช้ XHR polling ในกรณีที่ไม่สามารถใช้งาน WebSocket ได้. ถึงแม้ XHR polling จะทำให้แอพเราใช้งานได้ แต่มันก็ประสิทธิภาพแย่กว่า WebSocket เยอะ
พยายามหา google หาว่ามีใครเจอปัญหาแบบนี้หรือเปล่า แต่หาไม่ได้เลย ทุกคนที่เจอปัญหา จะได้รับคำตอบแนะนำให้ใส่ config ใน nginx ตามข้างต้น แล้วทุกคุณก็สามารถใช้งานกันได้ปกติแล้ว
ในที่สุดเราก็พบสาเหตุของปัญหาของเซิร์ฟเวอร์เรา. ในอดีตตอนที่เราเปิดใช้งาน HTTP/2 บนเซิร์ฟเวอร์ เราเจอปัญหาว่า Safari จะเข้าเว็บ https:// บนเซิร์ฟเวอร์ของเราไม่ได้ ซึ่งคำแนะนำในการแก้ปัญหาที่เราหาเจอในตอนนั้น เค้าแนะนำกันว่าให้เติม config นี้ใน nginx
proxy_hide_header Upgrade;
ซึ่งเราก็ได้เติมไปใน scope http ของ nginx เลยในตอนนั้น โดยที่ไม่เข้าใจสาเหตุที่แท้จริงของปัญหาด้วยซ้ำ ซึ่ง config บรรทัดนี้เองทำที่ให้เกิดปัญหา เพราะว่ามันไปเอา Header Upgrade ออกจาก Response Header นั่นเลยเป็นที่มาของ error ที่มันบอกว่า ‘Upgrade’ header is missing
ตอนนี้ถ้าเข้าใจไม่ผิด ปัญหามันเกิดจาก Apache มันจะตอบ Header Upgrade กลับมาเสมอถึงแม้ในกรณีที่มันไม่ควรจะตอบ Header Upgrade ออกมาก็ตาม ทำให้มันผิดมาตรฐาน RFC และ Safari มันก็จะไม่คุยต่อเลย (ส่วน Browser อื่น มันจะมองข้าม และโหลดข้อมูลมาแสดงเว็บตามปกติ) ดังนั้นการเติม proxy_hide_header Upgrade; ก็เพื่อซ่อน header Upgrade ที่มาจาก apache แล้วส่วนการรองรับเรื่อง HTTP/2 ให้เป็นหน้าที่ของ nginx คุยกับ client โดยตรงก็พอ
อ้างอิง
- https://bz.apache.org/bugzilla/show_bug.cgi?id=59311
การแก้ปัญหา เราต้องเอา proxy_hide_header Upgrade; ออกจาก scope http แล้วย้ายไปใส่ที่ scope location ทุกๆ บล็อคที่มีการทำ proxy_pass ไปยัง backend ที่เป็น Apache แทน