net.http: add synchronous HTTP/2 client connection (H2Conn)#27361
Merged
Conversation
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>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b62a632e19
ℹ️ 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".
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>
quaesitor-scientiam
pushed a commit
to quaesitor-scientiam/v
that referenced
this pull request
Jun 6, 2026
Wire the HTTP/2 client connection into the existing fetch path so a single
http.fetch call transparently speaks HTTP/2 when the server supports it.
- New opt-in field `enable_http2` on Request and FetchConfig (default false, so
existing callers are unaffected and nothing changes on the wire).
- In net_ssl_do, when enabled, advertise ALPN `h2, http/1.1`. After the
handshake, if the server selected `h2`, run the request over H2Conn; otherwise
fall back to the existing HTTP/1.1 path unchanged.
- Conversion glue (h2_client.v, pure/backend-agnostic): build an H2 request from
a net.http Request — lowercasing header names, dropping hop-by-hop headers,
mapping Host to the :authority pseudo-header, and collapsing cookies — and
convert the H2 response back to a net.http Response (decoding Content-Encoding
the same way the HTTP/1.1 path does, and reporting version 2.0).
Usage:
resp := http.fetch(url: 'https://example.com/', enable_http2: true)!
assert resp.version() == .v2_0 // when the server offers h2
Tests: pure unit tests for the request/response conversions, a full glue
round-trip (Request -> H2Conn over an in-memory transport -> Response), and an
opt-in end-to-end test against a real h2 server (run with `-d network`).
Verified manually against https://www.google.com/ (HTTP/2.0, 200), with the
default path still selecting HTTP/1.1. Passes under -W -cstrict -cc clang.
Depends on the H2Conn client (vlang#27361).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
JalonSolov
pushed a commit
that referenced
this pull request
Jun 6, 2026
* net.http: negotiate HTTP/2 in fetch via ALPN (opt-in)
Wire the HTTP/2 client connection into the existing fetch path so a single
http.fetch call transparently speaks HTTP/2 when the server supports it.
- New opt-in field `enable_http2` on Request and FetchConfig (default false, so
existing callers are unaffected and nothing changes on the wire).
- In net_ssl_do, when enabled, advertise ALPN `h2, http/1.1`. After the
handshake, if the server selected `h2`, run the request over H2Conn; otherwise
fall back to the existing HTTP/1.1 path unchanged.
- Conversion glue (h2_client.v, pure/backend-agnostic): build an H2 request from
a net.http Request — lowercasing header names, dropping hop-by-hop headers,
mapping Host to the :authority pseudo-header, and collapsing cookies — and
convert the H2 response back to a net.http Response (decoding Content-Encoding
the same way the HTTP/1.1 path does, and reporting version 2.0).
Usage:
resp := http.fetch(url: 'https://example.com/', enable_http2: true)!
assert resp.version() == .v2_0 // when the server offers h2
Tests: pure unit tests for the request/response conversions, a full glue
round-trip (Request -> H2Conn over an in-memory transport -> Response), and an
opt-in end-to-end test against a real h2 server (run with `-d network`).
Verified manually against https://www.google.com/ (HTTP/2.0, 200), with the
default path still selecting HTTP/1.1. Passes under -W -cstrict -cc clang.
Depends on the H2Conn client (#27361).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* net.http: honor explicit Host and keep streaming on HTTP/1.1 for h2 fetch
Address two send-path correctness gaps in the HTTP/2 fetch shim:
- Derive the :authority pseudo-header from an explicit Host header when the
caller set one, matching the HTTP/1.1 path. Previously virtual-host /
host-override requests sent the URL host as :authority over HTTP/2 even though
they worked over HTTP/1.1.
- Do not negotiate HTTP/2 for requests that use streaming response callbacks
(on_progress / on_progress_body) or stop limits (stop_copying_limit /
stop_receiving_limit). The HTTP/2 path buffers the full response, so those
would be silently ignored. The ALPN offer is now gated on this before
negotiation — a server cannot select h2 for such requests — so they keep the
existing HTTP/1.1 streaming behavior. (Streaming over HTTP/2 is a planned
follow-up.) The enable_http2 docs note this.
Adds tests for Host-derived authority and for uses_response_streaming().
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>
dy-tea
pushed a commit
to wenxuanjun/v
that referenced
this pull request
Jun 19, 2026
* net.http: negotiate HTTP/2 in fetch via ALPN (opt-in)
Wire the HTTP/2 client connection into the existing fetch path so a single
http.fetch call transparently speaks HTTP/2 when the server supports it.
- New opt-in field `enable_http2` on Request and FetchConfig (default false, so
existing callers are unaffected and nothing changes on the wire).
- In net_ssl_do, when enabled, advertise ALPN `h2, http/1.1`. After the
handshake, if the server selected `h2`, run the request over H2Conn; otherwise
fall back to the existing HTTP/1.1 path unchanged.
- Conversion glue (h2_client.v, pure/backend-agnostic): build an H2 request from
a net.http Request — lowercasing header names, dropping hop-by-hop headers,
mapping Host to the :authority pseudo-header, and collapsing cookies — and
convert the H2 response back to a net.http Response (decoding Content-Encoding
the same way the HTTP/1.1 path does, and reporting version 2.0).
Usage:
resp := http.fetch(url: 'https://example.com/', enable_http2: true)!
assert resp.version() == .v2_0 // when the server offers h2
Tests: pure unit tests for the request/response conversions, a full glue
round-trip (Request -> H2Conn over an in-memory transport -> Response), and an
opt-in end-to-end test against a real h2 server (run with `-d network`).
Verified manually against https://www.google.com/ (HTTP/2.0, 200), with the
default path still selecting HTTP/1.1. Passes under -W -cstrict -cc clang.
Depends on the H2Conn client (vlang#27361).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* net.http: honor explicit Host and keep streaming on HTTP/1.1 for h2 fetch
Address two send-path correctness gaps in the HTTP/2 fetch shim:
- Derive the :authority pseudo-header from an explicit Host header when the
caller set one, matching the HTTP/1.1 path. Previously virtual-host /
host-override requests sent the URL host as :authority over HTTP/2 even though
they worked over HTTP/1.1.
- Do not negotiate HTTP/2 for requests that use streaming response callbacks
(on_progress / on_progress_body) or stop limits (stop_copying_limit /
stop_receiving_limit). The HTTP/2 path buffers the full response, so those
would be silently ignored. The ALPN offer is now gated on this before
negotiation — a server cannot select h2 for such requests — so they keep the
existing HTTP/1.1 streaming behavior. (Streaming over HTTP/2 is a planned
follow-up.) The enable_http2 docs note this.
Adds tests for Host-derived authority and for uses_response_streaming().
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds a minimal, synchronous, single-stream HTTP/2 client connection to
net.http, building on the now-merged ALPN (#27343), HPACK (#27353), andframe-codec (#27356) PRs.
Purely additive — new files in the
httpmodule, no change to any existingcode path. Nothing wires it into
http.fetchyet (that ALPN shim is the nextPR), so there is no user-visible behaviour change.
Files
h2_conn.vH2Transportinterface,H2Conn,H2ClientRequest/H2ClientResponse, the request/response state machineh2_conn_test.vDesign
H2Transportinterface — the byte transport the connection runs over. Itsread/writesignatures matchnet.ssl.SSLConn, so an ALPN-negotiatedh2TLS socket satisfies it directly; tests use an in-memory mock, so the
connection logic is exercised without a socket.
request.
H2Conn.do(req)— HPACK-encodes the request headers, sends HEADERS (plusDATA for a body, chunked to the peer's
SETTINGS_MAX_FRAME_SIZEand boundedby the connection flow-control window), then reads frames until the stream
closes.
ACK), WINDOW_UPDATE (grow the send window), GOAWAY (fail). Receive-side flow
control is replenished with a WINDOW_UPDATE per DATA frame.
request stream is surfaced as an error.
This is deliberately synchronous and single-stream — the smallest shape that
is genuinely useful and fully testable. Stream multiplexing (with a background
reader thread) is a clear follow-up.
Tests
h2_conn_test.v(internalmodule httptest) drives the client over aMockTransportthat plays a scripted server side and records what the clientwrites. Coverage:
pseudo-headers by HPACK-decoding the client's HEADERS frame),
frame with END_STREAM),
Roadmap
Next steps, each its own PR:
http.fetchvia ALPN (h2negotiated → useH2Conn, else theexisting HTTP/1.1 path) — the user-facing unification.
🤖 Generated with Claude Code