Cơ chế WebSocket Handshake: HTTP Upgrade Header hoạt động thế nào?
WebSocket Handshake diễn ra thế nào? Phân tích HTTP Upgrade Header, Sec-WebSocket-Key/Accept, 101 Switching Protocols, subprotocol, proxy Nginx, bảo mật và debugging.
ShanverseUpdated 11 min read— viewsLoading views
WebSocket
Networking
“Nâng cấp HTTP” thực chất nghĩa là gì?
WebSocket là một protocol độc lập cho giao tiếp full-duplex. HTTP chỉ được dùng ở cửa vào để tận dụng:
URL, host và port quen thuộc.
Cookie/authentication hiện có.
Reverse proxy và load balancer.
TLS trên port 443.
Cơ chế status code nếu từ chối handshake.
Sau response 101 và dòng trống kết thúc header, hai phía không tiếp tục gửi HTTP request/response trên connection đó. Dữ liệu tiếp theo phải tuân theo WebSocket framing.
Mermaid
Loading diagram…
Comments
Toàn bộ luồng theo từng lớp
Bước 1: DNS và TCP
Với URL:
Client resolve DNS rồi mở TCP tới port 80 mặc định.
Với:
Client thường kết nối port 443.
Bước 2: TLS nếu là wss://
TLS handshake diễn ra trước HTTP Upgrade. Client kiểm tra certificate, thương lượng cipher và tạo encrypted channel. HTTP headers của handshake sau đó nằm bên trong TLS.
wss:// bảo vệ confidentiality và integrity trên đường truyền; nó không thay thế authentication hoặc authorization của ứng dụng.
Bước 3: client gửi HTTP Upgrade request
Ví dụ request:
Dòng trống cuối cùng kết thúc phần HTTP headers.
Ý nghĩa từng header quan trọng
Upgrade: websocket
Client đề nghị đổi protocol của connection hiện tại sang WebSocket.
Upgrade là hop-by-hop header: intermediary không được mặc định chuyển tiếp qua mọi hop. Reverse proxy phải được cấu hình để hiểu và forward nó.
Connection: Upgrade
Header này thông báo rằng Upgrade áp dụng cho connection hiện tại. Giá trị Connection có thể là danh sách token; server/proxy phải parse token thay vì so sánh chuỗi cứng nhắc.
Sec-WebSocket-Version: 13
Version 13 là version chuẩn phổ biến của WebSocket protocol. Nếu server không hỗ trợ version client yêu cầu, server có thể từ chối và thông báo version được hỗ trợ.
Sec-WebSocket-Key
Client tạo 16 byte ngẫu nhiên rồi Base64 encode. Giá trị này:
Thay đổi ở mỗi handshake.
Không phải password hoặc encryption key.
Không xác thực user.
Được server dùng để tính Sec-WebSocket-Accept.
Nó giúp chứng minh server hiểu WebSocket handshake thay vì một HTTP endpoint/proxy vô tình trả response có vẻ hợp lệ.
Origin
Browser thường gửi origin của trang tạo WebSocket. Đây là tín hiệu bảo mật quan trọng để chống Cross-Site WebSocket Hijacking khi cookie tự động được gửi.
WebSocket không sử dụng CORS preflight giống fetch. Server phải tự kiểm tra Origin theo allowlist.
Server có thể chọn một protocol trong danh sách và trả lại nó. Đây không phải transport protocol mới; nó là hợp đồng message ở tầng ứng dụng.
Sec-WebSocket-Extensions
Client đề nghị extension như permessage-deflate. Server chỉ được trả extension thực sự hỗ trợ và cấu hình an toàn; không echo tùy ý.
Server tính Sec-WebSocket-Accept thế nào?
Thuật toán:
GUID cố định của WebSocket:
Với key mẫu:
Kết quả:
Ví dụ Node.js:
Server trả 101 Switching Protocols
Response hợp lệ:
Client kiểm tra:
Status là 101.
Upgrade chứa token websocket.
Connection chứa token Upgrade.
Sec-WebSocket-Accept đúng với key đã gửi.
Subprotocol server chọn có nằm trong danh sách client đề nghị.
Extensions hợp lệ.
Nếu validation thất bại, client phải coi handshake là lỗi và đóng connection.
Vì sao là 101, không phải 200?
200 OK có nghĩa server đang trả một HTTP response thông thường. 101 Switching Protocols xác nhận hai phía đồng ý dừng semantics HTTP/1.1 trên connection này và chuyển sang WebSocket.
Các response lỗi vẫn là HTTP:
Sau 101, không còn cơ hội trả HTTP status cho lỗi message. Lỗi application phải dùng message protocol hoặc WebSocket close code.
Điều gì xảy ra ngay sau dòng trống?
Byte kế tiếp được parse như WebSocket frame:
Các nguyên tắc quan trọng:
Client → server frame phải được mask.
Server → client frame thông thường không mask.
TCP giữ thứ tự byte trong một connection.
Message có thể được chia thành nhiều frame.
Opcode phân biệt text, binary, continuation, close, ping và pong.
Control frame có payload tối đa 125 byte và không được fragment.
Text payload phải là UTF-8 hợp lệ.
Masking không mã hóa dữ liệu; wss:// mới cung cấp encryption trên đường truyền. Masking giúp tránh một số vấn đề với intermediary/proxy xử lý byte do client kiểm soát.
Handshake thủ công bằng curl
Có thể quan sát headers:
Nếu thành công, curl thấy 101. Tuy nhiên sau đó connection dùng binary WebSocket frames; curl không thay thế một WebSocket client đầy đủ để test message protocol.
Handshake mức thấp trong Node.js
Ví dụ sau chỉ minh họa switch protocol; không phải implementation production vì chưa parse frame, close, ping/pong, fragmentation và backpressure:
Biến head rất quan trọng: TCP là byte stream, nên runtime có thể đã đọc thêm byte sau HTTP headers. Bỏ head có thể làm mất frame đầu tiên.
Trong production, dùng thư viện đã kiểm thử:
Subprotocol negotiation đúng cách
Browser:
Server phải:
Parse danh sách protocol client đề nghị.
Chọn đúng một protocol hỗ trợ.
Trả protocol đó trong Sec-WebSocket-Protocol.
Từ chối nếu application bắt buộc protocol nhưng không có điểm chung.
Không được trả một protocol client chưa đề nghị.
Authentication diễn ra ở đâu?
Cookie/session
Browser tự gửi cookie phù hợp trong handshake. Đây là cách tự nhiên cho same-site app, nhưng server phải:
Validate Origin.
Áp dụng Secure, HttpOnly, SameSite phù hợp.
Kiểm tra session trước khi trả 101.
Authorize lại theo từng room/topic/message.
Bearer token
Browser WebSocket API không cho đặt arbitrary Authorization header. Các phương án thường gặp:
HttpOnly session cookie.
Short-lived, single-use handshake ticket lấy trước bằng HTTPS.
Token trong subprotocol theo thiết kế có kiểm soát.
Query token ngắn hạn—chỉ khi log/redaction đã xử lý tốt.
Không đặt access token dài hạn trong URL vì URL có thể xuất hiện trong proxy log, analytics hoặc error report.
Authenticate bằng message đầu tiên?
Có thể, nhưng connection đã được upgrade và chiếm tài nguyên trước khi auth hoàn tất. Nếu dùng cách này, phải đặt deadline rất ngắn, giới hạn message và không subscribe dữ liệu trước khi authenticate.
Reverse proxy phải làm gì?
Upgrade và Connection là hop-by-hop headers. Với Nginx:
Heartbeat ping/pong phải ngắn hơn idle timeout một khoảng an toàn. Đặt timeout vô hạn không thay thế health monitoring và connection cleanup.
Load balancer và horizontal scaling
Handshake chỉ tạo connection tới một backend cụ thể. Sau đó connection thường sống trên backend đó đến khi đóng.
Khi scale ngang:
Load balancer phải hỗ trợ WebSocket và timeout phù hợp.
Backend cần graceful drain khi deploy.
Broker như Redis, NATS hoặc Kafka phân phối event giữa các node.
Sticky session không thay thế shared state/event layer.
Connection registry cần ownership, TTL và cleanup.
Nếu backend chết, connection chết theo; client phải reconnect và resubscribe.
HTTP/2 và HTTP/3 có còn dùng Upgrade?
Classic WebSocket handshake với Connection: Upgrade là cơ chế HTTP/1.1. HTTP/2 không cho dùng các connection-specific headers đó.
WebSocket qua HTTP/2 dùng Extended CONNECT với pseudo-header :protocol = websocket khi client, intermediary và server cùng hỗ trợ. HTTP/3 có cơ chế Extended CONNECT tương ứng. Vì support phụ thuộc toàn bộ đường truyền, nhiều deployment vẫn dùng HTTP/1.1 Upgrade cho WebSocket ngay cả khi website phục vụ HTTP/2 hoặc HTTP/3.
Không copy Connection: Upgrade vào request HTTP/2 rồi kỳ vọng nó hoạt động.
Extensions và permessage-deflate
Compression có thể giảm bandwidth cho text lớn nhưng làm tăng:
CPU và memory theo connection.
Nguy cơ compression side channel nếu secret và dữ liệu attacker-controlled bị nén chung.
Độ phức tạp về context takeover và window size.
Chỉ bật sau benchmark và giới hạn tài nguyên. Payload nhỏ có thể tốn hơn lợi ích nén.
Bảo mật handshake production
Trước khi trả 101:
Path và HTTP method hợp lệ.
Upgrade, Connection, version và key được validate.
TLS bắt buộc trên Internet.
Host và Origin nằm trong allowlist.
User/session/ticket đã authenticate.
User được phép mở loại connection này.
Subprotocol và extensions được negotiate rõ ràng.
Có rate limit theo IP, account và tenant.
Có giới hạn connection toàn hệ thống và mỗi user.
Sau khi upgrade:
Authorize từng action/topic/room.
Validate schema, opcode, UTF-8 và message size.
Có ping/pong heartbeat và idle timeout.
Có backpressure và outbound queue limit.
Không log token hoặc payload nhạy cảm.
Close code/reason không làm lộ thông tin nội bộ.
Debugging Handshake
Browser báo Unexpected response code: 200
Request rơi vào HTTP route bình thường; proxy không forward Upgrade hoặc endpoint sai.
400 Bad Request
Thiếu/sai Sec-WebSocket-Key, version, Connection token hoặc request đi qua proxy đã strip header.
403 Forbidden
Origin/auth/authorization policy từ chối. Kiểm tra allowlist và cookie có được gửi không.
502 Bad Gateway
Proxy không tới được WebSocket upstream, sai port/protocol hoặc backend không lắng nghe sự kiện upgrade.
Kết nối thành công rồi đóng sau 60 giây
Idle timeout của proxy/load balancer thường là nguyên nhân. Thêm ping/pong hợp lý và cấu hình timeout nhất quán.
Thành công local nhưng thất bại production
Kiểm tra toàn bộ chain:
Mỗi hop phải hỗ trợ protocol, timeout và forwarding đúng.
Checklist quan sát bằng DevTools
Trong Network → WS:
Request URL dùng ws:// hay wss:// đúng môi trường.
Status 101 với HTTP/1.1 handshake.
Request có Upgrade, Connection, key, version và Origin.
Response có Sec-WebSocket-Accept hợp lệ.
Subprotocol client/server thống nhất.
Frames xuất hiện sau handshake.
Close frame có code/reason gì.
Có ping/pong ở hạ tầng hoặc application heartbeat.
Hướng dẫn kiến trúc và triển khai Live Presence, multiplayer cursors và Collaborative Editing bằng Yjs CRDT, WebSocket, awareness, Redis, persistence và scale-out.