pub struct RequireVersion(pub Version);Expand description
The caller’s per-request statement that this request needs a particular HTTP version, and must fail rather than go out over another one.
Put into http::Extensions on the request, and read by the transport at
the moment the protocol becomes known — which is before the head is
written, on every transport here that honours one. Absent, the
transport picks as it always did.
§It is AllowEarlyData’s mechanism with the polarity reversed
Same shape: a mark in the request’s extensions that a transport reads
and acts on before sending, Copy, defined in this crate because
transports read it and do not depend on hclient. The difference is
that one is a permission and this is a requirement, and that difference
is why both have to be per request rather than per client — see below.
§Why a demand and not a question
Capabilities::full_duplex and its neighbours report the floor:
the value that holds on the worst protocol a transport might negotiate.
That is right for a static answer and cannot be otherwise — Cargo
unifies features across a graph, so a library built on hclient can
never know whether some other crate turned http2 on — but it leaves a
caller who genuinely needs HTTP/2 with no way to act.
The two answers that do not work:
- Per response.
Response::version()already answers it, honestly, and after the fact. A caller structured for bidirectional streaming has to decide before it sends. - Per connection. There is no connection handle in the public API, so it means either a new seam or a query answered from a pool — and the pooled answer is racy in the way that matters: the entry can be evicted between the answer and the request that relied on it. It would be a fact about the past presented as a promise about the next request.
This is the third: the caller states the requirement, and the transport converts “the floor says no” into “this connection says yes” for one request, or fails it before committing to a shape that would deadlock.
§Why it cannot be a client-level setting
Turning an ALPN outcome into a request failure is correct for gRPC,
whose RPC cannot proceed over HTTP/1.1 at all, and wrong for a
browser-shaped client, which should degrade quietly. Only the caller
knows which of the two it is — the same argument that put
AllowEarlyData in the caller’s hands rather than in a transport’s
configuration.
§Exact match, deliberately, not a minimum
RequireVersion(HTTP_2) is satisfied by HTTP/2 and by nothing else. It
is tempting to read it as “at least”, and there is no ordering that
makes that mean anything: a caller who needs h2 framing does not want
HTTP/3 instead, and a caller who needs HTTP/1.1 — to keep an upgrade
path open, say — wants strictly less than HTTP/2, not more. A “minimum”
reading would satisfy the first demand with the wrong protocol and be
unable to express the second at all.
§Refusal, and the two shapes it takes
- The backend cannot honour demands at all
(
Capabilities::version_selectisfalse—hclient-fetchandhclient-wasi, neither of which chooses or even learns the version): a typedUnsupportedCapabilityfromClient, the same arm aRedirectPolicyagainstRedirectSupport::Internaltakes. It fires whatever version was demanded, because the backend cannot answer for any of them. - The backend honours demands and this connection does not match:
a typed
VersionNotAvailableunderErrorKind::Unsupported, raised by the transport before the head goes out.
A transport that always speaks one version still honours demands —
hclient-h3 reports version_select: true and answers
RequireVersion(HTTP_3) by proceeding and everything else with
VersionNotAvailable. Reporting false there would refuse the one
demand it trivially satisfies.
§The origin boundary, and why this one crosses it
AllowEarlyData comes off on a cross-origin redirect, because
“replaying this is safe” is a claim about what a request does at a
server and the caller judged only the first one. This mark is not
that kind of claim. It is a statement about the caller’s own code —
“the thing I am about to do needs this protocol” — and it is equally
true at hop 1 and at hop 4. Dropping it across an origin would mean a
redirect could silently deliver over HTTP/1.1 exactly the request that
said it could not use HTTP/1.1, which is the failure the mark exists to
prevent, arriving through the one door left open.
Tuple Fields§
§0: VersionTrait Implementations§
Source§impl Clone for RequireVersion
impl Clone for RequireVersion
Source§fn clone(&self) -> RequireVersion
fn clone(&self) -> RequireVersion
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read more