structured-proxy 1.0.1

Universal gRPC→REST transcoding proxy — config-driven, works with any gRPC service
Documentation

structured-proxy

Universal, config-driven gRPC→REST transcoding proxy. One binary, different YAML configs — different products.

Works with any gRPC service via proto descriptor files. No code generation, no custom handlers — just configuration.

Features

  • Dynamic REST routes from proto descriptors using google.api.http annotations
  • Auto-generated OpenAPI documentation from proto messages
  • Server-streaming RPC → SSE/chunked HTTP responses
  • Rate limiting (Shield) — endpoint classification + per-identifier limiting via YAML
  • JWT/OIDC validation — route-level auth policies with JWKS auto-discovery
  • Path aliasing — configurable route remapping (e.g., /oauth2/*/v1/oauth2/*)
  • Maintenance mode — 503 with configurable exempt paths
  • Health endpoints/health/live, /health/ready, /health/startup
  • Prometheus metrics/metrics endpoint
  • Zero code changes between services — same binary, different config

Quick Start

# Install
cargo install structured-proxy

# Run with your service config
structured-proxy --config my-service.yaml

Configuration

# my-service.yaml
listen: "0.0.0.0:8080"

upstream:
  address: "http://127.0.0.1:50051"

descriptor:
  file: "my-service.descriptor.bin"
  # OR: reflection: true

cors:
  allow_origins: ["*"]

# Optional: path aliases
aliases:
  - from: "/api/v1/*"
    to: "/my.package.v1.MyService/*"

# Optional: rate limiting
shield:
  enabled: true
  default_rpm: 60
  endpoints:
    - pattern: "/api/v1/heavy-*"
      rpm: 10
      identifier: header:x-api-key

Generate the descriptor file from your proto:

buf build -o my-service.descriptor.bin
# or
protoc --descriptor_set_out=my-service.descriptor.bin --include_imports *.proto

Library Usage

use structured_proxy::{ProxyConfig, build_proxy};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let config = ProxyConfig::from_file("my-service.yaml")?;
    let app = build_proxy(config).await?;

    let listener = tokio::net::TcpListener::bind("0.0.0.0:8080").await?;
    axum::serve(listener, app).await?;
    Ok(())
}

How It Works

  1. Load proto descriptor (file or gRPC reflection)
  2. Parse google.api.http annotations → generate REST routes
  3. Incoming HTTP request → transcode to gRPC (path params, query params, JSON body → protobuf)
  4. Forward to upstream gRPC service
  5. Response protobuf → transcode to JSON
  6. Serve OpenAPI spec at /openapi.json

Architecture

Client (HTTP/JSON)
    │
    ▼
┌─────────────────────┐
│  structured-proxy    │
│                      │
│  ┌────────────────┐  │
│  │ Shield (rate)  │  │
│  ├────────────────┤  │
│  │ Auth (JWT)     │  │
│  ├────────────────┤  │
│  │ Transcoder     │  │  REST → gRPC
│  │ (prost-reflect)│  │  JSON → Protobuf
│  ├────────────────┤  │
│  │ OpenAPI gen    │  │  /openapi.json
│  └────────────────┘  │
└─────────┬────────────┘
          │ gRPC
          ▼
   Upstream Service

License

Apache-2.0