The WebSocket Handshake
WebSocket ไม่ได้โผล่ขึ้นมาจากอากาศ แต่เริ่มต้นชีวิตในฐานะ HTTP request ธรรมดา ๆ แล้วค่อย upgrade ตัวเองในที่ การเข้าใจ handshake นี้คลายความลึกลับได้มากมาย: ทำไม WebSocket ถึงวิ่งบนพอร์ตเดียวกับทราฟฟิกเว็บ ทำไมถึงลอดผ่าน proxy และ firewall ส่วนใหญ่ได้ และทำไม scheme ws:// กับ wss:// ถึงสะท้อน http:// และ https://
ทุกอย่างเริ่มต้นในฐานะ HTTP
หัวข้อที่มีชื่อว่า “ทุกอย่างเริ่มต้นในฐานะ HTTP”client เปิด TCP connection ธรรมดาและส่ง HTTP GET request — แต่พร้อม header พิเศษที่บอกว่า “ฉันอยากสลับการเชื่อมต่อนี้ไปเป็นโปรโตคอล WebSocket” สอง header ที่ทำงานจริงคือ Upgrade: websocket และ Sec-WebSocket-Key
GET /chat HTTP/1.1Host: example.comUpgrade: websocketConnection: UpgradeSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==Sec-WebSocket-Version: 13Origin: https://example.comมีบางตัวในนี้ที่ควรรู้จักไว้:
Upgrade: websocketและConnection: Upgradeทำงานร่วมกันเพื่อส่งสัญญาณการสลับโปรโตคอลSec-WebSocket-Keyเป็นค่าสุ่มที่เข้ารหัส base64 ซึ่ง client สร้างขึ้นใหม่สำหรับการเชื่อมต่อนี้ ไม่ใช่ ความลับด้านความปลอดภัย แต่มีไว้ให้ client ยืนยันว่า response มาจาก server ที่เข้าใจ WebSocket handshake จริง ๆSec-WebSocket-Version: 13ตรึงเวอร์ชันของโปรโตคอลไว้ (13 คือเวอร์ชันมาตรฐาน)
Response แบบ 101
หัวข้อที่มีชื่อว่า “Response แบบ 101”ถ้า server พูด WebSocket ได้และยอมรับ ก็จะตอบกลับด้วยสถานะ 101 Switching Protocols — สถานะ HTTP ที่แปลตรงตัวว่า “จบ semantics ของ HTTP บนการเชื่อมต่อนี้แล้ว จากนี้ไปเป็น protocol คนละตัว”
HTTP/1.1 101 Switching ProtocolsUpgrade: websocketConnection: UpgradeSec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=ความมหัศจรรย์อยู่ที่ Sec-WebSocket-Accept server เอา Sec-WebSocket-Key ของ client มาต่อท้ายด้วย string คงที่ที่ spec กำหนดไว้ แฮชด้วย SHA-1 แล้วเข้ารหัส base64 ฝั่ง client คำนวณค่าเดียวกันแล้วเทียบว่าตรงกันไหม ถ้าตรง ก็แปลว่ากำลังคุยกับ server WebSocket ตัวจริง ไม่ใช่ cache ที่บังเอิญคืน 101 กลับมาแบบมั่ว ๆ ถ้าไม่ตรง ก็ไม่มีการเชื่อมต่อ
Handshake ตามลำดับ
หัวข้อที่มีชื่อว่า “Handshake ตามลำดับ”sequenceDiagram participant C as Client participant S as Server C->>S: TCP connection established C->>S: GET /chat HTTP/1.1 (Upgrade: websocket, Sec-WebSocket-Key) Note over S: validate headers, compute Sec-WebSocket-Accept S-->>C: HTTP/1.1 101 Switching Protocols (Sec-WebSocket-Accept) Note over C,S: same TCP connection — now speaking WebSocket C->>S: data frame S->>C: data frame
สังเกตสิ่งที่ไดอะแกรม ไม่ได้ แสดง: การเชื่อมต่อเส้นที่สอง หลัง 101 client และ server ยังคงใช้ TCP connection เดิม ที่มีอยู่แล้วต่อไป ไม่มีอะไรเปิดใหม่ HTTP request เป็นแค่ประตูทางเข้า พอผ่านประตูไปแล้ว ทั้งสองฝ่ายก็เลิกแลกเปลี่ยน message แบบ HTTP แล้วเริ่มแลกเปลี่ยน WebSocket frame แทน (หัวข้อของบทเรียนถัดไป)
ทำไมการออกแบบนี้ถึงฉลาด
หัวข้อที่มีชื่อว่า “ทำไมการออกแบบนี้ถึงฉลาด”เพราะ handshake คือ HTTP request การเชื่อมต่อ WebSocket จึงสามารถวิ่งบนพอร์ต 80 และ 443 เคียงข้างทราฟฟิกเว็บปกติ นำ TLS ที่มีอยู่มาใช้ซ้ำสำหรับ wss:// และลอดผ่าน proxy และ load balancer ที่เข้าใจ HTTP อยู่แล้ว protocol จึงได้ระยะเอื้อมของเว็บมาฟรี ๆ แล้วสลัด request/response model ของ HTTP ทิ้งทันทีที่ไม่ต้องใช้แล้ว
| ข้อดี (WebSocket Handshake) | ข้อแลกเปลี่ยน |
|---|---|
| ใช้ HTTP infrastructure เดิม — ผ่าน firewall และ proxy ได้ | handshake round-trip เพิ่ม latency ตอนเปิด connection ครั้งแรก |
| วิ่งบน port 80/443 ร่วมกับ HTTP ธรรมดา | proxy เก่าที่ไม่รู้จัก WebSocket อาจ block Upgrade header |
Sec-WebSocket-Accept ป้องกัน cache ที่ไม่เข้าใจ WS | ต้องการ server-side support — ไม่ใช่ทุก server ที่ support |
TLS (wss://) ต่อยอดจาก HTTPS ที่มีอยู่แล้ว | HTTP/2 multiplexing ไม่ทำงานกับ WebSocket — ต้องใช้ HTTP/1.1 |
ข้อผิดพลาดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ข้อผิดพลาดที่พบบ่อย”ไม่ Validate Origin ใน Handshake อาการ:
- ยอมรับทุก WebSocket connection โดยไม่ตรวจ
Originheader - เปิดช่องให้ cross-site WebSocket hijacking (CSWSH)
- ตรวจสอบ
Originheader เทียบกับ whitelist ก่อน respond 101
ทำ Auth หลัง Connection เปิดแล้ว อาการ:
- รับ handshake ก่อน ค่อย auth ทีหลัง ทำให้ connection เปิดอยู่ชั่วคราวโดยไม่ตรวจสอบ
- attach auth token ใน handshake (query param หรือ header) และ validate ก่อน respond 101
💡 ตัวอย่างจากของจริง
Binance WebSocket API:
- ใช้ WSS handshake บน
wss://stream.binance.com:9443- validate API key ใน handshake ก่อน accept connection
- reject ด้วย HTTP 401 แทนการรับแล้ว close ทีหลัง
GitHub Copilot:
- handshake ผ่าน HTTPS/WSS ที่ port 443
- ใช้ corporate proxy ที่รู้จัก Upgrade header ได้