blackbull.client.http2¶
blackbull.client.http2
¶
HTTP/2 client. HPACK and flow control are connection-owned; response state and deadlines are stream-owned.
ClientResponse
dataclass
¶
A complete HTTP response received by the client.
status is the HTTP status code (parsed from the :status pseudo-header).
headers are the final head's regular fields as a Headers instance
(bytes-keyed, lowercase-indexed). trailers is the trailer section that
followed the body: RFC 9113 §8.1 makes it a separate field section, and
gRPC puts grpc-status in it, so it is held apart rather than folded
into headers where its provenance would be lost. body is the
concatenation of all DATA-frame payloads received on the stream.
ended_on_head is true when END_STREAM arrived on the final head's
HEADERS frame, so no DATA frame and no trailer section followed — gRPC's
Trailers-Only shape. It is always false on HTTP/1.1.
HTTP2Client
¶
Bases: HTTP2Connection
Async HTTP/2 client.
Use as an async context manager::
async with HTTP2Client('localhost', 8000) as c:
res = await c.request(HTTPMethod.GET, '/')
ssl=None (the default) selects plaintext h2c. Provide an
ssl.SSLContext with set_alpn_protocols(['h2']) for h2 over TLS.
Multiple request() calls share the same connection: each gets its own
odd, monotonically-increasing client-initiated stream ID
(RFC 7540 §5.1.1) and the responses are demultiplexed by the receive loop.
frame_factory
property
¶
Return this connection's shared HPACK encoder/decoder and header limit.
Every header producer, including extended CONNECT, must reuse this context.
attach_stream_reader(stream_id)
async
¶
Deliver stream_id's DATA to a reader from now on, crediting as it is consumed; frames already queued for the raw stream go first.
execute_scenario(scenario)
async
¶
Run HTTP/2 fault steps and collect peer/transport outcomes in the result.
Peer and transport failures land on the result; a step outside the
vocabulary raises TypeError, an exited client ConnectionError
and a scenario the connection cannot run RuntimeError, all before
any byte.
Abort stops further steps.
receive_raw_frame()
async
¶
Escape hatch: read one raw frame, bypassing the receive loop's dispatch.
The read-side twin of send_raw_frame.
Only safe to call when the receive loop is not running (i.e. before
__aenter__ finishes or after the loop has been cancelled); a
concurrent loop would race this call for the reader.
Inherits client_h2_max_frame_size: one rule, not a second path
around it. A scenario needing an over-sized frame opts out with
BB_CLIENT_H2_MAX_FRAME_SIZE=0.
Refused after the context exits — the wait for a frame to begin is deliberately unbounded, so on a closed connection this parks for the life of the process.
register_raw_stream(stream_id)
¶
Mark stream_id as a raw-frame stream.
Frames arriving on this stream are pushed into the returned
asyncio.Queue instead of being routed through the
request/response state machine.
A fresh queue per call: registering the same stream twice is a
programming error, not a second subscription. The depth is
client_raw_queue_depth, which flow control cannot stand in for.
request(method, path, *, headers=(), body=b'')
async
¶
Send one request and await the matching response.
Adds :authority automatically from host:port. Header names
and values may be str or bytes; they are normalised to ASCII
str for HPACK encoding.
send_raw_frame(frame)
async
¶
Escape hatch: write a raw frame to the wire (negative-path tests).
Refused after the context exits: the transport is closed, so the write is discarded by asyncio without a word and the caller is told its frame reached the peer.
unregister_raw_stream(stream_id)
¶
Stop routing frames for stream_id into its raw-frame queue.
The one door a closed client still admits: this is teardown, nothing here needs the connection, and a cleanup path that raises after close turns an orderly shutdown into an error. What the peer still sends on the stream is ignored as after our own reset (§5.1).