پرش به مطلب اصلی

HttpProxyClient Node

HttpProxyClient connects through an upstream HTTP/1.1 proxy. It is a Layer 4 middle node with two modes:

  • connect (default): opens a CONNECT tunnel to the target, then carries an opaque TCP stream in both directions.
  • http: generates one configured HTTP request, uses upstream application bytes as its request body, and returns only the successful response body.

The following transport node connects to the proxy endpoint. HttpProxyClient's target-address and port select the origin destination reached through that proxy. These are separate addresses.

HTTP mode does not rewrite complete caller-supplied HTTP requests: the preceding node supplies body bytes only. Use CONNECT when the application already speaks HTTP, TLS, or another TCP protocol and should retain its own framing. HttpProxyServer provides the incoming forward-proxy interface; HttpClient provides a separate HTTP transport with additional framing modes.

Typical Placement​

TcpListener -> HttpProxyClient -> TcpConnector
TcpListener -> HttpProxyClient -> TlsClient -> TcpConnector

The second chain protects the connection to a TLS-enabled proxy. Configure TlsClient for that proxy's certificate name and HTTP/1.1, for example with alpns: ["http/1.1"]. TLS is not inserted automatically.

HTTP mode always requests an absolute http:// URL. Adding TLS after HttpProxyClient protects the proxy connection; it does not change the origin request to HTTPS. For HTTPS to the origin, use CONNECT and let the preceding application or TLS node supply the origin TLS session inside the tunnel.

CONNECT Example​

This node configuration forwards connections on local port 8443 through the proxy at 127.0.0.1:8080 to origin.example:443:

{
"name": "http-proxy-connect",
"nodes": [
{
"name": "listen",
"type": "TcpListener",
"next": "proxy",
"settings": { "address": "127.0.0.1", "port": 8443 }
},
{
"name": "proxy",
"type": "HttpProxyClient",
"next": "proxy-transport",
"settings": {
"mode": "connect",
"target-address": "origin.example",
"port": 443
}
},
{
"name": "proxy-transport",
"type": "TcpConnector",
"settings": { "address": "127.0.0.1", "port": 8080 }
}
]
}

HttpProxyClient sends CONNECT origin.example:443 HTTP/1.1, consumes the proxy's response header, and releases application bytes after a final 2xx response. The application must supply TLS itself when the origin expects TLS on port 443.

If an earlier node sets the destination context, use that destination with these client settings. This example also enables proxy authentication:

{
"name": "proxy",
"type": "HttpProxyClient",
"next": "proxy-transport",
"settings": {
"target-address": "dest_context->address",
"port": "dest_context->port",
"username": "alice",
"password": "replace-me"
}
}

The following connector must still address the proxy. Dynamic target values are captured before that connector can replace the line's destination context.

One-Request HTTP Examples​

To download a body, replace the proxy node in the CONNECT example with:

{
"name": "proxy",
"type": "HttpProxyClient",
"next": "proxy-transport",
"settings": {
"mode": "http",
"target-address": "origin.example",
"port": 80,
"method": "GET",
"path": "/download",
"body-mode": "none"
}
}

Each local connection starts GET http://origin.example:80/download HTTP/1.1. The local application reads the response body without sending its own request. Sending nonempty upstream data in this example closes the line.

For an upload whose size is known, use:

{
"name": "proxy",
"type": "HttpProxyClient",
"next": "proxy-transport",
"settings": {
"mode": "http",
"target-address": "origin.example",
"port": 80,
"method": "POST",
"path": "/upload?source=waterwall",
"body-mode": "fixed",
"content-length": 4096,
"headers": { "Content-Type": "application/octet-stream" }
}
}

The local application sends exactly 4096 body bytes, possibly across many writes, then keeps the connection open to read the response. It must not close or half-close its TCP connection to signal upload completion: WaterWall's TCP source EOF closes the line. The configured byte count already marks the request end.

Settings​

The node requires name, type: "HttpProxyClient", next, and a settings object. It belongs between Layer 4 nodes. Setting names and enumerated values are case-sensitive. Unknown or duplicate settings, wrong types, and conflicting mode options fail startup.

Target and Proxy Authentication​

SettingDefaultMeaning
mode"connect""connect" or "http".
target-addressRequiredOrigin IPv4, IPv6, domain, or exactly "dest_context->address"; 1–255 bytes.
domain-strategy"do-not-resolve-domains"Origin DNS selection; resolving values require CONNECT.
portRequiredOrigin integer port 1–65535, or exactly "dest_context->port".
username, passwordAbsentOptional pair for preemptive Basic proxy authentication. Both must be supplied together.
headers{}Additional string-valued request headers, in either mode.
verbosefalseBoolean; extra diagnostics without credentials or complete headers.

target-address contains a host or IP, not a URL; the port is separate. IPv6 authorities are bracketed on the wire, and every authority includes its port. Domain targets use proxy-side DNS by default. Optional domain-strategy enables local origin DNS in CONNECT mode only; HTTP mode preserves the original absolute URI authority and accepts only omission or do-not-resolve-domains.

