exchange 0.1.0

A minimal, configuration-driven HTTP reverse proxy built with hyper and tokio.
# ↔️ Exchange

A minimal, configuration-driven reverse proxy in Rust, built with `hyper`, `reqwest`, and `tokio`.

It reads a `proxy.toml` file at startup to configure routes and server settings, forwarding incoming requests to the appropriate upstream services.

-----

## 🚀 Features

  * **Configuration-driven:** All routes and settings are managed in a simple `proxy.toml` file.
  * **Path-based Routing:** Forward requests based on the incoming request path.
  * **Wildcard Matching:** Use `wildcard = true` to match all sub-paths of a given pattern.
  * **Connection Pooling:** Uses `reqwest`'s connection pool for efficient upstream connections.
  * **Configurable Limits:** Set maximum request body size and gateway timeouts.

-----

## 🔧 Configuration

Create a `proxy.toml` file in the same directory where you run the application.

### Example `proxy.toml`

```toml
# The port the proxy server will listen on
port = 8080

# --- Optional Settings ---

# Max request body size in bytes (default: 1048576, or 1MB)
max_body_bytes = 2097152 # 2MB

# Upstream request timeout (default: 5s)
# Uses serde's Duration format
gateway_timeout = { secs = 10, nanos = 0 }

# --- Routes ---

# An exact-match route
# Requests to "http://localhost:8080/api/users"
# will be proxied to "http://users-service:3000/api/users"
[[routes]]
pattern = "/api/users"
target = "http://users-service:3000"
# wildcard = false (this is the default)

# A wildcard route
# Requests to "http://localhost:8080/assets/main.css"
# will be proxied to "http://static-files:9000/assets/main.css"
[[routes]]
pattern = "/assets"
target = "http://static-files:9000"
wildcard = true
```

### Configuration Details

  * **`port`** (u16, *Required*): The port for the proxy to listen on.
  * **`max_body_bytes`** (u64, *Optional*): The maximum allowed request body size in bytes.
      * *Default:* `1048576` (1MB)
  * **`gateway_timeout`** (Duration, *Optional*): The time to wait for a response from the upstream service.
      * *Default:* `{ secs = 5, nanos = 0 }` (5 seconds)
  * **`connection_pool_size`** (usize, *Optional*): Max idle connections per upstream host.
      * *Default:* 2 \* (number of system cores)
  * **`[[routes]]`** (Array, *Required*): A list of route objects.
      * **`pattern`** (string): The incoming URI path to match.
      * **`target`** (string): The base URI of the upstream service to forward to.
      * **`wildcard`** (bool, *Optional*): If `true`, matches any path that *starts with* the `pattern`. If `false` (default), requires an *exact* path match.

-----

## 🛠️ How It Works

Exchange listens for incoming requests and matches them against its list of routes.

1.  A **Client** sends a request (e.g., `GET /assets/img.png`).
2.  **Exchange** receives the request on `port`.
3.  It searches its routes for a match.
      * It finds the `[[routes]]` entry with `pattern = "/assets"` and `wildcard = true`.
4.  It constructs the full target URL: `http://static-files:9000` (target) + `/assets/img.png` (original path).
5.  It forwards the request, headers, and body to the **Upstream Service**.
6.  The service's response is streamed back to the original **Client**.

If no route matches, a `404 Not Found` is returned. If the incoming request body exceeds `max_body_bytes`, a `413 Payload Too Large` is returned.

-----

## 📦 Running the Proxy

1.  Ensure you have Rust and Cargo installed.

2.  Create your `proxy.toml` file in the project's root directory.

3.  Run the server in release mode:

    ```sh
    cargo run --release
    ```

4.  The proxy will start on the port specified in your config file.

    ```
    Proxy server running on http://0.0.0.0:8080
    Press Ctrl+C to stop
    ```