HTTP 진화과정

Table of Contents

  1. 표준 이전의 HTTP
  2. HTTP 1.1
    1. 연결 상태 유지(Persisted connection)
    2. 파이프라이닝(Pipelining)
    3. HTTP 1.1 문제점
      1. Head-of-Line Blocking(HOLB)
      2. Connection 관리의 비효율성
      3. Header Overhead
  3. HTTPS
  4. RESTful
  5. HTTP 2.0
    1. Multiplexing
    2. Header 압축 (HPACK)
    3. 서버 푸시 (Server Push)
    4. 이진 프로토콜 (Binary Protocol)
  6. HTTP 1.1과 HTTP 2의 차이점
  7. HTTP 3.0

HTTP_Version

HTTP의 버전은 브라우저 개발자 도구의 네트워크 탭을 열어서 쉽게 확인할 수 있습니다. 위 사진의 프로토콜 컬럼을 살펴보면 http/1.1, h2, h3 가 적힌 것을 확인할 수가 있습니다. 이는 각 통신이 HTTP/1.1, HTTP/2, HTTP/3을 이용했다는 것을 의미합니다.

그런데 하나의 웹 사이트에서 왜 이렇게 다른 버전의 HTTP가 사용되고 있는 걸까요? 각 버전에는 무슨 차이가 있는 걸까요? 그래서 이번 기회에 HTTP의 각 버전이 등장하게 된 배경과 버전 별 특징을 정리해보고자 합니다.


표준 이전의 HTTP

HTTP/1.1을 알기 위해서는 표준 이전의 HTTP에 대해서도 알아야 합니다. 표준 이전의 HTTP 등장 배경을 먼저 요약하자면 다음과 같습니다.

  • HTTP/0.9: 문서화된 최초의 HTTP 버전
  • HTTP/1.0: 기존 HTTP의 기능을 확장한 버전
  • HTTP/1.1: 표준화 된 HTTP

초창기 HTTP에 대해 이야기하려면 월드 와이드 웹에 대한 이야기를 하지 않을 수가 없네요. 일반적으로 웹은 HTTP라는 프로토콜을 이용해 HTML 파일을 주고 받을 수 있는 공간을 의미합니다.

초창기 웹은 아주 단순한 서버-클라이어트 구조를 따랐습니다. 클라이언트에서 HTML을 받기위해 서버에게 HTTP 규격에 맞춰 요청을 보내면, 서버가 그에 맞는 HTML을 전송하는 것이 전부였습니다. 이러한 HTTP는 처음부터 TCP/IP 위에서 구현되도록 설계되었습니다. 지금은 TCP가 무겁고 느리다고 많이 비판받지만, 당시에는 TCP는 연결 지향적인 특성 때문에 UDP에 비해 안정적이고 신뢰성 있는 통신을 제공했기 때문입니다.

게다가 대역폭 및 트래픽을 최적화하는 기술인 흐름 제어, 혼잡 제어 기능도 기본적으로 제공했기 때문에 HTTP를 설계할 때 저렇게 복잡한 최적화 사항들까지 신경 쓸 필요가 없었습니다.

그러다 보니 당시 HTTP 구조는 매우 간단했습니다. 요청 메서드 종류도 GET 뿐이었고, 헤더나 상태코드도 없었습니다.응답은 무조건 HTML 파일 그 자체였습니다.

아래는 HTTP/0.9 방식을 이용한 요청과 응답의 예시입니다.

<!-- 요청 -->
GET /mypage.html

<!-- 응답 -->
<HTML>
Simple HTML page
</HTML>

그런데 웹이 점차 인기를 끌다 보니, 기존의 HTTP 사양만으로는 사용자들의 모든 요구사항을 충족할 수 없게 되자 여러 브라우저와 웹 서버 벤더는 각자 HTTP에 여러 가지 기능을 추가했는데, 당시에는 명시적으로 약속된 사항이 없어 이들 간에 많은 혼란이 있었습니다. 즉, 각 서버와 클라이언트의 가눙아 일관성 있게 구현되지 않았기 때문에 문제가 일어난 것입니다.

그래서 다양한 요구사항을 충족시키고 표준화하기 위해 HTTP WG(Working Group)이라는 조직이 탄생하였습니다. 이 조직이 여러 사양들을 정리해서 발표했는데 이를 HTTP/1.0이라 부릅니다.

