http.body
import http.body
httplifts part of this module out to its own top level; each name below is shown with the path that reaches it. Anything still spelledhttp.body.*needsimport http.body.
A request or response body, in whatever shape the wire delivered it: a
fixed Content-Length, a chunked stream, or nothing at all.
BodyReader hides that difference behind one interface, and
decode_content()/encode_content() handle the Content-Encoding
layer on top of it: gzip, deflate, brotli and zstd.
Constants
DEFAULT_BROTLI_QUALITY
http.body.DEFAULT_BROTLI_QUALITY: int = 5
The brotli quality this module compresses a response with when the caller names no quality of its own.
Brotli’s own default is 11, and that is a setting for compressing a file once at build time rather than for compressing a response per request. Measured against q5 on real response bodies, q11 costs 64-104x the CPU and, because of how brotli sizes its window at high quality, frequently produces a larger result:
| body | q11 | q5 |
|---|---|---|
| 1.5 KB JSON | 3.48 ms, 152 B | 0.054 ms, 152 B |
| 15 KB JSON | 13.5 ms, 654 B | 0.19 ms, 591 B |
| 150 KB JSON | 144 ms, 5119 B | 2.08 ms, 4831 B |
| 120 KB HTML | 178 ms, 3322 B | 1.88 ms, 3350 B |
A 178 ms compression on a single page is the entire time budget for a request, spent to make the response 0.8% smaller.
Functions
parse_content_length()
http.body.parse_content_length(value) -> number
Parses a Content-Length field value.
RFC 9112 §6.3 is strict here for good reason: a value that isn’t a plain run of digits, or a field carrying two different lengths, is the raw material of request smuggling, where a proxy and an origin read the same bytes as different numbers of messages.
Parameters
value(string)
Returns number
Raises ProtocolError if the value is not a bare decimal number
parse_chunk_size()
http.body.parse_chunk_size(line) -> number
Parses a chunk-size line, ignoring any chunk extensions after the ;.
Parameters
line(string)
Returns number
Raises ProtocolError on anything that isn’t hexadecimal
reader_for()
http.body.reader_for(connection, headers, is_request: bool, bodiless: ?bool) -> BodyReader
Works out how a message’s body is framed and returns a reader for it.
is_request matters because the two directions disagree about what “no
framing headers” means: a request without them has no body, while a
response without them runs until the connection closes.
Parameters
connection(Connection)headers(Headers)is_request(bool)bodiless(?bool) — force an empty body regardless of the headers - true for a response to HEAD, and for1xx,204and304, all of which carry framing headers that describe a body they do not actually send
Returns BodyReader
Raises ProtocolError if the framing headers contradict each other
encode_chunk()
http.body.encode_chunk(data) -> bytes
Wraps data as a single HTTP/1.1 chunk: the size in hexadecimal, a
CRLF, the data, and a CRLF.
Parameters
data(bytes)
Returns bytes
encode_last_chunk()
http.body.encode_last_chunk(trailers) -> bytes
The terminating 0\r\n chunk plus a trailer section.
Parameters
trailers(?Headers)
Returns bytes
decode_content()
http.body.decode_content(data, encoding) -> bytes
Reverses the content codings named in a Content-Encoding field,
innermost last, as RFC 9110 §8.4 requires.
identity is accepted and does nothing. Anything else raises, because
silently handing back still-encoded bytes would look like corrupt data
at the call site.
Parameters
data(bytes)encoding(string) — the raw field value, e.g.'gzip'
Returns bytes
Raises ProtocolError on an unknown coding or undecodable data
encode_content()
http.body.encode_content(data, coding: string, quality: ?number) -> bytes
Applies a content coding to data.
quality is interpreted on the scale of whichever coding is being
applied - brotli 0-11, deflate 0-9, zstd 1-22 - and is
clamped into range rather than rejected. Omit it to get the coding’s own
sensible default for a response compressed per request: 5 for brotli
(see DEFAULT_BROTLI_QUALITY), and each of the others’ native default,
which is already the right shape for dynamic content.
Parameters
data(bytes)coding(string) —'gzip','deflate','br','zstd'or'identity'quality(?number)
Returns bytes
Raises ProtocolError on an unknown coding
Note:
gziphas no quality parameter at this layer. It compresses at zlib’s own default level of 6, which is what nginx and every CDN use for dynamic responses anyway.
Classes
BodyReader
class http.BodyReader
Reads a message body off a connection according to whichever of HTTP/1.1’s framings applies.
The framing is decided once, by for_message(), and never re-derived:
Transfer-Encoding: chunked wins over Content-Length, a fixed length
is next, and a response with neither is delimited by the connection
closing. A request with neither has no body at all - a server may not
wait for a close that a client has no reason to perform.
Read it incrementally with read(), or in one go with read_all().
Either way, call drain() before reusing the connection, or the next
message will start parsing in the middle of this one’s body.
Fields
| Field | Type | Description |
|---|---|---|
mode | string | One of 'empty', 'length', 'chunked' or 'eof'. |
length | number | The declared body length for a 'length' body, or -1 when the length is not known in advance. |
trailers | Headers | Trailer fields, populated once a chunked body has been read to completion. |
Constructor
http.BodyReader(connection, mode: string, length)
Parameters
connection(Connection)mode(string)length(?number)
BodyReader.is_finished()
http.BodyReader.is_finished() -> bool
Whether the body has been read to its end.
Returns bool
BodyReader.read()
http.BodyReader.read(length)
Reads up to length more bytes of the body, or all of what remains when
length is omitted and the body is short.
Parameters
length(?number) — defaults to 64 KiB
Returns — bytes: empty once the body is exhausted
Raises ProtocolError on a malformed chunked body or a truncated
fixed-length one
BodyReader.read_all()
http.BodyReader.read_all(limit) -> bytes
Reads the whole body into memory.
Parameters
limit(?number) — the most to accept, in bytes;nilfor no limit
Returns bytes
Raises TooLargeError if the body exceeds limit
BodyReader.drain()
http.BodyReader.drain(limit)
Reads and throws away whatever is left, so the connection is positioned at the start of the next message and can be reused.
Returns false when the leftover exceeds limit - at which point
draining costs more than a new connection would, and the caller should
close instead.
Parameters
limit(?number) — the most worth draining; defaults to 1 MiB
Returns — bool: whether the connection is now reusable
2026, Richard Ore and Zuri contributors