HTTP // RFC 9110 · RFC 9113

HTTP is two protocols with one name. HTTP/1.1 puts a text start line, header fields, a blank line and a body on a TCP stream, with Content-Length or chunked framing. HTTP/2 replaces that with binary frames, stream IDs and HPACK-compressed header blocks. This page decodes both and shows where the request-smuggling bugs live.

RFC 9110 · semantics RFC 9112 · HTTP/1.1 RFC 9113 · HTTP/2 RFC 7541 · HPACK text + binary chunked HPACK · Huffman
layer application HTTP/1.1 text framing HTTP/2 9 B frame header transport TCP/80 · TCP/443

01HTTP/1.1 message layout

start line · header fields · empty line · body

An HTTP/1.1 message is a start line, zero or more header fields, an empty line and an optional body. Lines end in CRLF; the empty line is exactly CRLF. The start line is a request line for requests and a status line for responses.

the body is framed, not self-describing

HTTP/1.1 does not put a length on the whole message. The receiver decides where the body ends from the request method, the status code, Content-Length, Transfer-Encoding: chunked, or connection close. Every framing disagreement between two parsers is a potential request-smuggling bug.

02Request line & status line

ASCII · space separated · CRLF terminated

The request line carries the method, target and protocol version. The status line carries the version, a three-digit status code and a reason phrase. The reason phrase is informative only; clients must use the code.

MethodBody semanticsSafeIdempotent
GETNo defined body semanticsyesyes
HEADSame as GET, but no response bodyyesyes
POSTProcess the enclosed representationnono
PUTReplace the target resourcenoyes
DELETERemove the target resourcenoyes
PATCHApply a partial modificationnono
OPTIONSDescribe communication optionsyesyes
CONNECTEstablish a tunnelnono
Status classMeaningExamples
1xxInformational100 Continue, 101 Switching Protocols
2xxSuccess200 OK, 201 Created, 204 No Content
3xxRedirection301, 302, 304 Not Modified, 307, 308
4xxClient error400, 401, 403, 404, 429
5xxServer error500, 502, 503, 504

03Body framing

Content-Length · chunked · close-delimited

Framing is the part of HTTP/1.1 that most often breaks in the real world. A response with both Content-Length and Transfer-Encoding: chunked is invalid enough that RFC 9112 says the message ought to be treated as an error; a proxy that prefers the other header is how request smuggling starts.

FramingBody ends whenUsed byRisk
Content-LengthExactly N bytes have been readRequests and responses with a known sizeDuplicate or conflicting lengths; integer parsing differences
chunkedThe zero-length chunk and trailer section are readStreaming responses, unknown lengthChunk-size parsing, extensions, trailer handling
close-delimitedThe connection closesHTTP/1.0 responses and some HTTP/1.1 responsesTruncation looks like a complete message
no bodyImmediately after the header blockHEAD, 204, 304, 1xxA parser that ignores the method/status desynchronises the next message
both is a framing error, not a precedence rule

A message that carries both Content-Length and Transfer-Encoding: chunked is ambiguous and should be rejected. RFC 9112's parsing algorithm says Transfer-Encoding overrides Content-Length, but it also says such a message ought to be handled as an error. A front-end that honours one header while a back-end honours the other is the classic request-smuggling setup: do not forward the message, and do not let two parsers choose different framings.

04HTTP/2 frames

9-byte header · multiplexed streams · binary

HTTP/2 keeps the request/response semantics and replaces the text framing with a binary frame layer. Every frame starts with a 24-bit length, an 8-bit type, an 8-bit flags field, a reserved bit and a 31-bit stream identifier. A connection carries many streams at once, and header blocks are compressed with HPACK.

TypeFrameStreamPurpose
0DATAnon-zeroRequest or response body bytes, with optional padding.
1HEADERSnon-zeroHPACK header block; may be continued by CONTINUATION.
2PRIORITYnon-zeroLegacy dependency and weight hint.
3RST_STREAMnon-zeroAbruptly terminate one stream with an error code.
4SETTINGS0Connection parameters; ACK when the flags bit is set.
5PUSH_PROMISEnon-zeroPromised request headers for a server push.
6PING0Eight opaque bytes; used for liveness and RTT.
7GOAWAY0Last accepted stream and the reason for shutdown.
8WINDOW_UPDATE0 or non-zeroIncrease the connection or stream flow-control window.
9CONTINUATIONnon-zeroContinues a header block that did not fit in HEADERS.
FrameFlag bitNameMeaning
DATA0x1END_STREAMThis is the last frame from the sender on the stream.
DATA / HEADERS0x8PADDEDThe payload starts with a pad-length byte.
HEADERS0x4END_HEADERSNo CONTINUATION follows; the HPACK block is complete.
HEADERS0x20PRIORITYA 5-byte priority block precedes the header block.
SETTINGS / PING0x1ACKAcknowledges a previous frame; the payload is normally empty.
stream 0 is the connection

SETTINGS, PING and GOAWAY use stream 0 and apply to the whole connection. Streams are initiated with odd IDs by the client and even IDs by the server. A frame that appears on the wrong stream, or a DATA frame on stream 0, is a connection error, not a warning.

05HPACK header compression

static table · dynamic table · Huffman strings

