역주: 용어는 glossary.md, 번역 방식은 번역 가이드를 따른다. 이 문서에서 [HTTP]는 RFC 9110(번역), [HTTP/1.1]은 RFC 9112(번역)를 가리킨다.
이 명세는 HTTP 버전 2(HTTP/2)라고 부르는, HTTP(Hypertext Transfer Protocol) 의미론의 최적화된 표현을 설명한다. HTTP/2는 필드 압축(field compression)을 도입하고 같은 연결에서 여러 교환을 동시에 할 수 있게 하여, 네트워크 자원을 더 효율적으로 쓰고 지연 시간을 줄인다.
이 문서는 RFC 7540과 RFC 8740을 대체한다.
이 문서는 인터넷 표준화 트랙(Standards Track) 문서다.
이 문서는 IETF(Internet Engineering Task Force)의 결과물이며 IETF 커뮤니티의 합의를 나타낸다. 이 문서는 공개 검토를 거쳤고 IESG(Internet Engineering Steering Group)의 발행 승인을 받았다. 인터넷 표준에 대한 자세한 정보는 RFC 7841의 2절에 있다.
이 문서의 현재 상태, 정오표, 의견을 보내는 방법은 https://www.rfc-editor.org/info/rfc9113 에서 확인할 수 있다.
Copyright (c) 2022 IETF Trust and the persons identified as the document authors. All rights reserved.
This document is subject to BCP 78 and the IETF Trust’s Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License.
Copyright (c) 2022 IETF Trust 및 문서 저자로 명시된 사람들. 모든 권리 보유.
이 문서에는 BCP 78과, 이 문서의 발행일에 유효한 IETF 문서 관련 IETF Trust 법적 조항(https://trustee.ietf.org/license-info)이 적용된다. 이 문서들은 이 문서에 대한 독자의 권리와 제한을 설명하므로 주의 깊게 검토하기 바란다. 이 문서에서 추출한 코드 구성 요소(Code Components)에는 Trust 법적 조항 4.e절에 설명된 Revised BSD License 문구를 포함해야 하며, 이 코드 구성 요소는 Revised BSD License에 설명된 대로 보증 없이 제공된다.
역주: 원문이 따르는 IETF Trust 법적 조항에 따라 원문의 Copyright Notice를 그대로 두고 그 아래에 한국어 번역을 덧붙였으며, 법적 효력은 원문에 있다.
HTTP(Hypertext Transfer Protocol, [HTTP])를 사용하는 애플리케이션의 성능은 각 HTTP 버전이 기반 전송을 어떻게 사용하는지, 그리고 그 전송이 어떤 조건에서 동작하는지와 관련이 있다.
여러 요청(request)을 동시에 보내면 지연 시간(latency)을 줄이고 애플리케이션 성능을 높일 수 있다. HTTP/1.0은 주어진 TCP [TCP] 연결(connection)에서 한 번에 하나의 요청만 미처리 상태로 둘 수 있었다. HTTP/1.1 [HTTP/1.1]은 요청 파이프라이닝(pipelining)을 추가했지만, 이는 요청 동시성 문제를 일부만 해결했고 여전히 애플리케이션 계층의 HOL 블로킹(head-of-line blocking)을 겪는다. 그래서 HTTP/1.0과 HTTP/1.1 클라이언트(client)는 동시 요청을 보내기 위해 서버(server)와 여러 연결을 맺는다.
게다가 HTTP 필드(field)는 반복적이고 장황한 경우가 많아, 불필요한 네트워크 트래픽을 일으킬 뿐 아니라 초기 TCP 혼잡 윈도우(congestion window)를 빨리 채운다. 그 결과 새 TCP 연결에서 여러 요청을 보낼 때 지연 시간이 지나치게 길어질 수 있다.
HTTP/2는 HTTP 의미론(semantics)을 기반 연결에 최적화하여 대응시키는 방법을 정의해 이 문제들을 해결한다. 구체적으로, 같은 연결에서 메시지(message)의 인터리빙(interleaving)을 허용하고 HTTP 필드에 효율적인 코딩을 사용한다. 또한 요청의 우선순위 지정(prioritization)을 허용해 더 중요한 요청이 더 빨리 완료되게 함으로써 성능을 더 높인다.
그 결과로 나온 프로토콜은 HTTP/1.x보다 적은 TCP 연결을 쓸 수 있으므로 네트워크에 더 친화적이다. 이는 다른 흐름과의 경쟁이 줄고 연결이 더 오래 유지된다는 뜻이며, 이는 다시 사용 가능한 네트워크 용량을 더 잘 활용하게 한다. 그러나 TCP의 HOL 블로킹은 이 프로토콜이 해결하지 않는다는 점에 유의하라.
마지막으로, HTTP/2는 이진 메시지 프레이밍(binary message framing)을 사용해 메시지를 더 효율적으로 처리할 수 있게 한다.
이 문서는 RFC 7540과 RFC 8740을 대체한다. 주목할 만한 변경 사항은 부록 B에 나열되어 있다.
HTTP/2는 HTTP 의미론에 최적화된 전송을 제공한다. HTTP/2는 HTTP의 모든 핵심 기능을 지원하면서 HTTP/1.1보다 효율적인 것을 목표로 한다.
HTTP/2는 TCP 연결([TCP]) 위에서 동작하는 연결 지향(connection-oriented) 애플리케이션 계층 프로토콜이다. 클라이언트는 TCP 연결을 시작하는 쪽이다.
HTTP/2의 기본 프로토콜 단위는 프레임(frame, 4.1절)이다. 프레임 유형(frame type)마다 목적이 다르다. 예를 들어 HEADERS와 DATA 프레임은 HTTP 요청과 응답(response)의 기반이 되고(8.1절), SETTINGS, WINDOW_UPDATE, PUSH_PROMISE 같은 다른 프레임 유형은 HTTP/2의 다른 기능을 지원하는 데 쓰인다.
요청의 다중화(multiplexing)는 각 HTTP 요청/응답 교환을 그 교환만의 스트림(stream, 5장)에 연관시켜 이룬다. 스트림은 대체로 서로 독립적이므로, 막히거나 멈춘 요청이나 응답이 다른 스트림의 진행을 막지 않는다.
다중화를 효과적으로 쓰는 것은 흐름 제어(flow control)와 우선순위 지정에 달려 있다. 흐름 제어(5.2절)는 전송하는 데이터를 수신자(receiver)가 처리할 수 있는 만큼으로 제한하여, 다중화된 스트림을 효율적으로 쓸 수 있게 한다. 우선순위 지정(5.3절)은 제한된 자원이 가장 효과적으로 쓰이게 한다. 이번 HTTP/2 개정판은 [RFC7540]의 우선순위 신호 체계를 사용 중단한다.
연결에서 쓰이는 HTTP 필드에는 중복된 데이터가 많을 수 있으므로, 그 필드를 담은 프레임은 압축된다(4.3절). 이는 흔한 경우의 요청 크기에 특히 유리한 영향을 주어, 많은 요청을 패킷 하나로 압축할 수 있게 한다.
마지막으로, HTTP/2는 서버가 클라이언트에 응답을 푸시할 수 있는 새로운 선택적 상호 작용 방식을 추가한다(8.4절). 이는 클라이언트에게 필요하리라 서버가 예상하는 데이터를 서버가 추측에 기반해 클라이언트에 보낼 수 있게 하기 위한 것으로, 네트워크 사용량을 조금 더 쓰는 대신 지연 시간을 줄일 가능성을 얻는다. 서버는 요청을 합성해 PUSH_PROMISE 프레임으로 보냄으로써 이를 수행한다. 그런 다음 서버는 별도의 스트림에서 그 합성 요청에 대한 응답을 보낼 수 있다.
HTTP/2 명세는 네 부분으로 나뉜다.
일부 프레임 계층과 스트림 계층 개념은 HTTP와 분리되어 있지만, 이 명세는 완전히 범용적인 프레임 계층을 정의하지는 않는다. 프레임 계층과 스트림 계층은 HTTP의 필요에 맞춰져 있다.
이 문서의 키워드 “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “NOT RECOMMENDED”, “MAY”, “OPTIONAL”은 여기에 보인 것처럼 모두 대문자로 쓰였을 때, 그리고 그때에만 BCP 14 [RFC2119] [RFC8174]에 설명된 대로 해석한다.
모든 수치 값은 네트워크 바이트 순서(network byte order)를 따른다. 따로 명시하지 않으면 값은 부호 없는(unsigned) 값이다. 리터럴 값은 경우에 따라 십진수나 16진수로 제시한다. 16진수 리터럴은 십진수 리터럴과 구별하기 위해 “0x”를 앞에 붙인다.
이 명세는 RFC 9000 [QUIC]의 1.3절에 설명된 규칙을 사용해 이진 형식을 설명한다. 이 형식은 네트워크 바이트 순서를 사용하며 상위 비트를 하위 비트보다 먼저 나열한다는 점에 유의하라.
다음 용어를 사용한다.
마지막으로, “게이트웨이(gateway)”, “중개자(intermediary)”, “프록시(proxy)”, “터널(tunnel)”이라는 용어는 [HTTP]의 3.7절에 정의되어 있다. 중개자는 때에 따라 클라이언트로도, 서버로도 동작한다.
메시지 본문(message body)에 적용되는 용어 “콘텐츠(content)”는 [HTTP]의 6.4절에 정의되어 있다.
HTTP 요청을 생성하는 구현은 서버가 HTTP/2를 지원하는지 알아낼 필요가 있다.
HTTP/2는 [HTTP]의 4.2절에 정의된 “http”와 “https” URI 스킴(URI scheme)을 HTTP/1.1 [HTTP/1.1]과 같은 기본 포트 번호로 사용한다. 이 URI에는 업스트림(upstream) 서버(클라이언트가 연결을 맺으려는 바로 이웃한 피어)가 어떤 HTTP 버전을 지원하는지에 대한 표시가 전혀 없다.
HTTP/2 지원 여부를 판단하는 방법은 “http” URI와 “https” URI에서 다르다. “https” URI에 대해 알아내는 방법은 3.2절에 설명되어 있다. “http” URI에 대한 HTTP/2 지원은 대역 외(out-of-band) 수단으로만 알아낼 수 있으며, 3.3절에 설명된 대로 그 지원에 대한 사전 지식(prior knowledge)이 필요하다.
이 문서에 정의된 프로토콜에는 식별자가 두 개 있다. 둘 중 어느 것을 바탕으로 연결을 만들든, 이 문서에 설명된 전송, 프레이밍, 메시지 의미론을 사용한다는 뜻이다.
문자열 “h2”는 HTTP/2가 TLS(Transport Layer Security)를 사용하는 프로토콜을 식별한다. 9.2절을 참고하라. 이 식별자는 TLS ALPN(Application-Layer Protocol Negotiation) 확장 [TLS-ALPN] 필드와, TLS 위의 HTTP/2를 식별하는 모든 곳에서 쓰인다.
“h2” 문자열은 ALPN 프로토콜 식별자로 직렬화될 때 2옥텟 시퀀스 0x68, 0x32가 된다.
“h2c” 문자열은 예전에 HTTP 업그레이드 메커니즘의 Upgrade 헤더 필드([HTTP]의 7.8절)에서 쓰는 토큰(token)으로 사용되었다. 이 용법은 널리 배포된 적이 없으며 이 문서에서 사용 중단한다. “h2c”로 업그레이드할 때 함께 쓰이던 HTTP2-Settings 헤더 필드도 마찬가지다.
“https” URI에 요청하는 클라이언트는 ALPN 확장 [TLS-ALPN]과 함께 TLS [TLS13]를 사용한다.
TLS 위의 HTTP/2는 “h2” 프로토콜 식별자를 사용한다. 클라이언트는 “h2c” 프로토콜 식별자를 보내서는 안 되고, 서버는 이를 선택해서는 안 된다(MUST NOT). “h2c” 프로토콜 식별자는 TLS를 쓰지 않는 프로토콜을 나타낸다.
TLS 협상이 완료되면 클라이언트와 서버는 모두 연결 프리페이스(connection preface, 3.4절)를 반드시 보내야 한다(MUST).
클라이언트는 다른 수단으로 특정 서버가 HTTP/2를 지원한다는 것을 알 수 있다. 예를 들어 클라이언트는 서버가 HTTP/2를 지원한다는 정보를 설정으로 가지고 있을 수도 있다.
서버가 HTTP/2를 지원한다는 것을 아는 클라이언트는 TCP 연결을 맺고 연결 프리페이스(3.4절)를 보낸 뒤 HTTP/2 프레임을 보낼 수 있다. 서버는 연결 프리페이스가 있는지로 이런 연결을 식별할 수 있다. 이는 평문(cleartext) TCP 위에서 HTTP/2 연결을 맺는 경우에만 해당한다. TLS 위의 HTTP/2 연결은 TLS의 프로토콜 협상 [TLS-ALPN]을 반드시 사용해야 한다(MUST).
마찬가지로 서버는 연결 프리페이스(3.4절)를 반드시 보내야 한다(MUST).
추가 정보가 없다면, 이전에 HTTP/2를 지원했다는 사실은 그 서버가 앞으로의 연결에서도 HTTP/2를 지원하리라는 강한 신호가 되지 못한다. 예를 들어 서버 설정이 바뀌거나, 클러스터로 구성된 서버의 인스턴스마다 설정이 다르거나, 네트워크 조건이 바뀔 수 있다.
HTTP/2에서 각 엔드포인트는 사용 중인 프로토콜을 최종 확인하고 HTTP/2 연결의 초기 설정을 정하기 위해 연결 프리페이스를 보낼 의무가 있다. 클라이언트와 서버는 서로 다른 연결 프리페이스를 보낸다.
클라이언트 연결 프리페이스는 24옥텟 시퀀스로 시작하며, 이를 16진수로 나타내면 다음과 같다.
0x505249202a20485454502f322e300d0a0d0a534d0d0a0d0a
즉, 연결 프리페이스는 문자열 PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n으로 시작한다. 이 시퀀스 뒤에는 반드시 SETTINGS 프레임(6.5절)이 와야 하며(MUST), 이 프레임은 비어 있을 수 있다(MAY). 클라이언트는 클라이언트 연결 프리페이스를 연결의 첫 애플리케이션 데이터 옥텟으로 보낸다.
참고: 클라이언트 연결 프리페이스는 HTTP/1.1이나 HTTP/1.0 서버와 중개자 상당수가 이후 프레임을 처리하려 하지 않도록 고른 것이다. 이것이 [TALKING]에서 제기된 우려를 해결하지는 않는다는 점에 유의하라.
서버 연결 프리페이스는 비어 있을 수도 있는 SETTINGS 프레임(6.5절)으로 이루어지며, 이 프레임은 반드시 서버가 HTTP/2 연결에서 보내는 첫 프레임이어야 한다(MUST).
연결 프리페이스의 일부로 피어에게서 받은 SETTINGS 프레임은 연결 프리페이스를 보낸 뒤 반드시 확인 응답(acknowledge, 6.5.3절 참고)해야 한다(MUST).
불필요한 지연 시간을 피하기 위해, 클라이언트는 클라이언트 연결 프리페이스를 보낸 직후 서버 연결 프리페이스를 받기를 기다리지 않고 서버에 프레임을 더 보내도 된다. 그러나 서버 연결 프리페이스의 SETTINGS 프레임에는 클라이언트가 서버와 통신할 것으로 기대되는 방식을 필연적으로 바꾸는 설정이 들어 있을 수도 있다는 점에 유의하라. SETTINGS 프레임을 받으면 클라이언트는 정해진 설정을 모두 따를 것으로 기대된다. 일부 구성에서는 클라이언트가 프레임을 더 보내기 전에 서버가 SETTINGS를 전송할 수 있으므로, 이 문제를 피할 기회가 생긴다.
클라이언트와 서버는 유효하지 않은 연결 프리페이스를 PROTOCOL_ERROR 유형의 연결 오류(5.4.1절)로 반드시 처리해야 한다(MUST). 이 경우 GOAWAY 프레임(6.8절)은 생략할 수 있다(MAY). 유효하지 않은 프리페이스는 피어가 HTTP/2를 쓰지 않는다는 뜻이기 때문이다.
HTTP/2 연결이 맺어지면 엔드포인트는 프레임을 주고받기 시작할 수 있다.
모든 프레임은 고정된 9옥텟 헤더로 시작하고, 그 뒤에 가변 길이 프레임 페이로드(frame payload)가 온다.
HTTP Frame {
Length (24),
Type (8),
Flags (8),
Reserved (1),
Stream Identifier (31),
Frame Payload (..),
}
그림 1: 프레임 구성
프레임 헤더(frame header)의 필드는 다음과 같이 정의된다.
Length: 프레임 페이로드의 길이를 옥텟 단위의 부호 없는 24비트 정수로 나타낸 값. 수신자가 SETTINGS_MAX_FRAME_SIZE에 더 큰 값을 설정하지 않았다면 2^14(16,384)보다 큰 값을 보내서는 안 된다(MUST NOT).
프레임 헤더의 9옥텟은 이 값에 포함되지 않는다.
Type: 프레임의 8비트 유형. 프레임 유형은 프레임의 형식과 의미를 정한다. 이 문서에 정의된 프레임은 6장에 나열되어 있다. 구현은 알 수 없는 유형의 프레임을 반드시 무시하고 버려야 한다(MUST).
Flags: 프레임 유형에 따른 불리언(boolean) 플래그를 위해 예약된 8비트 필드.
플래그에는 지정된 프레임 유형에 따른 의미가 할당된다. 사용하지 않는 플래그는 특정 프레임 유형에 대해 정의된 의미가 없는 플래그다. 사용하지 않는 플래그는 받을 때 반드시 무시해야 하며(MUST), 보낼 때는 반드시 설정하지 않은 상태(0x00)로 두어야 한다(MUST).
Reserved: 예약된 1비트 필드. 이 비트의 의미는 정의되지 않았으며, 보낼 때는 반드시 설정하지 않은 상태(0x00)로 두어야 하고(MUST) 받을 때는 반드시 무시해야 한다(MUST).
Stream Identifier: 스트림 식별자(stream identifier, 5.1.1절 참고)를 부호 없는 31비트 정수로 나타낸 값. 값 0x00은 개별 스트림이 아니라 연결 전체와 연관된 프레임을 위해 예약되어 있다.
프레임 페이로드의 구조와 내용은 전적으로 프레임 유형에 따라 달라진다.
프레임 페이로드의 크기는 수신자가 SETTINGS_MAX_FRAME_SIZE 설정으로 알린 최대 크기로 제한된다. 이 설정은 2^14(16,384)부터 2^24-1(16,777,215)옥텟까지(양 끝 포함) 어떤 값이든 가질 수 있다.
모든 구현은 길이가 2^14옥텟까지인 프레임에 9옥텟 프레임 헤더(4.1절)를 더한 것까지 반드시 받아서 최소한으로 처리할 수 있어야 한다(MUST). 프레임 크기를 말할 때 프레임 헤더의 크기는 포함하지 않는다.
참고:
PING(6.7절) 같은 일부 프레임 유형은 허용되는 프레임 페이로드 데이터의 양에 추가 제한을 둔다.
프레임이 SETTINGS_MAX_FRAME_SIZE에 정의된 크기를 넘거나, 그 프레임 유형에 정의된 제한을 넘거나, 필수 프레임 데이터를 담기에 너무 작으면, 엔드포인트는 FRAME_SIZE_ERROR 오류 코드를 반드시 보내야 한다(MUST). 연결 전체의 상태를 바꿀 수 있는 프레임의 프레임 크기 오류는 반드시 연결 오류(5.4.1절)로 처리해야 한다(MUST). 여기에는 필드 블록(field block, 4.3절)을 담은 모든 프레임(즉, HEADERS, PUSH_PROMISE, CONTINUATION), SETTINGS 프레임, 그리고 스트림 식별자가 0인 모든 프레임이 포함된다.
엔드포인트가 프레임에서 쓸 수 있는 공간을 모두 써야 할 의무는 없다. 허용되는 최대 크기보다 작은 프레임을 쓰면 응답성을 높일 수 있다. 큰 프레임을 보내면 시간에 민감한 프레임(RST_STREAM, WINDOW_UPDATE, PRIORITY 등)을 보내는 것이 늦어질 수 있으며, 이런 프레임이 큰 프레임의 전송 때문에 막히면 성능에 영향을 줄 수 있다.
필드 섹션 압축(field section compression)은 필드 줄(field line, [HTTP]의 5.2절)의 집합을 압축해 필드 블록을 만드는 과정이다. 필드 섹션 압축 해제는 필드 블록을 디코딩해 필드 줄의 집합으로 되돌리는 과정이다. HTTP/2 필드 섹션 압축과 압축 해제의 세부 사항은 [COMPRESSION]에 정의되어 있으며, [COMPRESSION]은 역사적인 이유로 이 과정을 헤더 압축(header compression)과 압축 해제라고 부른다.
각 필드 블록은 하나의 필드 섹션(field section)에 속한 압축된 필드 줄을 모두 담는다. 헤더 섹션(header section)에는 메시지와 연관된 제어 데이터(control data)도 의사 헤더 필드(pseudo-header field, 8.3절)의 형태로 들어가며, 의사 헤더 필드는 필드 줄과 같은 형식을 사용한다.
참고: RFC 7540 [RFC7540]은 더 일반적인 용어인 “필드 블록” 대신 “헤더 블록(header block)”이라는 용어를 사용했다.
필드 블록은 요청, 응답, 약속된 요청, 푸시된 응답(8.4절 참고)의 제어 데이터와 헤더 섹션을 담는다. 중간 응답(interim response)과 PUSH_PROMISE(6.6절) 프레임에 담긴 요청을 제외한 이 모든 메시지는 트레일러 섹션(trailer section)을 담은 필드 블록을 선택적으로 포함할 수 있다.
필드 섹션은 필드 줄의 모음이다. 필드 블록 안의 각 필드 줄은 값 하나를 담는다. 직렬화된 필드 블록은 필드 블록 조각(field block fragment)이라고 부르는 하나 이상의 옥텟 시퀀스로 나뉜다. 첫 필드 블록 조각은 HEADERS(6.2절)나 PUSH_PROMISE(6.6절)의 프레임 페이로드에 담겨 전송되며, 이 둘 뒤에는 이후 필드 블록 조각을 담는 CONTINUATION(6.10절) 프레임이 이어질 수 있다.
Cookie 헤더 필드 [COOKIE]는 HTTP 매핑에서 특별하게 다룬다(8.2.3절 참고).
받는 엔드포인트는 조각들을 이어 붙여(concatenation) 필드 블록을 다시 조립한 뒤, 그 블록의 압축을 풀어 필드 섹션을 재구성한다.
완전한 필드 섹션은 다음 중 하나로 이루어진다.
END_HEADERS 플래그가 설정된 HEADERS 또는 PUSH_PROMISE 프레임 하나, 또는END_HEADERS 플래그가 설정되지 않은 HEADERS 또는 PUSH_PROMISE 프레임과 하나 이상의 CONTINUATION 프레임(마지막 CONTINUATION 프레임에는 END_HEADERS 플래그가 설정됨)각 필드 블록은 하나의 단위로 따로 처리된다. 필드 블록은 다른 유형이나 다른 스트림의 프레임이 끼어들지 않은 연속된 프레임 시퀀스로 반드시 전송해야 한다(MUST). HEADERS 또는 CONTINUATION 프레임 시퀀스의 마지막 프레임에는 END_HEADERS 플래그가 설정된다. PUSH_PROMISE 또는 CONTINUATION 프레임 시퀀스의 마지막 프레임에는 END_HEADERS 플래그가 설정된다. 이렇게 하면 필드 블록을 논리적으로 프레임 하나와 같은 것으로 다룰 수 있다.
필드 블록 조각은 HEADERS, PUSH_PROMISE, CONTINUATION 프레임의 프레임 페이로드로만 보낼 수 있다. 이 프레임들이 수신자가 유지하는 압축 컨텍스트를 바꿀 수 있는 데이터를 담기 때문이다. HEADERS, PUSH_PROMISE, CONTINUATION 프레임을 받는 엔드포인트는 그 프레임을 버릴 것이더라도 필드 블록을 다시 조립하고 압축을 풀 필요가 있다. 수신자는 필드 블록의 압축을 풀지 않으면 COMPRESSION_ERROR 유형의 연결 오류(5.4.1절)로 연결을 반드시 종료해야 한다(MUST).
필드 블록의 디코딩 오류는 반드시 COMPRESSION_ERROR 유형의 연결 오류(5.4.1절)로 처리해야 한다(MUST).
필드 압축(field compression)은 상태를 가진다(stateful). 각 엔드포인트에는 연결의 모든 필드 블록을 인코딩하고 디코딩하는 데 쓰는 HPACK 인코더 컨텍스트와 HPACK 디코더 컨텍스트가 있다. [COMPRESSION]의 4장은 각 컨텍스트의 주된 상태인 동적 테이블(dynamic table)을 정의한다.
동적 테이블에는 HPACK 디코더가 정하는 최대 크기가 있다. 엔드포인트는 자신의 HPACK 디코더 컨텍스트가 고른 크기를 SETTINGS_HEADER_TABLE_SIZE 설정으로 알린다. 6.5.2절을 참고하라. 연결이 맺어지면 양쪽 엔드포인트의 HPACK 디코더와 인코더의 동적 테이블 크기는 SETTINGS_HEADER_TABLE_SIZE 설정의 초깃값인 4,096바이트로 시작한다.
SETTINGS_HEADER_TABLE_SIZE로 설정한 최댓값의 변경은 엔드포인트가 설정을 확인 응답할 때(6.5.3절) 효력이 생긴다. 그 엔드포인트의 HPACK 인코더는 디코더가 설정한 최댓값 이하의 어떤 크기로든 동적 테이블을 설정할 수 있다. HPACK 인코더는 동적 테이블 크기 갱신(Dynamic Table Size Update) 명령([COMPRESSION]의 6.3절)으로 동적 테이블의 크기를 선언한다.
엔드포인트가 최댓값을 동적 테이블의 현재 크기보다 작게 줄이는 SETTINGS_HEADER_TABLE_SIZE 변경을 확인 응답하면, 그 엔드포인트의 HPACK 인코더는 다음 필드 블록을, 동적 테이블을 줄어든 최댓값 이하의 크기로 설정하는 동적 테이블 크기 갱신 명령으로 반드시 시작해야 한다(MUST). [COMPRESSION]의 4.2절을 참고하라. 엔드포인트는 최대 동적 테이블 크기 축소에 대한 피어의 확인 응답 뒤에 오는 필드 블록이 적합한 동적 테이블 크기 갱신 명령으로 시작하지 않으면, 그 필드 블록을 COMPRESSION_ERROR 유형의 연결 오류(5.4.1절)로 반드시 처리해야 한다(MUST).
구현자는
SETTINGS_HEADER_TABLE_SIZE값을 줄이는 것이 널리 상호 운용되지 않는다는 점에 유의하기 바란다. 연결 프리페이스를 사용해 초깃값 4,096보다 작게 줄이는 것은 그나마 더 잘 지원되지만, 일부 구현에서는 실패할 수도 있다.
“스트림(stream)”은 HTTP/2 연결 안에서 클라이언트와 서버 사이에 주고받는 독립적인 양방향 프레임 시퀀스다. 스트림에는 다음과 같은 중요한 특성이 있다.
HEADERS와 DATA 프레임의 순서는 의미상 중요하다.스트림의 생애 주기는 그림 2와 같다.
+--------+
send PP | | recv PP
,--------+ idle +--------.
/ | | \
v +--------+ v
+----------+ | +----------+
| | | send H / | |
,------+ reserved | | recv H | reserved +------.
| | (local) | | | (remote) | |
| +---+------+ v +------+---+ |
| | +--------+ | |
| | recv ES | | send ES | |
| send H | ,-------+ open +-------. | recv H |
| | / | | \ | |
| v v +---+----+ v v |
| +----------+ | +----------+ |
| | half- | | | half- | |
| | closed | | send R / | closed | |
| | (remote) | | recv R | (local) | |
| +----+-----+ | +-----+----+ |
| | | | |
| | send ES / | recv ES / | |
| | send R / v send R / | |
| | recv R +--------+ recv R | |
| send R / `----------->| |<-----------' send R / |
| recv R | closed | recv R |
`----------------------->| |<-----------------------'
+--------+
그림 2: 스트림 상태
HEADERS 프레임(뒤따르는 CONTINUATION 프레임 포함)END_STREAM 플래그RST_STREAM 프레임PUSH_PROMISE 프레임(뒤따르는 CONTINUATION 프레임 포함). 상태 전이는 약속된 스트림(promised stream)에 대한 것이다.이 다이어그램은 스트림 상태(stream state) 전이와, 그 전이에 영향을 주는 프레임과 플래그만 보여 준다는 점에 유의하라. 이 점에서 CONTINUATION 프레임은 상태 전이를 일으키지 않는다. CONTINUATION 프레임은 사실상 그 앞의 HEADERS나 PUSH_PROMISE의 일부다. 상태 전이를 따질 때 END_STREAM 플래그는 그 플래그를 담은 프레임과 별개의 이벤트로 처리된다. 그래서 END_STREAM 플래그가 설정된 HEADERS 프레임은 상태 전이를 두 번 일으킬 수 있다.
두 엔드포인트는 각자 스트림 상태에 대한 주관적인 관점을 가지며, 프레임이 전송 중일 때는 그 관점이 서로 다를 수 있다. 엔드포인트는 스트림 생성을 서로 조율하지 않는다. 스트림은 어느 엔드포인트든 일방적으로 생성한다. 상태가 어긋나서 생기는 나쁜 결과는 RST_STREAM을 보낸 뒤의 closed 상태로 한정되며, 이때는 닫은 뒤에도 한동안 프레임을 받을 수도 있다.
스트림 상태는 다음과 같다.
idle: 모든 스트림은 idle 상태(아직 쓰이지 않은 상태)에서 시작한다.
이 상태에서는 다음 전이가 유효하다.
HEADERS 프레임을 보내거나 서버로서 HEADERS 프레임을 받으면 스트림은 open 상태가 된다. 스트림 식별자는 5.1.1절에 설명된 대로 선택된다. 같은 HEADERS 프레임이 스트림을 곧바로 half-closed 상태(한쪽 방향만 닫힌 상태)로 만들 수도 있다.PUSH_PROMISE 프레임을 보내면, 거기서 식별한 idle 스트림을 나중에 쓰도록 예약한다. 예약된 스트림의 상태는 reserved (로컬)로 전이한다. PUSH_PROMISE 프레임은 서버만 보낼 수 있다.PUSH_PROMISE 프레임을 받으면, 거기서 식별한 idle 스트림을 나중에 쓰도록 예약한다. 예약된 스트림의 상태는 reserved (원격)으로 전이한다. PUSH_PROMISE 프레임은 클라이언트만 받을 수 있다.PUSH_PROMISE 프레임은 idle 스트림에서 보내는 것이 아니라, Promised Stream ID 필드에서 새로 예약한 스트림을 가리킨다는 점에 유의하라.idle 스트림은 곧바로 closed 상태로 전이한다. 이 전이는 다이어그램에 나타나 있지 않다는 점에 유의하라.이 상태의 스트림에서 HEADERS나 PRIORITY가 아닌 프레임을 받으면 반드시 PROTOCOL_ERROR 유형의 연결 오류(5.4.1절)로 처리해야 한다(MUST). 이 스트림을 5.1.1절에 설명된 대로 서버가 시작했다면, HEADERS 프레임을 받는 것도 반드시 PROTOCOL_ERROR 유형의 연결 오류(5.4.1절)로 처리해야 한다(MUST).
reserved (로컬): reserved (로컬) 상태의 스트림은 PUSH_PROMISE 프레임을 보내 약속한 스트림이다. PUSH_PROMISE 프레임은 idle 스트림을 원격 피어가 시작한 열린 스트림과 연관시켜 예약한다(8.4절 참고).
이 상태에서는 다음 전이만 가능하다.
HEADERS 프레임을 보낼 수 있다. 그러면 스트림이 half-closed (원격) 상태로 열린다.RST_STREAM 프레임을 보내 스트림을 closed 상태로 만들 수 있다. 이는 스트림 예약을 해제한다.이 상태에서 엔드포인트는 HEADERS, RST_STREAM, PRIORITY가 아닌 유형의 프레임을 보내서는 안 된다(MUST NOT).
이 상태에서 PRIORITY나 WINDOW_UPDATE 프레임을 받을 수 있다(MAY). 이 상태의 스트림에서 RST_STREAM, PRIORITY, WINDOW_UPDATE가 아닌 유형의 프레임을 받으면 반드시 PROTOCOL_ERROR 유형의 연결 오류(5.4.1절)로 처리해야 한다(MUST).
reserved (원격): reserved (원격) 상태의 스트림은 원격 피어가 예약한 스트림이다.
이 상태에서는 다음 전이만 가능하다.
HEADERS 프레임을 받으면 스트림이 half-closed (로컬)로 전이한다.RST_STREAM 프레임을 보내 스트림을 closed 상태로 만들 수 있다. 이는 스트림 예약을 해제한다.이 상태에서 엔드포인트는 RST_STREAM, WINDOW_UPDATE, PRIORITY가 아닌 유형의 프레임을 보내서는 안 된다(MUST NOT).
이 상태의 스트림에서 HEADERS, RST_STREAM, PRIORITY가 아닌 유형의 프레임을 받으면 반드시 PROTOCOL_ERROR 유형의 연결 오류(5.4.1절)로 처리해야 한다(MUST).
open: open 상태의 스트림은 두 피어 모두 어떤 유형의 프레임이든 보내는 데 쓸 수 있다. 이 상태에서 보내는 피어는 상대가 알린 스트림 수준의 흐름 제어 한도(5.2절)를 지킨다.
이 상태에서 어느 엔드포인트든 END_STREAM 플래그를 설정한 프레임을 보낼 수 있으며, 그러면 스트림은 half-closed 상태 중 하나로 전이한다. END_STREAM 플래그를 보내는 엔드포인트에서는 스트림 상태가 half-closed (로컬)이 되고, END_STREAM 플래그를 받는 엔드포인트에서는 스트림 상태가 half-closed (원격)이 된다.
이 상태에서 어느 엔드포인트든 RST_STREAM 프레임을 보낼 수 있으며, 그러면 스트림은 곧바로 closed로 전이한다.
half-closed (로컬): half-closed (로컬) 상태의 스트림은 WINDOW_UPDATE, PRIORITY, RST_STREAM이 아닌 프레임을 보내는 데 쓸 수 없다.
END_STREAM 플래그가 설정된 프레임을 받거나 어느 피어든 RST_STREAM 프레임을 보내면, 스트림은 이 상태에서 closed로 전이한다.
이 상태에서 엔드포인트는 어떤 유형의 프레임이든 받을 수 있다. 흐름 제어 대상 프레임을 계속 받으려면 WINDOW_UPDATE 프레임으로 흐름 제어 크레딧(credit)을 제공하는 것이 필요하다. 이 상태에서 수신자는 WINDOW_UPDATE 프레임을 무시할 수 있는데, 이 프레임은 END_STREAM 플래그가 설정된 프레임을 보낸 뒤 잠깐 동안 도착할 수도 있다.
이 상태에서 PRIORITY 프레임을 받을 수 있다.
half-closed (원격): half-closed (원격) 상태의 스트림은 피어가 더 이상 프레임을 보내는 데 쓰지 않는 스트림이다. 이 상태에서 엔드포인트는 더 이상 수신자 흐름 제어 윈도우(flow-control window)를 유지할 의무가 없다.
이 상태의 스트림에서 엔드포인트가 WINDOW_UPDATE, PRIORITY, RST_STREAM이 아닌 프레임을 더 받으면, 반드시 STREAM_CLOSED 유형의 스트림 오류(5.4.2절)로 응답해야 한다(MUST).
half-closed (원격) 상태의 스트림은 엔드포인트가 어떤 유형의 프레임이든 보내는 데 쓸 수 있다. 이 상태에서 엔드포인트는 상대가 알린 스트림 수준의 흐름 제어 한도(5.2절)를 계속 지킨다.
END_STREAM 플래그가 설정된 프레임을 보내거나 어느 피어든 RST_STREAM 프레임을 보내면, 스트림은 이 상태에서 closed로 전이할 수 있다.
closed: closed 상태는 마지막 상태다.
엔드포인트가 END_STREAM 플래그가 설정된 프레임을 보내고 받기도 한 뒤에 스트림은 closed 상태가 된다. 엔드포인트가 RST_STREAM 프레임을 보내거나 받은 뒤에도 스트림은 closed 상태가 된다.
엔드포인트는 닫힌 스트림에서 PRIORITY가 아닌 프레임을 보내서는 안 된다(MUST NOT). 엔드포인트는 아래에 적은 경우를 제외하고, 닫힌 스트림에서 그 밖의 유형의 프레임을 받는 것을 STREAM_CLOSED 유형의 연결 오류(5.4.1절)로 처리할 수 있다(MAY).
END_STREAM 플래그가 설정된 프레임이나 RST_STREAM 프레임을 보낸 엔드포인트는, 피어가 스트림을 닫는 프레임을 받아 처리하기 전까지 피어로부터 WINDOW_UPDATE나 RST_STREAM 프레임을 받을 수도 있다.
open 또는 half-closed (로컬) 상태의 스트림에서 RST_STREAM 프레임을 보낸 엔드포인트는 어떤 유형의 프레임이든 받을 수도 있다. 피어가 RST_STREAM 프레임을 처리하기 전에 이 프레임들을 보냈거나 보내려고 대기열에 넣었을 수 있기 때문이다. 엔드포인트는 이 상태에서 받은 모든 프레임을 반드시 최소한으로 처리한 뒤 버려야 한다(MUST). 이는 HEADERS와 PUSH_PROMISE 프레임에 대해 헤더 압축 상태를 갱신하는 것을 뜻한다. PUSH_PROMISE 프레임을 받으면, 그 프레임을 닫힌 스트림에서 받은 경우에도 약속된 스트림이 reserved (원격) 상태가 된다. 또한 DATA 프레임의 콘텐츠는 연결의 흐름 제어 윈도우에 포함해 센다.
엔드포인트는 closed 상태인 모든 스트림에 대해 이런 최소한의 처리를 할 수 있다. 엔드포인트는 스트림을 closed 상태로 만든 프레임을 피어가 받았음을 다른 신호로 알아낸 뒤, PRIORITY가 아닌 프레임을 받는 것을 PROTOCOL_ERROR 유형의 연결 오류(5.4.1절)로 처리할 수 있다(MAY). 엔드포인트는 피어가 닫는 신호를 받았음을 나타내는 프레임을 이 판단에 쓸 수 있다. 엔드포인트는 이 목적으로 타이머를 사용하지 않아야 한다(SHOULD NOT). 예를 들어 스트림을 닫은 뒤 SETTINGS 프레임을 보낸 엔드포인트는, 그 설정에 대한 확인 응답을 받은 뒤라면 그 스트림에서 받은 DATA 프레임을 안전하게 오류로 처리할 수 있다. 쓸 수 있는 다른 것으로는 PING 프레임, 스트림을 닫은 뒤에 생성된 스트림에서 데이터를 받는 것, 스트림을 닫은 뒤에 생성된 요청에 대한 응답 등이 있다.
더 구체적인 규칙이 없다면, 구현은 상태 설명에서 명시적으로 허용하지 않은 프레임을 받는 것을 PROTOCOL_ERROR 유형의 연결 오류(5.4.1절)로 처리해야 한다(SHOULD). PRIORITY는 어떤 스트림 상태에서든 보내고 받을 수 있다는 점에 유의하라.
이 절의 규칙은 이 문서에 정의된 프레임에만 적용된다. 의미를 알 수 없는 프레임을 받는 것은 오류로 처리할 수 없다. 그런 프레임을 보내고 받는 조건도 알 수 없기 때문이다. 5.5절을 참고하라.
HTTP 요청/응답 교환에 대한 상태 전이의 예는 8.8절에 있다. 서버 푸시(server push)에 대한 상태 전이의 예는 8.4.1절과 8.4.2절에 있다.
스트림은 부호 없는 31비트 정수로 식별된다. 클라이언트가 시작한 스트림은 반드시 홀수 스트림 식별자를 사용해야 하고(MUST), 서버가 시작한 스트림은 반드시 짝수 스트림 식별자를 사용해야 한다(MUST). 스트림 식별자 0(0x00)은 연결 제어 메시지에 쓰인다. 스트림 식별자 0으로는 새 스트림을 수립할 수 없다.
새로 수립한 스트림의 식별자는 시작한 엔드포인트가 열거나 예약한 모든 스트림보다 반드시 수치상 커야 한다(MUST). 이는 HEADERS 프레임으로 여는 스트림과 PUSH_PROMISE로 예약하는 스트림에 적용된다. 예상하지 못한 스트림 식별자를 받은 엔드포인트는 반드시 PROTOCOL_ERROR 유형의 연결 오류(5.4.1절)로 응답해야 한다(MUST).
HEADERS 프레임은 프레임 헤더의 스트림 식별자가 식별하는, 클라이언트가 시작한 스트림을 idle에서 open으로 전이시킨다. PUSH_PROMISE 프레임은 프레임 페이로드의 Promised Stream ID 필드가 식별하는, 서버가 시작한 스트림을 idle에서 reserved (로컬) 또는 reserved (원격)으로 전이시킨다. 스트림이 idle 상태에서 벗어나 전이하면, 피어가 더 작은 값의 스트림 식별자로 열었을 수 있는 idle 상태의 모든 스트림은 곧바로 closed로 전이한다. 즉, 엔드포인트는 스트림 식별자를 건너뛸 수 있으며, 그 결과 건너뛴 스트림은 곧바로 닫힌다.
스트림 식별자는 재사용할 수 없다. 오래 유지되는 연결에서는 엔드포인트가 쓸 수 있는 스트림 식별자 범위를 모두 소진할 수 있다. 새 스트림 식별자를 수립할 수 없는 클라이언트는 새 스트림을 위해 새 연결을 맺을 수 있다. 새 스트림 식별자를 수립할 수 없는 서버는 클라이언트가 새 스트림을 위해 새 연결을 열 수밖에 없도록 GOAWAY 프레임을 보낼 수 있다.
피어는 SETTINGS 프레임 안의 SETTINGS_MAX_CONCURRENT_STREAMS 매개 변수(6.5.2절 참고)로 동시에 활성화된 스트림의 수를 제한할 수 있다. 최대 동시 스트림 설정은 엔드포인트마다 따로 있으며, 그 설정을 받는 피어에만 적용된다. 즉, 클라이언트는 서버가 시작할 수 있는 최대 동시 스트림 수를 지정하고, 서버는 클라이언트가 시작할 수 있는 최대 동시 스트림 수를 지정한다.
open 상태이거나 두 half-closed 상태 중 하나인 스트림은, 엔드포인트가 열 수 있는 최대 스트림 수에 포함해 센다. 이 세 상태 중 어느 것에 있는 스트림이든 SETTINGS_MAX_CONCURRENT_STREAMS 설정으로 알린 한도에 포함해 센다. 두 reserved 상태 중 하나인 스트림은 스트림 한도에 포함하지 않는다.
엔드포인트는 피어가 설정한 한도를 초과해서는 안 된다(MUST NOT). 자신이 알린 동시 스트림 한도를 넘게 만드는 HEADERS 프레임을 받은 엔드포인트는 이를 반드시 PROTOCOL_ERROR 또는 REFUSED_STREAM 유형의 스트림 오류(5.4.2절)로 처리해야 한다(MUST). 어떤 오류 코드를 고르느냐에 따라 엔드포인트가 자동 재시도를 허용할지가 정해진다(자세한 내용은 8.7절 참고).
SETTINGS_MAX_CONCURRENT_STREAMS 값을 현재 열린 스트림 수보다 작게 줄이려는 엔드포인트는, 새 값을 넘는 스트림을 닫거나 스트림이 완료되도록 둘 수 있다.
다중화를 위해 스트림을 쓰면 TCP 연결 사용을 두고 경합이 생기고, 그 결과 스트림이 막힌다. 흐름 제어 체계는 같은 연결의 스트림들이 서로를 해롭게 방해하지 않도록 보장한다. 흐름 제어는 개별 스트림과 연결 전체 모두에 쓰인다.
HTTP/2는 WINDOW_UPDATE 프레임(6.9절)을 사용해 흐름 제어를 제공한다.
HTTP/2 스트림 흐름 제어는 프로토콜을 바꾸지 않고도 다양한 흐름 제어 알고리즘을 쓸 수 있게 하는 것을 목표로 한다. HTTP/2의 흐름 제어에는 다음 특성이 있다.
흐름 제어는 연결마다 따로 적용된다. HTTP/2 흐름 제어는 종단 간(end-to-end) 경로 전체가 아니라 홉(hop) 하나의 두 엔드포인트 사이에서 동작한다.
흐름 제어는 WINDOW_UPDATE 프레임에 기반한다. 수신자는 스트림 하나와 연결 전체에서 각각 몇 옥텟을 받을 준비가 되어 있는지 알린다. 이는 크레딧 기반 체계다.
흐름 제어는 방향이 있으며, 전체적인 제어는 수신자가 한다. 수신자는 스트림마다, 그리고 연결 전체에 대해 원하는 어떤 윈도우 크기든 설정할 수 있다(MAY). 송신자는 수신자가 부과한 흐름 제어 한도를 반드시 지켜야 한다(MUST). 클라이언트, 서버, 중개자는 모두 수신자로서 자신의 흐름 제어 윈도우를 각자 알리고, 보낼 때는 피어가 설정한 흐름 제어 한도를 따른다.
흐름 제어 윈도우의 초깃값은 새 스트림과 연결 전체 모두 65,535옥텟이다.
프레임 유형에 따라 흐름 제어 적용 여부가 정해진다. 이 문서에 명시된 프레임 중 흐름 제어 대상은 DATA 프레임뿐이다. 다른 프레임 유형은 모두 상대에게 알린 흐름 제어 윈도우의 공간을 소비하지 않는다. 이렇게 하면 중요한 제어 프레임이 흐름 제어 때문에 막히지 않는다.
엔드포인트는 자신의 흐름 제어를 끄기로 선택할 수 있지만, 피어의 흐름 제어 신호를 무시할 수는 없다.
HTTP/2는 WINDOW_UPDATE 프레임(6.9절)의 형식과 의미만 정의한다. 이 문서는 수신자가 이 프레임을 언제 보낼지와 보낼 값을 어떻게 정하는지 규정하지 않으며, 송신자가 패킷을 보내는 방식을 어떻게 고르는지도 명시하지 않는다. 구현은 자신의 필요에 맞는 어떤 알고리즘이든 고를 수 있다.
구현은 요청과 응답을 보내는 우선순위를 정하고, 요청의 HOL 블로킹을 어떻게 피할지 고르고, 새 스트림 생성을 관리할 책임도 진다. 이에 대한 알고리즘 선택은 어떤 흐름 제어 알고리즘과도 상호 작용할 수 있다.
흐름 제어는 자원 제약 아래서 동작하는 엔드포인트를 보호하기 위해 정의되었다. 예를 들어 프록시는 많은 연결 사이에서 메모리를 나눠 쓸 필요가 있고, 업스트림 연결은 느린데 다운스트림(downstream) 연결은 빠를 수도 있다. 흐름 제어는 수신자가 한 스트림의 데이터는 처리할 수 없지만 같은 연결의 다른 스트림은 계속 처리하고 싶은 경우를 다룬다.
이 기능이 필요 없는 배포는 최대 크기(2^31-1)의 흐름 제어 윈도우를 알리고, 데이터를 받을 때마다 WINDOW_UPDATE 프레임을 보내 이 윈도우를 유지할 수 있다. 이렇게 하면 그 수신자의 흐름 제어가 사실상 꺼진다. 반대로 송신자는 언제나 수신자가 알린 흐름 제어 윈도우의 제약을 받는다.
자원(예: 메모리)이 제한된 배포는 피어가 소비할 수 있는 메모리의 양을 제한하기 위해 흐름 제어를 쓸 수 있다. 그러나 대역폭 * 지연(bandwidth * delay product, [RFC7323] 참고)을 모르는 채로 흐름 제어를 켜면 사용 가능한 네트워크 자원을 최적이 아니게 쓰게 될 수 있다는 점에 유의하라.
현재의 대역폭 * 지연을 완전히 알고 있더라도 흐름 제어를 구현하기는 어려울 수 있다. 엔드포인트는 데이터를 읽을 수 있게 되는 즉시 TCP 수신 버퍼에서 HTTP/2 프레임을 반드시 읽고 처리해야 한다(MUST). 제때 읽지 못하면 WINDOW_UPDATE 같은 중요한 프레임을 읽고 처리하지 못해 교착 상태(deadlock)가 생길 수 있다. 프레임을 제때 읽는다고 해서 엔드포인트가 자원 고갈 공격에 노출되지는 않는다. HTTP/2 흐름 제어가 자원 할당을 제한하기 때문이다.
엔드포인트가 피어에게 이 연결에서 피어의 대역폭 * 지연보다 큰 흐름 제어 윈도우 공간을 언제나 쓸 수 있게 보장하지 못하면, 그 엔드포인트의 수신 처리량은 HTTP/2 흐름 제어 때문에 제한된다. 그 결과 성능이 떨어진다.
WINDOW_UPDATE 프레임을 제때 보내면 성능을 높일 수 있다. 엔드포인트는 수신 처리량을 높일 필요와 자원 고갈 위험을 관리할 필요 사이에서 균형을 맞추려 할 것이며, 윈도우 크기를 관리하는 전략을 정할 때 10.5절을 주의 깊게 참고할 필요가 있다.
HTTP/2처럼 다중화된 프로토콜에서는 대역폭과 연산 자원을 스트림에 할당하는 우선순위를 정하는 것이 좋은 성능을 얻는 데 결정적일 수 있다. 우선순위 지정 체계가 나쁘면 HTTP/2의 성능이 나빠질 수 있다. TCP 계층에서 병렬성이 없으므로, 성능이 HTTP/1.1보다 훨씬 나빠질 수도 있다.
좋은 우선순위 지정 체계는 리소스(resource)의 내용, 리소스들이 서로 어떻게 관련되는지, 피어가 그 리소스를 어떻게 쓸지 같은 맥락 지식을 적용하는 데서 도움을 얻는다. 특히 클라이언트는 서버의 우선순위 지정과 관련된 요청 우선순위 지식을 가지고 있을 수 있다. 그런 경우 클라이언트가 우선순위 정보를 제공하면 성능을 높일 수 있다.
RFC 7540은 요청의 우선순위를 알리는 풍부한 체계를 정의했다. 그러나 이 체계는 복잡한 것으로 드러났고, 일관되게 구현되지 않았다.
유연한 체계였던 만큼 클라이언트가 우선순위를 매우 다양한 방식으로 표현할 수 있었고, 채택된 방식들 사이에 일관성이 거의 없었다. 서버로서는 이 체계를 범용적으로 지원하도록 구현하기가 복잡했다. 우선순위 구현은 클라이언트와 서버 모두에서 고르지 않았다. 많은 서버 배포는 요청 처리의 우선순위를 정할 때 클라이언트의 신호를 무시했다.
요컨대 RFC 7540 [RFC7540]의 우선순위 신호는 성공하지 못했다.
이번 HTTP/2 갱신은 RFC 7540 [RFC7540]에 정의된 우선순위 신호를 사용 중단한다. 우선순위 신호와 관련된 내용 대부분은 이 문서에 포함하지 않는다. 이 문서의 구현이 RFC 7540에 설명된 우선순위 신호를 쓰는 구현과 계속 상호 운용되도록, 프레임 필드 설명과 일부 필수 처리는 남겨 둔다.
RFC 7540 우선순위 체계에 대한 자세한 설명은 [RFC7540]의 5.3절에 그대로 남아 있다.
많은 경우 좋은 성능을 얻으려면 우선순위 정보를 알리는 것이 필요하다. 우선순위 정보를 알리는 것이 중요한 곳에서는 엔드포인트가 [HTTP-PRIORITY]에 설명된 체계 같은 대안 체계를 쓰기를 권장한다.
RFC 7540의 우선순위 신호는 널리 채택되지 않았지만, 더 나은 정보가 없을 때는 그 신호가 주는 정보가 여전히 유용할 수 있다. HEADERS나 PRIORITY 프레임에서 우선순위 신호를 받은 엔드포인트는 그 정보를 적용해 이득을 볼 수 있다. 특히 이 신호를 사용하는 구현은 대안이 없을 때 이 우선순위 신호를 버려서 얻는 이득이 없다.
서버는 우선순위 신호가 전혀 없을 때 다른 맥락 정보를 사용해 요청의 우선순위를 정해야 한다(SHOULD). 서버는 신호가 완전히 없는 것을 클라이언트가 이 기능을 구현하지 않았다는 표시로 해석할 수 있다(MAY). [RFC7540]의 5.3.5절에 설명된 기본값은 대부분의 조건에서 성능이 나쁜 것으로 알려져 있으며, 이 기본값을 쓴 것이 의도적일 가능성은 낮다.
HTTP/2 프레이밍에서는 두 가지 부류의 오류가 생길 수 있다.
오류 코드 목록은 7장에 있다.
엔드포인트가 여러 오류를 일으키는 프레임을 만날 수도 있다. 구현은 처리 중에 여러 오류를 발견할 수 있지만(MAY), 그 결과로는 스트림 오류 하나와 연결 오류 하나까지만 보고해야 한다(SHOULD).
어떤 스트림에서 처음 보고된 스트림 오류는 그 스트림에서 다른 오류가 보고되지 않게 한다. 이에 비해 프로토콜은 GOAWAY 프레임을 여러 개 보내는 것을 허용하지만, 우아한 종료(graceful shutdown) 중에 오류를 만난 경우가 아니라면 엔드포인트는 한 유형의 연결 오류만 보고해야 한다(SHOULD). 이런 경우가 생기면, 엔드포인트는 앞서 보낸 NO_ERROR를 담은 GOAWAY에 더해 새 오류 코드를 담은 GOAWAY 프레임을 하나 더 보낼 수 있다(MAY).
엔드포인트가 서로 다른 여러 오류를 감지하면, 그중 어느 오류든 하나를 골라 보고할 수 있다(MAY). 프레임이 연결 오류를 일으키면 그 오류는 반드시 보고해야 한다(MUST). 또한 엔드포인트는 오류 상황을 감지했을 때 해당하는 어떤 오류 코드든 쓸 수 있다(MAY). 더 구체적인 오류 코드 대신 범용 오류 코드(PROTOCOL_ERROR나 INTERNAL_ERROR 등)를 언제나 쓸 수 있다.
연결 오류는 프레임 계층의 이후 처리를 막거나 연결 상태를 손상시키는 모든 오류다.
연결 오류를 만난 엔드포인트는 먼저 피어에게서 성공적으로 받은 마지막 스트림의 스트림 식별자를 담은 GOAWAY 프레임(6.8절)을 보내야 한다(SHOULD). GOAWAY 프레임에는 연결이 종료되는 이유를 나타내는 오류 코드(7장)가 들어 있다. 오류 상황 때문에 GOAWAY 프레임을 보낸 뒤, 엔드포인트는 TCP 연결을 반드시 닫아야 한다(MUST).
받는 엔드포인트가 GOAWAY를 확실히 받지 못할 수도 있다. 연결 오류가 생겼을 때 GOAWAY는 연결이 종료되는 이유를 피어에게 알리려는 최선의 시도일 뿐이다.
엔드포인트는 언제든 연결을 끝낼 수 있다. 특히 엔드포인트는 스트림 오류를 연결 오류로 처리하기로 선택할 수 있다(MAY). 엔드포인트는 상황이 허락한다면 연결을 끝낼 때 GOAWAY 프레임을 보내야 한다(SHOULD).
스트림 오류는 다른 스트림의 처리에 영향을 주지 않는, 특정 스트림과 관련된 오류다.
스트림 오류를 감지한 엔드포인트는 오류가 발생한 스트림의 스트림 식별자를 담은 RST_STREAM 프레임(6.4절)을 보낸다. RST_STREAM 프레임에는 오류의 유형을 나타내는 오류 코드가 들어 있다.
RST_STREAM은 엔드포인트가 스트림에서 보낼 수 있는 마지막 프레임이다. RST_STREAM 프레임을 보내는 피어는 원격 피어가 보냈거나 보내려고 대기열에 넣은 모든 프레임을 받을 준비가 반드시 되어 있어야 한다(MUST). 이 프레임들은, 연결 상태(필드 섹션 압축(4.3절)이나 흐름 제어를 위해 유지하는 상태 등)를 바꾸는 경우가 아니라면 무시할 수 있다.
보통 엔드포인트는 한 스트림에 RST_STREAM 프레임을 두 번 이상 보내지 않아야 한다(SHOULD NOT). 그러나 엔드포인트는 왕복 시간(round-trip time)보다 오래 지난 뒤에도 닫힌 스트림에서 프레임을 받으면 RST_STREAM 프레임을 더 보낼 수 있다(MAY). 이 동작은 잘못 동작하는 구현에 대처하기 위해 허용된다.
반복을 피하기 위해 엔드포인트는 RST_STREAM 프레임에 대한 응답으로 RST_STREAM을 보내서는 안 된다(MUST NOT).
스트림이 open 또는 half-closed 상태로 남아 있는 동안 TCP 연결이 닫히거나 리셋되면, 영향을 받은 스트림은 자동으로 재시도할 수 없다(자세한 내용은 8.7절 참고).
HTTP/2는 프로토콜 확장을 허용한다. 이 절에 설명된 제한 안에서, 프로토콜 확장은 추가 서비스를 제공하거나 프로토콜의 어떤 측면이든 바꾸는 데 쓸 수 있다. 확장은 HTTP/2 연결 하나의 범위 안에서만 유효하다.
이는 이 문서에 정의된 프로토콜 요소(protocol element)에 적용된다. 새 메서드, 상태 코드(status code), 필드를 정의하는 것 같은 기존의 HTTP 확장 방법([HTTP]의 16장 참고)에는 영향을 주지 않는다.
확장은 새 프레임 유형(4.1절), 새 설정(6.5절), 새 오류 코드(7장)를 쓸 수 있다. 이 확장 지점(extension point)을 관리하는 레지스트리는 [RFC7540]의 11장에 정의되어 있다.
구현은 확장 가능한 모든 프로토콜 요소에서 알 수 없거나 지원하지 않는 값을 반드시 무시해야 한다(MUST). 구현은 알 수 없거나 지원하지 않는 유형의 프레임을 반드시 버려야 한다(MUST). 이는 확장이 사전 합의나 협상 없이 이 확장 지점들을 안전하게 쓸 수 있다는 뜻이다. 그러나 필드 블록(4.3절) 중간에 나타나는 확장 프레임은 허용되지 않는다. 이런 프레임은 반드시 PROTOCOL_ERROR 유형의 연결 오류(5.4.1절)로 처리해야 한다(MUST).
확장은 이 문서에 정의된 프로토콜 요소나, 확장 메커니즘이 정의되지 않은 요소를 바꾸는 것을 피해야 한다(SHOULD). 여기에는 프레임 구성의 변경, 프레임을 HTTP 메시지로 구성하는 방식(8.1절)의 추가나 변경, 의사 헤더 필드의 정의, 그리고 준수하는 엔드포인트가 연결 오류(5.4.1절)로 처리할 수도 있는 모든 프로토콜 요소의 변경이 포함된다.
기존 프로토콜 요소나 상태를 바꾸는 확장은 사용하기 전에 반드시 협상해야 한다(MUST). 예를 들어 HEADERS 프레임의 구성을 바꾸는 확장은 피어가 이를 받아들일 수 있다는 긍정적인 신호를 줄 때까지 쓸 수 없다. 이 경우 바뀐 구성이 언제부터 효력을 가지는지도 조율할 필요가 있을 수도 있다. 예를 들어 DATA 프레임이 아닌 프레임을 흐름 제어 대상으로 다루려면 두 엔드포인트가 모두 이해할 필요가 있는 의미 변경이 필요하므로, 협상을 통해서만 할 수 있다.
이 문서는 확장 사용을 협상하는 특정 방법을 의무화하지 않지만, 그 목적으로 설정(6.5.2절)을 쓸 수 있다는 점을 언급한다. 두 피어가 모두 확장을 쓸 의향을 나타내는 값을 설정하면 그 확장을 쓸 수 있다. 확장 협상에 설정을 쓴다면, 그 초깃값은 반드시 확장이 처음에는 꺼져 있도록 정의해야 한다(MUST).
이 명세는 여러 프레임 유형을 정의하며, 각 프레임 유형은 고유한 8비트 유형 코드로 식별된다. 각 프레임 유형은 연결 전체나 개별 스트림을 수립하고 관리하는 데서 저마다 다른 목적을 가진다.
특정 프레임 유형을 전송하면 연결의 상태가 바뀔 수 있다. 엔드포인트들이 연결 상태에 대해 동기화된 관점을 유지하지 못하면, 그 연결에서 더 이상 성공적으로 통신할 수 없다. 따라서 엔드포인트들이 각 프레임의 사용이 상태에 어떤 영향을 주는지 공통으로 이해하는 것이 중요하다.
DATA 프레임(type=0x00)은 스트림과 연관된, 임의의 가변 길이 옥텟 시퀀스를 전달한다. 예를 들어 HTTP 요청이나 응답 메시지의 콘텐츠를 담는 데 하나 이상의 DATA 프레임이 쓰인다.
DATA 프레임에는 패딩(padding)도 들어갈 수 있다(MAY). 메시지의 크기를 감추기 위해 DATA 프레임에 패딩을 추가할 수 있다. 패딩은 보안 기능이다. 10.7절을 참고하라.
DATA Frame {
Length (24),
Type (8) = 0x00,
Unused Flags (4),
PADDED Flag (1),
Unused Flags (2),
END_STREAM Flag (1),
Reserved (1),
Stream Identifier (31),
[Pad Length (8)],
Data (..),
Padding (..2040),
}
그림 3: DATA 프레임 형식
Length, Type, Unused Flag(s), Reserved, Stream Identifier 필드는 4장에 설명되어 있다. DATA 프레임에는 다음 필드가 더 있다.
PADDED 플래그가 설정된 경우에만 있다.PROTOCOL_ERROR 유형의 연결 오류(5.4.1절)로 처리할 수 있다(MAY).DATA 프레임은 다음 플래그를 정의한다.
PADDED 플래그는 Pad Length 필드와 그 필드가 나타내는 패딩이 있음을 나타낸다.END_STREAM (0x01): 설정되면 END_STREAM 플래그는 이 프레임이 엔드포인트가 식별된 스트림에서 보낼 마지막 프레임임을 나타낸다. 이 플래그를 설정하면 스트림이 half-closed 상태 중 하나나 closed 상태(5.1절)가 된다.
참고: 데이터를 모두 보낸 뒤에야 스트림을 닫아야 함을 알게 된 엔드포인트는 Data 필드의 길이가 0이고
END_STREAM플래그가 설정된STREAM프레임DATA프레임을 보내 스트림을 닫을 수 있다. 이는 엔드포인트가 트레일러를 보내지 않는 경우에만 가능하다. 트레일러를 보내면END_STREAM플래그는HEADERS프레임에 나타나기 때문이다. 8.1절을 참고하라.
역주: 정오표 7013(Verified)에 따라 “STREAM frame”을 고친 내용이다.
DATA 프레임은 반드시 스트림과 연관되어야 한다(MUST). Stream Identifier 필드가 0x00인 DATA 프레임을 받으면, 수신자는 반드시 PROTOCOL_ERROR 유형의 연결 오류(5.4.1절)로 응답해야 한다(MUST).
DATA 프레임은 흐름 제어 대상이며, 스트림이 open 또는 half-closed (원격) 상태일 때만 보낼 수 있다. Pad Length와 Padding 필드가 있다면 이를 포함해 DATA 프레임 페이로드 전체가 흐름 제어에 포함된다. 스트림이 open 또는 half-closed (로컬) 상태가 아닌데 DATA 프레임을 받으면, 수신자는 반드시 STREAM_CLOSED 유형의 스트림 오류(5.4.2절)로 응답해야 한다(MUST).
패딩 옥텟의 총수는 Pad Length 필드의 값으로 정해진다. 패딩의 길이가 프레임 페이로드의 길이 이상이면, 수신자는 이를 반드시 PROTOCOL_ERROR 유형의 연결 오류(5.4.1절)로 처리해야 한다(MUST).
참고: 값이 0인 Pad Length 필드를 넣으면 프레임 크기를 1옥텟 늘릴 수 있다.
HEADERS 프레임(type=0x01)은 스트림을 여는 데(5.1절) 쓰이며, 필드 블록 조각도 담는다. 이름과 달리 HEADERS 프레임은 헤더 섹션이나 트레일러 섹션을 담을 수 있다. HEADERS 프레임은 idle, reserved (로컬), open, half-closed (원격) 상태의 스트림에서 보낼 수 있다.
HEADERS Frame {
Length (24),
Type (8) = 0x01,
Unused Flags (2),
PRIORITY Flag (1),
Unused Flag (1),
PADDED Flag (1),
END_HEADERS Flag (1),
Unused Flag (1),
END_STREAM Flag (1),
Reserved (1),
Stream Identifier (31),
[Pad Length (8)],
[Exclusive (1)],
[Stream Dependency (31)],
[Weight (8)],
Field Block Fragment (..),
Padding (..2040),
}
그림 4: HEADERS 프레임 형식
Length, Type, Unused Flag(s), Reserved, Stream Identifier 필드는 4장에 설명되어 있다. HEADERS 프레임 페이로드에는 다음 필드가 더 있다.
PADDED 플래그가 설정된 경우에만 있다.PRIORITY 플래그가 설정된 경우에만 있다. HEADERS 프레임의 우선순위 신호는 사용 중단되었다. 5.3.2절을 참고하라.PRIORITY 플래그가 설정된 경우에만 있다.PRIORITY 플래그가 설정된 경우에만 있다.PROTOCOL_ERROR 유형의 연결 오류(5.4.1절)로 처리할 수 있다(MAY).HEADERS 프레임은 다음 플래그를 정의한다.
PRIORITY 플래그는 Exclusive, Stream Dependency, Weight 필드가 있음을 나타낸다.PADDED 플래그는 Pad Length 필드와 그 필드가 나타내는 패딩이 있음을 나타낸다.END_HEADERS (0x04): 설정되면 END_HEADERS 플래그는 이 프레임이 필드 블록(4.3절) 전체를 담고 있으며 뒤에 CONTINUATION 프레임이 오지 않음을 나타낸다.
END_HEADERS 플래그가 설정되지 않은 HEADERS 프레임 뒤에는 반드시 같은 스트림의 CONTINUATION 프레임이 와야 한다(MUST). 수신자는 다른 유형의 프레임이나 다른 스트림의 프레임을 받는 것을 반드시 PROTOCOL_ERROR 유형의 연결 오류(5.4.1절)로 처리해야 한다(MUST).
END_STREAM (0x01): 설정되면 END_STREAM 플래그는 이 필드 블록(4.3절)이 엔드포인트가 식별된 스트림에서 보낼 마지막 필드 블록임을 나타낸다.
END_STREAM 플래그가 설정된 HEADERS 프레임은 스트림의 끝을 알린다. 그러나 END_STREAM 플래그가 설정된 HEADERS 프레임 뒤에도 같은 스트림의 CONTINUATION 프레임이 올 수 있다. 논리적으로 CONTINUATION 프레임은 HEADERS 프레임의 일부다.
HEADERS 프레임의 프레임 페이로드에는 필드 블록 조각(4.3절)이 들어 있다. HEADERS 프레임 하나에 다 들어가지 않는 필드 블록은 CONTINUATION 프레임(6.10절)에서 이어진다.
HEADERS 프레임은 반드시 스트림과 연관되어야 한다(MUST). Stream Identifier 필드가 0x00인 HEADERS 프레임을 받으면, 수신자는 반드시 PROTOCOL_ERROR 유형의 연결 오류(5.4.1절)로 응답해야 한다(MUST).
HEADERS 프레임은 4.3절에 설명된 대로 연결 상태를 바꾼다.
패딩 옥텟의 총수는 Pad Length 필드의 값으로 정해진다. 패딩의 길이가 프레임 페이로드의 길이 이상이면, 수신자는 이를 반드시 PROTOCOL_ERROR 유형의 연결 오류(5.4.1절)로 처리해야 한다(MUST).
참고: 값이 0인 Pad Length 필드를 넣으면 프레임 크기를 1옥텟 늘릴 수 있다.
PRIORITY 프레임(type=0x02)은 사용 중단되었다. 5.3.2절을 참고하라. PRIORITY 프레임은 idle이나 closed 스트림을 포함해 어떤 스트림 상태에서든 보낼 수 있다.
PRIORITY Frame {
Length (24) = 0x05,
Type (8) = 0x02,
Unused Flags (8),
Reserved (1),
Stream Identifier (31),
Exclusive (1),
Stream Dependency (31),
Weight (8),
}
그림 5: PRIORITY 프레임 형식
Length, Type, Unused Flag(s), Reserved, Stream Identifier 필드는 4장에 설명되어 있다. PRIORITY 프레임의 프레임 페이로드에는 다음 필드가 더 있다.
PRIORITY 프레임은 플래그를 정의하지 않는다.
PRIORITY 프레임은 언제나 스트림을 식별한다. 스트림 식별자가 0x00인 PRIORITY 프레임을 받으면, 수신자는 반드시 PROTOCOL_ERROR 유형의 연결 오류(5.4.1절)로 응답해야 한다(MUST).
PRIORITY 프레임을 보내거나 받아도 어떤 스트림의 상태(5.1절)에도 영향을 주지 않는다. PRIORITY 프레임은 idle이나 closed를 포함해 어떤 상태의 스트림에서든 보낼 수 있다. 필드 블록 하나(4.3절)를 이루는 연속된 프레임 사이에는 PRIORITY 프레임을 보낼 수 없다.
길이가 5옥텟이 아닌 PRIORITY 프레임은 반드시 FRAME_SIZE_ERROR 유형의 스트림 오류(5.4.2절)로 처리해야 한다(MUST).
RST_STREAM 프레임(type=0x03)은 스트림을 즉시 종료할 수 있게 한다. RST_STREAM은 스트림의 취소를 요청하거나 오류 상황이 발생했음을 나타내기 위해 보낸다.
RST_STREAM Frame {
Length (24) = 0x04,
Type (8) = 0x03,
Unused Flags (8),
Reserved (1),
Stream Identifier (31),
Error Code (32),
}
그림 6: RST_STREAM 프레임 형식
Length, Type, Unused Flag(s), Reserved, Stream Identifier 필드는 4장에 설명되어 있다. 이에 더해 RST_STREAM 프레임에는 오류 코드(7장)를 식별하는 부호 없는 32비트 정수 하나가 들어 있다. 오류 코드는 스트림이 종료되는 이유를 나타낸다.
RST_STREAM 프레임은 플래그를 정의하지 않는다.
RST_STREAM 프레임은 참조한 스트림을 완전히 종료하고 그 스트림을 closed 상태로 만든다. 어떤 스트림에서 RST_STREAM을 받은 뒤, 수신자는 PRIORITY를 제외하고 그 스트림에 프레임을 더 보내서는 안 된다(MUST NOT). 그러나 RST_STREAM을 보낸 뒤, 보낸 엔드포인트는 RST_STREAM이 도착하기 전에 피어가 보냈을 수 있는, 그 스트림의 프레임을 더 받아 처리할 준비가 반드시 되어 있어야 한다(MUST).
RST_STREAM 프레임은 반드시 스트림과 연관되어야 한다(MUST). 스트림 식별자가 0x00인 RST_STREAM 프레임을 받으면, 수신자는 이를 반드시 PROTOCOL_ERROR 유형의 연결 오류(5.4.1절)로 처리해야 한다(MUST).
idle 상태의 스트림에는 RST_STREAM 프레임을 보내서는 안 된다(MUST NOT). idle 스트림을 식별하는 RST_STREAM 프레임을 받으면, 수신자는 이를 반드시 PROTOCOL_ERROR 유형의 연결 오류(5.4.1절)로 처리해야 한다(MUST).
길이가 4옥텟이 아닌 RST_STREAM 프레임은 반드시 FRAME_SIZE_ERROR 유형의 연결 오류(5.4.1절)로 처리해야 한다(MUST).
SETTINGS 프레임(type=0x04)은 엔드포인트들이 통신하는 방식에 영향을 주는 구성 매개 변수(예: 피어 동작에 대한 선호와 제약)를 전달한다. SETTINGS 프레임은 그 설정을 받았음을 확인 응답하는 데에도 쓰인다. SETTINGS 프레임의 구성 매개 변수 하나하나를 “설정(setting)”이라고 부른다.
설정은 협상되지 않는다. 설정은 보내는 피어의 특성을 설명하며, 받는 피어가 이를 사용한다. 같은 설정에 대해 각 피어가 서로 다른 값을 알릴 수 있다. 예를 들어 클라이언트는 초기 흐름 제어 윈도우를 크게 설정할 수도 있는 반면, 서버는 자원을 아끼기 위해 더 작은 값을 설정할 수도 있다.
SETTINGS 프레임은 연결 초기에 두 엔드포인트가 모두 반드시 보내야 하며(MUST), 연결이 유지되는 동안 언제든 어느 엔드포인트든 보낼 수 있다(MAY). 구현은 이 명세에 정의된 모든 설정을 반드시 지원해야 한다(MUST).
SETTINGS 프레임의 각 매개 변수는 그 매개 변수의 기존 값을 대체한다. 설정은 나타나는 순서대로 처리되며, SETTINGS 프레임의 수신자는 각 설정의 현재 값 외에 다른 상태를 유지할 필요가 없다. 따라서 SETTINGS 매개 변수의 값은 수신자가 본 마지막 값이다.
SETTINGS 프레임은 받는 피어가 확인 응답한다. 이를 위해 SETTINGS 프레임은 ACK 플래그를 정의한다.
ACK 플래그는 이 프레임이 피어의 SETTINGS 프레임을 받아 적용했음을 확인 응답한다는 것을 나타낸다. 이 비트가 설정되면 SETTINGS 프레임의 프레임 페이로드는 반드시 비어 있어야 한다(MUST). ACK 플래그가 설정되고 길이 필드 값이 0이 아닌 SETTINGS 프레임을 받으면, 반드시 FRAME_SIZE_ERROR 유형의 연결 오류(5.4.1절)로 처리해야 한다(MUST). 자세한 내용은 6.5.3절(“설정 동기화”)을 참고하라.SETTINGS 프레임은 언제나 연결에 적용되며, 개별 스트림에는 적용되지 않는다. SETTINGS 프레임의 스트림 식별자는 반드시 0(0x00)이어야 한다(MUST). Stream Identifier 필드가 0x00이 아닌 SETTINGS 프레임을 받은 엔드포인트는 반드시 PROTOCOL_ERROR 유형의 연결 오류(5.4.1절)로 응답해야 한다(MUST).
SETTINGS 프레임은 연결 상태에 영향을 준다. 형식이 잘못되었거나 불완전한 SETTINGS 프레임은 반드시 PROTOCOL_ERROR 유형의 연결 오류(5.4.1절)로 처리해야 한다(MUST).
길이가 6옥텟의 배수가 아닌 SETTINGS 프레임은 반드시 FRAME_SIZE_ERROR 유형의 연결 오류(5.4.1절)로 처리해야 한다(MUST).
SETTINGS 프레임의 프레임 페이로드는 0개 이상의 설정으로 이루어지며, 각 설정은 부호 없는 16비트 설정 식별자와 부호 없는 32비트 값으로 이루어진다.
SETTINGS Frame {
Length (24),
Type (8) = 0x04,
Unused Flags (7),
ACK Flag (1),
Reserved (1),
Stream Identifier (31) = 0,
Setting (48) ...,
}
Setting {
Identifier (16),
Value (32),
}
그림 7: SETTINGS 프레임 형식
Length, Type, Unused Flag(s), Reserved, Stream Identifier 필드는 4장에 설명되어 있다. SETTINGS 프레임의 프레임 페이로드에는 Setting 필드가 몇 개든 들어 있을 수 있으며, 각 Setting 필드는 다음으로 이루어진다.
다음 설정이 정의되어 있다.
SETTINGS_HEADER_TABLE_SIZE (0x01): 이 설정으로 송신자는 필드 블록을 디코딩하는 데 쓰는 압축 테이블의 최대 크기를 옥텟 단위로 원격 엔드포인트에 알릴 수 있다. 인코더는 필드 블록 안에서 압축 형식 고유의 신호를 사용해 이 값 이하의 어떤 크기든 고를 수 있다([COMPRESSION] 참고). 초깃값은 4,096옥텟이다.
SETTINGS_ENABLE_PUSH (0x02): 이 설정은 서버 푸시를 켜거나 끄는 데 쓸 수 있다. 서버는 이 매개 변수가 0으로 설정된 것을 받았다면 PUSH_PROMISE 프레임을 보내서는 안 된다(MUST NOT). 8.4절을 참고하라. 이 매개 변수를 0으로 설정하고 그에 대한 확인 응답도 받은 클라이언트는 PUSH_PROMISE 프레임을 받는 것을 반드시 PROTOCOL_ERROR 유형의 연결 오류(5.4.1절)로 처리해야 한다(MUST).
SETTINGS_ENABLE_PUSH의 초깃값은 1이다. 클라이언트에게 이 값은 PUSH_PROMISE 프레임을 받을 의향이 있음을 나타낸다. 서버에게 이 초깃값은 아무 효과가 없으며 값 0과 같다. 0이나 1이 아닌 값은 반드시 PROTOCOL_ERROR 유형의 연결 오류(5.4.1절)로 처리해야 한다(MUST).
서버는 이 값을 명시적으로 1로 설정해서는 안 된다(MUST NOT). 서버는 SETTINGS 프레임을 보낼 때 이 설정을 생략할 수 있지만(MAY), 값을 넣는다면 그 값은 반드시 0이어야 한다(MUST). 클라이언트는 SETTINGS_ENABLE_PUSH가 1로 설정된 SETTINGS 프레임을 받는 것을 반드시 PROTOCOL_ERROR 유형의 연결 오류(5.4.1절)로 처리해야 한다(MUST).
SETTINGS_MAX_CONCURRENT_STREAMS (0x03): 이 설정은 송신자가 허용할 최대 동시 스트림 수를 나타낸다. 이 한도는 방향이 있다. 즉, 송신자가 수신자에게 생성을 허용하는 스트림의 수에 적용된다. 처음에는 이 값에 제한이 없다. 병렬성을 불필요하게 제한하지 않도록, 이 값은 100보다 작지 않게 할 것을 권장한다.
엔드포인트는 SETTINGS_MAX_CONCURRENT_STREAMS의 값 0을 특별하게 다루지 않아야 한다(SHOULD NOT). 값 0은 새 스트림 생성을 막지만, 이는 활성 스트림으로 소진된 어떤 한도에서도 일어날 수 있다. 서버는 값 0을 짧은 기간 동안만 설정해야 한다(SHOULD). 서버가 요청을 받고 싶지 않다면 연결을 닫는 것이 더 적절하다.
SETTINGS_INITIAL_WINDOW_SIZE (0x04): 이 설정은 스트림 수준 흐름 제어를 위한 송신자의 초기 윈도우 크기(옥텟 단위)를 나타낸다. 초깃값은 2^16-1(65,535)옥텟이다.
이 설정은 모든 스트림의 윈도우 크기에 영향을 준다(6.9.2절 참고).
최대 흐름 제어 윈도우 크기인 2^31-1보다 큰 값은 반드시 FLOW_CONTROL_ERROR 유형의 연결 오류(5.4.1절)로 처리해야 한다(MUST).
SETTINGS_MAX_FRAME_SIZE (0x05): 이 설정은 송신자가 받을 의향이 있는 가장 큰 프레임 페이로드의 크기를 옥텟 단위로 나타낸다.
초깃값은 2^14(16,384)옥텟이다. 엔드포인트가 알리는 값은 반드시 이 초깃값 이상, 허용되는 최대 프레임 크기(2^24-1, 즉 16,777,215옥텟) 이하여야 한다(MUST). 이 범위 밖의 값은 반드시 PROTOCOL_ERROR 유형의 연결 오류(5.4.1절)로 처리해야 한다(MUST).
SETTINGS_MAX_HEADER_LIST_SIZE (0x06): 이 권고 성격의 설정은 송신자가 받아들일 준비가 된 최대 필드 섹션 크기를 옥텟 단위로 피어에게 알린다. 이 값은 필드 줄의 압축되지 않은 크기에 기반하며, 이름과 값의 길이(옥텟 단위)에 필드 줄마다 32옥텟의 오버헤드를 더한 것을 포함한다.
주어진 요청에 대해 알린 것보다 낮은 한도를 적용할 수 있다(MAY). 이 설정의 초깃값은 무제한이다.
알 수 없거나 지원하지 않는 식별자가 든 SETTINGS 프레임을 받은 엔드포인트는 그 설정을 반드시 무시해야 한다(MUST).
SETTINGS의 값 대부분은 피어가 바뀐 매개 변수 값을 언제 받아 적용했는지 아는 것에서 이득을 얻거나, 그것을 알아야 한다. 그런 동기화 시점을 제공하기 위해, ACK 플래그가 설정되지 않은 SETTINGS 프레임의 수신자는 받은 즉시 가능한 한 빨리 갱신된 설정을 반드시 적용해야 한다(MUST). SETTINGS 프레임은 받은 순서대로 확인 응답된다.
SETTINGS 프레임의 값은 반드시 나타나는 순서대로, 값 사이에 다른 프레임 처리 없이 처리해야 한다(MUST). 지원하지 않는 설정은 반드시 무시해야 한다(MUST). 모든 값을 처리하고 나면, 수신자는 ACK 플래그가 설정된 SETTINGS 프레임을 반드시 즉시 내보내야 한다(MUST). ACK 플래그가 설정된 SETTINGS 프레임을 받으면, 설정을 바꾼 송신자는 확인 응답되지 않은 가장 오래된 SETTINGS 프레임의 값이 적용되었다고 믿을 수 있다.
SETTINGS 프레임의 송신자가 합리적인 시간 안에 확인 응답을 받지 못하면, SETTINGS_TIMEOUT 유형의 연결 오류(5.4.1절)를 낼 수 있다(MAY). 타임아웃을 정할 때는 피어의 처리 지연을 어느 정도 감안할 필요가 있다. 엔드포인트 사이의 왕복 시간에만 기반한 타임아웃은 잘못된 오류를 일으킬 수도 있다.
PUSH_PROMISE 프레임(type=0x05)은 송신자가 시작하려는 스트림을 피어 엔드포인트에 미리 알리는 데 쓰인다. PUSH_PROMISE 프레임에는 엔드포인트가 만들 계획인 스트림의 부호 없는 31비트 식별자와 함께, 그 스트림에 추가 맥락을 제공하는 필드 섹션이 들어 있다. PUSH_PROMISE 프레임의 사용은 8.4절에 자세히 설명되어 있다.
PUSH_PROMISE Frame {
Length (24),
Type (8) = 0x05,
Unused Flags (4),
PADDED Flag (1),
END_HEADERS Flag (1),
Unused Flags (2),
Reserved (1),
Stream Identifier (31),
[Pad Length (8)],
Reserved (1),
Promised Stream ID (31),
Field Block Fragment (..),
Padding (..2040),
}
그림 8: PUSH_PROMISE 프레임 형식
Length, Type, Unused Flag(s), Reserved, Stream Identifier 필드는 4장에 설명되어 있다. PUSH_PROMISE 프레임 페이로드에는 다음 필드가 더 있다.
PADDED 플래그가 설정된 경우에만 있다.PUSH_PROMISE가 예약하는 스트림을 식별하는 부호 없는 31비트 정수. 약속된 스트림 식별자는 반드시 송신자가 보낼 다음 스트림으로 유효한 선택(5.1.1절의 “새 스트림 식별자” 참고)이어야 한다(MUST).PROTOCOL_ERROR 유형의 연결 오류(5.4.1절)로 처리할 수 있다(MAY).PUSH_PROMISE 프레임은 다음 플래그를 정의한다.
PADDED 플래그는 Pad Length 필드와 그 필드가 나타내는 패딩이 있음을 나타낸다.END_HEADERS (0x04): 설정되면 END_HEADERS 플래그는 이 프레임이 필드 블록(4.3절) 전체를 담고 있으며 뒤에 CONTINUATION 프레임이 오지 않음을 나타낸다.
END_HEADERS 플래그가 설정되지 않은 PUSH_PROMISE 프레임 뒤에는 반드시 같은 스트림의 CONTINUATION 프레임이 와야 한다(MUST). 수신자는 다른 유형의 프레임이나 다른 스트림의 프레임을 받는 것을 반드시 PROTOCOL_ERROR 유형의 연결 오류(5.4.1절)로 처리해야 한다(MUST).
PUSH_PROMISE 프레임은 반드시 피어가 시작한 스트림 가운데 open 또는 half-closed (원격) 상태인 스트림에서만 보내야 한다(MUST). PUSH_PROMISE 프레임의 스트림 식별자는 그 프레임과 연관된 스트림을 나타낸다. Stream Identifier 필드가 0x00이면, 수신자는 반드시 PROTOCOL_ERROR 유형의 연결 오류(5.4.1절)로 응답해야 한다(MUST).
약속된 스트림을 약속한 순서대로 써야 하는 것은 아니다. PUSH_PROMISE는 나중에 쓸 스트림 식별자를 예약할 뿐이다.
피어 엔드포인트의 SETTINGS_ENABLE_PUSH 설정이 0이면 PUSH_PROMISE를 보내서는 안 된다(MUST NOT). 이 설정을 0으로 설정하고 확인 응답을 받은 엔드포인트는 PUSH_PROMISE 프레임을 받는 것을 반드시 PROTOCOL_ERROR 유형의 연결 오류(5.4.1절)로 처리해야 한다(MUST).
PUSH_PROMISE 프레임의 수신자는 약속된 스트림 식별자를 가리키는 RST_STREAM을 PUSH_PROMISE의 송신자에게 돌려보내, 약속된 스트림을 거부할 수 있다.
PUSH_PROMISE 프레임은 연결 상태를 두 가지 방식으로 바꾼다. 첫째, 필드 블록(4.3절)이 들어 있어 필드 섹션 압축을 위해 유지하는 상태를 바꿀 수 있다. 둘째, PUSH_PROMISE는 나중에 쓸 스트림도 예약하여, 약속된 스트림을 reserved (로컬) 또는 reserved (원격) 상태로 만든다. 송신자는 스트림이 open 또는 half-closed (원격) 상태가 아니라면 그 스트림에서 PUSH_PROMISE를 보내서는 안 된다(MUST NOT). 송신자는 약속된 스트림이 새 스트림 식별자(5.1.1절)로 유효한 선택임을 반드시 보장해야 한다(MUST). 즉, 약속된 스트림은 반드시 idle 상태여야 한다(MUST).
PUSH_PROMISE는 스트림을 예약하므로, PUSH_PROMISE 프레임을 무시하면 스트림 상태가 불확정해진다. 수신자는 open도 half-closed (로컬)도 아닌 스트림에서 PUSH_PROMISE를 받는 것을 반드시 PROTOCOL_ERROR 유형의 연결 오류(5.4.1절)로 처리해야 한다(MUST). 그러나 연관된 스트림에 RST_STREAM을 보낸 엔드포인트는, 피어가 RST_STREAM 프레임을 받아 처리하기 전에 만들었을 수 있는 PUSH_PROMISE 프레임을 반드시 처리해야 한다(MUST).
수신자는 허용되지 않는 스트림 식별자(5.1.1절)를 약속하는 PUSH_PROMISE를 받는 것을 반드시 PROTOCOL_ERROR 유형의 연결 오류(5.4.1절)로 처리해야 한다(MUST). 허용되지 않는 스트림 식별자란 현재 idle 상태가 아닌 스트림의 식별자라는 점에 유의하라.
패딩 옥텟의 총수는 Pad Length 필드의 값으로 정해진다. 패딩의 길이가 프레임 페이로드의 길이 이상이면, 수신자는 이를 반드시 PROTOCOL_ERROR 유형의 연결 오류(5.4.1절)로 처리해야 한다(MUST).
참고: 값이 0인 Pad Length 필드를 넣으면 프레임 크기를 1옥텟 늘릴 수 있다.
PING 프레임(type=0x06)은 송신자로부터의 최소 왕복 시간을 재고, 유휴 연결이 아직 작동하는지 확인하는 메커니즘이다. PING 프레임은 어느 엔드포인트든 보낼 수 있다.
PING Frame {
Length (24) = 0x08,
Type (8) = 0x06,
Unused Flags (7),
ACK Flag (1),
Reserved (1),
Stream Identifier (31) = 0,
Opaque Data (64),
}
그림 9: PING 프레임 형식
Length, Type, Unused Flag(s), Reserved, Stream Identifier 필드는 4장에 설명되어 있다.
프레임 헤더에 더해, PING 프레임의 프레임 페이로드에는 반드시 8옥텟의 불투명한 데이터(opaque data)가 들어 있어야 한다(MUST). 송신자는 원하는 어떤 값이든 넣고 그 옥텟을 어떤 방식으로든 쓸 수 있다.
ACK 플래그가 없는 PING 프레임의 수신자는 그에 대한 응답으로 ACK 플래그가 설정되고 프레임 페이로드가 같은 PING 프레임을 반드시 보내야 한다(MUST). PING 응답에는 다른 어떤 프레임보다 높은 우선순위를 주어야 한다(SHOULD).
PING 프레임은 다음 플래그를 정의한다.
ACK 플래그는 이 PING 프레임이 PING 응답임을 나타낸다. 엔드포인트는 PING 응답에 이 플래그를 반드시 설정해야 한다(MUST). 엔드포인트는 이 플래그가 든 PING 프레임에 응답해서는 안 된다(MUST NOT).PING 프레임은 어떤 개별 스트림과도 연관되지 않는다. Stream Identifier 필드 값이 0x00이 아닌 PING 프레임을 받으면, 수신자는 반드시 PROTOCOL_ERROR 유형의 연결 오류(5.4.1절)로 응답해야 한다(MUST).
길이 필드 값이 8이 아닌 PING 프레임을 받으면 반드시 FRAME_SIZE_ERROR 유형의 연결 오류(5.4.1절)로 처리해야 한다(MUST).
GOAWAY 프레임(type=0x07)은 연결 종료를 시작하거나 심각한 오류 상황을 알리는 데 쓰인다. GOAWAY로 엔드포인트는 이미 수립한 스트림의 처리는 마치면서 새 스트림을 받는 것은 우아하게 멈출 수 있다. 이를 통해 서버 유지 보수 같은 관리 작업을 할 수 있다.
엔드포인트가 새 스트림을 시작하는 것과 원격 피어가 GOAWAY 프레임을 보내는 것 사이에는 본질적인 경쟁 상태(race condition)가 있다. 이 경우에 대처하기 위해, GOAWAY에는 이 연결에서 보내는 엔드포인트가 처리했거나 처리할 수도 있는, 피어가 시작한 마지막 스트림의 스트림 식별자가 들어 있다. 예를 들어 서버가 GOAWAY 프레임을 보내면, 식별된 스트림은 클라이언트가 시작한 스트림 가운데 번호가 가장 큰 스트림이다.
GOAWAY를 보낸 뒤, 송신자는 수신자가 시작한 스트림 가운데 GOAWAY에 담긴 마지막 스트림 식별자보다 식별자가 큰 스트림에서 보낸 프레임을 무시한다. GOAWAY 프레임의 수신자는 그 연결에서 스트림을 더 열어서는 안 된다(MUST NOT). 다만 새 스트림을 위해 새 연결을 맺을 수는 있다.
GOAWAY의 수신자가 GOAWAY 프레임에 표시된 것보다 큰 스트림 식별자를 가진 스트림에서 데이터를 보냈다면, 그 스트림은 처리되지 않았거나 처리되지 않을 것이다. GOAWAY 프레임의 수신자는 그 스트림을 아예 생성되지 않았던 것처럼 다룰 수 있으며, 그러면 나중에 새 연결에서 그 스트림을 재시도할 수 있다.
엔드포인트는 원격 피어가 스트림이 부분적으로 처리되었는지 알 수 있도록, 연결을 닫기 전에 언제나 GOAWAY 프레임을 보내야 한다(SHOULD). 예를 들어 서버가 연결을 닫는 것과 동시에 HTTP 클라이언트가 POST를 보내면, 서버가 어떤 스트림에 대해 동작했을 수 있는지 나타내는 GOAWAY 프레임을 보내지 않는 한 클라이언트는 서버가 그 POST 요청을 처리하기 시작했는지 알 수 없다.
엔드포인트는 잘못 동작하는 피어에 대해서는 GOAWAY를 보내지 않고 연결을 닫기로 할 수도 있다.
GOAWAY 프레임이 연결 종료 바로 앞에 오지 않을 수도 있다. 연결을 더 쓸 일이 없는 GOAWAY 수신자도 연결을 끝내기 전에 GOAWAY 프레임을 보내야 한다(SHOULD).
GOAWAY Frame {
Length (24),
Type (8) = 0x07,
Unused Flags (8),
Reserved (1),
Stream Identifier (31) = 0,
Reserved (1),
Last-Stream-ID (31),
Error Code (32),
Additional Debug Data (..),
}
그림 10: GOAWAY 프레임 형식
Length, Type, Unused Flag(s), Reserved, Stream Identifier 필드는 4장에 설명되어 있다.
GOAWAY 프레임은 플래그를 정의하지 않는다.
GOAWAY 프레임은 특정 스트림이 아니라 연결에 적용된다. 엔드포인트는 스트림 식별자가 0x00이 아닌 GOAWAY 프레임을 반드시 PROTOCOL_ERROR 유형의 연결 오류(5.4.1절)로 처리해야 한다(MUST).
GOAWAY 프레임의 마지막 스트림 식별자에는, GOAWAY 프레임의 송신자가 어떤 동작을 했을 수 있거나 아직 할 수도 있는 스트림 가운데 가장 큰 스트림 식별자가 들어 있다. 식별된 스트림까지(그 스트림 포함)의 모든 스트림은 어떤 식으로든 처리되었을 수 있다. 처리된 스트림이 없으면 마지막 스트림 식별자를 0으로 설정할 수 있다.
참고: 여기서 “처리되었다”는 것은 스트림의 어떤 데이터가 소프트웨어의 상위 계층으로 전달되어 그 결과로 어떤 동작이 있었을 수 있다는 뜻이다.
연결이 GOAWAY 프레임 없이 종료되면, 마지막 스트림 식별자는 사실상 가능한 가장 큰 스트림 식별자다.
연결이 닫히기 전에 완전히 닫히지 않은, 식별자가 마지막 스트림 식별자 이하인 스트림에서는, HTTP GET, PUT, DELETE 같은 멱등(idempotent) 동작을 제외하고는 요청, 트랜잭션, 그 밖의 어떤 프로토콜 활동도 다시 시도할 수 없다. 식별자가 더 큰 스트림을 쓰는 프로토콜 활동은 새 연결을 사용해 안전하게 재시도할 수 있다.
번호가 마지막 스트림 식별자 이하인 스트림의 활동은 여전히 성공적으로 완료될 수도 있다. GOAWAY 프레임의 송신자는 GOAWAY 프레임을 보낸 뒤 진행 중인 스트림이 모두 완료될 때까지 연결을 열린 상태로 유지하여 연결을 우아하게 종료할 수도 있다.
엔드포인트는 상황이 바뀌면 GOAWAY 프레임을 여러 번 보낼 수 있다(MAY). 예를 들어 우아한 종료 중에 NO_ERROR와 함께 GOAWAY를 보낸 엔드포인트가, 이후 연결을 즉시 끝내야 하는 상황을 만날 수도 있다. 마지막으로 받은 GOAWAY 프레임의 마지막 스트림 식별자가 어떤 스트림에 대해 동작했을 수 있는지를 나타낸다. 엔드포인트는 마지막 스트림 식별자로 보내는 값을 늘려서는 안 된다(MUST NOT). 피어가 처리되지 않은 요청을 이미 다른 연결에서 재시도했을 수 있기 때문이다.
요청을 재시도할 수 없는 클라이언트는 서버가 연결을 닫을 때 전송 중인 요청을 모두 잃는다. 이는 클라이언트에게 HTTP/2로 서비스하지 않을 수도 있는 중개자에게 특히 그렇다. 연결을 우아하게 종료하려는 서버는 마지막 스트림 식별자를 2^31-1로 설정하고 NO_ERROR 코드를 담은 첫 GOAWAY 프레임을 보내야 한다(SHOULD). 이는 클라이언트에게 종료가 임박했으며 요청을 더 시작하는 것이 금지됨을 알린다. 전송 중인 스트림 생성을 위한 시간(최소한 왕복 시간 한 번)을 둔 뒤, 서버는 갱신한 마지막 스트림 식별자를 담은 GOAWAY 프레임을 하나 더 보낼 수 있다(MAY). 이렇게 하면 요청을 잃지 않고 연결을 깔끔하게 종료할 수 있다.
GOAWAY 프레임을 보낸 뒤, 송신자는 수신자가 시작한 스트림 가운데 식별자가 식별된 마지막 스트림보다 큰 스트림의 프레임을 버릴 수 있다. 그러나 연결 상태를 바꾸는 프레임은 완전히 무시할 수 없다. 예를 들어 HEADERS, PUSH_PROMISE, CONTINUATION 프레임은 필드 섹션 압축을 위해 유지하는 상태가 일관되도록(4.3절 참고) 반드시 최소한으로 처리해야 한다(MUST). 마찬가지로 DATA 프레임은 연결의 흐름 제어 윈도우에 반드시 포함해 세어야 한다(MUST). 이 프레임들을 처리하지 않으면 흐름 제어나 필드 섹션 압축 상태가 동기화되지 않을 수 있다.
GOAWAY 프레임에는 연결을 닫는 이유를 담은 32비트 오류 코드(7장)도 들어 있다.
엔드포인트는 어떤 GOAWAY 프레임의 프레임 페이로드에든 불투명한 데이터를 덧붙일 수 있다(MAY). 추가 디버그 데이터는 진단 목적으로만 쓰기 위한 것이며 의미상 가치는 없다. 디버그 정보에는 보안이나 프라이버시 측면에서 민감한 데이터가 들어 있을 수 있다. 로그로 남기거나 그 밖의 방식으로 지속 저장한 디버그 데이터에는 반드시 무단 접근을 막는 적절한 보호 장치가 있어야 한다(MUST).
WINDOW_UPDATE 프레임(type=0x08)은 흐름 제어를 구현하는 데 쓰인다. 개요는 5.2절을 참고하라.
흐름 제어는 개별 스트림과 연결 전체라는 두 수준에서 동작한다.
두 유형의 흐름 제어 모두 홉 단위(hop by hop), 즉 두 엔드포인트 사이에서만 동작한다. 중개자는 서로 의존하는 연결 사이에서 WINDOW_UPDATE 프레임을 전달하지 않는다. 그러나 어느 수신자든 데이터 전송을 조절하면, 흐름 제어 정보가 원래 송신자 쪽으로 간접적으로 전파될 수 있다.
흐름 제어는 흐름 제어 대상으로 식별된 프레임에만 적용된다. 이 문서에 정의된 프레임 유형 중에서는 DATA 프레임만 여기에 해당한다. 흐름 제어에서 제외된 프레임은, 수신자가 그 프레임을 처리하는 데 자원을 할당할 수 없는 경우가 아니라면 반드시 받아들여 처리해야 한다(MUST). 수신자는 프레임을 받아들일 수 없으면 FLOW_CONTROL_ERROR 유형의 스트림 오류(5.4.2절)나 연결 오류(5.4.1절)로 응답할 수 있다(MAY).
WINDOW_UPDATE Frame {
Length (24) = 0x04,
Type (8) = 0x08,
Unused Flags (8),
Reserved (1),
Stream Identifier (31),
Reserved (1),
Window Size Increment (31),
}
그림 11: WINDOW_UPDATE 프레임 형식
Length, Type, Unused Flag(s), Reserved, Stream Identifier 필드는 4장에 설명되어 있다. WINDOW_UPDATE 프레임의 프레임 페이로드는 예약된 1비트와, 송신자가 기존 흐름 제어 윈도우에 더해 전송할 수 있는 옥텟 수를 나타내는 부호 없는 31비트 정수로 이루어진다. 흐름 제어 윈도우 증가분의 허용 범위는 1부터 2^31-1(2,147,483,647)옥텟까지다.
WINDOW_UPDATE 프레임은 플래그를 정의하지 않는다.
WINDOW_UPDATE 프레임은 스트림 하나에 대한 것일 수도 있고 연결 전체에 대한 것일 수도 있다. 앞의 경우 프레임의 스트림 식별자가 영향을 받는 스트림을 나타내고, 뒤의 경우 값 “0”이 연결 전체가 프레임의 대상임을 나타낸다.
수신자는 흐름 제어 윈도우 증가분이 0인 WINDOW_UPDATE 프레임을 받는 것을 반드시 PROTOCOL_ERROR 유형의 스트림 오류(5.4.2절)로 처리해야 한다(MUST). 연결 흐름 제어 윈도우에 대한 오류는 반드시 연결 오류(5.4.1절)로 처리해야 한다(MUST).
END_STREAM 플래그가 설정된 프레임을 보낸 피어도 WINDOW_UPDATE를 보낼 수 있다. 이는 수신자가 half-closed (원격) 또는 closed 상태의 스트림에서 WINDOW_UPDATE 프레임을 받을 수 있다는 뜻이다. 수신자는 이를 오류로 처리해서는 안 된다(MUST NOT). 5.1절을 참고하라.
흐름 제어 대상 프레임을 받은 수신자는, 이를 연결 오류(5.4.1절)로 처리하는 경우가 아니라면, 그 프레임이 차지하는 양을 연결 흐름 제어 윈도우에서 반드시 언제나 차감해야 한다(MUST). 이는 프레임에 오류가 있어도 필요하다. 송신자는 그 프레임을 흐름 제어 윈도우에 포함해 세는데, 수신자가 그러지 않으면 송신자와 수신자의 흐름 제어 윈도우가 서로 달라질 수 있다.
길이가 4옥텟이 아닌 WINDOW_UPDATE 프레임은 반드시 FRAME_SIZE_ERROR 유형의 연결 오류(5.4.1절)로 처리해야 한다(MUST).
HTTP/2의 흐름 제어는 각 송신자가 모든 스트림마다 유지하는 윈도우를 사용해 구현된다. 흐름 제어 윈도우는 송신자가 전송할 수 있는 데이터의 옥텟 수를 나타내는 단순한 정숫값이다. 따라서 그 크기는 수신자의 버퍼 용량을 나타내는 척도다.
적용되는 흐름 제어 윈도우는 스트림 흐름 제어 윈도우와 연결 흐름 제어 윈도우 두 가지다. 송신자는 수신자가 알린 두 흐름 제어 윈도우 중 어느 쪽에서든 사용 가능한 공간을 넘는 길이의 흐름 제어 대상 프레임을 보내서는 안 된다(MUST NOT). 두 흐름 제어 윈도우 중 어느 쪽에도 사용 가능한 공간이 없을 때, END_STREAM 플래그가 설정된 길이 0의 프레임(즉, 빈 DATA 프레임)은 보낼 수 있다(MAY).
흐름 제어 계산에서 9옥텟 프레임 헤더는 세지 않는다.
흐름 제어 대상 프레임을 보낸 뒤, 송신자는 두 윈도우에서 사용 가능한 공간을 전송한 프레임의 길이만큼 줄인다.
프레임의 수신자는 데이터를 소비해 흐름 제어 윈도우의 공간을 비우면 WINDOW_UPDATE 프레임을 보낸다. 스트림 수준 흐름 제어 윈도우와 연결 수준 흐름 제어 윈도우에 대해 각각 따로 WINDOW_UPDATE 프레임을 보낸다. 수신자는 증가분이 매우 작은 WINDOW_UPDATE 프레임을 보내지 않도록 하는 메커니즘을 갖추는 것이 좋다. [RFC1122]의 4.2.3.3절을 참고하라.
WINDOW_UPDATE 프레임을 받은 송신자는 해당 윈도우를 프레임에 지정된 양만큼 갱신한다.
송신자는 흐름 제어 윈도우가 2^31-1옥텟을 넘게 해서는 안 된다(MUST NOT). 흐름 제어 윈도우가 이 최댓값을 넘게 만드는 WINDOW_UPDATE를 받은 송신자는 경우에 맞게 반드시 스트림이나 연결을 종료해야 한다(MUST). 스트림의 경우 송신자는 오류 코드가 FLOW_CONTROL_ERROR인 RST_STREAM을 보내고, 연결의 경우 오류 코드가 FLOW_CONTROL_ERROR인 GOAWAY 프레임을 보낸다.
송신자의 흐름 제어 대상 프레임과 수신자의 WINDOW_UPDATE 프레임은 서로 완전히 비동기적이다. 이 특성 덕분에 수신자는 스트림이 멈추지 않도록 송신자가 유지하는 윈도우 크기를 적극적으로 갱신할 수 있다.
HTTP/2 연결이 처음 맺어지면, 새 스트림은 초기 흐름 제어 윈도우 크기 65,535옥텟으로 생성된다. 연결 흐름 제어 윈도우도 65,535옥텟이다. 두 엔드포인트 모두 SETTINGS 프레임에 SETTINGS_INITIAL_WINDOW_SIZE 값을 넣어 새 스트림의 초기 윈도우 크기를 조정할 수 있다. 연결 흐름 제어 윈도우는 WINDOW_UPDATE 프레임으로만 바꿀 수 있다.
SETTINGS_INITIAL_WINDOW_SIZE 값을 설정하는 SETTINGS 프레임을 받기 전에는, 엔드포인트는 흐름 제어 대상 프레임을 보낼 때 기본 초기 윈도우 크기만 쓸 수 있다. 마찬가지로 연결 흐름 제어 윈도우는 WINDOW_UPDATE 프레임을 받을 때까지 기본 초기 윈도우 크기를 바탕으로 설정된다.
SETTINGS 프레임은 아직 활성화되지 않은 스트림의 흐름 제어 윈도우를 바꿀 뿐 아니라, 활성 흐름 제어 윈도우를 가진 스트림(즉, open 또는 half-closed (원격) 상태의 스트림)의 초기 흐름 제어 윈도우 크기도 바꿀 수 있다. SETTINGS_INITIAL_WINDOW_SIZE의 값이 바뀌면, 수신자는 자신이 유지하는 모든 스트림 흐름 제어 윈도우의 크기를 새 값과 이전 값의 차이만큼 반드시 조정해야 한다(MUST).
SETTINGS_INITIAL_WINDOW_SIZE가 바뀌면 흐름 제어 윈도우의 사용 가능한 공간이 음수가 될 수 있다. 송신자는 음수가 된 흐름 제어 윈도우를 반드시 추적해야 하며(MUST), 흐름 제어 윈도우를 양수로 만드는 WINDOW_UPDATE 프레임을 받을 때까지 새 흐름 제어 대상 프레임을 보내서는 안 된다(MUST NOT).
예를 들어 클라이언트가 연결을 맺자마자 60KB를 보냈는데 서버가 초기 윈도우 크기를 16KB로 설정하면, 클라이언트는 SETTINGS 프레임을 받았을 때 사용 가능한 흐름 제어 윈도우를 -44KB로 다시 계산한다. 클라이언트는 WINDOW_UPDATE 프레임이 윈도우를 양수로 되돌릴 때까지 음수 흐름 제어 윈도우를 유지하며, 그 뒤에야 다시 보낼 수 있다.
SETTINGS 프레임은 연결 흐름 제어 윈도우를 바꿀 수 없다.
엔드포인트는 어떤 흐름 제어 윈도우든 최대 크기를 넘게 만드는 SETTINGS_INITIAL_WINDOW_SIZE 변경을 반드시 FLOW_CONTROL_ERROR 유형의 연결 오류(5.4.1절)로 처리해야 한다(MUST).
현재 크기보다 작은 흐름 제어 윈도우를 쓰고 싶은 수신자는 새 SETTINGS 프레임을 보낼 수 있다. 그러나 송신자가 SETTINGS 프레임을 처리하기 전에 더 낮은 한도를 넘는 데이터를 보낼 수도 있으므로, 수신자는 이 윈도우 크기를 넘는 데이터를 받을 준비가 반드시 되어 있어야 한다(MUST).
초기 흐름 제어 윈도우 크기를 줄이는 SETTINGS 프레임을 보낸 뒤, 수신자는 흐름 제어 한도를 넘는 스트림을 계속 처리할 수 있다(MAY). 스트림을 계속 허용한다고 해서 수신자가 흐름 제어 윈도우를 위해 확보해 둔 공간을 곧바로 줄일 수 있는 것은 아니다. 송신자가 다시 보내려면 WINDOW_UPDATE 프레임이 필요하므로, 이 스트림들의 진행도 멈출 수 있다. 대신 수신자는 영향을 받는 스트림에 오류 코드가 FLOW_CONTROL_ERROR인 RST_STREAM을 보낼 수 있다(MAY).
CONTINUATION 프레임(type=0x09)은 필드 블록 조각(4.3절)의 시퀀스를 이어 가는 데 쓰인다. 앞 프레임이 같은 스트림에 있고 END_HEADERS 플래그가 설정되지 않은 HEADERS, PUSH_PROMISE, CONTINUATION 프레임이라면, CONTINUATION 프레임은 몇 개든 보낼 수 있다.
CONTINUATION Frame {
Length (24),
Type (8) = 0x09,
Unused Flags (5),
END_HEADERS Flag (1),
Unused Flags (2),
Reserved (1),
Stream Identifier (31),
Field Block Fragment (..),
}
그림 12: CONTINUATION 프레임 형식
Length, Type, Unused Flag(s), Reserved, Stream Identifier 필드는 4장에 설명되어 있다. CONTINUATION 프레임 페이로드에는 필드 블록 조각(4.3절)이 들어 있다.
CONTINUATION 프레임은 다음 플래그를 정의한다.
END_HEADERS (0x04): 설정되면 END_HEADERS 플래그는 이 프레임이 필드 블록(4.3절)을 끝냄을 나타낸다.
END_HEADERS 플래그가 설정되지 않았다면, 이 프레임 뒤에는 반드시 다른 CONTINUATION 프레임이 와야 한다(MUST). 수신자는 다른 유형의 프레임이나 다른 스트림의 프레임을 받는 것을 반드시 PROTOCOL_ERROR 유형의 연결 오류(5.4.1절)로 처리해야 한다(MUST).
CONTINUATION 프레임은 4.3절에 정의된 대로 연결 상태를 바꾼다.
CONTINUATION 프레임은 반드시 스트림과 연관되어야 한다(MUST). Stream Identifier 필드가 0x00인 CONTINUATION 프레임을 받으면, 수신자는 반드시 PROTOCOL_ERROR 유형의 연결 오류(5.4.1절)로 응답해야 한다(MUST).
CONTINUATION 프레임 앞에는 반드시 END_HEADERS 플래그가 설정되지 않은 HEADERS, PUSH_PROMISE, CONTINUATION 프레임이 와야 한다(MUST). 이 규칙의 위반을 발견한 수신자는 반드시 PROTOCOL_ERROR 유형의 연결 오류(5.4.1절)로 응답해야 한다(MUST).
오류 코드는 스트림 오류나 연결 오류의 이유를 전달하기 위해 RST_STREAM과 GOAWAY 프레임에서 쓰는 32비트 필드다.
오류 코드는 공통 코드 공간을 공유한다. 일부 오류 코드는 스트림이나 연결 전체 중 한쪽에만 적용되며, 다른 쪽 맥락에서는 정의된 의미가 없다.
다음 오류 코드가 정의되어 있다.
NO_ERROR (0x00): 관련 상황이 오류의 결과가 아니다. 예를 들어 GOAWAY는 연결의 우아한 종료를 나타내기 위해 이 코드를 담을 수도 있다.PROTOCOL_ERROR (0x01): 엔드포인트가 구체적이지 않은 프로토콜 오류를 감지했다. 이 오류는 더 구체적인 오류 코드가 없을 때 쓰기 위한 것이다.INTERNAL_ERROR (0x02): 엔드포인트가 예상하지 못한 내부 오류를 만났다.FLOW_CONTROL_ERROR (0x03): 엔드포인트가 피어의 흐름 제어 프로토콜 위반을 감지했다.SETTINGS_TIMEOUT (0x04): 엔드포인트가 SETTINGS 프레임을 보냈지만 제때 응답을 받지 못했다. 6.5.3절(“설정 동기화”)을 참고하라.STREAM_CLOSED (0x05): 엔드포인트가 스트림이 half-closed된 뒤에 프레임을 받았다.FRAME_SIZE_ERROR (0x06): 엔드포인트가 크기가 유효하지 않은 프레임을 받았다.REFUSED_STREAM (0x07): 엔드포인트가 애플리케이션 처리를 하기 전에 스트림을 거부했다(자세한 내용은 8.7절 참고).CANCEL (0x08): 엔드포인트가 스트림이 더 이상 필요 없음을 나타내기 위해 이 오류 코드를 쓴다.COMPRESSION_ERROR (0x09): 엔드포인트가 연결의 필드 섹션 압축 컨텍스트를 유지할 수 없다.CONNECT_ERROR (0x0a): CONNECT 요청(8.5절)에 대한 응답으로 맺은 연결이 리셋되었거나 비정상적으로 닫혔다.ENHANCE_YOUR_CALM (0x0b): 엔드포인트가 피어가 과도한 부하를 일으키고 있을 수 있는 동작을 보이는 것을 감지했다.INADEQUATE_SECURITY (0x0c): 기반 전송의 특성이 최소 보안 요구 사항을 충족하지 않는다(9.2절 참고).HTTP_1_1_REQUIRED (0x0d): 엔드포인트가 HTTP/2 대신 HTTP/1.1을 사용할 것을 요구한다.알 수 없거나 지원하지 않는 오류 코드가 특별한 동작을 일으켜서는 안 된다(MUST NOT). 구현은 이런 오류 코드를 INTERNAL_ERROR와 같은 것으로 처리할 수 있다(MAY).
HTTP/2는 HTTP 메시지 추상화([HTTP]의 6장)를 구체화한 것이다.
클라이언트는 이전에 쓰지 않은 스트림 식별자(5.1.1절)를 사용해 새 스트림에서 HTTP 요청을 보낸다. 서버는 요청과 같은 스트림에서 HTTP 응답을 보낸다.
HTTP 메시지(요청 또는 응답)는 다음으로 이루어진다.
HEADERS 프레임 하나(뒤에 CONTINUATION 프레임이 0개 이상 올 수도 있다),DATA 프레임 0개 이상([HTTP]의 6.4절 참고),HEADERS 프레임 하나(뒤에 CONTINUATION 프레임이 0개 이상 올 수도 있다).응답에 한해, 서버는 최종 응답을 담은 HEADERS 프레임 앞에 중간 응답을 몇 개든 보낼 수 있다(MAY). 중간 응답은 중간(1xx) HTTP 응답의 제어 데이터와 헤더 섹션을 담은 HEADERS 프레임(뒤에 CONTINUATION 프레임이 0개 이상 올 수도 있다)으로 이루어진다([HTTP]의 15장 참고). END_STREAM 플래그가 설정되고 정보 상태 코드를 담은 HEADERS 프레임은 형식이 잘못된(malformed) 것이다(8.1.1절).
시퀀스의 마지막 프레임에는 END_STREAM 플래그가 있다. 단, END_STREAM 플래그가 설정된 HEADERS 프레임 뒤에는 필드 블록의 남은 조각을 담은 CONTINUATION 프레임이 올 수 있다는 점에 유의하라.
HEADERS 프레임과 그 뒤에 올 수도 있는 CONTINUATION 프레임 사이에는 (어느 스트림의 것이든) 다른 프레임이 나타나서는 안 된다(MUST NOT).
HTTP/2는 메시지 콘텐츠를 담는 데 DATA 프레임을 사용한다. [HTTP/1.1]의 7.1절에 정의된 청크 전송 코딩(chunked transfer coding)은 HTTP/2에서 쓸 수 없다. 8.2.2절을 참고하라.
트레일러 필드(trailer field)는 스트림도 끝내는 필드 블록에 담긴다. 즉, 트레일러 필드는 HEADERS 프레임으로 시작하고 그 뒤에 CONTINUATION 프레임이 0개 이상 오는 시퀀스로 이루어지며, 그 HEADERS 프레임에는 END_STREAM 플래그가 있다. 트레일러에는 의사 헤더 필드(8.3절)가 들어 있어서는 안 된다(MUST NOT). 트레일러에서 의사 헤더 필드를 받은 엔드포인트는 그 요청이나 응답을 반드시 형식이 잘못된 것(8.1.1절)으로 처리해야 한다(MUST).
요청을 여는 HEADERS 프레임을 받은 뒤나, 최종(정보가 아닌) 상태 코드를 받은 뒤에 END_STREAM 플래그가 설정되지 않은 HEADERS 프레임을 받은 엔드포인트는, 해당 요청이나 응답을 반드시 형식이 잘못된 것(8.1.1절)으로 처리해야 한다(MUST).
HTTP 요청/응답 교환 하나는 스트림 하나를 완전히 소비한다. 요청은 스트림을 open 상태로 만드는 HEADERS 프레임으로 시작한다. 요청은 END_STREAM 플래그가 설정된 프레임으로 끝나며, 이 프레임으로 스트림은 클라이언트에서는 half-closed (로컬), 서버에서는 half-closed (원격)이 된다. 응답 스트림은 HEADERS 프레임에 담긴 중간 응답 0개 이상으로 시작하고, 그 뒤에 최종 상태 코드를 담은 HEADERS 프레임이 온다.
HTTP 응답은 서버가 END_STREAM 플래그가 설정된 프레임(필드 블록을 완성하는 데 필요한 CONTINUATION 프레임 포함)을 보내면, 또는 클라이언트가 이를 받으면 완료된다. 응답이 아직 보내고 받지 않은 요청의 어떤 부분에도 의존하지 않는다면, 서버는 클라이언트가 요청 전체를 보내기 전에 완전한 응답을 보낼 수 있다. 이 경우 서버는 완전한 응답(즉, END_STREAM 플래그가 설정된 프레임)을 보낸 뒤 오류 코드가 NO_ERROR인 RST_STREAM을 보내, 클라이언트에게 오류 없이 요청 전송을 중단하도록 요청할 수 있다(MAY). 클라이언트는 그런 RST_STREAM을 받았다는 이유로 응답을 버려서는 안 된다(MUST NOT). 다만 클라이언트는 다른 이유로는 재량에 따라 언제든 응답을 버릴 수 있다.
형식이 잘못된 요청이나 응답이란, 다른 면에서는 유효한 HTTP/2 프레임 시퀀스이지만 불필요한 프레임이 있거나, 금지된 필드나 의사 헤더 필드가 있거나, 필수 의사 헤더 필드가 없거나, 대문자 필드 이름(field name)이 들어 있거나, 필드 이름이나 값 또는 둘 다가 유효하지 않아서(특정 상황에서. 8.2절 참고) 유효하지 않은 것을 말한다.
메시지 콘텐츠를 포함한 요청이나 응답에는 content-length 헤더 필드가 들어갈 수 있다. 메시지가 콘텐츠가 없는 것으로 정의된 경우가 아니라면, content-length 헤더 필드의 값이 콘텐츠를 이루는 DATA 프레임 페이로드 길이의 합과 같지 않은 요청이나 응답도 형식이 잘못된 것이다. 예를 들어 204나 304 응답에는 콘텐츠가 없고, HEAD 요청에 대한 응답도 마찬가지다. [HTTP]의 6.4.1절에 설명된 대로 콘텐츠가 없는 것으로 정의된 응답은 DATA 프레임에 콘텐츠가 없더라도 0이 아닌 content-length 헤더 필드를 가질 수 있다(MAY).
HTTP 요청이나 응답을 처리하는 중개자(즉, 터널로 동작하지 않는 모든 중개자)는 형식이 잘못된 요청이나 응답을 전달해서는 안 된다(MUST NOT). 감지된 형식이 잘못된 요청이나 응답은 반드시 PROTOCOL_ERROR 유형의 스트림 오류(5.4.2절)로 처리해야 한다(MUST).
형식이 잘못된 요청에 대해, 서버는 스트림을 닫거나 리셋하기 전에 HTTP 응답을 보낼 수 있다(MAY). 클라이언트는 형식이 잘못된 응답을 받아들여서는 안 된다(MUST NOT).
메시지를 점진적으로 처리하는 엔드포인트는 요청이나 응답이 형식이 잘못되었음을 알아내기 전에 일부 처리를 이미 했을 수 있다. 예를 들어 요청 전체를 받지 않고도 정보 상태 코드나 404 상태 코드를 생성할 수 있을 것이다. 마찬가지로 중개자는 오류를 감지하기 전에 불완전한 메시지를 전달할 수도 있다. 응답이 요청의 나머지 부분이 올바른지에 의존하지 않는다면, 서버는 요청 전체를 받기 전에 최종 응답을 생성할 수 있다(MAY).
이 요구 사항들은 HTTP에 대한 몇 가지 흔한 공격을 막기 위한 것이다. 관대하게 처리하면 구현이 이런 취약점에 노출될 수 있으므로, 이 요구 사항들은 의도적으로 엄격하다.
HTTP 필드([HTTP]의 5장)는 HTTP/2에서 HPACK [COMPRESSION]으로 압축되어 HEADERS, CONTINUATION, PUSH_PROMISE 프레임으로 전달된다.
HTTP/2 메시지를 구성할 때 필드 이름은 반드시 소문자로 바꿔야 한다(MUST).
HTTP의 필드 이름과 값의 정의는 HPACK이 전달할 수 있는 일부 문자를 금지한다. HTTP/2 구현은 필드 이름과 값을 각각 [HTTP]의 5.1절과 5.5절의 정의에 따라 검증하고, 금지된 문자가 든 메시지를 형식이 잘못된 것(8.1.1절)으로 처리해야 한다(SHOULD).
필드를 검증하지 않으면 요청 스머글링(request smuggling) 공격에 악용될 수 있다. 특히 검증하지 않은 필드는, 캐리지 리턴(CR), 라인 피드(LF), COLON 같은 문자를 구분자로 쓰는 HTTP/1.1 [HTTP/1.1]로 메시지를 전달할 때 공격을 가능하게 할 수도 있다. 구현은 필드 이름과 값에 대해 반드시 다음과 같은 최소한의 검증을 수행해야 한다(MUST).
필드 값은 ASCII 공백 문자(ASCII SP 또는 HTAB, 0x20 또는 0x09)로 시작하거나 끝나서는 안 된다(MUST NOT).
참고: [HTTP]의 5.1절과 5.5절의 정의에 따라 필드를 검증하는 구현은 필드 이름에 대문자가 들어 있지 않은지만 추가로 확인하면 된다.
이 조건 중 어느 하나라도 위반하는 필드가 든 요청이나 응답은 반드시 형식이 잘못된 것(8.1.1절)으로 처리해야 한다(MUST). 특히 메시지를 전달할 때 필드를 처리하지 않는 중개자는 위에 금지된 것으로 나열한 값이 든 필드를 전달해서는 안 된다(MUST NOT).
요청 메시지가 이 요구 사항 중 하나를 위반하면, 더 알맞은 상태 코드가 정의되어 있거나 상태 코드를 보낼 수 없는 경우(예: 오류가 트레일러 필드에서 발생해서)가 아니라면, 구현은 400 (Bad Request) 상태 코드([HTTP]의 15.5.1절 참고)를 생성해야 한다(SHOULD).
참고: 해당 필드의 정의에 따르면 유효하지 않은 필드 값이라도 요청을 형식이 잘못된 것으로 만들지는 않는다. 위의 요구 사항은 [HTTP]의 5장에 정의된 필드의 일반 구문에만 적용된다.
HTTP/2는 연결 전용 헤더 필드를 나타내는 데 Connection 헤더 필드([HTTP]의 7.6.1절)를 쓰지 않는다. 이 프로토콜에서는 연결 전용 메타데이터를 다른 수단으로 전달한다. 엔드포인트는 연결 전용 헤더 필드가 든 HTTP/2 메시지를 생성해서는 안 된다(MUST NOT). 여기에는 Connection 헤더 필드와, [HTTP]의 7.6.1절에서 연결 전용 의미를 가진다고 나열한 필드(즉, Proxy-Connection, Keep-Alive, Transfer-Encoding, Upgrade)가 포함된다. 연결 전용 헤더 필드가 든 메시지는 반드시 형식이 잘못된 것(8.1.1절)으로 처리해야 한다(MUST).
이에 대한 유일한 예외는 TE 헤더 필드로, HTTP/2 요청에 들어 있을 수 있다(MAY). 들어 있다면 그 값은 “trailers” 외의 값을 담아서는 안 된다(MUST NOT).
HTTP/1.x 메시지를 HTTP/2로 바꾸는 중개자는 [HTTP]의 7.6.1절에서 설명한 대로 연결 전용 헤더 필드를 반드시 제거해야 한다(MUST). 그러지 않으면 다른 HTTP/2 엔드포인트가 그 메시지를 형식이 잘못된 것(8.1.1절)으로 처리한다.
참고: HTTP/2는 의도적으로 다른 프로토콜로의 업그레이드를 지원하지 않는다. 3장에 설명된 핸드셰이크(handshake) 방법이면 다른 프로토콜의 사용을 협상하기에 충분하다고 여겨진다.
Cookie 헤더 필드 [COOKIE]는 쿠키 쌍(cookie-pair, 또는 “부스러기(crumbs)”)을 구분하는 데 세미콜론(“;”)을 쓴다. 이 헤더 필드에는 여러 값이 들어 있지만 구분자로 쉼표(COMMA, “,”)를 쓰지 않으므로, 쿠키 쌍을 여러 필드 줄로 나눠 보낼 수 없다([HTTP]의 5.2절 참고). 개별 쿠키 쌍을 갱신하면 HPACK 테이블에 저장된 해당 필드 줄이 무효가 되므로, 이는 압축 효율을 크게 떨어뜨릴 수 있다.
압축 효율을 높이기 위해, Cookie 헤더 필드는 각각 하나 이상의 쿠키 쌍을 담은 별도의 헤더 필드로 나눌 수 있다(MAY). 압축을 푼 뒤 Cookie 헤더 필드가 여럿이면, HTTP/1.1 연결이나 범용 HTTP 서버 애플리케이션 같은 HTTP/2가 아닌 맥락으로 넘기기 전에 2옥텟 구분자 0x3b, 0x20(ASCII 문자열 “; “)을 사용해 이를 반드시 옥텟 문자열 하나로 이어 붙여야 한다(MUST).
따라서 다음 두 Cookie 헤더 필드 목록은 의미상 같다.
cookie: a=b; c=d; e=f
cookie: a=b
cookie: c=d
cookie: e=f
HTTP/2는 메시지 제어 데이터([HTTP]의 6.2절 참고)를 전달하기 위해 ‘:’ 문자(ASCII 0x3a)로 시작하는 특별한 의사 헤더 필드를 사용한다.
의사 헤더 필드는 HTTP 헤더 필드가 아니다. 엔드포인트는 이 문서에 정의된 것이 아닌 의사 헤더 필드를 생성해서는 안 된다(MUST NOT). 확장이 추가 의사 헤더 필드의 사용을 협상할 수 있다는 점에 유의하라. 5.5절을 참고하라.
의사 헤더 필드는 그것이 정의된 맥락에서만 유효하다. 요청용으로 정의된 의사 헤더 필드는 응답에 나타나서는 안 된다(MUST NOT). 응답용으로 정의된 의사 헤더 필드는 요청에 나타나서는 안 된다(MUST NOT). 의사 헤더 필드는 트레일러 섹션에 나타나서는 안 된다(MUST NOT). 엔드포인트는 정의되지 않았거나 유효하지 않은 의사 헤더 필드가 든 요청이나 응답을 반드시 형식이 잘못된 것(8.1.1절)으로 처리해야 한다(MUST).
모든 의사 헤더 필드는 필드 블록에서 반드시 모든 일반 필드 줄보다 앞에 나타나야 한다(MUST). 필드 블록에서 일반 필드 줄 뒤에 나타나는 의사 헤더 필드가 든 요청이나 응답은 반드시 형식이 잘못된 것(8.1.1절)으로 처리해야 한다(MUST).
같은 의사 헤더 필드 이름이 필드 블록에 두 번 이상 나타나서는 안 된다(MUST NOT). 의사 헤더 필드 이름이 반복된 HTTP 요청이나 응답의 필드 블록은 반드시 형식이 잘못된 것(8.1.1절)으로 처리해야 한다(MUST).
HTTP/2 요청에는 다음 의사 헤더 필드가 정의되어 있다.
:method 의사 헤더 필드에는 HTTP 메서드([HTTP]의 9장)가 들어 있다.
:scheme 의사 헤더 필드에는 요청 대상(request target)의 스킴 부분이 들어 있다. 스킴은 요청을 직접 생성할 때는 대상 URI([RFC3986]의 3.1절)에서 가져오고, 변환된 요청이라면 그 요청의 스킴에서 가져온다(예: [HTTP/1.1]의 3.3절 참고). CONNECT 요청(8.5절)에서는 스킴을 생략한다.
:scheme은 “http”와 “https” 스킴의 URI로 제한되지 않는다. 프록시나 게이트웨이는 HTTP가 아닌 스킴에 대한 요청을 변환해, HTTP를 사용해 HTTP가 아닌 서비스와 상호 작용할 수 있게 할 수 있다.
:authority 의사 헤더 필드는 대상 URI([HTTP]의 7.1절)의 authority 부분([RFC3986]의 3.2절)을 전달한다. HTTP/2 요청의 수신자는 :authority가 있다면 대상 URI를 정하는 데 Host 헤더 필드를 써서는 안 된다(MUST NOT).
HTTP/2 요청을 직접 생성하는 클라이언트는, 전달할 authority 정보가 없는 경우(이때는 :authority를 생성해서는 안 된다(MUST NOT))가 아니라면, authority 정보를 전달하는 데 반드시 :authority 의사 헤더 필드를 사용해야 한다(MUST).
클라이언트는 :authority 의사 헤더 필드와 다른 Host 헤더 필드를 가진 요청을 생성해서는 안 된다(MUST NOT). 서버는 :authority 의사 헤더 필드의 엔터티와 다른 엔터티를 식별하는 Host 헤더 필드가 든 요청을 형식이 잘못된 것으로 처리해야 한다(SHOULD). 필드 값을 비교하려면 정규화할 필요가 있다([RFC3986]의 6.2절 참고). 원본 서버(origin server)는 어떤 정규화 방법이든 적용할 수 있지만, 다른 서버는 두 필드에 대해 반드시 스킴 기반 정규화([RFC3986]의 6.2.3절 참고)를 수행해야 한다(MUST).
HTTP/2로 요청을 전달하는 중개자는, 원래 요청의 대상 URI에 authority 정보가 없는 경우(이때는 :authority를 생성해서는 안 된다(MUST NOT))가 아니라면, 원래 요청의 제어 데이터에 있는 authority 정보를 사용해 반드시 :authority 의사 헤더 필드를 구성해야 한다(MUST). Host 헤더 필드가 이 정보의 유일한 원천은 아니라는 점에 유의하라. [HTTP]의 7.2절을 참고하라.
Host 헤더 필드를 생성할 필요가 있는(HTTP/1.1 요청을 구성하려면 필요할 수도 있다) 중개자는, 요청 대상도 바꾸는 경우가 아니라면, Host 필드의 값으로 반드시 :authority 의사 헤더 필드의 값을 사용해야 한다(MUST). 이는 HTTP 라우팅의 잠재적 취약점을 피하기 위해 기존 Host 필드를 대체한다.
HTTP/2로 요청을 전달하는 중개자는 기존 Host 헤더 필드를 유지할 수 있다(MAY).
CONNECT나 asterisk-form OPTIONS 요청의 요청 대상에는 authority 정보가 결코 들어 있지 않다는 점에 유의하라. [HTTP]의 7.1절과 7.2절을 참고하라.
:authority에는 “http”나 “https” 스킴의 URI에 대해 사용 중단된 userinfo 하위 구성 요소가 들어 있어서는 안 된다(MUST NOT).
:path 의사 헤더 필드에는 대상 URI의 경로와 쿼리 부분(absolute-path 생성 규칙과, 선택적으로 ‘?’ 문자 뒤에 오는 query 생성 규칙. [HTTP]의 4.1절 참고)이 들어 있다. asterisk-form 요청(OPTIONS용)은 :path 의사 헤더 필드에 값 ‘*‘를 담는다.
이 의사 헤더 필드는 “http”나 “https” URI에 대해 비어 있어서는 안 된다(MUST NOT). 경로 구성 요소가 없는 “http”나 “https” URI는 반드시 값 ‘/’를 담아야 한다(MUST). 이 규칙의 예외는 다음과 같다.
OPTIONS 요청. 이 요청은 반드시 값이 ‘*‘인 :path 의사 헤더 필드를 담아야 한다(MUST). [HTTP]의 7.1절을 참고하라.:path 의사 헤더 필드를 생략하는 CONNECT 요청(8.5절).역주: 정오표 9175(Reported, 미확인)는 바로 위
OPTIONS예외의 조건에 “쿼리 구성 요소도 없는”을 더해야 한다고 지적한다. 확인되지 않은 정오표이므로 번역에는 반영하지 않았다.
모든 HTTP/2 요청은 CONNECT 요청(8.5절)이 아니라면 :method, :scheme, :path 의사 헤더 필드에 대해 반드시 정확히 하나의 유효한 값을 담아야 한다(MUST). 필수 의사 헤더 필드를 빠뜨린 HTTP 요청은 형식이 잘못된 것(8.1.1절)이다.
개별 HTTP/2 요청에는 프로토콜 버전을 명시적으로 나타내는 표시가 없다. 모든 HTTP/2 요청은 암묵적으로 프로토콜 버전 “2.0”을 가진다([HTTP]의 6.2절 참고).
HTTP/2 응답에는 HTTP 상태 코드 필드([HTTP]의 15장 참고)를 담는 :status 의사 헤더 필드 하나가 정의되어 있다. 이 의사 헤더 필드는 중간 응답을 포함해 모든 응답에 반드시 들어 있어야 한다(MUST). 그렇지 않으면 그 응답은 형식이 잘못된 것(8.1.1절)이다.
HTTP/2 응답은 암묵적으로 프로토콜 버전 “2.0”을 가진다.
HTTP/2에서 서버는 클라이언트가 앞서 시작한 요청과 연관해, 응답을 (그에 대응하는 “약속된(promised)” 요청과 함께) 클라이언트에 선제적으로 보낼(즉, “푸시(push)”할) 수 있다.
서버 푸시는 서버가 받은 요청 뒤에 어떤 요청이 올지 예측하여 그 요청들에 필요한 왕복을 없앰으로써, 클라이언트가 체감하는 성능을 높일 수 있도록 설계되었다. 예를 들어 HTML 요청 뒤에는 흔히 그 페이지가 참조하는 스타일시트와 스크립트 요청이 이어진다. 이 요청들이 푸시되면, 클라이언트는 HTML에서 그 참조를 받기를 기다렸다가 따로 요청을 보낼 필요가 없다.
실제로 서버 푸시는 효과적으로 쓰기 어렵다. 서버가 캐싱, 콘텐츠 협상(content negotiation), 사용자 행동 같은 요인을 고려해 클라이언트가 보낼 추가 요청을 정확히 예상해야 하기 때문이다. 예측이 틀리면, 와이어상(on the wire)의 추가 데이터가 나타내는 기회비용 때문에 성능이 떨어질 수 있다. 특히 상당한 양의 데이터를 푸시하면 더 중요한 응답과 경합 문제가 생길 수 있다.
클라이언트는 서버 푸시를 끄도록 요청할 수 있지만, 이는 홉마다 따로 협상된다. SETTINGS_ENABLE_PUSH 설정을 0으로 하면 서버 푸시가 꺼졌음을 나타낸다.
약속된 요청은 반드시 안전하고(safe, [HTTP]의 9.2.1절 참고) 캐시 가능(cacheable, [HTTP]의 9.2.3절 참고)해야 한다(MUST). 약속된 요청에는 콘텐츠나 트레일러 섹션이 들어갈 수 없다. 캐시 가능하지 않거나, 안전하다고 알려지지 않았거나, 요청 콘텐츠가 있음을 나타내는 약속된 요청을 받은 클라이언트는, 반드시 PROTOCOL_ERROR 유형의 스트림 오류(5.4.2절)로 약속된 스트림을 리셋해야 한다(MUST). 클라이언트가 새로 정의된 메서드를 안전하다고 인식하지 못하면, 이 때문에 약속된 스트림이 리셋될 수 있다는 점에 유의하라.
캐시 가능한 푸시된 응답([CACHING]의 3장 참고)은 클라이언트가 HTTP 캐시를 구현한다면 저장할 수 있다. 푸시된 응답은 약속된 스트림 식별자가 식별하는 스트림이 아직 열려 있는 동안에는 원본 서버에서 성공적으로 검증된 것으로 간주한다(예: “no-cache” 캐시 응답 지시자가 있는 경우. [CACHING]의 5.2.2.4절 참고).
캐시 가능하지 않은 푸시된 응답은 어떤 HTTP 캐시에도 저장해서는 안 된다(MUST NOT). 이런 응답은 애플리케이션에 따로 제공할 수 있다(MAY).
서버는 :authority 의사 헤더 필드에 서버가 권한 있는(authoritative, 10.1절 참고) 값을 반드시 넣어야 한다(MUST). 클라이언트는 서버가 권한 있지 않은 PUSH_PROMISE를 반드시 PROTOCOL_ERROR 유형의 스트림 오류(5.4.2절)로 처리해야 한다(MUST).
중개자는 서버로부터 푸시를 받고, 그것을 클라이언트에 전달하지 않기로 할 수 있다. 다시 말해 푸시된 정보를 어떻게 쓸지는 그 중개자가 정한다. 마찬가지로 중개자는 서버가 아무 동작을 하지 않았는데도 클라이언트에 추가로 푸시하기로 할 수도 있다.
클라이언트는 푸시할 수 없다. 따라서 서버는 PUSH_PROMISE 프레임을 받는 것을 반드시 PROTOCOL_ERROR 유형의 연결 오류(5.4.1절)로 처리해야 한다(MUST). 서버는 SETTINGS_ENABLE_PUSH 설정을 0이 아닌 값으로 설정할 수 없다(6.5.2절 참고).
서버 푸시는 의미상 서버가 요청에 응답하는 것과 같다. 다만 이 경우 그 요청도 서버가 PUSH_PROMISE 프레임으로 보낸다.
PUSH_PROMISE 프레임에는 서버가 그 요청에 귀속시키는 제어 데이터와 완전한 요청 헤더 필드 집합을 담은 필드 블록이 들어 있다. 메시지 콘텐츠가 있는 요청에 대한 응답은 푸시할 수 없다.
약속된 요청은 언제나 클라이언트의 명시적인 요청과 연관된다. 서버가 보내는 PUSH_PROMISE 프레임은 그 명시적인 요청의 스트림에서 전송된다. PUSH_PROMISE 프레임에는 서버가 쓸 수 있는 스트림 식별자 중에서 고른 약속된 스트림 식별자도 들어 있다(5.1.1절 참고).
PUSH_PROMISE와 그 뒤에 오는 CONTINUATION 프레임의 헤더 필드는 반드시 유효하고 완전한 요청 헤더 필드 집합(8.3.1절)이어야 한다(MUST). 서버는 :method 의사 헤더 필드에 안전하고 캐시 가능한 메서드를 반드시 넣어야 한다(MUST). 클라이언트가 완전하고 유효한 헤더 필드 집합이 없거나 :method 의사 헤더 필드가 안전하지 않은 메서드를 나타내는 PUSH_PROMISE를 받으면, 반드시 약속된 스트림에서 PROTOCOL_ERROR 유형의 스트림 오류(5.4.2절)로 응답해야 한다(MUST).
서버는 약속된 응답을 참조하는 프레임을 보내기 전에 PUSH_PROMISE(6.6절) 프레임을 보내야 한다(SHOULD). 이렇게 하면 클라이언트가 PUSH_PROMISE 프레임을 받기 전에 요청을 보내는 경쟁 상태를 피할 수 있다.
예를 들어 서버가 여러 이미지 파일에 대한 링크가 포함된 문서의 요청을 받고 그 이미지들을 클라이언트에 푸시하기로 하면, 이미지 링크가 담긴 DATA 프레임보다 앞서 PUSH_PROMISE 프레임을 보내야 클라이언트가 포함된 링크를 발견하기 전에 리소스가 푸시될 것임을 알 수 있다. 마찬가지로 서버가 필드 블록에서(예: Link 헤더 필드에서) 참조하는 리소스를 푸시한다면, 헤더를 보내기 전에 PUSH_PROMISE를 보내야 클라이언트가 그 리소스를 요청하지 않는다.
클라이언트는 PUSH_PROMISE 프레임을 보내서는 안 된다(MUST NOT).
서버는 클라이언트가 시작한 어떤 스트림에서든 PUSH_PROMISE 프레임을 보낼 수 있지만, 그 스트림은 서버 입장에서 반드시 open 또는 half-closed (원격) 상태여야 한다(MUST). PUSH_PROMISE 프레임은 응답을 이루는 프레임 사이사이에 끼울 수 있지만, 필드 블록 하나를 이루는 HEADERS와 CONTINUATION 프레임 사이에는 끼울 수 없다.
PUSH_PROMISE 프레임을 보내면 새 스트림이 생기고, 그 스트림은 서버에서는 reserved (로컬) 상태, 클라이언트에서는 reserved (원격) 상태가 된다.
PUSH_PROMISE 프레임을 보낸 뒤, 서버는 약속된 스트림 식별자를 쓰는, 서버가 시작한 스트림에서 푸시된 응답을 응답(8.3.2절)으로 전달하기 시작할 수 있다. 서버는 이 스트림에서 8.1절에 정의된 것과 같은 프레임 시퀀스를 사용해 HTTP 응답을 전송한다. 첫 HEADERS 프레임을 보낸 뒤 이 스트림은 클라이언트 쪽에서 half-closed가 된다(5.1절).
PUSH_PROMISE 프레임을 받고 푸시된 응답을 받아들이기로 한 클라이언트는, 약속된 스트림이 닫힐 때까지 약속된 응답에 대한 요청을 보내지 않아야 한다(SHOULD NOT).
클라이언트가 어떤 이유로든 서버로부터 푸시된 응답을 받고 싶지 않다고 판단하거나, 서버가 약속된 응답을 보내기 시작하는 데 너무 오래 걸리면, 클라이언트는 CANCEL 또는 REFUSED_STREAM 코드를 쓰고 푸시된 스트림의 식별자를 가리키는 RST_STREAM 프레임을 보낼 수 있다.
클라이언트는 SETTINGS_MAX_CONCURRENT_STREAMS 설정을 사용해 서버가 동시에 푸시할 수 있는 응답의 수를 제한할 수 있다. SETTINGS_MAX_CONCURRENT_STREAMS 값으로 0을 알리면 서버가 응답을 푸시하는 데 필요한 스트림을 열지 못한다. 그러나 예약된 스트림은 동시 스트림 한도에 포함하지 않으므로, 이것으로 서버가 PUSH_PROMISE 프레임으로 스트림을 예약하는 것까지 막지는 못한다. 푸시된 리소스를 받고 싶지 않은 클라이언트는 원하지 않는 예약된 스트림을 리셋하거나 SETTINGS_ENABLE_PUSH를 0으로 설정할 필요가 있다.
푸시된 응답을 받은 클라이언트는 반드시 서버가 권한 있거나(10.1절 참고), 푸시된 응답을 제공한 프록시가 해당 요청에 맞게 설정되어 있음을 검증해야 한다(MUST). 예를 들어 example.com DNS-ID([RFC6125] 참고)에 대해서만 인증서를 제시하는 서버는 https://www.example.org/doc에 대한 응답을 푸시하는 것이 허용되지 않는다.
PUSH_PROMISE 스트림의 응답은 HEADERS 프레임으로 시작하는데, 이 프레임은 곧바로 스트림을 서버에서는 half-closed (원격) 상태로, 클라이언트에서는 half-closed (로컬) 상태로 만든다. 응답은 END_STREAM 플래그가 설정된 프레임으로 끝나며, 이 프레임은 스트림을 closed 상태로 만든다.
참고: 클라이언트는 서버 푸시에 대해
END_STREAM플래그가 설정된 프레임을 결코 보내지 않는다.
CONNECT 메서드([HTTP]의 9.3.6절)는 HTTP 연결을 원격 호스트로의 터널로 바꾸는 데 쓰인다. CONNECT는 주로 “https” 리소스와 상호 작용하기 위해 HTTP 프록시와 함께 원본 서버와의 TLS 세션을 맺는 데 쓰인다.
HTTP/2에서 CONNECT 메서드는 연결 전체를 터널로 바꾸는 것이 아니라, HTTP/2 스트림 하나 위에 원격 호스트로의 터널을 맺는다. CONNECT 헤더 섹션은 8.3.1절(“요청 의사 헤더 필드”)에 정의된 대로 구성하되 몇 가지가 다르다. 구체적으로는 다음과 같다.
:method 의사 헤더 필드는 CONNECT로 설정한다.:scheme과 :path 의사 헤더 필드는 반드시 생략해야 한다(MUST).:authority 의사 헤더 필드에는 연결할 호스트와 포트가 들어 있다(CONNECT 요청의 요청 대상의 authority-form과 같다. [HTTP/1.1]의 3.2.3절 참고).이 제한을 따르지 않는 CONNECT 요청은 형식이 잘못된 것(8.1.1절)이다.
CONNECT를 지원하는 프록시는 :authority 의사 헤더 필드가 식별하는 호스트와 포트로 TCP 연결 [TCP]을 맺는다. 이 연결이 성공적으로 맺어지면, 프록시는 [HTTP]의 9.3.6절에 정의된 대로 2xx 계열 상태 코드를 담은 HEADERS 프레임을 클라이언트에 보낸다.
각 피어가 보낸 첫 HEADERS 프레임 뒤에 오는 모든 DATA 프레임은 TCP 연결에서 보낸 데이터에 대응한다. 클라이언트가 보낸 DATA 프레임의 프레임 페이로드는 프록시가 TCP 서버로 전송하고, TCP 서버로부터 받은 데이터는 프록시가 DATA 프레임으로 조립한다. DATA나 스트림 관리 프레임(RST_STREAM, WINDOW_UPDATE, PRIORITY)이 아닌 프레임 유형은 연결된 스트림에서 보내서는 안 되며(MUST NOT), 받으면 반드시 스트림 오류(5.4.2절)로 처리해야 한다(MUST).
TCP 연결은 어느 피어든 닫을 수 있다. DATA 프레임의 END_STREAM 플래그는 TCP FIN 비트와 같은 것으로 처리된다. 클라이언트는 END_STREAM 플래그가 설정된 프레임을 받은 뒤 END_STREAM 플래그가 설정된 DATA 프레임을 보낼 것으로 기대된다. END_STREAM 플래그가 설정된 DATA 프레임을 받은 프록시는 첨부된 데이터를 마지막 TCP 세그먼트에 FIN 비트를 설정해 보낸다. FIN 비트가 설정된 TCP 세그먼트를 받은 프록시는 END_STREAM 플래그가 설정된 DATA 프레임을 보낸다. 마지막 TCP 세그먼트나 DATA 프레임은 비어 있을 수 있다는 점에 유의하라.
TCP 연결 오류는 RST_STREAM으로 알린다. 프록시는 RST 비트가 설정된 TCP 세그먼트를 받는 것을 포함해 TCP 연결의 모든 오류를 CONNECT_ERROR 유형의 스트림 오류(5.4.2절)로 처리한다. 마찬가지로 프록시는 스트림이나 HTTP/2 연결에서 오류를 감지하면 RST 비트를 설정한 TCP 세그먼트를 반드시 보내야 한다(MUST).
HTTP/2는 101 (Switching Protocols) 정보 상태 코드([HTTP]의 15.2.2절)를 지원하지 않는다.
101 (Switching Protocols)의 의미는 다중화된 프로토콜에 적용할 수 없다. 비슷한 기능은 확장 CONNECT [RFC8441]으로 가능해질 수도 있으며, 다른 프로토콜도 HTTP/2와 같은 메커니즘을 써서 그 프로토콜의 사용을 협상할 수 있다(3장 참고).
일반적으로 HTTP 클라이언트는 오류가 발생했을 때 멱등이 아닌 요청을 재시도할 수 없다. 오류의 성격을 알아낼 수단이 없기 때문이다([HTTP]의 9.2.2절 참고). 오류 전에 서버에서 일부 처리가 이루어졌을 수 있으며, 요청을 다시 시도하면 바람직하지 않은 결과가 생길 수 있다.
HTTP/2는 요청이 처리되지 않았음을 클라이언트에 보장하는 두 가지 메커니즘을 제공한다.
GOAWAY 프레임은 처리되었을 수 있는 가장 큰 스트림 번호를 나타낸다. 따라서 그보다 번호가 큰 스트림의 요청은 안전하게 재시도할 수 있음이 보장된다.REFUSED_STREAM 오류 코드를 RST_STREAM 프레임에 넣어, 어떤 처리도 일어나기 전에 스트림이 닫히고 있음을 나타낼 수 있다. 리셋된 스트림에서 보낸 요청은 모두 안전하게 재시도할 수 있다.처리되지 않은 요청은 실패한 것이 아니다. 클라이언트는 멱등이 아닌 메서드의 요청이라도 이를 자동으로 재시도할 수 있다(MAY).
서버는 스트림이 처리되지 않았다는 사실을 보장할 수 없다면, 그 스트림이 처리되지 않았다고 나타내서는 안 된다(MUST NOT). 어떤 스트림의 프레임이 애플리케이션 계층으로 전달되었다면, 그 스트림에 REFUSED_STREAM을 써서는 안 되며(MUST NOT), GOAWAY 프레임에는 반드시 그 스트림 식별자 이상의 스트림 식별자를 넣어야 한다(MUST).
이 메커니즘들에 더해, PING 프레임은 클라이언트가 연결을 쉽게 시험해 볼 수 있는 방법을 제공한다. 일부 미들박스(middlebox, 예: 네트워크 주소 변환기나 로드 밸런서)가 연결 바인딩을 조용히 버리므로, 유휴 상태로 남아 있는 연결은 끊어질 수 있다. PING 프레임을 쓰면 클라이언트는 요청을 보내지 않고도 연결이 아직 활성 상태인지 안전하게 시험해 볼 수 있다.
이 절은 HTTP/1.1 요청과 응답을, 그와 같은 HTTP/2 요청과 응답의 예시와 함께 보여 준다.
HTTP GET 요청에는 제어 데이터와 요청 헤더가 있고 메시지 콘텐츠는 없으므로, 직렬화된 요청 헤더 필드 블록을 담은 HEADERS 프레임 하나(뒤에 CONTINUATION 프레임이 0개 이상 올 수 있다)로 전송된다. 다음 예의 HEADERS 프레임에는 END_HEADERS와 END_STREAM 플래그가 모두 설정되어 있으며, CONTINUATION 프레임은 보내지 않는다.
GET /resource HTTP/1.1 HEADERS
Host: example.org ==> + END_STREAM
Accept: image/jpeg + END_HEADERS
:method = GET
:scheme = https
:authority = example.org
:path = /resource
host = example.org
accept = image/jpeg
마찬가지로 제어 데이터와 응답 헤더만 있는 응답은, 직렬화된 응답 헤더 필드 블록을 담은 HEADERS 프레임(역시 뒤에 CONTINUATION 프레임이 0개 이상 올 수 있다)으로 전송된다.
HTTP/1.1 304 Not Modified HEADERS
ETag: "xyzzy" ==> + END_STREAM
Expires: Thu, 23 Jan ... + END_HEADERS
:status = 304
etag = "xyzzy"
expires = Thu, 23 Jan ...
제어 데이터, 요청 헤더, 메시지 콘텐츠가 있는 HTTP POST 요청은 HEADERS 프레임 하나, 그 뒤에 요청 헤더를 담은 CONTINUATION 프레임 0개 이상, 그 뒤에 DATA 프레임 하나 이상으로 전송되며, 마지막 CONTINUATION(또는 HEADERS) 프레임에는 END_HEADERS 플래그가, 마지막 DATA 프레임에는 END_STREAM 플래그가 설정된다.
POST /resource HTTP/1.1 HEADERS
Host: example.org ==> - END_STREAM
Content-Type: image/jpeg - END_HEADERS
Content-Length: 123 :method = POST
:authority = example.org
:path = /resource
{binary data} :scheme = https
CONTINUATION
+ END_HEADERS
content-type = image/jpeg
host = example.org
content-length = 123
DATA
+ END_STREAM
{binary data}
어떤 필드 줄을 이루는 데이터가 여러 필드 블록 조각에 나뉘어 있을 수 있다는 점에 유의하라. 이 예에서 필드 줄을 프레임에 배치한 방식은 예시일 뿐이다.
제어 데이터, 응답 헤더, 메시지 콘텐츠가 있는 응답은 HEADERS 프레임, 그 뒤에 CONTINUATION 프레임 0개 이상, 그 뒤에 DATA 프레임 하나 이상으로 전송되며, 시퀀스의 마지막 DATA 프레임에는 END_STREAM 플래그가 설정된다.
HTTP/1.1 200 OK HEADERS
Content-Type: image/jpeg ==> - END_STREAM
Content-Length: 123 + END_HEADERS
:status = 200
{binary data} content-type = image/jpeg
content-length = 123
DATA
+ END_STREAM
{binary data}
101이 아닌 1xx 상태 코드를 쓰는 정보 응답은 HEADERS 프레임과, 그 뒤에 오는 CONTINUATION 프레임 0개 이상으로 전송된다.
트레일러 섹션은 요청이나 응답의 필드 블록과 모든 DATA 프레임을 보낸 뒤에 필드 블록으로 보낸다. 트레일러 섹션을 이루는 필드 블록을 시작하는 HEADERS 프레임에는 END_STREAM 플래그가 설정된다.
다음 예에는 Expect 헤더 필드에 “100-continue” 토큰이 든 요청에 대한 응답으로 보내는 100 (Continue) 상태 코드와 트레일러 섹션이 모두 들어 있다.
HTTP/1.1 100 Continue HEADERS
Extension-Field: bar ==> - END_STREAM
+ END_HEADERS
:status = 100
extension-field = bar
HTTP/1.1 200 OK HEADERS
Content-Type: image/jpeg ==> - END_STREAM
Transfer-Encoding: chunked + END_HEADERS
Trailer: Foo :status = 200
content-type = image/jpeg
123 trailer = Foo
{binary data}
0 DATA
Foo: bar - END_STREAM
{binary data}
HEADERS
+ END_STREAM
+ END_HEADERS
foo = bar
이 절은 상호 운용성을 높이고, 알려진 보안 취약점에 대한 노출을 줄이고, 구현 간의 차이가 생길 가능성을 줄이는 HTTP의 특성을 설명한다.
HTTP/2 연결은 지속 연결이다. 최상의 성능을 위해, 클라이언트는 서버와 더 이상 통신할 필요가 없다고 판단될 때(예: 사용자가 특정 웹 페이지를 떠날 때)나 서버가 연결을 닫을 때까지 연결을 닫지 않을 것으로 기대된다.
클라이언트는 주어진 호스트와 포트 쌍에 대해 HTTP/2 연결을 둘 이상 열지 않아야 한다(SHOULD NOT). 여기서 호스트는 URI, 선택된 대체 서비스(alternative service) [ALT-SVC], 또는 설정된 프록시에서 얻는다.
클라이언트는 대체용으로 연결을 더 만들 수 있다. 사용 가능한 스트림 식별자 공간을 거의 소진한 연결을 대체하거나(5.1.1절), TLS 연결의 키 재료(keying material)를 갱신하거나, 오류를 만난 연결을 대체하기 위해서다(5.4.1절).
클라이언트는 서로 다른 Server Name Indication [TLS-EXT] 값을 쓰거나 서로 다른 TLS 클라이언트 인증서를 제공하기 위해 같은 IP 주소와 TCP 포트로 여러 연결을 열 수 있지만(MAY), 같은 구성으로 여러 연결을 만드는 것은 피해야 한다(SHOULD).
서버가 열린 연결을 가능한 한 오래 유지하기를 권장하지만, 필요하면 서버는 유휴 연결을 종료해도 된다. 어느 엔드포인트든 전송 계층 TCP 연결을 닫기로 하면, 종료하는 엔드포인트는 두 엔드포인트가 앞서 보낸 프레임이 처리되었는지를 확실히 판단하고 남은 필요한 작업을 우아하게 완료하거나 종료할 수 있도록, 먼저 GOAWAY(6.8절) 프레임을 보내야 한다(SHOULD).
원본 서버로 직접 맺었거나 CONNECT 메서드(8.5절)로 만든 터널을 통해 맺은 연결은, URI authority 구성 요소가 서로 다른 여러 요청에 재사용할 수 있다(MAY). 원본 서버가 권한 있는 한(10.1절) 연결을 재사용할 수 있다. TLS를 쓰지 않는 TCP 연결에서는 호스트가 같은 IP 주소로 해석되었는지에 달려 있다.
“https” 리소스의 경우, 연결 재사용은 추가로 URI의 호스트에 대해 유효한 인증서를 가지고 있는지에 달려 있다. 서버가 제시한 인증서는 클라이언트가 URI의 호스트에 대해 새 TLS 연결을 맺을 때 수행할 모든 검사를 반드시 통과해야 한다(MUST). 인증서 하나로 여러 출처(origin)에 대한 권한(authority)을 확립할 수 있다. 클라이언트가 서버가 어떤 URI에 대해 권한 있는지 판단하는 방법은 [HTTP]의 4.3절에 설명되어 있다.
일부 배포에서는 연결을 여러 출처에 재사용하면 요청이 잘못된 원본 서버로 향할 수 있다. 예를 들어 원본 서버를 고르는 데 TLS Server Name Indication [TLS-EXT] 확장을 쓰는 미들박스가 TLS 터미네이션(TLS termination)을 수행할 수도 있다. 이는 서버가 다른 면에서는 권한 있더라도, 클라이언트가 그 요청의 의도된 대상이 아닐 수 있는 서버로 요청을 보낼 수 있다는 뜻이다.
클라이언트가 연결을 재사용하지 않기를 바라는 서버는 요청에 대한 응답으로 421 (Misdirected Request) 상태 코드를 보내, 그 요청에 대해 권한 있지 않음을 나타낼 수 있다([HTTP]의 15.5.20절 참고).
HTTP/2로 프록시를 쓰도록 설정된 클라이언트는 연결 하나를 통해 그 프록시로 요청을 보낸다. 즉, 프록시를 거쳐 보내는 모든 요청은 프록시로의 연결을 재사용한다.
TLS 위의 HTTP/2 구현은 반드시 TLS 버전 1.2 [TLS12] 이상을 사용해야 한다(MUST). [TLSBCP]의 일반적인 TLS 사용 지침을 따라야 하며(SHOULD), HTTP/2에 고유한 몇 가지 제한이 더해진다.
TLS 구현은 TLS의 Server Name Indication(SNI) [TLS-EXT] 확장을 반드시 지원해야 한다(MUST). 서버가 도메인 이름 [DNS-TERMS]으로 식별된다면, 대상 호스트를 나타내는 다른 메커니즘을 쓰는 경우가 아니라면 클라이언트는 server_name TLS 확장을 반드시 보내야 한다(MUST).
TLS 1.3 [TLS13]을 협상하는 HTTP/2 배포의 요구 사항은 9.2.3절에 있다. TLS 1.2 배포는 9.2.1절과 9.2.2절의 요구 사항을 따른다. 구현이 요구 사항을 따르는 기본값을 제공하기를 권장하지만, 궁극적으로 준수할 책임은 배포에 있다고 본다.
이 절은 HTTP/2와 함께 쓸 수 있는 TLS 1.2 기능 집합에 대한 제한을 설명한다. 배포상의 한계 때문에, 이 제한을 충족하지 않을 때 TLS 협상을 실패시키는 것이 불가능할 수도 있다. 엔드포인트는 이 TLS 요구 사항을 충족하지 않는 HTTP/2 연결을 INADEQUATE_SECURITY 유형의 연결 오류(5.4.1절)로 즉시 종료할 수 있다(MAY).
TLS 1.2 위의 HTTP/2 배포는 압축을 반드시 꺼야 한다(MUST). TLS 압축은 그렇지 않았다면 드러나지 않았을 정보를 노출할 수 있다 [RFC3749]. HTTP/2가 맥락을 더 잘 알고 있어 성능, 보안 등의 이유로 더 적합할 가능성이 높은 압축 기능을 제공하므로, 범용 압축은 필요하지 않다.
TLS 1.2 위의 HTTP/2 배포는 재협상(renegotiation)을 반드시 꺼야 한다(MUST). 엔드포인트는 TLS 재협상을 반드시 PROTOCOL_ERROR 유형의 연결 오류(5.4.1절)로 처리해야 한다(MUST). 재협상을 끄면, 기반 암호 스위트(cipher suite)가 암호화할 수 있는 메시지 수의 한도 때문에 오래 유지되는 연결을 쓸 수 없게 될 수 있다는 점에 유의하라.
엔드포인트는 핸드셰이크에서 제시하는 클라이언트 자격 증명에 기밀성 보호를 제공하기 위해 재협상을 쓸 수 있지만(MAY), 재협상은 반드시 연결 프리페이스를 보내기 전에 일어나야 한다(MUST). 서버는 연결을 맺은 직후 재협상 요청을 보면 클라이언트 인증서를 요청해야 한다(SHOULD).
이는 사실상 특정 보호 리소스에 대한 요청에 대응해 재협상을 쓰는 것을 막는다. 향후 명세가 이 사용 사례를 지원하는 방법을 제공할 수도 있다. 또는 서버가 HTTP_1_1_REQUIRED 유형의 오류(5.4절)를 사용해 클라이언트에게 재협상을 지원하는 프로토콜을 쓰도록 요청할 수도 있다.
구현은 임시(ephemeral) 유한체(finite field) Diffie-Hellman(DHE)을 쓰는 암호 스위트([TLS12]의 8.1.2절)에 대해 최소 2048비트, 임시 타원 곡선(elliptic curve) Diffie-Hellman(ECDHE)을 쓰는 암호 스위트 [RFC8422]에 대해 최소 224비트의 임시 키 교환 크기를 반드시 지원해야 한다(MUST). 클라이언트는 4096비트까지의 DHE 크기를 반드시 받아들여야 한다(MUST). 엔드포인트는 하한보다 작은 키 크기의 협상을 INADEQUATE_SECURITY 유형의 연결 오류(5.4.1절)로 처리할 수 있다(MAY).
TLS 1.2 위의 HTTP/2 배포는 부록 A에 나열된 금지된 암호 스위트를 사용하지 않아야 한다(SHOULD NOT).
금지된 암호 스위트 중 하나가 협상되면, 엔드포인트는 INADEQUATE_SECURITY 유형의 연결 오류(5.4.1절)를 생성하기로 할 수 있다(MAY). 금지된 암호 스위트를 쓰기로 한 배포는, 잠재적인 피어 집합이 그 암호 스위트를 받아들인다고 알려진 경우가 아니라면 연결 오류를 일으킬 위험을 진다.
구현은 금지되지 않은 암호 스위트의 협상에 대한 반응으로 이 오류를 생성해서는 안 된다(MUST NOT). 따라서 클라이언트가 금지되지 않은 암호 스위트를 제시할 때는 그 암호 스위트를 HTTP/2와 함께 쓸 준비가 되어 있어야 한다.
금지된 암호 스위트 목록에는 TLS 1.2가 필수로 정한 암호 스위트가 포함되어 있으므로, TLS 1.2 배포끼리 허용된 암호 스위트 집합이 서로 겹치지 않을 수 있다. TLS 핸드셰이크 실패를 일으키는 이 문제를 피하기 위해, TLS 1.2를 쓰는 HTTP/2 배포는 P-256 타원 곡선 [RFC8422]과 함께 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 [TLS-ECDHE]를 반드시 지원해야 한다(MUST).
클라이언트는 HTTP/2를 지원하지 않는 서버와도 연결할 수 있도록 금지된 암호 스위트를 지원한다고 알릴 수도 있다는 점에 유의하라. 이렇게 하면 서버는 HTTP/2에서 금지된 암호 스위트와 함께 HTTP/1.1을 선택할 수 있다. 그러나 애플리케이션 프로토콜과 암호 스위트를 독립적으로 고르면, 이 때문에 금지된 암호 스위트로 HTTP/2가 협상될 수 있다.
TLS 1.3에는 이전 버전에 없던 기능이 여럿 있다. 이 절은 그 기능들의 사용을 다룬다.
HTTP/2 서버는 핸드셰이크 후의 TLS 1.3 CertificateRequest 메시지를 보내서는 안 된다(MUST NOT). HTTP/2 클라이언트는 핸드셰이크 후의 TLS CertificateRequest 메시지를 반드시 PROTOCOL_ERROR 유형의 연결 오류(5.4.1절)로 처리해야 한다(MUST).
핸드셰이크 후 인증의 금지는 클라이언트가 “post_handshake_auth” TLS 확장을 제시했더라도 적용된다. 핸드셰이크 후 인증 지원은 ALPN [TLS-ALPN]과 독립적으로 알릴 수도 있다. 클라이언트는 다른 프로토콜에서 쓰기 위해 이 기능을 제시할 수도 있지만, 이 확장을 포함했다고 해서 HTTP/2 안에서 지원한다는 뜻이 될 수는 없다.
[TLS13]은 핸드셰이크 후 메시지로 NewSessionTicket과 KeyUpdate도 정의하는데, 이들은 HTTP/2와 직접 상호 작용하지 않으므로 쓸 수 있다. 새 유형의 TLS 메시지 사용이 애플리케이션 계층 프로토콜과의 상호 작용에 의존하지 않는다면, 그 TLS 메시지는 핸드셰이크가 완료된 뒤에 보낼 수 있다.
[RFC8470]의 지침을 지킨다면, 요청을 보내는 데 TLS 조기 데이터(early data)를 쓸 수 있다(MAY). 클라이언트는 모든 서버 설정의 초깃값을 가정하고 조기 데이터로 요청을 보낸다.
이 프로토콜의 여러 보안 특성을 제공하려면 TLS를 사용할 필요가 있다. 9.2절에 설명된 대로 TLS를 사용하지 않으면 이 절의 주장 가운데 많은 것이 성립하지 않는다.
HTTP/2는 서버가 주어진 응답을 제공하는 데 권한 있는지 판단할 때 HTTP의 권한 정의에 의존한다([HTTP]의 4.3절 참고). 이는 “http” URI 스킴에 대해서는 로컬 이름 해석에, “https” 스킴에 대해서는 인증된 서버 신원에 의존한다.
교차 프로토콜 공격(cross-protocol attack)에서 공격자는 클라이언트가 한 프로토콜로, 다른 프로토콜을 이해하는 서버를 향해 트랜잭션을 시작하게 만든다. 공격자는 그 트랜잭션이 두 번째 프로토콜에서 유효한 트랜잭션으로 보이게 만들 수도 있다. 이를 웹 맥락의 기능과 결합하면, 사설 네트워크에서 보호가 허술한 서버와 상호 작용하는 데 쓸 수 있다.
HTTP/2에 대한 ALPN 식별자로 TLS 핸드셰이크를 완료하는 것은 교차 프로토콜 공격에 대한 충분한 보호로 볼 수 있다. ALPN은 서버가 HTTP/2로 진행할 의향이 있다는 긍정적인 표시를 제공하므로, TLS 기반의 다른 프로토콜에 대한 공격을 막는다.
TLS의 암호화는 평문 프로토콜에 대한 교차 프로토콜 공격에 쓰일 수 있는 데이터를 공격자가 제어하기 어렵게 만든다.
HTTP/2의 평문 버전은 교차 프로토콜 공격에 대한 보호가 최소한에 그친다. 연결 프리페이스(3.4절)에는 HTTP/1.1 서버를 혼란시키도록 설계된 문자열이 들어 있지만, 다른 프로토콜에 대해서는 특별한 보호를 제공하지 않는다.
HPACK은 다른 HTTP 버전에서 구분자로 처리될 수 있는 필드 이름과 값을 인코딩하는 것을 허용한다. HTTP/2 요청이나 응답을 변환하는 중개자는 메시지를 다른 HTTP 버전으로 변환하기 전에 8.2절의 규칙에 따라 필드를 반드시 검증해야 한다(MUST). 유효하지 않은 구분자가 든 필드를 변환하면 수신자가 메시지를 잘못 해석하게 만들 수 있으며, 공격자가 이를 악용할 수 있다.
8.2절에는 의사 헤더 필드의 검증에 대한 구체적인 규칙이 없다. 이 필드들의 값을 사용한다면 추가 검증이 필요하다. 이는 :scheme, :authority, :path를 결합해 URI 문자열 하나 [RFC3986]를 만드는 경우 특히 중요하다. 그 URI나 :path만을 :method와 결합해 요청 줄(request line)을 구성할 때([HTTP/1.1]의 3장에서처럼)도 비슷한 문제가 생길 수도 있다. 입력 값을 완전히 검증하지 않는 한 단순히 이어 붙이는 것은 안전하지 않다.
중개자는 다른 이유로도, 특히 [HTTP]의 5장에 있는 HTTP ABNF 문법을 따르지 않는 필드처럼, 유효하지 않은 필드 이름이나 값이 든 필드를 거부할 수 있다. 8.2절이 요구하는 최소한의 검증 외에는 필드를 검증하지 않는 중개자는 유효하지 않은 필드 이름이나 값이 든 메시지를 전달할 수도 있다.
전달하기 전에 제거해야 하는 필드([HTTP]의 7.6.1절 참고)를 받은 중개자는 메시지를 전달할 때 그 헤더 필드를 반드시 제거하거나 대체해야 한다(MUST). 또한 중개자는 Content-Length 필드가 든 메시지를 전달할 때 메시지가 형식이 올바른지(8.1.1절) 주의를 기울일 필요가 있다. 이렇게 하면 메시지가 어느 시점에 HTTP/1.1로 변환되더라도 프레이밍이 올바르다.
푸시된 응답에는 클라이언트의 명시적인 요청이 없다. 요청은 서버가 PUSH_PROMISE 프레임으로 제공한다.
푸시된 응답은 원본 서버가 Cache-Control 헤더 필드로 제공하는 지침에 따라 캐싱할 수 있다. 그러나 서버 하나가 둘 이상의 테넌트(tenant)를 호스팅하면 문제가 생길 수 있다. 예를 들어 서버가 자기 URI 공간의 작은 부분을 여러 사용자에게 각각 제공할 수도 있다.
여러 테넌트가 같은 서버의 공간을 공유하는 경우, 그 서버는 테넌트가 자신이 권한을 갖지 않은 리소스의 표현을 푸시할 수 없도록 반드시 보장해야 한다(MUST). 이를 강제하지 않으면, 한 테넌트가 캐시에서 제공될 표현을 제공해 권한 있는 테넌트가 제공하는 실제 표현보다 우선하게 만들 수 있다.
원본 서버가 권한 있지 않은 푸시된 응답(10.1절 참고)은 사용하거나 캐싱해서는 안 된다(MUST NOT).
HTTP/2 연결은 동작하는 데 HTTP/1.1 연결보다 더 많은 자원 할당을 요구할 수 있다. 필드 섹션 압축과 흐름 제어 모두 더 많은 상태의 할당에 의존한다. 이 기능들에 대한 설정은 이 기능들을 위한 메모리 할당이 엄격하게 제한되도록 보장한다.
PUSH_PROMISE 프레임의 수는 같은 방식으로 제한되지 않는다. 서버 푸시를 받아들이는 클라이언트는 reserved (원격) 상태에 있을 수 있는 스트림의 수를 제한해야 한다(SHOULD). 지나치게 많은 서버 푸시 스트림은 ENHANCE_YOUR_CALM 유형의 스트림 오류(5.4.2절)로 처리할 수 있다.
여러 HTTP/2 구현이 서비스 거부(denial of service)에 취약한 것으로 드러났다 [NFLX-2019-002]. 다음은 구현이 서비스 거부 공격을 당할 수 있는 알려진 방식의 목록이다.
WINDOW_UPDATE 프레임으로 흐름 제어 증가분을 아주 작게 주면 송신자가 많은 수의 DATA 프레임을 생성하게 만들 수 있다.PING 프레임에 응답할 의무가 있다.SETTINGS 프레임마다 확인 응답이 필요하다.RST_STREAM 프레임을 보내게 만들 수 있다.DATA나 CONTINUATION 프레임을 보내는 것처럼 작은 프레임의 일부 용도는 전적으로 정당하므로 여기서는 주의가 필요하다.SETTINGS 프레임도 피어가 처리 시간을 더 쓰게 만드는 데 악용될 수도 있다. 의미 없이 설정을 바꾸거나, 정의되지 않은 설정을 여러 개 보내거나, 같은 프레임에서 같은 설정을 여러 번 바꾸는 방식이 있을 수 있다.PRIORITY 프레임으로 우선순위 재지정을 처리하는 데는 상당한 처리 시간이 필요할 수 있으며, 많은 PRIORITY 프레임을 보내면 과부하로 이어질 수 있다.SETTINGS의 한도는 즉시 줄일 수 없으므로, 엔드포인트는 새 한도를 넘을 수 있는 피어의 동작에 노출된다. 특히 연결을 맺은 직후에는 서버가 설정한 한도를 클라이언트가 알지 못하므로, 명백한 프로토콜 위반 없이 그 한도를 넘을 수 있다.서비스 거부에 악용될 수 있는 기능 대부분(SETTINGS 변경, 작은 프레임, 필드 섹션 압축 등)에는 정당한 용도가 있다. 이 기능들은 불필요하게 또는 지나치게 쓰일 때만 부담이 된다.
이 기능들의 사용을 감시하지 않는 엔드포인트는 서비스 거부의 위험에 스스로를 노출한다. 구현은 이 기능들의 사용을 추적하고 사용 한도를 정해야 한다(SHOULD). 엔드포인트는 의심스러운 활동을 ENHANCE_YOUR_CALM 유형의 연결 오류(5.4.1절)로 처리할 수 있다(MAY).
큰 필드 블록(4.3절)은 구현이 많은 양의 상태를 할당하게 만들 수 있다. 라우팅에 중요한 필드 줄이 필드 블록의 끝 쪽에 나타날 수 있어서, 필드를 최종 목적지로 스트리밍하지 못하게 된다. 이런 순서 문제와 캐시 정확성 보장 같은 다른 이유 때문에, 엔드포인트는 필드 블록 전체를 버퍼에 담을 필요가 있을 수도 있다. 필드 블록의 크기에는 엄격한 한도가 없으므로, 일부 엔드포인트에서는 필드 블록에 가용 메모리의 상당 부분을 할당해야 하는 상황이 생길 수 있다.
엔드포인트는 SETTINGS_MAX_HEADER_LIST_SIZE를 사용해, 압축되지 않은 필드 블록의 크기에 적용될 수 있는 한도를 피어에게 알릴 수 있다. 이 설정은 권고일 뿐이므로, 엔드포인트는 이 한도를 넘는 필드 블록을 보내고 요청이나 응답이 형식이 잘못된 것으로 처리될 위험을 감수하기로 할 수 있다(MAY). 이 설정은 연결마다 따로 적용되므로, 어떤 요청이나 응답이든 더 낮은 알 수 없는 한도를 가진 홉을 만날 수 있다. 중개자는 서로 다른 피어가 제시한 값을 전달해 이 문제를 피하려 시도할 수 있지만, 그럴 의무는 없다.
처리하려는 것보다 큰 필드 블록을 받은 서버는 HTTP 431 (Request Header Fields Too Large) 상태 코드 [RFC6585]를 보낼 수 있다. 클라이언트는 처리할 수 없는 응답을 버릴 수 있다. 연결이 닫히는 경우가 아니라면, 일관된 연결 상태를 보장하기 위해 엔드포인트는 필드 블록을 반드시 처리해야 한다(MUST).
스트림 생성은 TCP 연결의 생성·유지에 비해 비용이 비교적 적으므로, CONNECT 메서드는 프록시에 불균형한 부하를 만드는 데 쓰일 수 있다. 또한 나가는 TCP 연결이 TIME_WAIT 상태로 남으므로, 프록시는 CONNECT 요청을 담은 스트림이 닫힌 뒤에도 그 TCP 연결을 위한 자원을 일부 유지할 수도 있다. 따라서 프록시는 CONNECT 요청이 소비하는 자원을 제한하는 데 SETTINGS_MAX_CONCURRENT_STREAMS에만 의존할 수 없다.
비밀 데이터가 공격자가 제어하는 데이터와 같은 맥락에서 압축되면, 압축 때문에 공격자가 비밀 데이터를 알아낼 수 있다. HTTP/2는 필드 줄의 압축(4.3절)을 가능하게 한다. 다음 우려 사항은 HTTP의 압축 콘텐츠 코딩([HTTP]의 8.4.1절) 사용에도 적용된다.
웹의 특성을 악용하는, 실제로 입증된 압축 공격이 있다(예: [BREACH]). 공격자는 서로 다른 평문을 담은 여러 요청을 유도하고 각각의 결과 암호문 길이를 관찰하는데, 비밀에 대한 추측이 맞으면 길이가 더 짧게 나타난다.
보안 채널로 통신하는 구현은, 데이터의 원천마다 별도의 압축 사전을 쓰는 경우가 아니라면, 기밀 데이터와 공격자가 제어하는 데이터가 모두 들어 있는 콘텐츠를 압축해서는 안 된다(MUST NOT). 데이터의 원천을 확실히 알 수 없으면 압축을 써서는 안 된다(MUST NOT). TLS가 제공하는 것 같은 범용 스트림 압축은 HTTP/2와 함께 써서는 안 된다(MUST NOT). 9.2절을 참고하라.
헤더 필드 압축에 관한 추가 고려 사항은 [COMPRESSION]에 설명되어 있다.
HTTP/2의 패딩은 TLS [TLS13]가 제공하는 것 같은 범용 패딩을 대체하기 위한 것이 아니다. 불필요한 패딩은 오히려 역효과를 낼 수도 있다. 올바르게 적용하려면 패딩하는 데이터에 대한 구체적인 지식이 필요할 수 있다.
압축에 의존하는 공격을 완화하려면, 대응책으로 패딩보다 압축을 끄거나 제한하는 편이 나을 수도 있다.
패딩은 프레임 콘텐츠의 정확한 크기를 감추는 데 쓸 수 있으며, HTTP 안의 특정 공격, 예를 들어 압축된 콘텐츠에 공격자가 제어하는 평문과 비밀 데이터가 모두 들어 있는 공격(예: [BREACH])을 완화하기 위해 제공된다.
패딩을 써도 얼핏 보이는 것보다 보호가 약할 수 있다. 기껏해야 패딩은 공격자가 관찰해야 하는 프레임 수를 늘려 길이 정보를 추론하기 더 어렵게 만들 뿐이다. 잘못 구현된 패딩 체계는 쉽게 뚫릴 수 있다. 특히 분포를 예측할 수 있는 무작위 패딩은 보호 효과가 거의 없다. 마찬가지로 프레임 페이로드를 고정 크기로 패딩하면, 프레임 페이로드 크기가 고정 크기 경계를 넘을 때 정보가 드러난다. 공격자가 평문을 제어할 수 있다면 이런 노출이 일어날 수 있다.
중개자는 DATA 프레임의 패딩을 유지해야 하지만(SHOULD), HEADERS와 PUSH_PROMISE 프레임의 패딩은 버릴 수 있다(MAY). 중개자가 프레임의 패딩 양을 바꿀 정당한 이유 하나는 패딩이 제공하는 보호를 개선하는 것이다.
HTTP/2의 여러 특성은 관찰자가 한 클라이언트나 서버의 동작을 시간에 걸쳐 연관 지을 기회를 준다. 여기에는 설정 값, 흐름 제어 윈도우를 관리하는 방식, 스트림에 우선순위를 할당하는 방식, 자극에 반응하는 타이밍, 설정으로 제어되는 기능의 처리가 포함된다.
이들이 동작상의 관찰 가능한 차이를 만드는 한, [PRIVACY]의 3.2절에 정의된 대로 특정 클라이언트를 핑거프린팅(fingerprinting)하는 근거로 쓰일 수 있다.
TCP 연결 하나를 쓰는 HTTP/2의 방식은 한 사이트에서의 사용자 활동을 연관 지을 수 있게 한다. 서로 다른 출처에 연결을 재사용하면 그 출처들에 걸쳐 추적할 수 있다.
PING과 SETTINGS 프레임은 즉시 응답을 요구하므로, 엔드포인트가 피어까지의 지연 시간을 재는 데 쓸 수 있다. 이는 특정 상황에서 프라이버시에 영향을 줄 수도 있다.
원격 타이밍 공격은 서버가 비밀을 사용하는 요청을 처리하는 데 걸리는 시간의 변화를 관찰해 서버에서 비밀을 알아낸다. HTTP/2는 요청을 동시에 생성하고 처리할 수 있게 하므로, 공격자가 요청 처리가 시작되는 시점을 더 잘 제어할 수 있다. 여러 HTTP/2 요청이 같은 IP 패킷이나 TLS 레코드에 담길 수 있다. 따라서 HTTP/2는 요청 전달의 변동성을 없애 요청 순서와 응답 전달만을 타이밍 변동성의 원천으로 남김으로써, 원격 타이밍 공격을 더 효율적으로 만들 수 있다.
처리 시간이 비밀의 값에 의존하지 않도록 하는 것이 어떤 형태의 타이밍 공격이든 막는 최선의 방어다.
이번 HTTP/2 개정판은 [RFC7540]에 정의된 HTTP2-Settings 헤더 필드와 h2c 업그레이드 토큰을 폐기된 것으로 표시한다.
[RFC7540]의 11장은 h2와 h2c ALPN 식별자를 PRI HTTP 메서드와 함께 등록했다. RFC 7540은 프레임 유형, 설정, 오류 코드를 위한 레지스트리도 만들었다. 이 등록과 레지스트리는 HTTP/2에 적용되지만, 이 문서에서 다시 정의하지는 않는다.
IANA는 다음 레지스트리에서 RFC 7540에 대한 참조를 이 문서를 가리키도록 갱신했다: “TLS ALPN 프로토콜 ID(TLS Application-Layer Protocol Negotiation (ALPN) Protocol IDs)”, “HTTP/2 프레임 유형(HTTP/2 Frame Type)”, “HTTP/2 설정(HTTP/2 Settings)”, “HTTP/2 오류 코드(HTTP/2 Error Code)”, “HTTP 메서드 레지스트리(HTTP Method Registry)”. PRI 메서드의 등록은 3.4절을 가리키도록 갱신했으며, 다른 절 번호는 모두 바뀌지 않았다.
IANA는 “HTTP/2 프레임 유형”과 “HTTP/2 설정” 레지스트리 가운데 RFC 7540에서 실험용(Experimental Use)으로 예약했던 부분의 정책을 바꿨다. 레지스트리의 이 부분은 각 레지스트리의 나머지 부분과 같은 정책으로 운영된다.
이 절은 [RFC7540]의 11.5절이 “HTTP 필드 이름 레지스트리(Hypertext Transfer Protocol (HTTP) Field Name Registry)”에 등록한 HTTP2-Settings 헤더 필드를 폐기된 것으로 표시한다. 이 기능은 제거되었다. 3.1절을 참고하라. 등록은 [HTTP]의 18.4절이 요구하는 세부 사항을 담도록 다음과 같이 갱신된다.
이 절은 [RFC7540]의 11.8절이 “HTTP 업그레이드 토큰 레지스트리(Hypertext Transfer Protocol (HTTP) Upgrade Token Registry)”에 등록한 h2c 업그레이드 토큰을 폐기된 것으로 기록한다. 이 기능은 제거되었다. 3.1절을 참고하라. 등록은 다음과 같이 갱신된다.
역주: 11.1절과 11.2절의 등록 항목 값은 IANA 레지스트리 값이라 원문 그대로 두고, 항목 이름만 번역해 원어를 병기했다(11.2절에서는 11.1절과 겹치는 “참조”의 병기를 반복하지 않았다).
역주: RFC 5246(TLS 1.2)도 2026년 7월 RFC 9846으로 대체되었다(번역). 본문의 [TLS12] 절 참조는 RFC 5246의 절을 가리킨다.
역주: RFC 8446은 2026년 7월 RFC 9846으로 대체되었다(번역). RFC 9846도 같은 TLS 버전 1.3을 규정하며, RFC 8446의 절 구성을 대체로 그대로 유지한다.
역주: 참고 문헌 항목은 원문 그대로 두고 Markdown 목록으로 바꾸었다. 여러 줄에 걸친 항목은 한 줄로 이었고, 줄바꿈으로 끊긴 URL은 공백 없이 이었다.
HTTP/2 구현은 TLS 1.2에서 다음 암호 스위트 중 어느 것이든 협상되는 것을 INADEQUATE_SECURITY 유형의 연결 오류(5.4.1절)로 처리할 수 있다(MAY).
참고: 이 목록은 [RFC7540]을 개발할 당시 등록되어 있던 TLS 암호 스위트 집합으로 만든 것이다. 이 목록에는 임시 키 교환을 제공하지 않는 암호 스위트와, TLS null·스트림·블록 암호 유형([TLS12]의 6.2.3절에 정의)에 기반한 암호 스위트가 들어 있다. 이런 특성을 가진 암호 스위트가 더 정의될 수 있는데, 그런 암호 스위트는 명시적으로 금지되지 않는다.
자세한 내용은 9.2.2절을 참고하라.
역주: 암호 스위트 이름 목록은 원문 그대로 두었다.
이번 개정판에는 다음과 같은 실질적인 변경이 있다.
PRIORITY 프레임의 형식과 HEADERS 프레임의 우선순위 필드에 대한 정의, 그리고 PRIORITY 프레임을 보내고 받을 수 있는 때를 정하는 규칙은 남겨 두었지만, 이 필드들의 의미는 RFC 7540에서만 설명한다. RFC 7540의 우선순위 신호 체계는 성공하지 못했다. [HTTP-PRIORITY]의 더 단순한 신호를 쓸 것을 권장한다.Host와 :authority가 서로 다른 것이 더 이상 허용되지 않는다.편집상의 변경도 있다. 특히 용어와 문서 구조의 변경은 핵심 HTTP 의미론 [HTTP]의 갱신에 따른 것이다. 그 문서들에는 이제 421 상태 코드나 연결 병합(connection coalescing)처럼 RFC 7540에서 처음 정의된 개념 일부가 들어 있다.
이 문서에 대한 상당한 기여는 여러 해에 걸쳐 HTTP 워킹 그룹에 기여한 많은 사람들의 공이다. 기여에 대해 감사를 받아야 할 사람들의 더 자세한 목록은 [RFC7540]에 있다.
Mike Belshe와 Roberto Peon이 이 문서의 바탕이 된 글을 썼다.
역주: 사람 이름은 원문 표기 그대로 두었다.
Martin Thomson (editor)
Mozilla
Australia
Email: mt@lowentropy.net
Cory Benfield (editor)
Apple Inc.
Email: cbenfield@apple.com
역주: 저자 주소는 2022년 발행 당시의 정보를 원문 그대로 옮긴 것이다.
김마리 (Mariel Kim)
Email: marikim.code@gmail.com
GitHub: https://github.com/marik0de