Binary HTTP

Binary HTTP (BHTTP) is a binary format for representing a single HTTP request or response as a self-contained message. It encodes the method, target, fields, and content into a compact byte sequence that travels without a live HTTP connection, which is what makes it suitable for encryption and forwarding by Oblivious HTTP.

What Binary HTTP is

Binary HTTP is a serialization of one complete HTTP message into a byte sequence. A regular HTTP message lives on a connection, where framing, ordering, and length come from the transport. BHTTP removes that dependency. The whole message, control data, header fields, content, and trailers, becomes a single binary blob that any party can store, move, sign, or encrypt as one unit.

The format covers requests and responses equally. Each encoded message starts with a framing indicator that names what follows, so a decoder knows immediately whether the bytes describe a request or a response and how the rest is delimited.

Why it exists

Binary HTTP exists to carry an entire HTTP message outside of an HTTP connection. Text-based HTTP/1.1 messages depend on the connection for boundaries and timing, and the field syntax tolerates many edge cases that complicate exact, reversible encoding. BHTTP defines a strict, deterministic byte layout so an HTTP message can be treated as data.

That property is what Oblivious HTTP needs. OHTTP encrypts a request with the gateway's public key and routes the ciphertext through a relay that never sees the plaintext. The thing being encrypted is a Binary HTTP message. The client encodes the request in BHTTP, encrypts the bytes, and the gateway decrypts and decodes back to the original request. Authenticated encryption applies to the full message at once, which only works when the message is a single self-contained sequence of bytes.

Message structure

A Binary HTTP message is a fixed sequence of sections in order:

  1. Framing indicator
  2. Control data
  3. Header section
  4. Content
  5. Trailer section
  6. Padding

The framing indicator is a single integer that identifies the variant. Control data carries the request line or status code. The header section holds the field lines. Content holds the message body. The trailer section holds fields that follow the body. Padding is any number of zero-valued bytes appended at the end.

All lengths and numeric values use the variable-length integer encoding from QUIC. A single integer occupies 1, 2, 4, or 8 bytes, with the two high bits of the first byte signaling the length. This keeps small numbers compact while still allowing large values.

Trailing empty sections may be truncated. A decoder treats any missing section as if it had been sent with a length of zero, so a message with no content and no trailers can stop right after the header section.

Known-length and indeterminate-length encodings

Binary HTTP defines two encodings, and the framing indicator selects between them. Known-length encoding prefixes each variable section with its byte count. Indeterminate-length encoding omits the counts and marks the end of each section with a terminator, which suits content that is generated incrementally.

The framing indicator carries four defined values:

Value Meaning
0 Known-length request
1 Known-length response
2 Indeterminate-length request
3 Indeterminate-length response

Any other value makes the message invalid.

In the known-length form, the header section, content, and trailer section each begin with a variable-length integer giving the number of bytes that follow. A decoder reads the count, then reads exactly that many bytes. This form fits a message whose full size is known before encoding starts.

In the indeterminate-length form, content is written as a series of non-zero-length chunks, each prefixed with its own length, ending with a zero chunk length that acts as a content terminator. Header and trailer sections end with a content terminator as well. This form fits streaming producers that encode bytes as they arrive without knowing the total length in advance.

Control data

Control data is the section that carries the request line for a request or the status code for a response. The framing indicator already settled which kind of message this is, so the decoder knows how to read the control data that follows.

Request control data holds four length-prefixed byte strings in a fixed order: method, scheme, authority, and path. These map to the HTTP/2 pseudo-header fields :method, :scheme, :authority, and :path, and follow the same rules. A GET to https://example.re/ encodes the method GET, the scheme https, the authority example.re, and the path /, each preceded by its length.

Response control data holds a single variable-length integer status code. Final status codes range from 200 to 599. Informational responses in the 1xx range precede the final response, each carrying its own status code and header section, repeating until a final status code appears.

Header and trailer fields

The header section and trailer section both encode field lines as a sequence of name and value pairs. Each field line is a length-prefixed name followed by a length-prefixed value. Both the name and the value are byte strings preceded by their length, so the format never relies on a colon, comma, or line break to find a boundary.

Field names follow HTTP field rules. The pseudo-fields :method, :scheme, :authority, :path, and :status belong to control data and do not appear as regular field lines. Connection-specific fields are removed during encoding. Multiple cookie values combine using a semicolon rather than a comma, matching how cookies behave across HTTP versions.

The header section appears before the content. The trailer section appears after it and carries any fields that depend on the content, such as an integrity digest computed over the body.

Padding and validity

Binary HTTP allows padding with any number of zero-valued bytes after the trailer section. Padding lets an encoder produce messages of a uniform size, which hides the true length of an encrypted request or response from an observer watching ciphertext size. A non-zero padding byte makes the message invalid.

A decoder that finds an invalid message stops processing it. Examples of invalidity include an unknown framing indicator value, a field boundary that runs past the section length, or non-zero padding. The decoder logs the error and may produce an error response rather than guessing at the intended content.

The message/bhttp media type

The media type for Binary HTTP is message/bhttp. It identifies a payload that contains a single HTTP message in the binary representation, as a counterpart to the older message/http type used for text-based messages. Only the 8bit and binary content transfer encodings apply, since the format is binary by design.

Oblivious HTTP wraps a message/bhttp payload inside its own encrypted media types, message/ohttp-req and message/ohttp-res. The BHTTP message is the plaintext that OHTTP encrypts. Other systems that need to move a complete HTTP message as data, such as message signing or store-and-forward gateways, use the same message/bhttp type to label the encoded bytes.

Example

Binary HTTP is not human-readable, so the layout below shows the field structure of a known-length GET request rather than raw bytes. Each row is one element of the encoded message in order, with the variable-length integers shown as their decoded numeric values.

The request being encoded:

GET /index.html HTTP/1.1
Host: example.re
User-Agent: example-client

The same request in Binary HTTP, annotated section by section:

Framing indicator:   0            (known-length request)

Control data:
  Method length:     3
  Method:            "GET"
  Scheme length:     5
  Scheme:            "https"
  Authority length:  10
  Authority:         "example.re"
  Path length:       11
  Path:              "/index.html"

Header section:
  Section length:    33           (bytes in the field lines below)
  Field line 1:
    Name length:     10
    Name:            "user-agent"
    Value length:    14
    Value:           "example-client"

Content length:      0            (no body)

The framing indicator 0 marks a known-length request. Control data spells out the request target as four length-prefixed strings, which together carry the same information as the request line and the authority. The Host header is represented by the authority field in control data, so it does not repeat as a field line. The header section starts with a byte count, then lists each field as a length-prefixed name and value. Content length 0 ends the message, and the empty trailer section and padding are truncated.

See also

Last updated: June 5, 2026