warp — Rust 里把请求处理拼成 Filter 积木的 web 框架
是什么
warp 是一个 Rust 异步 web 框架,由 hyper 作者 seanmonstar 维护,核心抽象叫 Filter——把”路由匹配 / 提取参数 / 校验 header / 解析 body”通通做成可组合的小函数,用 .and()、.or() 像积木一样拼起来。日常类比:像安检通道——每一关只负责一件事(看身份证 / 过 X 光 / 验登机牌),过得了所有关的人才到柜台办手续。
你写:
let hello = warp::path!("hello" / String) .map(|name| format!("Hello, {}!", name));这一行就完成了:路由匹配 /hello/:name、提取 name: String、调用 handler 返回字符串。warp 在编译期把整条 Filter 链的输出类型推出来,handler 的参数列表是自动对上的。
底层基于 hyper(HTTP 引擎)和 tokio(async runtime),与 actix-web / axum / rocket 同处 Rust web 框架第一梯队。
为什么重要
不理解 warp,下面这些事都没法解释:
- 为什么 Rust web 框架能”不写宏、不写字符串路径”也做出类型安全路由
- 为什么同一份 Rust 后端,axum 用
Router::new().route(...)、warp 用.and().or(),路线分歧的根因在哪 - 为什么”函数式组合中间件”听起来优雅,写多了又会被编译错误劝退
- 为什么 hyper 作者要先做 warp 才做别的——它是 hyper 上层最早的”工程化包装”之一
核心要点
warp 的设计可以拆成 三件事:
-
Filter 是一个 trait:每个 Filter 要么”提取出一个值”(比如从 path 里抽出
String、从 header 抽出Bearer token),要么”拒绝请求”(rejection)。Filter 的输出是个 元组——Filter<Extract = (String,)>表示这个 Filter 给后续提供一个 String。 -
.and()把元组拼起来,.or()做”前者拒了就试后者”:a.and(b)输出(A1..An, B1..Bn)拼接的元组;a.or(b)是 fallback,不是 HTTP 短路。最后.map()或.and_then()拿到完整元组,写 handler。 -
类型推导让 handler 签名自动对上:你不用手写 “这个 handler 收 path 段 + JSON body + Authorization 头”,编译器从 Filter 链推出来。错一个,编译器就拒绝。
三件事加起来:路由 = Filter 树,中间件 = Filter 装饰,handler = Filter 链末端的 .map()。
实践案例
案例 1:最小 JSON API
use warp::Filter;
#[tokio::main]async fn main() { let hi = warp::path!("hi" / String) .map(|name| format!("hi {}", name)); warp::serve(hi).run(([127, 0, 0, 1], 3030)).await;}逐行解释:
warp::path!("hi" / String)匹配/hi/:name,把:name提取成String.map(|name| ...)拿到String,返回响应warp::serve(...).run(...)起 HTTP server
整段不用宏定义路由表,类型推导自动对上。
案例 2:组合鉴权 Filter
fn auth() -> impl Filter<Extract = (String,), Error = warp::Rejection> + Clone { warp::header::<String>("authorization") .and_then(|token: String| async move { if token.starts_with("Bearer ") { Ok(token) } else { Err(warp::reject::not_found()) } })}
let protected = warp::path!("me") .and(auth()) .map(|token: String| format!("token ok: {}", token));auth() 是一个可复用的 Filter——任何路由 .and(auth()) 就能套上鉴权。组合性来自于 Filter 是一等公民。
案例 3:WebSocket echo
let ws = warp::path("ws") .and(warp::ws()) .map(|ws: warp::ws::Ws| { ws.on_upgrade(|websocket| async move { let (tx, rx) = websocket.split(); rx.forward(tx).await.ok(); }) });warp::ws() 拿到 upgrade Filter,on_upgrade 把 HTTP 切到 WebSocket,剩下用 futures::Stream 处理。WebSocket 在 warp 里和普通路由共用同一套 Filter 抽象。
踩过的坑
-
编译错误信息巨长:Filter 链类型推导嵌套深,写错一个
.and()顺序,编译器刷几十行Filter<Extract = (...,...,...)>错误,新手容易劝退。诀窍:从最小可编译版本逐段加 Filter。 -
.or()不是 HTTP 短路:.or()只在前者 reject 时尝试后者,不是”前者返 4xx 就走后者”。把.or()当 nginxtry_files用会出错。 -
错误处理必须显式
recover():Filter 链末尾忘写.recover(handle_rejection),所有 rejection 默认变 404,让人困惑”我明明返了 401 怎么变 404”。 -
接 tower 生态需要适配层:warp 的中间件不是
tower::Service,要用 tower 的限流 / 熔断中间件得包一层 adapter——这是 axum 后来在路由层选 tower 的原因之一。
适用 vs 不适用场景
适用:
- 中小型 JSON API / WebSocket 服务,喜欢函数式风格
- 需要细粒度组合 Filter 的场景(多种鉴权 / 多种 body 类型混合路由)
- 已经在用 hyper 生态,想要薄包装
不适用:
- 团队不熟悉函数式组合 / Rust 类型推导——上手门槛比 axum 高
- 需要重度依赖 tower 中间件生态 → 用 axum
- 需要”路由表声明式集中管理” → 用 actix-web 或 rocket 的宏路由
- 复杂大型应用 + 多人协作 → axum 的
Router模式更直观
历史小故事(可跳过)
- 2018 年前后:seanmonstar 在 hyper 上层做 warp,发布到 crates.io;当时 Rust 异步还没 stable,
async/await1.39 之后大家才开始大规模写 - 2019-2020 年:warp 成为 Rust web 框架早期代表之一,与 actix-web、rocket 形成三足
- 2021 年:tokio 团队推出 axum,路由层借鉴 tower::Service 思路,与 warp 路线分化但生态高度互通(都跑 hyper)
- 如今:warp 仍活跃维护、10k star,社区典型用法是”小型服务 + 喜欢函数式”;axum 因为 tower 生态更广泛,新项目占比上升
学到什么
- Filter 抽象 = “把请求处理变成函数组合”——这是从 Haskell servant、Scala finagle 一脉相承的思路,不是 Rust 独创
- 类型推导越强,编译错误越长——这是函数式 web 框架的通病,warp / servant 都中招
- 生态选型比”哪个最优雅”重要:tower 中间件生态决定了 axum 后来居上,warp 的孤立 Filter 抽象是双刃剑
- 同一个底层(hyper),上层框架可以走完全不同的设计路线——warp 选函数式组合,axum 选 Service trait,actix-web 选 actor 模型
延伸阅读
- 官方文档:docs.rs/warp(Filter trait 一节必读)
- 例子集合:github.com/seanmonstar/warp/tree/master/examples(30+ 例子涵盖 WebSocket / TLS / 静态文件)
- 对比文章:搜 “warp vs axum rust” 能看到很多生态讨论
- axum —— tokio 团队的 web 框架,路由走 tower::Service
- actix-web —— actor 模型 web 框架,性能 benchmark 常年第一梯队
- rocket —— 宏驱动路由,强调”看着像 Flask 一样直观”
关联
- axum —— 同样基于 hyper / tokio,路由层选 tower::Service 而非 Filter
- actix-web —— 另一条路线:actor 模型 + 宏路由
- rocket —— 宏驱动 web 框架,DX 偏 Flask 风格
- hindley-milner —— warp 的类型推导背后是 Rust 编译器的 HM 系派生
- fastapi —— Python 类比物:用类型注解推导 handler 签名
反向链接
- actix-web —— Actix Web — Rust 上长期占据 TechEmpower 榜首的 web 框架
- axum —— axum — 用 Rust 类型系统当『路由参数表』的 Web 框架
- bevy —— Bevy — Rust 数据驱动 ECS 游戏引擎
- embassy —— Embassy — 嵌入式 Rust 的 async/await 运行时
- fastapi —— FastAPI — 用 Python 类型注解写 API
- fastify —— Fastify — 让 schema 替你写校验和序列化的 Node.js 框架
- fish —— fish — 装好就比 bash 加插件好用的交互 shell
- flutter-rust-bridge —— flutter-rust-bridge — Dart 调 Rust 像调本地函数
- hindley-milner —— Hindley-Milner — 编译器自己猜变量类型
- kitty —— kitty — GPU 加速终端,把分屏和图片协议焊在一个二进制里
- nushell —— nushell — 让命令之间传 Excel 表而不是传纸条
- plug —— Plug — 把 HTTP 中间件写成『conn 进 conn 出』的纯函数
- poem —— poem — 一份 impl 块同时变 HTTP API + OpenAPI 文档站的 Rust 框架
- rocket —— Rocket — 用 Rust attribute macro 把路由当函数签名写的 web 框架
- salvo —— Salvo — 把中间件和处理器统一成一个 Handler trait 的 Rust web 框架
- slim-framework —— Slim — PHP 圈最轻的 web 框架,专给小 API 用
- smoltcp —— smoltcp — 不依赖操作系统的 Rust TCP/IP 协议栈
- spin —— Spin — 用 WebAssembly 模块当 serverless handler 的开源框架
- tide —— Tide — async-std 阵营里 koa 风格的极简 Rust web 框架
- zsh —— zsh — 比 bash 更聪明的兼容派 shell