HTTP/1.0에서 주목할 만한 몇 가지 사항이 있는데, 간단하게만 정리하고 넘어가겠습니다.

  • 버전 정보가 명시되었고 각 요청, 응답 사이에서 전송되었습니다.
  • 요청 메서드가 GET, HEAD, POST 세 가지로 확장되었습니다.
  • 상태 코드가 추가되어 클라이언트 측에서 요청 결과에 따라 동작할 수 있게 되었습니다.
  • 요청과 응답에 대한 부가적인 메타데이터를 담는 헤더 필드가 추가되었습니다.
  • HTTP 헤더(Content-Type)의 도움으로 HTML 이외의 파일도 전송할 수 있게 되었습니다.


아래는 HTTP/1.0을 이용한 간단한 요청,응답 예시입니다.

<!-- 요청 -->
GET /mypage.html HTTP/1.0
User-Agent: NCSA_Mosaic/2.0 (Windows 3.1)

<!-- 응답 -->
200 OK
Date: Tue, 15 Nov 1994 01:11:11 GMT
Server: CERN/3.0 libwww/2.17
Content-Type: text/html
<HTML>
Simple HTML page
<img src="/myimage.png">
</HTML>

HTTP 1.1

사실 HTTP WG는 HTTP/1.0 사양을 정리하는 동시에 좀 더 표준화 된 사양인 HTTP/1.1의 초안을 제작하고 있었습니다.

HTTP 1.0 은 각 요청마다 새로운 TCP 연결을 맺고, 요청을 처리하고 연결을 끊는 방식이다보니, 3-way handshake4-way handshake 하는 과정에서 오버헤드가 발생해서 비효율적이었습니다. 그리고, 각 요청에 대한 응답을 받은 후에 다음 요청을 보낼 수 있어서 요청/응답이 순차적/직렬적으로 처리되어 Latency가 발생했습니다.

따라서 HTTP 1.1 에서는 이런 문제를 해결하기 위해서 Persistent ConnectionPipelining 을 지원하게 되었습니다.

연결 상태 유지(Persisted connection)

image-20250524160422100

HTTP 1.0에서는 요청에 따른 응답이 수신되면 TCP 연결을 바로 종료하였기 때문에 웹 페이지에서 다수의 HTTP 요청이 발생하는 경우 매번 TCP handshake 과정을 거쳐야 해서 속도가 느려졌습니다. 게다가 TCP 자체의 흐름 제어와 혼잡 제어 알고리즘으로 인해 네트워크 성능이 100% 나오지 않기도 했습니다.

따라서 HTTP 1.1은 기본적으로 한 번 수립한 연결을 재사용하게 설정되어 있습니다. 이렇게 되면 연결을 맺고 끊는 과정이 줄어들기 때문에 지연시간을 줄일 수 있습니다. 물론 연결을 유지하는 시간이 길어질수록 서버에 부하가 생기기 때문에 연결을 유지하는 시간을 제한하고 있으며 이를 Keep-Alive라고 부릅니다.

  • Client <-> Server 간 연결을 한 번 맺으면 여러 요청을 그 연결에서 처리 할 수 있게 한다.
  • Connection: Keep-alive 헤더가 설정되어, Client나 Server가 명시적으로 연결을 끊지 않는 한 연결이 유지된다.


파이프라이닝(Pipelining)

image-20250524161226322

파이프라이닝은 클라언트가 여러 요청을 연달아 보내야 할 때, 각 응답을 기다리는 것이 아니라 발생한 요청은 일단 전송하고 보는 방식입니다. 기존에는 여러 요청이 있을 때 하나의 요청과 그 응답을 세트로 반복하면서 처리하는 것을 볼 수 있는데요. 이것이 지연을 발생시키고 있는 것을 볼 수 있습니다.

그래서 HTTP 1.1에서는 파이프라이닝 기법을 사용합니다. 일단 클라이언트 측에서 여러 요청을 순차적으로 보내면, 서버는 받은 순서에 따라 응답을 제공하는 방식으로 지연을 개선합니다. 이때 각 요청 별 응답을 구분하기 위해 순서를 엄격히 지켜야 합니다.

  • Pipelining 은 Client 가 Server 에 여러 요청을 연속적 으로 보내고 순서대로 처리하는 방식이다.
  • Pipelining 이 적용되면 하나의 Connection 으로 다수의 요청과 응답을 처리할 수 있게 해서 Latency 를 줄일 수 있다.


