rfc-ko

RFC 9114: HTTP/3 (한국어 번역)

역주: 용어는 glossary.md, 번역 방식은 번역 가이드를 따른다. 이 문서에서 [HTTP]는 RFC 9110(번역), [HTTP/1.1]은 RFC 9112(번역), [HTTP/2]는 RFC 9113(번역)을 가리킨다.


요약

QUIC 전송 프로토콜에는 스트림 다중화(stream multiplexing), 스트림별 흐름 제어(flow control), 지연 시간이 짧은 연결 수립처럼 HTTP를 위한 전송에 바람직한 기능이 여럿 있다. 이 문서는 QUIC 위에 HTTP 의미론(semantics)을 대응시키는 방법을 설명한다. 또한 이 문서는 QUIC이 대신하게 된 HTTP/2 기능을 밝히고, HTTP/2 확장(extension)을 HTTP/3으로 옮기는 방법을 설명한다.

이 메모의 상태

이 문서는 인터넷 표준화 트랙(Standards Track) 문서다.

이 문서는 IETF(Internet Engineering Task Force)의 결과물이며 IETF 커뮤니티의 합의를 나타낸다. 이 문서는 공개 검토를 거쳤고 IESG(Internet Engineering Steering Group)의 발행 승인을 받았다. 인터넷 표준에 대한 자세한 정보는 RFC 7841의 2절에 있다.

이 문서의 현재 상태, 정오표, 의견을 보내는 방법은 https://www.rfc-editor.org/info/rfc9114 에서 확인할 수 있다.

저작권 고지

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를 그대로 두고 그 아래에 한국어 번역을 덧붙였으며, 법적 효력은 원문에 있다.

목차


1. 소개

HTTP 의미론(semantics, [HTTP])은 인터넷에서 폭넓은 서비스에 쓰인다. 이 의미론은 대개 HTTP/1.1, HTTP/2와 함께 쓰여 왔다. HTTP/1.1은 다양한 전송 계층과 세션 계층 위에서 쓰여 왔고, HTTP/2는 주로 TCP 위의 TLS와 함께 쓰여 왔다. HTTP/3은 같은 의미론을 새로운 전송 프로토콜인 QUIC 위에서 지원한다.

1.1. 이전 HTTP 버전

HTTP/1.1([HTTP/1.1])은 공백으로 구분된 텍스트 필드(field)를 사용해 HTTP 메시지(message)를 전달한다. 이런 교환은 사람이 읽을 수 있지만, 메시지 형식을 정하는 데 공백을 쓰면 파싱이 복잡해지고 변형된 동작을 지나치게 너그럽게 받아들이게 된다.

HTTP/1.1에는 다중화(multiplexing) 계층이 없기 때문에, 요청(request)을 병렬로 처리하려고 여러 TCP 연결(connection)을 쓰는 경우가 많다. 그러나 TCP는 여러 연결에 걸쳐 혼잡 제어(congestion control)를 공유하지 않으므로, 이렇게 하면 혼잡 제어와 네트워크 효율에 나쁜 영향을 준다.

HTTP/2([HTTP/2])는 전송 계층을 바꾸지 않고 지연 시간(latency)을 개선하기 위해 이진 프레이밍(binary framing)과 다중화 계층을 도입했다. 그러나 HTTP/2 다중화의 병렬성은 TCP의 손실 복구(loss recovery) 메커니즘에 보이지 않기 때문에, 패킷 하나가 손실되거나 순서가 바뀌면 각 트랜잭션이 손실된 패킷에 직접 영향을 받았는지와 관계없이 모든 활성 트랜잭션이 멈추게 된다.

1.2. QUIC에 맡기는 것

QUIC 전송 프로토콜은 HTTP/2 프레임 계층이 제공하는 것과 비슷한 스트림(stream) 다중화와 스트림별 흐름 제어(flow control)를 갖추고 있다. QUIC은 스트림 수준에서 신뢰성을 제공하고 연결 전체에 걸쳐 혼잡 제어를 하므로, HTTP를 TCP에 대응시킨 경우보다 성능을 높일 수 있다. 또한 QUIC은 전송 계층에 TLS 1.3([TLS])을 통합하여, TCP 위에서 TLS를 실행하는 것과 비슷한 기밀성(confidentiality)과 무결성(integrity)을 제공하면서, TCP Fast Open([TFO])처럼 연결 설정 지연 시간도 줄인다.

이 문서는 HTTP/2의 설계를 많이 참고하여, QUIC 전송 프로토콜 위에 HTTP 의미론을 대응시키는 HTTP/3을 정의한다. HTTP/3은 데이터의 기밀성 및 무결성 보호, 피어(peer) 인증, 그리고 신뢰할 수 있고 순서가 지켜지는 스트림별 전달을 QUIC에 맡긴다. 스트림 수명과 흐름 제어 문제는 QUIC에 맡기는 한편, 각 스트림에서는 HTTP/2 프레이밍과 비슷한 이진 프레이밍을 쓴다. HTTP/2의 일부 기능은 QUIC이 대신하고, 다른 기능은 QUIC 위에서 구현한다.

QUIC은 [QUIC-TRANSPORT]에 설명되어 있다. HTTP/2의 전체 설명은 [HTTP/2]를 참고하라.

2. HTTP/3 프로토콜 개요

HTTP/3은 QUIC 전송 프로토콜과, HTTP/2와 비슷한 내부 프레임 계층을 사용해 HTTP 의미론을 위한 전송을 제공한다.

클라이언트(client)는 어떤 엔드포인트(endpoint)에 HTTP/3 서버(server)가 있다는 것을 알게 되면 QUIC 연결을 연다. QUIC은 프로토콜 협상(negotiation), 스트림 기반 다중화, 흐름 제어를 제공한다. HTTP/3 엔드포인트를 찾는 방법은 3.1절에 설명되어 있다.

각 스트림 안에서 HTTP/3 통신의 기본 단위는 프레임(frame, 7.2절)이다. 프레임 유형(frame type)마다 목적이 다르다. 예를 들어 HEADERS와 DATA 프레임은 HTTP 요청과 응답(response)의 기반이 된다(4.1절). 연결 전체에 적용되는 프레임은 전용 제어 스트림(control stream)으로 전달된다.

요청의 다중화는 [QUIC-TRANSPORT]의 2장에 설명된 QUIC 스트림 추상화를 사용해 이루어진다. 요청-응답 쌍 하나마다 QUIC 스트림 하나를 쓴다. 스트림은 서로 독립적이므로, 한 스트림이 막히거나 패킷 손실을 겪어도 다른 스트림의 진행을 막지 않는다.

서버 푸시(server push)는 HTTP/2([HTTP/2])에서 도입된 상호 작용 방식으로, 클라이언트가 어떤 요청을 하리라 예상하고 서버가 그 요청-응답 교환을 클라이언트에 푸시할 수 있게 한다. 이는 네트워크 사용량을 더 쓰는 대신 지연 시간을 줄일 가능성을 얻는 절충이다. 서버 푸시를 관리하는 데는 PUSH_PROMISE, MAX_PUSH_ID, CANCEL_PUSH 같은 여러 HTTP/3 프레임이 쓰인다.

HTTP/2에서처럼 요청 필드와 응답 필드는 압축해서 전송한다. HPACK([HPACK])은 압축된 필드 섹션(field section)이 순서대로 전송된다는 것(QUIC이 보장하지 않는 것)에 의존하기 때문에, HTTP/3은 HPACK 대신 QPACK([QPACK])을 쓴다. QPACK은 별도의 단방향 스트림(unidirectional stream)을 사용해 필드 테이블 상태를 바꾸고 추적하며, 인코딩된 필드 섹션은 테이블 상태를 바꾸지 않고 참조만 한다.

2.1. 문서 구성

다음 장들은 HTTP/3 연결의 수명 주기를 자세히 개관한다.

와이어 프로토콜의 세부 사항과 전송 계층과의 상호 작용은 이어지는 장들에서 설명한다.

마지막 장들에서는 추가 자료를 제공한다.

2.2. 표기 규칙과 용어

이 문서의 키워드 “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “NOT RECOMMENDED”, “MAY”, “OPTIONAL”은 여기에 보인 것처럼 모두 대문자로 쓰였을 때, 그리고 그때에만 BCP 14 [RFC2119] [RFC8174]에 설명된 대로 해석한다.

이 문서는 [QUIC-TRANSPORT]의 가변 길이 정수 인코딩(variable-length integer encoding)을 사용한다.

다음 용어를 사용한다.

용어 “콘텐츠(content)”는 [HTTP]의 6.4절에 정의되어 있다.

마지막으로, “리소스(resource)”, “메시지(message)”, “사용자 에이전트(user agent)”, “원본 서버(origin server)”, “게이트웨이(gateway)”, “중개자(intermediary)”, “프록시(proxy)”, “터널(tunnel)”이라는 용어는 [HTTP]의 3장에 정의되어 있다.

이 문서의 패킷 다이어그램은 필드의 순서와 크기를 나타내기 위해 [QUIC-TRANSPORT]의 1.3절에 정의된 형식을 사용한다.

3. 연결 설정과 관리

3.1. HTTP/3 엔드포인트 찾기

HTTP는 권한 있는 응답(authoritative response)이라는 개념에 의존한다. 권한 있는 응답이란, 대상 URI에서 식별되는 원본 서버가(또는 그 지시에 따라) 응답 메시지를 만드는 시점의 대상 리소스 상태를 고려할 때 그 요청에 가장 적절하다고 판단된 응답이다. HTTP URI에 대해 권한 있는 서버를 찾는 방법은 [HTTP]의 4.3절에서 다룬다.

“https” 스킴은 URI의 authority 구성 요소가 식별하는 호스트에 대해 클라이언트가 신뢰할 수 있다고 여기는 인증서의 보유 여부에 권한(authority)을 연관시킨다. 클라이언트는 TLS 핸드셰이크(handshake)에서 서버 인증서를 받으면, [HTTP]의 4.3.4절에 설명된 절차를 사용해 그 인증서가 URI의 원본 서버와 허용 가능한 수준으로 일치하는지 반드시 검증해야 한다(MUST). 인증서를 URI의 원본 서버에 대해 검증할 수 없으면, 클라이언트는 그 서버를 그 출처(origin)에 대해 권한 있다고 여겨서는 안 된다(MUST NOT).

클라이언트는 호스트 식별자를 IP 주소로 해석하고, 그 주소의 지정된 포트로 QUIC 연결을 수립하고(위에 설명한 서버 인증서 검증을 포함한다), 그 보안 처리된 연결을 통해 서버에 그 URI를 대상으로 하는 HTTP/3 요청 메시지를 보내는 방식으로 “https” URI를 가진 리소스에 접근을 시도할 수 있다(MAY). HTTP/3을 고르는 다른 메커니즘을 쓰지 않는 한, TLS 핸드셰이크 동안 ALPN(Application-Layer Protocol Negotiation, [RFC7301] 참고) 확장(extension)에서 토큰 “h3”을 쓴다.

연결 문제(예: UDP 차단) 때문에 QUIC 연결 수립에 실패할 수 있다. 이 경우 클라이언트는 TCP 기반 HTTP 버전을 사용하려고 시도해야 한다(SHOULD).

서버는 어떤 UDP 포트에서든 HTTP/3을 제공할 수 있다(MAY). 대체 서비스(alternative service) 알림에는 언제나 명시적인 포트가 들어 있고, URI에는 명시적인 포트나 스킴에 연관된 기본 포트가 들어 있다.

3.1.1. HTTP 대체 서비스

HTTP 출처는 Alt-Svc HTTP 응답 헤더 필드나 HTTP/2 ALTSVC 프레임([ALTSVC])에 “h3” ALPN 토큰을 써서, 그와 동등한 HTTP/3 엔드포인트를 사용할 수 있다는 것을 알릴 수 있다.

예를 들어 출처는 HTTP 응답에 다음 헤더 필드를 넣어, 같은 호스트 이름의 UDP 포트 50781에서 HTTP/3을 사용할 수 있다는 것을 나타낼 수도 있다.

Alt-Svc: h3=":50781"

HTTP/3 지원을 나타내는 Alt-Svc 레코드를 받으면, 클라이언트는 지정된 호스트와 포트로 QUIC 연결 수립을 시도할 수 있다(MAY). 이 연결이 성공하면 클라이언트는 이 문서에 설명된 대응 방식을 사용해 HTTP 요청을 보낼 수 있다.

3.1.2. 다른 스킴

HTTP는 전송 프로토콜과 독립적이지만, “http” 스킴은 authority 구성 요소에서 식별되는 호스트가 무엇이든 그 호스트의 지정된 포트에서 TCP 연결을 받을 수 있는지에 권한을 연관시킨다. HTTP/3은 TCP를 쓰지 않으므로, “http” URI로 식별되는 리소스의 권한 있는 서버에 직접 접근하는 데 HTTP/3을 쓸 수는 없다. 그러나 [ALTSVC] 같은 프로토콜 확장을 쓰면, 권한 있는 서버가 마찬가지로 권한 있고 HTTP/3으로 도달할 수도 있는 다른 서비스를 알려 줄 수 있다.

스킴이 “https”가 아닌 출처에 요청을 하기 전에, 클라이언트는 서버가 그 스킴을 제공할 의향이 있는지 반드시 확인해야 한다(MUST). 스킴이 “http”인 출처에 대해 이를 확인하는 실험적인 방법이 [RFC8164]에 설명되어 있다. 앞으로 여러 스킴에 대해 다른 메커니즘이 정의될 수도 있다.

3.2. 연결 수립

HTTP/3은 기반 전송으로 QUIC 버전 1에 의존한다. HTTP/3에서 다른 QUIC 전송 버전을 쓰는 방법은 앞으로의 명세에서 정의할 수 있다(MAY).

QUIC 버전 1은 핸드셰이크 프로토콜로 TLS 버전 1.3 이상을 사용한다. HTTP/3 클라이언트는 TLS 핸드셰이크 동안 서버에 대상 호스트를 알리는 메커니즘을 반드시 지원해야 한다(MUST). 서버가 도메인 이름([DNS-TERMS])으로 식별되면, 클라이언트는 대상 호스트를 알리는 다른 메커니즘을 쓰지 않는 한 SNI(Server Name Indication, [RFC6066] 참고) TLS 확장을 반드시 보내야 한다(MUST).

QUIC 연결은 [QUIC-TRANSPORT]에 설명된 대로 수립된다. 연결을 수립하는 동안, TLS 핸드셰이크에서 ALPN 토큰 “h3”을 선택하는 것으로 HTTP/3 지원을 나타낸다. 같은 핸드셰이크에서 다른 애플리케이션 계층 프로토콜의 지원도 제시할 수 있다(MAY).

핵심 QUIC 프로토콜에 관한 연결 수준 옵션은 초기 암호 핸드셰이크에서 설정되지만, HTTP/3에 고유한 설정(setting)은 SETTINGS 프레임으로 전달된다. QUIC 연결이 수립된 뒤, 각 엔드포인트는 자신의 HTTP 제어 스트림의 첫 프레임으로 반드시 SETTINGS 프레임을 보내야 한다(MUST).

3.3. 연결 재사용

HTTP/3 연결은 여러 요청에 걸쳐 지속된다. 최상의 성능을 위해, 클라이언트는 서버와 더 이상 통신할 필요가 없다고 판단될 때(예: 사용자가 특정 웹 페이지를 떠날 때)나 서버가 연결을 닫을 때까지 연결을 닫지 않을 것으로 기대된다.

서버 엔드포인트로의 연결이 있으면, 그 연결을 URI authority 구성 요소가 서로 다른 여러 요청에 재사용할 수 있다(MAY). 기존 연결을 새 출처에 쓰려면, 클라이언트는 [HTTP]의 4.3.4절에 설명된 절차를 사용해, 서버가 제시한 인증서를 새 원본 서버에 대해 반드시 검증해야 한다(MUST). 이는 클라이언트가 서버 인증서와, 그 인증서를 검증하는 데 필요한 추가 정보를 보관해 둘 필요가 있다는 뜻이다. 그렇게 하지 않는 클라이언트는 그 연결을 추가 출처에 재사용할 수 없다.

어떤 이유로든 인증서가 새 출처에 대해 허용되지 않으면, 그 연결을 재사용해서는 안 되며(MUST NOT) 새 출처를 위한 새 연결을 수립해야 한다(SHOULD). 인증서를 검증할 수 없는 이유가 이미 그 연결에 연관된 다른 출처에도 해당할 수도 있다면, 클라이언트는 그 출처들에 대해 서버 인증서를 다시 검증해야 한다(SHOULD). 예를 들어 인증서가 만료되었거나 폐지되어 검증에 실패했다면, 그 인증서로 권한을 확립했던 다른 모든 출처를 무효로 하는 데 이 사실을 쓸 수도 있다.

클라이언트는 주어진 IP 주소와 UDP 포트로 HTTP/3 연결을 둘 이상 열지 않아야 한다(SHOULD NOT). 여기서 IP 주소와 포트는 URI, 선택된 대체 서비스([ALTSVC]), 구성된 프록시, 또는 이들 가운데 어느 것의 이름 해석에서 얻은 것일 수도 있다. 클라이언트는 서로 다른 전송 구성이나 TLS 구성을 사용해 같은 IP 주소와 UDP 포트로 HTTP/3 연결을 여러 개 열 수 있지만(MAY), 같은 구성으로 연결을 여러 개 만드는 것은 피해야 한다(SHOULD).

