# Hermes Hermes is a minimal HTTP/1.1 forward proxy written in Rust. It supports streaming plain HTTP requests and HTTPS tunneling with `CONNECT`, with OpenTelemetry traces, metrics, and logs exported over OTLP/HTTP. ## Features - Absolute-form HTTP forwarding (`GET http://host/path`) - HTTPS and arbitrary TCP tunneling with `CONNECT host:port` - Streaming request and response bodies - Upstream connection pooling and configurable timeouts - Removal of standard and `Connection`-nominated hop-by-hop headers - W3C Trace Context extraction and injection for plain HTTP traffic - Structured, trace-correlated logs on stdout and through OTLP - Bounded-cardinality HTTP and tunnel metrics - Localhost-only binding by default TLS is not terminated or inspected. HTTPS remains end-to-end encrypted between the client and destination. ## Build and run ```sh cargo build cargo run ``` The proxy listens on `127.0.0.1:8080` by default. ```sh # Plain HTTP curl --proxy http://127.0.0.1:8080 http://example.com/ # HTTPS via CONNECT curl --proxy http://127.0.0.1:8080 https://example.com/ ``` ### Proxy configuration | Environment variable | Default | Description | | --- | --- | --- | | `HERMES_LISTEN_ADDR` | `127.0.0.1:8080` | Downstream listen address | | `HERMES_CONNECT_TIMEOUT_MS` | `10000` | TCP connection timeout for `CONNECT` and the HTTP connector | | `HERMES_UPSTREAM_TIMEOUT_MS` | `30000` | Time limit for receiving upstream HTTP response headers | | `RUST_LOG` | `info` | `tracing` filter, for example `hermes=debug` | Do not expose Hermes on a public interface without adding appropriate authentication and destination access controls. The default configuration intentionally binds to localhost. ## OpenTelemetry Hermes exports traces, metrics, and logs independently to an OpenTelemetry Collector using OTLP/HTTP (protobuf). Exporters use the standard OpenTelemetry environment variables. ```sh export OTEL_SERVICE_NAME=hermes export OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4318 cargo run ``` Signal-specific settings such as `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`, `OTEL_EXPORTER_OTLP_METRICS_ENDPOINT`, and `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT` are also supported by the exporter. Standard OTLP headers, timeouts, and trace sampling environment variables can be used. Traces and logs are batched, metrics are exported periodically, and all providers are flushed during shutdown. An unavailable collector does not prevent the proxy from forwarding traffic, although exporter errors may appear in stdout logs. ### Local collector [`examples/otel-collector.yaml`](examples/otel-collector.yaml) receives OTLP/HTTP and prints telemetry to the collector log: ```sh docker run --rm \ -p 4318:4318 \ -v "$PWD/examples/otel-collector.yaml:/etc/otelcol/config.yaml:ro" \ otel/opentelemetry-collector:latest ``` In another terminal, run Hermes and send a request through it. The collector will print the exported resource spans, metrics, and log records. ### Traces - `proxy.request`: downstream plain HTTP server operation - `proxy.upstream`: upstream HTTP client operation - `proxy.connect`: `CONNECT` handshake and tunnel lifetime - `proxy.upstream_connect`: upstream TCP connection establishment For plain HTTP, incoming W3C `traceparent` and `tracestate` headers establish the parent context, and a new context is injected upstream. Trace context cannot be propagated inside an encrypted `CONNECT` tunnel. ### Metrics Hermes uses standard HTTP instrument names where applicable and adds proxy-specific tunnel instruments: - `hermes.proxy.requests` - `http.server.request.duration` - `http.server.active_requests` - `http.client.request.duration` - `hermes.proxy.errors` - `hermes.proxy.tunnels.active` - `hermes.proxy.tunnel.duration` - `hermes.proxy.tunnel.bytes` Metric attributes are deliberately bounded. Raw URLs, destination hostnames, and client addresses are not metric attributes. ### Logs Startup, shutdown, request failures, and tunnel completion are emitted as structured `tracing` events. Logs emitted inside a trace span include its trace and span IDs in OTLP. Headers, bodies, credentials, and query strings are not logged by the proxy. ## Limitations - HTTP/1.1 downstream and upstream only; no HTTP/2 or gRPC proxying - No TLS interception, caching, authentication, ACLs, or rate limiting - No HTTP Upgrade forwarding other than `CONNECT` - No dynamic configuration or Envoy xDS support ## Development ```sh cargo fmt --all -- --check cargo test cargo clippy --all-targets --all-features -- -D warnings ```