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.
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.
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 terminatedThe 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.
| Method | Body semantics | Safe | Idempotent |
|---|---|---|---|
| GET | No defined body semantics | yes | yes |
| HEAD | Same as GET, but no response body | yes | yes |
| POST | Process the enclosed representation | no | no |
| PUT | Replace the target resource | no | yes |
| DELETE | Remove the target resource | no | yes |
| PATCH | Apply a partial modification | no | no |
| OPTIONS | Describe communication options | yes | yes |
| CONNECT | Establish a tunnel | no | no |
| Status class | Meaning | Examples |
|---|---|---|
| 1xx | Informational | 100 Continue, 101 Switching Protocols |
| 2xx | Success | 200 OK, 201 Created, 204 No Content |
| 3xx | Redirection | 301, 302, 304 Not Modified, 307, 308 |
| 4xx | Client error | 400, 401, 403, 404, 429 |
| 5xx | Server error | 500, 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.
| Framing | Body ends when | Used by | Risk |
|---|---|---|---|
| Content-Length | Exactly N bytes have been read | Requests and responses with a known size | Duplicate or conflicting lengths; integer parsing differences |
| chunked | The zero-length chunk and trailer section are read | Streaming responses, unknown length | Chunk-size parsing, extensions, trailer handling |
| close-delimited | The connection closes | HTTP/1.0 responses and some HTTP/1.1 responses | Truncation looks like a complete message |
| no body | Immediately after the header block | HEAD, 204, 304, 1xx | A parser that ignores the method/status desynchronises the next message |
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 · binaryHTTP/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.
| Type | Frame | Stream | Purpose |
|---|---|---|---|
0 | DATA | non-zero | Request or response body bytes, with optional padding. |
1 | HEADERS | non-zero | HPACK header block; may be continued by CONTINUATION. |
2 | PRIORITY | non-zero | Legacy dependency and weight hint. |
3 | RST_STREAM | non-zero | Abruptly terminate one stream with an error code. |
4 | SETTINGS | 0 | Connection parameters; ACK when the flags bit is set. |
5 | PUSH_PROMISE | non-zero | Promised request headers for a server push. |
6 | PING | 0 | Eight opaque bytes; used for liveness and RTT. |
7 | GOAWAY | 0 | Last accepted stream and the reason for shutdown. |
8 | WINDOW_UPDATE | 0 or non-zero | Increase the connection or stream flow-control window. |
9 | CONTINUATION | non-zero | Continues a header block that did not fit in HEADERS. |
| Frame | Flag bit | Name | Meaning |
|---|---|---|---|
| DATA | 0x1 | END_STREAM | This is the last frame from the sender on the stream. |
| DATA / HEADERS | 0x8 | PADDED | The payload starts with a pad-length byte. |
| HEADERS | 0x4 | END_HEADERS | No CONTINUATION follows; the HPACK block is complete. |
| HEADERS | 0x20 | PRIORITY | A 5-byte priority block precedes the header block. |
| SETTINGS / PING | 0x1 | ACK | Acknowledges a previous frame; the payload is normally empty. |
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 stringsHPACK 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.
| Pattern | Prefix | Meaning | Updates dynamic table |
|---|---|---|---|
1xxxxxxx | 7-bit index | Indexed header field | No |
01xxxxxx | 6-bit name index | Literal with incremental indexing | Yes |
0000xxxx | 4-bit name index | Literal without indexing | No |
0001xxxx | 4-bit name index | Literal never indexed (sensitive) | No |
001xxxxx | 5-bit size | Dynamic table size update | Resizes the table |
| Index | Name | Value | Why it matters |
|---|---|---|---|
1 | :authority | Equivalent to the HTTP/1.1 Host header. | |
2 | :method | GET | Encoded as a single 0x82 byte. |
3 | :method | POST | Single 0x83 byte. |
4 | :path | / | Single 0x84 byte. |
7 | :scheme | https | Single 0x87 byte. |
8 | :status | 200 | Single 0x88 byte. |
28 | content-length | Name index reused for a literal value. | |
31 | content-type | Name index reused for a literal value. | |
57 | transfer-encoding | Forbidden in HTTP/2; its presence is a protocol error. | |
58 | user-agent | Common literal field. |
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 §6no bytes built yet
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 9112Header 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
waitingno bytes built yet
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 754108Wire notes
what changes between HTTP/1.1, HTTP/2 and HTTP/3Methods, 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-EncodingandUpgrademust not appear. TE is allowed only with the valuetrailers. - 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
h2orhttp/1.1. The HTTP bytes only become visible after the TLS handshake and record decryption.