Skip to content

blackbull.client.http1

blackbull.client.http1

HTTP/1.1 client (RFC 7230).

Provides HTTP1Client plus the lower-level HTTP1RequestSender / HTTP1ResponseRecipient helpers that frame and unframe HTTP/1.1 messages on the wire.

Symmetric with the server-side HTTP1Sender / HTTP1Recipient in :mod:blackbull.server.sender and :mod:blackbull.server.recipient, but reversed: the client writes request lines + request headers + request body, and reads status lines + response headers + response body.

HTTP1Client

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 :meth:handoff; after that, the returned :class: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 :meth: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

Walk scenario.steps against the connected socket.

Never raises. Every outcome (response, timeout, transport failure, hard-abort) is folded into the returned :class:ScenarioResult so callers can categorise without try/except boilerplate per scenario.

Step dispatch
  • :class:SendBytes → :meth:send_raw
  • :class:Sleep → :func:asyncio.sleep
  • :class:ReadResponse → :meth:read_response
  • :class:Aborttransport.abort() (RST on Linux); walks no further steps.

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, timeout=None) async

Read one HTTP/1.1 response from the connection.

Optional timeout bounds the entire read (status line + headers + body). Raises :class:asyncio.TimeoutError if the deadline is hit; the caller decides whether to treat that as a transport- fail or a normal protocol outcome.

reset_wire_buffer()

Discard previously captured wire bytes.

send_body_bytes(data, *, byte_interval=0.0) async

Send body octets to the peer.

Same semantics as :meth:send_raw, kept separate for readability at call sites that frame headers separately from the body.

send_chunk(data) async

Send one Transfer-Encoding: chunked chunk.

Caller must have already emitted Transfer-Encoding: chunked via :meth:send_header_line and called :meth:end_headers. Finish the chunked stream with :meth: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.

When byte_interval > 0 the bytes are transmitted one at a time with byte_interval seconds between writes — the primitive slowloris-style stall the differential tests rely on. Each per- byte write is followed by drain() (inherited from :class:AsyncioWriter), so the bytes actually leave the socket on schedule rather than accumulating 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.

Adds Content-Length automatically for fixed-size byte bodies; switches to Transfer-Encoding: chunked for AsyncIterable bodies. 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.

send_prepared(prepared) async

Write a request returned by :meth:prepare.

HTTP1ResponseRecipient

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

Bidirectional transport after CONNECT or an HTTP 101 switch.

Obtain one with :meth: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.