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 (HTTP/1.1 persistent connections, RFC 7230 §6.3) until __aexit__ closes it. 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.

read_response(*, 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.

HTTP1ResponseRecipient

Reads an HTTP/1.1 response from an AbstractReader.

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