스티븐 루딘, 하비에르 가르사 님의 "러닝 HTTP/2" 책을 정리한 글입니다.
1. HTTP/2의 계층
프레이밍 계층
- HTTP 프로토콜을 효율적으로 주고받기 위한 기능을 수행
특징 | 설명 |
바이너리 프로토콜 사용 |
HTTP/2는 텍스트 기반이 아닌 바이너리 프로토콜을 사용하여 효율적 전송
|
헤더 압축 |
반복적인 헤더 데이터를 압축하여 네트워크 효율성을 높임
|
암호화 |
데이터를 암호화하여 보안성을 강화 (기본적으로 HTTPS 사용)
|
다중화 |
하나의 연결을 통해 여러 요청과 응답을 동시에 처리하여 성능 향상
|
데이터 계층
- HTTP1.1 과의 호환성 유지
- 실제 데이터 전송
2. 연결
- h2에서 모든 프레임과 스트림은 하나의 연결을 통해 전송됨
매커니즘
- 클라이언트가 연결을 시작
- 새 연결 열기 or HTTP/1.1에서 Upgrader: h2c
- HTTP1.1에서 HTTP/2.0로 업그레이드된 경우를 고려하여 설계됨
- 클라이언트가 초기 데이터 전송
- 클라이언트는 연결을 시작할 때 특정한 16진수 옥텟 스트림을 첫번째 데이터로 전송
- 서버가 HTTP/2.0 연결을 지원하는지 확인하기 위함
- 클라이언트가 SETTINGS 프레임 전송
- 서버가 h2로 통신할수 있음을 확인하기 위한 목적
- 서버가 이를 수신하면 클라이언트는 ACK 및 자신의 SETTINGS 프레임으로 응답
- 클라이언트는 서버의 응답을 기다리지 않고 미리 프레임 전송을 할 수 있음
- SETTINGS 프레임 협상 완료
- 만약 클라이언트가 SETTINGS 프레임보다 다른 프레임을 먼저 받는다면 협상은 실패됨
- 양측은 GOAWAY 프레임을 전송하며 연결을 종료함
3. 프레임
텍스트 구분 형식
- h1에서 요청/응답 시 사용하는 방식
단점
단점 | 설명 |
동기 호출 |
한 번에 하나씩 처리 (요청 후 응답을 받아야 다음 요청 가능)
|
속도 저하 | 순차적 요청 처리로 인해 느림 |
오류 발생 가능 |
네트워크 지연 또는 장애 시 요청이 실패할 가능성 높음
|
메모리 사용 예측 어려움 |
헤더에 사이즈 정보가 없어 얼마나 메모리를 사용할지 예측하기 어려움
|
프레이밍
- 프로토콜의 사용자가 쉽게 읽고 파싱하고 생성할 수 있는 방식
- 프레임 단위로 전송
장점
장점 | 설명 |
파싱이 간단 |
구조화된 프레임 형식으로 인해 데이터 해석이 쉬움
|
길이를 알 수 있음 | 각 프레임의 길이가 명확하게 정의됨 |
메모리 사용량 예측 가능 |
프레임 크기를 기반으로 파싱에 필요한 메모리를 미리 계산 가능
|
다중화 가능 |
하나의 연결에서 여러 개의 스트림을 동시에 처리할 수 있음
|
요청과 응답을 뒤섞을 수 있음 |
여러 개의 요청과 응답을 병렬로 처리하여 효율성 증가
|
이전 응답과 관계없이 요청 가능 |
특정 요청의 응답을 기다릴 필요 없이 새로운 요청 전송 가능
|
프레임 구조
필드 | 크기 (바이트) | 설명 |
Length | 3 | 프레임의 페이로드 길이 |
Type | 1 | 프레임의 타입 |
Flags | 1 | 프레임의 플래그 (프레임 타입에 따라 다름) |
Stream Identifier | 4 | 스트림 식별자 |
Payload | 가변적 | 프레임에 해당하는 실제 데이터 또는 제어 정보 (타입에 따라 달라짐) |
프레임 유형
프레임 타입 | ID | 설명 |
DATA | 0x0 | 데이터 전송을 위한 프레임 (HTTP 요청/응답의 본문 데이터) |
HEADERS | 0x1 | HTTP 헤더 데이터를 전송하기 위한 프레임 |
PRIORITY | 0x2 | 스트림의 우선순위를 설정하는 프레임 |
RST_STREAM | 0x3 | 스트림을 중단시키는 프레임 |
SETTINGS | 0x4 | 연결 설정 정보를 교환하는 프레임 (예: 윈도우 크기, 버퍼 크기 등) |
PUSH_PROMISE | 0x5 |
클라이언트의 요청에 따라 서버가 예비적으로 푸시할 리소스를 알리는 프레임
|
PING | 0x6 | 연결 상태를 점검하기 위한 프레임 |
GOAWAY | 0x7 | 연결 종료를 알리는 프레임 |
WINDOW_UPDATE | 0x8 |
윈도우 크기 조정 (Flow Control) 관련 프레임
|
CONTINUATION | 0x9 |
긴 헤더 데이터를 여러 프레임에 나누어 전송할 때 사용하는 프레임
|
4. 스트림
- HTTP/2.0 연결 내에서 독립적이고 양방향으로 교환되는 프레임 모음
- 클라이언트는 요청을 할 때 새 스트림을 개시하고, 서버는 동일한 스트림에서 응답함
- 프레이밍 덕분에 다수의 요청과 응답이 서로를 차단하지 않고 병렬 처리 가능
- HEADERS 프레임 전송을 통해 새 스트림이 시작됨
메시지
- HTTP 요청이나 응답을 총칭하는 개념 (RFC 7230)
- 메시지 전송을 위해 스트림을 생성함
- 요청/응답 메시지의 쌍을 각각 하나의 스트림을 통해 교환
- 하나의 메시지에는 반드시 먼저 HEADERS 프레임이 전송됨
구조
구분 | HTTP/1.1 (h1) | HTTP/2 (h2) |
전송 방식 | 텍스트 기반 (줄바꿈 사용) | 바이너리 프레임 기반 |
메시지 구조 | 요청줄/상태줄 + 헤더 + 본문 | 가상 헤더(HEADERS 프레임) + 본문 (DATA 프레임) |
헤더 처리 방식 | 개별 헤더 필드 전송 | HPACK 압축 사용 (중복 제거) |
헤더 예시 | GET /index.html HTTP/1.1 Host: example.com |
:method GET :scheme https :path /index.html |
전송 효율 | 헤더 중복 문제, 전송 속도 저하 | 헤더 압축 및 멀티플렉싱 지원으로 효율적 |
흐름 제어
- 서버 뿐만 아니라 클라이언트도 전송 크기를 조절할 수 있음
- WINDOW_UPDATE 프레임에 흐름 제어 정보를 지정함
- 자신이 수신할 수 있는 바이트 수를 표시함
목적
- 여러 스트림이 서로 방해하지 않게 하기 위해 사용
- 중재를 위해 사용
우선 순위
- 현대의 브라우저는 웹 페이지에서 가장 중요한 요소를 먼저 요청할 수 있음
- 최적의 순서로 개체를 가져올 수 있으므로 성능이 개선됨
의존성 트리
- 특정 개체에 의존하는 개체가 있음을 표시하는 자료구조
- 상대적인 가중치를 정의하여 서버에게 우선순위를 알려줄 수 있음
5. 서버 푸시
- 특정 개체가 클라이언트에서 사용될 것 같은것을 서버측에서 미리 전달해주는 것
개체 푸시하기
- 서버는 개체를 푸시하기로 결정하면 PUSH_PROMISE 프레임을 구성함
조건
조건 | 설명 |
스트림 ID 매칭 |
PUSH_PROMISE 프레임의 스트림 ID는 응답과 쌍을 이루는 요청의 스트림 ID여야 함.
|
HEADERS 블록 일치 |
클라이언트가 직접 개체를 요청할 때 사용하는 HEADERS 블록과 동일해야 함.
|
캐싱 가능성 |
푸시된 개체는 클라이언트 측에서 캐싱될 수 있어야 함.
|
멱등성 보장 |
:method 헤더 필드는 멱등성을 가져야 함 (일반적으로 GET 요청이 사용됨)
|
전송 순서 |
PUSH_PROMISE 프레임은 DATA 프레임보다 먼저 도착해야 함.
|
- 조건이 충족되지 않으면, 클라이언트는 스트림을 재설정하거나 (RST_STREAM) 에러를 전송함 (GOAWAY)
푸시할 대상 선정하기
기준 | 설명 |
개체의 캐싱 여부 |
개체가 이미 브라우저 캐시에 존재할 가능성이 높다면 푸시가 불필요할 수 있음.
|
개체의 추정 우선순위 |
더 중요한 리소스를 먼저 푸시하는 것이 바람직함.
|
푸시로 인한 성능 이점 |
클라이언트가 직접 요청하는 것보다 푸시하는 것이 성능상 이점이 있는지 평가함.
|
- 서버는 클라이언트의 성능 향상을 목표로 푸시할지 결정함
6. 헤더 압축(HPACK)
- 페이지당 요청/응답수가 많아지면서 네트워크 혼잡도가 높아짐
- 요청들의 각 헤더들은 상당한 중복을 가짐
HPACK 알고리즘 (RFC 7541)
- 헤더 크기를 줄이기 위해 사용하는 압축 알고리즘
압축 방법
압축 방법 | 설명 |
인덱스 기반 압축 |
동일한 헤더가 반복될 경우, 전체 문자열 대신 헤더 테이블의 인덱스만 전송
|
동적 테이블 활용 |
새로 추가된 헤더를 동적 테이블에 저장하고, 이후 요청에서 인덱스로 참조
|
허프만 코딩 |
헤더 값을 가변 길이의 허프만 코드로 변환 (추가적인 압축 효과)
|
장점
장점 | 설명 |
헤더 크기 감소 |
반복되는 헤더를 인덱스로 전송하여 데이터 크기를 최소화.
|
전송 속도 향상 |
압축을 통해 네트워크 대역폭 절약 및 응답 속도 증가.
|
효율적인 리소스 사용 |
동적 테이블을 사용하여 중복 헤더 전송을 줄이고, LRU 방식으로 관리.
|
보안성 향상 |
헤더 크기를 줄여 특정 공격(예: BREACH 공격) 가능성을 낮춤.
|
전송 과정
1. 클라이언트가 첫 번째 요청을 보냄
:method: GET
:scheme: https
:authority: example.com
:user-agent: Mozilla/5.0
:accept-language: en-US
- 서버는 받은 헤더를 동적 테이블에 저장
- 이후 요청에서 재사용할 수 있도록 준비함
2. 두 번째 요청에서 중복된 헤더는 인덱스로 전송, 변경된 값만 새롭게 전송
:method: GET
:scheme: https
:authority: example.com
:user-agent: Mozilla/5.0
:accept-language: fr-FR ← 값이 변경됨
- 동일한 헤더는 테이블의 인덱스를 전송하여 크기를 최소화
- 변경된 헤더는 헤더 이름의 인덱스 + 새로운 값만 전송
3. 서버가 변경된 값만 반영하고 응답을 생성함
7. 전송 절차
nghttp -v -n --no-dep -w 14 -a -H "Header1: Foo" https://nghttp2.org
0. 연결
[ 0.063] Connected
The negotiated protocol: h2 # h2 협상 성공
[ 0.106] send SETTINGS frame <length=12, flags=0x00, stream_id=0> # SETTINGS 프레임 전송
(niv=2)
[SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
[SETTINGS_INITIAL_WINDOW_SIZE(0x04):16383] # 윈도우 크기를 16KB로 지정
1. 요청
[ 0.106] send HEADERS frame <length=42, flags=0x05, stream_id=1>
; END_STREAM | END_HEADERS # 서버에 더이상 전송할 헤더 및 데이터 없음을 알림
(padlen=0)
; Open new stream
:method: GET
:path: /
:scheme: https
:authority: nghttp2.org
accept: */*
accept-encoding: gzip, deflate
user-agent: nghttp2/1.44.0
header1: Foo # 임의로 추가한 헤더
2. 응답
[ 0.146] recv SETTINGS frame <length=30, flags=0x00, stream_id=0> # 서버의 SETTINGS 프레임 수신
(niv=5)
[SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
[SETTINGS_INITIAL_WINDOW_SIZE(0x04):1048576]
[UNKNOWN(0x09):1]
[SETTINGS_ENABLE_CONNECT_PROTOCOL(0x08):1]
[SETTINGS_HEADER_TABLE_SIZE(0x01):8192]
[ 0.146] recv SETTINGS frame <length=0, flags=0x01, stream_id=0> # SETTINGS 프레임에 대한 ACK 송신
; ACK
(niv=0)
[ 0.181] recv (stream_id=1) :status: 200 # 응답 코드
[ 0.181] recv (stream_id=1) date: Mon, 31 Mar 2025 10:32:31 GMT
[ 0.181] recv (stream_id=1) content-type: text/html
[ 0.181] recv (stream_id=1) last-modified: Sun, 02 Mar 2025 07:54:08 GMT
[ 0.181] recv (stream_id=1) etag: "67c40ea0-18b4"
[ 0.181] recv (stream_id=1) accept-ranges: bytes
[ 0.181] recv (stream_id=1) content-length: 6324
[ 0.181] recv (stream_id=1) x-backend-header-rtt: 0.002
[ 0.181] recv (stream_id=1) strict-transport-security: max-age=31536000
[ 0.181] recv (stream_id=1) server: nghttpx
[ 0.181] recv (stream_id=1) alt-svc: h3=":443"; ma=3600, h3-29=":443"; ma=3600
[ 0.181] recv (stream_id=1) via: 2 nghttpx
[ 0.181] recv (stream_id=1) x-frame-options: SAMEORIGIN
[ 0.181] recv (stream_id=1) x-xss-protection: 1; mode=block
[ 0.181] recv (stream_id=1) x-content-type-options: nosniff
[ 0.181] recv HEADERS frame <length=251, flags=0x04, stream_id=1> # 전송할 데이터가 있음을 의미 (END_STREAM X)
; END_HEADERS
(padlen=0)
; First response header
[ 0.181] recv DATA frame <length=6324, flags=0x01, stream_id=1> # 데이터 전송
; END_STREAM
3. 종료
[ 0.273] send GOAWAY frame <length=8, flags=0x00, stream_id=0>
(last_stream_id=2, error_code=NO_ERROR(0x00), opaque_data(0)=[])
'Network' 카테고리의 다른 글
[NGINX 쿡북] 2. 고성능 부하분산 (0) | 2025.04.01 |
---|---|
[NGINX 쿡북] 1. 엔진엑스 기초 (0) | 2025.04.01 |
[러닝 HTTP/2] 4. HTTP/2로의 전환 (0) | 2025.03.30 |
[러닝 HTTP/2] 3. 웹을 파헤치는 이유와 방법 (0) | 2025.03.30 |
[IBM Technology] What are DNS Zones And Records? (0) | 2025.03.29 |