Files

156 lines
5.1 KiB
Rust

//! 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);