사전 RFC 9112
표준

RFC 9112

gabury1고친 사람 github-actions[bot]

RFC 9112 는 웹의 요청과 응답을 어떤 글자 모양으로 적을지 정한 표준 문서입니다. 받는 쪽이 흘러오는 바이트에서 메시지 하나를 어디까지 읽을지를 이 문서가 못박습니다. 한 번 맺은 연결을 언제까지 쓰고 언제 닫을지도 함께 정합니다.

쉽고 빠른 이해

RFC 9112 는 웹이 쓰는 약속인 HTTP(HyperText Transfer Protocol, 하이퍼텍스트 전송 프로토콜)의 1.1 판에서 메시지를 어떤 글자로 적는지 정한 문서입니다. 브라우저가 서버에 보내는 GET /index.html HTTP/1.1 한 줄의 생김새가 이 규칙을 따릅니다.

규칙이 없으면 보내는 쪽과 받는 쪽이 바이트를 다르게 끊어 읽습니다. 한 요청이 둘로 읽히거나 두 요청이 하나로 붙어 읽힙니다.

돌아가는 방식은 이렇습니다.

  1. 첫 줄에 무엇을 요구하는지, 또는 결과가 무엇인지를 적습니다
  2. 그 아래로 이름과 값을 짝지은 줄을 늘어놓고 빈 줄 하나로 끝을 알립니다
  3. 본문이 몇 바이트인지는 그 줄들에 적힌 값으로 정합니다

대가도 있습니다. 메시지가 사람이 읽는 글자라서 받는 쪽이 한 줄씩 훑어야 합니다. 요청을 연달아 보내도 응답은 보낸 순서대로만 돌아옵니다. 앞 응답이 늦으면 뒤 응답은 다 만들어 놓고도 기다립니다.

상세

RFC(Request for Comments, 의견 요청)는 IETF(Internet Engineering Task Force, 인터넷 기술 표준을 만드는 단체)가 번호를 붙여 내는 문서입니다. RFC 9112 는 그중 「HTTP/1.1」이라는 제목을 단 문서입니다.

이 문서가 정하는 것은 HTTP 의 1.1 판이 메시지를 어떤 글자로 적느냐입니다. 메시지는 요청 하나 또는 응답 하나를 이루는 바이트 묶음입니다.

이 문서는 2022년 6월에 나왔습니다. 이전 판인 RFC 7230 을 대체합니다. 인터넷 표준 단계까지 오른 RFC 는 STD(Standard, 표준) 번호를 하나 더 받습니다. RFC 9112 가 받은 번호는 STD 99 입니다.

요청과 응답이 무슨 뜻인지는 RFC 9110 이 맡습니다. 어떤 메서드가 무엇을 요구하고 어떤 응답 코드가 무슨 결과를 가리키는지가 거기 있습니다. 같은 뜻을 다른 바이트 모양으로 나르는 HTTP/2 와 HTTP/3 은 각각 다른 문서가 정합니다.

flowchart TD
    A["RFC 7230 · 이전 판"] -->|대체| B["RFC 9112 · 1.1 판이 쓰는 바이트 모양"]
    C["RFC 9110 · 요청과 응답의 뜻"] -->|그 뜻을 나른다| B

메시지의 모양

HTTP/1.1 메시지는 사람이 읽을 수 있는 글자로 적습니다. 첫 줄을 시작줄이라고 부릅니다. 그 아래로 이름과 값을 짝지은 필드 줄이 이어집니다. 이 필드 줄 묶음을 헤더라고도 부릅니다.

빈 줄 하나가 헤더의 끝을 알립니다. 본문이 있으면 빈 줄 다음에 붙습니다.

flowchart TD
    M["메시지"] --> S["시작줄"]
    M --> F["필드 줄 · 0개 이상"]
    M --> E["빈 줄"]
    M --> B["본문 · 있을 때만"]
    S --> R["요청줄"]
    S --> T["상태줄"]

아래는 요청 하나입니다.

GET /index.html HTTP/1.1   // 시작줄
Host: example.com          // 필드 줄
Accept: text/html          // 필드 줄
                           // 빈 줄. 헤더가 끝났다

모든 줄은 CRLF(Carriage Return Line Feed, 캐리지 리턴과 라인 피드)로 끝냅니다. 줄바꿈을 두 바이트로 정해 둔 덕분에 받는 쪽은 그 두 바이트를 찾아 줄을 끊습니다. 빈 줄은 CRLF 하나만 있는 줄입니다. 그것이 헤더가 끝났다는 표시입니다.

응답도 모양이 같습니다. 첫 줄만 다릅니다.

HTTP/1.1 200 OK            // 시작줄
Content-Type: text/html    // 필드 줄
Content-Length: 27         // 본문은 27바이트
                           // 빈 줄
