Skip to content
Open
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
b266a16
[router] wasm: add wasm module and module manager
tonyluj Sep 21, 2025
1e27394
[router] wasm: add wasm runtime and config
tonyluj Sep 21, 2025
8dc48e7
[router] wasm: runtime add thread pool
tonyluj Sep 21, 2025
cd0ac10
[router] wasm: using wasm module manager as top level executor
tonyluj Sep 21, 2025
c50c0fb
[router] wasm: add basic metrics
tonyluj Sep 21, 2025
6bf8e9e
[router] wasm: add runtime tests
tonyluj Sep 29, 2025
10a7c16
[router] wasm: add config unit tests
tonyluj Sep 29, 2025
e91d3d8
[router] wasm: code format
tonyluj Sep 29, 2025
799decb
[router] wasm: drain all task when drop
tonyluj Sep 29, 2025
72828df
[router] wasm: fix timeout when drop
tonyluj Sep 29, 2025
0bbd0da
[router] wasm: code format
tonyluj Sep 29, 2025
81d0d10
[router] wasm: add middleware attach type
tonyluj Sep 30, 2025
0ec640f
[router] wasm: add errors
tonyluj Sep 30, 2025
380a9cb
[router] wasm: code format
tonyluj Sep 30, 2025
5257cf1
[router] wasm: add api to manage wasm modules
tonyluj Sep 30, 2025
8b3f82f
[router] wasm: introduce wit interface
tonyluj Oct 1, 2025
5533ab8
[router] wasm: add wit executor
tonyluj Oct 1, 2025
5bb1cc7
[router] wasm: add middleware logic
tonyluj Oct 1, 2025
affdfe7
[router] wasm: fix conflicts
tonyluj Oct 31, 2025
50c1806
[router] wasm: remote unused executors
tonyluj Oct 1, 2025
447ec8f
[router] wasm: add docs and examples
tonyluj Oct 1, 2025
f06aade
[router] wasm: add myself to wasm CODEOWNERS
tonyluj Oct 31, 2025
c5accc3
[router] wasm: code format
tonyluj Oct 31, 2025
a7838a4
Merge branch 'main' into tonyluj/sgl-router-wasm-latest
tonyluj Oct 31, 2025
c197046
Merge branch 'main' into tonyluj/sgl-router-wasm-latest
tonyluj Nov 4, 2025
158dfad
[router] wasm: move functions to wasm module to make middleware clean
tonyluj Nov 4, 2025
0fa7995
[router] wasm: update examples for more safe access of RATE_LIMIT_SATE
tonyluj Nov 4, 2025
053718f
[router] wasm: replace unwarp with error handling
tonyluj Nov 4, 2025
de5be86
[router] wasm: remove unwrap in middleware
tonyluj Nov 4, 2025
338c44f
[router] wasm: some minor improvements
tonyluj Nov 4, 2025
e0453e7
[router] wasm: simplify auth logic
tonyluj Nov 4, 2025
4b28fb3
[router] wasm: remove unused code
tonyluj Nov 4, 2025
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
2 changes: 2 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,7 @@
/sgl-router/src/routers @CatherineSue @key4ng @slin1237
/sgl-router/src/tokenizer @slin1237 @CatherineSue
/sgl-router/src/tool_parser @slin1237 @CatherineSue
/sgl-router/src/wasm @tonyluj
/sgl-router/examples/wasm @tonyluj
/test/srt/test_modelopt* @Edwardf0t1
/test/srt/ascend @ping1jing2
6 changes: 6 additions & 0 deletions sgl-router/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,12 @@ backoff = { version = "0.4", features = ["tokio"] }
strum = { version = "0.26", features = ["derive"] }
once_cell = "1.21.3"

# wasm dependencies
sha2 = "0.10"
wasmtime = { version = "38.0", features = ["component-model", "async"] }
wasmtime-wasi = "38.0"
async-channel = "2.5"

[build-dependencies]
tonic-prost-build = "0.14.2"
prost-build = "0.14.1"
Expand Down
28 changes: 28 additions & 0 deletions sgl-router/examples/wasm/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Rust build artifacts
target/
**/target/

# Cargo lock files (examples don't need locked dependencies)
Cargo.lock
**/Cargo.lock

# Generated WASM files
*.wasm
*.component.wasm
**/*.wasm
**/*.component.wasm

# Build scripts output
build/

# IDE files
.idea/
.vscode/
*.swp
*.swo
*~

# OS files
.DS_Store
Thumbs.db

103 changes: 103 additions & 0 deletions sgl-router/examples/wasm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# WASM Guest Examples for sgl-router

This directory contains example WASM middleware components demonstrating how to implement custom middleware for sgl-router using the WebAssembly Component Model (WIT).

## Examples Overview

### [wasm-guest-auth](./wasm-guest-auth/)

API key authentication middleware that validates API keys for requests to `/api` and `/v1` paths.

