Merge pull request #59 from svc-design/codex/implement-nginx-wasm_module-with-rust-proxy-wasm

Add Askai limiter Proxy-Wasm module
This commit is contained in:
shenlan 2025-08-03 23:35:45 +08:00 committed by GitHub
commit 21b284fcad
4 changed files with 138 additions and 0 deletions

View File

@ -0,0 +1,24 @@
load_module modules/ngx_http_wasm_module.so;
http {
wasm {
module limiter /etc/nginx/wasm/askai_limiter.wasm;
}
server {
listen 443 ssl;
server_name cn-homepage.svc.plus;
ssl_certificate /etc/ssl/svc.plus.pem;
ssl_certificate_key /etc/ssl/svc.plus.rsa.key;
location /api/askai {
wasm_call limiter;
proxy_pass http://127.0.0.1:8080/api/askai;
}
location / {
try_files $uri $uri/ /index.html;
}
}
}

View File

@ -0,0 +1,10 @@
[package]
name = "askai_limiter"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
proxy-wasm = "0.2"

View File

@ -0,0 +1,50 @@
# Askai Limiter Proxy-Wasm Module
This module provides a simple API rate limiter for Nginx using the experimental
`ngx_http_wasm_module` and the [proxy-wasm-rust-sdk](https://github.com/proxy-wasm/proxy-wasm-rust-sdk).
It enforces a global daily limit of **200** requests per API endpoint.
## Build
```bash
rustup target add wasm32-wasip1
cargo build --release --target wasm32-wasip1
```
The compiled module will be located at
`target/wasm32-wasip1/release/askai_limiter.wasm`.
## Nginx Configuration
Example snippet that loads the compiled module and applies it to the
`/api/askai` route:
```nginx
load_module modules/ngx_http_wasm_module.so;
http {
wasm {
module limiter /etc/nginx/wasm/askai_limiter.wasm;
}
server {
listen 443 ssl;
server_name cn-homepage.svc.plus;
ssl_certificate /etc/ssl/svc.plus.pem;
ssl_certificate_key /etc/ssl/svc.plus.rsa.key;
location /api/askai {
wasm_call limiter;
proxy_pass http://127.0.0.1:8080/api/askai;
}
location / {
try_files $uri $uri/ /index.html;
}
}
}
```
Requests beyond the first 200 in a single day will return HTTP 429 with the
body `{"error":"API daily limit reached"}`.

View File

@ -0,0 +1,54 @@
use proxy_wasm::traits::*;
use proxy_wasm::types::*;
use std::time::{SystemTime, UNIX_EPOCH};
struct AskaiLimiter;
impl Context for AskaiLimiter {}
impl HttpContext for AskaiLimiter {
fn on_http_request_headers(&mut self, _num_headers: usize, _end_of_stream: bool) -> Action {
// Use the day since UNIX epoch as the key
let today = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs()
/ 86_400;
let key = format!("askai:{}", today);
// Read the current count from shared data
let (data, _cas) = self.get_shared_data(&key);
let mut count = data
.and_then(|d| String::from_utf8(d).ok())
.and_then(|s| s.parse::<u32>().ok())
.unwrap_or(0);
if count >= 200 {
self.send_http_response(
429,
vec![("Content-Type", "application/json")],
Some(b"{\"error\":\"API daily limit reached\"}"),
);
return Action::Pause;
}
// Increment and store the updated count
count += 1;
let _ = self.set_shared_data(&key, Some(count.to_string().as_bytes()), None);
Action::Continue
}
}
impl RootContext for AskaiLimiter {
fn on_configure(&mut self, _configuration_size: usize) -> bool {
true
}
fn create_http_context(&self, _context_id: u32) -> Option<Box<dyn HttpContext>> {
Some(Box::new(AskaiLimiter))
}
}
proxy_wasm::main! {{
proxy_wasm::set_http_context(|_, _| Box::new(AskaiLimiter));
}}