Rust Warp 框架详细介绍
Warp 是 Rust 生态里高性能、类型安全、基于 futures 的异步 Web 框架,由 Tokio 团队开发,底层基于 Tokio 运行时。
定位:轻量、函数式风格、组合式路由,不像 Axum 那样偏向宏路由,Warp 大量使用链式组合,把路由、过滤器、中间件全部抽象成
Filter。
Warp核心特点
- 完全异步:基于 Tokio 异步运行时,IO 全部非阻塞,高并发性能优秀。
- Filter(过滤器)为核心抽象 所有路由、参数提取、请求校验、中间件、响应生成,全部是
Filter,可以链式组合、复用。 - 类型安全:编译期就校验路由参数,不会出现运行时解析错误。
- 无宏:几乎不用过程宏,全部是 Rust 原生函数链式调用。
- 内置能力丰富:JSON、表单、Cookie、Header、WebSocket、文件服务、CORS、压缩、日志。
- 缺点:上手比 Axum 略陡;生态相比 Axum 小;复杂业务代码容易链式过长。
对比:
- Warp:Filter 组合式,函数式,适合写中间件、websocket、微服务;
- Axum:宏路由,更贴近传统 Web 框架,社区更大,生态更丰富。
Warp核心概念:Filter 过滤器
warp::filters::Filter 是 Warp 的灵魂。 Filter 可以:
- 匹配请求路径、方法、header;
- 提取数据(路径参数、query、body json);
- 校验请求,不满足直接返回错误响应;
- 把提取出来的值传递给处理函数。
Filter 支持组合:
and():多个过滤器同时满足,把多个结果合并;or():二选一匹配;map():对提取的值做转换;and_then():异步处理;recover():捕获错误,返回自定义响应。
简单理解:Filter 就像流水线,一层层过滤请求,把需要的数据交给 handler。
Warp最小示例
Cargo.toml
[dependencies]
warp = "0.3"
tokio = { version = "1.0", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }main.rs
use warp::{Filter, http::StatusCode};
#[tokio::main]
async fn main() {
// 定义路由过滤器
let hello_route = warp::path!("hello" / String)
.and(warp::get())
.map(|name: String| {
format!("Hello, {}!", name)
});
// 启动服务
warp::serve(hello_route)
.run(([127, 0, 0, 1], 3030))
.await;
}访问 http://127.0.0.1:3030/hello/warp,返回 Hello, warp!
Warp常用内置 Filter
1. 路径匹配
// /user/123
warp::path!("user" / u64)
// 匹配任意路径
warp::path::full()
// 匹配根路径 /
warp::path::end()2. HTTP 方法
warp::get()
warp::post()
warp::put()
warp::delete()3. Query 参数
use serde::Deserialize;
#[derive(Debug, Deserialize)]
struct QueryParam {
name: String,
age: Option<u32>,
}
let query_filter = warp::query::<QueryParam>();4. JSON 请求体
use serde::Serialize, Deserialize;
#[derive(Debug, Deserialize)]
struct User {
name: String,
}
let json_body = warp::body::json::<User>();5. Header 提取
// 提取 Authorization header
let auth_header = warp::header::header("authorization");6. 中间件(Filter 实现)
Warp 没有单独的中间件概念,中间件就是 Filter。 例如:简单日志中间件
let log_filter = warp::filters::log::log();
// 组合:先日志,再匹配路由
let routes = log_filter.and(hello_route);7. CORS 跨域
let cors = warp::cors()
.allow_any_origin()
.allow_methods(vec!["GET","POST"])
.allow_headers(vec!["Content-Type"]);
let routes = cors.and(hello_route);8. WebSocket
Warp 原生支持 WebSocket,非常好用:
let ws_route = warp::path("ws")
.and(warp::ws())
.map(|ws: warp::ws::Ws| {
ws.on_upgrade(|socket| async move {
// socket 就是 websocket 连接
})
});Warp Handler 处理函数
Filter 提取数据之后,有两种处理方式:
.map():同步函数,返回响应;.and_then():异步函数,返回Result<Response, Rejection>。
Rejection:拒绝,代表过滤器不匹配,会返回错误。
错误处理
Warp 使用 Rejection 来表示请求被拒绝,通过 .recover() 把拒绝转为 HTTP 响应。
let routes = hello_route
.recover(|rej: warp::Rejection| async move {
if rej.is_not_found() {
Ok(warp::reply::with_status("not found", StatusCode::NOT_FOUND))
} else {
Ok(warp::reply::with_status("server error", StatusCode::INTERNAL_SERVER_ERROR))
}
});响应 Reply
warp::reply 模块构造返回值:
reply::html("")reply::json(&data)reply::with_status(body, status_code)reply::file("./static/index.html")静态文件
完整业务示例(POST JSON)
use serde::{Deserialize, Serialize};
use warp::{Filter, http::StatusCode, reply::json};
#[derive(Debug, Deserialize)]
struct Req {
msg: String,
}
#[derive(Debug, Serialize)]
struct Resp {
ok: bool,
data: String,
}
#[tokio::main]
async fn main() {
let post_route = warp::path("api")
.and(warp::post())
.and(warp::body::json::<Req>())
.and_then(|req: Req| async move {
let res = Resp {
ok: true,
data: format!("收到消息:{}", req.msg),
};
Ok::<_, warp::Rejection>(json(&res))
});
warp::serve(post_route)
.run(([127,0,0,1],3030))
.await;
}Warp 的优缺点
✅ 优点
- 类型安全,编译期校验参数;
- 原生 WebSocket 支持优秀;
- Filter 组合模式非常适合复用逻辑;
- 基于 Tokio,性能很高;
- 内置 cors、压缩、日志、静态文件。
❌ 缺点
- 链式写法,复杂路由容易代码很长可读性差;
- 没有宏路由,写路由不如 Axum 直观;
- 社区活跃度不如 Axum,第三方库少;
- 错误处理(Rejection)学习成本高。
Warp适合什么场景
- 高性能 API 服务;
- WebSocket 长连接服务;
- 微服务、代理服务;
- 需要大量复用过滤器逻辑的项目。
选型建议:
- 如果你主要写 REST API,追求简单,优先 Axum;
- 大量 WebSocket、需要高度复用过滤器逻辑,可以选择 Warp。
Warp常见坑
- Filter 必须返回 Result:
and_then返回Result<Reply, Rejection>,不能直接返回值; - 过滤器组合顺序:
and()顺序会影响提取参数顺序; - Rejection 不会自动转响应,必须写
recover; - 不要在 filter 里面做重阻塞操作,全部异步;
- Warp 0.3 是稳定版本,0.4 还在开发。
Warp 路由与中间件机制
Warp 没有传统意义上分开的「路由模块」和「中间件模块」,全部基于 Filter(过滤器)抽象实现。
核心思想:路由匹配、参数提取、鉴权、日志、跨域,本质都是 Filter,通过
and/or组合拼接出完整处理链路。
路由机制
Warp 的路由本质就是Filter 的组合。
- 一个 Filter 代表一条匹配规则;
and():全部条件同时满足,串联执行,把提取的数据向下传递;or():多选一,满足任意一条分支;- 路由是链式组合,不是注解宏。
1. 路径匹配 Filter
// 静态路径
warp::path("api")
// 路径参数 /user/123
warp::path!("user" / u64)
// 路径结束,匹配根 /
warp::path::end()
// 获取完整请求路径
warp::path::full()注意:
path!("a"/b)是宏,会生成 Filter,编译期就确定类型,路径参数的类型错误编译直接报错。
2. HTTP 方法过滤
warp::get()
warp::post()
warp::put()
warp::delete()必须和 path 用 and 组合:
let route = warp::path("hello")
.and(warp::get())
.map(|| "hello world");3. 路由分支:or 多路路由
or() 实现多路由分支,匹配成功就走该分支,不匹配继续尝试下一个。
let route1 = warp::path("a").and(warp::get()).map(|| "A");
let route2 = warp::path("b").and(warp::get()).map(|| "B");
// 匹配 /a 或者 /b
let routes = route1.or(route2);⚠️ 坑:
or两边 Filter 输出类型必须一致,否则编译报错。
4. 路由分组、模块化
把一组路由封装成函数返回 Filter,实现路由拆分。
// 用户模块路由
fn user_routes() -> impl Filter<Extract = (String,), Error = warp::Rejection> {
warp::path("user")
.and(warp::get())
.and(warp::path!("id" / u64))
.map(|id| format!("user id: {}", id))
}
// 主路由
let routes = user_routes();5. 路由优先级
Warp 是顺序匹配,or 从左往右尝试;
长路径要写在前面,短路径放后面,否则会被短路径优先匹配吃掉。
❌错误示例:
// 错误!/user/info 会被 /user 匹配
let r1 = warp::path("user").map(|| "user");
let r2 = warp::path!("user"/"info").map(|| "info");
let routes = r1.or(r2);✅正确:把更具体的路由放左边
let routes = r2.or(r1);6. 404 处理
Warp 没有命中任何路由时,会产生 Rejection,通过 .recover() 捕获返回 404。
let routes = all_routes.recover(|rej| async move {
if rej.is_not_found() {
Ok(warp::reply::with_status("404 not found", warp::http::StatusCode::NOT_FOUND))
} else {
Ok(warp::reply::with_status("error", warp::http::StatusCode::INTERNAL_SERVER_ERROR))
}
});中间件机制
Warp 没有单独的中间件 trait,中间件 = Filter。 Filter 可以做:
- 请求预处理(日志、鉴权、校验header)
- 修改请求上下文
- 直接拒绝请求(返回 Rejection,终止链路)
- 后置处理(响应修改,压缩,CORS)
Filter 的两种角色
- 前置过滤器(中间件):在业务路由之前执行;
- 业务路由过滤器:匹配路径、提取参数,处理业务逻辑。
组合顺序很关键
// 顺序:先执行 log_filter,再执行业务路由
let routes = log_filter.and(business_route);
and左边先执行,右边后执行。
内置中间件 Filter
- 日志中间件
let log = warp::filters::log::log();
let routes = log.and(route);- CORS跨域中间件
let cors = warp::cors()
.allow_any_origin()
.allow_methods(vec!["GET", "POST"]);
let routes = cors.and(route);- 压缩中间件 gzip
let compress = warp::filters::compression::gzip();
let routes = compress.and(route);自定义中间件 Filter(鉴权示例)
需求:校验 Authorization 请求头,没有则拒绝请求。
use warp::{Filter, Rejection};
// 自定义鉴权中间件 Filter
fn auth_middleware() -> impl Filter<Extract = (String,), Error = Rejection> {
warp::header::header("authorization")
.and_then(|token: String| async move {
// 校验token逻辑
if token.starts_with("Bearer ") {
Ok(token)
} else {
Err(warp::reject::custom(AuthError))
}
})
}
// 自定义拒绝类型
#[derive(Debug)]
struct AuthError;
// 路由使用:先鉴权,再执行业务
let protected_route = auth_middleware()
.and(warp::path("protected"))
.and(warp::get())
.map(|token| format!("authorized: {}", token));要点:
- 校验失败,调用
warp::reject::custom(xxx),抛出 Rejection,直接终止整个 Filter 链路,不会继续往下走业务逻辑;- 成功则返回数据(token),传递给后面的 handler;
- 最后用
.recover()把自定义 Rejection 转为 HTTP 响应。
中间件的两种写法
- 全局中间件:
global_filter.and(routes),所有路由全部经过; - 局部中间件:只作用于某一组路由,只给部分接口使用。
// 局部:只有 /api 下面接口需要鉴权
let api_route = auth_middleware().and(warp::path("api").and(...));
// 公开路由不需要鉴权
let public_route = warp::path("public").and(...);
let all = api_route.or(public_route);后置处理(修改响应)
Filter 不仅处理请求,也可以处理返回响应。
// 给所有响应添加响应头
let add_header = warp::reply::with_header("X-Server", "warp-demo");
let route = warp::path("test").map(|| "ok").map(add_header);三、Filter 执行链路完整流程
客户端请求
↓
全局中间件 Filter(日志、cors)
↓
路由匹配 Filter(path + method)
↓
局部中间件 Filter(鉴权)
↓
提取参数:query / json body / header
↓
and_then / map 业务handler处理
↓
生成Reply响应
↓
后置处理(压缩、修改响应头)
↓
返回客户端如果任意一步 filter reject(拒绝),链路直接中断,进入 recover 错误处理。
关键概念对比:Filter vs Axum 中间件
| 项目 | Warp | Axum |
|---|---|---|
| 实现方式 | Filter组合,无专门中间件trait | Tower Layer + Service,独立中间件trait |
| 路由 | 链式Filter组合,or分支 | 宏路由 #[get("/xxx")] |
| 错误 | Rejection拒绝机制,需要recover转换响应 | Result返回,错误可以用Layer处理 |
| 复用 | Filter可以直接复用,组合很灵活 | Layer可以全局/局部挂载 |
常见坑
or两边返回类型必须一致,否则编译报错;- 路由顺序,更精确的路由放左边,否则被宽泛路由覆盖;
- 自定义中间件 reject 之后,必须写 recover 处理自定义 Rejection,否则返回默认 500;
- Filter 是编译期类型,每个 filter 输出类型都要匹配;
- 不要在 Filter 里面做阻塞IO,全部使用异步。
完整小示例
完整小示例:全局日志 + 局部鉴权 + 多路由
use warp::{Filter, Rejection, http::StatusCode, reply};
#[derive(Debug)]
struct AuthFail;
fn auth() -> impl Filter<Extract = (String,), Error = Rejection> {
warp::header::header("authorization")
.and_then(|t: String| async move {
if t == "Bearer secret" {
Ok(t)
} else {
Err(warp::reject::custom(AuthFail))
}
})
}
#[tokio::main]
async fn main() {
// 全局中间件:日志
let log = warp::filters::log::log();
// 公开路由
let public = warp::path("public")
.and(warp::get())
.map(|| "public api");
// 需要鉴权的路由
let protected = auth()
.and(warp::path("protected"))
.and(warp::get())
.map(|token| format!("protected, token:{}", token));
// 合并路由
let routes = public.or(protected);
// 全局中间件套在最外层
let all = log.and(routes)
.recover(|rej| async move {
if rej.is_custom::<AuthFail>() {
Ok(reply::with_status("unauthorized", StatusCode::UNAUTHORIZED))
} else if rej.is_not_found() {
Ok(reply::with_status("404", StatusCode::NOT_FOUND))
} else {
Ok(reply::with_status("server error", StatusCode::INTERNAL_SERVER_ERROR))
}
});
warp::serve(all).run(([127,0,0,1],3030)).await;
}