![[Network] HTTP/1.0, HTTP/1.1, HTTP/2.0, HTTP/3.0](https://img1.daumcdn.net/thumb/R750x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFJlqu%2FbtsNumblrjo%2FYKxLAdtL9yHLFJ9f8CxX3k%2Fimg.jpg)
웹은 정적 문서 몇 장을 보여주던 시대를 지나, 실시간 영상 스트리밍부터 멀티플레이 게임까지 소화하는 거대한 플랫폼이 되었다. 하지만 이 모든 것의 기반이 되는 HTTP는 아주 단순한 프로토콜에서 출발했다.
HTTP는 웹의 진화와 함께 끊임없이 변화해왔고, 각 버전은 이전 버전의 문제를 해결하려는 시도에서 태어났다. 이 글을 통해 HTTP/1.0부터 HTTP/3.0까지 각 버전이 어떻게 등장했고, 어떤 문제를 해결했으며, 그 과정에서 또 어떤 새로운 고민을 남겼는지 살펴보자.
📡 HTTP/1.0
문서 요청 및 응답
HTTP/1.0 이전에는 사실 웹이라는 게 그렇게 널리 퍼져 있지도 않았고, 웹페이지를 본다는 것도 정말 단순한 일이었다. 텍스트 파일 하나 다운로드하듯이 HTML 문서 하나 가져오면 끝이었다. 이미지나 자바스크립트 같은 기술은 그 당시에 존재하지 않았다.
하지만 HTTP/1.0이 나오면서 브라우저가 서버한테 “이 파일 줘”라고 말하는 방식이 공식적으로 정해졌고, 서버는 “OK”라고 대답하는 방식이 생겼다. 이게 바로 ‘요청(request) - 응답(response)’구조다.
우리는 지금 이걸 너무 당연하게 생각하지만, 이게 없었다면 브라우저는 서버가 뭘 원하는지 매번 통일되지 않은 방식으로 설명했어야 했고, 서버도 그걸 해석하기 위해 각 브라우저마다 따로 코드를 짜야 했다.
HTTP/1.0은 웹 전체가 같은 언어를 쓰도록 규칙을 만들어 준 것, 인터넷 위에 표준화된 문서 요청 방식을 정의한 것이 가장 큰 특징이다.
단점
문제는 HTTP 스펙이 너무 단순했다는 점이다. 요청을 하나 하면, 서버는 응답을 보내고 TCP 연결을 끊는다. 다음에 또 요청하려면 다시 연결해야한다.
이 구조에서 TCP 연결을 매번 새로 만드는 비용이 생각보다 크다. 여기서 RTT$_{Round-Trip \space Time}$라는 개념이 나온다.
RTT
작은 패킷 하나를 보내고 그 응답이 돌아오기까지 걸리는 시간을 의미한다. HTTP 요청을 하려면 TCP 연결을 만들기 위한 handshake만 해도 1 RTT가 필요하고, 요청-응답 자체도 1 RTT가 더 들기 때문에 최소 2 RTT + 데이터 전송 시간이 걸리는 구조다.
HTML 문서 요청 → 연결 설정 (1 RTT) + 요청/응답 (1 RTT) + 데이터 전송
- 이미지 1 → 또 같은 과정 반복
- 이미지 2 → 또 반복
웹 페이지에 이미지가 10개 있으면? 10번 연결 만들고 끊는 걸 반복한다. 초창기에는 이런 현상이 그렇게 크지 않았을 것이다. 하지만 웹 페이지는 시간이 지날수록 점점 복잡해졌다. HTML 문서 하나만 가져오는 것이 아니라, CSS, JS 등등 다양한 파일들이 같이 필요하게 되었다. 그 때부터 이 구조가 아주 비효율적으로 작동하기 시작했다.
결국 HTTP/1.0은 웹의 통일된 규칙을 정했다는 점에서 의의가 있지만, 많은 요청과 응답을 주고받아야 하는 현재의 상황에는 너무 단순해 맞지 않았던 것이다.
📡 HTTP/1.1
HTTP/1.0은 말 그대로 ‘요청-응답’만 처리하고 곧바로 연결을 닫아버리는 구조였다. 이 방식이 처음엔 단순하고 깔끔했지만, 시간이 지나면서 웹페이지에 포함된 객체 수가 많아졌다. RTT 오버헤드, 연결 재사용 불가 같은 단점들이 눈에 띄게 드러났다.
그래서 1997년에 등장한 HTTP/1.1은 이 문제를 해결하기 위해 가장 먼저 TCP 연결을 “끊지 않는” 방식을 표준으로 삼았다. 이걸 persistent connection이라 부른다.
Persistent HTTP
HTTP/1.1에서는 한 번 연결을 열고, 그 연결을 통해 여러 개의 HTTP 요청을 순차적으로 보내고 응답을 받는 방식이 기본이 되었다.
이제 웹페이지를 불러올 때
- HTML을 요청하고
- 연결을 그대로 유지한 채
- 이미지, CSS, JS 등등을 순차적으로 요청
할 수 있다. 매번 TCP 연결을 새로 만들 필요가 없어진 것이다. 이게 바로 HTTP/1.1이 HTTP/1.0보다 훨씬 빠르고 효율적인 이유다.
Pipelining
HTTP/1.1은 pipelining이라는 기능도 제안했다. 클라이언트가 여러 개의 요청을 한꺼번에 보내고, 서버가 순서대로 응답하는 방식이다.
요청과 응답이 겹치니까, RTT를 줄일 수 있을 거라는 기대가 있었다. 하지만 현실은 조금 달랐다.
단점: Head-of-Line Blocking
pipelining은 요청을 동시에 보낼 수는 있어도, 서버가 응답을 반드시 순서대로 보내야 한다는 제약이 있었다. 즉, 첫 번째 요청 응답이 느리면 뒤에 빠르게 처리될 수 있는 요청들도 기다려야 한다. 이걸 Head-of-Line Blocking(HOL blocking)이라 부른다.
결국 pipelining은 이론상으로는 좋았지만, 실제로는 거의 사용되지 않았다.
개선안
브라우저는 HTTP/1.1의 persistent connection만으로는 여전히 부족하다고 느꼈다. 그래서 동시에 여러 개의 TCP 연결을 열기 시작했다. 예를 들어, 하나의 서버에 6개의 병렬 연결을 동시에 열어두고 그 안에서 동시에 요청들을 처리하는 방식으로 성능을 뽑았다.
하지만 이건 어디까지나 임시방편이었다. TCP 연결 수가 늘면 서버 리소스는 또 부담되고, 결국 이 구조 자체가 비효율이라는 점은 여전히 남았던 것이다.
중간 정리
HTTP/1.1은 HTTP/1.0의 구조적 비효율을 개선하는 데 큰 역할을 했다.
- 연결을 매번 닫는 비효율 → persistent connection으로 해결
- 여러 요청을 겹쳐서 보내기 위한 시도 → pipelining
- 병렬 처리는 결국 여러 연결을 동시에 여는 식으로 대체됨
이런 상황에서 진짜로 ‘병렬 처리’가 제대로 동작하는 HTTP가 필요하게 되었고, 그게 바로 HTTP/2였다.
📡 HTTP/2
HTTP/1.1까지의 웹은 겉으로 보기엔 꽤 쓸 만했지만, 속은 비효율적이었다. 브라우저는 웹페이지 하나를 불러오려고 한 서버에 TCP 연결을 6개씩 열고 요청이 빠르게 끝나도 여전히 응답 순서를 기다려야 하는 문제가 있었다.
그래서 2015년, HTTP/2가 등장했다.
multiplexing
HTTP/2는 multiplexing이라는 개념을 도입했다. 하나의 TCP 연결 안에 stream이라는 논리적 통로를 여러 개 만들고, 그 안에서 요청과 응답을 병렬로 처리하는 방식이다. 이전에는 요청을 여러 개 보내도 응답은 무조건 순서대로 와야 했는데, 이제는 A, B, C 요청을 보내고 응답도 순서에 구애받지 않고 따로따로 돌아올 수 있게 되었다.
binary protocol
HTTP/1.1까지는 모든 메시지가 사람이 직접 눈으로 봐도 읽을 수 있게 되어 있는 텍스트 기반이었다.
HTTP/2는 모든 메시지를 바이너리 프레이밍으로 처리하게 된 것이다. 이 덕분에 메시지 파싱이 빨라지고, 오류 가능성이 줄고, 네트워크에서 더 효율적으로 데이터를 다룰 수 있게 됐다.
HPACK
HTTP 요청마다 항상 붙는 User-Agent
, Accept-Language
, Cookie
같은 헤더는 대부분 바뀌지 않는데, 매 요청마다 이걸 똑같이 보내는 건 낭비라고 생각했다.
그래서 HTTP/2는 HPACK이라는 방식으로 중복되는 헤더를 압축해서 전송하도록 했다.
헤더가 HPACK으로 압축되면 전체 트래픽의 53%가 감소할 수 있다고 한다.
Server Push
서버가 클라이언트가 요청하지 않은 리소스를 미리 보내주는 Server Push 기능도 도입되었다. 예를 들어 브라우저가 HTML을 요청했을 때 서버가 ‘이 페이지엔 CSS랑 JS도 필요할 거야’라고 판단하면, 클라이언트가 요청하지 않았어도 미리 보내주는 기능이다. 이걸 잘 쓰면 페이지 로딩을 더 빠르게 만들 수도 있다.
잠깐 옆으로 새자면, Server Push는 실제로는 잘 쓰지 않는다.
1. 서버는 클라이언트가 뭘 캐시하고 있는지 모른다. 이미 가지고 있는 CSS를 또 보내줄 수 있다.
2. 브라우저 입장에서 Server Push 는 좀 애매하다. 받은 리소스를 지금 쓸지 말지 결정하는게 어렵다.
3. 클라이언트 입장에서 더 중요한 파일이 있을 수 있는데, 서버는 그렇지 않고 다른 파일을 push 할 수 있다.
단점
이 쯤 되면 모든 문제가 해결된 것 같지만, 아직 HTTP/2에는 구조적 한계가 아직 남아있다. 그것은 바로 여전히 TCP 위에서 작동한다는 점이다.
TCP는 패킷 하나가 손실되면, 그 이후의 모든 데이터가 도착할 때까지 기다려야 한다. 이걸 TCP 레벨의 Head-of-Line Blocking이라고 부른다.
아무리 HTTP/2가 stream을 나누고 병렬로 처리한다 해도, 기반이 되는 TCP가 전체 연결 단위로 움직이기 때문에 하나의 stream이 늦어지면 다른 stream도 영향을 받을 수밖에 없다.
진짜 Head-Of-Line Blocking 병목을 해결하려면 TCP 자체를 벗어나야 했다.
📡 HTTP/3
지금까지 HTTP는 계속 진화해왔지만, 사실 TCP라는 기반 위에서 움직인다는 점은 한 번도 바뀌지 않았다. HTTP/1.1, HTTP/2.0 모두 연결을 재사용하거나, 데이터를 동시에 보낼 수 있게 하는 등 여러 가지 개선을 해왔지만, TCP의 구조적인 한계는 항상 발목을 잡았다.
TCP 덜어내기
TCP는 신뢰성 높은 전송 프로토콜이다. 그래서 하나의 패킷이 손실되면 그 이후 모든 데이터도 일시적으로 멈춰버린다. 왜냐하면 TCP는 순서를 보장하고, 전체 흐름을 하나의 스트림으로 취급하기 때문이다.
이걸 TCP-level Head-of-Line blocking이라고 부른다. 브라우저가 HTML, CSS, JS를 동시에 요청했는데, CSS 응답에 해당하는 TCP 패킷이 하나 손실되면? 그 CSS뿐만 아니라 HTML, JS까지 모두 대기 상태에 빠질 수 있다.
HTTP/2는 stream을 나누긴 했지만, 모든 stream이 결국 하나의 TCP 흐름에 얹혀 있었기 때문에 이 문제는 해결되지 않았다.
HTTP/3는 UDP 위에서 작동하는 QUIC이라는 새로운 전송 프로토콜 위에 만들어졌다.
QUIC(Quick UDP Internet Connections)는 UDP를 기반으로 하지만, 자체적으로 TCP와 비슷한 기능들을 애플리케이션 레벨에서 직접 구현하고 있다.
- Multiplexed streams, 하나의 연결 안에서 여러 stream이 독립적으로 움직임
- 0-RTT 연결 설정, 기존에 접속한 적 있는 서버라면 거의 즉시 통신 시작 가능
- TLS 1.3 내장, 보안이 기본값
- 손실된 stream만 복구, TCP처럼 전체 연결이 멈추지 않음
이 덕분에 HTTP/3는 하나의 리소스 전송에 문제가 생겨도 다른 리소스가 영향을 받지 않는다.
그림으로 비교
- HTTP/1.1, HTTP/2 (TCP 기반)
- Stream A (HTML) ← 손실 발생
- Stream B (CSS) ← 대기 상태
- Stream C (JS) ← 대기 상태
- HTTP/3 (QUIC 기반)
- Stream A (HTML) ← 손실 발생, 재전송
- Stream B (CSS) ← 정상 응답
- Stream C (JS) ← 정상 응답
마무리
- HTTP/1.0: 요청 하나에 연결 하나.
- HTTP/1.1: 연결을 유지(persistent)하고, 오버헤드를 줄여 성능을 높이려 했지만 완벽하진 않았다.
- HTTP/2: 하나의 연결로 병렬 요청을 가능하게 만들었고, 헤더 압축과 push 같은 기능으로 최적화를 시도했음. 그러나 TCP 기반이라는 한계는 남아 있었다.
- HTTP/3: 결국 TCP를 벗어나, QUIC이라는 새로운 전송 계층 위에서 진짜 병목 없는 스트리밍을 구현했다.
오늘날의 브라우저는 대부분 HTTP/1.1, HTTP/2, HTTP/3를 모두 지원한다. 클라이언트와 서버가 서로 지원하는 가장 높은 버전으로 자동 협상해서 통신한다.
references
'CSE > 네트워크 (network)' 카테고리의 다른 글
[Network] HTTP Video Streaming, DASH (0) | 2025.04.22 |
---|---|
[Network] Go-Back-N, Selective Repeat (0) | 2025.04.20 |
[Network] reliable data transfer (0) | 2025.04.20 |
[Network] UDP (0) | 2025.04.20 |
[Network] multiplexing, demultiplexing (0) | 2025.04.20 |
컴퓨터 전공 관련, 프론트엔드 개발 지식들을 공유합니다. React, Javascript를 다룰 줄 알며 요즘에는 Typescript에도 관심이 생겨 공부하고 있습니다. 서로 소통하면서 프로젝트 하는 것을 즐기며 많은 대외활동으로 개발 능력과 소프트 스킬을 다듬어나가고 있습니다.
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!