net.http: negotiate HTTP/2 in fetch via ALPN (opt-in)#27362
Merged
JalonSolov merged 2 commits intoJun 6, 2026
Conversation
Contributor
Author
|
#codex review |
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>
ab9454b to
c86f758
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c86f758630
ℹ️ 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".
…etch 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>
JalonSolov
pushed a commit
that referenced
this pull request
Jun 7, 2026
* net.http: stream response callbacks and stop limits over HTTP/2 (closes #27368) The HTTP/2 fetch path (#27362) buffered the entire response body, so requests using on_progress / on_progress_body / stop_copying_limit / stop_receiving_limit were forced onto HTTP/1.1. This adds real streaming support so they work on the HTTP/2 path too. - New H2ClientRequest fields: on_data (per-DATA-frame callback) and stop_copying_limit / stop_receiving_limit, mirroring the HTTP/1.1 semantics. - H2Conn.read_response now tracks cumulative body bytes, reads Content-Length if present, fires on_data per DATA frame (including chunk, cumulative body_so_far, content-length, and status), respects stop_copying_limit (caps the response body while still firing callbacks and draining the stream), and respects stop_receiving_limit (breaks the read loop early). - The h2_do shim in backend.c.v adapts the Request's on_progress and on_progress_body into a single H2DataFn closure and threads the two stop limits through. The previous gate (uses_response_streaming) is removed, and the enable_http2 docs note that on_progress fires per DATA payload on h2 rather than per raw network read. Tests over the in-memory transport assert: on_data fires per DATA frame with cumulative body_so_far, Content-Length (when present), and status; stop_copying_limit caps the response body while callbacks keep firing across all chunks; stop_receiving_limit breaks the loop early. Verified end-to-end against https://www.google.com/ — http.fetch(enable_http2: true, on_progress_body: f) reports HTTP/2.0, status 200, and the on_progress_body callback fires once per 16 KiB DATA frame with cumulative bytes matching the final body length. Passes under -W -cstrict -cc clang. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * net.http: RST_STREAM(CANCEL) and mark H2Conn unusable on early termination When stop_receiving_limit triggered, the response stream was left open without sending RST_STREAM. On a reused H2Conn the peer's in-flight DATA frames for the abandoned stream would still arrive, consuming the connection-level receive window and risking starvation of subsequent requests. Fix: when bailing early, send RST_STREAM with error code CANCEL on the request stream (RFC 7540 Section 8.1.4 / 5.4.2) so the peer stops sending more DATA, and set a new H2Conn.aborted flag so subsequent H2Conn.do() calls return a clear error rather than proceeding on a half-drained connection. Strengthens the stop_receiving_limit test to assert the client emitted RST_STREAM(CANCEL) on the request stream and that a second do() on the same connection errors out. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Richard Wheeler <quaesitor.scientiam@gmail.com> Co-authored-by: Claude Opus 4.7 <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>
dy-tea
pushed a commit
to wenxuanjun/v
that referenced
this pull request
Jun 19, 2026
…g#27369) * net.http: stream response callbacks and stop limits over HTTP/2 (closes vlang#27368) The HTTP/2 fetch path (vlang#27362) buffered the entire response body, so requests using on_progress / on_progress_body / stop_copying_limit / stop_receiving_limit were forced onto HTTP/1.1. This adds real streaming support so they work on the HTTP/2 path too. - New H2ClientRequest fields: on_data (per-DATA-frame callback) and stop_copying_limit / stop_receiving_limit, mirroring the HTTP/1.1 semantics. - H2Conn.read_response now tracks cumulative body bytes, reads Content-Length if present, fires on_data per DATA frame (including chunk, cumulative body_so_far, content-length, and status), respects stop_copying_limit (caps the response body while still firing callbacks and draining the stream), and respects stop_receiving_limit (breaks the read loop early). - The h2_do shim in backend.c.v adapts the Request's on_progress and on_progress_body into a single H2DataFn closure and threads the two stop limits through. The previous gate (uses_response_streaming) is removed, and the enable_http2 docs note that on_progress fires per DATA payload on h2 rather than per raw network read. Tests over the in-memory transport assert: on_data fires per DATA frame with cumulative body_so_far, Content-Length (when present), and status; stop_copying_limit caps the response body while callbacks keep firing across all chunks; stop_receiving_limit breaks the loop early. Verified end-to-end against https://www.google.com/ — http.fetch(enable_http2: true, on_progress_body: f) reports HTTP/2.0, status 200, and the on_progress_body callback fires once per 16 KiB DATA frame with cumulative bytes matching the final body length. Passes under -W -cstrict -cc clang. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * net.http: RST_STREAM(CANCEL) and mark H2Conn unusable on early termination When stop_receiving_limit triggered, the response stream was left open without sending RST_STREAM. On a reused H2Conn the peer's in-flight DATA frames for the abandoned stream would still arrive, consuming the connection-level receive window and risking starvation of subsequent requests. Fix: when bailing early, send RST_STREAM with error code CANCEL on the request stream (RFC 7540 Section 8.1.4 / 5.4.2) so the peer stops sending more DATA, and set a new H2Conn.aborted flag so subsequent H2Conn.do() calls return a clear error rather than proceeding on a half-drained connection. Strengthens the stop_receiving_limit test to assert the client emitted RST_STREAM(CANCEL) on the request stream and that a second do() on the same connection errors out. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Richard Wheeler <quaesitor.scientiam@gmail.com> Co-authored-by: Claude Opus 4.7 <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
Wires the HTTP/2 client connection (#27361) into the existing fetch path so a
single
http.fetchcall transparently speaks HTTP/2 when the serversupports it, and HTTP/1.1 otherwise. This is the user-facing unification of the
HTTP/2 work (ALPN #27343, HPACK #27353, frames #27356, client #27361), now that
all of its dependencies have merged.
Changes
request.v,http.venable_http2field toRequestandFetchConfig(and copy it throughprepare)backend.c.vh2, http/1.1when enabled; if the server selectsh2, run overH2Conn(h2_do), else the existing HTTP/1.1 path unchangedh2_client.vh2_client_test.v,h2_conn_test.vDesign
enable_http2defaults tofalse, so existingcallers are completely unaffected and no ALPN extension is sent. A follow-up
can flip the default to "prefer h2" once this has proven out.
negotiated_alpn() == 'h2'innet_ssl_do; the HTTP/1.1 path is untouched.(RFC 7540 §8.1.2.2), maps
Hostto the:authoritypseudo-header, collapsescookies into one field, and decodes
Content-Encodingon the response exactlylike the HTTP/1.1 path.
resp.version()reports2.0.one H2 request); those are later follow-ups.
Tests
lowercasing, hop-by-hop stripping, cookie collapsing, content-length,
authority/port).
Request→H2Connover an in-memory transport →Response, with no socket.-d networkso it does not add CI flakiness.Verified end-to-end against
https://www.google.com/(HTTP/2.0, 200), with thedefault path still selecting HTTP/1.1.
-W -cstrict -cc clangpasses; the fullvlib/net/httpsuite is green (1 Windows-only skip).🤖 Generated with Claude Code