하지만, 완전한 멀티플렉싱이 아닌 응답 처리를 미루는 방식이여서 각 응답의 처리는 순차적으로 처리되면, 결국 후순위 응답은 지연될 수 밖에 없습니다.


이 외에도 HTML을 분할해 전송하는 청크 전송 인코딩(Chunked Transfer Encoding)이나 요청과 응답에 대한 메터 정보를 담는 캐시 제어(Cache-Control), 동일 IP에 여러 도메인을 호스트 할 수 있게 해주는 호스트(Host)등의 헤더와 기능이 추가되었습니다.

HTTP 1.1 문제점

HTTP 1.1Persistent ConnectionPipelining 같은 기능을 통해서 HTTP 1.0 의 단점을 개선했지만, 여전히 여러 문제점 이 남아 있습니다.

Head-of-Line Blocking(HOLB)

image-20250524163000525

  • Client가 세 개의 요청(Request1, Request2, Request3)을 동시에 보내지만, Server는 순차적으로 처리한다.
  • 첫 번째 요청(Process1)이 처리되는 동안 다른 요청들 (Process2, Process3)은 차단(Blocked)되어 대기한다.


Head-of-Line Blocking 가 발생하는 이유

  • 순차적 처리

HTTP 1.1 에서는 하나의 연결 위에서 요청이 순서대로 처리되어야 한다.

  • 직렬 응답

서버는 요청 받은 순서대로 응답해야하므로, 하나의 요청이 지연되면 다음 요청도 자연스럽게 지연된다.

  • 리소스 다운로드

브라우저가 여러 리소스를 요청할 때, 큰 파일(이미지, 동영상) 이 먼저 다운로드 되면, 더 중요한 작은 파일들(css, js)이 대기한다.

Connection 관리의 비효율성

image-20250524163609582

여러 클라이언트가 서버와 Persistent Connection을 유지하게 되는 경우 서버는 여러 연결을 동시에 관리해야해서 서버에 부하가 갈 수 있다.

Header Overhead

image-20250524163654360

  • 클라이언트가 서버에 여러 요청을 보낼 때, 각 요청마다 동일한 Header를 반복해서 전송하게 된다.
  • 중복된 Header 정보는 불필요한 대역폭을 차지하게 되어 효율성이 떨어지게 된다.

HTTPS

HTTPS 진화과정

한편으로는 HTTP의 보안을 강화한 HTTPS가 이맘때 등장했습니다. HTTP 그 자체는 암호화되지 않은 텍스트로만 통신하기 때문에, 중간에 누군가가 통신 내용을 가로채거나 사용자의 정보를 탈취할 수 있다는 문제점이 있었습니다.

이를 방지하기 위해서는 HTTP 통신에 신뢰성과 무결성을 추가할 필요가 있었습니다. HTTPS는 이 문제를 대화 상대가 서로 자신이 신뢰할 수 있음을 증명하는 인증서를 사용하고, 통신 내용은 SSL(Secure Socket Layer) 또는 TLS(Transport Layer Security)라는 프로토콜로 암호화하는 방식으로 해결했습니다.

이러한 HTTPS 통신에서 서버와 클라이언트는 서로 신뢰할 수 있는 상대인지 확인하기 위해 인증서를 이용한 비대칭 키 암호화 방식을 사용합니다.

HTTPS 관련 자세한 내용은 만화로 보는 HTTPS가 작동하는 방식을 통해 읽어보시면 좋을 것 같습니다.


RESTful

RESTful

기존의 웹은 주로 클라이언트 측의 요청에 따라 서버가 완성된 HTML 파일을 만들어 제공하는 것이 기본이었습니다. 하지만 서버와 서버 간의 통신의 경우에는 데이터만 주고받을 수 있는 API가 필요했는데 여기에 대한 표준이 없었습니다.

당시 HTTP가 웹에서 사용되는 표준 프로토콜이었기 때문에, HTTP를 기반으로 웹의 장점을 최대한 활용할 수 있는 아키텍처로 REST가 제안되었습니다. REST는 HTTP의 메서드를 활용하여 CRUD를 구현하고, URI를 통해 자원을 명시하는 등 HTTP 통신의 특성을 최대한 활용하는 아키텍처입니다.

