-
Notifications
You must be signed in to change notification settings - Fork 3.3k
[router] add WASM support for middleware #12471
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
tonyluj
wants to merge
32
commits into
sgl-project:main
Choose a base branch
from
openanolis:tonyluj/sgl-router-wasm-latest
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+3,091
−1
Open
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 1e27394
[router] wasm: add wasm runtime and config
tonyluj 8dc48e7
[router] wasm: runtime add thread pool
tonyluj cd0ac10
[router] wasm: using wasm module manager as top level executor
tonyluj c50c0fb
[router] wasm: add basic metrics
tonyluj 6bf8e9e
[router] wasm: add runtime tests
tonyluj 10a7c16
[router] wasm: add config unit tests
tonyluj e91d3d8
[router] wasm: code format
tonyluj 799decb
[router] wasm: drain all task when drop
tonyluj 72828df
[router] wasm: fix timeout when drop
tonyluj 0bbd0da
[router] wasm: code format
tonyluj 81d0d10
[router] wasm: add middleware attach type
tonyluj 0ec640f
[router] wasm: add errors
tonyluj 380a9cb
[router] wasm: code format
tonyluj 5257cf1
[router] wasm: add api to manage wasm modules
tonyluj 8b3f82f
[router] wasm: introduce wit interface
tonyluj 5533ab8
[router] wasm: add wit executor
tonyluj 5bb1cc7
[router] wasm: add middleware logic
tonyluj affdfe7
[router] wasm: fix conflicts
tonyluj 50c1806
[router] wasm: remote unused executors
tonyluj 447ec8f
[router] wasm: add docs and examples
tonyluj f06aade
[router] wasm: add myself to wasm CODEOWNERS
tonyluj c5accc3
[router] wasm: code format
tonyluj a7838a4
Merge branch 'main' into tonyluj/sgl-router-wasm-latest
tonyluj c197046
Merge branch 'main' into tonyluj/sgl-router-wasm-latest
tonyluj 158dfad
[router] wasm: move functions to wasm module to make middleware clean
tonyluj 0fa7995
[router] wasm: update examples for more safe access of RATE_LIMIT_SATE
tonyluj 053718f
[router] wasm: replace unwarp with error handling
tonyluj de5be86
[router] wasm: remove unwrap in middleware
tonyluj 338c44f
[router] wasm: some minor improvements
tonyluj e0453e7
[router] wasm: simplify auth logic
tonyluj 4b28fb3
[router] wasm: remove unused code
tonyluj File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | ||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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. | ||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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"] } | ||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | ||
| - 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 | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | ||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | ||
| } | ||
tonyluj marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| // 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); | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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"] } | ||
|
|
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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