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:
Abort→transport.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.