pub struct AllowEarlyData;Expand description
The caller’s per-request statement that this request may go into TLS 1.3 early data (0-RTT).
Put into http::Extensions on the request. Absent, the request waits
for the handshake to complete, and there is no configuration in which
a request the caller did not mark ends up in early data. Present
against a transport reporting EarlyDataSupport::None, it is a typed
UnsupportedCapability rather than a silent no-op.
§What marking a request asserts, and what it does not
It is an assertion that replaying this request is SAFE — not that replaying it is POSSIBLE. Those are different questions, and only the caller can answer the first one.
RequestBody::retry_kind answers the
second: Free, ViaFactory, Impossible — can I send these bytes
again. A transport needs that answer, because a rejected 0-RTT request
has to be replayed after the handshake and a
RetryKind::Impossible body cannot be.
So RetryKind is a correctness precondition here, and it is checked
as one.
It is emphatically not a safety condition, and reading it as one is
the mistake to avoid. POST /transfer with a
fully buffered body is RetryKind::Free — trivially replayable, and
precisely the request that must never enter early data, because an
attacker can replay it too. quinn says the same in one line: “this
enables transmission of 0-RTT data, which is vulnerable to replay
attacks, and should therefore never invoke non-idempotent operations”.
The notion that would answer the safety question — method safety and
idempotency — deliberately does not exist in this codebase, and its
absence is written down where the one v0.2 retry lives. RFC 8470 §2 puts
the default on the conservative side (“clients MAY send requests with
safe HTTP methods … and MUST NOT send unsafe methods (or methods whose
safety is not known) in early data”) and, in the same sentence, says
why a method table cannot be the whole answer: “absent other
information”. GET is not safe on plenty of real APIs, and only the
caller knows which. Hence this extension: a caller-visible decision,
with a method check beneath it, rather than a table hidden in a
transport.
§The third failure path
A request placed in early data can fail in three places, not one: no
usable key material (nothing was risked, fall back silently), the server
rejecting the 0-RTT keys (replay on the same connection once the
handshake finishes — the transport’s job, invisible to the caller), and
HTTP 425 Too Early (RFC 8470 §5.2), which arrives a full round
trip later and must be retried not in early data. The third is a
status-code branch in the client, not in a transport.
A retry built for a 425 must remove this extension from the request
it replays. RFC 8470 requires it, and it is not a formality: on
hclient-h3 this mark is part of the connection pool’s key, so a
replay that kept it would ask for the early-data connection and — if
that one has been evicted or closed since — would open a fresh one and
go out in early data again, to the server that just refused to risk it.
See hclient_h3::early.
§The other boundary: an origin
The mark does not cross one, and hclient’s redirect stage drops it on
the same condition that drops Cookie and Authorization — the host or
scheme changed.
The asymmetry is the point and the two halves are easy to conflate. This
is a claim about what a request does at a server, so a caller who
marked a request for origin A never judged origin B, and carrying it
across would act on a judgement nobody made. A method change is the
opposite case and the mark stays: a 303 rewriting POST to GET
leaves a request strictly less consequential than the one already
vouched for.
Trait Implementations§
Source§impl Clone for AllowEarlyData
impl Clone for AllowEarlyData
Source§fn clone(&self) -> AllowEarlyData
fn clone(&self) -> AllowEarlyData
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read more