이맘때쯤 XML이나 JSON 같은 데이터 포맷이 등장하고, 웹 브라우저에서도 비동기로 서버에 요청을 보낼 수 있는 기술인 Ajax(Astnchronous JavaScript and XML)가 보편화되면서 REST 구조를 기반으로 한 웹 서비스들이 각광받게 됩니다.


HTTP 2.0

HTTP/1.1이 발표된 1997년 이후로 HTTP는 꾸준히 확장되었고, 그 사이 웹은 더욱 복잡해졌습니다. 그러다 보니 HTTP/1.1만으로는 극복할 수 없는 한계점이 점점 드러나기 시작했죠.

우선 헤더의 중복입니다. HTTP/1.1에 다양한 기능이 추가되면서 헤더에도 많은 메타 데이터가 담기게 되었습니다. 하지만 매 요청마다 헤더를 중복해서 전송해야만 했는데, 이것이 굉장한 낭비였고 심지어 전송하려는 값보다 헤더의 크기가 더 큰 경우도 있었습니다.

하지만 무엇보다고 가장 큰 문제는 서버가 항상 요청받은 순서대로 응답해야 하므로 발생하는 HOLB(Head-of-Line Blocking)였습니다. HTTP/1.1에서는 하나의 연결 내에서 응답 다중화(Multiplexing)를 할 수 없었기 때문에 요청이 순차적으로 처리되어야 했는데, 서버가 응답 작성 중간에 문제가 생기면 후속 요청들이 전송되지 못하고 지연되는 문제가 있었습니다.

결국 HTTP/1.1 방식에는 물리적인 TCP 연결을 여러 개 두는 방식으로 병렬 연결을 구현하게 되었습니다. 브라우저마다 정책도 다른 일반적으로 최대 6개의 동시 TCP 연결을 지원한다는 것으로 알려져 있습니다. 하지만 이 방식은 임시적인 해결책일 뿐 HTTP/1.1의 HOLB를 해결할 수 있는 해결책은 아니었습니다.

이 외에도 요청 별 우선순위를 지정할 수 없고, 클라이언트 기반 통신이기 때문에 서버에서 클라이언트 측으로 데이터를 전송할 수 있는 기능이 없다는 등의 문제가 있었습니다.

HTTP/2는 이러한 한계를 극복하기 위해 2015년 국제 표준으로 등장했습니다.

RESTful

우선 HTTP/2는 SPDY라는 프로토콜을 기반으로 동작합니다. SPDY는 HTTP/1.1의 잘 알려진 성능 제한 사항을 해결하여 웹 페이지의 로드 대기 시간을 줄이는 것을 목표로 구글에서 개발하고 2009년 중반에 발표한 프로토콜입니다.

이러한 HTTP/2는 기존 프로토콜과 호환성을 유지하면서, HTTP/1.1의 문제를 해결하기 위해 여러 특징을 가지고 있습니다.

Multiplexing

HTTP/2에서는 하나의 TCP 연결에서 여러 요청을 동시에 처리할 수 있는데 이것은 TCP 연결을 스트림,메시지,프레임이라는 단위로 더욱 세분화했기 때문입니다.

  1. 스트림: 요청과 응답이 양방향으로 오가는 논리적 연결 단위, TCP 연결에서 여러 개의 스트림이 동시에 존재할 수 있다.
  2. 메시지: 하나의 요청과 응답을 구성하는 단위
  3. 프레임: 메시지를 구성하는 최소 단위, 잘게 쪼개어 전송되기 때문에 수신 측에서 다시 조립하여 사용

RESTful

위의 사진에는 하나의 TCP 연결 내에 3개의 스트림이 있는 것을 볼 수 있습니다. 그 중 5번 스트림은 클라이언트 측에서 서버로 데이터를 전송 중이며, 1,3번 스트림은 서버 측에서 클라이언트로 데이터를 전송 중입니다. 또한 3번 스트림의 프레임이 1번 프레임 사이에 끼어들어 있는 것을 볼 수 있습니다. 클라이언트 측에서는 이것을 다시 조립하여 사용합니다.

이러한 응답 다중화 기능 덕분에 HTTP/1.1에서 발생한 HOLB 문제를 해결할 수 있었습니다.

