Skip to content

Protocol Engines

The Engine demultiplexes requests by HTTP version and routes each request to the appropriate per-version engine. Each engine is a self-contained Akka.Streams sub-graph composed of a unified ConnectionStage (which handles encoding, decoding, and request/response correlation internally). The engine's output connects to a transport stage (TcpConnectionStage or QuicConnectionStage from Servus.Akka) that manages the actual network connection.


HTTP/1.0 Engine

HTTP/1.0 uses a close-then-respond model. Each connection handles exactly one request, then closes.

Internal composition:

HttpRequestMessage → Http10ConnectionStage → [TcpConnectionStage] → TCP
TCP → [TcpConnectionStage] → Http10ConnectionStage → HttpResponseMessage
ComponentRole
Http10ConnectionStageUnified stage: serialises request to wire bytes (sets Connection: close), parses the HTTP/1.0 response, and correlates request/response (FIFO, depth 1)
TcpConnectionStageTCP transport (from Servus.Akka) — acquires a connection lease from the manager actor, reads/writes bytes

Notable behaviours:

  • No keep-alive — every request opens and closes its own TCP connection
  • No chunked transfer encoding
  • Response body length determined by Content-Length header or connection close (EOF)

HTTP/1.1 Engine

HTTP/1.1 adds persistent connections and keep-alive control. The unified connection stage handles encoding, decoding, correlation, and keep-alive evaluation internally.

Internal composition:

HttpRequestMessage → Http11ConnectionStage → [TcpConnectionStage] → TCP
TCP → [TcpConnectionStage] → Http11ConnectionStage → HttpResponseMessage
ComponentRole
Http11ConnectionStageUnified stage: serialises request (adds Host, Connection, Transfer-Encoding: chunked as needed), parses HTTP/1.1 responses (handles chunked decoding), correlates request/response (FIFO, depth > 1 enables pipelining), and evaluates keep-alive signals
TcpConnectionStageTCP transport with connection reuse (from Servus.Akka) — returns leases to the pool on keep-alive, requests new connections on close

Keep-alive handling:

After decoding each response, Http11ConnectionStage evaluates the Connection header internally:

  • Connection: keep-alive (or HTTP/1.1 default) → the connection lease is returned to the connection manager actor for reuse
  • Connection: close → the lease is released without returning it to the idle queue; the next request triggers a new connection

Pipelining:

Http11ConnectionStage uses a FIFO queue internally to correlate requests with responses, enabling HTTP/1.1 pipelining: multiple requests can be in-flight on the same connection, and responses are matched to requests in order.


HTTP/2 Engine

HTTP/2 provides stream multiplexing — many logical requests share a single TCP connection, each assigned a unique stream ID.

Internal composition:

HttpRequestMessage → Http20ConnectionStage → [TcpConnectionStage] → TCP
TCP → [TcpConnectionStage] → Http20ConnectionStage → HttpResponseMessage
ComponentRole
Http20ConnectionStageCentral unified stage: allocates client stream IDs (1, 3, 5, …), HPACK-encodes request headers and emits HEADERS + DATA frames, handles frame encoding/decoding (9-byte frame header + payload), manages connection-level frames (SETTINGS, PING, WINDOW_UPDATE, GOAWAY), tracks connection and stream-level flow control windows, assembles per-stream HEADERS + DATA frames into HttpResponseMessage, and correlates responses by stream ID
TcpConnectionStageTCP transport (from Servus.Akka) — emits the HTTP/2 connection preface on first connect

HPACK header compression:

HpackEncoder and HpackDecoder maintain synchronised dynamic tables. Sensitive headers (Authorization, Cookie) are automatically marked NeverIndex. The dynamic table size is negotiated via SETTINGS_HEADER_TABLE_SIZE during the connection preface exchange.

Flow control:

Http20ConnectionStage tracks both connection-level and stream-level window sizes. It emits WINDOW_UPDATE frames when the consumer reads data, preventing the remote server from stalling. The Akka.Streams backpressure demand signal is translated into HTTP/2 flow-control credits.


HTTP/3 Engine

HTTP/3 runs over QUIC instead of TCP. Each request uses an independent QUIC stream, which eliminates the head-of-line blocking that affects HTTP/2 over a single TCP connection.

Internal composition:

HttpRequestMessage → Http30ConnectionStage → [QuicConnectionStage] → QUIC
QUIC → [QuicConnectionStage] → Http30ConnectionStage → HttpResponseMessage
ComponentRole
Http30ConnectionStageCentral unified stage: QPACK-encodes request headers, emits HEADERS + DATA frames over QUIC streams, handles frame encoding/decoding using QUIC variable-length encoding, manages connection-level frames (SETTINGS, GOAWAY), handles stream multiplexing and lifecycle, assembles per-stream frames into HttpResponseMessage, and QPACK-decodes response headers
QuicConnectionStageQUIC transport (from Servus.Akka) — acquires a QUIC connection from the manager actor, writes/reads bytes over QUIC streams

QPACK header compression:

QPACK is the HTTP/3 equivalent of HPACK, adapted for QUIC's out-of-order delivery. QpackEncoder and QpackDecoder maintain synchronised dynamic tables and communicate updates via dedicated encoder/decoder instruction streams.

No head-of-line blocking:

Unlike HTTP/2 where a single lost TCP packet can stall all streams, HTTP/3's QUIC transport delivers each stream independently. A lost packet on one stream does not affect other in-flight requests.

Released under the MIT License.