서버는 열린 HTTP/3 연결을 가능한 한 오래 유지하는 것이 권장되지만, 필요하면 유휴(idle) 연결을 종료해도 된다. 어느 엔드포인트든 HTTP/3 연결을 닫기로 하면, 연결을 종료하는 엔드포인트는 먼저 GOAWAY 프레임(5.2절)을 보내야 한다(SHOULD). 그래야 두 엔드포인트가 앞서 보낸 프레임이 처리되었는지 확실히 판단하고, 남은 필요한 작업을 우아하게 완료하거나 종료할 수 있다.

특정 출처에 대해 클라이언트가 HTTP/3 연결을 재사용하지 않기를 바라는 서버는 요청에 대한 응답으로 421 (Misdirected Request) 상태 코드(status code)를 보내, 그 요청에 대해 권한 있지 않음을 나타낼 수 있다([HTTP]의 7.4절 참고).

4. HTTP/3에서 HTTP 의미론 표현하기

4.1. HTTP 메시지 프레이밍

클라이언트는 요청 스트림(request stream)에서 HTTP 요청을 보낸다. 요청 스트림은 클라이언트가 시작한 양방향 QUIC 스트림이다. 6.1절을 참고하라. 클라이언트는 주어진 스트림에서 반드시 요청을 하나만 보내야 한다(MUST). 서버는 아래에 자세히 설명하는 대로, 요청과 같은 스트림에서 HTTP 중간 응답(interim response)을 0개 이상 보내고 그 뒤에 HTTP 최종 응답(final response)을 하나 보낸다. HTTP 중간 응답과 최종 응답에 대한 설명은 [HTTP]의 15장을 참고하라.

푸시된 응답은 서버가 시작한 단방향 QUIC 스트림으로 보낸다. 6.2.2절을 참고하라. 서버는 일반 응답과 같은 방식으로 HTTP 중간 응답을 0개 이상 보내고 그 뒤에 HTTP 최종 응답을 하나 보낸다. 푸시는 4.6절에서 더 자세히 설명한다.

주어진 스트림에서 요청을 여러 개 받거나, HTTP 최종 응답 뒤에 HTTP 응답을 추가로 받으면 반드시 형식이 잘못된(malformed) 것으로 처리해야 한다(MUST).

HTTP 메시지(요청 또는 응답)는 다음으로 이루어진다.

  1. 메시지 제어 데이터(control data)를 포함한 헤더 섹션. HEADERS 프레임 하나로 보낸다.
  2. 선택적으로, 콘텐츠가 있다면 그 콘텐츠. 일련의 DATA 프레임으로 보낸다.
  3. 선택적으로, 트레일러 섹션이 있다면 그 트레일러 섹션. HEADERS 프레임 하나로 보낸다.

헤더 섹션과 트레일러 섹션은 [HTTP]의 6.3절과 6.5절에, 콘텐츠는 [HTTP]의 6.4절에 설명되어 있다.

유효하지 않은 프레임 시퀀스를 받으면 반드시 H3_FRAME_UNEXPECTED 유형의 연결 오류로 처리해야 한다(MUST). 특히 어떤 HEADERS 프레임보다도 앞에 오는 DATA 프레임, 또는 트레일러를 담은 HEADERS 프레임 뒤에 오는 HEADERS나 DATA 프레임은 유효하지 않은 것으로 간주한다. 다른 프레임 유형, 특히 알 수 없는 프레임 유형은 그 유형 자체의 규칙에 따라 허용될 수도 있다. 9장을 참고하라.

서버는 응답 메시지의 프레임 앞이나 뒤에, 또는 그 프레임 사이사이에 PUSH_PROMISE 프레임을 하나 이상 보낼 수 있다(MAY). 이 PUSH_PROMISE 프레임은 응답의 일부가 아니다. 자세한 내용은 4.6절을 참고하라. PUSH_PROMISE 프레임은 푸시 스트림(push stream)에서는 허용되지 않는다. PUSH_PROMISE 프레임이 들어 있는 푸시된 응답은 반드시 H3_FRAME_UNEXPECTED 유형의 연결 오류로 처리해야 한다(MUST).

예약된 프레임(7.2.8절)을 포함해 알 수 없는 유형의 프레임(9장)은, 요청 스트림이나 푸시 스트림에서 이 절에 설명한 다른 프레임의 앞이나 뒤에, 또는 그 사이사이에 보낼 수 있다(MAY).

HEADERS와 PUSH_PROMISE 프레임은 QPACK 동적 테이블(dynamic table)의 갱신을 참조할 수도 있다. 이 갱신은 메시지 교환의 직접적인 일부는 아니지만, 이 갱신을 먼저 받아 처리해야만 메시지를 소비할 수 있다. 자세한 내용은 4.2절을 참고하라.

전송 코딩(transfer coding, [HTTP/1.1]의 7장 참고)은 HTTP/3에 정의되어 있지 않다. Transfer-Encoding 헤더 필드를 사용해서는 안 된다(MUST NOT).

응답은 같은 요청에 대한 최종 응답 앞에 중간 응답(1xx. [HTTP]의 15.2절 참고)이 하나 이상 올 때, 그리고 그때에만 여러 메시지로 이루어질 수 있다(MAY). 중간 응답에는 콘텐츠나 트레일러 섹션이 들어 있지 않다.

HTTP 요청/응답 교환 하나는 클라이언트가 시작한 양방향 QUIC 스트림 하나를 완전히 소비한다. 클라이언트는 요청을 보낸 뒤 반드시 그 스트림의 송신 방향을 닫아야 한다(MUST). 클라이언트는 CONNECT 메서드(4.4절 참고)를 쓰는 경우가 아니라면, 스트림을 닫는 일이 자신의 요청에 대한 응답 수신에 좌우되게 해서는 안 된다(MUST NOT). 서버는 최종 응답을 보낸 뒤 반드시 그 스트림의 송신 방향을 닫아야 한다(MUST). 이 시점에서 QUIC 스트림은 완전히 닫힌다.

스트림이 닫히면 이는 최종 HTTP 메시지의 끝을 나타낸다. 어떤 메시지는 크거나 길이에 제한이 없으므로, 엔드포인트는 진행할 수 있을 만큼 메시지를 받으면 HTTP 메시지의 일부라도 처리를 시작해야 한다(SHOULD). 클라이언트가 시작한 스트림이 완전한 응답을 제공할 만큼의 HTTP 메시지 없이 끝나면, 서버는 오류 코드 H3_REQUEST_INCOMPLETE로 응답 스트림을 중단해야 한다(SHOULD).

응답이 요청 가운데 아직 송수신되지 않은 부분에 전혀 의존하지 않는다면, 서버는 클라이언트가 요청 전체를 보내기 전에 완전한 응답을 보낼 수 있다. 서버는 요청의 나머지를 받을 필요가 없으면, 요청 스트림 읽기를 중단하고, 완전한 응답을 보내고, 스트림의 송신 부분을 깔끔하게 닫을 수 있다(MAY). 클라이언트에게 요청 스트림에서 전송을 멈추도록 요청할 때는 오류 코드 H3_NO_ERROR를 써야 한다(SHOULD). 클라이언트는 자신의 요청이 갑자기 끝났다는 이유로 완전한 응답을 버려서는 안 된다(MUST NOT). 다만 클라이언트는 다른 이유로는 재량에 따라 언제든 응답을 버릴 수 있다. 서버가 일부 응답이나 완전한 응답을 보내면서 요청 읽기를 중단하지 않으면, 클라이언트는 요청 콘텐츠를 계속 보내고 스트림을 정상적으로 닫아야 한다(SHOULD).

4.1.1. 요청 취소와 거부

요청 스트림이 열리면 어느 엔드포인트든 그 요청을 취소할 수 있다(MAY). 클라이언트는 응답이 더 이상 필요 없으면 요청을 취소하고, 서버는 응답할 수 없거나 응답하지 않기로 하면 요청을 취소한다. 가능하다면, 서버는 이미 처리하기 시작한 요청을 취소하기보다 적절한 상태 코드를 담은 HTTP 응답을 보낼 것을 권고한다(RECOMMENDED).

구현은 스트림에서 아직 열려 있는 방향을 모두 갑자기 끝내는 방식으로 요청을 취소해야 한다(SHOULD). 그러려면 구현은 스트림의 송신 부분을 리셋하고 스트림의 수신 부분에서 읽기를 중단한다. [QUIC-TRANSPORT]의 2.4절을 참고하라.

서버가 애플리케이션 처리를 전혀 하지 않고 요청을 취소하면, 그 요청은 “거부된(rejected)” 것으로 간주한다. 서버는 오류 코드 H3_REQUEST_REJECTED로 응답 스트림을 중단해야 한다(SHOULD). 이 맥락에서 “처리된(processed)”이라는 말은, 스트림의 데이터 일부가 소프트웨어의 어떤 상위 계층으로 전달되었고 그 결과로 그 계층이 어떤 동작을 했을 수도 있다는 뜻이다. 클라이언트는 서버가 거부한 요청을 전혀 보내지 않은 것처럼 처리할 수 있으므로, 나중에 그 요청을 재시도할 수 있다.

서버는 일부 또는 전부 처리된 요청에 H3_REQUEST_REJECTED 오류 코드를 써서는 안 된다(MUST NOT). 일부 처리한 뒤 응답을 포기하는 서버는 오류 코드 H3_REQUEST_CANCELLED로 응답 스트림을 중단해야 한다(SHOULD).

클라이언트는 요청을 취소할 때 오류 코드 H3_REQUEST_CANCELLED를 써야 한다(SHOULD). 이 오류 코드를 받은 서버는 아무 처리도 하지 않았다면 오류 코드 H3_REQUEST_REJECTED를 사용해 응답을 갑자기 끝낼 수 있다(MAY). 클라이언트는 서버가 이 오류 코드로 요청 스트림을 닫도록 요청한 경우가 아니라면 H3_REQUEST_REJECTED 오류 코드를 써서는 안 된다(MUST NOT).

완전한 응답을 받은 뒤 스트림이 취소되면, 클라이언트는 그 취소를 무시하고 응답을 사용할 수 있다(MAY). 그러나 응답 일부만 받은 뒤 스트림이 취소되면 그 응답을 사용하지 않아야 한다(SHOULD NOT). GET, PUT, DELETE 같은 멱등(idempotent) 동작만 안전하게 재시도할 수 있다. 클라이언트는 메서드와 관계없이 요청 의미가 멱등임을 알 수단이 있거나 원래 요청이 적용되지 않았음을 감지할 수단이 있는 경우가 아니라면, 멱등이 아닌 메서드를 쓴 요청을 자동으로 재시도하지 않아야 한다(SHOULD NOT). 자세한 내용은 [HTTP]의 9.2.2절을 참고하라.

4.1.2. 형식이 잘못된 요청과 응답

형식이 잘못된 요청이나 응답이란, 다른 면에서는 유효한 프레임 시퀀스이지만 다음 이유로 유효하지 않은 것을 말한다.

Content-Length 헤더 필드([HTTP]의 8.6절)가 들어 있을 때 콘텐츠가 있는 것으로 정의된 요청이나 응답은, Content-Length 헤더 필드의 값이 받은 DATA 프레임 길이의 합과 같지 않으면 형식이 잘못된 것이다. Content-Length가 있더라도 콘텐츠가 결코 없는 것으로 정의된 응답은, DATA 프레임에 콘텐츠가 들어 있지 않더라도 0이 아닌 Content-Length 헤더 필드를 가질 수 있다.

HTTP 요청이나 응답을 처리하는 중개자(즉, 터널로 동작하지 않는 모든 중개자)는 형식이 잘못된 요청이나 응답을 전달해서는 안 된다(MUST NOT). 감지된 형식이 잘못된 요청이나 응답은 반드시 H3_MESSAGE_ERROR 유형의 스트림 오류로 처리해야 한다(MUST).

형식이 잘못된 요청에 대해, 서버는 스트림을 닫거나 리셋하기 전에 오류를 나타내는 HTTP 응답을 보낼 수 있다(MAY). 클라이언트는 형식이 잘못된 응답을 받아들여서는 안 된다(MUST NOT). 이 요구 사항들은 HTTP에 대한 몇 가지 흔한 공격을 막기 위한 것이라는 점에 유의하라. 관대하게 처리하면 구현이 이런 취약점에 노출될 수 있으므로, 이 요구 사항들은 의도적으로 엄격하다.

4.2. HTTP 필드

HTTP 메시지는 “HTTP 필드(HTTP fields)”라고 부르는 일련의 키-값 쌍으로 메타데이터를 전달한다. [HTTP]의 6.3절과 6.5절을 참고하라. 등록된 HTTP 필드의 목록은 https://www.iana.org/assignments/http-fields/에서 관리하는 “HTTP 필드 이름 레지스트리(Hypertext Transfer Protocol (HTTP) Field Name Registry)”를 참고하라. HTTP/2와 마찬가지로 HTTP/3에는 필드 이름에 쓰는 문자, Connection 헤더 필드, 의사 헤더 필드와 관련해 추가로 고려할 사항이 있다.

필드 이름은 ASCII 문자의 부분 집합으로 이루어진 문자열이다. HTTP 필드 이름과 값의 속성은 [HTTP]의 5.1절에서 더 자세히 다룬다. 필드 이름의 문자는 인코딩하기 전에 반드시 소문자로 바꿔야 한다(MUST). 필드 이름에 대문자가 들어 있는 요청이나 응답은 반드시 형식이 잘못된 것으로 처리해야 한다(MUST).

HTTP/3은 연결 전용 필드를 나타내는 데 Connection 헤더 필드를 쓰지 않는다. 이 프로토콜에서는 연결 전용 메타데이터를 다른 수단으로 전달한다. 엔드포인트는 연결 전용 필드가 든 HTTP/3 필드 섹션을 생성해서는 안 된다(MUST NOT). 연결 전용 필드가 든 메시지는 반드시 형식이 잘못된 것으로 처리해야 한다(MUST).

이에 대한 유일한 예외는 TE 헤더 필드로, HTTP/3 요청 헤더에 들어 있을 수 있다(MAY). 들어 있다면 그 값은 “trailers” 외의 값을 담아서는 안 된다(MUST NOT).

HTTP/1.x 메시지를 HTTP/3으로 바꾸는 중개자는 [HTTP]의 7.6.1절에서 설명한 대로 연결 전용 헤더 필드를 반드시 제거해야 한다(MUST). 그러지 않으면 다른 HTTP/3 엔드포인트가 그 메시지를 형식이 잘못된 것으로 처리한다.

4.2.1. 필드 압축

[QPACK]은 HPACK의 변형을 설명한다. 이 변형은 압축 때문에 생길 수 있는 HOL 블로킹(head-of-line blocking)의 양을 인코더가 어느 정도 제어할 수 있게 한다. 이로써 인코더는 압축 효율과 지연 시간 사이에서 균형을 잡을 수 있다. HTTP/3은 헤더 섹션에 있는 제어 데이터를 포함해, 헤더 섹션과 트레일러 섹션을 압축하는 데 QPACK을 쓴다.

압축 효율을 높이기 위해, Cookie 헤더 필드([COOKIES])는 압축하기 전에 각각 하나 이상의 쿠키 쌍(cookie-pair)을 담은 별도의 필드 줄(field line)로 나눌 수 있다(MAY). 압축을 푼 필드 섹션에 쿠키 필드 줄이 여럿 들어 있으면, HTTP/1.1 연결이나 범용 HTTP 서버 애플리케이션 같은 HTTP/2나 HTTP/3이 아닌 맥락으로 넘기기 전에 2바이트 구분자 “; “(ASCII 0x3b, 0x20)를 사용해 이를 반드시 바이트 문자열 하나로 이어 붙여야 한다(MUST).

4.2.2. 헤더 크기 제약

HTTP/3 구현은 개별 HTTP 메시지에서 받아들일 메시지 헤더의 최대 크기에 한도를 둘 수 있다(MAY). 처리하려는 것보다 큰 헤더 섹션을 받은 서버는 HTTP 431 (Request Header Fields Too Large) 상태 코드([RFC6585])를 보낼 수 있다. 클라이언트는 처리할 수 없는 응답을 버릴 수 있다. 필드 목록의 크기는 압축하지 않은 필드 크기를 바탕으로 계산하며, 이름과 값의 길이(바이트)에 필드마다 32바이트의 오버헤드를 더한다.

구현이 이 한도를 피어에게 알리고 싶다면, SETTINGS_MAX_FIELD_SECTION_SIZE 매개 변수에 바이트 수로 전달할 수 있다. 이 매개 변수를 받은 구현은 지정된 크기를 넘는 HTTP 메시지 헤더를 보내지 않아야 한다(SHOULD NOT). 피어가 그 헤더의 처리를 거부할 가능성이 높기 때문이다. 그러나 HTTP 메시지는 원본 서버에 도달하기 전에 중개자를 하나 이상 거칠 수 있다. [HTTP]의 3.7절을 참고하라. 이 한도는 메시지를 처리하는 각 구현이 따로 적용하므로, 이 한도보다 작은 메시지라고 해서 받아들여진다는 보장은 없다.

4.3. HTTP 제어 데이터

HTTP/2와 마찬가지로, HTTP/3은 필드 이름이 ‘:’ 문자(ASCII 0x3a)로 시작하는 일련의 의사 헤더 필드를 사용한다. 이 의사 헤더 필드는 메시지 제어 데이터를 전달한다. [HTTP]의 6.2절을 참고하라.

의사 헤더 필드는 HTTP 필드가 아니다. 엔드포인트는 이 문서에 정의된 것이 아닌 의사 헤더 필드를 생성해서는 안 된다(MUST NOT). 그러나 확장이 이 제한의 변경을 협상할 수도 있다. 9장을 참고하라.

