Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

http.body

import http.body

http lifts part of this module out to its own top level; each name below is shown with the path that reaches it. Anything still spelled http.body.* needs import 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:

bodyq11q5
1.5 KB JSON3.48 ms, 152 B0.054 ms, 152 B
15 KB JSON13.5 ms, 654 B0.19 ms, 591 B
150 KB JSON144 ms, 5119 B2.08 ms, 4831 B
120 KB HTML178 ms, 3322 B1.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 for 1xx, 204 and 304, 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: gzip has 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

FieldTypeDescription
modestringOne of 'empty', 'length', 'chunked' or 'eof'.
lengthnumberThe declared body length for a 'length' body, or -1 when the length is not known in advance.
trailersHeadersTrailer 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; nil for 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