domain-strategyCONNECT behavior
do-not-resolve-domainsDefault; send the domain to the proxy.
resolve-domains-and-accept-dns-returned-orderFirst usable DNS answer.
resolve-domains-and-prefer-ipv4Prefer IPv4, falling back to returned IPv6.
resolve-domains-and-prefer-ipv6Prefer IPv6, falling back to returned IPv4.
resolve-domains-and-use-only-ipv4IPv4 answer required.
resolve-domains-and-use-only-ipv6IPv6 answer required.
resolve-domains-with-core-settingsUse the core DNS selection strategy.

These exact lowercase strings are required. Unknown, empty, null, non-string, and duplicate values fail startup. Resolving strategies fail startup in HTTP mode even for literal targets. Literal IPv4/IPv6 and already-resolved dynamic IPs bypass DNS, including opposite-family literals: these policies select DNS answers, not explicit IPs. Preference fallback does not retry proxy requests.

Local DNS runs before the client core Init and following transport Init. The selected numeric origin and port appear in both CONNECT authority and Host; IPv6 is bracketed. Configured and dynamic address/port values can be mixed. The following connector independently selects and resolves the proxy with its own policy. Missing dynamic target values or explicitly UDP, ICMP, or packet destination protocols close the affected line; an unspecified protocol becomes TCP.

For example, these settings resolve the origin locally:

{
"mode": "connect",
"target-address": "origin.example",
"port": 443,
"domain-strategy": "resolve-domains-and-prefer-ipv4"
}

If DNS selects 192.0.2.20, the request is CONNECT 192.0.2.20:443 HTTP/1.1 with Host: 192.0.2.20:443. The original hostname does not override Host. TLS inside CONNECT is the preceding application's/node's responsibility: retain the origin hostname for SNI and certificate verification. TLS after HttpProxyClient authenticates the proxy.

Credentials each contain 1–255 bytes, without control bytes or DEL; the username also cannot contain :. The client sends Proxy-Authorization: Basic ... in its first request and does not retry after an authentication challenge. These credentials do not become origin Authorization or a WaterWall line user identity. Basic encoding does not encrypt credentials; TLS to the proxy can protect them in transit.

Additional header names must be valid HTTP tokens and unique ignoring ASCII case. Values cannot contain CR/LF or other controls except horizontal tab. The node controls Host, Proxy-Authorization, Content-Length, Transfer-Encoding, and Connection. Custom headers also cannot set Proxy-Connection, Keep-Alive, TE, Trailer, Upgrade, or Expect. Origin Authorization and Content-Type are allowed.

HTTP Request Options​

These settings are accepted only with mode: "http":

SettingDefaultMeaning
method"POST"GET, HEAD, POST, PUT, PATCH, DELETE, or OPTIONS.
path"/"Escaped origin path and optional query, starting with /.
body-mode"chunked""none", "fixed", or "chunked"; HEAD requires "none".
content-lengthAbsentRequired only for "fixed"; integer 0–9007199254740991.