의사 헤더 필드는 그것이 정의된 맥락에서만 유효하다. 요청용으로 정의된 의사 헤더 필드는 응답에 나타나서는 안 된다(MUST NOT). 응답용으로 정의된 의사 헤더 필드는 요청에 나타나서는 안 된다(MUST NOT). 의사 헤더 필드는 트레일러 섹션에 나타나서는 안 된다(MUST NOT). 엔드포인트는 정의되지 않았거나 유효하지 않은 의사 헤더 필드가 든 요청이나 응답을 반드시 형식이 잘못된 것으로 처리해야 한다(MUST).

모든 의사 헤더 필드는 헤더 섹션에서 반드시 일반 헤더 필드보다 앞에 나타나야 한다(MUST). 헤더 섹션에서 일반 헤더 필드 뒤에 나타나는 의사 헤더 필드가 든 요청이나 응답은 반드시 형식이 잘못된 것으로 처리해야 한다(MUST).

4.3.1. 요청 의사 헤더 필드

요청에는 다음 의사 헤더 필드가 정의되어 있다.

모든 HTTP/3 요청은 CONNECT 요청이 아니라면 :method, :scheme, :path 의사 헤더 필드에 대해 반드시 정확히 하나의 값을 담아야 한다(MUST). 4.4절을 참고하라.

:scheme 의사 헤더 필드가 authority 구성 요소가 필수인 스킴(“http”와 “https” 포함)을 식별하면, 요청에는 반드시 :authority 의사 헤더 필드나 Host 헤더 필드가 들어 있어야 한다(MUST). 이 필드들이 있다면 비어 있어서는 안 된다(MUST NOT). 두 필드가 모두 있다면 반드시 같은 값을 담아야 한다(MUST). 스킴에 필수 authority 구성 요소가 없고 요청 대상에도 authority가 주어지지 않았다면, 요청에 :authority 의사 헤더나 Host 헤더 필드가 들어 있어서는 안 된다(MUST NOT).

필수 의사 헤더 필드를 빠뜨렸거나 그 의사 헤더 필드에 유효하지 않은 값이 든 HTTP 요청은 형식이 잘못된 것이다.

HTTP/3은 HTTP/1.1 요청 줄에 들어 있는 버전 식별자를 전달하는 방법을 정의하지 않는다. HTTP/3 요청은 암묵적으로 프로토콜 버전 “3.0”을 가진다.

4.3.2. 응답 의사 헤더 필드

응답에는 HTTP 상태 코드를 담는 :status 의사 헤더 필드 하나가 정의되어 있다. [HTTP]의 15장을 참고하라. 이 의사 헤더 필드는 모든 응답에 반드시 들어 있어야 한다(MUST). 그렇지 않으면 그 응답은 형식이 잘못된 것이다(4.1.2절 참고).

HTTP/3은 HTTP/1.1 상태 줄에 들어 있는 버전이나 이유 구문(reason phrase)을 전달하는 방법을 정의하지 않는다. HTTP/3 응답은 암묵적으로 프로토콜 버전 “3.0”을 가진다.

4.4. CONNECT 메서드

CONNECT 메서드는 수신자(recipient)에게 요청 대상이 식별하는 목적지 원본 서버로의 터널을 맺도록 요청한다. [HTTP]의 9.3.6절을 참고하라. CONNECT는 주로 “https” 리소스와 상호 작용하기 위해 HTTP 프록시와 함께 원본 서버와의 TLS 세션을 맺는 데 쓰인다.

HTTP/1.x에서 CONNECT는 HTTP 연결 전체를 원격 호스트로의 터널로 바꾸는 데 쓰인다. HTTP/2와 HTTP/3에서 CONNECT 메서드는 스트림 하나 위에 터널을 맺는 데 쓰인다.

CONNECT 요청은 반드시 다음과 같이 구성해야 한다(MUST).

요청 스트림은 전송할 데이터를 담기 위해 요청이 끝나도 열린 채로 남는다. 이 제한을 따르지 않는 CONNECT 요청은 형식이 잘못된 것이다.

CONNECT를 지원하는 프록시는 :authority 의사 헤더 필드가 식별하는 서버로 TCP 연결([RFC0793])을 맺는다. 이 연결이 성공적으로 맺어지면, 프록시는 [HTTP]의 15.3절에 정의된 2xx 계열 상태 코드를 담은 HEADERS 프레임을 클라이언트에 보낸다.

스트림의 모든 DATA 프레임은 TCP 연결에서 보내거나 받은 데이터에 대응한다. 클라이언트가 보낸 DATA 프레임의 페이로드는 프록시가 TCP 서버로 전송하고, TCP 서버로부터 받은 데이터는 프록시가 DATA 프레임으로 조립한다. TCP 세그먼트의 크기와 개수가 HTTP DATA 프레임이나 QUIC STREAM 프레임의 크기와 개수에 예측 가능하게 대응한다는 보장은 없다는 점에 유의하라.

CONNECT 메서드가 완료되면, 그 스트림에서는 DATA 프레임만 보내는 것이 허용된다. 확장 프레임은 확장의 정의에서 명시적으로 허용한다면 쓸 수 있다(MAY). 그 밖의 알려진 프레임 유형을 받으면 반드시 H3_FRAME_UNEXPECTED 유형의 연결 오류로 처리해야 한다(MUST).

TCP 연결은 어느 피어든 닫을 수 있다. 클라이언트가 요청 스트림을 끝내면(즉, 프록시의 수신 스트림이 “Data Recvd” 상태에 들어가면), 프록시는 TCP 서버로의 연결에서 FIN 비트를 설정한다. FIN 비트가 설정된 패킷을 받은 프록시는 클라이언트로 보내는 송신 스트림을 닫는다. 한 방향으로만 하프 클로즈(half closed)된 채 남은 TCP 연결은 유효하지 않은 것이 아니지만, 서버가 제대로 처리하지 못하는 경우가 많다. 따라서 클라이언트는 CONNECT의 대상에게서 아직 데이터를 받을 것으로 예상하는 동안에는 스트림의 송신 방향을 닫지 않아야 한다(SHOULD NOT).

TCP 연결 오류는 스트림을 갑자기 끝내는 것으로 알린다. 프록시는 RST 비트가 설정된 TCP 세그먼트를 받는 것을 포함해 TCP 연결의 모든 오류를 H3_CONNECT_ERROR 유형의 스트림 오류로 처리한다.

마찬가지로, 프록시는 스트림이나 QUIC 연결에서 오류를 감지하면 반드시 TCP 연결을 닫아야 한다(MUST). 클라이언트가 스트림을 리셋했거나 스트림 읽기를 중단했음을 감지하면, 프록시는 반드시 TCP 연결을 닫아야 한다(MUST). 클라이언트가 스트림을 리셋하거나 읽기를 중단하면, 프록시는 스트림의 두 방향이 모두 취소되도록 다른 방향에도 같은 동작을 해야 한다(SHOULD). 이 모든 경우에 기반 TCP 구현이 허용한다면, 프록시는 RST 비트를 설정한 TCP 세그먼트를 보내야 한다(SHOULD).

CONNECT는 임의의 서버로 터널을 만들기 때문에, CONNECT를 지원하는 프록시는 그 사용을 알려진 포트 집합이나 안전한 요청 대상 목록으로 제한해야 한다(SHOULD). 자세한 내용은 [HTTP]의 9.3.6절을 참고하라.

4.5. HTTP 업그레이드

HTTP/3은 HTTP 업그레이드 메커니즘([HTTP]의 7.8절)이나 101 (Switching Protocols) 정보 상태 코드([HTTP]의 15.2.2절)를 지원하지 않는다.

4.6. 서버 푸시

서버 푸시는 클라이언트가 어떤 요청을 하리라 예상하고 서버가 그 요청-응답 교환을 클라이언트에 푸시할 수 있게 하는 상호 작용 방식이다. 이는 네트워크 사용량을 더 쓰는 대신 지연 시간을 줄일 가능성을 얻는 절충이다. HTTP/3 서버 푸시는 [HTTP/2]의 8.2절에 설명된 것과 비슷하지만, 다른 메커니즘을 쓴다.

역주: RFC 9113에서 서버 푸시를 설명하는 곳은 8.4절이다. 원문의 “Section 8.2”는 그대로 두었다.

서버는 각 서버 푸시에 고유한 푸시 ID(push ID)를 할당한다. 푸시 ID는 HTTP/3 연결의 수명 동안 여러 맥락에서 그 푸시를 가리키는 데 쓰인다.

푸시 ID 공간은 0에서 시작해 MAX_PUSH_ID 프레임이 정한 최댓값에서 끝난다. 특히 서버는 클라이언트가 MAX_PUSH_ID 프레임을 보내기 전에는 푸시할 수 없다. 클라이언트는 서버가 약속할 수 있는 푸시의 수를 제어하기 위해 MAX_PUSH_ID 프레임을 보낸다. 서버는 0부터 시작해 푸시 ID를 차례대로 써야 한다(SHOULD). MAX_PUSH_ID 프레임을 보낸 적이 없을 때, 또는 스트림이 최대 푸시 ID보다 큰 푸시 ID를 참조할 때 푸시 스트림을 받으면, 클라이언트는 이를 반드시 H3_ID_ERROR 유형의 연결 오류로 처리해야 한다(MUST).

푸시 ID는 요청 메시지의 제어 데이터와 헤더 필드를 담은 PUSH_PROMISE 프레임 하나 이상에서 쓰인다. 이 프레임은 그 푸시를 만들어 낸 요청 스트림에서 보낸다. 이로써 서버 푸시를 클라이언트 요청에 연관시킬 수 있다. 같은 푸시 ID가 여러 요청 스트림에서 약속되면, 압축 해제된 요청 필드 섹션은 반드시 같은 필드를 같은 순서로 담아야 하며(MUST), 각 필드의 이름과 값은 반드시 서로 같아야 한다(MUST).

그런 다음 그 약속들을 최종적으로 이행하는 푸시 스트림에 푸시 ID가 들어간다. 푸시 스트림은 자신이 이행하는 약속의 푸시 ID를 밝힌 뒤, 4.1절에 설명한 대로 약속된 요청에 대한 응답을 담는다.

마지막으로, 푸시 ID는 CANCEL_PUSH 프레임에서 쓰일 수 있다. 7.2.3절을 참고하라. 클라이언트는 약속된 리소스를 받고 싶지 않음을 나타내는 데 이 프레임을 쓴다. 서버는 앞서 한 약속을 이행하지 않겠다는 것을 나타내는 데 이 프레임을 쓴다.

모든 요청을 푸시할 수 있는 것은 아니다. 서버는 다음 속성을 가진 요청을 푸시할 수 있다(MAY).

서버는 :authority 의사 헤더 필드에 서버가 권한 있는 값을 반드시 넣어야 한다(MUST). 클라이언트가 푸시된 요청이 나타내는 출처에 대해 그 연결을 아직 검증하지 않았다면, 그 출처에 대한 요청을 그 연결로 보내기 전에 수행할 검증 절차와 같은 절차를 반드시 수행해야 한다(MUST). 3.3절을 참고하라. 이 검증에 실패하면, 클라이언트는 그 서버를 그 출처에 대해 권한 있다고 여겨서는 안 된다(MUST NOT).

캐시 가능하지 않거나, 안전하다고 알려지지 않았거나, 요청 콘텐츠가 있음을 나타내거나, 클라이언트가 서버를 권한 있다고 여기지 않는 요청을 담은 PUSH_PROMISE 프레임을 받으면, 클라이언트는 CANCEL_PUSH 프레임을 보내야 한다(SHOULD). 그에 대응하는 응답은 사용하거나 캐시해서는 안 된다(MUST NOT).

푸시된 응답은 각각 하나 이상의 클라이언트 요청과 연관된다. 푸시는 PUSH_PROMISE 프레임을 받은 요청 스트림과 연관된다. 같은 서버 푸시를 여러 요청 스트림에서 같은 푸시 ID의 PUSH_PROMISE 프레임으로 추가 클라이언트 요청과 연관시킬 수 있다. 이런 연관은 프로토콜 동작에 영향을 주지 않지만, 사용자 에이전트는 푸시된 리소스를 어떻게 쓸지 정할 때 이를 고려할 수 있다(MAY).

응답의 특정 부분에 대해 PUSH_PROMISE 프레임이 놓이는 순서는 중요하다. 서버는 약속된 응답을 참조하는 HEADERS나 DATA 프레임을 보내기 전에 PUSH_PROMISE 프레임을 보내야 한다(SHOULD). 이렇게 하면 서버가 푸시할 리소스를 클라이언트가 요청할 가능성이 줄어든다.

순서가 바뀌어서, 푸시 스트림 데이터가 그에 대응하는 PUSH_PROMISE 프레임보다 먼저 도착할 수 있다. 클라이언트가 아직 알지 못하는 푸시 ID를 가진 새 푸시 스트림을 받으면, 연관된 클라이언트 요청과 푸시된 요청 헤더 필드를 둘 다 알 수 없다. 클라이언트는 짝이 되는 PUSH_PROMISE를 기대하며 스트림 데이터를 버퍼에 담아 둘 수 있다. 클라이언트는 스트림 흐름 제어([QUIC-TRANSPORT]의 4.1절)를 사용해, 서버가 푸시된 스트림에 쏟을 수 있는 데이터의 양을 제한할 수 있다. 적당한 시간 안에 대응하는 PUSH_PROMISE 프레임이 처리되지 않으면, 클라이언트는 푸시 스트림 읽기를 중단하고 이미 읽은 데이터를 버려야 한다(SHOULD).

푸시 스트림 데이터는 클라이언트가 푸시를 취소한 뒤에 도착할 수도 있다. 이 경우 클라이언트는 오류 코드 H3_REQUEST_CANCELLED로 스트림 읽기를 중단할 수 있다. 이는 서버에 추가 데이터를 전송하지 말도록 요청하고, 그런 데이터는 받는 즉시 버린다는 것을 나타낸다.

캐시 가능한 푸시된 응답([HTTP-CACHING]의 3장 참고)은 클라이언트가 HTTP 캐시를 구현한다면 저장할 수 있다. 푸시된 응답은 그 응답을 받은 시점에 원본 서버에서 성공적으로 검증된 것으로 간주한다(예: “no-cache” 캐시 응답 지시자가 있는 경우. [HTTP-CACHING]의 5.2.2.4절 참고).

캐시 가능하지 않은 푸시된 응답은 어떤 HTTP 캐시에도 저장해서는 안 된다(MUST NOT). 이런 응답은 애플리케이션에 따로 제공할 수 있다(MAY).

5. 연결 종료

HTTP/3 연결은 한번 수립되면 닫힐 때까지 오랜 시간에 걸쳐 많은 요청과 응답에 쓸 수 있다. 연결 종료는 여러 가지 방식으로 일어날 수 있다.

5.1. 유휴 연결

각 QUIC 엔드포인트는 핸드셰이크 중에 유휴 타임아웃(idle timeout)을 선언한다. QUIC 연결이 이 시간보다 오래 유휴 상태로(패킷을 받지 않은 채) 있으면, 피어는 연결이 닫혔다고 가정한다. 기존 연결이 QUIC 핸드셰이크 중에 협상된 유휴 타임아웃보다 오래 유휴 상태였다면, HTTP/3 구현은 새 요청을 위해 새 HTTP/3 연결을 열 필요가 있을 것이다. 그리고 유휴 타임아웃에 가까워지고 있다면 그렇게 해야 한다(SHOULD). [QUIC-TRANSPORT]의 10.1절을 참고하라.

HTTP 클라이언트는 [QUIC-TRANSPORT]의 10.1.2절에 설명된 대로, 요청이나 서버 푸시에 대한 응답이 아직 오지 않은 동안에는 연결을 열어 두도록 전송 계층에 요청할 것으로 기대된다. 클라이언트가 서버의 응답을 기다리고 있지 않다면, 필요하지 않을 수도 있는 연결을 유지하는 데 노력을 들이기보다 유휴 연결이 타임아웃되게 두는 편이 낫다. 게이트웨이는 서버로의 연결 수립에 드는 지연 시간 비용을 치르기보다, 필요할 것을 예상하고 연결을 유지할 수 있다(MAY). 서버는 연결을 능동적으로 열어 두지 않아야 한다(SHOULD NOT).

5.2. 연결 종료 절차

연결이 유휴 상태가 아니더라도, 어느 엔드포인트든 연결 사용을 멈추고 우아한 연결 종료(graceful connection close)를 시작하기로 할 수 있다. 엔드포인트는 GOAWAY 프레임을 보내 HTTP/3 연결의 우아한 종료를 시작한다. GOAWAY 프레임에는 이 연결에서 처리되었거나 처리될 수도 있는 요청이나 푸시의 범위를 수신자에게 알려 주는 식별자가 들어 있다. 서버는 클라이언트가 시작한 양방향 스트림 ID를 보내고, 클라이언트는 푸시 ID를 보낸다. 지정된 식별자 이상인 요청이나 푸시는 GOAWAY를 보낸 쪽이 거부한다(4.1.1절). 처리된 요청이나 푸시가 없다면 이 식별자는 0일 수 있다(MAY).

GOAWAY 프레임의 정보로 클라이언트와 서버는 HTTP/3 연결이 종료되기 전에 어떤 요청이나 푸시가 받아들여졌는지 합의할 수 있다. GOAWAY 프레임을 보내면, 엔드포인트는 영향을 받는 스트림의 전송 상태를 정리하기 위해, 지정한 식별자 이상의 식별자를 가진 요청이나 푸시를 모두 명시적으로 취소(4.1.1절과 7.2.3절 참고)해야 한다(SHOULD). 엔드포인트는 요청이나 푸시가 더 도착하면 계속 그렇게 해야 한다(SHOULD).