<html>Hello, world!</html> // 본문

요청과 응답의 차이는 둘뿐입니다. 시작줄, 그리고 본문의 길이를 정하는 방법입니다.

시작줄 — 요청줄과 상태줄

시작줄은 빈칸으로 나뉜 세 토막입니다. 요청이면 요청줄, 응답이면 상태줄이라고 부릅니다.

첫 토막 둘째 토막 셋째 토막
요청줄 요청 메서드 요청 대상 프로토콜 판
상태줄 프로토콜 판 상태 코드 사유 문구

요청 대상은 대개 /index.html 처럼 경로로 적습니다. 프록시에 보낼 때는 http://example.com/index.html 처럼 주소 전체를 적기도 합니다. 프록시는 요청을 대신 받아 다른 서버로 넘기는 중계 장비라서 어느 서버로 보낼지를 알아야 하기 때문입니다.

사유 문구는 OK 나 Not Found 처럼 사람이 읽으라고 붙인 문장입니다. 기계는 앞의 숫자만 보면 됩니다. 그래서 이 토막은 비어 있어도 됩니다.

필드 줄

필드 줄은 이름과 콜론과 값으로 이루어집니다. 필드 이름은 대소문자를 가리지 않습니다. Host 와 host 는 같은 이름입니다.

이름과 콜론 사이에는 빈칸을 넣을 수 없습니다. 받는 쪽은 그런 줄이 들어오면 그 메시지를 거절해야 합니다. 빈칸 하나를 어디는 이름의 일부로 보고 어디는 아니라고 보면, 같은 메시지가 장비마다 다르게 읽히기 때문입니다.

요청에는 Host 필드를 반드시 하나 넣습니다. 서버 한 대가 여러 도메인의 요청을 받기 때문에, 어느 이름으로 찾아왔는지를 알려 줘야 서버가 내줄 것을 고를 수 있습니다.

본문의 끝을 정하는 방법

연결 위로는 바이트가 끊김 없이 흘러옵니다. 본문을 어디까지 읽고 멈출지는 앞서 읽은 필드 줄이 알려 줍니다.

Content-Length 는 본문의 바이트 수를 미리 적어 두는 필드입니다. 길이를 아는 응답이라면 이것이 가장 단순합니다.

Transfer-Encoding 은 본문을 조각으로 나눠 보낼 때 쓰는 필드입니다. 조각을 다 보냈다는 표시는 길이가 0 인 조각입니다.

받는 쪽은 아래 순서로 따집니다.

flowchart TD
    A["헤더를 다 읽었다"] --> B{"Transfer-Encoding 이 있나"}
    B -->|있다| C["길이 0 인 조각이 나올 때까지 읽는다"]
    B -->|없다| D{"Content-Length 가 있나"}
    D -->|있다| E["적힌 바이트 수만큼 읽는다"]
    D -->|없다| F{"요청인가 응답인가"}
    F -->|요청| G["본문이 없는 것으로 본다"]
    F -->|응답| H["연결이 닫힐 때까지 읽는다"]

둘이 함께 오면 Transfer-Encoding 이 이깁니다.

길이 표시가 하나도 없는 응답은 연결이 닫히는 것으로 끝을 알립니다. 요청에는 이 방법이 없습니다. 클라이언트가 연결을 닫아 버리면 서버가 응답을 돌려보낼 길이 사라지기 때문입니다. 그래서 길이 표시가 없는 요청은 본문이 없는 요청입니다.

청크 전송 인코딩

만들면서 바로 내보내는 응답은 다 만들기 전에는 길이를 모릅니다. 그런 응답을 위해 본문을 조각으로 나눠 보내는 방법이 청크 전송 인코딩입니다. 조각마다 그 조각의 길이를 앞에 적으므로, 전체 길이를 몰라도 보내기 시작할 수 있습니다.

4                          // 다음 조각은 4바이트
Wiki
5                          // 다음 조각은 5바이트
pedia
0                          // 길이 0. 본문이 끝났다

길이는 십육진수로 적습니다. 길이 0 인 조각 뒤에는 트레일러를 붙일 수 있습니다. 트레일러는 본문을 다 만든 뒤에야 값이 정해지는 필드를 담습니다. 본문 전체를 훑어야 나오는 검사값이 그런 값입니다.

한 연결을 나눠 쓰는 방법

요청 하나마다 TCP(Transmission Control Protocol, 전송 제어 프로토콜) 연결을 새로 맺으면 연결을 맺는 왕복이 매번 듭니다. HTTP/1.1 메시지는 이 연결 위에 실려 오갑니다. 그래서 1.1 판은 응답을 보낸 뒤에도 연결을 열어 둡니다.

