HTTP
HTTP
HTTP는 처음 등장한 이후 지금까지 “더 빠르게, 더 효율적으로” 통신하기 위한 방향으로 계속 발전해왔다. 각 버전은 이전 버전에서 드러난 성능 한계를 해결하기 위해 등장했고, 그 한계를 이해하면 다음 버전이 왜 그런 구조를 택했는지 자연스럽게 이해할 수 있다.
HTTP/1
- 정의: 1996년에 표준화된 초기 HTTP(HTTP/1.0)로, 클라이언트가 요청을 보내고 서버가 응답하는 가장 기본적인 요청-응답(Request-Response) 구조를 확립했다.
- 동작 원리
- 통신 내용이 사람이 읽을 수 있는 텍스트(Plain Text) 형태로 오간다 (
GET /index.html HTTP/1.0같은 요청 라인 + 헤더 + 바디). - 요청 하나를 보낼 때마다 새로운 TCP 연결을 맺고, 응답을 받으면 그 연결을 바로 끊는다(Non-persistent Connection). 즉 요청과 TCP 연결이 1:1로 대응한다.
- 상태 코드(200, 404 등),
Content-Type같은 헤더가 도입되어 HTML뿐 아니라 이미지, 파일 등 다양한 형식을 주고받을 수 있게 되었다.
- 통신 내용이 사람이 읽을 수 있는 텍스트(Plain Text) 형태로 오간다 (
- 한계
- 웹페이지 하나를 렌더링하려면 HTML, CSS, JS, 이미지 등 리소스를 각각 별도로 요청해야 하는데, 리소스마다 TCP 3-way handshake(그리고 HTTPS라면 TLS handshake까지)를 매번 새로 거쳐야 해서 오버헤드가 매우 컸다.
- 페이지에 리소스 수가 늘어날수록 “연결 수립 → 요청 → 응답 → 연결 종료”를 반복하는 비용이 누적되어 체감 속도가 크게 느려졌다.
HTTP/1.1
1997년에 표준화되며 HTTP/1.0의 “매 요청마다 새 연결”이라는 구조적 비효율을 개선하는 데 초점을 맞췄다.
- Persistent Connection (Keep-Alive)
- 하나의 TCP 연결을 끊지 않고 유지하면서 여러 번의 요청/응답을 순차적으로 주고받을 수 있게 되었다(기본값으로 활성화).
- 매 요청마다 TCP handshake를 반복하지 않아도 되므로 연결 수립 오버헤드가 크게 줄었다.
- Pipelining
- 이전 응답을 기다리지 않고 다음 요청을 연달아 보낼 수 있는 기능도 추가되었지만, 서버는 응답을 반드시 요청이 온 순서대로 돌려줘야 한다는 제약이 있어 실효성이 크지 않았고, 대부분의 브라우저가 기본적으로 비활성화했다.
- 그 외 개선
Host헤더가 필수화되어 하나의 IP 주소로 여러 도메인을 운영하는 가상 호스팅(Virtual Hosting)이 가능해졌다.Chunked Transfer Encoding이 도입되어 응답 크기(Content-Length)를 미리 알 수 없는 상황에서도 데이터를 조각(chunk) 단위로 스트리밍할 수 있게 되었다.Cache-Control등 캐시 관련 헤더가 강화되어 불필요한 재요청을 줄일 수 있게 되었다.
- 여전히 남은 한계: HOL(Head-of-Line) Blocking
- 하나의 연결에서는 응답이 순서대로 와야 하기 때문에, 앞선 요청의 응답이 늦어지면 뒤에 대기 중인 요청들도 함께 지연된다.
- 브라우저들은 이를 우회하기 위해 도메인당 6개 정도의 TCP 연결을 동시에 맺는 방식으로 병렬성을 확보했지만, 이는 연결 수 자체가 늘어나 서버/네트워크 자원을 더 많이 소모하고, 여전히 각 연결 안에서는 순서를 기다려야 하는 근본적인 해결책은 아니었다.
HTTP/2
2015년 Google의 SPDY 프로토콜을 기반으로 표준화되었으며, HTTP/1.1의 HOL Blocking 문제를 HTTP 계층에서 해결하는 데 집중했다.
- 바이너리 프레이밍(Binary Framing Layer): 텍스트 기반 메시지 대신 바이너리 형식의 프레임 단위로 통신한다. 파싱이 더 빠르고 정확하며 파싱 오류의 여지가 줄어든다.
- 멀티플렉싱(Multiplexing): 하나의 TCP 연결 위에서 여러 개의 스트림(요청/응답)을 동시에 주고받을 수 있다. 각 스트림의 프레임이 서로 잘게 쪼개져 인터리빙(interleaving)되어 전송되므로, 앞선 요청 하나가 느리다고 해서 뒤의 다른 요청까지 밀리지 않는다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
HTTP/1.1 (up to ~6 parallel TCP connections per domain)
conn A: [--- req 1 ---][--- req 2 ---]
conn B: [--- req 3 ---][--- req 4 ---]
conn C: [--- req 5 ---]
-> requests on the SAME connection are still processed strictly in order
(a slow request 1 blocks request 2 right behind it)
HTTP/2 (1 TCP connection, multiple multiplexed streams)
stream 1: [-req-][-------resp-------]
stream 2: [-req-][--resp--]
stream 3: [-req-][----resp----]
connection: |--------------------------------------------|
frames from all 3 streams are interleaved on the same connection
-> a slow stream no longer blocks the other streams at the HTTP layer
- 헤더 압축(HPACK): 요청마다 반복되는 헤더(쿠키, User-Agent 등)를 압축 테이블로 관리해 중복 전송을 줄인다.
- 스트림 우선순위(Stream Prioritization): 클라이언트가 스트림 간 우선순위와 의존 관계를 지정해, 중요한 리소스(예: CSS)를 먼저 받도록 서버에 힌트를 줄 수 있다.
- 서버 푸시(Server Push): 클라이언트가 요청하기 전에 서버가 필요할 것으로 예상되는 리소스를 미리 보내주는 기능도 도입되었으나, 실제로는 캐시 활용도가 낮고 오히려 불필요한 데이터를 보내는 경우가 많아 이후 대부분의 브라우저에서 지원이 중단되었다.
- 여전히 남은 한계: TCP 레벨 HOL Blocking
- HTTP 계층에서는 스트림을 나눴지만, 결국 이 스트림들은 하나의 TCP 연결을 공유한다.
- TCP는 패킷을 순서대로 전달해야 하는 프로토콜이라, 그중 패킷 하나라도 유실되면 TCP가 재전송을 마칠 때까지 같은 연결을 쓰는 모든 스트림이 함께 멈춰버린다. HTTP 계층의 멀티플렉싱만으로는 해결할 수 없는, 전송 계층(TCP) 자체의 구조적 한계다.
HTTP/3 on QUIC
HTTP/2가 해결하지 못한 TCP 레벨의 HOL Blocking을 근본적으로 없애기 위해, 아예 전송 계층을 TCP에서 UDP 기반의 QUIC으로 교체한 버전이다. 2022년 정식 표준(RFC 9114)으로 제정되었다.
- 독립적인 스트림: QUIC은 스트림마다 유실/재전송을 독립적으로 관리한다. 한 스트림에서 패킷이 유실되어도 그 스트림만 재전송을 기다릴 뿐, 다른 스트림은 영향을 받지 않는다 → TCP 레벨 HOL Blocking이 완전히 사라진다.
- 연결과 TLS의 통합, 빠른 handshake: 기존에는 TCP handshake와 TLS handshake가 별도로 이루어져 왕복(RTT)이 여러 번 필요했지만, QUIC은 전송 계층 연결 수립과 TLS 1.3 handshake를 하나로 합쳐 1-RTT(신규 연결) 또는 0-RTT(이전에 접속한 적 있는 서버에 재연결)로 통신을 시작할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
TCP + TLS 1.2 (typical HTTPS handshake, ~2-3 RTT before any data)
Client Server
|------ TCP SYN ------------------------->|
|<----- TCP SYN-ACK -----------------------| RTT 1 (TCP handshake)
|------ TCP ACK --------------------------->|
|------ TLS ClientHello ------------------->|
|<----- TLS ServerHello, Cert, ... ---------| RTT 2 (TLS handshake)
|------ TLS Finished ----------------------->|
|------ HTTP request ------------------------>| RTT 3 (first real data)
QUIC (transport + TLS 1.3 combined, 1-RTT / 0-RTT)
Client Server
|------ QUIC Initial (+ TLS ClientHello) ->|
|<----- QUIC Initial (+ TLS ServerHello) ---| RTT 1 (transport + TLS combined)
|------ HTTP request ------------------------>| data can follow right after RTT 1
(0-RTT possible on reconnect with cached session)
- Connection Migration(연결 마이그레이션): TCP는 (출발지 IP, 출발지 Port, 목적지 IP, 목적지 Port) 조합으로 연결을 식별하기 때문에 와이파이에서 LTE로 네트워크가 바뀌면 연결이 끊긴다. QUIC은 IP/Port 대신 자체적인 Connection ID로 연결을 식별하므로, 네트워크가 바뀌어도 같은 연결을 그대로 이어갈 수 있다 → 모바일 환경에서 특히 유리하다.
- 기본 암호화: QUIC은 TLS 1.3을 프로토콜 자체에 내장하고 있어 평문 통신을 지원하지 않는다. 즉 QUIC 위에서는 항상 암호화된 통신이 이루어진다.
- QPACK: HTTP/2의 HPACK과 유사한 헤더 압축 기법이지만, 스트림 간 독립성을 해치지 않도록 재설계된 압축 방식을 사용한다.
정리
| 구분 | HTTP/1.0 | HTTP/1.1 | HTTP/2 | HTTP/3 (QUIC) |
|---|---|---|---|---|
| 전송 계층 | TCP | TCP | TCP | UDP 기반 QUIC |
| 연결 방식 | 요청마다 새 연결 | Persistent Connection | 단일 연결 + 멀티플렉싱 | 단일 연결(Connection ID) + 멀티플렉싱 |
| 데이터 형식 | 텍스트 | 텍스트 | 바이너리 프레임 | 바이너리 프레임 |
| 헤더 압축 | 없음 | 없음 | HPACK | QPACK |
| HOL Blocking | 요청이 직렬 처리되어 사실상 없음(대신 매우 느림) | 있음 (연결 내) | TCP 레벨에서 있음 | 없음 (스트림이 서로 독립적) |
| Handshake | TCP만 | TCP만 | TCP + TLS 별도 | 통합 1-RTT/0-RTT |
| 네트워크 전환 시 | 연결 끊김 | 연결 끊김 | 연결 끊김 | Connection Migration으로 유지 |
결국 HTTP의 발전 과정은 “연결을 얼마나 적게, 얼마나 효율적으로 맺고 유지할 것인가”와 “여러 요청을 어떻게 서로 방해하지 않고 동시에 처리할 것인가”라는 두 가지 문제를 계속 더 깊은 계층(애플리케이션 → TCP 위의 멀티플렉싱 → 전송 계층 자체 교체)에서 풀어온 과정이라고 볼 수 있다.
This post is licensed under CC BY 4.0 by the author.