엔드포인트는 피어에게서 GOAWAY 프레임을 받은 뒤에는 그 연결에서 새 요청을 시작하거나 새 푸시를 약속해서는 안 된다(MUST NOT). 클라이언트는 추가 요청을 보내기 위해 새 연결을 수립할 수 있다(MAY).

일부 요청이나 푸시는 이미 전송 중일 수도 있다.

서버는 연결을 닫는다는 것을 미리 알면, 미리 알리는 시간이 짧더라도 GOAWAY 프레임을 보내야 한다(SHOULD). 그래야 원격 피어가 요청이 일부 처리되었는지 알 수 있다. 예를 들어 서버가 QUIC 연결을 닫는 것과 동시에 HTTP 클라이언트가 POST를 보낸다면, 서버가 어떤 스트림을 처리했을 수도 있는지 나타내는 GOAWAY 프레임을 보내지 않는 한, 클라이언트는 서버가 그 POST 요청을 처리하기 시작했는지 알 수 없다.

엔드포인트는 서로 다른 식별자를 나타내는 GOAWAY 프레임을 여러 번 보낼 수 있지만(MAY), 각 프레임의 식별자는 앞선 어떤 프레임의 식별자보다도 커서는 안 된다(MUST NOT). 클라이언트가 처리되지 않은 요청을 이미 다른 HTTP 연결에서 재시도했을 수도 있기 때문이다. 앞서 받은 것보다 큰 식별자를 담은 GOAWAY를 받으면 반드시 H3_ID_ERROR 유형의 연결 오류로 처리해야 한다(MUST).

연결을 우아하게 종료하려는 엔드포인트는 가능한 최댓값(서버는 2^62-4, 클라이언트는 2^62-1)으로 설정한 GOAWAY 프레임을 보낼 수 있다. 이렇게 하면 피어가 확실히 새 요청이나 푸시를 더 만들지 않게 된다. 전송 중인 요청이나 푸시가 도착할 시간을 준 뒤, 엔드포인트는 연결이 끝나기 전에 받아들일 수도 있는 요청이나 푸시를 나타내는 GOAWAY 프레임을 또 보낼 수 있다. 이렇게 하면 요청을 잃지 않고 연결을 깔끔하게 종료할 수 있다.

클라이언트는 자신이 보내는 GOAWAY의 Push ID 필드 값을 고를 때 더 자유롭다. 값 2^62-1은 서버가 이미 약속한 푸시를 계속 이행할 수 있음을 나타낸다. 그보다 작은 값은 클라이언트가 이 값 이상의 푸시 ID를 가진 푸시를 거부할 것임을 나타낸다. 서버와 마찬가지로 클라이언트는, 지정한 푸시 ID가 앞서 보낸 어떤 값보다도 크지 않은 한, 이어서 GOAWAY 프레임을 보낼 수 있다(MAY).

어떤 요청이나 푸시가 도착하더라도 처리되거나 받아들여지지 않을 것임을 GOAWAY가 나타내는 경우에도, 기반 전송 자원은 여전히 남아 있다. 이 요청을 시작한 엔드포인트는 전송 상태를 정리하기 위해 그 요청을 취소할 수 있다.

받아들인 요청과 푸시가 모두 처리되면, 엔드포인트는 연결이 유휴 상태가 되게 둘 수도 있고, 연결의 즉시 종료를 시작할 수 있다(MAY). 우아한 종료를 마친 엔드포인트는 연결을 닫을 때 H3_NO_ERROR 오류 코드를 써야 한다(SHOULD).

클라이언트가 사용할 수 있는 양방향 스트림 ID를 요청으로 모두 써 버렸다면, 클라이언트가 더 이상 요청을 할 수 없으므로 서버는 GOAWAY 프레임을 보낼 필요가 없다.

5.3. 애플리케이션의 즉시 종료

HTTP/3 구현은 언제든지 QUIC 연결을 즉시 닫을 수 있다. 그러면 애플리케이션 계층이 연결을 끝냈음을 나타내는 QUIC CONNECTION_CLOSE 프레임이 피어에게 전송된다. 이 프레임의 애플리케이션 오류 코드(application error code)는 연결이 닫히는 이유를 피어에게 알려 준다. HTTP/3에서 연결을 닫을 때 쓸 수 있는 오류 코드는 8장을 참고하라.

연결을 닫기 전에, 클라이언트가 일부 요청을 재시도할 수 있도록 GOAWAY 프레임을 보낼 수 있다(MAY). GOAWAY 프레임을 QUIC CONNECTION_CLOSE 프레임과 같은 패킷에 넣으면 클라이언트가 그 프레임을 받을 가능성이 높아진다.

명시적으로 닫히지 않은 열린 스트림이 있다면, 그 스트림은 연결이 닫힐 때 암묵적으로 닫힌다. [QUIC-TRANSPORT]의 10.2절을 참고하라.

5.4. 전송 계층의 종료

여러 가지 이유로, QUIC 전송 계층은 연결이 끝났다는 것을 애플리케이션 계층에 알릴 수도 있다. 이는 피어의 명시적 종료, 전송 수준 오류, 또는 연결을 끊는 네트워크 토폴로지 변화 때문일 수도 있다.

연결이 GOAWAY 프레임 없이 끝나면, 클라이언트는 전체든 일부든 보낸 요청은 모두 처리되었을 수도 있다고 반드시 가정해야 한다(MUST).

6. 스트림 매핑과 사용

QUIC 스트림은 바이트를 신뢰할 수 있게 순서대로 전달하지만, 다른 스트림의 바이트와의 전달 순서는 보장하지 않는다. QUIC 버전 1에서 HTTP 프레임이 든 스트림 데이터는 QUIC STREAM 프레임에 담겨 전달되지만, 이 프레이밍은 HTTP 프레임 계층에는 보이지 않는다. 전송 계층은 받은 스트림 데이터를 버퍼에 담고 순서를 맞춰, 애플리케이션에 신뢰할 수 있는 바이트 스트림을 제공한다. QUIC은 스트림 안에서 순서가 바뀐 전달을 허용하지만, HTTP/3은 이 기능을 쓰지 않는다.

QUIC 스트림은 시작한 쪽에서 수신자 쪽으로만 데이터를 나르는 단방향 스트림이거나, 양쪽 방향으로 데이터를 나르는 양방향 스트림이다. 스트림은 클라이언트와 서버 어느 쪽이든 시작할 수 있다. QUIC 스트림에 대한 자세한 내용은 [QUIC-TRANSPORT]의 2장을 참고하라.

HTTP 필드와 데이터를 QUIC으로 보낼 때는 QUIC 계층이 스트림 관리의 대부분을 처리한다. HTTP는 QUIC을 쓸 때 별도의 다중화를 할 필요가 없다. QUIC 스트림으로 보낸 데이터는 언제나 특정 HTTP 트랜잭션이나 HTTP/3 연결 전체의 맥락에 대응하기 때문이다.

6.1. 양방향 스트림

클라이언트가 시작한 양방향 스트림은 모두 HTTP 요청과 응답에 쓰인다. 양방향 스트림을 쓰면 응답을 요청과 쉽게 짝지을 수 있다. 이런 스트림을 요청 스트림이라고 부른다.

이는 클라이언트의 첫 요청이 QUIC 스트림 0에서 일어나고, 그 뒤의 요청은 스트림 4, 8 등에서 일어난다는 뜻이다. 이런 스트림을 열 수 있도록, HTTP/3 서버는 허용 스트림 수와 초기 스트림 흐름 제어 윈도우(flow-control window)의 최솟값을 0이 아닌 값으로 정해야 한다(SHOULD). 병렬성을 불필요하게 제한하지 않도록, 한 번에 적어도 100개의 요청 스트림을 허용해야 한다(SHOULD).

HTTP/3은 서버가 시작한 양방향 스트림을 쓰지 않는다. 다만 확장이 이런 스트림의 용도를 정의할 수도 있다. 클라이언트는 그런 확장이 협상된 경우가 아니라면, 서버가 시작한 양방향 스트림을 받는 것을 반드시 H3_STREAM_CREATION_ERROR 유형의 연결 오류로 처리해야 한다(MUST).

6.2. 단방향 스트림

단방향 스트림은 어느 방향이든 여러 용도로 쓰인다. 용도는 스트림 유형(stream type)으로 나타내며, 스트림 유형은 스트림 맨 앞에 가변 길이 정수로 보낸다. 이 정수 뒤에 오는 데이터의 형식과 구조는 스트림 유형이 정한다.

Unidirectional Stream Header {
  Stream Type (i),
}

그림 1: 단방향 스트림 헤더

이 문서는 두 가지 스트림 유형을 정의한다. 제어 스트림(6.2.1절)과 푸시 스트림(6.2.2절)이다. [QPACK]은 스트림 유형 두 가지를 더 정의한다. 그 밖의 스트림 유형은 HTTP/3 확장이 정의할 수 있다. 자세한 내용은 9장을 참고하라. 일부 스트림 유형은 예약되어 있다(6.2.3절).

HTTP/3 연결의 수명 초기 단계의 성능은 단방향 스트림을 만들고 그 위에서 데이터를 주고받는 일에 민감하다. 스트림 수나 이 스트림들의 흐름 제어 윈도우를 지나치게 제한하는 엔드포인트는 원격 피어가 일찍 한도에 도달해 막힐 가능성을 높인다. 특히 구현은 원격 피어가 자신에게 허용된 단방향 스트림 가운데 일부로 예약된 스트림 동작(6.2.3절)을 시험하고 싶어 할 수도 있다는 점을 고려하는 것이 좋다.

각 엔드포인트는 HTTP 제어 스트림을 위해 단방향 스트림을 적어도 하나 만들 필요가 있다. QPACK에는 단방향 스트림이 두 개 더 필요하고, 다른 확장에는 스트림이 더 필요할 수도 있다. 따라서 클라이언트와 서버가 보내는 전송 매개 변수(transport parameter)는 반드시 피어가 단방향 스트림을 적어도 세 개 만들 수 있게 허용해야 한다(MUST). 또한 이 전송 매개 변수는 각 단방향 스트림에 적어도 1,024바이트의 흐름 제어 크레딧(credit)을 제공해야 한다(SHOULD).

피어가 필수 단방향 스트림을 만들기 전에 초기 크레딧을 모두 써 버렸다면, 엔드포인트에게는 피어가 단방향 스트림을 더 만들 수 있도록 크레딧을 추가로 줄 의무가 없다는 점에 유의하라. 엔드포인트는 HTTP 제어 스트림과, 필수 확장이 요구하는 단방향 스트림(QPACK 인코더 스트림과 디코더 스트림 등)을 먼저 만들고, 그 뒤에 피어가 허용하는 만큼 스트림을 더 만들어야 한다(SHOULD).

스트림 헤더가 나타내는 스트림 유형을 수신자가 지원하지 않으면, 그 의미를 알 수 없으므로 스트림의 나머지를 소비할 수 없다. 알 수 없는 스트림 유형을 받은 수신자는 반드시 스트림 읽기를 중단하거나, 들어오는 데이터를 더 처리하지 않고 버려야 한다(MUST). 읽기를 중단한다면, 수신자는 H3_STREAM_CREATION_ERROR 오류 코드나 예약된 오류 코드(8.1절)를 써야 한다(SHOULD). 수신자는 알 수 없는 스트림 유형을 어떤 종류의 연결 오류로도 여겨서는 안 된다(MUST NOT).

일부 스트림 유형은 연결 상태에 영향을 줄 수 있으므로, 수신자는 스트림 유형을 읽기 전에 들어오는 단방향 스트림의 데이터를 버리지 않아야 한다(SHOULD NOT).

구현은 피어가 지원하는지 알기 전에 스트림 유형을 보낼 수 있다(MAY). 그러나 QPACK이나 다른 확장을 포함해 기존 프로토콜 구성 요소의 상태나 의미를 바꿀 수 있는 스트림 유형은 피어가 지원한다는 것을 알기 전까지 보내서는 안 된다(MUST NOT).

송신자는 따로 명시되지 않았다면 단방향 스트림을 닫거나 리셋할 수 있다. 수신자는 단방향 스트림 헤더를 받기 전에 단방향 스트림이 닫히거나 리셋되는 것을 반드시 용인해야 한다(MUST).

6.2.1. 제어 스트림

제어 스트림은 스트림 유형 0x00으로 나타낸다. 이 스트림의 데이터는 7.2절에 정의된 HTTP/3 프레임으로 이루어진다.

각 쪽은 연결 초기에 반드시 제어 스트림을 하나 시작하고, 이 스트림의 첫 프레임으로 자신의 SETTINGS 프레임을 보내야 한다(MUST). 제어 스트림의 첫 프레임이 다른 프레임 유형이면 반드시 H3_MISSING_SETTINGS 유형의 연결 오류로 처리해야 한다(MUST). 피어마다 제어 스트림은 하나만 허용된다. 제어 스트림이라고 주장하는 두 번째 스트림을 받으면 반드시 H3_STREAM_CREATION_ERROR 유형의 연결 오류로 처리해야 한다(MUST). 송신자는 제어 스트림을 닫아서는 안 되고(MUST NOT), 수신자는 송신자에게 제어 스트림을 닫도록 요청해서는 안 된다(MUST NOT). 어느 쪽의 제어 스트림이든 어느 시점에 닫히면 반드시 H3_CLOSED_CRITICAL_STREAM 유형의 연결 오류로 처리해야 한다(MUST). 연결 오류는 8장에 설명되어 있다.

제어 스트림의 내용은 다른 스트림의 동작을 관리하는 데 쓰이므로, 엔드포인트는 피어의 제어 스트림이 막히지 않도록 흐름 제어 크레딧을 충분히 제공해야 한다(SHOULD).

제어 스트림에는 양방향 스트림 하나 대신 단방향 스트림 한 쌍을 쓴다. 이렇게 하면 어느 피어든 데이터를 보낼 수 있게 되는 즉시 보낼 수 있다. QUIC 연결에서 0-RTT를 쓸 수 있는지에 따라, 클라이언트가 먼저 스트림 데이터를 보낼 수도 있고, 서버가 먼저 보낼 수도 있다.

6.2.2. 푸시 스트림

서버 푸시는 HTTP/2에서 도입된 선택적 기능으로, 요청이 있기 전에 서버가 응답을 시작할 수 있게 한다. 자세한 내용은 4.6절을 참고하라.

푸시 스트림은 스트림 유형 0x01로 나타내며, 그 뒤에 이 스트림이 이행하는 약속의 푸시 ID가 가변 길이 정수로 인코딩되어 온다. 이 스트림의 나머지 데이터는 7.2절에 정의된 HTTP/3 프레임으로 이루어지며, 4.1절에 정의된 대로 HTTP 중간 응답 0개 이상과 그 뒤에 오는 HTTP 최종 응답 하나로 약속된 서버 푸시를 이행한다. 서버 푸시와 푸시 ID는 4.6절에 설명되어 있다.

서버만 푸시할 수 있다. 서버가 클라이언트가 시작한 푸시 스트림을 받으면, 반드시 H3_STREAM_CREATION_ERROR 유형의 연결 오류로 처리해야 한다(MUST).

Push Stream Header {
  Stream Type (i) = 0x01,
  Push ID (i),
}

그림 2: 푸시 스트림 헤더

클라이언트는 푸시 스트림 헤더를 읽기 전에 푸시 스트림 읽기를 중단하지 않아야 한다(SHOULD NOT). 그렇게 하면 어떤 푸시 ID가 이미 소비되었는지를 두고 클라이언트와 서버의 판단이 어긋날 수 있다.

각 푸시 ID는 푸시 스트림 헤더에서 반드시 한 번만 쓰여야 한다(MUST). 푸시 스트림 헤더에 다른 푸시 스트림 헤더에서 쓰인 푸시 ID가 들어 있음을 감지하면, 클라이언트는 이를 반드시 H3_ID_ERROR 유형의 연결 오류로 처리해야 한다(MUST).

6.2.3. 예약된 스트림 유형

N이 음이 아닌 정수일 때 0x1f * N + 0x21 꼴의 스트림 유형은, 알 수 없는 유형은 무시해야 한다는 요구 사항을 시험하기 위해 예약되어 있다. 이런 스트림에는 의미가 없으며, 애플리케이션 계층 패딩(padding)이 필요할 때 보낼 수 있다. 또한 현재 전송되는 데이터가 없는 연결에서도 보낼 수 있다(MAY). 엔드포인트는 이런 스트림을 받았을 때 어떤 의미가 있는 것으로 여겨서는 안 된다(MUST NOT).

스트림의 페이로드와 길이는 보내는 구현이 원하는 어떤 방식으로든 정한다. 예약된 스트림 유형을 보낼 때, 구현은 스트림을 깔끔하게 끝내거나 리셋할 수 있다(MAY). 스트림을 리셋할 때는 H3_NO_ERROR 오류 코드나 예약된 오류 코드(8.1절)를 써야 한다(SHOULD).

7. HTTP 프레임 계층

HTTP 프레임은 6장에 설명한 대로 QUIC 스트림으로 전달된다. HTTP/3은 제어 스트림, 요청 스트림, 푸시 스트림이라는 세 가지 스트림 유형을 정의한다. 이 장은 HTTP/3 프레임 형식과 각 프레임이 허용되는 스트림 유형을 설명한다. 개요는 표 1을 참고하라. HTTP/2 프레임과 HTTP/3 프레임의 비교는 부록 A.2에 있다.

