Skip to content

blackbull.client.http1

blackbull.client.http1

HTTP/1.1 request framing and response reading (RFC 9110, RFC 9112).

Fate

Bases: Enum

What a finished response leaves its connection fit for.

HTTP1Client

Bases: _TransportOwner

Async HTTP/1.1 client.

Use as an async context manager::

async with HTTP1Client('localhost', 8000) as c:
    res = await c.request(HTTPMethod.GET, '/path')

The connection persists across multiple request() calls when HTTP version, Connection options, and body framing permit it. A successful CONNECT or 101 can be transferred with handoff; after that, the returned HTTP1UpgradeSession owns transport closure. Pass ssl= to use TLS.

The Host header is injected automatically when the caller omits it.

wire_buffer property

Bytes sent so far by the low-level primitives in this session.

Empty unless the client was constructed with record_wire_bytes=True. Reset with reset_wire_buffer.

end_chunked() async

Emit the size-0 terminator chunk that closes a chunked body.

end_headers() async

Emit the bare CRLF that terminates the header block.

execute_scenario(scenario) async

Run connected-socket scenario steps and collect outcomes in ScenarioResult.

Peer and transport failures land on the result; a step outside the vocabulary raises TypeError and an exited client ConnectionError, both before any byte. Abort stops further steps. The vocabulary is docs/guide/fault_injection.md.

handoff()

Transfer a completed CONNECT/101 transport to a raw session.

The returned session preserves bytes already buffered by the HTTP reader and exposes both read and write sides. This operation is available exactly once. If the client context exits before handoff, the client closes the transport; after handoff, the session owns it.

read_response(*, request_method=None, offered_protocols=None, timeout=None) async

Read one HTTP/1.1 response from the connection.

Optional timeout bounds the entire read (status line + headers + body). Raises asyncio.TimeoutError if the deadline is hit; the caller decides whether to treat that as a transport- fail or a normal protocol outcome. Supply the protocols offered with Connection: upgrade as offered_protocols to validate a 101 for handoff; without them, a 101 is returned but its transport is closed.

reset_wire_buffer()

Discard previously captured wire bytes.

send_body_bytes(data, *, byte_interval=0.0) async

Send body bytes with the same semantics as send_raw.

send_chunk(data) async

Send one Transfer-Encoding: chunked chunk.

Caller must have already emitted Transfer-Encoding: chunked via send_header_line and called end_headers. Finish the chunked stream with end_chunked.

send_header_line(name, value) async

Emit one Name: Value\r\n header line with no dedup or validation. Callers wanting a duplicate Content-Length or a header value containing arbitrary bytes use this primitive directly.

send_raw(data, *, byte_interval=0.0) async

Push arbitrary bytes onto the underlying socket.

With byte_interval > 0 the bytes go out one at a time, that many seconds apart — the primitive a slowloris-style stall is built from. Each write is drained, so the pacing is on the wire and not in the asyncio send buffer.

send_request_line(method, target, *, version=b'HTTP/1.1') async

Emit METHOD<SP>TARGET<SP>HTTP/1.1\r\n with no validation.

Accepts arbitrary bytes for method/target/version so a test can deliberately send b"BREW", lowercase versions, or garbage tokens. No automatic Host or Content-Length injection — the caller drives the wire bit by bit.

stream(method, path, *, headers=(), body=b'') async

Send a request and yield body chunks lazily.

Unlike request() this does not buffer the response body, so gigabyte-sized responses do not need to fit in memory. Status and headers are not exposed by this method; use request() if you need them.

HTTP1RequestSender

Writes an HTTP/1.1 request — request line, headers, body — to an AbstractWriter.

Owns the request's framing: a byte body is sent under one Content-Length, an AsyncIterable body under that length when the caller declared one and under Transfer-Encoding: chunked when it did not. The Host header MUST be present (RFC 7230 §5.4) — the helper raises ProtocolError if it is not.

prepare(method, path, headers, body=b'') classmethod

Validate and render everything that can fail before wire I/O.

The high-level client claims response ownership only after this method succeeds. A caller error such as a mismatched Content-Length or an unencodable request target therefore cannot retire an untouched keep-alive connection.

Framing belongs to the sender, so the whole field pair is decided here: a caller's Content-Length is checked against the body and then written once, canonically. A caller's Transfer-Encoding is honoured only where rewriting it cannot change what the body means — the value chunked, the one framing this client writes — and refused otherwise. A body whose length is known goes out as that many raw octets, a stream included, with the total checked as it goes; one whose length is not knowable up front is chunk-framed instead.

The return value is internal: pass it to send_prepared unchanged.

send_prepared(prepared) async

Write a request returned by prepare.

HTTP1ResponseHeadReader

Read bounded response heads without body or connection-reuse policy.

receive_head(reader, *, skip_interim=True) async

Read through bounded interim heads and return the final head.

RFC 9110 requires clients to parse one or more 1xx responses before a final response. 101 is returned immediately because the following bytes belong to the switched protocol. The count cap owns aggregate work while the existing head limits continue to own each individual head.

HTTP1ResponseRecipient

Bases: HTTP1ResponseHeadReader

Reads an HTTP/1.1 response from an AbstractReader.

Decodes Content-Length-bound, Transfer-Encoding: chunked, and connection-close-delimited bodies. Returns a ClientResponse; stream() returns an async iterator of body chunks instead, so large responses don't have to fit in memory — true of all body framings.

receive(reader, *, method=None, skip_interim=True) async

Read one final response, optionally exposing an interim response.

Production callers keep skip_interim=True and therefore pass over 100/102/103 responses until the final response. 101 is final by protocol-switch semantics. The low-level fault-injection API passes skip_interim=False so each peer message remains observable.

method= overrides the constructor's, for a caller that knows it only at the read.

HTTP1UpgradeSession

Bases: _TransportClose

Bidirectional transport after CONNECT or an HTTP 101 switch.

Obtain one with HTTP1Client.handoff. The handoff is one-shot: once returned, this session owns transport closure and the originating HTTP client can no longer read or write the connection.

close() async

Close the transferred transport. Idempotent.

read(n=-1) async

Read switched-protocol bytes, including HTTP read-ahead bytes.

write(data) async

Write raw switched-protocol bytes.

__getattr__(name)

Deprecated SendBytes alias for SendRawBytes (BBD005).