Skip to content

net.http: add HTTP/2 frame codec (RFC 7540)#27356

Merged
JalonSolov merged 2 commits into
vlang:masterfrom
quaesitor-scientiam:net-http2-frames
Jun 6, 2026
Merged

net.http: add HTTP/2 frame codec (RFC 7540)#27356
JalonSolov merged 2 commits into
vlang:masterfrom
quaesitor-scientiam:net-http2-frames

Conversation

@quaesitor-scientiam

Copy link
Copy Markdown
Contributor

What

Adds the HTTP/2 binary framing layer (RFC 7540 Sections 4 & 6) to
net.http: encoders and decoders for all ten frame types, plus a fallthrough
for unknown types.

Like the HPACK PR (#27353), this is purely additive — new files in the
http module, no changes to any existing net.http code path. Nothing calls
it yet, so there is no user-visible behaviour change. It is independent of both
#27353 (HPACK) and #27343 (ALPN) and can be reviewed in parallel.

Files

File Contents
h2_frame.v H2FrameHeader, the per-type frame structs, the H2Frame sum type, decoders (h2_read_frame / h2_parse_frame) and .encode()
h2_error.v H2ErrorCode enum (RFC 7540 Section 7)
h2_frame_test.v Tests

Design notes

  • All ten frame types (DATA, HEADERS, PRIORITY, RST_STREAM, SETTINGS,
    PUSH_PROMISE, PING, GOAWAY, WINDOW_UPDATE, CONTINUATION) modelled as a
    H2Frame sum type, plus H2UnknownFrame.
  • Unknown frame types are preserved, not rejected (RFC 7540 Section 4.1),
    so the connection layer can ignore them while still advancing the stream.
  • PADDED handling for DATA/HEADERS/PUSH_PROMISE and the PRIORITY section
    on HEADERS are decoded; the encoder never emits padding.
  • Structural validation that the connection layer maps to
    FRAME_SIZE_ERROR / PROTOCOL_ERROR: fixed-size frames (PRIORITY,
    RST_STREAM, PING, WINDOW_UPDATE), SETTINGS payload as a multiple of 6 with an
    empty ACK, and the stream-id rules (DATA/HEADERS non-zero; SETTINGS/PING/
    GOAWAY zero).
  • Decoded byte fields are cloned, so frames own their data independently of the
    input buffer.

Tests

h2_frame_test.v (internal module http test) covers:

  • An encode/decode round-trip for every frame type.
  • The on-wire 9-byte header layout and reserved-bit masking on read.
  • Padding decode, including a pad length that exceeds the frame.
  • The PRIORITY section on HEADERS (exclusive, dependency, weight).
  • Unknown-frame preservation and exact re-encoding.
  • Reading consecutive frames from a single buffer.
  • Structural validation errors: truncated payload, bad SETTINGS length, SETTINGS
    ACK with a payload, bad WINDOW_UPDATE/PING length, DATA on stream 0, SETTINGS
    on a non-zero stream.
./vnew test vlib/net/http/h2_frame_test.v                       # passes
./vnew -W -cstrict -cc clang test vlib/net/http/h2_frame_test.v # passes
./vnew -silent test vlib/net/http/                              # 15 passed, 1 skipped (Windows)

Roadmap

This is the third of several small, independent PRs building toward HTTP/2,
alongside #27343 (ALPN in net.ssl) and #27353 (HPACK). With framing and HPACK in
place, the next step is the client connection state machine that ties them
together over an ALPN-negotiated h2 TLS socket.

🤖 Generated with Claude Code

Add the HTTP/2 binary framing layer: encoders and decoders for all ten frame
types plus a fallthrough for unknown types. Like the HPACK PR, this is purely
additive (new files in the http module) and does not touch any existing
net.http code path; nothing calls it yet.

Included:
- H2FrameHeader parsing of the 9-byte header, ignoring the reserved bit.
- A H2Frame sum type with DATA, HEADERS, PRIORITY, RST_STREAM, SETTINGS,
  PUSH_PROMISE, PING, GOAWAY, WINDOW_UPDATE, CONTINUATION, and an UnknownFrame
  variant. Unknown frame types are preserved rather than rejected, as required
  by RFC 7540 Section 4.1.
- h2_read_frame / h2_parse_frame decoders and an .encode() method, with PADDED
  and PRIORITY handling for HEADERS/DATA/PUSH_PROMISE.
- Structural validation that maps to FRAME_SIZE_ERROR / PROTOCOL_ERROR at the
  connection layer: fixed-size frames (PRIORITY, RST_STREAM, PING,
  WINDOW_UPDATE), SETTINGS payload as a multiple of 6 with empty ACK, and the
  stream-id rules (DATA/HEADERS/etc. non-zero; SETTINGS/PING/GOAWAY zero).
- H2ErrorCode enum (Section 7) and the SETTINGS identifier and frame
  type/flag constants.

Tests cover an encode/decode round-trip for every frame type, the on-wire
header layout, reserved-bit masking, padding decode (including pad length
exceeding the frame), the priority section on HEADERS, unknown-frame
preservation and re-encoding, reading consecutive frames from one buffer, and
the structural validation errors above. Passes under -W -cstrict -cc clang.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e79358ae89

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread vlib/net/http/h2_frame.v
Comment thread vlib/net/http/h2_frame.v
Add comments noting that PRIORITY self-dependency (RFC 7540 Section 5.3.1) and
zero WINDOW_UPDATE increments (Section 6.9) are intentionally not rejected by
the frame codec. Both are stream errors whose correct handling is RST_STREAM on
the affected stream (and, for a zero increment on stream 0, a connection
error). That stream-vs-connection distinction belongs to the connection layer,
which needs the decoded frame to respond; rejecting in the codec would turn a
recoverable stream error into a connection-fatal decode error.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@JalonSolov JalonSolov merged commit 57badad into vlang:master Jun 6, 2026
73 of 83 checks passed
JalonSolov pushed a commit that referenced this pull request Jun 6, 2026
* net.http: add synchronous HTTP/2 client connection (H2Conn)

Build on the ALPN (#27343), HPACK (#27353), and frame-codec (#27356) PRs to add
a minimal, synchronous, single-stream HTTP/2 client. Additive only: new files in
the http module, no change to existing code paths. Nothing wires it into
http.fetch yet (that ALPN shim is a follow-up), so there is no user-visible
behaviour change.

- H2Transport interface — the byte transport the connection runs over. Its
  read/write signatures match net.ssl.SSLConn, so an ALPN-negotiated `h2` TLS
  socket satisfies it directly; tests use an in-memory mock, so the connection
  is exercised without a socket.
- Connection preface + SETTINGS handshake (sent lazily on the first request).
- H2Conn.do(req): HPACK-encodes the request headers, sends HEADERS (plus DATA
  for a body, chunked to the peer's max frame size and bounded by the
  connection flow-control window), then reads frames until the stream closes.
- Inline servicing of connection-level frames: SETTINGS (apply + ACK), PING
  (echo ACK), WINDOW_UPDATE (grow the send window), GOAWAY (fail). Receive-side
  flow control is replenished with a WINDOW_UPDATE per DATA frame.
- CONTINUATION reassembly for response header blocks; RST_STREAM on the request
  stream is surfaced as an error.

Hermetic tests over the mock transport cover: a basic GET, a multi-DATA
response, a POST with a body, GOAWAY, RST_STREAM, CONTINUATION-split response
headers, and PING ACK. Passes under -W -cstrict -cc clang; the full
vlib/net/http suite is green.

Not included here, by design (follow-ups): stream multiplexing with a
background reader thread, connection pooling, wiring into http.fetch via ALPN,
and the server side.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* net.http: HTTP/2 client header splitting and per-stream flow control

Address two send-path correctness gaps in H2Conn:

- Split request header blocks larger than the peer's SETTINGS_MAX_FRAME_SIZE
  into a HEADERS frame followed by CONTINUATION frames (RFC 7540 Section 4.3),
  via send_header_block. Previously a large header set (e.g. big Cookie or
  Authorization) was sent as one oversized HEADERS frame that a compliant peer
  would reject.

- Track the per-stream send window in addition to the connection window
  (RFC 7540 Section 6.9). DATA is now bounded by min(connection, stream)
  window, stream-level WINDOW_UPDATE credits the active stream, and a change to
  SETTINGS_INITIAL_WINDOW_SIZE retroactively adjusts the stream window by the
  delta (Section 6.9.2). Previously only the connection window was tracked, so
  a peer lowering the initial window or granting stream-level updates could be
  overrun or could stall the client.

Adds tests for a request whose header block spans HEADERS + CONTINUATION, and
for a body larger than the initial window that resumes after the peer grows
both windows (no DATA frame exceeding the max frame size).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Richard Wheeler <quaesitor.scientiam@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
dy-tea pushed a commit to wenxuanjun/v that referenced this pull request Jun 19, 2026
* net.http: add HTTP/2 frame codec (RFC 7540) for HTTP/2

Add the HTTP/2 binary framing layer: encoders and decoders for all ten frame
types plus a fallthrough for unknown types. Like the HPACK PR, this is purely
additive (new files in the http module) and does not touch any existing
net.http code path; nothing calls it yet.

Included:
- H2FrameHeader parsing of the 9-byte header, ignoring the reserved bit.
- A H2Frame sum type with DATA, HEADERS, PRIORITY, RST_STREAM, SETTINGS,
  PUSH_PROMISE, PING, GOAWAY, WINDOW_UPDATE, CONTINUATION, and an UnknownFrame
  variant. Unknown frame types are preserved rather than rejected, as required
  by RFC 7540 Section 4.1.
- h2_read_frame / h2_parse_frame decoders and an .encode() method, with PADDED
  and PRIORITY handling for HEADERS/DATA/PUSH_PROMISE.
- Structural validation that maps to FRAME_SIZE_ERROR / PROTOCOL_ERROR at the
  connection layer: fixed-size frames (PRIORITY, RST_STREAM, PING,
  WINDOW_UPDATE), SETTINGS payload as a multiple of 6 with empty ACK, and the
  stream-id rules (DATA/HEADERS/etc. non-zero; SETTINGS/PING/GOAWAY zero).
- H2ErrorCode enum (Section 7) and the SETTINGS identifier and frame
  type/flag constants.

Tests cover an encode/decode round-trip for every frame type, the on-wire
header layout, reserved-bit masking, padding decode (including pad length
exceeding the frame), the priority section on HEADERS, unknown-frame
preservation and re-encoding, reading consecutive frames from one buffer, and
the structural validation errors above. Passes under -W -cstrict -cc clang.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* net.http: document deferred HTTP/2 semantic checks in frame codec

Add comments noting that PRIORITY self-dependency (RFC 7540 Section 5.3.1) and
zero WINDOW_UPDATE increments (Section 6.9) are intentionally not rejected by
the frame codec. Both are stream errors whose correct handling is RST_STREAM on
the affected stream (and, for a zero increment on stream 0, a connection
error). That stream-vs-connection distinction belongs to the connection layer,
which needs the decoded frame to respond; rejecting in the codec would turn a
recoverable stream error into a connection-fatal decode error.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Richard Wheeler <quaesitor.scientiam@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
dy-tea pushed a commit to wenxuanjun/v that referenced this pull request Jun 19, 2026
)

* net.http: add synchronous HTTP/2 client connection (H2Conn)

Build on the ALPN (vlang#27343), HPACK (vlang#27353), and frame-codec (vlang#27356) PRs to add
a minimal, synchronous, single-stream HTTP/2 client. Additive only: new files in
the http module, no change to existing code paths. Nothing wires it into
http.fetch yet (that ALPN shim is a follow-up), so there is no user-visible
behaviour change.

- H2Transport interface — the byte transport the connection runs over. Its
  read/write signatures match net.ssl.SSLConn, so an ALPN-negotiated `h2` TLS
  socket satisfies it directly; tests use an in-memory mock, so the connection
  is exercised without a socket.
- Connection preface + SETTINGS handshake (sent lazily on the first request).
- H2Conn.do(req): HPACK-encodes the request headers, sends HEADERS (plus DATA
  for a body, chunked to the peer's max frame size and bounded by the
  connection flow-control window), then reads frames until the stream closes.
- Inline servicing of connection-level frames: SETTINGS (apply + ACK), PING
  (echo ACK), WINDOW_UPDATE (grow the send window), GOAWAY (fail). Receive-side
  flow control is replenished with a WINDOW_UPDATE per DATA frame.
- CONTINUATION reassembly for response header blocks; RST_STREAM on the request
  stream is surfaced as an error.

Hermetic tests over the mock transport cover: a basic GET, a multi-DATA
response, a POST with a body, GOAWAY, RST_STREAM, CONTINUATION-split response
headers, and PING ACK. Passes under -W -cstrict -cc clang; the full
vlib/net/http suite is green.

Not included here, by design (follow-ups): stream multiplexing with a
background reader thread, connection pooling, wiring into http.fetch via ALPN,
and the server side.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* net.http: HTTP/2 client header splitting and per-stream flow control

Address two send-path correctness gaps in H2Conn:

- Split request header blocks larger than the peer's SETTINGS_MAX_FRAME_SIZE
  into a HEADERS frame followed by CONTINUATION frames (RFC 7540 Section 4.3),
  via send_header_block. Previously a large header set (e.g. big Cookie or
  Authorization) was sent as one oversized HEADERS frame that a compliant peer
  would reject.

- Track the per-stream send window in addition to the connection window
  (RFC 7540 Section 6.9). DATA is now bounded by min(connection, stream)
  window, stream-level WINDOW_UPDATE credits the active stream, and a change to
  SETTINGS_INITIAL_WINDOW_SIZE retroactively adjusts the stream window by the
  delta (Section 6.9.2). Previously only the connection window was tracked, so
  a peer lowering the initial window or granting stream-level updates could be
  overrun or could stall the client.

Adds tests for a request whose header block spans HEADERS + CONTINUATION, and
for a body larger than the initial window that resumes after the peer grows
both windows (no DATA frame exceeding the max frame size).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Richard Wheeler <quaesitor.scientiam@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants