chore: vendor sglang v0.5.10 snapshot
This commit is contained in:
27
third_party/sglang/sgl-model-gateway/examples/wasm/.gitignore
vendored
Normal file
27
third_party/sglang/sgl-model-gateway/examples/wasm/.gitignore
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
# 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
|
||||
102
third_party/sglang/sgl-model-gateway/examples/wasm/README.md
vendored
Normal file
102
third_party/sglang/sgl-model-gateway/examples/wasm/README.md
vendored
Normal file
@@ -0,0 +1,102 @@
|
||||
# WASM Guest Examples for sgl-model-gateway
|
||||
|
||||
This directory contains example WASM middleware components demonstrating how to implement custom middleware for sgl-model-gateway using the WebAssembly Component Model.
|
||||
|
||||
## 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-model-gateway 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.
|
||||
10
third_party/sglang/sgl-model-gateway/examples/wasm/wasm-guest-auth/Cargo.toml
vendored
Normal file
10
third_party/sglang/sgl-model-gateway/examples/wasm/wasm-guest-auth/Cargo.toml
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "wasm-guest-auth"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
wit-bindgen = { version = "0.21", features = ["macros"] }
|
||||
62
third_party/sglang/sgl-model-gateway/examples/wasm/wasm-guest-auth/README.md
vendored
Normal file
62
third_party/sglang/sgl-model-gateway/examples/wasm/wasm-guest-auth/README.md
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
# WASM Auth Example for sgl-model-gateway
|
||||
|
||||
This example demonstrates API key authentication middleware for sgl-model-gateway using the WebAssembly Component Model.
|
||||
|
||||
## Overview
|
||||
|
||||
This middleware validates API keys for requests to `/api` and `/v1` paths:
|
||||
|
||||
- Supports `Authorization: Bearer <key>` header
|
||||
- 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
|
||||
77
third_party/sglang/sgl-model-gateway/examples/wasm/wasm-guest-auth/build.sh
vendored
Executable file
77
third_party/sglang/sgl-model-gateway/examples/wasm/wasm-guest-auth/build.sh
vendored
Executable file
@@ -0,0 +1,77 @@
|
||||
#!/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
|
||||
70
third_party/sglang/sgl-model-gateway/examples/wasm/wasm-guest-auth/src/lib.rs
vendored
Normal file
70
third_party/sglang/sgl-model-gateway/examples/wasm/wasm-guest-auth/src/lib.rs
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
//! WASM Guest Auth Example for sgl-model-gateway
|
||||
//!
|
||||
//! This example demonstrates API key authentication middleware
|
||||
//! for sgl-model-gateway using the WebAssembly Component Model.
|
||||
//!
|
||||
//! Features:
|
||||
//! - API Key authentication
|
||||
|
||||
wit_bindgen::generate!({
|
||||
path: "../../../src/wasm/interface",
|
||||
world: "sgl-model-gateway",
|
||||
});
|
||||
|
||||
use exports::sgl::model_gateway::{
|
||||
middleware_on_request::Guest as OnRequestGuest,
|
||||
middleware_on_response::Guest as OnResponseGuest,
|
||||
};
|
||||
use sgl::model_gateway::middleware_types::{Action, Request, Response};
|
||||
|
||||
/// 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::model_gateway::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 api_key = find_header_value(&req.headers, "authorization")
|
||||
.and_then(|h| {
|
||||
h.strip_prefix("Bearer ")
|
||||
.or_else(|| h.strip_prefix("ApiKey "))
|
||||
.map(|s| s.to_string())
|
||||
})
|
||||
.or_else(|| find_header_value(&req.headers, "x-api-key"));
|
||||
|
||||
// Reject if API key is missing or invalid
|
||||
if api_key.as_deref() != Some(EXPECTED_API_KEY) {
|
||||
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);
|
||||
10
third_party/sglang/sgl-model-gateway/examples/wasm/wasm-guest-logging/Cargo.toml
vendored
Normal file
10
third_party/sglang/sgl-model-gateway/examples/wasm/wasm-guest-logging/Cargo.toml
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "wasm-guest-logging"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
wit-bindgen = { version = "0.21", features = ["macros"] }
|
||||
53
third_party/sglang/sgl-model-gateway/examples/wasm/wasm-guest-logging/README.md
vendored
Normal file
53
third_party/sglang/sgl-model-gateway/examples/wasm/wasm-guest-logging/README.md
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
# WASM Logging Example for sgl-model-gateway
|
||||
|
||||
This example demonstrates logging and tracing middleware for sgl-model-gateway using the WebAssembly Component Model.
|
||||
|
||||
## Overview
|
||||
|
||||
This middleware provides:
|
||||
|
||||
- **Request Tracking** - Adds tracking headers (`x-request-id`, `x-wasm-processed`, `x-processed-at`, `x-api-route`)
|
||||
- **Status Code Conversion** - Converts `500` errors to `503`
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Build and Deploy
|
||||
|
||||
```bash
|
||||
# Build
|
||||
cd examples/wasm-guest-logging
|
||||
./build.sh
|
||||
|
||||
# Deploy (replace file_path with actual path)
|
||||
curl -X POST http://localhost:3000/wasm \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"modules": [{
|
||||
"name": "logging-middleware",
|
||||
"file_path": "/absolute/path/to/wasm_guest_logging.component.wasm",
|
||||
"module_type": "Middleware",
|
||||
"attach_points": [{"Middleware": "OnRequest"}, {"Middleware": "OnResponse"}]
|
||||
}]
|
||||
}'
|
||||
```
|
||||
|
||||
### Customization
|
||||
|
||||
Modify `on_request` or `on_response` functions in `src/lib.rs` to add custom tracking headers or status code conversions.
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
# Check tracking headers
|
||||
curl -v http://localhost:3000/v1/models 2>&1 | \
|
||||
grep -E "(x-request-id|x-wasm-processed|x-processed-at)"
|
||||
|
||||
# Test status code conversion (requires endpoint returning 500)
|
||||
curl -v http://localhost:3000/some-endpoint 2>&1 | grep -E "(< HTTP|500|503)"
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- Verify module attached to both `OnRequest` and `OnResponse` phases
|
||||
- Check router logs for execution errors
|
||||
- Ensure module built successfully
|
||||
77
third_party/sglang/sgl-model-gateway/examples/wasm/wasm-guest-logging/build.sh
vendored
Executable file
77
third_party/sglang/sgl-model-gateway/examples/wasm/wasm-guest-logging/build.sh
vendored
Executable file
@@ -0,0 +1,77 @@
|
||||
#!/bin/bash
|
||||
# Build script for WASM guest logging example
|
||||
# This script simplifies the build process for the WASM middleware component
|
||||
|
||||
set -e
|
||||
|
||||
echo "Building WASM guest logging 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-logging 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_logging.wasm"
|
||||
WASM_COMPONENT="target/wasm32-wasip2/release/wasm_guest_logging.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
|
||||
88
third_party/sglang/sgl-model-gateway/examples/wasm/wasm-guest-logging/src/lib.rs
vendored
Normal file
88
third_party/sglang/sgl-model-gateway/examples/wasm/wasm-guest-logging/src/lib.rs
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
//! WASM Guest Logging Example for sgl-model-gateway
|
||||
//!
|
||||
//! This example demonstrates logging and tracing middleware
|
||||
//! for sgl-model-gateway using the WebAssembly Component Model.
|
||||
//!
|
||||
//! Features:
|
||||
//! - Request tracking and tracing headers
|
||||
//! - Response status code conversion
|
||||
|
||||
wit_bindgen::generate!({
|
||||
path: "../../../src/wasm/interface",
|
||||
world: "sgl-model-gateway",
|
||||
});
|
||||
|
||||
use exports::sgl::model_gateway::{
|
||||
middleware_on_request::Guest as OnRequestGuest,
|
||||
middleware_on_response::Guest as OnResponseGuest,
|
||||
};
|
||||
use sgl::model_gateway::middleware_types::{Action, Header, ModifyAction, Request, Response};
|
||||
|
||||
/// Main middleware implementation
|
||||
struct Middleware;
|
||||
|
||||
// Helper function to create header
|
||||
fn create_header(name: &str, value: &str) -> Header {
|
||||
Header {
|
||||
name: name.to_string(),
|
||||
value: value.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
// Implement on-request interface
|
||||
impl OnRequestGuest for Middleware {
|
||||
fn on_request(req: Request) -> Action {
|
||||
let mut modify_action = ModifyAction {
|
||||
status: None,
|
||||
headers_set: vec![],
|
||||
headers_add: vec![],
|
||||
headers_remove: vec![],
|
||||
body_replace: None,
|
||||
};
|
||||
|
||||
// Request Logging and Tracing
|
||||
// Add tracing headers with request ID
|
||||
modify_action
|
||||
.headers_add
|
||||
.push(create_header("x-request-id", &req.request_id));
|
||||
modify_action
|
||||
.headers_add
|
||||
.push(create_header("x-wasm-processed", "true"));
|
||||
modify_action.headers_add.push(create_header(
|
||||
"x-processed-at",
|
||||
&req.now_epoch_ms.to_string(),
|
||||
));
|
||||
|
||||
// Add custom header for API requests
|
||||
if req.path.starts_with("/api") || req.path.starts_with("/v1") {
|
||||
modify_action
|
||||
.headers_add
|
||||
.push(create_header("x-api-route", "true"));
|
||||
}
|
||||
|
||||
Action::Modify(modify_action)
|
||||
}
|
||||
}
|
||||
|
||||
// Implement on-response interface
|
||||
impl OnResponseGuest for Middleware {
|
||||
fn on_response(resp: Response) -> Action {
|
||||
// Status code conversion: Convert 500 to 503 for better client handling
|
||||
if resp.status == 500 {
|
||||
let modify_action = ModifyAction {
|
||||
status: Some(503),
|
||||
headers_set: vec![],
|
||||
headers_add: vec![],
|
||||
headers_remove: vec![],
|
||||
body_replace: None,
|
||||
};
|
||||
Action::Modify(modify_action)
|
||||
} else {
|
||||
// No modification needed
|
||||
Action::Continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Export the component
|
||||
export!(Middleware);
|
||||
10
third_party/sglang/sgl-model-gateway/examples/wasm/wasm-guest-ratelimit/Cargo.toml
vendored
Normal file
10
third_party/sglang/sgl-model-gateway/examples/wasm/wasm-guest-ratelimit/Cargo.toml
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "wasm-guest-ratelimit"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
wit-bindgen = { version = "0.21", features = ["macros"] }
|
||||
68
third_party/sglang/sgl-model-gateway/examples/wasm/wasm-guest-ratelimit/README.md
vendored
Normal file
68
third_party/sglang/sgl-model-gateway/examples/wasm/wasm-guest-ratelimit/README.md
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
# WASM Rate Limit Example for sgl-model-gateway
|
||||
|
||||
This example demonstrates rate limiting middleware for sgl-model-gateway using the WebAssembly Component Model.
|
||||
|
||||
## Overview
|
||||
|
||||
This middleware provides rate limiting:
|
||||
|
||||
- **Default**: 60 requests per minute per identifier
|
||||
- **Identifier Priority**: API Key > IP Address > Request ID
|
||||
- **Response**: Returns `429 Too Many Requests` when limit exceeded
|
||||
|
||||
**Important**: This is a simplified demonstration. Since WASM components are stateless, each worker thread maintains its own counter. For production, implement rate limiting at the router/host level with shared state.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Build and Deploy
|
||||
|
||||
```bash
|
||||
# Build
|
||||
cd examples/wasm-guest-ratelimit
|
||||
./build.sh
|
||||
|
||||
# Deploy (replace file_path with actual path)
|
||||
curl -X POST http://localhost:3000/wasm \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"modules": [{
|
||||
"name": "ratelimit-middleware",
|
||||
"file_path": "/absolute/path/to/wasm_guest_ratelimit.component.wasm",
|
||||
"module_type": "Middleware",
|
||||
"attach_points": [{"Middleware": "OnRequest"}]
|
||||
}]
|
||||
}'
|
||||
```
|
||||
|
||||
### Customization
|
||||
|
||||
Modify constants in `src/lib.rs`:
|
||||
|
||||
```rust
|
||||
const RATE_LIMIT_REQUESTS: u64 = 100; // requests per window
|
||||
const RATE_LIMIT_WINDOW_MS: u64 = 60_000; // time window in ms
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
# Send multiple requests (first 60 succeed, then 429)
|
||||
for i in {1..65}; do
|
||||
curl -s -o /dev/null -w "%{http_code}\n" \
|
||||
http://localhost:3000/v1/models \
|
||||
-H "Authorization: Bearer secret-api-key-12345"
|
||||
done
|
||||
```
|
||||
|
||||
## Limitations
|
||||
|
||||
- Per-instance state (not shared across workers)
|
||||
- No cross-process state sharing
|
||||
- Memory growth with unique identifiers
|
||||
- State lost on instance restart
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- Verify module attached to `OnRequest` phase
|
||||
- Check identifier extraction logic matches request format
|
||||
- Note: Each WASM worker has separate counter
|
||||
77
third_party/sglang/sgl-model-gateway/examples/wasm/wasm-guest-ratelimit/build.sh
vendored
Executable file
77
third_party/sglang/sgl-model-gateway/examples/wasm/wasm-guest-ratelimit/build.sh
vendored
Executable file
@@ -0,0 +1,77 @@
|
||||
#!/bin/bash
|
||||
# Build script for WASM guest rate limit example
|
||||
# This script simplifies the build process for the WASM middleware component
|
||||
|
||||
set -e
|
||||
|
||||
echo "Building WASM guest rate limit 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-ratelimit 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_ratelimit.wasm"
|
||||
WASM_COMPONENT="target/wasm32-wasip2/release/wasm_guest_ratelimit.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
|
||||
155
third_party/sglang/sgl-model-gateway/examples/wasm/wasm-guest-ratelimit/src/lib.rs
vendored
Normal file
155
third_party/sglang/sgl-model-gateway/examples/wasm/wasm-guest-ratelimit/src/lib.rs
vendored
Normal file
@@ -0,0 +1,155 @@
|
||||
//! WASM Guest Rate Limit Example for sgl-model-gateway
|
||||
//!
|
||||
//! This example demonstrates rate limiting middleware
|
||||
//! for sgl-model-gateway using the WebAssembly Component Model.
|
||||
//!
|
||||
//! Features:
|
||||
//! - Rate limiting based on API Key or IP address
|
||||
//! - Fixed time window (e.g., 60 requests per minute)
|
||||
//! - Returns 429 Too Many Requests when limit exceeded
|
||||
//!
|
||||
//! Note: This is a simplified implementation. Since WASM components are stateless,
|
||||
//! each instance maintains its own counters. For production use, consider
|
||||
//! implementing rate limiting at the host/router level with shared state.
|
||||
|
||||
wit_bindgen::generate!({
|
||||
path: "../../../src/wasm/interface",
|
||||
world: "sgl-model-gateway",
|
||||
});
|
||||
|
||||
use std::cell::RefCell;
|
||||
|
||||
use exports::sgl::model_gateway::{
|
||||
middleware_on_request::Guest as OnRequestGuest,
|
||||
middleware_on_response::Guest as OnResponseGuest,
|
||||
};
|
||||
use sgl::model_gateway::middleware_types::{Action, Request, Response};
|
||||
|
||||
/// Main middleware implementation
|
||||
struct Middleware;
|
||||
|
||||
// Rate limit configuration
|
||||
const RATE_LIMIT_REQUESTS: u64 = 60; // Maximum requests per window
|
||||
const RATE_LIMIT_WINDOW_MS: u64 = 60_000; // Time window in milliseconds (1 minute)
|
||||
|
||||
// Simple in-memory counter (per WASM instance)
|
||||
// In a real implementation, this would be shared across all instances
|
||||
// This is a simplified example for demonstration purposes
|
||||
struct RateLimitState {
|
||||
requests: Vec<(String, u64)>, // (identifier, timestamp_ms)
|
||||
}
|
||||
|
||||
impl RateLimitState {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
requests: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up old entries outside the time window
|
||||
fn cleanup(&mut self, current_time_ms: u64) {
|
||||
let cutoff = current_time_ms.saturating_sub(RATE_LIMIT_WINDOW_MS);
|
||||
self.requests.retain(|(_, timestamp)| *timestamp > cutoff);
|
||||
}
|
||||
|
||||
// Check if identifier has exceeded rate limit
|
||||
fn check_limit(&mut self, identifier: &str, current_time_ms: u64) -> bool {
|
||||
self.cleanup(current_time_ms);
|
||||
|
||||
// Count requests in current window for this identifier
|
||||
let count = self
|
||||
.requests
|
||||
.iter()
|
||||
.filter(|(id, timestamp)| {
|
||||
id == identifier
|
||||
&& *timestamp > current_time_ms.saturating_sub(RATE_LIMIT_WINDOW_MS)
|
||||
})
|
||||
.count() as u64;
|
||||
|
||||
if count >= RATE_LIMIT_REQUESTS {
|
||||
return false; // Limit exceeded
|
||||
}
|
||||
|
||||
// Add new request
|
||||
self.requests
|
||||
.push((identifier.to_string(), current_time_ms));
|
||||
true // Within limit
|
||||
}
|
||||
}
|
||||
|
||||
// Thread-local state (per WASM instance thread)
|
||||
// Using thread_local! is safer than static mut as it avoids unsafe blocks
|
||||
// and provides separate state for each thread automatically
|
||||
thread_local! {
|
||||
static RATE_LIMIT_STATE: RefCell<RateLimitState> = RefCell::new(RateLimitState::new());
|
||||
}
|
||||
|
||||
fn get_identifier(req: &Request) -> String {
|
||||
// Helper function to find header value
|
||||
let find_header_value =
|
||||
|headers: &[sgl::model_gateway::middleware_types::Header], name: &str| -> Option<String> {
|
||||
headers
|
||||
.iter()
|
||||
.find(|h| h.name.eq_ignore_ascii_case(name))
|
||||
.map(|h| h.value.clone())
|
||||
};
|
||||
|
||||
// Prefer API Key as identifier (more stable than IP)
|
||||
if let Some(auth_header) = find_header_value(&req.headers, "authorization") {
|
||||
if auth_header.starts_with("Bearer ") {
|
||||
return format!("api_key:{}", &auth_header[7..]);
|
||||
} else if auth_header.starts_with("ApiKey ") {
|
||||
return format!("api_key:{}", &auth_header[7..]);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(api_key) = find_header_value(&req.headers, "x-api-key") {
|
||||
return format!("api_key:{}", api_key);
|
||||
}
|
||||
|
||||
// Fall back to IP address from forwarded headers
|
||||
if let Some(forwarded_for) = find_header_value(&req.headers, "x-forwarded-for") {
|
||||
// Take first IP from comma-separated list
|
||||
let ip = forwarded_for.split(',').next().unwrap_or("").trim();
|
||||
if !ip.is_empty() {
|
||||
return format!("ip:{}", ip);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(real_ip) = find_header_value(&req.headers, "x-real-ip") {
|
||||
return format!("ip:{}", real_ip);
|
||||
}
|
||||
|
||||
// Last resort: use request ID (not ideal, but better than nothing)
|
||||
format!("req_id:{}", req.request_id)
|
||||
}
|
||||
|
||||
// Implement on-request interface
|
||||
impl OnRequestGuest for Middleware {
|
||||
fn on_request(req: Request) -> Action {
|
||||
let identifier = get_identifier(&req);
|
||||
let current_time_ms = req.now_epoch_ms;
|
||||
|
||||
// Access thread-local state safely without unsafe blocks
|
||||
// Each thread gets its own RateLimitState instance
|
||||
RATE_LIMIT_STATE.with(|state| {
|
||||
let mut state = state.borrow_mut();
|
||||
if !state.check_limit(&identifier, current_time_ms) {
|
||||
// Rate limit exceeded
|
||||
return Action::Reject(429);
|
||||
}
|
||||
// Within rate limit, continue processing
|
||||
Action::Continue
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Implement on-response interface (empty - not used for rate limiting)
|
||||
impl OnResponseGuest for Middleware {
|
||||
fn on_response(_resp: Response) -> Action {
|
||||
Action::Continue
|
||||
}
|
||||
}
|
||||
|
||||
// Export the component
|
||||
export!(Middleware);
|
||||
Reference in New Issue
Block a user