"오늘날 HTTP/1.1, HTTP/2, HTTP/3가 함께 쓰여. 메시지가 뜻하는 바는 이어지고, 네트워크에 싣는 방식이 달라졌어."
가계도
HTTP는 응용 계층의 계약을 최대한 유지하면서 진화해 왔어. 이 퀘스트에서 배우는 메서드, 상태 코드, 헤더, URI의 의미는 버전이 달라도 이어져. 크게 달라지는 부분은 전송 표현, 즉 그 정보를 네트워크 위에 어떤 형식으로 싣느냐야.
- HTTP/0.9 (1991) — 한 줄짜리 GET 요청과 본문 응답만 있던 아주 단순한 형식이야. 지금은 역사적인 버전으로 남아 있어.
- HTTP/1.0 (1996) — 상태 줄과 헤더, 다양한 메서드가 자리 잡았어. 지속 연결이 기본이 아니어서 보통 요청마다 새 TCP 연결을 열었지.
- HTTP/1.1 (1997, 2022년 RFC 9112로 정비) — 지속 연결을 기본으로 삼고, 청크 전송 인코딩과 필수
Host헤더, 파이프라이닝을 정의했어. 시작 줄과 헤더를 텍스트로 표현하므로 netcat에서 직접 작성해 볼 수 있어. - HTTP/2 (2015, RFC 9113) — 이진 프레이밍, 하나의 TCP 연결에서 여러 스트림을 처리하는 다중화, HPACK 헤더 압축, 스트림 우선순위 기능을 도입했어. 의미론은 유지하면서 전송 효율을 높였지.
- HTTP/3 (2022, RFC 9114) — TCP 대신 UDP 기반 QUIC 위에서 동작해. TLS 1.3을 통합하고 스트림별 손실 복구와 연결 이동을 지원하면서도 HTTP 의미론은 그대로 이어 가.
각 버전이 해결한 문제
HTTP/1.1의 대표 변화는 지속 연결이야. TCP 연결을 열어 둔 채 다음 요청에 재사용해 연결 설정 비용을 줄였어. 필수 Host 헤더는 하나의 IP 주소에서 여러 도메인을 구분하는 가상 호스팅을 안정적으로 만들었고.
HTTP/2의 대표 변화는 다중화야. 여러 요청과 응답을 프레임으로 나눠 하나의 TCP 연결에서 번갈아 전송해. 이전에는 브라우저가 같은 출처의 자산을 동시에 가져오려고 여러 TCP 연결을 열었지만, HTTP/2에서는 하나의 연결에 여러 스트림을 실을 수 있어. HPACK 헤더 압축은 반복해서 전송되던 User-Agent, Cookie, Accept-* 같은 필드의 중복도 줄여 줘.
HTTP/3의 대표 변화는 스트림 사이의 전송 계층 head-of-line blocking을 줄인 거야. HTTP/2는 모든 스트림이 하나의 TCP 바이트 스트림을 공유하므로 패킷 하나가 유실되면 복구될 때까지 다른 스트림의 데이터 전달도 막힐 수 있어. QUIC은 손실 복구를 스트림별로 다뤄 영향 범위를 줄이고, 더 빠른 연결 설정과 네트워크 변경 시 연결 이동도 지원해.
버전을 의식해야 할 때
애플리케이션 코드에서는 버전을 직접 다룰 일이 많지 않아. HTTP 클라이언트(httpx, fetch, axios, curl)와 서버가 지원 범위 안에서 버전을 협상하고, 대체로 같은 API를 제공하기 때문이야.
그래도 버전 차이가 중요한 상황이 있어:
- 모바일 네트워크 — 패킷 손실이 잦은 환경에서는 HTTP/3의 스트림별 손실 복구가 유리할 수 있어.
- 작은 자산이 많을 때 — HTTP/2와 HTTP/3의 다중화는 HTTP/1.1에서 여러 연결을 관리하던 부담을 줄여 줘.
- 오래 유지되는 스트리밍 — HTTP/2에서는 SSE 스트림과 일반 요청을 별도 스트림으로 나눠 하나의 연결에서 함께 처리할 수 있어.
- 방화벽이나 기업 네트워크 — UDP가 차단되면 HTTP/3 연결이 실패할 수 있으므로 HTTP/2나 HTTP/1.1로 대체할 경로가 필요해.
- 디버깅 — HTTP/2의 전송 형식은 이진 프레임이라 원시 바이트를 눈으로 읽기 어려워.
curl --http1.1로 HTTP/1.1 교환을 보거나, HTTP/2는 Wireshark와 브라우저 DevTools 같은 도구로 해석해서 확인해.