pub struct RequestStream {
send: FramedSendStream,
recv: FramedRecvStream,
request_id: VarInt,
kind: RequestKind,
fetch_data: Option<FramedSendStream>,
draft: DraftVersion,
stream_id: u64,
cancelled: bool,
finished: bool,
origin: RequestOrigin,
responded: bool,
owes_publish_done: bool,
}Expand description
One request and its answer, on a bidirectional stream of their own.
Draft-19 Section 3.3 keeps requests off the control plane: each request is the first message on a bidirectional stream it opens, and the response comes back on that same stream. Responses carry no request id on this draft — the stream is the correlation, which is why this handle exists and why a bare request id is no longer enough to find an answer.
§Reading and writing go through the connection
This handle owns both halves of the stream but not the session, so the
endpoint state machine and the observer stay where they were. Read a
response with Connection::recv_on_request_stream, write a follow-up with
Connection::send_on_request_stream, and cancel with
Connection::cancel_request_stream.
cancel and peer_cancelled are on
the handle because they touch the stream and nothing else, and Drop
needs the first of them. Neither moves the endpoint’s record of the request,
which is why the connection carries a pair of its own.
§Dropping this cancels the request
A dropped handle resets the send half and sends STOP_SENDING on the
receive half, both with REQUEST_CANCELLED, unless the stream was
already cancelled or finished. Letting the default drop stand would send a
FIN instead, telling the peer the request ended cleanly when it was
abandoned.
The consequence is sharp and worth stating: a live subscription’s request
stream must be held for the subscription’s life, because PUBLISH_DONE
arrives on it. Keeping only request_id and letting
the handle fall out of scope cancels the subscription.
What a drop cannot do is say so at the endpoint. Drop holds the stream
and not the session, so the request stays where it was in the endpoint’s
record while the stream it travelled on is gone. Call
Connection::cancel_request_stream wherever that record matters.
§Which side opened it changes what this handle does
origin says whether this endpoint opened the stream or
accepted it, and three behaviours turn on it: reads dispatch as responses
or as follow-ups to the peer’s request, the respond_* helpers refuse a
stream this endpoint opened, and Drop resets with
REQUEST_UNANSWERED rather than REQUEST_CANCELLED. Everything else —
cancel, peer_cancelled,
Connection::send_on_request_stream — is the same in both directions.
Draft-19 Section 3.3.3 is explicit that a cancel is available to both:
“Senders cancel requests if the response is no longer of interest;
Receivers cancel requests if they are unable to or choose not to respond.”
finish is the one act that is constrained rather than
merely different: Section 3.3.2 forbids a FIN before the messages that
direction still owes.
All fields are private so the shape can grow without breaking callers.
Fields§
§send: FramedSendStream§recv: FramedRecvStream§request_id: VarInt§kind: RequestKind§fetch_data: Option<FramedSendStream>The unidirectional stream this request’s objects are being served on.
Only a FETCH has one, and only once the caller has opened it through
Connection::open_fetch_stream_on. Held here rather than in a table
on the connection because the request stream is already the thing that
knows what this request still owes, and because a handle kept beside
the request cannot outlive it.
draft: DraftVersion§stream_id: u64§cancelled: bool§finished: bool§origin: RequestOrigin§responded: boolWhether a respond_* helper has written a response on this stream.
True on a RequestOrigin::Peer stream from the response written on
it, and on a RequestOrigin::Local one from the answer to an update
the peer sent, which is the only response this endpoint writes on a
stream of its own.
owes_publish_done: boolWhether this endpoint is the publisher of a subscription established on this stream and has not yet sent PUBLISH_DONE.
True from the PUBLISH this endpoint sent, and from the SUBSCRIBE_OK it
wrote answering a peer’s SUBSCRIBE; false again once PUBLISH_DONE is
out. It is what draft-19 Section 3.3.2’s second clause is checked
against in finish.
Implementations§
Source§impl RequestStream
impl RequestStream
Sourcepub fn request_id(&self) -> VarInt
pub fn request_id(&self) -> VarInt
The request id the endpoint allocated for this request.
Useful for logging and for endpoint calls that still take one. It is not enough to find the response: draft-19 responses carry no request id, so only this stream identifies them.
Sourcepub fn kind(&self) -> RequestKind
pub fn kind(&self) -> RequestKind
Which of the seven request types opened this stream.
Sourcepub fn stream_id(&self) -> u64
pub fn stream_id(&self) -> u64
The transport-level stream identifier, the same one
ClientEvent::StreamOpened reports for data streams.
Sourcepub fn draft(&self) -> DraftVersion
pub fn draft(&self) -> DraftVersion
The draft version this stream is framed for.
Sourcepub fn origin(&self) -> RequestOrigin
pub fn origin(&self) -> RequestOrigin
Which side opened this stream.
RequestOrigin::Peer means this endpoint owes a response and the
respond_* helpers apply; RequestOrigin::Local means it is waiting
for one.
Sourcepub fn responded(&self) -> bool
pub fn responded(&self) -> bool
Whether a response has been written on this stream by one of the
respond_* helpers.
On a RequestOrigin::Local stream this says an update the peer sent
was answered here, not that the request itself was: that one is
answered by the peer.
Sourcepub fn owes_publish_done(&self) -> bool
pub fn owes_publish_done(&self) -> bool
Whether this endpoint still owes the peer a PUBLISH_DONE on this stream.
True for a PUBLISH this endpoint sent and for a peer’s SUBSCRIBE it
answered with SUBSCRIBE_OK — the two ways draft-19 Section 3.3.2’s
“publisher of an Established subscription” is reached — until
Connection::publish_done or Connection::publish_done_on has run.
While it is true, finish refuses.
Sourcepub fn fetch_data(&mut self) -> Option<&mut FramedSendStream>
pub fn fetch_data(&mut self) -> Option<&mut FramedSendStream>
The stream this request’s objects are being served on, if one is open.
Only a FETCH answered through
Connection::open_fetch_stream_on
has one. Writing objects goes through this rather than through a handle
the caller keeps, so that the connection can still reach the stream
when a rule says to reset it.
Sourcefn reset_fetch_data(&mut self)
fn reset_fetch_data(&mut self)
Reset the fetch data stream, if one was opened, and forget it.
The sentence that requires this names no error code, so the code comes from the registry rather than from here: CANCELLED is “the stream was cancelled by either endpoint”, which is what a publisher abandoning the objects it was serving has done.
Sourcepub fn is_cancelled(&self) -> bool
pub fn is_cancelled(&self) -> bool
Whether cancel has already run on this handle.
Says nothing about the peer: a peer reset is learned from
peer_cancelled or from the next read.
Sourcepub fn cancel(&mut self, code: u64) -> Result<(), ConnectionError>
pub fn cancel(&mut self, code: u64) -> Result<(), ConnectionError>
Cancel the request by resetting the stream, handing the peer code.
Draft-17 removed UNSUBSCRIBE and FETCH_CANCEL and draft-19 has not
brought them back: resetting the request stream is how a request is
withdrawn. Both halves are shut — a QUIC bidirectional stream has two
independent halves, so resetting only the send half would leave the
peer free to keep writing a response nobody will read. The send half is
reset with code and the receive half is stopped with the same value.
REQUEST_CANCELLED is the ordinary choice. Draft-19 Section 3.3.4
says an application SHOULD take the code from the Stream Reset Error
Codes registry when resetting, or sending STOP_SENDING on, any stream —
request streams included — so StreamResetErrorCode is where a value
other than the default should come from. The parameter is a plain u64
because the registry reserves greasing code points that have no variant;
a caller with a named code has StreamResetErrorCode::as_u64.
This is the stream and nothing else. The endpoint’s record of the
request does not move, so a response already in flight is still accepted
after this returns. Connection::cancel_request_stream does both and
is what a caller holding a connection should reach for; this stays
because Drop has no connection to reach.
Idempotent, and it retires the Drop behaviour: a cancelled handle
does nothing further when it goes out of scope. Errors from a stream
that was already reset or stopped are swallowed for the same reason —
the request is cancelled either way.
§Errors
ConnectionError::Transport carrying TransportError::Write if
code is outside the QUIC varint range (0..2^62). Nothing is sent
in that case, and the handle is not marked cancelled, so a caller
can retry with a representable code.
Sourcepub async fn peer_cancelled(&mut self) -> Result<Option<u64>, ConnectionError>
pub async fn peer_cancelled(&mut self) -> Result<Option<u64>, ConnectionError>
Wait for the peer to cancel this request, consuming nothing.
A caller applying backpressure is deliberately not calling
Connection::recv_on_request_stream, which is the only other place a
peer reset surfaces — so without this the abandonment goes unobserved
for as long as the backpressure lasts. This grants no flow-control
credit and is cancel-safe.
Returns Ok(Some(code)) with the peer’s application error code, or
Ok(None) meaning no reset is observable, now or ever — stop
asking. A caller that re-polls after Ok(None) spins.
Like cancel, this records nothing at the endpoint.
Connection::peer_cancelled_on_request_stream is the same wait with
the record attached.
On WebTransport this always answers Ok(None): wtransport exposes no
reset-only observable, so a WebTransport caller learns of a peer cancel
on its next read and not before.
Sourcepub async fn finish(&mut self) -> Result<(), ConnectionError>
pub async fn finish(&mut self) -> Result<(), ConnectionError>
Finish the send half cleanly, leaving the receive half open.
Draft-19 Section 3.3.2 settles what a FIN means, which earlier drafts left open: “A FIN only indicates that an endpoint will send no further messages in that direction; it is not a request cancellation.” A requester may therefore FIN before its response arrives — “A requester, with the exception of the sender of PUBLISH, MAY FIN immediately after sending a message if it will not send a REQUEST_UPDATE” — and the receive half stays open to carry the response, which is why this touches only the send half.
What the same section forbids is finishing early: “An endpoint MUST NOT send a FIN on a direction of a request stream until it has sent all required messages on that direction for its request type”, and “An endpoint that receives a FIN before all required messages have arrived treats the request as failed.” The section names the two messages that are always required — “an endpoint sending a response to a request MUST send the corresponding response message, and the publisher of an Established subscription MUST send PUBLISH_DONE” — and both are things this handle knows about itself, so both are checked here and nothing is written when either is outstanding.
A responder that owes a response has not sent one:
ConnectionError::FinBeforeResponse. A publisher that owes a
PUBLISH_DONE, whether from a PUBLISH it sent or a peer’s SUBSCRIBE it
accepted, gets ConnectionError::FinBeforePublishDone — which is why
Connection::publish_done and Connection::publish_done_on are the
routes that end a publication, rather than a bare finish.
What is not checked is everything the caller alone knows: a SUBSCRIBE_NAMESPACE responder deciding it has no more namespaces to announce, or a requester deciding it will send no REQUEST_UPDATE. The section leaves those to the endpoint, and so does this.
Drafts 17 and 18 have no Section 3.3.2 and no guard: their finish is
unconditional. A porter must not carry this one back to them.
A finished handle, like a cancelled one, does nothing further on
Drop. That matters here: the default drop resets both halves, which
Section 3.3.2 contrasts with a FIN as the abrupt close, so a request
that ended cleanly must come through this to avoid being reported as
cancelled.
Trait Implementations§
Source§impl Drop for RequestStream
impl Drop for RequestStream
Source§fn drop(&mut self)
fn drop(&mut self)
Reset the request unless it was already cancelled or finished.
See the type-level note: the default drop would FIN the send half, which claims a clean end for a request the caller walked away from — and on draft-19 a FIN is a stronger claim than on earlier drafts, since Section 3.3.2 has the receiver of one treat the request as failed only when messages are still owed and complete otherwise.
The code says which walking away it was. A stream this endpoint opened
is cancelled — REQUEST_CANCELLED — which is the requester act
Section 3.3.3 describes. A stream the peer opened is reset with
REQUEST_UNANSWERED whether or not a response was already written:
before one, the request was never served; after one, the obligations
that follow it are still outstanding.