Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 130 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 13 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ required-features = ["cli"]
hyper = { version = "1.3", features = ["http1", "client", "server"] }
hyper-util = { version = "0.1", features = ["client", "server", "http1", "tokio"] }
http-body-util = "0.1"
tokio = { version = "1", features = ["rt-multi-thread", "macros", "net", "time", "signal", "process", "fs"] }
tokio = { version = "1", features = ["rt-multi-thread", "macros", "net", "time", "signal", "process", "fs", "io-util"] }
anyhow = "1.0"
thiserror = "2.0.18"
bytes = "1.0"
Expand All @@ -48,13 +48,24 @@ uuid = { version = "1", features = ["v4"] }
serde = { version = "1.0", features = ["derive"], optional = true }
serde_json = { version = "1.0", optional = true }
hyper-rustls = { version = "0.27", optional = true }
tokio-rustls = { version = "0.26", optional = true }
rustls = { version = "0.23", default-features = false, features = ["aws_lc_rs", "logging", "std", "tls12"], optional = true }
rustls-pemfile = { version = "2", optional = true }
tracing = "0.1"
clap = { version = "4.5", features = ["derive"], optional = true }
tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"], optional = true }
http-body = "1.0.1"

[dev-dependencies]
criterion = { version = "0.5.1", default-features = false }
rcgen = { version = "0.13" }
tempfile = "3"
hyper = "1.3"
hyper-util = { version = "0.1", features = ["client", "server", "http1", "tokio"] }
hyper-rustls = { version = "0.27" }
rustls = { version = "0.23", default-features = false, features = ["aws_lc_rs", "logging", "std", "tls12"] }
rustls-pemfile = { version = "2" }
http-body-util = "0.1"

[package.metadata.docs.rs]
all-features = true
Expand All @@ -78,6 +89,6 @@ harness = false
[features]
default = ["cli", "tls", "api", "logging"]
cli = ["dep:clap", "dep:tracing-subscriber"]
tls = ["dep:hyper-rustls"]
tls = ["dep:hyper-rustls", "dep:tokio-rustls", "dep:rustls", "dep:rustls-pemfile"]
api = ["dep:serde", "dep:serde_json"]
logging = []
66 changes: 62 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Lightweight, embeddable HTTP reverse proxy written in Rust with Caddy-like confi
- **Header Manipulation**: Add, modify, or remove headers
- **URI Rewriting**: Replace parts of request URIs
- **HTTP/HTTPS Backend Support**: Full support for both HTTP and HTTPS backends
- **TLS Termination**: HTTPS on the frontend with SNI-based multi-domain support
- **Method-based Routing**: Different behavior for different HTTP methods
- **Direct Responses**: Respond with custom status codes and bodies
- **Authentication Module**: Token validation and header substitution
Expand Down Expand Up @@ -51,13 +52,21 @@ tiny-proxy = "0.3"
Run as standalone server:

```bash
# Auto-detect listeners from config (recommended)
tiny-proxy --config config.caddy

# Or specify a single listen address
tiny-proxy --config config.caddy --addr 127.0.0.1:8080
```

#### CLI Arguments

- `--config, -c`: Path to configuration file (default: `./file.caddy`)
- `--addr, -a`: Address to listen on (default: `127.0.0.1:8080`)
- `--addr, -a`: Optional. Bind a **single** listener on this address (plain `start()`).
When omitted, **auto-detect mode** (`start_all()`): one listener per site address in config.
TLS sites → HTTPS with SNI; non-TLS → HTTP. In auto-detect mode only, each TLS port also
gets an HTTP→HTTPS redirect listener (`redirect_port = tls_port - 443 + 80`, e.g. 443→80,
8443→8080). With `--addr`, only the specified listener runs — **no** automatic redirect server.

### Library Mode

Expand Down Expand Up @@ -109,7 +118,13 @@ async fn main() -> anyhow::Result<()> {
#### Hot-Reload Configuration

Update configuration at runtime without restart. The proxy uses `Arc<RwLock<Config>>` internally,
so any config change takes effect immediately for new connections:
so routing and directive changes take effect immediately for new connections.

> **TLS certificates**: cert/key files and `TlsAcceptor` are loaded when a listener starts.
> Hot-reload updates site routing and directives, but **not** TLS certificates — to pick up
> new certs or keys, restart the proxy (or the TLS listener).

Example:

```rust
use tiny_proxy::{Config, Proxy};
Expand Down Expand Up @@ -188,6 +203,45 @@ localhost:8080 {

Timeout values support duration suffixes: `30s`, `5m`, `2h`, `1d`, or plain numbers (seconds).

#### `tls`

Enable HTTPS on the frontend with TLS termination. Specify paths to the certificate chain and private key (PEM format).

```caddy
# Single domain with TLS
example.com:443 {
tls /etc/ssl/cert.pem /etc/ssl/key.pem
reverse_proxy backend:8080
}

# Multiple domains on port 443 (SNI-based routing)
example.com:443 {
tls /etc/ssl/example.com/cert.pem /etc/ssl/example.com/key.pem
reverse_proxy backend:8080
}

api.example.com:443 {
tls /etc/ssl/api.example.com/cert.pem /etc/ssl/api.example.com/key.pem
reverse_proxy api-backend:3000
}
```

**Auto-detect mode** (no `--addr`, uses `start_all()`):
- One HTTPS listener per TLS site address, with SNI-based certificate selection
- HTTP→HTTPS redirect per TLS port: `redirect_port = tls_port - 443 + 80` (443→80, 8443→8080)
- Correct `X-Forwarded-Proto: https` sent to backends

**Single-address mode** (`--addr`): only the given listener is started — no automatic redirect server.
Use this when you bind one port manually; use auto-detect for full multi-site + redirect setup.

> If the redirect port is already in use, HTTPS continues to work; redirect is skipped with a warning.

> **Host header**: on default ports (443/80), browsers omit the port (`Host: example.com`).
> On non-default TLS ports (e.g. 8443), browsers include it (`Host: example.com:8443`) — config
> keys must match. See `find_site` docs in `handler.rs` for details.

> **Known limitation**: TLS certs are loaded at listener startup; hot-reload does not reload them (see Hot-Reload above).

#### `handle_path`

Match paths with pattern (supports wildcard `*`).
Expand Down Expand Up @@ -382,7 +436,11 @@ Enable CLI dependencies and `tiny-proxy` binary.

#### `tls` (default)

Enable HTTPS backend support using `hyper-rustls` (pure Rust TLS).
Enable TLS support — both **frontend TLS termination** (HTTPS listeners) and **backend HTTPS** connections:

- Frontend: `rustls` + `tokio-rustls` for HTTPS listeners with SNI-based routing
- Backend: `hyper-rustls` for proxying to HTTPS backends
- `rustls-pemfile` for loading PEM certificate chains and private keys

#### `api` (default)

Expand Down Expand Up @@ -524,7 +582,7 @@ cargo run --example background
- ⏳ Static file serving
- ⏳ Try files (SPA support)
- ⏳ Buffering control
- TLS/SSL support
- TLS/SSL termination (SNI, multi-domain, HTTP→HTTPS redirect)
- ⏳ WebSocket support
- ⏳ Rate limiting
- ✅ Structured access log with X-Request-ID (method, path, host, status, duration, bytes_sent)
Expand Down
Loading
Loading