HPACK is a stateful compression format shared by every stream on an HTTP/2 connection. Header fields are either indexed into a 61-entry static table, indexed into a dynamic table that both peers update in lock-step, or sent as literals. Strings may be Huffman coded. A decoder that loses state cannot recover until the connection ends.

PatternPrefixMeaningUpdates dynamic table
1xxxxxxx7-bit indexIndexed header fieldNo
01xxxxxx6-bit name indexLiteral with incremental indexingYes
0000xxxx4-bit name indexLiteral without indexingNo
0001xxxx4-bit name indexLiteral never indexed (sensitive)No
001xxxxx5-bit sizeDynamic table size updateResizes the table
IndexNameValueWhy it matters
1:authorityEquivalent to the HTTP/1.1 Host header.
2:methodGETEncoded as a single 0x82 byte.
3:methodPOSTSingle 0x83 byte.
4:path/Single 0x84 byte.
7:schemehttpsSingle 0x87 byte.
8:status200Single 0x88 byte.
28content-lengthName index reused for a literal value.
31content-typeName index reused for a literal value.
57transfer-encodingForbidden in HTTP/2; its presence is a protocol error.
58user-agentCommon literal field.
Huffman is not encryption

HPACK Huffman coding is a fixed prefix code from RFC 7541. It makes header blocks smaller, not secret. The full 257-symbol table is implemented in this page's decoder; a block can mix Huffman and raw strings freely.

Encode a header block

This builds an HPACK block using static-table indexing where possible and literal fields otherwise. It is useful for seeing how few bytes a repeated pseudo-header costs, and for feeding a HEADERS payload into the decoder.

HPACK encoder

RFC 7541 §6
Press Encode HPACK to build a header block.
HPACK block hex
no bytes built yet
dynamic table size is a connection property

Each entry costs name length + value length + 32 bytes. The peer may announce a smaller SETTINGS_HEADER_TABLE_SIZE, and the encoder must emit a dynamic table size update before using the new size. Eviction is from the oldest entry. If you start decoding in the middle of a connection, indexed fields may refer to entries you never saw.

06HTTP/1.1 encoder

request · response · Content-Length · chunked

Build an HTTP/1.1 message with exact framing. The encoder adds Host for HTTP/1.1 requests when it is missing, replaces any supplied Content-Length/Transfer-Encoding with the framing implied by the body, and can split the body into chunks.

HTTP parameters

RFC 9112

Header names are case-insensitive on the wire, but this encoder preserves the capitalization you type. Content-Length and Transfer-Encoding are generated from the body and the chunked checkbox; do not add them by hand.

wire output

waiting
Set the fields and press Build HTTP.
HTTP bytes
no bytes built yet
message — headers — body —
this is HTTP/1.1 only

HTTP/2 is binary. Its headers are HPACK-compressed and its body is split across DATA frames, so a text encoder cannot produce it. Use the decoder below to inspect an HTTP/2 frame stream, or read the frame and HPACK sections above.

07Decoder

HTTP/1.1 · HTTP/2 frames · HPACK · full packets

Paste an HTTP/1.1 message, an HTTP/2 frame stream, a bare HPACK block, a TCP payload or a complete packet. HTTP/1.1 framing is resolved from the method, status, Content-Length and Transfer-Encoding; HTTP/2 frames are walked in order and header blocks are decoded with a connection-level HPACK context. A single response does not identify the request method, so a HEAD response cannot be distinguished from an ordinary 200 response with an empty body unless you supply the request context.

parse bytes

RFC 9112 · RFC 9113 · RFC 7541
No HTTP message decoded yet.

08Wire notes

what changes between HTTP/1.1, HTTP/2 and HTTP/3
HTTP semantics are stable; framing is not

Methods, status codes and header field semantics come from RFC 9110 and apply to HTTP/1.1, HTTP/2 and HTTP/3. The start line, header encoding, multiplexing and flow control are version-specific. A tool that only understands text HTTP will misread every HTTP/2 connection.

  • HTTP/2 headers must be lowercase. Field names containing uppercase letters are malformed. Pseudo-headers (:method, :path, :scheme, :authority, :status) come before regular fields.
  • Connection-specific headers are forbidden in HTTP/2. Connection, Keep-Alive, Proxy-Connection, Transfer-Encoding and Upgrade must not appear. TE is allowed only with the value trailers.
  • HTTP/2 flow control is per stream and per connection. DATA frames consume window; WINDOW_UPDATE restores it. A zero increment is a protocol error.
  • HPACK state is per connection, not per stream. HEADERS and CONTINUATION on different streams share one dynamic table. Starting a decode mid-connection can make indexed fields unresolvable.
  • Request smuggling lives in framing disagreement. Duplicate Content-Length, both Content-Length and chunked, whitespace before a colon, and obs-fold are all parser-differential bugs. Reject ambiguous messages.
  • Trailers are real headers. In chunked HTTP/1.1 they follow the zero chunk; in HTTP/2 they are a HEADERS frame after DATA with END_STREAM. Do not drop them silently.
  • HTTP/3 is HTTP over QUIC. Requests and responses run on QUIC streams, and header compression is QPACK rather than HPACK. There is no TCP head-of-line blocking, but stream-level flow control and QUIC loss recovery replace it.
  • HTTPS is HTTP inside TLS. ALPN selects h2 or http/1.1. The HTTP bytes only become visible after the TLS handshake and record decryption.

References