WebSocket 완전 정복 · 1부. 기초 · 16편 중 2편
이전 편: 1편 — HTTP로는 왜 안 되는가
1편에서 WebSocket이 왜 필요한지를 봤습니다. 이번 편은 각론에 들어가기 전의 지도입니다 — 이 프로토콜을 정의하는 문서가 무엇이고, 주소는 어떻게 쓰며, 연결 하나가 열려서 닫히기까지 어떤 단계를 지나는지를 한 번에 훑습니다. 핸드셰이크 헤더, 프레임 구조, 인증은 각각 3편·6편·10편에서 따로 다룹니다.
RFC 6455 — 하나의 표준으로 수렴하기까지
WebSocket은 RFC 6455(2011)로 표준화된 프로토콜입니다. TCP 위에서 동작하며, 시작만 HTTP로 하고 이후에는 HTTP와 무관한 자체 프레임 형식을 씁니다.
핸드셰이크 요청에는 Sec-WebSocket-Version: 13이라는 헤더가 실립니다. RFC 6455는 이 값이 반드시 13이어야 한다고 못 박고, 서버가 그 버전을 지원하지 않으면 자기가 받아들일 수 있는 버전 목록을 같은 이름의 헤더에 담아 응답하도록 정해 뒀습니다. 그런데 표준의 첫 버전인데 왜 1이 아니라 13일까요.
답은 RFC 6455가 만든 IANA WebSocket 버전 번호 레지스트리에 있습니다. 0부터 8까지는 표준화 이전의 인터넷 초안(draft-ietf-hybi-thewebsocketprotocol-00부터 -08)에 배정된 "Interim" 번호이고, 9부터 12는 예약, 13이 RFC 6455 자체를 가리키는 유일한 "Standard" 번호입니다. 레지스트리는 Interim 번호의 용도를 "이 RFC가 발행되기 전에 개발된 것과 같은, 이미 배포된 버전들을 구현자가 식별하고 상호운용하기 위한 것"이라고 설명합니다. 표준이 확정되기 전에 초안 단계의 구현들이 실제로 돌아가고 있었고, 그것들이 서로를 구분하느라 번호를 소비했다는 뜻입니다. 지금도 남은 흔적은 이 버전 번호 하나입니다.
한 가지 덧붙이면, 이 시리즈가 다루는 것은 HTTP/1.1의 Upgrade로 시작하는 RFC 6455의 WebSocket입니다. HTTP/2와 HTTP/3 위에서 WebSocket을 여는 방법은 별도 규격(RFC 8441, RFC 9220의 Extended CONNECT)으로 정의돼 있습니다. 여기서는 별도 규격이 있다는 사실까지만 알아 두고, 16편 — 생태계와 대안에서 다시 짚습니다.
ws://와 wss:// — 스킴과 기본 포트, URI 규칙
ws://: 평문, 기본 포트 80wss://: TLS, 기본 포트 443
RFC 6455가 정한 URI 문법은 HTTP URL과 거의 같습니다. 브라우저에서 new WebSocket()에 넘기는 문자열이 바로 이것입니다.
ws://host[:port]/path[?query]
wss://host[:port]/path[?query]- 포트는 생략할 수 있습니다. 생략하면
ws는 80,wss는 443으로 해석되므로wss://example.com/chat과wss://example.com:443/chat은 같은 곳을 가리킵니다. - 경로가 비어 있으면
/가 됩니다. 핸드셰이크의 GET 줄에 실리는 리소스 이름은 경로에 쿼리를 이어 붙인 것이고, 경로가 없으면/로 채워집니다. - fragment는 쓸 수 없습니다. HTTP URL에 흔한
#section같은 조각 식별자는 WebSocket URI에서 의미가 없고, 규격은 이를 금지합니다.#문자가 다른 용도로 필요하면%23으로 이스케이프해야 합니다. - 스킴 비교는 대소문자를 가리지 않습니다. 스킴이
wss와 대소문자 구분 없이 일치하면 secure로 취급됩니다.
실서비스에서 wss://만 쓰는 이유
실서비스에서는 반드시 wss://를 쓰세요. 보안 이유도 있지만, 중간의 프록시나 방화벽이 평문 Upgrade 요청을 망가뜨리는 경우가 많기 때문이기도 합니다. TLS 안에 숨기면 중간 장비가 내용을 건드리지 못합니다.
연결의 생애주기
연결의 생애: HTTP 핸드셰이크(Upgrade) → 데이터 프레임 교환 → Ping/Pong 생존 확인 → Close 핸드셰이크로 종료.
이 한 줄을 상태 단위로 펼치면 다음과 같습니다.
CONNECTING (readyState 0)
클라이언트 → 서버 : HTTP GET + Upgrade: websocket
서버 → 클라이언트 : HTTP/1.1 101 Switching Protocols
│
▼
OPEN (readyState 1)
클라이언트 ⇄ 서버 : 데이터 프레임 (텍스트/바이너리, 양방향)
클라이언트 ⇄ 서버 : Ping / Pong (생존 확인)
│ 한쪽이 Close 프레임을 보냄
▼
CLOSING (readyState 2)
상대가 Close 프레임으로 응답
│ 밑의 TCP 연결이 닫힘
▼
CLOSED (readyState 3)네 상태의 이름과 숫자는 브라우저 WebSocket 객체의 readyState가 돌려주는 값 그대로입니다(4편 — 브라우저 API). 규격의 용어로는 Close 프레임을 보내거나 받은 순간 연결이 CLOSING 상태에 들어가고, 밑의 TCP 연결이 닫혀야 CLOSED가 됩니다. Close 핸드셰이크를 마친 뒤에 TCP가 닫혔으면 "깨끗하게(cleanly)" 닫힌 것이고, 그렇지 않으면 아닙니다 — 4편의 onclose 이벤트에 실려 오는 wasClean이 바로 이 구분입니다.
첫 단계인 핸드셰이크에는 Origin 헤더가 실립니다. 브라우저가 페이지의 출처를 자동으로 적어 보내는 값이고, 서버는 이것으로 어느 사이트의 스크립트가 연결을 열려고 하는지를 판단해 원하지 않는 출처면 403으로 거절합니다. 다만 이 헤더의 목적은 정확히 여기까지입니다. RFC 6455는 Origin의 의도가 브라우저가 아닌 클라이언트의 접속을 막는 것이 아니라, 신뢰할 수 있는 브라우저가 악성 스크립트의 통제 아래 핸드셰이크를 위조하지 못하게 하는 것이라고 분명히 합니다. 브라우저 밖의 클라이언트(curl, 다른 서버, 직접 짠 프로그램)는 Origin을 원하는 값으로 써서 보낼 수 있습니다. 그래서 Origin 검사는 브라우저발 요청에 대한 방어이지 인증이 아닙니다. 이 검사가 막아 주는 공격(CSWSH)과 진짜 인증 설계는 10편 — 인증과 인가에서 다룹니다.
각 단계의 상세는 뒤 편에 있습니다. 핸드셰이크는 3편, 데이터 프레임은 6편, Ping/Pong과 Close는 7편입니다.
왜 TCP를 직접 쓰지 않고 WebSocket인가
WebSocket이 하는 일을 늘어놓고 보면 "메시지 단위로 양방향 통신"뿐인데, 이것은 TCP 소켓으로도 됩니다. 왜 굳이 HTTP로 시작하는 프로토콜을 하나 더 만들었을까요.
첫째, 브라우저는 스크립트에 임의의 TCP 소켓을 열어 주지 않습니다. 웹 페이지의 스크립트가 할 수 있는 네트워크 접근은 출처(origin) 기반 보안 모델 안에 있어야 합니다. RFC 6455는 WebSocket의 설계 의도를 "웹의 제약 안에서 가능한 한 날것의 TCP를 스크립트에 노출하는 것에 가깝게"라고 표현하고, 그 제약의 첫 항목으로 브라우저를 위한 출처 기반 보안 모델을 듭니다. 앞 절의 Origin 헤더가 그 일부입니다.
둘째, HTTP Upgrade로 시작하기 때문에 이미 깔린 인프라를 그대로 지납니다. 핸드셰이크가 유효한 HTTP 요청이므로 WebSocket 서버는 HTTP 서버와 같은 포트(80/443)를 나눠 쓸 수 있고, 방화벽과 프록시 입장에서는 처음에 평범한 HTTP 요청이 지나가는 것으로 보입니다. 규격은 이것을 "HTTP 및 이미 배포된 HTTP 인프라(프록시 등)와 공존할 수 있는 비교적 단순한 프로토콜"이라고 설명합니다. 물론 101 이후에는 더 이상 HTTP가 아니므로 중간 장비가 그 사실을 모르면 문제가 생깁니다. 그 이야기는 앞에서 본 wss:// 권고와 14편 — 인프라로 이어집니다.
셋째, TCP는 바이트 스트림이라 메시지 경계가 없습니다. WebSocket은 그 위에 최소한의 프레이밍을 얹어 메시지 단위와 텍스트/바이너리 구분을 되돌려 주고, 프록시가 끼어 있어도 동작하도록 설계된 Close 핸드셰이크를 더합니다. 규격의 표현으로는 "그 밖에는 아무것도 더하지 않는다"입니다. 1편에서 본 "메시지 단위"라는 특징은 여기서 옵니다.
더 깊이
- 풀스택 개발자를 위한 네트워크 4장 — 전송 계층: TCP와 UDP, 포트, 소켓: WebSocket이 올라타는 TCP 연결과 소켓이 실제로 무엇인지, 바이트 스트림이라는 말이 무슨 뜻인지를 다룹니다.