HTTP의 역사
HTTP란?
HTTP는 HyperText Transfer Protocol의 약자로,
이름에서 알 수 있듯이 처음엔 하이퍼텍스트 문서(링크를 통해 서로 다른 문서들을 연결한 문서)를 주고받기 위해 설계된 프로토콜입니다. 그래서 최초의 HTTP 버전은 오직 HTML 문서만 주고 받을 수 있었습니다.
HTTP/0.9
- HTTP/0.9는 원래 간단한 HTML 객체를 받아오기 위해 만들어졌습니다.
- HTTP/0.9는 GET 메서드만 지원합니다.
- 헤더, 버전번호는 지원하지 않습니다.
요청
GET / mypage.html;
응답
<html>
A very simple HTML page
</html>
HTTP/1.0 - 확장성 만들기
HTTP/0.9는 매우 제한적이었으며, 브라우저와 서버를 통해서 빠르게 다양한 특징을 가지게 되었습니다.
- 처음으로 널리 쓰이기 시작한 버전
- 각 요청 안에 버전 번호가 포함되어 전송
- 상태 코드 라인 또한 응답의 시작 부분에 붙어 전송
- 브라우저가 요청에 대한 성공/실패를 알 수있음.
- 그 결과에 대한 동작(예: 특정 방법으로 로컬 캐시를 갱신하거나 사용)을 할 수 있게 됨.
HEAD,POST가 추가되었습니다.HEAD는 리소스를 다운받지 않고도 그에대한 무언가를 알 수 있다(타입이라던가)Content-Type: text/html,User-Agent: Chrome등- 응답 상태 코드를 통해, 개체가 존재하는 지 확인 가능(응답 본문은 X)
- 응답 헤더의 Content-Type 덕분에, 일반
HTML파일들 외에 다른 문서들을 전송할 수 있었습니다.- 브라우저는 이 정보를 보고
Content-Type: application/javascript→ JS 엔진(V8, SpiderMonkey 등)으로 실행함.Content-Type: text/css→ 해당 내용을 스타일로 적용함.Content-Type: text/html→ 브라우저는 렌더링 엔진으로 해석해서 웹페이지로 그림
- 브라우저는 이 정보를 보고
- 시각적으로 매력적인 웹페이지와 상호작용하는 폼을 실현
HTML 문서 전송
GET /mypage.html HTTP/1.0
User-Agent: NCSA_Mosaic/2.0 (Windows 3.1)
200 OK
Date: Tue, 15 Nov 1994 08:12:31 GMT
Server: CERN/3.0 libwww/2.17
Content-Type: text/html
<HTML>
A page with an image
<IMG SRC="/myimage.gif">
</HTML>
이미지 내려받기
GET /myimage.gif HTTP/1.0
User-Agent: NCSA_Mosaic/2.0 (Windows 3.1)
200 OK
Date: Tue, 15 Nov 1994 08:12:32 GMT
Server: CERN/3.0 libwww/2.17
Content-Type: text/gif
(image content)
HTTP/1.0+
- keep-alive 커넥션(지속 커넥션)이 옵션으로 들어감
HTTP/1.1
→ 1.1에서는 keep-alive 커넥션(지속 커넥션)이 기본값
HTTP의 첫 번째 표준화 버전인 HTTP/1.1은 HTTP/1.0 이후 불과 몇 달 뒤인 1997년 초에 바에 발표되었습니다.
HTTP/1.1은 모호함을 명확하게 하고 많은 개선 사항들을 도입했습니다.
- 캐시를 제어할 수 있는 메커니즘이 추가되었습니다.
- Cache-Control(캐시의 규칙을 정하는 헤더), ETag(리소스의 버전(version)을 나타내는 식별자), If-None-Match(ETag 비교해서 안 바뀌었으면 304 받음)
PUT,OPTIONS,DELETE등의 메서드가 추가되었습니다.- pipelining 기능이 추가되었습니다.
- HOST 요청 헤더를 반드시 포함하도록합니다.
HOST 요청 헤더
HTTP 요청을 보낼 땐, https://www.google.com/index.html과 같은 절대 경로가 아니라 /index.html처럼 상대 경로를 명시합니다.
- HTTP가 만들어졌을 땐 웹 서버 하나 당 오직 하나의 웹 사이트만 호스팅
- 굳이 절대 경로를 명시할 필요가없었기 때문인데요, 하지만 요즘엔 가상 호스팅이라고 해서 하나의 서버에서 여러 개의 도메인을 호스팅할 수있습니다.
HTTP/1.1에서 HOST 요청 헤더를 포함하지 않는 경우, 서버는 해당 요청을 무시해야 합니다. 물론 대부분의 서버가 HOST 헤더 없이도 알아서 잘 동작하도록 구현되어 있긴 하지만 HTTP/1.1을 사용하는 경우 HOST 헤더를 명시하는 것이 바람직합니다.
Persistent Connection
단일 TCP Connection 내에서, 수차례의 요청/응답을 처리하는 가장 기본적인 모델입니다.
위 그림에서 open과 close는 각각 TCP connection의 열림과 닫힘을 의미합니다.
Persistent Connection 모델에서는 한 번 open한 connection 위에서 여러번의 client-server간 통신이 일어납니다.
HTTP pipelining
HTTP pipelining에서는 Persistent Connection 모델에서 한 단계 더 나아가, client가 server의 응답을 기다리지 않고 다음 요청을 날릴 수 있습니다.
- 여러 개의 요청은 응답이 도착하기 전까지 큐에 쌓입니다.
Pipelining의 제약사항
- 서버의 응답이 FIFO 방식으로 동작한다는 것. → HTTP 응답은 요청 순서와 같게 와야한다.
- HTTP 메시지는 순번이 매겨져 있지 않아서 응답이 순서 없이 오면 순서에 맞게 정렬할 방법이 없다.
- 먼저 들어온 요청 중 아주 처리가 오래 걸리는 요청이 있으면, 그 다음 요청들은 무한정 기다려야하는 문제가 있다.
→ 이 문제를 **HOL blocking(Head Of Line blocking)**문제라고 한다.
- HTTP 클라이언트는 POST 요청 같이 반복해서 보낼 경우 문제가 생기는 요청은 파이프라인을 통해 보내면 안된다. 에러가 발생하면 파이프라인을 통한 요청 중에 어떤 것들이 서버에서 처리되었는지 알 수가 없다.
- POST는 비멱등성(한 번 더 보내면 결과가 달라지고 부작용이 생긴다. → 같은 요청이 2번 보내짐)
HTTP/2.0 – 다중 스트림과 성능 개선
HTTP/1.1까지는 텍스트 기반 프로토콜이었지만, HTTP/2부터는 바이너리 프레이밍(binary framing) 이 도입됩니다.
바이너리 프레이밍(binary framing)은 요청/응답을 프레임(frame)이라는 작은 조각들로 쪼개서 보내고, 각각에 스트림 ID를 붙여 관리하는 방식입니다.
이 덕분에 하나의 TCP 연결 위에서 여러 요청/응답을 동시에 섞어서 보낼 수 있게 되었고,
이게 바로 HTTP/2의 핵심 기능인 Multiplexing(다중화)입니다.
1. Multiplexed Streams (Multiplexing)
1) HTTP/1.1에서의 한계 상기
HTTP/1.1 + Persistent Connection에서는:
- 하나의 TCP 연결을 여러 요청이 순서대로 사용함
- HTTP pipelining을 써도, 응답은 반드시 요청 순서대로 와야 함
- 그래서 하나의 느린 요청이 앞에 있으면, 그 뒤의 빠른 요청들이 같이 기다려야 함 → Head-of-Line Blocking(HOLB)
[HTTP/1.1 Pipelining]
요청: R1 R2 R3
응답: ---------R1---------R2--R3
R1이 느리면 R2, R3도 같이 지연됨 (순서를 깨면 안 되기 때문)
2) HTTP/2에서의 Multiplexing 개념
HTTP/2에서는 하나의 TCP 연결 위에 여러 개의 스트림(stream)을 동시에 올려놓고 통신합니다.
- 각 요청/응답 = 하나의 스트림(Stream 1, Stream 3, Stream 5 …)
- 각 스트림은 ID로 구분되고, 프레임 단위로 쪼개져서 섞여 전송됨
- 서버/클라이언트는 프레임에 붙은 스트림 ID를 보고 어떤 요청의 조각인지 다시 조립
[HTTP/2 Multiplexing – 개념]
TCP 연결 하나
Stream 1: H1 D1-1 D1-2 D1-3
Stream 3: H3 D3-1 D3-2
Stream 5: H5 D5-1 D5-2
전송 순서 예시:
H1, H3, D1-1, H5, D3-1, D5-1, D1-2, D3-2, D5-2, D1-3 ...
→ 서로 다른 요청/응답의 조각들이 “섞여서” 한 연결 위를 흘러감
→ 클라이언트/서버는 스트림 ID를 보고 다시 맞춰서 조립
3) 왜 이게 빠른가?
- 여러 요청이 동시에 진행될 수 있음 → 느린 요청 하나가 있다고 해서 다른 요청이 막혀있지 않음
- 브라우저는 HTML을 받으면서 동시에 JS, CSS, 이미지 요청을 날리고, 서버는 그 응답을 병렬적으로 조금씩 조금씩 보내줄 수 있음.
- HTTP/1.1 pipelining처럼 응답 순서를 무조건 지켜야 하지 않아서
- 우선 순위를 부여할 수 있다. HOL Blocking 문제가 크게 줄어듦 (TCP 레벨의 HOLB는 여전히 존재하지만, HTTP 레벨에선 많이 완화됨)
2. HTTP/1.1 vs HTTP/2 흐름 비교
HTTP/1.1 (Persistent + Pipelining)
TCP 연결 1개
요청: R1 R2 R3
응답: ----R1------ ----R2------ ----R3------
→ R1이 느리면 R2, R3도 함께 지연
→ 응답 순서(1 → 2 → 3)를 지켜야 함
HTTP/2 (Multiplexing)
TCP 연결 1개
요청: R1, R2, R3를 거의 동시에 보냄
응답: R1, R2, R3의 조각들이 섞여서 옴
[R1-1][R2-1][R3-1][R1-2][R2-2][R3-2] ...
→ 빠르게 준비된 스트림의 데이터부터 보내줄 수 있음
→ HOLB 문제 완화, 전체 페이지 로딩 시간 단축
3. Multiplexing 덕분에 생긴 변화들
-
과거 HTTP/1.1에서는 성능을 위해:
-
도메인 샤딩(서브도메인 여러 개로 나눠서 연결 수 늘리기)
- 하나의 도메인 내부에서
- TCP 연결 최대 6개
- 6개가 넘어가면, 비어있는 도메인을 기다리면서 Queue를 형성
- 하나의 도메인 내부에서
-
리소스 번들링(JS/CSS 한 파일로 합치기)
-
이미지 스프라이트 : 여러 이미지를 큰 이미지로 합치고 css 속성으로 다르게 렌더링 같은 “꼼수”를 많이 썼음
-
-
HTTP/2에서는 한 연결로 많은 요청을 동시에 효율적으로 처리할 수 있어서:
- 불필요한 번들링을 줄이고,
- 리소스를 더 잘게 나눠도 성능상 손해가 적어짐.
헤더 압축
과거에는 웹페이지 하나를 방문할 때의 요청이 많지 않았기 때문에 헤더의 크기가 큰 것이 문제가 되지 않았지만 요즈음에는 웹페이지 하나를 보기위해 많은 요청을 보내기 때문에 헤더의 크기가 대역폭, RTT에 문제를 끼치게 된다.
- 중복되는 헤더 필드는 한 번만 전송하고 이후에는 인덱스로 압축
- 변경된 헤더만 다시 보냄
- 결과적으로 헤더 크기를 크게 줄여 네트워크 비용을 감소