multiplexing

  • HTTP 2 는 요청과 응답을 병렬로 처리 할 수 있는 멀티 플렉싱입니다.

여러 TCP 연결을 설정할 필요가 없어서 네트워크 오버헤드가 감소됩니다.

  • Client 가 여러 요청을 동시에 보내도, 각 요청이 독립적으로 처리되어서 Head-of-Line Blocking 문제를 해결합니다.
  • 각 스트림에 우선 순위 를 할당하여 중요한 리소스 먼저 전송할 수 있습니다.


Head-of-Line Blocking 이 해결 되었다는 것은, Application Layer 에서 Head-of-Line Blocking 이 해결되었다는 것이다.

HTTP/2 도 결국엔 TCP 위에서 동작하기 때문에 Transport Layer 에서의 Head-of-Line Blocking 이 존재한다.

TCP 는 순서 보장을 위한 프로토콜이라 중간에 패킷 손실되면, 손실된 패킷을 다시 전송받을 때까지 나머지 패킷의 처리가 지연

하나의 TCP 연결에서 여러 요청이 stream 으로 요청이 된다고 해도 중간에 패킷 손실되면, 지연이 발생한다는 의미

Head-of-Line Blocking 문제는 UDP 기반의 QUIC 프로토콜(HTTP/3) 에서 해결가능하다.


Header 압축 (HPACK)

multiplexing

HTTP/2는 헤더 필드 압축을 지원합니다. HPACK은 달라진 부분만 다시 전송하는 허프만 코딩(Huffman Coding) 기법을 사용합니다. 달라지지 않은 부분은 전송하지 않기 때문에 데이터 전송량을 줄이고, 대역폭 사용을 최적화합니다.


서버 푸시 (Server Push)

Server_push

  • Client가 요청하지 않은 리소스도 Server가 미리 전송할 수 있게 하여 페이지 로딩 속도를 개선합니다.
  • 여러 요청/응답 사이클을 줄여 네트워크 사용을 최적화합니다.
  • 우선 순위를 지정하여 중요한 리소스를 먼저 푸시하여 페이지 렌더링 속도를 향상 시킬 수 있습니다.


과도한 서버 푸시는 불필요한 데이터 전송이 발생할 수 있습니다.


이진 프로토콜 (Binary Protocol)

HTTP/1.1은 텍스트 기반 프로토콜이기 때문에 아스키코드로 작성되었습니다. 덕분에 사람이 읽기에는 편하지만, 불필요하게 데이터가 커지는 문제가 있었습니다.

HTTP/2에서는 보내야 할 데이터를 바이너리로 변환하는 계층이 있기 때문에 단순 텍스트를 전송하는 것보다 훨씬 더 효율적으로 데이터를 전송할 수 있습니다. 이것은 사실 HTTP/2의 버전이 HTTP/1.2가 아닌 이유이기도 한데, 예전처럼 텍스트 기반의 전송이 아닌 바이너링 프레이밍을 사용하기 때문입니다.

image-20250525004518788


HTTP 1.1과 HTTP 2의 차이점

image-20250525004710933

특징 HTTP 1.1 HTTP 2.x
Connection Persistent Connection (지속 연결) Persistent Connection + Multiplexing
Request Handling 요청이 순차적으로 처리됨 요청이 동시에 처리 가능 (Multiplexing)
Header Compression 없음 HPACK 으로 헤더 압축
Protocol 텍스트 기반 Binary 기반
Prioritization 없음 스트림 우선 순위 설정 가능
Server Push 없음 있음

HTTP 3.0

HTTP/2가 발표된 지 채 4년도 지나지 않아 HTTP/3가 발표되었습니다. HTTP/3는 어떤 문제점을 해결하기 위해 등장한 걸까요?

그 이유는 HTTP/2가 여전히 TCP 위에서 동작하기 때문에 TCP로 인해 발생하는 문제를 해결할 수 없었기 때문입니다. 우선 TCP는 신뢰성을 지향하기 때문에 데이터 손실이 발생하면 재전송을 수행합니다. 그런데 TCP는 패킷을 정확한 순서대로 처리해야 하기 때문에 재전송을 수행하고 대기하는 과정에서 병목 현상이 발생합니다. 즉, TCP라는 프로토콜 자체의 HOLB 문제를 해결할 수 없었습니다.