프레임 제어 스트림 요청 스트림 푸시 스트림 절
DATA 아니요 예 예 7.2.1절
HEADERS 아니요 예 예 7.2.2절
CANCEL_PUSH 예 아니요 아니요 7.2.3절
SETTINGS 예 (1) 아니요 아니요 7.2.4절
PUSH_PROMISE 아니요 예 아니요 7.2.5절
GOAWAY 예 아니요 아니요 7.2.6절
MAX_PUSH_ID 예 아니요 아니요 7.2.7절
예약됨 예 예 예 7.2.8절

표 1: HTTP/3 프레임과 스트림 유형 개요

SETTINGS 프레임은 제어 스트림의 첫 프레임으로만 나타날 수 있다. 표 1에서는 이를 (1)로 표시했다. 구체적인 지침은 해당 절에 있다.

QUIC 프레임과 달리 HTTP/3 프레임은 여러 패킷에 걸칠 수 있다는 점에 유의하라.

7.1. 프레임 구성

모든 프레임은 다음 형식을 가진다.

HTTP/3 Frame Format {
  Type (i),
  Length (i),
  Frame Payload (..),
}

그림 3: HTTP/3 프레임 형식

프레임에는 다음 필드가 들어 있다.

각 프레임의 페이로드에는 반드시 그 프레임의 설명에서 밝힌 필드가 정확히 들어 있어야 한다(MUST). 밝힌 필드 뒤에 바이트가 더 있는 프레임 페이로드나, 밝힌 필드가 끝나기 전에 끝나는 프레임 페이로드는 반드시 H3_FRAME_ERROR 유형의 연결 오류로 처리해야 한다(MUST). 특히 중복된 길이 인코딩은 서로 모순되지 않는지 반드시 검증해야 한다(MUST). 10.8절을 참고하라.

스트림이 깔끔하게 끝났는데 그 스트림의 마지막 프레임이 잘려 있다면, 반드시 H3_FRAME_ERROR 유형의 연결 오류로 처리해야 한다(MUST). 갑자기 끝나는 스트림은 프레임의 어느 지점에서든 리셋될 수도 있다.

7.2. 프레임 정의

7.2.1. DATA

DATA 프레임(type=0x00)은 HTTP 요청이나 응답 콘텐츠에 연관된 임의의 가변 길이 바이트 시퀀스를 전달한다.

DATA 프레임은 반드시 HTTP 요청이나 응답에 연관되어야 한다(MUST). 제어 스트림에서 DATA 프레임을 받으면, 수신자는 반드시 H3_FRAME_UNEXPECTED 유형의 연결 오류로 응답해야 한다(MUST).

DATA Frame {
  Type (i) = 0x00,
  Length (i),
  Data (..),
}

그림 4: DATA 프레임

7.2.2. HEADERS

HEADERS 프레임(type=0x01)은 QPACK으로 인코딩된 HTTP 필드 섹션을 전달하는 데 쓰인다. 자세한 내용은 [QPACK]을 참고하라.

HEADERS Frame {
  Type (i) = 0x01,
  Length (i),
  Encoded Field Section (..),
}

그림 5: HEADERS 프레임

HEADERS 프레임은 요청 스트림이나 푸시 스트림에서만 보낼 수 있다. 제어 스트림에서 HEADERS 프레임을 받으면, 수신자는 반드시 H3_FRAME_UNEXPECTED 유형의 연결 오류로 응답해야 한다(MUST).

7.2.3. CANCEL_PUSH

CANCEL_PUSH 프레임(type=0x03)은 푸시 스트림을 받기 전에 서버 푸시의 취소를 요청하는 데 쓰인다. CANCEL_PUSH 프레임은 가변 길이 정수로 인코딩한 푸시 ID(4.6절 참고)로 서버 푸시를 식별한다.

클라이언트가 CANCEL_PUSH 프레임을 보내는 것은 약속된 리소스를 받고 싶지 않다는 뜻이다. 서버는 그 리소스 전송을 중단해야 하지만(SHOULD), 그 방법은 대응하는 푸시 스트림의 상태에 따라 다르다. 서버가 아직 푸시 스트림을 만들지 않았다면 만들지 않는다. 푸시 스트림이 열려 있다면, 서버는 그 스트림을 갑자기 끝내야 한다(SHOULD). 푸시 스트림이 이미 끝났다면, 서버는 그래도 스트림을 갑자기 끝낼 수 있고(MAY), 아무 동작도 하지 않을 수 있다(MAY).

서버는 앞서 보낸 약속을 이행하지 않겠다는 것을 나타내기 위해 CANCEL_PUSH 프레임을 보낸다. 클라이언트는 약속된 응답을 이미 받아 처리한 경우가 아니라면, 대응하는 약속이 이행되리라고 기대할 수 없다. 푸시 스트림이 열렸는지와 관계없이, 서버는 약속을 이행하지 않기로 판단하면 CANCEL_PUSH 프레임을 보내야 한다(SHOULD). 스트림이 이미 열렸다면, 서버는 오류 코드 H3_REQUEST_CANCELLED로 그 스트림의 전송을 중단할 수 있다.

CANCEL_PUSH 프레임을 보내는 것은 기존 푸시 스트림의 상태에 직접 영향을 주지 않는다. 클라이언트는 대응하는 푸시 스트림을 이미 받았다면 CANCEL_PUSH 프레임을 보내지 않아야 한다(SHOULD NOT). 서버가 CANCEL_PUSH를 아직 처리하지 않았을 수도 있으므로, 클라이언트가 CANCEL_PUSH 프레임을 보낸 뒤에 푸시 스트림이 도착할 수 있다. 클라이언트는 오류 코드 H3_REQUEST_CANCELLED로 그 스트림 읽기를 중단해야 한다(SHOULD).

CANCEL_PUSH 프레임은 제어 스트림으로 보낸다. 제어 스트림이 아닌 스트림에서 CANCEL_PUSH 프레임을 받으면 반드시 H3_FRAME_UNEXPECTED 유형의 연결 오류로 처리해야 한다(MUST).

CANCEL_PUSH Frame {
  Type (i) = 0x03,
  Length (i),
  Push ID (i),
}

그림 6: CANCEL_PUSH 프레임

CANCEL_PUSH 프레임은 가변 길이 정수로 인코딩한 푸시 ID를 담는다. Push ID 필드는 취소하는 서버 푸시를 식별한다. 4.6절을 참고하라. 연결에서 현재 허용되는 것보다 큰 푸시 ID를 참조하는 CANCEL_PUSH 프레임을 받으면 반드시 H3_ID_ERROR 유형의 연결 오류로 처리해야 한다(MUST).

클라이언트가 받은 CANCEL_PUSH 프레임은, 순서가 바뀌어서 아직 PUSH_PROMISE 프레임에 나오지 않은 푸시 ID를 가리킬 수도 있다. 서버가 아직 PUSH_PROMISE 프레임에 나오지 않은 푸시 ID에 대한 CANCEL_PUSH 프레임을 받으면 반드시 H3_ID_ERROR 유형의 연결 오류로 처리해야 한다(MUST).

7.2.4. SETTINGS

SETTINGS 프레임(type=0x04)은 피어 동작에 대한 선호와 제약처럼, 엔드포인트가 통신하는 방식에 영향을 주는 구성 매개 변수(configuration parameter)를 전달한다. 개별 SETTINGS 매개 변수는 “설정(setting)”이라고도 부를 수 있다. 각 설정 매개 변수의 식별자와 값은 “설정 식별자(setting identifier)”와 “설정 값(setting value)”이라고 부를 수 있다.

SETTINGS 프레임은 언제나 HTTP/3 연결 전체에 적용되며, 개별 스트림에는 적용되지 않는다. 각 피어는 각 제어 스트림(6.2.1절 참고)의 첫 프레임으로 반드시 SETTINGS 프레임을 보내야 하며(MUST), 그 뒤에는 보내서는 안 된다(MUST NOT). 엔드포인트가 제어 스트림에서 두 번째 SETTINGS 프레임을 받으면, 반드시 H3_FRAME_UNEXPECTED 유형의 연결 오류로 응답해야 한다(MUST).

SETTINGS 프레임은 제어 스트림이 아닌 스트림으로 보내서는 안 된다(MUST NOT). 엔드포인트가 다른 스트림에서 SETTINGS 프레임을 받으면, 반드시 H3_FRAME_UNEXPECTED 유형의 연결 오류로 응답해야 한다(MUST).

SETTINGS 매개 변수는 협상되지 않는다. 이 매개 변수는 받는 피어가 쓸 수 있는, 보내는 피어의 특성을 나타낸다. 그러나 SETTINGS를 사용함으로써 암묵적인 협상이 이루어질 수는 있다. 각 피어가 SETTINGS로 자신이 지원하는 값의 집합을 알리는 것이다. 각 피어가 두 집합을 어떻게 결합해 쓸 값을 정하는지는 그 설정의 정의에서 설명한다. SETTINGS는 그 선택이 언제 효력을 갖는지 식별하는 메커니즘을 제공하지 않는다.

같은 매개 변수에 대해 각 피어가 서로 다른 값을 알릴 수 있다. 예를 들어 클라이언트는 매우 큰 응답 필드 섹션도 기꺼이 소비하려 할 수도 있지만, 서버는 요청 크기에 더 신중하다.

같은 설정 식별자가 SETTINGS 프레임에 두 번 이상 나타나서는 안 된다(MUST NOT). 수신자는 중복된 설정 식별자가 있는 것을 H3_SETTINGS_ERROR 유형의 연결 오류로 처리할 수 있다(MAY).

SETTINGS 프레임의 페이로드는 매개 변수 0개 이상으로 이루어진다. 각 매개 변수는 설정 식별자와 값으로 이루어지며, 둘 다 QUIC 가변 길이 정수로 인코딩된다.

Setting {
  Identifier (i),
  Value (i),
}

SETTINGS Frame {
  Type (i) = 0x04,
  Length (i),
  Setting (..) ...,
}

그림 7: SETTINGS 프레임

구현은 이해하지 못하는 식별자를 가진 매개 변수를 반드시 무시해야 한다(MUST).

역주: 정오표 8999(Reported, 미확인)는 바로 위 문장에 “7.2.4.1절에 명시된 예약된 설정 식별자는 제외한다”를 더해야 한다고 지적한다. 확인되지 않은 정오표이므로 번역에는 반영하지 않았다.

7.2.4.1. 정의된 SETTINGS 매개 변수

HTTP/3에는 다음 설정이 정의되어 있다.

N이 음이 아닌 정수일 때 0x1f * N + 0x21 꼴의 설정 식별자는, 알 수 없는 식별자는 무시해야 한다는 요구 사항을 시험하기 위해 예약되어 있다. 이런 설정에는 정의된 의미가 없다. 엔드포인트는 자신의 SETTINGS 프레임에 이런 설정을 적어도 하나 넣어야 한다(SHOULD). 엔드포인트는 이런 설정을 받았을 때 어떤 의미가 있는 것으로 여겨서는 안 된다(MUST NOT).

이런 설정에는 정의된 의미가 없으므로, 설정 값은 구현이 고르는 어떤 값이든 될 수 있다.

[HTTP/2]에 정의되었으나 대응하는 HTTP/3 설정이 없는 설정 식별자도 예약되어 있다(11.2.2절). 이런 예약된 설정은 보내서는 안 되며(MUST NOT), 받으면 반드시 H3_SETTINGS_ERROR 유형의 연결 오류로 처리해야 한다(MUST).

HTTP/3 확장은 설정을 더 정의할 수 있다. 자세한 내용은 9장을 참고하라.

7.2.4.2. 초기화

HTTP 구현은 피어의 설정에 대해 현재 알고 있는 바에 따르면 유효하지 않을 프레임이나 요청을 보내서는 안 된다(MUST NOT).

모든 설정은 초기값에서 시작한다. 설정을 담은 패킷은 손실되거나 지연될 수 있으므로, 각 엔드포인트는 피어의 SETTINGS 프레임이 도착하기 전에는 이 초기값을 사용해 메시지를 보내야 한다(SHOULD). SETTINGS 프레임이 도착하면 설정은 모두 새 값으로 바뀐다.

이렇게 하면 메시지를 보내기 전에 SETTINGS 프레임을 기다릴 필요가 없다. 엔드포인트는 SETTINGS 프레임을 보내기 전에 피어에게서 어떤 데이터를 받는 것도 요구해서는 안 된다(MUST NOT). 설정은 전송 계층이 데이터를 보낼 준비가 되는 즉시 반드시 보내야 한다(MUST).

서버의 경우, 각 클라이언트 설정의 초기값은 기본값이다.

1-RTT QUIC 연결을 쓰는 클라이언트의 경우, 각 서버 설정의 초기값은 기본값이다. 서버가 SETTINGS를 즉시 보내더라도, SETTINGS를 담은 패킷을 QUIC이 처리하기 전에 1-RTT 키는 언제나 사용할 수 있게 된다. 클라이언트는 요청을 보내기 전에 SETTINGS가 도착하기를 한없이 기다리지 않아야 하지만(SHOULD NOT), 첫 요청을 보내기 전에 SETTINGS를 처리할 가능성을 높이기 위해 받은 데이터그램을 처리해야 한다(SHOULD).

0-RTT QUIC 연결을 쓸 때, 각 서버 설정의 초기값은 이전 세션에서 쓴 값이다. 클라이언트는 재개(resumption) 정보를 받은 HTTP/3 연결에서 서버가 제공한 설정을 저장해야 하지만(SHOULD), 특정 상황에서는(예: SETTINGS 프레임보다 세션 티켓(session ticket)을 먼저 받았을 때) 설정을 저장하지 않기로 할 수 있다(MAY). 클라이언트는 0-RTT를 시도할 때 저장한 설정(저장한 값이 없으면 기본값)을 반드시 따라야 한다(MUST). 서버가 새 설정을 제공하면, 클라이언트는 반드시 그 값을 따라야 한다(MUST).

서버는 자신이 알린 설정을 기억해 두거나, 티켓에 그 값의 무결성이 보호된 사본을 저장해 두었다가 0-RTT 데이터를 받아들일 때 그 정보를 복원할 수 있다. 서버는 0-RTT 데이터를 받아들일지 판단할 때 HTTP/3 설정 값을 사용한다. 서버는 클라이언트가 기억하는 설정이 서버의 현재 설정과 호환된다고 판단할 수 없으면 0-RTT 데이터를 받아들여서는 안 된다(MUST NOT). 기억된 설정을 따르는 클라이언트가 서버의 현재 설정을 위반하지 않는다면, 그 기억된 설정은 호환되는 것이다.

서버는 0-RTT를 받아들인 뒤 자신의 SETTINGS 프레임에서 다른 설정을 제공할 수 있다(MAY). 서버가 0-RTT 데이터를 받아들였다면, 서버의 SETTINGS 프레임은 클라이언트가 0-RTT 데이터로 위반하게 될 수도 있는 방식으로 한도를 줄이거나 값을 바꿔서는 안 된다(MUST NOT). 서버는 기본값과 다른 설정을 반드시 모두 넣어야 한다(MUST). 서버가 0-RTT를 받아들인 뒤 앞서 지정한 설정과 호환되지 않는 설정을 보내면, 반드시 H3_SETTINGS_ERROR 유형의 연결 오류로 처리해야 한다(MUST). 서버가 0-RTT를 받아들인 뒤, 앞서 기본값이 아닌 값으로 지정했고 클라이언트가 이해하는 설정 값(예약된 설정 식별자는 제외)을 빠뜨린 SETTINGS 프레임을 보내면, 반드시 H3_SETTINGS_ERROR 유형의 연결 오류로 처리해야 한다(MUST).

7.2.5. PUSH_PROMISE

PUSH_PROMISE 프레임(type=0x05)은 요청 스트림에서 서버가 클라이언트로 약속된 요청 헤더 섹션을 전달하는 데 쓰인다.

PUSH_PROMISE Frame {
  Type (i) = 0x05,
  Length (i),
  Push ID (i),
  Encoded Field Section (..),
}

그림 8: PUSH_PROMISE 프레임

페이로드는 다음으로 이루어진다.

서버는 클라이언트가 MAX_PUSH_ID 프레임(7.2.7절)으로 제공한 것보다 큰 푸시 ID를 써서는 안 된다(MUST NOT). 클라이언트는 자신이 알린 것보다 큰 푸시 ID를 담은 PUSH_PROMISE 프레임을 받으면 반드시 H3_ID_ERROR 유형의 연결 오류로 처리해야 한다(MUST).

서버는 같은 푸시 ID를 여러 PUSH_PROMISE 프레임에서 쓸 수 있다(MAY). 그렇다면 압축 해제된 요청 헤더 집합은 반드시 같은 필드를 같은 순서로 담아야 하며(MUST), 각 필드의 이름과 값은 반드시 정확히 일치해야 한다(MUST). 클라이언트는 여러 번 약속된 리소스에 대해 요청 헤더 섹션을 비교해야 한다(SHOULD). 클라이언트가 이미 약속된 푸시 ID를 받고 불일치를 감지하면, 반드시 H3_GENERAL_PROTOCOL_ERROR 유형의 연결 오류로 응답해야 한다(MUST). 압축 해제된 필드 섹션이 정확히 일치하면, 클라이언트는 푸시된 콘텐츠를 PUSH_PROMISE 프레임을 받은 각 스트림과 연관시켜야 한다(SHOULD).

같은 푸시 ID를 중복해서 참조할 수 있게 하는 것은 주로 동시 요청 때문에 생기는 중복을 줄이기 위해서다. 서버는 오랜 기간에 걸쳐 푸시 ID를 재사용하는 것을 피해야 한다(SHOULD). 클라이언트는 서버 푸시 응답을 소비하고 나서 나중에 재사용하려고 보관해 두지 않을 가능성이 높다. 이미 소비하고 버린 푸시 ID를 쓰는 PUSH_PROMISE 프레임을 본 클라이언트는 그 약속을 무시할 수밖에 없다.

