Streaming Reverse Proxy
Relevant source files
The following files were used as context for generating this wiki page:
The Streaming Reverse Proxy is the core component of Hodor. It manages the transmission of HTTP and WebSocket traffic between clients and an upstream backend. It enforces authentication gates before proxying data and ensures that request and response bodies stream without buffering in memory.
Sources: src/proxy.rs:1-25, README.md:10-25, AGENTS.md:15-30
Request Lifecycle and Routing
The Hodor server uses the axum framework to handle incoming connections. Every request that does not match the internal /_gate/ routes falls through to the proxy_or_login handler. This handler determines whether the request meets authentication requirements or needs redirection to a login page.
The following diagram illustrates the high-level decision flow for an incoming request:
The diagram shows how Hodor evaluates authentication layers before granting access to the upstream service.
Sources: src/main.rs:21-27, src/proxy.rs:60-75, README.md:143-155
Authentication Methods
Hodor identifies three distinct ways a request clears the security gate. It communicates this method to the upstream service via the X-Hodor-Auth header.
| Method | Value | Trigger Condition |
|---|---|---|
Password | password | Client presents a valid HMAC-SHA256 signed session cookie. |
Bypass | bypass | Client IP matches a configured BYPASS_CIDRS network. |
Public | public | Request path matches an entry in BYPASS_PATHS. |
Sources: src/proxy.rs:29-45, README.md:200-215
Architecture and Data Flow
The proxy logic resides in src/proxy.rs. It utilizes hyper and tokio to manage asynchronous streaming.
Connection Handling
- Header Normalization: Hodor strips all client-supplied
X-Hodor-*headers to prevent authentication forgery. It also removes standard hop-by-hop headers (e.g.,Connection,Transfer-Encoding,Upgrade) and any headers listed in theConnectionheader itself. - Forwarding Headers: The proxy appends the client IP to
X-Forwarded-Forand setsX-Forwarded-Protobased on whether the request arrived via HTTP or HTTPS (ifTRUST_PROXYis enabled). - Upstream Request: It builds a new request using the configured
UPSTREAMauthority. IfPRESERVE_HOSTis false, it replaces theHostheader with the upstream's host. - Streaming: The proxy streams bodies bidirectionally. It enforces a timeout for receiving upstream response headers via
UPSTREAM_HEADER_TIMEOUT, but does not apply this deadline to the body stream, allowing large transfers.
Sources: src/proxy.rs:136-160, src/proxy.rs:252-270, AGENTS.md:38-45, README.md:225-235
WebSocket Tunneling
When Hodor detects a WebSocket upgrade request (indicated by Connection: upgrade and Upgrade: websocket), it initiates a bidirectional byte tunnel.
This diagram represents the upgrade process and subsequent byte-level proxying for WebSockets.
Sources: src/proxy.rs:170-180, src/proxy.rs:218-232, src/tests/proxy.rs:188-210
Configuration Options
Proxy behavior is customized through several environment variables and TOML settings.
| Configuration Key | Description | Default |
|---|---|---|
UPSTREAM | The target backend URL (e.g., http://app:3000). | Required |
UPSTREAM_HEADER | A static Name: Value header to add to all proxied requests. | None |
PRESERVE_HOST | If true, keeps the client's Host header; otherwise, uses the upstream host. | false |
UPSTREAM_HEADER_TIMEOUT | Seconds to wait for upstream headers before returning 502. | 30 |
TRUST_PROXY | Enables processing of X-Forwarded-* headers from the immediate peer. | false |
Sources: src/config.rs:18-45, README.md:75-105
Security and Header Stripping
Hodor enforces strict header isolation to protect the upstream. It removes all headers prefixed with x-hodor- from the incoming client request before injecting its own authenticated X-Hodor-Auth header. This ensures the upstream can trust the header value.
// src/proxy.rs:198-210
fn overwrite_auth_headers(headers: &mut HeaderMap, auth_method: AuthMethod) {
let forged: Vec<HeaderName> = headers
.keys()
.filter(|name| name.as_str().starts_with(HODOR_HEADER_PREFIX))
.cloned()
.collect();
for name in forged {
headers.remove(&name);
}
headers.insert(
HeaderName::from_static(X_HODOR_AUTH_HEADER),
auth_method.as_header_value(),
);
}
Sources: src/proxy.rs:198-210, AGENTS.md:40-42, src/tests/proxy.rs:360-370
Error Handling
If the upstream service is unreachable or fails to respond within the configured upstream_connect_timeout or upstream_header_timeout, Hodor returns a 502 Bad Gateway. This response is rendered using the error_template.html (or a custom template if configured) via the bad_gateway function.
Sources: src/proxy.rs:163-170, src/templates.rs:145-152, README.md:320-335