또한 TCP는 혼잡 제어를 수행하기 때문에 전송 속도를 낮은 상태에서 천천히 높이는 방식으로 속도 제어를 취합니다. 이는 네트워크 상황이 좋을 때는 불필요한 지연을 발생시킵니다. 그리고 프로토콜 자체의 불필요한 헤더 등도 고칠 수가 없었습니다. 결국 TCP는 현대 사회에 어울리지 않은 프로토콜이었던 것입니다.

이러한 문제를 해결하기 위해 HTTP/3는 QUIC이라는 프로토콜 위에서 동작합니다. QUIC는 TCP의 신뢰성 보장을 위해 제공되는 기능들을 UDP 기반으로 직접 구현하여 성능을 개선한, 구글이 2013년에 공개한 프로토콜입니다. 따라서 HTTP/3는 HTTP over QUIC이라는 이름으로도 불립니다.

QUIC

사실 UDP는 TCP와 달리 기본적인 신뢰성을 제공하지 않습니다. UDP 프로토콜 자체의 구조가 간단하기 때문에 QUIC는 신뢰성을 위해 패킷 재전송, 혼잡 제어, 흐름 제어 기능 등을 직접 구현했습니다. 즉 QUIC은 신뢰성 기능이 제공되는 UDP 기반의 프로토콜입니다.

HTTP/3는 QUIC이라는 UDP 기반 프로토콜을 사용하기 때문에 TCP 기반의 HTTP/2에서 해결하지 못한 문제점을 해결할 수 있었습니다.

QUIC

우선 HTTP/3는 연결 정보를 캐싱하여 재사용할 수 있는 0-RTT 기능을 제공합니다. TCP의 경우 최초 연결 수립 시 3-way handshake 과정이 필요하지만, HTTP/3는 최초 연결 설정에서 연결에 필요한 정보들과 데이터를 함께 전송하여 1-RTT로 시간을 절약합니다. 또한 한 번 성공한 연결은 캐싱해 놓았다가 다음 연결 때에는 캐싱된 정보를 바탕으로 바로 연결을 수립할 수 있기 때문에 0-RTT가 가능합니다.

QUIC

또한 HTTP/3는 연결 다중화를 지원하며, 각 스트림이 독립적으로 작용합니다. 위 사진을 보면 각 스트림이 독립적으로 동작한다는 것이 어떤 의미인지 쉽게 알 수 있을 것입니다. HTTP/2에서는 연결 다중화가 지원되어 여러 스트림이 동시에 지원할 수 있지만, TCP 특성 상 데이터 손실이 발생하면 데이터 복구를 우선 처리하면서 HOLB가 발생합니다. 하지만 QUIC 기반의 HTTP/3는 연결 내 스트림이 완전히 독립적으로 동작하기 때문에 데이터 손실이 발생해도 다른 스트림에 영향을 주지 않습니다.

그리고 HTTP/3는 IP 기반이 아닌, 연결 별 고유 UUID(Connection ID)를 이용해 각 연결을 식별합니다. TCP 기반 통신의 경우에는 Wi-Fi 환경에서 셀룰러 환경으로 이동하는 경우 IP 주소가 변경되기 때문에 연결 재수립 과정을 거쳐야 하지만, QUIC는 연결 ID 기반으로 식별하기 때문에 연결을 그대로 유지할 수 있습니다.

HTTP/2와 마찬가지로 TLS 연결 설정 과정이 QUIC 내부에 포함되기 때문에 HTTP/3는 HTTPS 사용이 강제되고, 우선 순위 제어, 서버 푸시 등의 기능을 제공합니다.

HTTP/3의 헤더 프레임은 HTTP/2.0의 HPACK과 유사하게 QPACK을 이용해 압축되어 전송됩니다. QUIC의 스트림이 독립적으로 송수신함에 따라 이에 맞춰 개선된 것 정도로 이해하면 됩니다.

마지막으로 기존 HTTP 체계와 호환되기 때문에, TCP 기반 통신 중 HTTP/3가 지원된다면 서버가 이를 클라이언트 측에 알려 HTTP/3 방식의 통신으로 전환을 유도할 수 있기도 합니다.

HTTP 진화과정
HTTP 진화과정

Start the conversation