blackbull.native¶
blackbull.native
¶
Native response message for the H1 send path (native-ization, Sprint 92).
The unified response message BlackBull's own server carries on the native
path. One class replaces the ASGI start/body/trailers dicts: a response
object may carry any combination of header (status line + headers),
body (chunks), and trailers, so a complete response is one object
and one send, while streaming is header-object then body-chunk objects.
Design invariants (validated in scratch/send-model-c.py):
- Presence is
is not None, never truthiness — an empty body is a real body (204-style). Middleware must preserveNonewhen transforming (header or []turnsNoneinto[]and the sender mis-detects a header — a real bug caught in the scratch model). - DX via properties, not the wire shape —
resp.headeris a zero-copy view withget/append/getlist/__contains__/__len__/__iter__(BlackBullHeaders-like);bodyis a plainbyteswithcontent_length/is_empty/content_typehelpers. - Server hot path may read the raw slots (
_header/_body) to skip the property+view overhead; the properties exist for middleware and handler-facing DX. to_asgi()is the boundary conversion — 1 object → ASGI event list, used only at conversion boundaries: the external ASGI edge (asgi=True/ external hosts) and the middleware native-read arms (symmetric with :meth:Connection.as_scope).
NativeResponse
¶
A response on the native send path: header and/or body and/or trailers.
header is None when absent (never [] — presence is decided by
is not None). body is None when absent; b'' is a real
empty body. more_body marks a non-terminal body chunk (streaming).
expects_trailers preserves the ASGI http.response.start
trailers: True flag so the sender withholds the terminal chunk until
the trailers event (lossless full-form compat — a terminal body before
trailers would otherwise corrupt chunked framing).
complete(status, header, body)
classmethod
¶
Header plus terminal body — a whole response in one object.
to_asgi()
¶
Convert to the ASGI event list (http.response.* dicts).
One object → one or more ASGI events, in wire order. Used only at
conversion boundaries — the external ASGI edge (external hosts /
asgi=True) and the middleware native-read arms (cache,
compression); the native H1 sender path never materialises these
dicts.
with_trailers(status, header, body, trailers)
classmethod
¶
Header, body and trailers together — the gRPC unary shape.
more_body is True by construction, and load-bearing:
HTTP2Sender only takes its trailers-coalescing path for a
non-terminal body chunk, holding HEADERS + DATA so they flush with
the trailing HEADERS in one write. END_STREAM rides the trailers
either way (RFC 9113 §8.1).
NativeWSMessage
¶
One message on the native WebSocket send channel.
The WS counterpart of :class:NativeResponse, and it exists for the same
reason. HTTP got a native send message in Sprint 92/93 and the sender a
native arm; WebSocket was carried along as "conn is native, no scope" while
its event channel stayed ASGI-shaped — so websocket.* dicts still
travelled object → middleware → actor → sender on BlackBull's own path.
The handler never saw them (that is the :class:~blackbull.websocket.WebSocket
object's whole point), but everything under it did.
Three kinds, discriminated by :attr:kind rather than by which of seven
fields happens to be set — the variants carry disjoint payloads, so a tag
reads better here than the presence test that suits NativeResponse's
combinable arms:
ACCEPT—subprotocol/headers; completes the handshake.SEND— exactly one oftext(str) ordata(bytes).CLOSE—code/reason.
data rather than bytes: the ASGI key is bytes, but a slot of
that name shadows the builtin at every use site inside the class.
:meth:to_asgi maps it back for the boundary.
to_asgi()
¶
Convert to the ASGI websocket.* event list.
Used only at conversion boundaries — the external ASGI edge and the
raw (conn, receive, send) compat surface — exactly like
:meth:NativeResponse.to_asgi on the HTTP side.