제어 스트림에서 PUSH_PROMISE 프레임을 받으면, 클라이언트는 반드시 H3_FRAME_UNEXPECTED 유형의 연결 오류로 응답해야 한다(MUST).

클라이언트는 PUSH_PROMISE 프레임을 보내서는 안 된다(MUST NOT). 서버는 PUSH_PROMISE 프레임을 받는 것을 반드시 H3_FRAME_UNEXPECTED 유형의 연결 오류로 처리해야 한다(MUST).

서버 푸시 메커니즘 전체에 대한 설명은 4.6절을 참고하라.

7.2.6. GOAWAY

GOAWAY 프레임(type=0x07)은 어느 엔드포인트든 HTTP/3 연결의 우아한 종료를 시작하는 데 쓰인다. GOAWAY를 쓰면 엔드포인트는 앞서 받은 요청과 푸시의 처리는 마저 끝내면서 새 요청이나 푸시를 받아들이는 것을 멈출 수 있다. 이로써 서버 유지 보수 같은 관리 작업이 가능해진다. GOAWAY 자체는 연결을 닫지 않는다.

GOAWAY Frame {
  Type (i) = 0x07,
  Length (i),
  Stream ID/Push ID (i),
}

그림 9: GOAWAY 프레임

GOAWAY 프레임은 언제나 제어 스트림으로 보낸다. 서버에서 클라이언트 방향으로는, 클라이언트가 시작한 양방향 스트림의 QUIC 스트림 ID를 가변 길이 정수로 인코딩해 담는다. 클라이언트는 다른 종류의 스트림 ID를 담은 GOAWAY 프레임을 받으면 반드시 H3_ID_ERROR 유형의 연결 오류로 처리해야 한다(MUST).

클라이언트에서 서버 방향으로는, GOAWAY 프레임이 가변 길이 정수로 인코딩한 푸시 ID를 담는다.

GOAWAY 프레임은 특정 스트림이 아니라 연결 전체에 적용된다. 클라이언트는 엔드포인트는 제어 스트림이 아닌 스트림에서 받은 GOAWAY 프레임을 반드시 H3_FRAME_UNEXPECTED 유형의 연결 오류로 처리해야 한다(MUST).

역주: 정오표 7780(Verified)에 따라 주어 “A client”를 “An endpoint”로 고친 내용이다.

GOAWAY 프레임의 사용에 대한 자세한 내용은 5.2절을 참고하라.

7.2.7. MAX_PUSH_ID

MAX_PUSH_ID 프레임(type=0x0d)은 서버가 시작할 수 있는 서버 푸시의 수를 클라이언트가 제어하는 데 쓰인다. 이 프레임은 서버가 PUSH_PROMISE와 CANCEL_PUSH 프레임에서 쓸 수 있는 푸시 ID의 최댓값을 정한다. 따라서 이 프레임은 QUIC 전송 계층이 유지하는 한도에 더해, 서버가 시작할 수 있는 푸시 스트림의 수도 제한한다.

MAX_PUSH_ID 프레임은 언제나 제어 스트림으로 보낸다. 다른 스트림에서 MAX_PUSH_ID 프레임을 받으면 반드시 H3_FRAME_UNEXPECTED 유형의 연결 오류로 처리해야 한다(MUST).

서버는 MAX_PUSH_ID 프레임을 보내서는 안 된다(MUST NOT). 클라이언트는 MAX_PUSH_ID 프레임을 받는 것을 반드시 H3_FRAME_UNEXPECTED 유형의 연결 오류로 처리해야 한다(MUST).

HTTP/3 연결이 만들어질 때 최대 푸시 ID는 정해져 있지 않다. 즉, 서버는 MAX_PUSH_ID 프레임을 받기 전에는 푸시할 수 없다. 약속된 서버 푸시의 수를 관리하려는 클라이언트는 서버가 서버 푸시를 이행하거나 취소함에 따라 MAX_PUSH_ID 프레임을 보내 최대 푸시 ID를 늘릴 수 있다.

MAX_PUSH_ID Frame {
  Type (i) = 0x0d,
  Length (i),
  Push ID (i),
}

그림 10: MAX_PUSH_ID 프레임

MAX_PUSH_ID 프레임은 서버가 쓸 수 있는 푸시 ID의 최댓값을 식별하는 가변 길이 정수 하나를 담는다. 4.6절을 참고하라. MAX_PUSH_ID 프레임으로 최대 푸시 ID를 줄일 수는 없다. 앞서 받은 것보다 작은 값을 담은 MAX_PUSH_ID 프레임을 받으면 반드시 H3_ID_ERROR 유형의 연결 오류로 처리해야 한다(MUST).

7.2.8. 예약된 프레임 유형

N이 음이 아닌 정수일 때 0x1f * N + 0x21 꼴의 프레임 유형은, 알 수 없는 유형은 무시해야 한다는 요구 사항을 시험하기 위해 예약되어 있다(9장). 이런 프레임에는 의미가 없으며, 프레임을 보낼 수 있는 어떤 스트림에서든 보낼 수 있다(MAY). 이로써 이런 프레임을 애플리케이션 계층 패딩에 쓸 수 있다. 엔드포인트는 이런 프레임을 받았을 때 어떤 의미가 있는 것으로 여겨서는 안 된다(MUST NOT).

프레임의 페이로드와 길이는 구현이 원하는 어떤 방식으로든 정한다.

HTTP/2에서 쓰였으나 대응하는 HTTP/3 프레임이 없는 프레임 유형도 예약되어 있다(11.2.1절). 이런 프레임 유형은 보내서는 안 되며(MUST NOT), 받으면 반드시 H3_FRAME_UNEXPECTED 유형의 연결 오류로 처리해야 한다(MUST).

8. 오류 처리

스트림을 성공적으로 완료할 수 없을 때, QUIC은 애플리케이션이 그 스트림을 갑자기 끝내고(리셋하고) 이유를 알릴 수 있게 한다. [QUIC-TRANSPORT]의 2.4절을 참고하라. 이를 “스트림 오류(stream error)”라고 부른다. HTTP/3 구현은 QUIC 스트림을 닫기로 하고 오류 유형을 알릴 수 있다. 오류 코드의 와이어 인코딩은 8.1절에 정의되어 있다. 스트림 오류는 오류 상황을 나타내는 HTTP 상태 코드와 다르다. 스트림 오류는 송신자가 요청이나 응답 전체를 전송하거나 소비하지 못했다는 것을 나타내고, HTTP 상태 코드는 성공적으로 받은 요청의 결과를 나타낸다.

연결 전체를 끝낼 필요가 있으면, QUIC은 마찬가지로 이유를 알리는 메커니즘을 제공한다. [QUIC-TRANSPORT]의 5.3절을 참고하라. 이를 “연결 오류(connection error)”라고 부른다. 스트림 오류와 마찬가지로, HTTP/3 구현은 QUIC 연결을 끝내고 8.1절의 오류 코드를 사용해 이유를 알릴 수 있다.

스트림과 연결을 닫는 이유를 “오류”라고 부르지만, 이런 동작이 꼭 연결이나 어느 한쪽 구현에 문제가 있다는 뜻은 아니다. 예를 들어 요청한 리소스가 더 이상 필요 없으면 스트림을 리셋할 수 있다.

엔드포인트는 특정 상황에서 스트림 오류를 연결 오류로 처리해, 스트림 하나의 상황에 대한 대응으로 연결 전체를 닫기로 할 수 있다(MAY). 구현은 이렇게 하기로 정하기 전에 미처리 요청에 미칠 영향을 고려할 필요가 있다.

새 오류 코드는 협상 없이 정의될 수 있으므로(9장 참고), 예상하지 못한 맥락에서 오류 코드를 쓰거나 알 수 없는 오류 코드를 받으면 반드시 H3_NO_ERROR와 같은 것으로 처리해야 한다(MUST). 그러나 스트림을 닫는 것은 오류 코드와 관계없이 다른 영향을 줄 수 있다. 예를 들어 4.1절을 참고하라.

8.1. HTTP/3 오류 코드

스트림을 갑자기 끝내거나, 스트림 읽기를 중단하거나, HTTP/3 연결을 즉시 닫을 때 쓰도록 다음 오류 코드가 정의되어 있다.

N이 음이 아닌 정수일 때 0x1f * N + 0x21 꼴의 오류 코드는, 알 수 없는 오류 코드는 H3_NO_ERROR와 같은 것으로 처리해야 한다는 요구 사항을 시험하기 위해 예약되어 있다(9장). 구현은 H3_NO_ERROR를 보낼 상황에서 어느 정도의 확률로 이 공간의 오류 코드를 골라야 한다(SHOULD).

9. HTTP/3 확장

HTTP/3은 프로토콜 확장을 허용한다. 이 장에 설명된 제한 안에서, 프로토콜 확장은 추가 서비스를 제공하거나 프로토콜의 어떤 측면이든 바꾸는 데 쓸 수 있다. 확장은 HTTP/3 연결 하나의 범위 안에서만 유효하다.

이는 이 문서에 정의된 프로토콜 요소에 적용된다. 새 메서드, 상태 코드, 필드를 정의하는 것 같은 기존의 HTTP 확장 방법에는 영향을 주지 않는다.

확장은 새 프레임 유형(7.2절), 새 설정(7.2.4.1절), 새 오류 코드(8장), 새 단방향 스트림 유형(6.2절)을 쓰는 것이 허용된다. 이 확장 지점(extension point)을 관리하기 위해 레지스트리가 만들어졌다. 프레임 유형(11.2.1절), 설정(11.2.2절), 오류 코드(11.2.3절), 스트림 유형(11.2.4절)이다.

구현은 확장 가능한 모든 프로토콜 요소에서 알 수 없거나 지원하지 않는 값을 반드시 무시해야 한다(MUST). 구현은 알 수 없거나 지원하지 않는 유형의 단방향 스트림에서 데이터를 반드시 버리거나 읽기를 중단해야 한다(MUST). 이는 이 확장 지점들을 확장이 사전 합의나 협상 없이 안전하게 쓸 수 있다는 뜻이다. 그러나 제어 스트림의 첫 프레임인 SETTINGS 프레임처럼(6.2.1절 참고) 알려진 프레임 유형이 특정 위치에 있어야 하는 경우, 알 수 없는 프레임 유형은 그 요구 사항을 충족하지 않으며, 오류로 처리해야 한다(SHOULD).

기존 프로토콜 구성 요소의 의미를 바꿀 수 있는 확장은 사용하기 전에 반드시 협상해야 한다(MUST). 예를 들어 HEADERS 프레임의 구성을 바꾸는 확장은 피어가 이를 받아들일 수 있다는 긍정적인 신호를 줄 때까지 쓸 수 없다. 이렇게 바뀐 구성이 언제부터 효력을 가지는지 조율하는 일은 복잡해질 수 있다. 따라서 기존 프로토콜 요소를 새로 정의할 때는 새 식별자를 할당하는 편이 더 효과적일 가능성이 높다.

이 문서는 확장 사용을 협상하는 특정 방법을 의무화하지 않지만, 그 목적으로 설정(7.2.4.1절)을 쓸 수 있다는 점을 언급한다. 두 피어가 모두 확장을 쓸 의향을 나타내는 값을 설정하면 그 확장을 쓸 수 있다. 확장 협상에 설정을 쓴다면, 그 설정이 생략되었을 때 확장이 꺼지도록 기본값을 반드시 정의해야 한다(MUST).

10. 보안 고려 사항

HTTP/3의 보안 고려 사항은 TLS를 쓰는 HTTP/2와 비슷할 것이다. 그러나 [HTTP/2]의 10장에 있는 고려 사항 가운데 많은 것은 [QUIC-TRANSPORT]에 적용되며 그 문서에서 다룬다.

10.1. 서버 권한

HTTP/3은 HTTP의 권한 정의에 의존한다. 권한 확립에 관한 보안 고려 사항은 [HTTP]의 17.1절에서 다룬다.

10.2. 교차 프로토콜 공격

TLS와 QUIC 핸드셰이크에서 ALPN을 쓰면, 애플리케이션 계층 바이트를 처리하기 전에 대상 애플리케이션 프로토콜이 확정된다. 이로써 엔드포인트는 피어가 같은 프로토콜을 쓰고 있다는 강한 보증을 얻는다.

그렇다고 모든 교차 프로토콜 공격(cross-protocol attack)으로부터 보호된다는 보장은 없다. [QUIC-TRANSPORT]의 21.5절은 인증된 전송을 쓰지 않는 엔드포인트에 대해 QUIC 패킷의 평문을 사용해 요청 위조를 수행할 수 있는 몇 가지 방식을 설명한다.

10.3. 중개자 캡슐화 공격

HTTP/3 필드 인코딩은 HTTP에서 쓰는 구문([HTTP]의 5.1절)으로는 유효한 필드 이름이 아닌 이름을 표현할 수 있게 한다. 유효하지 않은 필드 이름이 든 요청이나 응답은 반드시 형식이 잘못된 것으로 처리해야 한다(MUST). 따라서 중개자는 유효하지 않은 필드 이름이 든 HTTP/3 요청이나 응답을 HTTP/1.1 메시지로 변환할 수 없다.

마찬가지로 HTTP/3은 유효하지 않은 필드 값도 전송할 수 있다. 인코딩할 수 있는 값 대부분은 필드 파싱을 바꾸지 않지만, 캐리지 리턴(ASCII 0x0d), 라인 피드(ASCII 0x0a), 널 문자(ASCII 0x00)는 그대로 변환되면 공격자가 악용할 수도 있다. 필드 값에 허용되지 않는 문자가 든 요청이나 응답은 반드시 형식이 잘못된 것으로 처리해야 한다(MUST). 유효한 문자는 [HTTP]의 5.5절에 있는 “field-content” ABNF 규칙이 정의한다.

10.4. 푸시된 응답의 캐시 가능성

푸시된 응답에는 클라이언트의 명시적인 요청이 없다. 요청은 서버가 PUSH_PROMISE 프레임으로 제공한다.

푸시된 응답은 원본 서버가 Cache-Control 헤더 필드로 제공하는 지침에 따라 캐싱할 수 있다. 그러나 서버 하나가 둘 이상의 테넌트(tenant)를 호스팅하면 문제가 생길 수 있다. 예를 들어 서버가 자기 URI 공간의 작은 부분을 여러 사용자에게 각각 제공할 수도 있다.

여러 테넌트가 같은 서버의 공간을 공유하는 경우, 그 서버는 테넌트가 자신이 권한을 갖지 않은 리소스의 표현을 푸시할 수 없도록 반드시 보장해야 한다(MUST). 이를 강제하지 않으면, 한 테넌트가 캐시에서 제공될 표현을 제공해 권한 있는 테넌트가 제공하는 실제 표현보다 우선하게 만들 수 있다.

클라이언트는 원본 서버가 권한 있지 않은 푸시된 응답을 거부할 의무가 있다. 4.6절을 참고하라.

10.5. 서비스 거부 고려 사항

HTTP/3 연결은 동작하는 데 HTTP/1.1이나 HTTP/2 연결보다 더 많은 자원 할당을 요구할 수 있다. 필드 압축과 흐름 제어를 쓰려면 더 많은 상태를 저장하기 위한 자원 할당이 필요하다. 이 기능들에 대한 설정은 이 기능들을 위한 메모리 할당이 엄격하게 제한되도록 보장한다.

PUSH_PROMISE 프레임의 수도 비슷한 방식으로 제한된다. 서버 푸시를 받아들이는 클라이언트는 한 번에 발행하는 푸시 ID의 수를 제한해야 한다(SHOULD).

처리 용량은 상태 용량만큼 효과적으로 보호할 수 없다.

피어가 무시할 의무가 있는, 정의되지 않은 프로토콜 요소를 보낼 수 있다는 점은 피어가 처리 시간을 더 쓰게 만드는 데 악용될 수 있다. 정의되지 않은 SETTINGS 매개 변수를 여러 개 설정하거나, 알 수 없는 프레임 유형이나 스트림 유형을 여러 개 보내는 방식으로 이렇게 할 수도 있다. 그러나 이해하지 않아도 되는 확장이나 트래픽 분석(traffic analysis)에 대한 저항력을 높이는 패딩처럼, 어떤 용도는 전적으로 정당하다는 점에 유의하라.

필드 섹션 압축도 처리 자원을 낭비하게 할 기회를 준다. 잠재적 악용에 대한 자세한 내용은 [QPACK]의 7장을 참고하라.

이 모든 기능, 즉 서버 푸시, 알 수 없는 프로토콜 요소, 필드 압축에는 정당한 용도가 있다. 이 기능들은 불필요하게 또는 지나치게 쓰일 때만 부담이 된다.

이런 동작을 감시하지 않는 엔드포인트는 서비스 거부(denial-of-service) 공격의 위험에 스스로를 노출한다. 구현은 이 기능들의 사용을 추적하고 사용 한도를 정해야 한다(SHOULD). 엔드포인트는 의심스러운 활동을 H3_EXCESSIVE_LOAD 유형의 연결 오류로 처리할 수 있지만(MAY), 오탐은 유효한 연결과 요청을 방해하게 된다.

10.5.1. 필드 섹션 크기 제한

