HTTP-1.1
고친 사람 github-actions[bot]
HTTP/1.1
HTTP/1.1 은 브라우저와 서버가 요청과 응답을 글자로 적어 주고받게 하는 웹 전송 규칙입니다. 앞선 판은 파일 하나를 받을 때마다 연결을 새로 맺고 끊었습니다. HTTP/1.1 은 그 연결을 열어 둔 채로 다음 요청에 이어 씁니다.
쉽고 빠른 이해
HTTP/1.1 은 브라우저가 서버에게 「이 주소의 문서를 주세요」라고 글로 적어 보내고 서버가 「여기 있습니다」라고 글로 적어 돌려주는 규칙입니다. 웹페이지 하나를 열 때 오가는 말이 모두 이 꼴로 적힙니다.
이게 없으면 파일 하나를 받을 때마다 연결을 새로 맺어야 합니다. 연결을 맺는 데만 왕복이 한 번 더 들기 때문에, 그림이 수십 개 걸린 페이지는 파일을 받는 시간보다 연결을 맺는 시간이 더 듭니다.
어떻게 도나:
- 연결을 하나 맺고 무엇을 달라는 요청을 글자로 적어 보냅니다
- 서버가 결과와 그 내용을 글자로 적어 돌려줍니다
- 연결을 끊지 않고 그 위로 다음 요청을 이어 보냅니다
대가도 있습니다. 연결 하나는 한 번에 요청 하나만 처리합니다. 앞 요청의 응답이 늦으면 뒤 요청은 보내 놓고도 기다립니다. 브라우저는 그 줄서기를 피하려고 서버마다 연결을 여러 개 열어 둡니다.
문서 몇 개만 오가는 쪽에서는 지금도 이것으로 충분합니다. 파일이 수십 개씩 걸리는 페이지에서는 다음 판이 낫습니다.
상세
HTTP/1.1 은 웹에서 문서와 그림을 주고받는 규칙인 HTTP(HyperText Transfer Protocol, 하이퍼텍스트 전송 프로토콜)의 한 판입니다. 주고받는 말을 사람이 읽을 수 있는 글자로 적고, 그 글자를 연결 하나 위로 흘려보냅니다. 지금도 웹 서버와, 요청이 지나는 길목에 서서 대신 받아 넘기는 프록시가 이 꼴을 읽을 줄 안다고 보고 만들어집니다.
메시지의 생김새부터 이 판이 맞는 때까지 차례로 봅니다.
메시지의 네 토막
오가는 메시지 하나는 네 토막으로 이루어집니다. 첫 줄, 이름과 값을 적은 헤더 여러 줄, 빈 줄 하나, 그리고 본문입니다. 빈 줄이 헤더가 끝났다는 표시라서, 받는 쪽은 빈 줄을 만나기 전까지 읽은 것을 헤더로 봅니다.
요청과 응답은 첫 줄만 다르고 나머지 세 토막은 같은 꼴로 놓입니다.
block-beta columns 2 a["요청 · 첫 줄"] b["응답 · 첫 줄"] c["헤더"] d["헤더"] e["빈 줄"] f["빈 줄"] g["본문"] h["본문"]
요청의 첫 줄은 요청줄입니다. 세 토막으로 적습니다. 무엇을 할지 말하는 요청 메서드, 어느 문서인지 말하는 요청 대상, 그리고 어느 판으로 말하는지입니다.
응답의 첫 줄은 상태줄입니다. 판, 결과를 세 자리 숫자로 적은 상태 코드, 그리고 사람이 읽는 짧은 말이 차례로 옵니다.
block-beta columns 4 t1["요청줄"] a["요청 메서드"] b["요청 대상"] c["판"] t2["상태줄"] d["판"] e["상태 코드"] f["짧은 말"]
실제로 오가는 글자는 아래처럼 생겼습니다. 첫 줄이 요청줄, 다음 줄이 헤더, 그 아래 아무것도 없는 줄이 헤더의 끝입니다.
GET /index.html HTTP/1.1
Host: example.com
응답도 같은 꼴입니다. 첫 줄이 상태줄, 다음 줄이 헤더, 아무것도 없는 줄 아래가 본문입니다.
HTTP/1.1 200 OK
Content-Type: text/html
<html>...
본문이 무엇인지, 얼마나 긴지, 어느 사이트를 향한 요청인지 같은 것은 모두 헤더가 나릅니다. 본문은 그 내용물이고, 요청에 따라 아예 없기도 합니다.
본문의 끝을 알리는 두 방법
받는 쪽은 본문을 어디까지 읽어야 할지 스스로 알 수 없습니다. 헤더까지는 빈 줄이 끝을 알려 주지만 본문에는 그런 표시가 없습니다. 보내는 쪽이 끝을 알려 줘야 합니다.
첫째는 길이를 미리 적는 것입니다. Content-Length 헤더에 본문이 몇 바이트인지 적어 두면 받는 쪽은 그만큼만 읽고 멈춥니다. 이미 다 만들어 둔 파일을 보낼 때 쓸 수 있는 방법입니다.
둘째는 조각으로 나눠 보내는 것입니다. 만들면서 내보내는 응답은 길이를 미리 알 수 없습니다. 이때는 본문을 조각으로 잘라 조각마다 앞에 그 조각의 길이를 붙여 보냅니다. 길이가 영인 조각이 끝을 알립니다.
보내는 글자를 늘어놓으면 길이 칸과 데이터 칸이 번갈아 놓이고 맨 뒤에 영이 옵니다.
block-beta columns 5 a["5"] b["Hello"] c["3"] d["Wor"] e["0 · 여기가 끝"]
앞의 숫자가 바로 뒤에 오는 조각의 길이입니다. 이 방법이 청크 전송 인코딩입니다. 검색 결과를 찾는 대로 흘려보내거나 로그를 실시간으로 내려보내는 응답이 이렇게 나갑니다.
연결을 이어 쓰는 방법
연결을 하나 맺는 데는 왕복이 한 번 듭니다. 연결하자는 말과 알겠다는 답이 오간 뒤에야 첫 요청을 보낼 수 있습니다. 파일 하나마다 이 왕복을 되풀이하면 내용을 받는 시간보다 연결을 맺는 시간이 더 듭니다.
앞선 판에서 파일 둘을 받으면 연결을 맺고 끊는 일이 두 번 일어납니다.
sequenceDiagram
participant 브 as 브라우저
participant 서 as 서버
브->>서: 연결을 맺는다
브->>서: 문서를 달라
서-->>브: 문서
브->>서: 연결을 끊는다
브->>서: 연결을 다시 맺는다
브->>서: 스타일을 달라
서-->>브: 스타일
브->>서: 연결을 끊는다
HTTP/1.1 은 응답을 준 뒤에도 연결을 닫지 않습니다. 한쪽이 닫자고 말하기 전까지 같은 연결 위로 다음 요청을 이어 보냅니다. 이것이 지속 연결입니다.
sequenceDiagram
participant 브 as 브라우저
participant 서 as 서버
브->>서: 연결을 맺는다
브->>서: 문서를 달라
서-->>브: 문서
브->>서: 스타일을 달라
서-->>브: 스타일
Note over 브,서: 연결은 닫히지 않고 남는다
앞선 판에서는 연결을 살려 두려면 헤더 하나로 따로 부탁해야 했습니다. HTTP/1.1 에서는 살려 두는 쪽이 기본 동작입니다. 닫겠다는 쪽이 말을 합니다.
한 연결에 한 번에 하나
연결을 열어 둬도 한 연결이 한 번에 처리하는 요청은 하나입니다. 요청은 잇달아 보낼 수 있지만 응답은 보낸 순서대로 하나씩 돌아옵니다. 응답에 몇 번째 요청의 것인지 적는 칸이 없어서, 받는 쪽은 보낸 순서와 오는 순서가 같다고 믿을 수밖에 없습니다.
HTTP/1.1 에는 답을 기다리지 않고 요청을 잇달아 보내는 방법도 있습니다. 이것이 파이프라이닝입니다. 보내는 쪽은 빨라지지만 응답이 순서대로 나가야 한다는 것은 달라지지 않습니다. 앞 요청이 오래 걸리면 뒤 요청의 응답은 다 만들어 놓고도 기다립니다.
sequenceDiagram
participant 브 as 브라우저
participant 서 as 서버
브->>서: 큰 그림을 달라
브->>서: 작은 아이콘을 달라
Note over 서: 아이콘은 벌써 준비됐다
서-->>브: 큰 그림
서-->>브: 작은 아이콘
줄 앞의 것이 안 나가서 뒤가 통째로 서는 이 현상이 헤드 오브 라인 블로킹입니다. 파이프라이닝을 켜도 이 줄서기가 남기 때문에 브라우저는 대개 이 방법을 쓰지 않습니다.
대신 브라우저는 서버 하나에 연결을 여러 개 열어 두고 요청을 나눠 겁니다. 연결을 더 여는 만큼 서버가 들고 있어야 할 연결 수도 늘어납니다. 요청이 지나는 길목에 선 프록시나 캐시 같은 중간 장비도 그 연결을 같이 들고 있어야 합니다.
flowchart TD
B["브라우저"]
subgraph G["같은 서버로 연 연결"]
C1["연결 1"]
C2["연결 2"]
C3["연결 3 · 나머지도 같다"]
end
B --> C1
B --> C2
B --> C3
C1 --> S["서버 한 대"]
C2 --> S
C3 --> S
이 줄서기를 규칙 안에서 없앤 것이 다음 판인 HTTP/2 입니다.
한 서버가 사이트 여럿을 맡는 법
요청줄에 적히는 것은 서버 안에서의 경로뿐입니다. 어느 사이트를 향한 요청인지는 거기에 없습니다. 서버 한 대가 사이트 하나만 맡던 때는 그것으로 충분했습니다.
HTTP/1.1 은 Host 헤더를 반드시 보내게 했습니다. 이 헤더에 사이트 이름을 적으면 서버는 같은 주소로 들어온 요청을 사이트별로 갈라 처리할 수 있습니다. 한 대가 사이트 여러 개를 맡는 가상 호스팅이 여기서 나옵니다.
flowchart TD
R["요청 · Host: a.example.com"]
subgraph S["서버 한 대"]
A["사이트 a.example.com"]
B["사이트 b.example.com"]
C["사이트 c.example.com"]
end
R --> A
요청을 받아 뒤로 넘기는 리버스 프록시도 이 헤더를 보고 넘길 곳을 고릅니다. 그래서 이 값을 잘못 적으면 서버는 엉뚱한 사이트의 문서를 돌려줍니다.
맞는 곳과 안 맞는 곳
어떤 쪽에서 이 판이 아직 맞고 어떤 쪽에서 밀리는지를, 글자로 적는다는 성질 하나로 갈라 봅니다.
오가는 것이 글자라서 중간에서 들여다보고 손대기 쉽습니다. 요청을 가로채 대신 답하거나 뒤로 넘기는 HTTP 캐시와 게이트웨이 같은 중간 장비가 그 성질 위에 서 있습니다.
글자로만 되어 있어서 요청 한 줄을 손으로 적어 그대로 보내 볼 수 있습니다. 고장 난 곳을 찾을 때 이것이 값을 합니다.
반대로 요청이 한꺼번에 여럿 몰리는 쪽에서는 얻는 것이 적습니다. 파일 수십 개가 걸린 페이지나 호출을 쉬지 않고 주고받는 서버 사이 통신에서는 연결을 여러 개 여는 것으로 버텨야 합니다.
글자로 적는 만큼 같은 헤더가 요청마다 되풀이됩니다. 헤더가 본문보다 커지는 요청도 있습니다. 뒤 판들이 헤더를 압축해 보내는 것은 이 되풀이 때문입니다.
내용이 글자 그대로 네트워크를 지나므로 중간에서 누구나 읽을 수 있습니다. 이것을 막으려면 TLS(Transport Layer Security, 전송 계층 보안) 위에 얹어 HTTPS(HyperText Transfer Protocol Secure) 로 씁니다. 실리는 내용은 바뀌지 않고 나르는 회선만 덮입니다.
flowchart TD
subgraph T["TLS 가 덮는 범위"]
H["HTTP/1.1 · 오가는 글자"]
end
H --> C["TCP"]
C --> I["IP"]
관련 항목
정의를 담은 표준 문서
RFC 9112 · RFC 9110 · RFC 9111 · RFC 7230 · RFC 2616 · IETF
메시지를 이루는 구성 요소
메시지 · 요청줄 · 상태줄 · 헤더 · 요청 대상 · 메시지 본문 · 트레일러
요청이 쓰는 메서드
요청 메서드 · GET · POST · PUT · DELETE · HEAD · OPTIONS · PATCH · CONNECT · TRACE
본문의 끝을 알리는 수단
Content-Length · 청크 전송 인코딩 · Transfer-Encoding · 스트리밍 응답
연결을 이어 쓰는 장치
지속 연결 · Keep-Alive · Connection 헤더 · 파이프라이닝 · 커넥션 풀
줄서기를 피하려던 그 시절의 우회책
헤드 오브 라인 블로킹 · 도메인 샤딩 · 스프라이트 시트 · 인라이닝 · 멀티플렉싱
아래에 깔려 나르는 프로토콜
같은 쓰임을 두고 겨루는 다른 전송 규칙
HTTP · HTTP/2 · HTTP/3 · QUIC · 웹소켓 · 서버 전송 이벤트
응답의 뜻을 정하는 값
상태 코드 · 콘텐츠 협상 · 조건부 요청 · ETag · MIME 타입 · 쿠키
메시지를 읽고 넘기는 중간 장비
프록시 · 리버스 프록시 · 게이트웨이 · HTTP 캐시 · 가상 호스팅 · Host 헤더 · nginx
이 위에 얹혀 도는 규약
다른 이름: HTTP/1.1 · HTTP 1.1 · HTTP version 1.1