ข้ามไปยังเนื้อหา

The WebSocket Handshake

WebSocket ไม่ได้โผล่ขึ้นมาจากอากาศ แต่เริ่มต้นชีวิตในฐานะ HTTP request ธรรมดา ๆ แล้วค่อย upgrade ตัวเองในที่ การเข้าใจ handshake นี้คลายความลึกลับได้มากมาย: ทำไม WebSocket ถึงวิ่งบนพอร์ตเดียวกับทราฟฟิกเว็บ ทำไมถึงลอดผ่าน proxy และ firewall ส่วนใหญ่ได้ และทำไม scheme ws:// กับ wss:// ถึงสะท้อน http:// และ https://

client เปิด TCP connection ธรรมดาและส่ง HTTP GET request — แต่พร้อม header พิเศษที่บอกว่า “ฉันอยากสลับการเชื่อมต่อนี้ไปเป็นโปรโตคอล WebSocket” สอง header ที่ทำงานจริงคือ Upgrade: websocket และ Sec-WebSocket-Key

GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Origin: https://example.com

มีบางตัวในนี้ที่ควรรู้จักไว้:

  • Upgrade: websocket และ Connection: Upgrade ทำงานร่วมกันเพื่อส่งสัญญาณการสลับโปรโตคอล
  • Sec-WebSocket-Key เป็นค่าสุ่มที่เข้ารหัส base64 ซึ่ง client สร้างขึ้นใหม่สำหรับการเชื่อมต่อนี้ ไม่ใช่ ความลับด้านความปลอดภัย แต่มีไว้ให้ client ยืนยันว่า response มาจาก server ที่เข้าใจ WebSocket handshake จริง ๆ
  • Sec-WebSocket-Version: 13 ตรึงเวอร์ชันของโปรโตคอลไว้ (13 คือเวอร์ชันมาตรฐาน)

ถ้า server พูด WebSocket ได้และยอมรับ ก็จะตอบกลับด้วยสถานะ 101 Switching Protocols — สถานะ HTTP ที่แปลตรงตัวว่า “จบ semantics ของ HTTP บนการเชื่อมต่อนี้แล้ว จากนี้ไปเป็น protocol คนละตัว”

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

ความมหัศจรรย์อยู่ที่ Sec-WebSocket-Accept server เอา Sec-WebSocket-Key ของ client มาต่อท้ายด้วย string คงที่ที่ spec กำหนดไว้ แฮชด้วย SHA-1 แล้วเข้ารหัส base64 ฝั่ง client คำนวณค่าเดียวกันแล้วเทียบว่าตรงกันไหม ถ้าตรง ก็แปลว่ากำลังคุยกับ server WebSocket ตัวจริง ไม่ใช่ cache ที่บังเอิญคืน 101 กลับมาแบบมั่ว ๆ ถ้าไม่ตรง ก็ไม่มีการเชื่อมต่อ

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
การ upgrade จาก HTTP request ไปสู่ WebSocket แบบสด

สังเกตสิ่งที่ไดอะแกรม ไม่ได้ แสดง: การเชื่อมต่อเส้นที่สอง หลัง 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 โดยไม่ตรวจ Origin header
  • เปิดช่องให้ cross-site WebSocket hijacking (CSWSH)
  • ตรวจสอบ Origin header เทียบกับ 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 ได้
server ตอบกลับด้วย HTTP status code อะไรเพื่อยอมรับการ upgrade ไปเป็น WebSocket?
จุดประสงค์ของ Sec-WebSocket-Key และ Sec-WebSocket-Accept คืออะไร?
หลังจาก response 101 เกิดอะไรขึ้นกับการเชื่อมต่อ?