Files
reverse-proxy/tasks/proxy/headers-and-forwarding.md

4.1 KiB

id, name, status, depends_on, scope, risk, impact, level
id name status depends_on scope risk impact level
proxy/headers-and-forwarding Implement proxy header injection, hop-by-hop removal, and request forwarding with hyper Client completed
proxy/host-routing
moderate medium component implementation

Description

Implement the core reverse proxy logic: inject proxy headers, remove hop-by-hop headers, and forward requests to the upstream via a shared hyper::Client.

Proxy Header Injection

The proxy is an edge proxy — it sits directly in front of the internet with no trusted proxies upstream. This means existing X-Forwarded-For headers from the client cannot be trusted.

Header Value Source Behavior
Host Original request Host header Preserved as-is
X-Real-IP ConnectInfo<SocketAddr> remote IP Set to client's IP address
X-Forwarded-For ConnectInfo<SocketAddr> remote IP Replaced, not appended
X-Forwarded-Proto Determined by listener port https for https_port, http for http_port

Hop-by-Hop Header Removal

Remove these headers before forwarding to upstream (RFC 2616 §13.5.1):

  • Connection, Keep-Alive, Proxy-Authorization, Proxy-Authenticate
  • TE, Trailers, Transfer-Encoding, Upgrade

Also remove these from upstream responses before sending to client.

Request Forwarding

  1. Build the upstream URI: {upstream_scheme}://{upstream}{path}?{query}
  2. Copy request method, headers (with proxy headers injected, hop-by-hop removed), and body
  3. Send via shared hyper::Client with per-site timeout overrides
  4. Stream response back to client (chunk-by-chunk, not buffered)
  5. Handle client disconnect (log at debug, close upstream connection)
  6. Handle upstream disconnect (send whatever was already sent, close connection)

hyper Client Configuration

  • Created once at startup, shared via axum State
  • HTTP/1.1 only for upstream connections
  • No redirect following (proxies should not follow redirects)
  • Connection pooling (hyper default behavior)
  • Per-site timeout overrides: upstream_connect_timeout_secs (default 5s), upstream_request_timeout_secs (default 60s)

Upstream Scheme

Default is http://. When upstream_scheme is "https", validate the upstream's TLS certificate using the system's native TLS root certificates. Certificate validation failures result in 502 Bad Gateway.

Acceptance Criteria

  • X-Real-IP set from ConnectInfo<SocketAddr> remote IP
  • X-Forwarded-For replaced (not appended) with client IP
  • X-Forwarded-Proto set to https or http based on listener port
  • Host header preserved as-is
  • Hop-by-hop headers removed before forwarding to upstream
  • Hop-by-hop headers removed from upstream response before sending to client
  • No Server header added to responses
  • No Via header added in Phase 1
  • Request body streamed (not buffered) to upstream
  • Response body streamed (not buffered) to client
  • Client disconnect logged at debug level, upstream connection closed
  • Upstream disconnect: client receives whatever was already sent
  • Per-site timeout overrides applied to hyper Client requests
  • upstream_scheme: "https" validates upstream TLS certificate with system roots
  • Shared hyper::Client instance via axum State
  • Unit tests for header injection and removal
  • Integration test: proxy request to upstream, verify headers and response

References

  • docs/architecture/proxy.md — header injection, request forwarding, error handling
  • docs/architecture/decisions/002-custom-proxy-handler.md — custom handler rationale
  • docs/architecture/decisions/017-upstream-connection-defaults.md — HTTP/1.1, no redirects
  • docs/architecture/decisions/021-x-forwarded-for-edge-proxy.md — edge proxy model

Notes

The X-Forwarded-For: replace, don't append behavior is critical. The proxy is the edge — there are no trusted proxies upstream. Existing X-Forwarded-For values from the client could be spoofed and must not be trusted.

Summary

To be filled on completion