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:
| Field | Default |
|---|---|
http-version | both |
upgrade | true when http-version is both |
path | / |
method | POST |
status | 200 |
websocket | false |
http1-mode | single |
Optional Settings
| Field | Default | Description |
|---|---|---|
http-version | both | Protocol mode. Accepts numeric and string aliases. |
upgrade | true only when http-version is both | Accepts HTTP/1.1 upgrade requests when allowed. |
upgrade-protocol | h2c | HTTP/1.1 upgrade token. Non-h2c tokens switch to raw byte forwarding after 101. |
upgrade-request-headers | none | Required headers that must be present in the upgrade request. |
upgrade-response-headers | none | Extra headers sent only on the 101 Switching Protocols response. |
host | none | Optional expected host. Mismatches are rejected. |
path | / | Expected request path. |
method | POST | Expected request method for non-WebSocket HTTP transport. |
status | 200 | Response status code. Current valid range is 100 through 599. |
headers | none | Extra response headers. Values must be strings. |
content-type | none | Emits a built-in Content-Type value when matched by Waterwall's internal table. |
websocket | false | Accepts WebSocket opening handshake, then uses WebSocket frames for payload. |
websocket-origin | none | Optional required WebSocket Origin. |
websocket-subprotocol | none | Optional required subprotocol. It is echoed in the response. |
full-duplex | false | Prevents request-body end from immediately becoming an upstream Waterwall Finish. |
http1-mode | single | HTTP/1.1 shape: single or split. The old boolean http1-split is also accepted. |
split | defaults described below | Settings for HTTP/1.1 split pairing. |
no-split-upload-buffering-limit | false | Test-oriented option that disables the normal upload buffering limit before the download half joins. |
verbose | false | Enables 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:
| Value | Meaning |
|---|---|
1, 1.1, http1, http1.1 | Force HTTP/1.1. |
2, 2.0, http2, h2 | Force direct HTTP/2. |
both, any, auto, 1.1+2 | Detect 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
Finishsends 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:
| Field | Default | Description |
|---|---|---|
upload-method | top-level method | Method expected for the upload request. |
download-method | GET | Method expected for the download request. |
upload-path | top-level path | Path expected for the upload request. |
download-path | top-level path | Path expected for the download request. |
id-placement | query | Where the shared id is read from. |
id-name | wwid | Shared id field name. |
direction-placement | query | Where the direction marker is read from. |
direction-name | wwdir | Direction marker field name. |
upload-value | upload | Direction value for the upload half. |
download-value | download | Direction value for the download half. |
token | none | Optional shared token required from both halves. |
token-placement | header | Where the optional token is read from. |
token-name | X-Waterwall-Token | Token field name. |
Placement values can be query, header, cookie, or path. Path templates can contain {id}, {direction}, {cache}, and {token}.
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_STREAMbecomes upstreamFinishunlessfull-duplexis enabled - downstream
Finishsends responseEND_STREAM
Important implementation details:
MAX_CONCURRENT_STREAMSis set to1- 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:
ConnectioncontainingUpgradeandHTTP2-SettingsUpgrade: h2cHTTP2-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-Keymust be validSec-WebSocket-Versionmust be13- request body is not accepted
- optional
host,path,websocket-origin, andwebsocket-subprotocolchecks must pass
HTTP/2 WebSocket requirements:
- request must use extended
CONNECT :protocolmust bewebsocket:pathand authority/host must match configured expectations when setSec-WebSocket-Versionmust be13
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
HttpServerdirectly afterTcpListenerwhen the incoming traffic is TLS encrypted. UseTlsServerfirst. - 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.