Skip to main content

HttpServer

HttpServer is the server-side counterpart to HttpClient. It parses an incoming HTTP request stream, removes HTTP framing, and forwards only request-body payload to the next Waterwall node. In the reverse direction, it wraps downstream payload as an HTTP response body.

It supports HTTP/1.1, direct HTTP/2, HTTP/1.1 to h2c upgrade, custom HTTP/1.1 upgrade tokens, HTTP/1.1 split mode, and WebSocket transport mode.

HttpServer does not terminate TLS. If traffic arrives as HTTPS, place TlsServer before HttpServer.

Typical Placement

TcpListener -> HttpServer -> SomeServiceTunnel
TcpListener -> TlsServer -> HttpServer -> SomeServiceTunnel

With a full client/server HTTP transport:

Client side:
TcpListener -> MuxClient -> HttpClient -> TlsClient -> TcpConnector

Server side:
TcpListener -> TlsServer -> HttpServer -> MuxServer -> TcpConnector

Basic Example

{
"name": "http-server",
"type": "HttpServer",
"settings": {
"http-version": "both",
"upgrade": true,
"host": "example.com",
"path": "/api/tunnel",
"method": "POST",
"status": 200,
"headers": {
"server": "waterwall"
}
},
"next": "service"
}

Required Settings

There are no tunnel-specific mandatory settings. The top-level settings object must exist and must not be empty.

Important defaults:

FieldDefault
http-versionboth
upgradetrue when http-version is both
path/
methodPOST
status200
websocketfalse
http1-modesingle

Optional Settings

FieldDefaultDescription
http-versionbothProtocol mode. Accepts numeric and string aliases.
upgradetrue only when http-version is bothAccepts HTTP/1.1 upgrade requests when allowed.
upgrade-protocolh2cHTTP/1.1 upgrade token. Non-h2c tokens switch to raw byte forwarding after 101.
upgrade-request-headersnoneRequired headers that must be present in the upgrade request.
upgrade-response-headersnoneExtra headers sent only on the 101 Switching Protocols response.
hostnoneOptional expected host. Mismatches are rejected.
path/Expected request path.
methodPOSTExpected request method for non-WebSocket HTTP transport.
status200Response status code. Current valid range is 100 through 599.
headersnoneExtra response headers. Values must be strings.
content-typenoneEmits a built-in Content-Type value when matched by Waterwall's internal table.
websocketfalseAccepts WebSocket opening handshake, then uses WebSocket frames for payload.
websocket-originnoneOptional required WebSocket Origin.
websocket-subprotocolnoneOptional required subprotocol. It is echoed in the response.
full-duplexfalsePrevents request-body end from immediately becoming an upstream Waterwall Finish.
http1-modesingleHTTP/1.1 shape: single or split. The old boolean http1-split is also accepted.
splitdefaults described belowSettings for HTTP/1.1 split pairing.
no-split-upload-buffering-limitfalseTest-oriented option that disables the normal upload buffering limit before the download half joins.
verbosefalseEnables more protocol, handshake, and framing logs.

content-type is selected from Waterwall's built-in content-type table. For a custom response value that is not in the table, set it through headers.

HTTP Version Modes

http-version accepts these values:

ValueMeaning
1, 1.1, http1, http1.1Force HTTP/1.1.
2, 2.0, http2, h2Force direct HTTP/2.
both, any, auto, 1.1+2Detect direct HTTP/2 or handle HTTP/1.1, including optional upgrade.

HTTP/1.1 Single Mode

In http1-mode: "single", one HTTP/1.1 request carries the upload side and one HTTP/1.1 response carries the download side:

  • request headers are consumed by HttpServer
  • request body bytes are forwarded to the next node as Waterwall payload
  • response payload from the next node is emitted as the HTTP response body
  • the response uses Transfer-Encoding: chunked
  • downstream Finish sends the final response chunk before the real Waterwall finish is forwarded

The request parser supports chunked bodies, Content-Length, and requests with no body. Chunked trailers are consumed internally.

When full-duplex is false, request-body completion is reflected as upstream Waterwall Finish. When full-duplex is true, the request-end is recorded internally and the service-facing line can remain open until the transport closes.

HTTP/1.1 Split Mode

Split mode pairs two HTTP/1.1 requests into one Waterwall line:

  • the upload request body is decoded and sent to the next node
  • the download request receives the response body generated from downstream Waterwall payload
  • both halves are matched by shared id, direction, and optional token metadata

This mode is designed to match HttpClient split mode on the other side.

{
"name": "http-server",
"type": "HttpServer",
"settings": {
"http-version": 1,
"http1-mode": "split",
"path": "/tunnel",
"split": {
"upload-method": "POST",
"download-method": "GET",
"id-placement": "query",
"id-name": "sid",
"direction-placement": "query",
"direction-name": "part",
"token": "shared-token",
"token-placement": "header",
"token-name": "X-Tunnel-Token"
},
"headers": {
"Cache-Control": "no-store"
}
},
"next": "service"
}