The path cannot contain raw whitespace, non-ASCII bytes, fragments (#), backslashes, or malformed percent escapes. The generated request line is limited to 8192 bytes, including the method, absolute URI, and HTTP version. Every HTTP request carries Connection: close. Supplying content-length with any body mode other than fixed is invalid.

Limits and Timeouts​

SettingDefaultMeaning
max-header-bytes32768Integer 1024–65536; limits each response header block and the complete generated request header.
header-timeout-ms15000Deadline from the first byte of each incomplete response header block.
connect-response-timeout-ms30000CONNECT only: deadline from request-header handoff until final success.
idle-timeout-ms300000No-byte-progress deadline, starting at Init and refreshed by payload progress.

Origin DNS uses the async DNS service timeout/retry policy. Client idle accounting starts at core Init after origin DNS, so idle-timeout-ms excludes that wait but includes subsequent proxy DNS, connection and handshake waiting.

Timeouts accept integers 1–2147483647 milliseconds; zero does not disable them. Paused time counts, and expiry closes the line. HTTP mode cannot specify connect-response-timeout-ms; its response waiting is covered by the header and idle deadlines.

Request Bodies and Upload Completion​

Body modeWire framingCompletion
noneNo request bodyHeader completes the request; nonempty application input closes the line.
fixedContent-LengthExactly the configured count completes the upload while leaving the response direction open.
chunkedTransfer-Encoding: chunkedEach nonempty application buffer becomes one chunk; see the completion limitation below.

Fixed-length accounting spans payload callbacks. A buffer that would exceed the remaining count is rejected before any of that buffer is forwarded. A zero content length completes the request at the header. An empty payload callback never signals end-of-body in any mode.

Chunked encoding puts a hexadecimal size before each piece of body data and a CRLF after it. It lets the header be sent before the total upload size is known. The application supplies raw bytes; HttpProxyClient adds chunk framing and removes it from chunked responses. Chunk boundaries do not need to match TCP packets or the application's writes.

Chunked Upload Completion Limitation​

A finite chunked upload cannot finish sending and then wait for a later response. WaterWall's Finish callback tears down the line; it is not a send-only completion or TCP half-close signal.

When the source sends Finish, HttpProxyClient may send 0\r\n\r\n to terminate the chunked request. This happens only if the request header has already been sent, no older upload buffers or active chunk remain, normal dispatch is still allowed, and the response has not already completed. Advisory Pause does not suppress this terminal chunk. The node then destroys its state and forwards Finish; it does not retain the line to receive a response. An incomplete upload with older pending work is closed without a terminal chunk, and final-byte delivery is not guaranteed.

A terminal HTTP chunk can mark the request end without closing TCP. The limitation here is the absence of a separate WaterWall signal for “upload finished, keep receiving,” not a requirement to half-close the proxy connection.

Use body-mode: "fixed" when the length is known and the peer answers after the whole upload. Use chunked only with a peer that can respond while the upload remains open. The proxy must also permit that streaming exchange rather than buffering the whole request. With HttpProxyServer, a final response cancels an incomplete upload; the origin should consume the bytes it needs before responding.

Response and Connection Behavior​

The request header is sent as soon as the following Init returns and normal dispatch remains allowed, including during advisory Pause. There is no first-payload delay, and it does not wait for transport Est. Every transport Est is forwarded independently, including repeats, and does not mean the proxy has accepted the request.

CONNECT holds application data until a successful final response, then relays opaque bytes. HTTP mode can send its body immediately after the request header and receive a response while uploading. Pause/Resume are relayed transparently, including duplicates. Ready headers, FIFO drains and chunk suffixes do not wait for Resume; protocol barriers and storage bounds still apply. Locally originated protocol/FIFO Pause is released when its hold clears, while a relayed Resume may override that advisory signal.

Both modes remain active for the lifetime of the line: CONNECT retains its configured idle deadline after negotiation, and HTTP mode also retains body framing and completion duties. Permanent bypass would discard those obligations.

Both modes require an HTTP/1.1 final 2xx response and consume up to 32 informational responses internally. HTTP/1.0, 101/Upgrade, non-2xx finals (including 407 and redirects), malformed framing, truncation, and exceeded limits close the line. Proxy response headers and error bodies are not delivered to the application.

After successful CONNECT, body-framing fields are ignored and subsequent bytes belong to the tunnel. HTTP mode handles fixed-length, chunked, and close-delimited response bodies. It validates and discards trailers and does not decompress Content-Encoding; compressed content remains compressed body data. HEAD and no-content responses produce no application body.

A complete framed HTTP response immediately closes the single exchange, discards trailing bytes, and cancels any remaining upload. A close-delimited response ends when the proxy closes its side. Already delivered body bytes cannot be retracted if later framing or truncation is invalid.

There is no connection pooling or reuse, request pipelining, retry, redirect following, authentication challenge flow, Upgrade/WebSocket negotiation, HTTP/2, HTTP/3, UDP support, or built-in TLS. These restrictions describe the proxy interface; an established CONNECT tunnel does not inspect the protocol inside it.

Splice and Buffer Limits​

Splice requires a supported platform/build, misc.splice, and support from every node in the expanded chain. HttpProxyClient preserves eligible splice buffers for established CONNECT relay, fixed/chunked uploads, and response buffers wholly inside a known body-data range. A chunked upload prepends its size to the body buffer and sends a separate CRLF suffix in FIFO order.

Header/framing input and buffers crossing body boundaries may be fully materialized into ordinary best-fit storage. HttpProxyClient does not request ordinary reads by default and handles both ordinary and splice input in all phases. Adding TlsClient disables splice for the chain. Splice support does not guarantee zero-copy on every delivery.

With local origin DNS, the internal DomainResolver retains early input in its separate FIFO: 2 MiB logical bytes and 2 MiB allocation charge, without a separate 1,024-entry cap. It preserves ordinary and pipe-backed buffers, including resident prefixes. Resolver and client limits apply to separate stages, not one aggregate connection-memory budget.

Each delivered input is limited to 2 MiB. Necessary retained input in each direction shares limits of 2 MiB logical bytes, 2 MiB queue-capacity charge, and 1,024 entries. Allocation charge includes padding and buffer overhead, so retention can be refused before reaching the logical byte limit. Overflow closes the line. These limits bound individual deliveries and pending storage, not the total size of a streaming body.

Parser limits also include an 8192-byte start line, 128 header fields, a 1024-byte chunk-size/extension line, and a 16384-byte/64-field trailer section. Header and framing scratch have separate bounded storage. Materialization can temporarily add one ordinary input buffer containing up to 2 MiB of payload. These limits are independent of buffer-pool tiers and kernel pipe capacity and are not a total process-memory guarantee.

Node Metadata​

PropertyValue
Node flagskNodeFlagSupportsSplice
can_have_prevtrue
can_have_nexttrue
layer_groupkNodeLayer4
layer_group_prev_nodekNodeLayer4
layer_group_next_nodekNodeLayer4
required_padding_left10 bytes for a chunk prefix