큰 필드 섹션(4.1절)은 구현이 많은 양의 상태를 할당하게 만들 수 있다. 라우팅에 중요한 헤더 필드가 헤더 섹션의 끝 쪽에 나타날 수 있어서, 헤더 섹션을 최종 목적지로 스트리밍하지 못하게 된다. 이런 순서 문제와 캐시 정확성 보장 같은 다른 이유 때문에, 엔드포인트는 헤더 섹션 전체를 버퍼에 담을 필요가 있을 가능성이 높다. 필드 섹션의 크기에는 엄격한 한도가 없으므로, 일부 엔드포인트에서는 헤더 필드에 가용 메모리의 상당 부분을 할당해야 하는 상황이 생길 수 있다.

엔드포인트는 SETTINGS_MAX_FIELD_SECTION_SIZE(4.2.2절) 설정을 사용해, 필드 섹션의 크기에 적용될 수도 있는 한도를 피어에게 알릴 수 있다. 이 설정은 권고일 뿐이므로, 엔드포인트는 이 한도를 넘는 필드 섹션을 보내고 요청이나 응답이 형식이 잘못된 것으로 처리될 위험을 감수하기로 할 수 있다(MAY). 이 설정은 HTTP/3 연결마다 따로 적용되므로, 어떤 요청이나 응답이든 더 낮은 알 수 없는 한도를 가진 홉을 만날 수 있다. 중개자는 서로 다른 피어가 제시한 값을 전달해 이 문제를 피하려 시도할 수 있지만, 그럴 의무는 없다.

처리하려는 것보다 큰 필드 섹션을 받은 서버는 HTTP 431 (Request Header Fields Too Large) 상태 코드([RFC6585])를 보낼 수 있다. 클라이언트는 처리할 수 없는 응답을 버릴 수 있다.

10.5.2. CONNECT 문제

스트림 생성은 TCP 연결의 생성·유지에 비해 비용이 비교적 적으므로, CONNECT 메서드는 프록시에 불균형한 부하를 만드는 데 쓰일 수 있다. 따라서 CONNECT를 지원하는 프록시는 받아들이는 동시 요청 수를 더 보수적으로 정할 수도 있다.

또한 나가는 TCP 연결이 TIME_WAIT 상태로 남으므로, 프록시는 CONNECT 요청을 담은 스트림이 닫힌 뒤에도 그 TCP 연결을 위한 자원을 일부 유지할 수도 있다. 이를 고려해, 프록시는 TCP 연결이 끝난 뒤 얼마 동안 QUIC 스트림 한도를 늘리는 것을 미룰 수도 있다.

10.6. 압축 사용

비밀 데이터가 공격자가 제어하는 데이터와 같은 맥락에서 압축되면, 압축 때문에 공격자가 비밀 데이터를 알아낼 수 있다. HTTP/3은 필드의 압축(4.2절)을 가능하게 한다. 다음 우려 사항은 HTTP의 압축 콘텐츠 코딩 사용에도 적용된다. [HTTP]의 8.4.1절을 참고하라.

웹의 특성을 악용하는, 실제로 입증된 압축 공격이 있다(예: [BREACH]). 공격자는 서로 다른 평문을 담은 여러 요청을 유도하고 각각의 결과 암호문 길이를 관찰하는데, 비밀에 대한 추측이 맞으면 길이가 더 짧게 나타난다.

보안 채널로 통신하는 구현은, 데이터의 원천마다 별도의 압축 컨텍스트를 쓰는 경우가 아니라면, 기밀 데이터와 공격자가 제어하는 데이터가 모두 들어 있는 콘텐츠를 압축해서는 안 된다(MUST NOT). 데이터의 원천을 확실히 알 수 없으면 압축을 써서는 안 된다(MUST NOT).

필드 섹션 압축에 관한 추가 고려 사항은 [QPACK]에 설명되어 있다.

10.7. 패딩과 트래픽 분석

패딩은 프레임 콘텐츠의 정확한 크기를 감추는 데 쓸 수 있으며, HTTP 안의 특정 공격, 예를 들어 압축된 콘텐츠에 공격자가 제어하는 평문과 비밀 데이터가 모두 들어 있는 공격(예: [BREACH])을 완화하기 위해 제공된다.

HTTP/2는 연결이 트래픽 분석에 더 잘 견디도록 PADDING 프레임과 다른 프레임의 Padding 필드를 일부 유형의 프레임에 있는 Padding 필드를 쓰지만, HTTP/3은 전송 계층 패딩에 의존하거나, 7.2.8절과 6.2.3절에서 다룬 예약된 프레임 유형과 스트림 유형을 쓸 수 있다. 이런 패딩 방법들은 패딩의 단위, 보호하려는 정보에 대해 패딩을 어떻게 배치하는지, 패킷 손실이 생겼을 때 패딩이 적용되는지, 구현이 패딩을 어떻게 제어할 수 있는지에 따라 서로 다른 결과를 낸다.

역주: 정오표 7702(Verified)에 따라 HTTP/2에 없는 PADDING 프레임을 뺀 내용이다.

예약된 스트림 유형은 연결이 유휴 상태일 때도 트래픽을 보내는 것처럼 보이게 하는 데 쓸 수 있다. HTTP 트래픽은 흔히 몰아서 발생하므로, 겉보기 트래픽은 그런 몰림의 시점이나 지속 시간을 감추는 데 쓸 수 있으며, 데이터를 끊임없이 보내는 것처럼 보이게 할 수도 있다. 그러나 이런 트래픽도 여전히 수신자의 흐름 제어를 받으므로, 이런 스트림을 신속히 비우고 흐름 제어 크레딧을 추가로 주지 않으면 송신자가 실제 트래픽을 보내는 능력이 제한될 수 있다.

압축에 의존하는 공격을 완화하려면, 대응책으로 패딩보다 압축을 끄거나 제한하는 편이 나을 수도 있다.

패딩을 써도 얼핏 보이는 것보다 보호가 약할 수 있다. 불필요한 패딩은 오히려 역효과를 낼 수도 있다. 기껏해야 패딩은 공격자가 관찰해야 하는 프레임 수를 늘려 길이 정보를 추론하기 더 어렵게 만들 뿐이다. 잘못 구현된 패딩 체계는 쉽게 뚫릴 수 있다. 특히 분포를 예측할 수 있는 무작위 패딩은 보호 효과가 거의 없다. 마찬가지로 페이로드를 고정 크기로 패딩하면, 페이로드 크기가 고정 크기 경계를 넘을 때 정보가 드러난다. 공격자가 평문을 제어할 수 있다면 이런 노출이 일어날 수 있다.

10.8. 프레임 파싱

여러 프로토콜 요소에는 중첩된 길이 요소가 들어 있는데, 대개 가변 길이 정수를 담은, 길이가 명시된 프레임의 형태다. 이는 부주의한 구현자에게 보안 위험이 될 수 있다. 구현은 프레임의 길이가 그 프레임에 든 필드의 길이와 정확히 일치하는지 반드시 확인해야 한다(MUST).

10.9. 조기 데이터

HTTP/3에서 0-RTT를 쓰면 재전송 공격(replay attack)에 노출된다. HTTP/3을 0-RTT와 함께 쓸 때는 [HTTP-REPLAY]의 재전송 방지 완화책을 반드시 적용해야 한다(MUST). [HTTP-REPLAY]를 HTTP/3에 적용할 때, TLS 계층에 대한 언급은 QUIC 안에서 수행되는 핸드셰이크를 가리키고, 애플리케이션 데이터에 대한 언급은 모두 스트림의 내용을 가리킨다.

10.10. 연결 이전

일부 HTTP 구현은 로깅이나 접근 제어(access control) 목적으로 클라이언트 주소를 쓴다. QUIC 클라이언트의 주소는 연결 중에 바뀔 수도 있으므로(그리고 앞으로의 버전은 여러 주소를 동시에 쓰는 것을 지원할 수도 있으므로), 그런 구현은 관련이 있을 때 클라이언트의 현재 주소(하나 또는 여럿)를 능동적으로 가져오거나, 원래 주소가 바뀔 수도 있다는 것을 명시적으로 받아들일 필요가 있을 것이다.

10.11. 프라이버시 고려 사항

HTTP/3의 여러 특성은 관찰자가 한 클라이언트나 서버의 동작을 시간에 걸쳐 연관 지을 기회를 준다. 여기에는 설정 값, 자극에 반응하는 타이밍, 설정으로 제어되는 모든 기능의 처리가 포함된다.

이들이 동작상의 관찰 가능한 차이를 만드는 한, 특정 클라이언트를 핑거프린팅(fingerprinting)하는 근거로 쓰일 수 있다.

QUIC 연결 하나를 쓰는 것을 선호하는 HTTP/3의 방식은 한 사이트에서의 사용자 활동을 연관 지을 수 있게 한다. 서로 다른 출처에 연결을 재사용하면 그 출처들에 걸친 활동을 연관 지을 수 있다.

QUIC의 여러 기능은 즉시 응답을 요구하므로, 엔드포인트가 피어까지의 지연 시간을 재는 데 쓸 수 있다. 이는 특정 상황에서 프라이버시에 영향을 줄 수도 있다.

11. IANA 고려 사항

이 문서는 새 ALPN 프로토콜 ID를 등록하고(11.1절), HTTP/3의 코드 포인트(code point) 할당을 관리하는 새 레지스트리를 만든다.

11.1. HTTP/3 식별 문자열 등록

이 문서는 [RFC7301]이 만든 “TLS ALPN 프로토콜 ID(TLS Application-Layer Protocol Negotiation (ALPN) Protocol IDs)” 레지스트리에 HTTP/3을 식별하는 새 항목을 등록한다.

문자열 “h3”은 HTTP/3을 식별한다.

11.2. 새 레지스트리

이 문서가 만드는 새 레지스트리는 [QUIC-TRANSPORT]의 22.1절에 문서화된 QUIC 등록 정책에 따라 운영된다. 이 레지스트리들은 모두 [QUIC-TRANSPORT]의 22.1.1절에 나열된 공통 필드 집합을 포함한다. 이 레지스트리들은 “Hypertext Transfer Protocol version 3 (HTTP/3)” 제목 아래에 모여 있다.

이 레지스트리들의 초기 할당은 모두 permanent 상태가 부여되며, 변경 관리자(change controller)로 IETF를, 연락처로 HTTP 워킹 그룹(ietf-http-wg@w3.org)을 적는다.

11.2.1. 프레임 유형

이 문서는 HTTP/3 프레임 유형 코드를 위한 레지스트리를 만든다. “HTTP/3 프레임 유형(HTTP/3 Frame Types)” 레지스트리는 62비트 공간을 관리한다. 이 레지스트리는 QUIC 레지스트리 정책을 따른다. 11.2절을 참고하라. 이 레지스트리의 영구 등록은 Specification Required 정책([RFC8126])으로 할당한다. 단, 0x00부터 0x3f까지(16진수, 양 끝 포함)의 값은 [RFC8126]의 4.9절과 4.10절에 정의된 Standards Action 또는 IESG Approval로 할당한다.

이 레지스트리는 [HTTP/2]에 정의된 “HTTP/2 프레임 유형(HTTP/2 Frame Type)” 레지스트리와 별개이지만, 코드 공간이 겹치는 곳에서는 할당이 서로 대응하는 것이 바람직하다. 어떤 항목이 한쪽 레지스트리에만 있다면, 대응하는 값을 관련 없는 동작에 할당하지 않도록 모든 노력을 기울여야 한다(SHOULD). 전문가 검토자(expert reviewer)는 대응하는 레지스트리의 같은 값과 충돌할 관련 없는 등록을 거부할 수 있다(MAY).

11.2절에 설명된 공통 필드 외에, 이 레지스트리의 영구 등록에는 반드시 다음 필드가 들어 있어야 한다(MUST).

프레임 유형의 명세에는 반드시 조건에 따라 나타나는 부분을 포함해 프레임 구성과 그 의미에 대한 설명이 들어 있어야 한다(MUST).

표 2의 항목은 이 문서가 등록한다.

프레임 유형(Frame Type) 값(Value) 명세(Specification)
DATA 0x00 7.2.1절
HEADERS 0x01 7.2.2절
Reserved 0x02 This document
CANCEL_PUSH 0x03 7.2.3절
SETTINGS 0x04 7.2.4절
PUSH_PROMISE 0x05 7.2.5절
Reserved 0x06 This document
GOAWAY 0x07 7.2.6절
Reserved 0x08 This document
Reserved 0x09 This document
MAX_PUSH_ID 0x0d 7.2.7절

표 2: 초기 HTTP/3 프레임 유형

N이 음이 아닌 정수일 때 0x1f * N + 0x21 꼴의 각 코드(즉, 0x21, 0x40, …, 0x3ffffffffffffffe까지)는 IANA가 할당해서는 안 되며(MUST NOT), 할당된 값의 목록에 나타나서는 안 된다(MUST NOT).

11.2.2. 설정 매개 변수

이 문서는 HTTP/3 설정을 위한 레지스트리를 만든다. “HTTP/3 설정(HTTP/3 Settings)” 레지스트리는 62비트 공간을 관리한다. 이 레지스트리는 QUIC 레지스트리 정책을 따른다. 11.2절을 참고하라. 이 레지스트리의 영구 등록은 Specification Required 정책([RFC8126])으로 할당한다. 단, 0x00부터 0x3f까지(16진수, 양 끝 포함)의 값은 [RFC8126]의 4.9절과 4.10절에 정의된 Standards Action 또는 IESG Approval로 할당한다.

이 레지스트리는 [HTTP/2]에 정의된 “HTTP/2 설정(HTTP/2 Settings)” 레지스트리와 별개이지만, 할당이 서로 대응하는 것이 바람직하다. 어떤 항목이 한쪽 레지스트리에만 있다면, 대응하는 값을 관련 없는 동작에 할당하지 않도록 모든 노력을 기울여야 한다(SHOULD). 전문가 검토자는 대응하는 레지스트리의 같은 값과 충돌할 관련 없는 등록을 거부할 수 있다(MAY).

11.2절에 설명된 공통 필드 외에, 이 레지스트리의 영구 등록에는 반드시 다음 필드가 들어 있어야 한다(MUST).

표 3의 항목은 이 문서가 등록한다.

설정 이름(Setting Name) 값(Value) 명세(Specification) 기본값(Default)
Reserved 0x00 This document N/A
Reserved 0x02 This document N/A
Reserved 0x03 This document N/A
Reserved 0x04 This document N/A
Reserved 0x05 This document N/A
MAX_FIELD_SECTION_SIZE 0x06 7.2.4.1절 Unlimited

표 3: 초기 HTTP/3 설정

형식상의 이유로, 설정 이름에서 접두사 SETTINGS_를 빼서 줄여 쓸 수 있다.

N이 음이 아닌 정수일 때 0x1f * N + 0x21 꼴의 각 코드(즉, 0x21, 0x40, …, 0x3ffffffffffffffe까지)는 IANA가 할당해서는 안 되며(MUST NOT), 할당된 값의 목록에 나타나서는 안 된다(MUST NOT).

11.2.3. 오류 코드

이 문서는 HTTP/3 오류 코드를 위한 레지스트리를 만든다. “HTTP/3 오류 코드(HTTP/3 Error Codes)” 레지스트리는 62비트 공간을 관리한다. 이 레지스트리는 QUIC 레지스트리 정책을 따른다. 11.2절을 참고하라. 이 레지스트리의 영구 등록은 Specification Required 정책([RFC8126])으로 할당한다. 단, 0x00부터 0x3f까지(16진수, 양 끝 포함)의 값은 [RFC8126]의 4.9절과 4.10절에 정의된 Standards Action 또는 IESG Approval로 할당한다.

오류 코드 등록은 오류 코드에 대한 설명을 포함할 의무가 있다. 전문가 검토자는 새 등록이 기존 오류 코드와 중복될 가능성이 있는지 살펴보는 것이 좋다. 기존 등록을 쓰는 것을 장려하지만 의무는 아니다. “HTTP/2 오류 코드(HTTP/2 Error Code)” 레지스트리에 등록된 값을 쓰는 것은 바람직하지 않으며, 전문가 검토자는 그런 등록을 거부할 수 있다(MAY).

11.2절에 설명된 공통 필드 외에, 이 레지스트리에는 필드 두 개가 더 있다. 이 레지스트리의 영구 등록에는 반드시 다음 필드가 들어 있어야 한다(MUST).

표 4의 항목은 이 문서가 등록한다. 이 오류 코드들은 HTTP/2 오류 코드와 충돌하지 않도록 Specification Required 정책으로 운영되는 범위에서 골랐다.

이름(Name) 값(Value) 설명(Description) 명세(Specification)
H3_NO_ERROR 0x0100 No error 8.1절
H3_GENERAL_PROTOCOL_ERROR 0x0101 General protocol error 8.1절
H3_INTERNAL_ERROR 0x0102 Internal error 8.1절
H3_STREAM_CREATION_ERROR 0x0103 Stream creation error 8.1절
H3_CLOSED_CRITICAL_STREAM 0x0104 Critical stream was closed 8.1절
H3_FRAME_UNEXPECTED 0x0105 Frame not permitted in the current state 8.1절
H3_FRAME_ERROR 0x0106 Frame violated layout or size rules 8.1절
H3_EXCESSIVE_LOAD 0x0107 Peer generating excessive load 8.1절
H3_ID_ERROR 0x0108 An identifier was used incorrectly 8.1절
H3_SETTINGS_ERROR 0x0109 SETTINGS frame contained invalid values 8.1절
H3_MISSING_SETTINGS 0x010a No SETTINGS frame received 8.1절
H3_REQUEST_REJECTED 0x010b Request not processed 8.1절
H3_REQUEST_CANCELLED 0x010c Data no longer needed 8.1절
H3_REQUEST_INCOMPLETE 0x010d Stream terminated early 8.1절
H3_MESSAGE_ERROR 0x010e Malformed message 8.1절
H3_CONNECT_ERROR 0x010f TCP reset or error on CONNECT request 8.1절
H3_VERSION_FALLBACK 0x0110 Retry over HTTP/1.1 8.1절

