WsOverH2Stream
struct WsOverH2StreamStream-keyed adapter that turns an h2 stream into a WS tunnel.
Owns the per-stream receive buffer (DATA frames are appended, WS frames are pulled out as they complete) and the outgoing masking-key state (a deterministic monotonic counter is fine here -- the over-the-wire mask is moot when h2 is itself over TLS, but we still emit it so the adapter is symmetrical with the h1 path).
Lifetime: one instance per Extended CONNECT stream. The
adapter does not drive the underlying h2 connection; the
caller is responsible for feeding bytes into / draining bytes
out of the connection. The adapter only reads
conn.conn.streams[sid].data (and clears it after each
successful decode) and pushes outgoing WS frames into
conn.send_data.
Implements Movable
- Fields
stream_id: Int: The h2 stream id (must match the id used in :func:bootstrap_ws_over_h2).read_buffer: List[UInt8]: Accumulated, undecoded receive bytes from :attr:Stream.data. Refilled on every :meth:pull_frames.closed: Bool: Set toTrueafter a CLOSE frame is sent or received. Subsequent send/recv attempts raise.max_frame_size: Int: Largest frame payload :meth:try_pull_frameaccepts. A frame that declares more is answered with CLOSE 1009 (MESSAGE_TOO_BIG) and the pull raises. Defaults to :data:DEFAULT_MAX_FRAME_BYTES.
__init__
def __init__(out self, stream_id: Int) Create an adapter bound to stream_id.
- Args
stream_id: The h2 stream id for the Extended CONNECT tunnel; allocate via :meth:Http2ClientConnection.next_stream_id.
send_frame
def send_frame(mut self, mut conn: Http2ClientConnection, frame: WsFrame) Encode frame and queue it as a DATA frame on this stream.
Per RFC 6455 §5.3 we mask the payload (mandatory on the
client side) with a fresh key from the OS CSPRNG. It was a
32-bit counter, which the RFC rules out: the key must be
unpredictable to whoever controls the payload, or masking does
not stop intermediary cache poisoning. The end_stream flag is set IFF the
frame is a CLOSE frame, which mirrors RFC 8441 §5.5
("close handshake completion translates to half-close").
- Args
conn: The HTTP/2 client connection.frame: The WS frame to send.
try_pull_frame
def try_pull_frame(mut self, mut conn: Http2ClientConnection) -> Optional[WsFrame] Drain the stream's DATA buffer and decode at most one complete WS frame.
Returns None if the buffer doesn't yet hold a full
frame (call again once more bytes have been fed). Returns
the next WsFrame otherwise; callers loop until None
to flush all frames waiting in this stream.
RFC 6455 §5.5 CLOSE frames flip :attr:closed so the
caller can treat the next outbound send as illegal and
the next inbound poll as EOF.
- Args
conn: The HTTP/2 client connection.
is_closed
def is_closed(self) -> Bool Return True once a CLOSE frame has been sent or received on this stream. Used by the caller to stop pumping after the close handshake completes.
close
def close(mut self, mut conn: Http2ClientConnection, code: UInt16 = UInt16(1000), reason: String = "") Send a WS CLOSE frame and END_STREAM the h2 stream atomically.
After this call :attr:closed is True and further
:meth:send_frame invocations raise. The caller is still
responsible for draining any in-flight CLOSE response
from the peer (RFC 6455 §5.5.1: the responder echoes the
code + reason when it acknowledges).
- Args
conn: The HTTP/2 client connection.code: Close status code (default :attr:WsCloseCode.NORMAL).reason: UTF-8 reason phrase (<= 123 bytes).