HTTP
브라우저가 서버에 무엇을 달라고 말하고 서버가 그것을 돌려주는 규칙입니다. 요청 하나를 보내면 응답 하나가 돌아옵니다. 서버는 앞선 요청을 기억해 두지 않습니다.
상세
HTTP(HyperText Transfer Protocol, 하이퍼텍스트 전송 프로토콜)는 클라이언트/서버 프로토콜입니다. 신뢰성 있는 전송 계층 또는 세션 계층의 "연결" 위에서 동작합니다. 여기서 클라이언트는 하나 이상의 HTTP 요청을 보내려고 서버에 연결을 맺는 프로그램입니다. 서버는 HTTP 요청을 처리해 HTTP 응답을 보내려고 연결을 받아들이는 프로그램입니다. 클라이언트와 서버라는 말은 특정 연결에서 그 프로그램이 맡은 역할만 가리킵니다. 같은 프로그램이 어떤 연결에서는 클라이언트로, 다른 연결에서는 서버로 동작할 수도 있습니다.
HTTP 는 상태 없는 프로토콜로 정의되어 있습니다. 요청 메시지 하나하나의 의미를 따로 떼어놓고 이해할 수 있다는 뜻입니다. 연결과 그 위의 메시지 사이의 관계는 메시지 해석에 영향을 주지 않습니다. 그래서 CONNECT 요청이나 Upgrade 헤더 필드가 붙은 요청은 연결의 첫 메시지가 아니어도 언제든 나타날 수 있습니다. 많은 구현이 이 상태 없는 설계에 기대어 프록시된 연결을 재사용하거나 요청을 여러 서버로 동적으로 로드 밸런싱합니다.
주고받는 것은 메시지입니다. 클라이언트는 메서드와 요청 대상을 담은 요청 메시지를 서버에 보냅니다. 요청에는 요청 수식자·클라이언트 정보·표현 메타데이터를 담은 헤더 필드, 메서드에 따라 처리될 내용, 내용을 보내는 동안 모은 정보를 전하는 트레일러 필드가 함께 들어가기도 합니다. 서버는 상태 코드를 담은 응답 메시지를 하나 이상 보내 답합니다. 응답에도 서버 정보·리소스 메타데이터·표현 메타데이터를 담은 헤더 필드, 상태 코드에 따라 해석될 내용, 트레일러 필드가 들어가기도 합니다.
메시지의 뼈대는 판(버전)을 가리지 않고 같습니다. HTTP 의 각 주요 판은 메시지를 전하는 자기 문법을 따로 정의합니다. RFC 9110 은 그 위에 추상 자료형 하나를 세워 둡니다. 한 판의 메시지가 다른 판을 거쳐 중계되어도 뜻이 바뀌지 않게 하려는 것입니다. 메시지는 네 가지로 이루어집니다. 메시지를 설명하고 경로를 정하는 제어 데이터, 그 제어 데이터를 확장하고 보내는 쪽·메시지·내용·맥락에 관한 정보를 더 실어 나르는 이름/값 쌍의 헤더 조회표, 길이가 정해지지 않을 수도 있는 내용의 흐름, 내용을 보내는 동안 얻은 정보를 전하는 이름/값 쌍의 트레일러 조회표입니다. 프레이밍과 제어 데이터가 먼저 가고 헤더 구역이 뒤따릅니다. 내용이 있으면 헤더 구역 다음에 오고, 그 뒤에 트레일러 구역이 올 수도 있습니다.
메시지는 자기 설명적이도록 의도되어 있습니다. 받는 쪽이 그 메시지에 관해 알아야 할 것은 전부 메시지 자신을 보고 알아낼 수 있습니다. 전송 중에 압축되거나 생략된 부분을 되돌린 뒤라는 단서가 붙습니다. 보내는 쪽이 앞선 메시지들로 쌓아 둔 애플리케이션 상태를 알 필요는 없습니다. 다만 클라이언트는 대응하는 응답을 파싱·해석·캐싱할 때 그 요청을 기억하고 있어야 합니다. HEAD 메서드에 대한 응답은 GET 에 대한 응답의 앞부분과 똑같이 생겼지만 같은 방식으로 파싱할 수 없습니다.
교환 순서
한 왕복은 클라이언트가 보내는 요청 메시지 하나와 서버가 돌려주는 응답 메시지로 이루어집니다.
sequenceDiagram
participant 클라이언트
participant 서버
클라이언트->>서버: request-line
클라이언트->>서버: field-line 들, 빈 줄, message-body
서버-->>클라이언트: 1xx status-line (중간 응답, 올 수도 있습니다)
서버-->>클라이언트: status-line
서버-->>클라이언트: field-line 들, 빈 줄, message-body
연결을 어떻게 맺는지는 이 순서에 들어 있지 않습니다. RFC 9112 는 여러 전송 계층·세션 계층 프로토콜로 연결을 맺는 방법을 기술하는 일이 이 명세의 범위를 벗어난다고 적습니다. 대신 HTTP 연결 하나가 밑단의 전송 연결 하나에 대응한다고만 적어 둡니다.
HTTP/1.1 메시지 하나는 시작줄 다음에 CRLF 가 오고, 헤더 필드 줄이 없거나 여럿 오고, 헤더 구역의 끝을 알리는 빈 줄이 오고, 선택적인 메시지 본문이 옵니다.
HTTP-message = start-line CRLF
*( field-line CRLF )
CRLF
[ message-body ]
요청과 응답은 문법상 시작줄에서만 갈립니다. 그리고 메시지 본문의 길이를 정하는 알고리즘에서 갈립니다. 요청의 시작줄은 요청줄입니다. 메서드 토큰으로 시작해 공백 하나, 요청 대상, 다시 공백 하나가 오고 프로토콜 판으로 끝납니다. 메서드 토큰은 대상 리소스에 수행할 요청 메서드를 가리키고 대소문자를 구분합니다.
request-line = method SP request-target SP HTTP-version
응답 메시지의 첫 줄은 상태줄입니다. 프로토콜 판, 공백, 상태 코드, 다시 공백이 오고 상태 코드를 설명하는 선택적인 텍스트 구절로 끝납니다. 상태 코드는 클라이언트의 요청을 이해하고 만족시키려던 서버의 시도 결과를 나타내는 세 자리 정수입니다. 받는 쪽은 그 상태 코드가 자기가 아는 것이면 그 코드에 정의된 의미로, 모르는 것이면 그 코드가 속한 부류에 따라 나머지 응답 메시지를 해석합니다. 이유 구절은 클라이언트가 무시해야 합니다. 그것이 믿을 만한 정보 통로가 아니기 때문입니다. 로케일에 맞춰 번역되거나, 중개자가 덮어쓰거나, 다른 판의 HTTP 로 전달되면서 버려질 수 있습니다.
status-line = HTTP-version SP status-code SP [ reason-phrase ]
어느 응답이 어느 요청의 짝인지는 순서가 정합니다. HTTP/1.1 에는 요청 메시지와 그 응답 메시지를 묶어 주는 요청 식별자가 없습니다. 그래서 같은 연결에서 응답이 도착하는 순서가 요청을 보낸 순서와 정확히 일치한다는 것에 기댑니다. 한 연결에 처리 중인 요청을 여럿 걸어 둔 클라이언트는 보낸 순서대로 그 목록을 유지해야 합니다. 그리고 받은 응답 메시지를 아직 최종 응답을 못 받은 첫 요청에 붙여야 합니다.
한 요청에 응답 메시지가 둘 이상 오는 경우는 하나뿐입니다. 1xx 정보 응답이 하나 이상 최종 응답보다 앞설 때입니다. 1xx 는 요청한 동작을 끝내고 최종 응답을 보내기 전에 연결 상태나 요청 진행을 알리는 중간 응답입니다. 1xx 응답은 헤더 구역이 끝나는 자리에서 끝나며 내용이나 트레일러를 담을 수 없습니다. 클라이언트는 최종 응답 앞에 온 1xx 응답을 하나 이상 파싱할 수 있어야 합니다. 예상하지 못한 1xx 응답이면 사용자 에이전트가 무시해도 됩니다. 예를 들어 100 Continue 는 요청의 첫 부분이 도착했고 아직 서버에게 거절당하지 않았다는 뜻입니다. 요청에 100-continue 기대를 담은 Expect 헤더 필드가 있었다면 서버가 요청 내용을 받기를 원한다는 신호이고, 클라이언트는 요청을 계속 보내고 그 100 응답은 버리는 편이 좋습니다.
예시
RFC 9110 이 싣고 있는 HTTP/1.1 한 왕복입니다. http://www.example.com/hello.txt 에 대한 GET
요청입니다.
GET /hello.txt HTTP/1.1
User-Agent: curl/7.64.1
Host: www.example.com
Accept-Language: en, mi
첫 줄이 요청줄입니다. 메서드가 GET, 요청 대상이 /hello.txt, 프로토콜 판이 HTTP/1.1
입니다. 그 아래 세 줄이 헤더 필드입니다. Host 가 대상 호스트를, User-Agent 가 보내는
프로그램을, Accept-Language 가 원하는 언어를 담고 있습니다.
HTTP/1.1 200 OK
Date: Mon, 27 Jul 2009 12:28:53 GMT
Server: Apache
Last-Modified: Wed, 22 Jul 2009 19:15:56 GMT
ETag: "34aa387-d-1568eb00"
Accept-Ranges: bytes
Content-Length: 51
Vary: Accept-Encoding
Content-Type: text/plain
Hello World! My content includes a trailing CRLF.
첫 줄이 상태줄입니다. 프로토콜 판 HTTP/1.1, 상태 코드 200, 이유 구절 OK 입니다. 그 아래가
헤더 필드이고, 빈 줄 하나가 헤더 구역의 끝을 알립니다. 빈 줄 다음이 메시지 본문입니다.
Content-Type 이 본문을 text/plain 으로, Content-Length 가 그 길이를 51 바이트로 밝히고
있습니다. ETag 와 Last-Modified 는 조건부 요청과 캐싱이 쓰는 표현 메타데이터입니다.
갈래
의미는 같고 그 의미를 전송 위에 어떻게 실어 나르는가가 갈리는 축입니다. HTTP 의 각 주요 판은 메시지를 전하는 자기 문법을 따로 정의합니다. 어느 판을 쓰는가는 애플리케이션의 성능과 이어져 있습니다. 각 판이 밑단 전송을 어떻게 쓰는지, 그 전송이 어떤 조건에서 도는지에 달려 있기 때문입니다.
HTTP/1.1
공백으로 구분된 텍스트 필드로 HTTP 메시지를 전합니다. 사람이 읽을 수 있는 대신 공백을 메시지 서식에 쓰는 탓에 파싱이 복잡해지고 제각각인 동작을 지나치게 너그럽게 받아들이게 됩니다.
동시성은 연결을 여러 개 여는 방식으로 얻습니다. HTTP/1.0 은 한 TCP 연결에 처리 중인 요청을 하나만 둘 수 있었습니다. HTTP/1.1 은 요청 파이프라이닝을 더했지만 요청 동시성 문제를 부분적으로만 해결했고 여전히 애플리케이션 계층의 머리줄 막힘을 겪습니다. 서버 쪽 처리가 오래 걸리거나 아주 큰 내용을 나르는 요청 하나가 같은 연결의 뒤 요청들을 막는 현상입니다. 그래서 클라이언트들은 서버에 연결을 여러 개 맺어 요청을 병렬로 처리합니다. 대가가 따라옵니다. 연결마다 서버 자원을 씁니다. TCP 는 여러 연결 사이에 혼잡 제어를 공유하지 않으므로 혼잡 제어와 네트워크 효율에 부정적인 영향이 있습니다. 연결 수를 늘리면 처음에 동기화된 송신 행동이 겹쳐 원래 없었을 혼잡을 만들어 내기도 합니다. 명세는 최대 연결 수를 못 박지 않고 클라이언트가 보수적으로 열기를 권합니다.
HTTP/2
바이너리 프레이밍과 다중화 계층을 도입해 전송 계층은 그대로 두고 지연을 줄입니다. HTTP 의 의미를 밑단 연결에 최적화해 대응시킨 것입니다. 같은 연결 위에서 메시지를 서로 끼워 넣을 수 있고 HTTP 필드를 효율적으로 부호화합니다. 요청에 우선순위를 매길 수도 있어 더 중요한 요청이 먼저 끝나게 합니다. HTTP/1.x 에 비해 TCP 연결을 적게 쓰므로 다른 흐름과 덜 다투고 연결이 오래 삽니다.
sequenceDiagram
participant 클라이언트
participant 서버
클라이언트->>서버: 스트림 하나의 HEADERS
클라이언트->>서버: 다른 스트림의 HEADERS
서버-->>클라이언트: 다른 스트림의 DATA
서버-->>클라이언트: 스트림 하나의 DATA
Note over 클라이언트,서버: 한 연결 위에서 프레임이 서로 끼워집니다
다중화의 단위가 스트림입니다. 스트림은 HTTP/2 연결 안에서 클라이언트와 서버가 주고받는 독립적인 양방향 프레임 열입니다. HTTP/2 연결 하나에 동시에 열린 스트림이 여럿 있을 수 있고, 양쪽 끝 어느 쪽이든 여러 스트림의 프레임을 서로 끼워 넣습니다. 스트림은 어느 쪽이든 열 수 있고 어느 쪽이든 닫을 수 있습니다. 프레임을 보내는 순서에는 의미가 있어 받는 쪽은 받은 순서대로 처리합니다. 특히 HEADERS 프레임과 DATA 프레임의 순서는 의미상 유의미합니다. 스트림은 정수로 식별하고, 그 식별자는 스트림을 시작한 쪽이 붙입니다.
남는 문제가 있습니다. TCP 의 머리줄 막힘은 이 프로토콜이 해결하지 않습니다. HTTP/2 다중화의 병렬성이 TCP 의 손실 복구 메커니즘에는 보이지 않기 때문입니다. 패킷 하나가 유실되거나 순서가 뒤바뀌면 그 패킷과 직접 관계가 없는 트랜잭션까지 포함해 진행 중인 모든 트랜잭션이 멈춥니다.
HTTP/3
같은 HTTP 의미를 새 전송 프로토콜인 QUIC 위에서 나릅니다. QUIC 은 HTTP/2 프레이밍 계층이 주던 것과 비슷한 스트림 다중화와 스트림별 흐름 제어를 자기 안에 갖고 있습니다. 스트림 수준에서 신뢰성을 주고 연결 전체에 걸쳐 혼잡 제어를 하므로 TCP 대응에 비해 HTTP 의 성능을 개선할 여지가 있습니다. QUIC 은 TLS 1.3 도 전송 계층에 품고 있어 TCP 위에 TLS 를 올린 것과 견줄 만한 기밀성과 무결성을 주면서 연결 설정 지연은 TCP Fast Open 수준으로 줄입니다.
HTTP/3 은 HTTP/2 의 설계를 많이 가져왔습니다. 스트림 수명과 흐름 제어 문제는 QUIC 에 맡기고, 각 스트림 위에서는 HTTP/2 프레이밍과 비슷한 바이너리 프레이밍을 씁니다. 클라이언트는 어느 종단점에 HTTP/3 서버가 있다는 것을 알게 되면 QUIC 연결을 엽니다. 스트림 안의 기본 단위는 프레임이고, HEADERS 프레임과 DATA 프레임이 요청과 응답의 바탕이 됩니다. 연결 전체에 적용되는 프레임은 전용 제어 스트림으로 나릅니다. 요청 하나와 응답 하나가 QUIC 스트림 하나를 씁니다. 스트림들은 서로 독립적이라 어느 한 스트림이 막히거나 패킷 손실을 겪어도 다른 스트림의 진행을 막지 않습니다.
보장과 가정
HTTP 메시징은 밑단의 전송 계층·세션 계층 연결 프로토콜과 무관합니다. 다만 전제 하나는 둡니다. HTTP 는 신뢰성 있는 전송을 가정합니다. 요청이 순서대로 배달되고 그에 대응하는 응답도 순서대로 배달된다는 것까지 가정합니다. HTTP 요청·응답 구조를 밑단 전송 프로토콜의 데이터 단위에 어떻게 대응시키는지는 명세 범위 밖입니다. 연결 하나가 밑단 전송 연결 하나에 대응한다는 것만 정해져 있습니다.
이 가정 위에 HTTP/1.1 의 응답 짝짓기가 서 있습니다. 요청 식별자가 없으므로 도착 순서만이 짝을 정합니다. 순서가 어긋나면 응답이 엉뚱한 요청에 붙습니다. 명세는 어긋난 자리에서 무엇을 할지도 정해 두었습니다. 처리 중인 요청이 없는 연결에서 데이터를 받은 클라이언트는 그것을 유효한 응답으로 여겨서는 안 됩니다. 그리고 그 연결을 닫는 편이 좋습니다. 메시지 구분이 이미 모호해졌기 때문입니다. 데이터가 CRLF 하나 이상뿐이면 예외입니다.
상태 없음도 보장이자 가정입니다. 요청 하나의 의미는 따로 떼어놓고 이해할 수 있습니다. 그 대가로 서버는 같은 연결의 두 요청이 같은 사용자 에이전트에서 왔다고 가정해서는 안 됩니다. 연결이 보안되어 있고 그 에이전트 전용일 때만 예외입니다. 일부 비표준 HTTP 확장이 이 요구사항을 어긴 것으로 알려져 있고, 그 결과 보안과 상호운용성 문제가 생겼습니다.
메서드가 무엇을 약속하는지도 정해져 있습니다. 정의된 의미가 사실상 읽기 전용인 요청 메서드를 안전하다고 부릅니다. 클라이언트가 오리진 서버의 상태 변경을 요청하지도 기대하지도 않는다는 뜻입니다. 안전한 메서드를 적당히 쓰는 것이 해를 끼치거나 재산을 잃게 하거나 오리진 서버에 이례적인 부담을 지울 것으로 기대되지도 않습니다. 이 명세가 정의한 메서드 중에서는 GET·HEAD·OPTIONS·TRACE 가 안전합니다. 한정이 붙습니다. 이 정의는 구현이 잠재적으로 해로운 동작이나 완전히 읽기 전용이지는 않은 동작, 부작용을 내는 동작을 안전한 메서드 안에 넣는 것을 막지 않습니다. 중요한 것은 클라이언트가 그 추가 동작을 요청하지 않았고 그에 대한 책임을 질 수 없다는 점입니다.
이 가정 위에 자동 수집기와 프리페치가 섭니다. 안전한 메서드와 안전하지 않은 메서드를 가르는 목적이
거기에 있습니다. 가정이 깨지는 자리도 명세가 짚습니다. 대상 URI 안의 파라미터가 동작을 고르게
만들어진 리소스가 그렇습니다. page?do=delete 처럼 질의 파라미터 안에 동작을 넣는 방식은 웹 기반
콘텐츠 편집 소프트웨어에서 흔합니다. 그런 리소스의 목적이 안전하지 않은 동작을 수행하는 것이라면
리소스 소유자는 안전한 요청 메서드로 접근했을 때 그 동작을 막거나 허용하지 않아야 합니다. 그렇게
하지 않으면 링크 관리·프리페치·검색 색인 구축 같은 목적으로 모든 URI 참조에 GET 을 거는 자동
프로세스가 불행한 부작용을 냅니다.
재시도는 멱등성 위에 섭니다. 같은 메서드로 동일한 요청을 여러 번 보냈을 때 서버에 의도된 효과가 한 번 보냈을 때와 같으면 그 메서드를 멱등하다고 부릅니다. 이 명세가 정의한 메서드 중에서는 PUT·DELETE 와 안전한 메서드들이 멱등합니다. 멱등한 메서드를 따로 구분하는 이유는 클라이언트가 서버의 응답을 읽기 전에 통신 실패가 나면 요청을 자동으로 되풀이할 수 있기 때문입니다. PUT 요청을 보냈는데 응답을 받기 전에 밑단 연결이 닫혔다면 클라이언트는 새 연결을 맺고 그 요청을 다시 보낼 수 있습니다. 원래 요청이 성공했더라도 되풀이가 같은 의도된 효과를 낸다는 것을 알기 때문입니다. 다만 응답은 다를 수 있습니다.
가정이 없는 쪽에는 금지가 붙습니다. 클라이언트는 멱등하지 않은 메서드의 요청을 자동으로 재시도하지 않는 편이 좋습니다. 메서드와 무관하게 그 요청의 의미가 실제로 멱등하다는 것을 알 방법이 있거나, 원래 요청이 적용되지 않았음을 알아낼 방법이 있을 때만 예외입니다. 프록시는 멱등하지 않은 요청을 자동으로 재시도해서는 안 됩니다. 클라이언트는 실패한 자동 재시도를 다시 자동으로 재시도하지 않는 편이 좋습니다. 멱등성 역시 사용자가 요청한 것에만 적용됩니다. 서버가 멱등한 요청마다 각각을 따로 기록하거나 리비전 이력을 남기거나 다른 멱등하지 않은 부작용을 구현하는 것은 자유입니다.
관련 항목
HTTP 를 정의하는 표준
RFC 9110 · RFC 9112 · RFC 9113 · RFC 9114
HTTP 안에서 갈리는 버전
HTTP 가 올라타는 전송 계층
HTTP 통신에 관여하는 역할
클라이언트 · 서버 · 프록시 · 사용자 에이전트 · 오리진 서버 · 브라우저
HTTP 요청을 실행하는 메서드
GET · HEAD · OPTIONS · TRACE · PUT · DELETE · POST · CONNECT
HTTP 메시지에 실리는 헤더 필드
Host · User-Agent · Accept-Language · Content-Type · Content-Length · ETag · Last-Modified · Accept-Ranges · Vary · Date · Server · Expect · Upgrade
HTTP 메시지를 이루는 부분
메시지 · 요청줄 · 상태줄 · 제어 데이터 · 트레일러 · 상태 코드 · 메타데이터 · 요청 메서드 · 요청 대상 · URI
HTTP 판 차이를 만드는 전송 요소
성능 · 지연 · 동시성 · 네트워크 · 혼잡 제어 · 머리줄 막힘 · 스트림 · 트랜잭션
HTTP 메서드가 지키는 성질
HTTP 가 안전한 메서드로 뒷받치는 자동 처리
다른 이름: Hypertext Transfer Protocol · 하이퍼텍스트 전송 프로토콜