표 4: 초기 HTTP/3 오류 코드

N이 음이 아닌 정수일 때 0x1f * N + 0x21 꼴의 각 코드(즉, 0x21, 0x40, …, 0x3ffffffffffffffe까지)는 IANA가 할당해서는 안 되며(MUST NOT), 할당된 값의 목록에 나타나서는 안 된다(MUST NOT).

11.2.4. 스트림 유형

이 문서는 HTTP/3 단방향 스트림 유형을 위한 레지스트리를 만든다. “HTTP/3 스트림 유형(HTTP/3 Stream Types)” 레지스트리는 62비트 공간을 관리한다. 이 레지스트리는 QUIC 레지스트리 정책을 따른다. 11.2절을 참고하라. 이 레지스트리의 영구 등록은 Specification Required 정책([RFC8126])으로 할당한다. 단, 0x00부터 0x3f까지(16진수, 양 끝 포함)의 값은 [RFC8126]의 4.9절과 4.10절에 정의된 Standards Action 또는 IESG Approval로 할당한다.

11.2절에 설명된 공통 필드 외에, 이 레지스트리의 영구 등록에는 반드시 다음 필드가 들어 있어야 한다(MUST).

영구 등록의 명세에는 반드시 스트림 내용의 구성과 의미를 포함해 스트림 유형에 대한 설명이 들어 있어야 한다(MUST).

표 5의 항목은 이 문서가 등록한다.

스트림 유형(Stream Type) 값(Value) 명세(Specification) 송신자(Sender)
Control Stream 0x00 6.2.1절 Both
Push Stream 0x01 4.6절 Server

표 5: 초기 스트림 유형

N이 음이 아닌 정수일 때 0x1f * N + 0x21 꼴의 각 코드(즉, 0x21, 0x40, …, 0x3ffffffffffffffe까지)는 IANA가 할당해서는 안 되며(MUST NOT), 할당된 값의 목록에 나타나서는 안 된다(MUST NOT).

역주: 등록 항목과 표의 값은 IANA 레지스트리 값이라 원문 그대로 두고, 항목 이름(표 머리)만 번역해 원어를 병기했다. 표마다 IANA 레지스트리의 열 이름과 대응되도록, 필드 목록과 표 머리에서 항목 이름의 원어를 다시 병기했다. 등록 정책 이름(Specification Required, Standards Action, IESG Approval), 등록 상태 값(permanent), IANA의 그룹 제목 “Hypertext Transfer Protocol version 3 (HTTP/3)”도 원어로 두었다. 소문자 “are required to”, “is advised to”, “optional”, “may”는 대문자 키워드가 아니므로 괄호를 붙이지 않았다.

12. 참고 문헌

12.1. 규범 참고 문헌

12.2. 참고용 참고 문헌

역주: 참고 문헌 항목은 원문 그대로 두고 Markdown 목록으로 바꾸었다. 여러 줄에 걸친 항목은 한 줄로 이었고, 줄바꿈으로 끊긴 URL은 공백 없이 이었다.

부록 A. HTTP/2에서 옮겨 갈 때 고려할 점

HTTP/3은 HTTP/2에서 많은 영향을 받았으며 비슷한 점이 많다. 이 부록은 HTTP/3을 설계하며 택한 접근 방식을 설명하고, HTTP/2와의 중요한 차이를 짚고, HTTP/2 확장을 HTTP/3에 대응시키는 방법을 설명한다.

HTTP/3은 HTTP/2와 비슷한 것이 바람직하지만 엄격한 요구 사항은 아니라는 전제에서 출발한다. HTTP/3은 QUIC이 TCP와 다른 곳에서 HTTP/2와 갈라지는데, (스트림 같은) QUIC 기능을 활용하기 위해서이거나 (전체 순서 보장이 없는 것 같은) 중요한 단점을 감안하기 위해서다. HTTP/3은 요청과 응답이 스트림에 대응하는 관계 같은 핵심 측면에서 HTTP/2와 비슷하지만, HTTP/3 설계의 세부 사항은 HTTP/2와 상당히 다르다.

이 부록에서는 중요한 차이 몇 가지를 짚는다.

A.1. 스트림

HTTP/3은 HTTP/2보다 많은 수의 스트림(2^62-1)을 쓸 수 있다. 스트림 식별자 공간이 고갈되는 것에 대한 고려 사항은 같지만, 공간이 훨씬 커서 연결 흐름 제어 윈도우의 한도 같은 QUIC의 다른 한도에 먼저 도달할 가능성이 높다.

HTTP/2와 달리 HTTP/3에서 스트림 동시성은 QUIC이 관리한다. QUIC은 모든 데이터를 받았고 보낸 데이터를 피어가 확인 응답(acknowledge)했을 때 스트림이 닫혔다고 본다. HTTP/2는 END_STREAM 비트가 든 프레임이 전송 계층에 넘겨졌을 때 스트림이 닫혔다고 본다. 그 결과 같은 교환이라도 스트림이 더 오랫동안 “활성” 상태로 남을 수 있다. HTTP/3 서버는 예상되는 사용 패턴에 따라, HTTP/2와 같은 수준의 동시성을 얻기 위해 클라이언트가 시작한 양방향 스트림을 동시에 더 많이 허용하기로 할 수도 있다.

HTTP/2에서는 요청 본문과 응답 본문(DATA 프레임의 프레임 페이로드)만 흐름 제어 대상이다. HTTP/3에서는 모든 프레임이 QUIC 스트림으로 전송되므로, 모든 스트림의 모든 프레임이 흐름 제어를 받는다.

다른 단방향 스트림 유형이 있기 때문에, HTTP/3은 동시에 진행 중인 푸시의 수를 제어하는 데 동시 단방향 스트림 수에만 의존하지 않는다. 대신 HTTP/3 클라이언트는 MAX_PUSH_ID 프레임을 사용해 HTTP/3 서버로부터 받는 푸시의 수를 제어한다.

A.2. HTTP 프레임 유형

HTTP/2의 여러 프레이밍 개념은 전송 계층이 처리하므로 QUIC 위에서는 생략할 수 있다. 프레임이 이미 스트림 위에 있으므로 스트림 번호를 생략할 수 있다. 프레임이 다중화를 막지 않으므로(QUIC의 다중화는 이 계층 아래에서 일어난다), 가변 최대 길이 패킷 지원을 없앨 수 있다. 스트림 종료는 QUIC이 처리하므로 END_STREAM 플래그가 필요 없다. 이로써 일반 프레임 구성에서 Flags 필드를 없앨 수 있다.

프레임 페이로드는 대체로 [HTTP/2]에서 가져왔다. 그러나 QUIC에는 HTTP/2에도 있는 기능(예: 흐름 제어)이 많다. 이런 경우 HTTP 매핑은 그 기능을 다시 구현하지 않는다. 그 결과 HTTP/2 프레임 유형 몇 가지는 HTTP/3에서 필요하지 않다. HTTP/2에서 정의한 프레임을 더 이상 쓰지 않는 경우, HTTP/2 구현과 HTTP/3 구현 사이의 이식성을 최대화하기 위해 그 프레임 ID를 예약해 두었다. 그러나 두 매핑에 모두 나오는 프레임 유형이라도 의미가 똑같지는 않다.

차이의 상당수는 HTTP/2가 모든 스트림에 걸쳐 프레임 사이의 절대적인 순서를 제공하는 반면, QUIC은 이 보장을 각 스트림 안에서만 제공한다는 사실에서 생긴다. 그 결과 어떤 프레임 유형이 서로 다른 스트림의 프레임도 여전히 보낸 순서대로 받을 것이라고 가정한다면, HTTP/3에서는 그 가정이 깨진다.

아래에서는 기능을 맞게 고친 예 몇 가지와 함께, HTTP/2 확장을 HTTP/3으로 옮기는 확장 프레임 구현자를 위한 일반 지침을 설명한다.

A.2.1. 우선순위 지정의 차이

HTTP/2는 PRIORITY 프레임과 (선택적으로) HEADERS 프레임에서 우선순위 할당을 지정한다. HTTP/3은 우선순위를 알리는 수단을 제공하지 않는다.

우선순위를 명시적으로 알리지는 않지만, 그렇다고 해서 좋은 성능을 얻는 데 우선순위 지정(prioritization)이 중요하지 않다는 뜻은 아니라는 점에 유의하라.

A.2.2. 필드 압축의 차이

HPACK은 순서대로 전달된다는 가정 아래 설계되었다. 인코딩된 필드 섹션의 시퀀스는 인코딩된 순서와 같은 순서로 엔드포인트에 도착해야만(그리고 디코딩되어야만) 한다. 그래야 두 엔드포인트의 동적 상태가 동기화된 상태로 유지된다.

QUIC은 이런 전체 순서를 보장하지 않으므로, HTTP/3은 QPACK이라는 HPACK의 수정판을 쓴다. QPACK은 단방향 스트림 하나로 동적 테이블의 모든 변경을 수행해, 갱신의 전체 순서를 보장한다. 인코딩된 필드를 담은 프레임은 모두 특정 시점의 테이블 상태를 참조할 뿐 바꾸지 않는다.

자세한 내용은 [QPACK]에 있다.

A.2.3. 흐름 제어의 차이

HTTP/2는 스트림 흐름 제어 메커니즘을 명시한다. HTTP/2 프레임은 모두 스트림으로 전달되지만, DATA 프레임 페이로드만 흐름 제어 대상이다. QUIC은 스트림 데이터에 대한 흐름 제어를 제공하며, 이 문서에 정의된 HTTP/3 프레임 유형은 모두 스트림으로 전송된다. 따라서 모든 프레임 헤더와 페이로드가 흐름 제어 대상이다.

A.2.4. 새 프레임 유형 정의를 위한 지침

HTTP/3의 프레임 유형 정의는 흔히 QUIC 가변 길이 정수 인코딩을 쓴다. 특히 스트림 ID가 이 인코딩을 쓰는데, 이 인코딩은 HTTP/2에서 쓰는 인코딩보다 넓은 범위의 값을 표현할 수 있다. HTTP/3의 일부 프레임은 스트림 ID가 아닌 식별자(예: 푸시 ID)를 쓴다. 확장 프레임 유형의 인코딩에 스트림 ID가 들어 있다면 그 인코딩을 다시 정의할 필요가 있을 수도 있다.

일반 HTTP/3 프레임에는 Flags 필드가 없으므로, 플래그에 의존하는 프레임은 플래그를 위한 공간을 프레임 페이로드의 일부로 할당할 필요가 있다.

이런 문제 말고는, 프레임 유형을 정의하는 HTTP/2 확장은 대개 HTTP/2의 스트림 0을 HTTP/3의 제어 스트림으로 바꾸는 것만으로 QUIC에 이식할 수 있다. HTTP/3 확장은 순서를 가정하지 않지만 순서가 있다고 해서 해가 되지도 않으며, HTTP/2에 이식할 수 있을 것으로 기대된다.

A.2.5. HTTP/2와 HTTP/3 프레임 유형 비교

HTTP/2 확장이 정의한 프레임 유형은 여전히 적용할 수 있다면 HTTP/3용으로 따로 등록할 필요가 있다. [HTTP/2]에 정의된 프레임의 ID는 단순하게 하기 위해 예약해 두었다. HTTP/3의 프레임 유형 공간은 훨씬 크므로(62비트 대 8비트), HTTP/3 프레임 유형 가운데 상당수는 대응하는 HTTP/2 코드 포인트가 없다는 점에 유의하라. 11.2.1절을 참고하라.

A.3. HTTP/2 SETTINGS 매개 변수

HTTP/2와의 중요한 차이는 설정이 제어 스트림의 첫 프레임으로 한 번만 전송되고, 그 뒤로는 바뀔 수 없다는 것이다. 이로써 변경의 동기화를 둘러싼 많은 경계 상황이 없어진다.

HTTP/2가 SETTINGS 프레임으로 지정하는 일부 전송 수준 옵션은 HTTP/3에서 QUIC 전송 매개 변수로 대체된다. HTTP/3에 남아 있는 HTTP 수준 설정은 HTTP/2에서와 같은 값을 쓴다. 대체된 설정은 예약되어 있으며, 받으면 오류다. 남아 있는 값과 예약된 값에 대한 논의는 7.2.4.1절을 참고하라.

다음은 각 HTTP/2 SETTINGS 매개 변수가 어떻게 대응되는지를 나열한 것이다.

HTTP/3에서 설정 값은 HTTP/2처럼 고정 길이 32비트 필드가 아니라 가변 길이 정수(6, 14, 30, 62비트 길이)다. 이렇게 하면 인코딩이 짧아지는 경우가 많지만, 32비트 공간 전체를 쓰는 설정은 인코딩이 길어질 수 있다. HTTP/2에서 옮겨 온 설정은 더 효율적인 인코딩을 위해 값을 30비트로 제한하도록 다시 정의하거나, 30비트보다 많이 필요하면 62비트 공간을 활용하도록 다시 정의하기로 할 수도 있다.

설정은 HTTP/2용과 HTTP/3용을 따로 정의할 필요가 있다. [HTTP/2]에 정의된 설정의 ID는 단순하게 하기 위해 예약해 두었다. HTTP/3의 설정 식별자 공간은 훨씬 크므로(62비트 대 16비트), HTTP/3 설정 가운데 상당수는 대응하는 HTTP/2 코드 포인트가 없다는 점에 유의하라. 11.2.2절을 참고하라.

QUIC 스트림은 순서가 바뀌어 도착할 수도 있으므로, 엔드포인트는 다른 스트림에 응답하기 전에 피어의 설정이 도착하기를 기다리지 않는 것이 좋다. 7.2.4.2절을 참고하라.

A.4. HTTP/2 오류 코드

QUIC에는 HTTP/2가 제공하는 “스트림” 오류와 “연결” 오류의 개념이 똑같이 있다. 그러나 HTTP/2와 HTTP/3의 차이 때문에 오류 코드를 버전 사이에 그대로 옮길 수는 없다.

[HTTP/2]의 7장에 정의된 HTTP/2 오류 코드는 논리적으로 다음과 같이 HTTP/3 오류 코드에 대응한다.

오류 코드는 HTTP/2용과 HTTP/3용을 따로 정의할 필요가 있다. 11.2.3절을 참고하라.

A.4.1. HTTP/2 오류와 HTTP/3 오류 사이의 대응

HTTP/2와 HTTP/3 사이를 변환하는 중개자는 어느 쪽 업스트림에서든 오류 상황을 만날 수도 있다. 오류가 발생했다는 것을 다운스트림에 알리는 것은 유용하지만, 오류 코드는 대체로 연결 안에서만 의미가 있는 문제를 반영하므로 대개 전파해도 의미가 없다.

업스트림 원본 서버에서 오류를 만난 중개자는 폭넓은 종류의 오류에 알맞은 502 (Bad Gateway) 같은 HTTP 상태 코드를 보내 이를 나타낼 수 있다.

드물게는 오류를 수신자 쪽에서 가장 가깝게 들어맞는 오류 유형에 대응시켜 전파하는 것이 유익한 경우가 있다. 예를 들어 원본 서버에서 REFUSED_STREAM 유형의 HTTP/2 스트림 오류를 받은 중개자는 요청이 처리되지 않았고 재시도해도 안전하다는 분명한 신호를 얻는다. 이 오류 상황을 H3_REQUEST_REJECTED 유형의 HTTP/3 스트림 오류로 클라이언트에 전파하면, 클라이언트는 자신이 가장 적절하다고 판단하는 조치를 취할 수 있다. 반대 방향으로는, 중개자가 H3_REQUEST_CANCELLED로 스트림을 끝내는 방식으로 나타낸 클라이언트의 요청 취소를 전달하는 것이 유익하다고 판단할 수도 있다. 4.1.1절을 참고하라.

오류 사이의 변환은 위의 논리적 대응에 설명되어 있다. 오류 코드는, 우연한 변환 때문에 대상 버전에 맞지 않거나 알 수 없는 오류 코드를 쓰게 되는 일을 막기 위해, 서로 겹치지 않는 공간에 정의되어 있다. 중개자는 스트림 오류를 연결 오류로 격상하는 것이 허용되지만, 일시적이거나 간헐적인 오류일 수도 있는 것 때문에 HTTP/3 연결이 치르는 비용을 알고 있는 것이 좋다.

감사의 글

Robbie Shade와 Mike Warres는 이 문서의 전신인 draft-shade-quic-http2-mapping의 저자였다.

IETF QUIC 워킹 그룹은 많은 사람들에게서 엄청난 지원을 받았다. 그 가운데 다음 사람들이 이 문서에 상당한 기여를 했다.

Mike Bishop의 기여 가운데 일부는 Mike Bishop이 Microsoft에 재직하던 동안 Microsoft의 지원을 받았다.

역주: 사람 이름은 원문 표기 그대로 두었다.

색인

C D G H M P R S

C

D

G

H

M

P

R

S

역주: 색인 항목은 원문 그대로 두고, 절 번호만 이 문서의 해당 절 링크로 바꾸었다. 원문에서 강조(*_…_*)된 참조는 굵게 표시했다.

저자 주소

Mike Bishop (editor)
Akamai
Email: mbishop@evequefou.be

역주: 저자 주소는 2022년 발행 당시의 정보를 원문 그대로 옮긴 것이다.

번역자

김마리 (Mariel Kim)
Email:  marikim.code@gmail.com
GitHub: https://github.com/marik0de