이것이 지속 연결입니다. 따로 말하지 않으면 기본 동작입니다. 더 쓰지 않을 쪽은 Connection 필드에 close 를 적어 알립니다.

응답을 기다리지 않고 요청 여럿을 연달아 보낼 수도 있습니다. 이것을 HTTP 파이프라이닝이라고 부릅니다.

sequenceDiagram
    participant 클라이언트
    participant 서버
    클라이언트->>서버: 요청 1
    클라이언트->>서버: 요청 2
    Note over 서버: 응답 2 를 먼저 다 만들어 놓고도 보내지 못한다
    서버-->>클라이언트: 응답 1
    서버-->>클라이언트: 응답 2

응답에는 어느 요청에 대한 것인지를 가리키는 표시가 없습니다. 그래서 서버는 받은 순서 그대로 응답을 돌려줘야 합니다.

앞 요청의 응답이 늦으면 뒤 요청의 응답은 다 만들어 놓고도 기다립니다. 이것이 파이프라이닝을 쓰는 클라이언트가 드문 까닭입니다. 클라이언트는 대신 연결을 여러 개 여는 쪽을 고릅니다.

끊어 읽기가 갈리면 생기는 일

메시지 사이에 낀 장비들이 같은 바이트를 다르게 끊어 읽으면 문제가 생깁니다. 앞단 프록시는 Content-Length 로 끊고 뒷단 서버는 Transfer-Encoding 으로 끊는 경우가 그렇습니다. 한쪽이 본문으로 읽은 꼬리가 다른 쪽에는 다음 요청의 머리가 됩니다.

sequenceDiagram
    participant 클라이언트
    participant 프록시
    participant 서버
    클라이언트->>프록시: 요청 하나
    Note over 프록시: Content-Length 로 끊어 읽는다
    프록시->>서버: 읽은 대로 넘긴다
    Note over 서버: Transfer-Encoding 으로 끊어 읽는다
    Note over 서버: 프록시가 본문으로 읽은 꼬리를 다음 요청의 머리로 읽는다

공격자는 그 틈에 요청을 하나 더 밀어 넣을 수 있습니다. 이것을 HTTP 요청 스머글링이라고 부릅니다.

이 문서가 끊어 읽는 규칙을 촘촘하게 적어 둔 까닭이 이것입니다. 두 필드가 함께 온 요청은 받는 쪽이 오류로 다뤄야 합니다. 넘겨받은 메시지를 다시 내보내는 장비라면 자기가 읽은 대로 다시 적어 보내야 합니다.

요구 강도

이 문서는 규칙마다 요구 강도를 대문자 낱말로 붙입니다. 이 낱말들의 뜻은 RFC 2119 가 정합니다. 강도를 갈라 두어야 어디까지 어겨도 표준을 지킨 것인지 알 수 있습니다.

낱말 뜻 이 문서의 예
MUST · MUST NOT 반드시 지킵니다 요청에 Host 필드를 하나 넣습니다
SHOULD 이유가 있으면 어길 수 있습니다 연결을 닫을 때 먼저 알립니다
MAY 해도 되고 안 해도 됩니다 길이 0 인 조각 뒤에 트레일러를 붙입니다

관련 항목

함께 HTTP 를 정의하는 표준 문서

RFC 9110 · RFC 9111 · RFC 9113 · RFC 9114 · RFC 7230 · RFC 2119 · RFC 5234

이 문서를 펴내고 번호를 매기는 기구와 체계

RFC · IETF · STD · IANA

메시지를 이루는 구성 요소

시작줄 · 요청줄 · 상태줄 · 헤더 · 필드 · 메시지 본문 · 트레일러 · CRLF · 사유 문구

이 문서가 뜻을 정하는 헤더 필드

Content-Length · Transfer-Encoding · Connection · Host · TE · Upgrade · Expect

본문을 조각으로 나눠 보내는 방식

청크 전송 인코딩 · 전송 인코딩 · 콘텐츠 인코딩 · 스트리밍 응답

한 연결을 여러 요청이 나눠 쓰는 방법

지속 연결 · HTTP 파이프라이닝 · Keep-Alive · 커넥션 풀 · 헤드 오브 라인 블로킹

메시지를 잘못 끊어 읽을 때 터지는 공격

HTTP 요청 스머글링 · HTTP 응답 스플리팅 · 캐시 포이즈닝

이 메시지를 중간에서 다시 읽는 중계 장비

프록시 · 리버스 프록시 · 게이트웨이 · 로드 밸런서 · CDN

같은 뜻을 다른 바이트 모양으로 나르는 프로토콜

HTTP/1.1 · HTTP/2 · HTTP/3 · QUIC · 웹소켓

이 메시지가 올라타는 아래 계층 프로토콜

TCP · TLS · HTTPS · 소켓 · 포트

다른 이름: STD 99