Open-Source Wikis

/

nginx

/

Systems

/

HTTP/2

nginx/nginx

HTTP/2

Active contributors: Sergey Kandaurov, Maxim Dounin, Roman Arutyunyan

Purpose

HTTP/2 (RFC 7540 / 9113) on top of nginx's HTTP request engine. Implements the binary framing layer, stream multiplexing, flow control, and HPACK header compression. Stream contents are converted into the same ngx_http_request_t objects that HTTP/1.1 produces, so the rest of the request engine doesn't need to know the protocol version.

The implementation landed in mainline on 2015-09-11 (commit message: "The HTTP/2 implementation (RFC 7240, 7241)") and replaced an earlier never-merged SPDY effort.

Directory layout

src/http/v2/
├── ngx_http_v2.{c,h}             # frame parser + connection state machine
├── ngx_http_v2_filter_module.c   # output filter that produces frames
├── ngx_http_v2_module.{c,h}      # the directives (http2_*, etc.)
├── ngx_http_v2_table.c           # the dynamic HPACK table (RFC 7541)
└── ngx_http_v2_encode.c          # HPACK encode helpers

ngx_http_v2.c is the largest single file in src/http/v2/ at ~4,700 lines.

Key abstractions

Type Role
ngx_http_v2_connection_t Per-connection state: streams rbtree, settings, flow control window
ngx_http_v2_stream_t Per-stream state: window, queues, link to ngx_http_request_t
ngx_http_v2_frame_t A frame on the output queue
ngx_http_v2_state_t (function ptr) Current parser state — frame header, payload, settings, headers
ngx_http_v2_send_frame() Append a frame to the output queue and arm the write
ngx_http_v2_huff_decode_state_t HPACK static-Huffman decoder state

Connection state machine

stateDiagram-v2
    [*] --> Preface
    Preface --> ReadFrameHeader: 24-byte preface received
    ReadFrameHeader --> ReadFramePayload
    ReadFramePayload --> Dispatch
    Dispatch --> ReadFrameHeader
    Dispatch --> Settings
    Dispatch --> Headers
    Dispatch --> Continuation
    Dispatch --> Data
    Dispatch --> Ping
    Dispatch --> WindowUpdate
    Dispatch --> RstStream
    Dispatch --> Goaway
    Dispatch --> Priority
    Dispatch --> PushPromise
    Goaway --> Drain
    Drain --> [*]

The parser is implemented as a state-function pointer (h2c->state.handler) rather than a switch. Each function reads what it can from c->buffer and returns:

  • the next state function (advance)
  • the same state function (stay; need more bytes)

This avoids re-parsing on partial data — recv() boundaries don't matter.

Frame types handled

Frame Action
DATA Append to the stream's request body
HEADERS Decode HPACK, build the request, dispatch to phase engine
PRIORITY Update stream priority (limited use; nginx doesn't reorder)
RST_STREAM Cancel the stream; finalize the request with 499/error
SETTINGS Update connection settings (table size, max_concurrent, etc)
PUSH_PROMISE Accepted only outbound; rejected from clients per spec
PING Echo back
GOAWAY Begin shutdown; reject new streams; let existing finish
WINDOW_UPDATE Update flow-control windows
CONTINUATION Continue a HEADERS / PUSH_PROMISE block

HPACK

src/http/v2/ngx_http_v2_table.c implements RFC 7541's dynamic table. The static Huffman tables for the Huffman-coded header values are in src/http/ngx_http_huff_decode.c (~4,400 lines, mostly auto-generated lookup tables).

The decoder is byte-oriented and table-driven; each input byte indexes into a lookup that gives next state and emitted symbols. No inflate/deflate, no zlib involvement.

Stream → request bridge

When HEADERS (and any CONTINUATIONs) complete, the parser:

  1. Decodes the HPACK block, accumulating cookies, building the parsed header list.
  2. Allocates an ngx_http_request_t rooted in a new request pool.
  3. Fills r->method, r->method_name, r->uri, r->args, r->http_version = NGX_HTTP_VERSION_20, etc.
  4. Calls ngx_http_v2_run_request() which posts the request to the engine.

From here it's the same request engine as HTTP/1: 11 phases, content handler, output filters. The only differences:

  • Body bytes come from DATA frames, not from the socket directly.
  • Output goes through ngx_http_v2_filter_module (replaces chunked + write).
  • Concurrency: many requests run on one connection.

Output

ngx_http_v2_filter_module sits in the body filter chain and produces DATA frames. Each filter pass either:

  • Builds and queues frames for the response (header block, body chunks)
  • Returns NGX_AGAIN if flow-control windows are exhausted

Frames are sent in priority order from a per-connection queue, respecting both the connection-level window and each stream's window. A separate write event handler (ngx_http_v2_send_output_queue) drains the queue.

Flow control

Two-tier:

  • Connection-level window (h2c->send_window) — global cap on bytes in flight to the peer
  • Stream-level window (stream->send_window) — per-stream cap

The framework decrements both on each DATA send and increments when a WINDOW_UPDATE arrives. If a stream has bytes ready but no window, its frame stays queued.

The receive side mirrors this: nginx sends WINDOW_UPDATE based on http2_recv_buffer_size-derived thresholds.

Settings

Each side announces:

  • SETTINGS_HEADER_TABLE_SIZE
  • SETTINGS_MAX_CONCURRENT_STREAMS
  • SETTINGS_INITIAL_WINDOW_SIZE
  • SETTINGS_MAX_FRAME_SIZE
  • SETTINGS_MAX_HEADER_LIST_SIZE
  • SETTINGS_ENABLE_PUSH (always 0 in modern nginx — push has been removed by browsers and is a no-op here)

Server-side defaults are set by ngx_http_v2_module directives (http2_max_concurrent_streams, http2_recv_buffer_size, etc.).

ALPN

The TLS handshake negotiates HTTP/2 via ALPN (h2). When a TLS connection negotiates h2, ngx_http_ssl_module calls ngx_http_v2_init instead of the HTTP/1 init path. Plaintext H2 (h2c) is supported via the listen ... http2; directive but rare in practice.

Integration points

  • Event loop / OpenSSL — H2 reads from c->ssl like any other connection; the framing parser is just an event handler.
  • HTTP request engine — bridge in ngx_http_v2_run_request. Phases, filters, finalization are unchanged.
  • HPACK — connects to the existing static Huffman tables in src/http/ngx_http_huff_*.
  • Variables$http2, $http_* (request headers via H2 trailers handled), $ssl_* for the underlying TLS.

Entry points for modification

H2 spec compliance fixes typically land in the per-frame state functions. Performance work in the recent past has focused on the encoder's frame coalescing (reducing per-stream bookkeeping). Don't change the HPACK Huffman tables — they're spec-mandated.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

HTTP/2 – nginx wiki | Factory