Skip to content

blackbull.server.parser

blackbull.server.parser

parse_headers(frame)

Build a native :class:Connection (http or websocket) from a HEADERS frame, or None when the request is malformed.

Hot path on every request — kept as a module-level function so that callers avoid the dict-lookup + parser allocation that ParserFactory requires.

Sprint 79 Phase 4: yields a :class:Connection rather than an ASGI scope dict (the H/2 analogue of :meth:HTTP1Actor._parse). The actor derives the compat scope via :meth:Connection.as_scope at the dispatch boundary until Phase 5 switches the consumers onto conn directly; the websocket-only subprotocols scope key is likewise attached there (it is not a :class:Connection field — see the proposal §2.1 field set), the same way :meth:HTTP1Actor._handle_upgrade augments the derived scope.

Sprint 80 alloc hygiene (proposals/connection-alloc-hygiene.md, 2026-07-24): builds the :class:Connection once, at the very end, from locals accumulated while walking the pseudo-headers — the same single-construction idiom :meth:HTTP1Actor._parse already uses (Phase 1). The previous version built a placeholder via a removed _default_connection helper (with a throwaway Headers([]), discarded ~154 ns/req) and mutated it field by field. Every early-out below now returns None instead of a half-built Connection — including the (previously reachable but never-observed) host-validation-failure path, since _request_headers_with_host already marks frame.malformed before returning None, and every caller checks frame.malformed before reading this function's result. The contract is now uniformly result is None ⟺ frame.malformed. The plain-HTTP branch's final construction goes through _build_h2_connection (Phase 2 — an object.__new__ lean builder), not the dataclass constructor; see that function's docstring.

Also performs request-level pseudo-header presence checks (RFC 9113 §8.3.1). Field-level checks already happened in parse_payload. On any known-bad input — parse_payload having flagged frame.malformed, or a missing/empty required pseudo found here (which sets frame.malformed) — we return None rather than build a throwaway :class:Connection: the actor's frame.malformed check answers RST_STREAM before it would read the result, so no object is constructed on the error path.