[Router] Add WASM pluggable OnRequest middleware (v0.1) - #251
[Router] Add WASM pluggable OnRequest middleware (v0.1)#251spencerr221 wants to merge 5 commits into
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c9f2aeb2f2
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| let mut linker = Linker::new(&engine); | ||
| wasmtime_wasi::p2::add_to_linker_sync(&mut linker) | ||
| .map_err(|err| WasmMiddlewareError::Engine(err.to_string()))?; |
There was a problem hiding this comment.
Enforce deadlines across synchronous WASI host calls
A component can call wasi:clocks/monotonic-clock.subscribe followed by blocking wasi:io/poll.poll; because the full synchronous WASI implementation is linked here, that host call can block a worker far beyond the configured 100 ms epoch deadline. Epoch interruption only regains control when execution returns to Wasm, so a plugin that sleeps indefinitely can occupy every worker and leave subsequent matching requests permanently rejected or queued. Use async WASI execution with an outer timeout/cancellation mechanism, or avoid exposing blocking imports, so the advertised execution deadline covers host calls too.
Useful? React with 👍 / 👎.
| if let Some(runtime) = wasm_runtime { | ||
| protected_routes = protected_routes.route_layer(axum::middleware::from_fn_with_state( | ||
| crate::wasm_middleware::WasmRouteMiddlewareState { | ||
| runtime, | ||
| max_payload_size, | ||
| }, | ||
| crate::wasm_middleware::wasm_on_request_middleware, | ||
| )); |
There was a problem hiding this comment.
Apply middleware to every configurable server route
When --wasm-middleware-route names an admin, public, worker-management, or transparent-proxy path (for example /add_worker or a backend-specific fallback path), the plugin is never invoked because this layer is attached only to protected_routes before the other routers and fallback are merged. The CLI describes the option as an arbitrary HTTP path and accepts these values without validation, so operators can believe a configured request policy is active when it is silently bypassed. Attach the layer at the combined application level, or reject routes outside the supported protected-route set.
Useful? React with 👍 / 👎.
|
Please follow the template(https://github.com/vllm-project/router/blob/main/.github/PULL_REQUEST_TEMPLATE.md) for PR's body. |
There was a problem hiding this comment.
🟡 Changes recommended
Security exposure, ineffective resource limits, route bypasses, and missing observability must be addressed.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Adds opt-in WASM OnRequest middleware with a versioned WIT contract, Wasmtime runtime, configuration wiring, example plugin, and CI coverage.
Changes:
- Implements bounded, digest-pinned WASM request processing.
- Adds Rust/Python CLI configuration and route attachment.
- Adds an example component and integration workflow.
File summaries
| File | Description |
|---|---|
wit/router-middleware.wit |
Defines the middleware ABI. |
src/wasm_middleware.rs |
Implements the host runtime and Axum adapter. |
src/server.rs |
Loads and mounts middleware. |
src/main.rs |
Adds Rust CLI options. |
src/lib.rs |
Adds Python binding fields. |
README.md |
Documents usage. |
py_src/vllm_router/router_args.py |
Adds Python CLI options. |
py_test/integration/test_wasm_middleware.py |
Tests end-to-end behavior. |
py_test/fixtures/router_manager.py |
Supports binary and WASM flags. |
py_test/fixtures/mock_worker.py |
Exposes forwarded headers. |
examples/wasm_middleware/src/lib.rs |
Implements the example guest. |
examples/wasm_middleware/README.md |
Documents the example. |
examples/wasm_middleware/Cargo.toml |
Configures the guest crate. |
examples/wasm_middleware/build.sh |
Builds the guest component. |
examples/wasm_middleware/.gitignore |
Excludes generated artifacts. |
Cargo.toml |
Adds WASM runtime dependencies. |
Cargo.lock |
Locks new dependencies. |
.github/workflows/wasm-middleware.yml |
Builds and tests middleware. |
Review details
- Files reviewed: 17/18 changed files
- Comments generated: 10
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| headers | ||
| .iter() | ||
| .map(|(name, value)| WitHeader { | ||
| name: name.as_str().to_string(), | ||
| value: value.as_bytes().to_vec(), |
| protected_routes = protected_routes.route_layer(axum::middleware::from_fn_with_state( | ||
| crate::wasm_middleware::WasmRouteMiddlewareState { | ||
| runtime, | ||
| max_payload_size, | ||
| }, |
| let mut engine_config = Config::new(); | ||
| engine_config.wasm_component_model(true); | ||
| engine_config.epoch_interruption(true); | ||
| let mut pooling = PoolingAllocationConfig::default(); |
| } | ||
|
|
||
| let route_set = config.routes.iter().cloned().collect(); | ||
| let queue_permits = Arc::new(Semaphore::new(config.queue_capacity)); |
| if status < 400 { | ||
| return Err(WasmMiddlewareError::InvalidAction(format!( | ||
| "reject status must be >= 400, got {status}" | ||
| ))); | ||
| } |
| if let Ok(header_name) = HeaderName::from_bytes(name.as_bytes()) { | ||
| headers.remove(header_name); | ||
| } |
|
|
||
| let method = request.method().as_str().to_string(); | ||
| let query = request.uri().query().unwrap_or_default().to_string(); | ||
| let request_id = request_id_from_headers(request.headers()); |
| let (mut parts, body) = request.into_parts(); | ||
| let bytes = match axum::body::to_bytes(body, state.max_payload_size).await { | ||
| Ok(bytes) => bytes, | ||
| Err(err) => { | ||
| let message = err.to_string(); | ||
| if message.contains("length limit exceeded") { | ||
| warn!( | ||
| "wasm middleware rejected oversized body (limit {} bytes)", | ||
| state.max_payload_size | ||
| ); |
| Err(err) => { | ||
| warn!("wasm middleware failed closed: {err}"); | ||
| StatusCode::INTERNAL_SERVER_ERROR.into_response() |
| self.inner.route_set.contains(path) | ||
| } | ||
|
|
||
| pub fn metrics(&self) -> WasmRuntimeMetrics { |
Introduce the RFC HTTP envelope WIT, a Wasmtime host runtime with digest pinning and fail-closed errors, CLI/Python wiring, and an example guest under examples/wasm_middleware. Default attach point is POST /v1/chat/completions, with optional --wasm-middleware-route. Signed-off-by: LiuBingyu <liubingyu4@huawei.com>
Exercise modify/reject/path-isolation against a real Router binary and mock worker, and extend CI to build the binary and run the pytest. Signed-off-by: LiuBingyu <liubingyu4@huawei.com>
Ignore wit/wast in codespell, and COPY wit/ into Dockerfile.router so image builds (Trivy) can compile wasm_middleware bindgen.
2388b0b to
d3dfefc
Compare
python -m build packs from the sdist; without wit/, wasmtime bindgen fails in the Buildkite Build Python Wheel job.
wuhang2014
left a comment
There was a problem hiding this comment.
It looks good for now except no integration test in Rust in tests/ which can be run by cargo test --test '*' with mock HTTP workers in tests/common/.
Rust wasm_middleware unit tests and the Python integration test need the example component artifact that build.sh produces.

Introduce the RFC HTTP envelope WIT, a Wasmtime host runtime with digest pinning and fail-closed errors, CLI/Python wiring, and an example guest under examples/wasm_middleware. Default attach point is POST /v1/chat/completions, with optional --wasm-middleware-route.
Summary
vllm:router-middleware@0.1.0HTTP envelope WIT and Wasmtime host runtime--wasm-middleware/--wasm-middleware-sha256/--wasm-middleware-routePOST /v1/chat/completions(fail-closed, opt-in)examples/wasm_middleware/Refs: [RFC]: WASM-Based Pluggable Middleware for vLLM Router #236
Test plan
examples/wasm_middleware/build.shcargo test --lib wasm_middleware::cargo test --bin vllm-router parses_wasm_middlewareWASM Middlewareworkflow