**Features:**
- Validates API keys from `Authorization` header or `x-api-key` header
- Returns `401 Unauthorized` for missing or invalid keys
- Attach point: `OnRequest` only

**Use case:** Protect API endpoints with API key authentication.

### [wasm-guest-logging](./wasm-guest-logging/)

Request tracking and status code conversion middleware.

**Features:**
- Adds tracking headers (`x-request-id`, `x-wasm-processed`, `x-processed-at`, `x-api-route`)
- Converts `500` errors to `503` for better client handling
- Attach points: `OnRequest` and `OnResponse`

**Use case:** Request tracing and error status code conversion.

### [wasm-guest-ratelimit](./wasm-guest-ratelimit/)

Rate limiting middleware with configurable limits.

**Features:**
- Rate limiting per identifier (API Key, IP, or Request ID)
- Default: 60 requests per minute
- Returns `429 Too Many Requests` when limit exceeded
- Attach point: `OnRequest` only

**Note:** This is a simplified demonstration with per-instance state. For production, use router-level rate limiting with shared state.

**Use case:** Protect against request flooding and abuse.

## Quick Start

Each example includes its own README with detailed build and deployment instructions. See individual example directories for:

- Build instructions
- Deployment configuration
- Customization options
- Testing examples

## Common Prerequisites

All examples require:

- Rust toolchain (latest stable)
- `wasm32-wasip2` target: `rustup target add wasm32-wasip2`
- `wasm-tools`: `cargo install wasm-tools`
- sgl-router running with WASM enabled (`--enable-wasm`)

## Building All Examples

```bash
cd examples/wasm
for example in wasm-guest-auth wasm-guest-logging wasm-guest-ratelimit; do
echo "Building $example..."
cd $example && ./build.sh && cd ..
done
```

## Deploying Multiple Modules

You can deploy all three modules together:

```bash
curl -X POST http://localhost:3000/wasm \
-H "Content-Type: application/json" \
-d '{
"modules": [
{
"name": "auth-middleware",
"file_path": "/path/to/wasm_guest_auth.component.wasm",
"module_type": "Middleware",
"attach_points": [{"Middleware": "OnRequest"}]
},
{
"name": "logging-middleware",
"file_path": "/path/to/wasm_guest_logging.component.wasm",
"module_type": "Middleware",
"attach_points": [{"Middleware": "OnRequest"}, {"Middleware": "OnResponse"}]
},
{
"name": "ratelimit-middleware",
"file_path": "/path/to/wasm_guest_ratelimit.component.wasm",
"module_type": "Middleware",
"attach_points": [{"Middleware": "OnRequest"}]
}
]
}'
```

Modules execute in the order they are deployed. If a module returns `Reject`, subsequent modules won't execute.

11 changes: 11 additions & 0 deletions sgl-router/examples/wasm/wasm-guest-auth/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "wasm-guest-auth"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]
wit-bindgen = { version = "0.21", features = ["macros"] }

62 changes: 62 additions & 0 deletions sgl-router/examples/wasm/wasm-guest-auth/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# WASM Auth Example for sgl-router

This example demonstrates API key authentication middleware for sgl-router using the WebAssembly Component Model (WIT).

## Overview

This middleware validates API keys for requests to `/api` and `/v1` paths:

- Supports `Authorization: Bearer <key>` header
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

api key is already supported
perhaps we can leave example in a different PR?
for example, write an example using other languages

- Supports `Authorization: ApiKey <key>` header
- Supports `x-api-key` header
- Returns `401 Unauthorized` for missing or invalid keys

**Default API Key**: `secret-api-key-12345`

## Quick Start

### Build and Deploy

```bash
# Build
cd examples/wasm-guest-auth
./build.sh

# Deploy (replace file_path with actual path)
curl -X POST http://localhost:3000/wasm \
-H "Content-Type: application/json" \
-d '{
"modules": [{
"name": "auth-middleware",
"file_path": "/absolute/path/to/wasm_guest_auth.component.wasm",
"module_type": "Middleware",
"attach_points": [{"Middleware": "OnRequest"}]
}]
}'
```

### Customization

Modify `EXPECTED_API_KEY` in `src/lib.rs`:

```rust
const EXPECTED_API_KEY: &str = "your-secret-key";
```

## Testing

```bash
# Test unauthorized (returns 401)
curl -v http://localhost:3000/api/test

# Test authorized (passes)
curl -v http://localhost:3000/api/test \
-H "Authorization: Bearer secret-api-key-12345"
```

## Troubleshooting

- Verify API key matches `EXPECTED_API_KEY` in code
- Check request header format and path (`/api` or `/v1`)
- Verify module is attached to `OnRequest` phase
- Check router logs for errors
78 changes: 78 additions & 0 deletions sgl-router/examples/wasm/wasm-guest-auth/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#!/bin/bash
# Build script for WASM guest auth example
# This script simplifies the build process for the WASM middleware component

set -e

echo "Building WASM guest auth example..."