Common split fields:

FieldDefaultDescription
upload-methodtop-level methodMethod expected for the upload request.
download-methodGETMethod expected for the download request.
upload-pathtop-level pathPath expected for the upload request.
download-pathtop-level pathPath expected for the download request.
id-placementqueryWhere the shared id is read from.
id-namewwidShared id field name.
direction-placementqueryWhere the direction marker is read from.
direction-namewwdirDirection marker field name.
upload-valueuploadDirection value for the upload half.
download-valuedownloadDirection value for the download half.
tokennoneOptional shared token required from both halves.
token-placementheaderWhere the optional token is read from.
token-nameX-Waterwall-TokenToken field name.

Placement values can be query, header, cookie, or path. Path templates can contain {id}, {direction}, {cache}, and {token}.

warning

Split mode requires http-version = 1 and cannot be combined with websocket.

no-split-upload-buffering-limit disables the normal protection that closes a split upload side if it sends too much data before the matching download side has joined. It is mainly useful for controlled tests and unusual multi-worker timing cases, not normal deployments.

HTTP/2 Mode

In direct HTTP/2 mode, HttpServer accepts one logical HTTP/2 request stream for the Waterwall line:

  • request DATA frames become upstream Waterwall payload
  • downstream Waterwall payload becomes response DATA frames
  • request END_STREAM becomes upstream Finish unless full-duplex is enabled
  • downstream Finish sends response END_STREAM

Important implementation details:

  • MAX_CONCURRENT_STREAMS is set to 1
  • initial stream window is set to 1 MiB
  • maximum frame size is set to 32 KiB
  • extra HTTP/2 streams are refused with REFUSED_STREAM
  • WebSocket mode enables SETTINGS_ENABLE_CONNECT_PROTOCOL

This node is intentionally a single-stream HTTP transport, not a general HTTP/2 stream multiplexer.

Upgrade Behavior

When http-version is both and upgrade is enabled, HttpServer can accept HTTP/1.1 upgrade requests.

For default h2c, the request must include the expected upgrade metadata:

  • Connection containing Upgrade and HTTP2-Settings
  • Upgrade: h2c
  • HTTP2-Settings

After accepting the upgrade, HttpServer sends 101 Switching Protocols, applies the peer settings, and switches into HTTP/2 mode. The original upgraded request is stream 1; the current design waits for one post-upgrade HTTP/2 tunnel stream for payload.

If the upgrade request carries a body, the server treats that case conservatively instead of trying to combine the body with the upgraded HTTP/2 session.

If upgrade-protocol is set to a custom token other than h2c, a matching request receives 101 Switching Protocols and the tunnel becomes raw bidirectional byte forwarding after the handshake. upgrade-request-headers can require extra request headers, and upgrade-response-headers can add response headers for that 101.

WebSocket Mode

With websocket: true, HttpServer accepts a WebSocket handshake and then transports Waterwall payload as WebSocket frames.

HTTP/1.1 WebSocket requirements:

  • method must be GET
  • request must contain WebSocket upgrade headers
  • Sec-WebSocket-Key must be valid
  • Sec-WebSocket-Version must be 13
  • request body is not accepted
  • optional host, path, websocket-origin, and websocket-subprotocol checks must pass

HTTP/2 WebSocket requirements:

  • request must use extended CONNECT
  • :protocol must be websocket
  • :path and authority/host must match configured expectations when set
  • Sec-WebSocket-Version must be 13

After the handshake:

  • client-to-server frames must be masked
  • server-to-client frames are sent unmasked
  • downstream Waterwall payload is sent as WebSocket binary frames
  • text, binary, and continuation frames from the peer are forwarded as plain payload
  • ping and pong are handled internally
  • a close frame is sent before the real Waterwall finish

Finish And Lifecycle Behavior

HttpServer sends protocol-final bytes before forwarding some Waterwall Finish events:

  • HTTP/1.1 sends the final response chunk
  • HTTP/2 sends response END_STREAM
  • WebSocket sends a close frame

The implementation marks the relevant direction as finished before sending final bytes, then destroys local HTTP state before forwarding the real Waterwall Finish. This keeps final-byte backpressure from reflecting pause/resume callbacks toward a side that has already finished.

Split mode creates and manages the logical Waterwall line that joins the upload and download HTTP halves. The transport halves are closed without treating them as independent service connections.

Buffer Padding

HttpServer advertises 16 bytes of left padding for its framing paths. Keep this in mind when composing tunnels that also prepend protocol bytes.

Common Mistakes

  • Placing HttpServer directly after TcpListener when the incoming traffic is TLS encrypted. Use TlsServer first.
  • Expecting headers to pass through to the next node. The next node receives only body payload.
  • Combining HTTP/1.1 split mode with WebSocket or HTTP/2.
  • Expecting multiple independent HTTP/2 streams to become multiple Waterwall lines.
  • Using custom WebSocket extensions. The WebSocket path transports unextended frames.