Skip to content
This repository was archived by the owner on May 8, 2026. It is now read-only.

Commit e4986d6

Browse files
normanmaurermondain
andcommitted
Add :protocol pseudo-header support for Extended CONNECT (RFC 9220) (#15771)
Netty's HTTP/3 codec currently only recognizes the standard pseudo-headers (`:method`, `:scheme`, `:authority`, `:path`, `:status`), but RFC 9220 defines an additional `:protocol` pseudo-header for Extended CONNECT requests. This prevents applications from implementing WebTransport over HTTP/3 and WebSocket over HTTP/3. - Added `PROTOCOL` enum value to `Http3Headers.PseudoHeaderName` - Added `protocol()` getter and `protocol(CharSequence)` setter to `Http3Headers` interface - Implemented protocol methods in `DefaultHttp3Headers` - Enhanced `Http3HeadersSink` validation to distinguish between: - Regular CONNECT (requires `:method`, `:authority`) - Extended CONNECT (requires `:method`, `:scheme`, `:authority`, `:path`, `:protocol`) - Extended CONNECT requests with `:protocol` header are now accepted - WebTransport over HTTP/3 implementations can use Netty's HTTP/3 codec - WebSocket over HTTP/3 (RFC 9220) is now supported - Fully backward compatible with existing CONNECT requests - [RFC 9220: Bootstrapping WebSockets with HTTP/3](https://www.rfc-editor.org/rfc/rfc9220.html) - [WebTransport over HTTP/3 (draft-ietf-webtrans-http3)](https://datatracker.ietf.org/doc/draft-ietf-webtrans-http3/) - [RFC 9114: HTTP/3](https://www.rfc-editor.org/rfc/rfc9114.html) Section 4.4 Co-authored-by: Paul Gregoire <mondain@gmail.com>
1 parent 1da2cb7 commit e4986d6

3 files changed

Lines changed: 70 additions & 7 deletions

File tree

src/main/java/io/netty/incubator/codec/http3/Http3Headers.java

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,18 @@ enum PseudoHeaderName {
5151
/**
5252
* {@code :status}.
5353
*/
54-
STATUS(":status", false, 0x10);
54+
STATUS(":status", false, 0x10),
55+
56+
/**
57+
* {@code :protocol}.
58+
* <p>
59+
* Used for Extended CONNECT requests as defined in RFC 9220.
60+
* This pseudo-header is only valid for CONNECT requests and indicates
61+
* the desired protocol for the connection (e.g., "webtransport", "websocket").
62+
*
63+
* @see <a href="https://www.rfc-editor.org/rfc/rfc9220.html">RFC 9220: Bootstrapping WebSockets with HTTP/3</a>
64+
*/
65+
PROTOCOL(":protocol", true, 0x20);
5566

5667
private static final char PSEUDO_HEADER_PREFIX = ':';
5768
private static final byte PSEUDO_HEADER_PREFIX_BYTE = (byte) PSEUDO_HEADER_PREFIX;
@@ -184,6 +195,21 @@ public int getFlag() {
184195
*/
185196
Http3Headers status(CharSequence value);
186197

198+
/**
199+
* Sets the {@link PseudoHeaderName#PROTOCOL} header
200+
* <p>
201+
* This pseudo-header is used for Extended CONNECT requests as defined in RFC 9220.
202+
* Common values include "webtransport" and "websocket".
203+
*
204+
* @param value the value for the header.
205+
* @return this instance itself.
206+
* @see <a href="https://www.rfc-editor.org/rfc/rfc9220.html">RFC 9220: Bootstrapping WebSockets with HTTP/3</a>
207+
*/
208+
default Http3Headers protocol(CharSequence value) {
209+
set(PseudoHeaderName.PROTOCOL.value(), value);
210+
return this;
211+
}
212+
187213
/**
188214
* Gets the {@link PseudoHeaderName#METHOD} header or {@code null} if there is no such header
189215
*
@@ -224,6 +250,19 @@ public int getFlag() {
224250
@Nullable
225251
CharSequence status();
226252

253+
/**
254+
* Gets the {@link PseudoHeaderName#PROTOCOL} header or {@code null} if there is no such header
255+
* <p>
256+
* This pseudo-header is used for Extended CONNECT requests as defined in RFC 9220.
257+
*
258+
* @return the value of the header.
259+
* @see <a href="https://www.rfc-editor.org/rfc/rfc9220.html">RFC 9220: Bootstrapping WebSockets with HTTP/3</a>
260+
*/
261+
@Nullable
262+
default CharSequence protocol() {
263+
return get(PseudoHeaderName.PROTOCOL.value());
264+
}
265+
227266
/**
228267
* Returns {@code true} if a header with the {@code name} and {@code value} exists, {@code false} otherwise.
229268
* <p>

src/main/java/io/netty/incubator/codec/http3/Http3HeadersSink.java

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import static io.netty.incubator.codec.http3.Http3Headers.PseudoHeaderName.AUTHORITY;
2424
import static io.netty.incubator.codec.http3.Http3Headers.PseudoHeaderName.METHOD;
2525
import static io.netty.incubator.codec.http3.Http3Headers.PseudoHeaderName.PATH;
26+
import static io.netty.incubator.codec.http3.Http3Headers.PseudoHeaderName.PROTOCOL;
2627
import static io.netty.incubator.codec.http3.Http3Headers.PseudoHeaderName.SCHEME;
2728
import static io.netty.incubator.codec.http3.Http3Headers.PseudoHeaderName.STATUS;
2829
import static io.netty.incubator.codec.http3.Http3Headers.PseudoHeaderName.getPseudoHeader;
@@ -76,12 +77,29 @@ void finish() throws Http3HeadersValidationException, Http3Exception {
7677
CharSequence method = headers.method();
7778
// fast-path
7879
if (HttpMethod.CONNECT.asciiName().contentEqualsIgnoreCase(method)) {
79-
// For CONNECT we must only include:
80-
// - :method
81-
// - :authority
82-
final int requiredPseudoHeaders = METHOD.getFlag() | AUTHORITY.getFlag();
83-
if (receivedPseudoHeaders != requiredPseudoHeaders) {
84-
throw new Http3HeadersValidationException("Not all mandatory pseudo-headers included.");
80+
// Check if this is an Extended CONNECT request (RFC 9220)
81+
// Extended CONNECT includes the :protocol pseudo-header
82+
if ((receivedPseudoHeaders & PROTOCOL.getFlag()) != 0) {
83+
// Extended CONNECT (RFC 9220) requires:
84+
// - :method
85+
// - :scheme
86+
// - :authority
87+
// - :path
88+
// - :protocol
89+
final int requiredPseudoHeaders = METHOD.getFlag() | SCHEME.getFlag() |
90+
AUTHORITY.getFlag() | PATH.getFlag() | PROTOCOL.getFlag();
91+
if (receivedPseudoHeaders != requiredPseudoHeaders) {
92+
throw new Http3HeadersValidationException(
93+
"Not all mandatory pseudo-headers included for Extended CONNECT.");
94+
}
95+
} else {
96+
// Regular CONNECT (RFC 9114) requires:
97+
// - :method
98+
// - :authority
99+
final int requiredPseudoHeaders = METHOD.getFlag() | AUTHORITY.getFlag();
100+
if (receivedPseudoHeaders != requiredPseudoHeaders) {
101+
throw new Http3HeadersValidationException("Not all mandatory pseudo-headers included.");
102+
}
85103
}
86104
} else if (HttpMethod.OPTIONS.asciiName().contentEqualsIgnoreCase(method)) {
87105
// See:

src/main/java/io/netty/incubator/codec/http3/Http3SettingsFrame.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ public interface Http3SettingsFrame extends Http3ControlStreamFrame, Iterable<Ma
4040
*/
4141
long HTTP3_SETTINGS_MAX_FIELD_SECTION_SIZE = 0x6;
4242

43+
/**
44+
* See <a href="https://www.rfc-editor.org/rfc/rfc9220.html#section-5">
45+
* SETTINGS_ENABLE_CONNECT_PROTOCOL</a>.
46+
*/
47+
long HTTP3_SETTINGS_ENABLE_CONNECT_PROTOCOL = 0x8;
48+
4349
@Override
4450
default long type() {
4551
return Http3CodecUtils.HTTP3_SETTINGS_FRAME_TYPE;

0 commit comments

Comments
 (0)