# Check if we're in the right directory
if [ ! -f "Cargo.toml" ]; then
echo "Error: Cargo.toml not found. Please run this script from the wasm-guest-auth directory."
exit 1
fi

# Check for required tools
command -v cargo >/dev/null 2>&1 || { echo "Error: cargo is required but not installed. Aborting." >&2; exit 1; }

# Check and install wasm32-wasip2 target
echo "Checking for wasm32-wasip2 target..."
if ! rustup target list --installed | grep -q "wasm32-wasip2"; then
echo "wasm32-wasip2 target not found. Installing..."
rustup target add wasm32-wasip2
echo "✓ wasm32-wasip2 target installed"
else
echo "✓ wasm32-wasip2 target already installed"
fi

# Check for wasm-tools
if ! command -v wasm-tools >/dev/null 2>&1; then
echo "Error: wasm-tools is required but not installed."
echo "Install it with: cargo install wasm-tools"
exit 1
fi

# Build with cargo (wit-bindgen uses cargo, not wasm-pack)
echo "Running cargo build..."
cargo build --target wasm32-wasip2 --release

# Output locations
WASM_MODULE="target/wasm32-wasip2/release/wasm_guest_auth.wasm"
WASM_COMPONENT="target/wasm32-wasip2/release/wasm_guest_auth.component.wasm"

if [ ! -f "$WASM_MODULE" ]; then
echo "Error: Build failed - WASM module not found"
exit 1
fi

# Check if the file is already a component
echo "Checking WASM file format..."
if wasm-tools print "$WASM_MODULE" 2>/dev/null | grep -q "^(\s*component"; then
echo "✓ WASM file is already in component format"
# Copy to component path for consistency
cp "$WASM_MODULE" "$WASM_COMPONENT"
else
# Wrap the WASM module into a component format
echo "Wrapping WASM module into component format..."
wasm-tools component new "$WASM_MODULE" -o "$WASM_COMPONENT"
if [ ! -f "$WASM_COMPONENT" ]; then
echo "Error: Failed to create component file"
exit 1
fi
fi

if [ -f "$WASM_COMPONENT" ]; then
echo ""
echo "✓ Build successful!"
echo " WASM module: $WASM_MODULE"
echo " WASM component: $WASM_COMPONENT"
echo ""
echo "Next steps:"
echo "1. Use the component file ($WASM_COMPONENT) when adding the module"
echo "2. Prepare the module configuration (see README.md for JSON format)"
echo "3. Use the API endpoint to add the module (see README.md for details)"
else
echo "Error: Component file not found"
exit 1
fi

76 changes: 76 additions & 0 deletions sgl-router/examples/wasm/wasm-guest-auth/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
//! WASM Guest Auth Example for sgl-router
//!
//! This example demonstrates API key authentication middleware
//! for sgl-router using the WebAssembly Component Model (WIT).
//!
//! Features:
//! - API Key authentication

wit_bindgen::generate!({
path: "../../../src/wasm/wit",
world: "sgl-router",
});

use exports::sgl::router::middleware_on_request::Guest as OnRequestGuest;
use exports::sgl::router::middleware_on_response::Guest as OnResponseGuest;
use sgl::router::middleware_types::{Request, Response, Action};

/// Expected API Key (in production, this should be passed as configuration)
const EXPECTED_API_KEY: &str = "secret-api-key-12345";

/// Main middleware implementation
struct Middleware;

// Helper function to find header value
fn find_header_value(headers: &[sgl::router::middleware_types::Header], name: &str) -> Option<String> {
headers
.iter()
.find(|h| h.name.eq_ignore_ascii_case(name))
.map(|h| h.value.clone())
}

// Implement on-request interface
impl OnRequestGuest for Middleware {
fn on_request(req: Request) -> Action {
// API Key Authentication
// Check for API key in Authorization header for /api routes
if req.path.starts_with("/api") || req.path.starts_with("/v1") {
let auth_header = find_header_value(&req.headers, "authorization");
let api_key = auth_header
.and_then(|h| {
if h.starts_with("Bearer ") {
Some(h[7..].to_string())
} else if h.starts_with("ApiKey ") {
Some(h[7..].to_string())
} else {
None
}
})
.or_else(|| find_header_value(&req.headers, "x-api-key"));

if let Some(key) = api_key {
if key != EXPECTED_API_KEY {
// Invalid API key - reject with 401 Unauthorized
return Action::Reject(401);
}
} else {
// Missing API key - reject with 401 Unauthorized
return Action::Reject(401);
}
}

// Authentication passed, continue processing
Action::Continue
}
}

// Implement on-response interface (empty - not used for auth)
impl OnResponseGuest for Middleware {
fn on_response(_resp: Response) -> Action {
Action::Continue
}
}

// Export the component
export!(Middleware);

11 changes: 11 additions & 0 deletions sgl-router/examples/wasm/wasm-guest-logging/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "wasm-guest-logging"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]
wit-bindgen = { version = "0.21", features = ["macros"] }

Loading
Loading