Skip to content

Rust Warp 框架详细介绍 ​

Warp 是 Rust 生态里高性能、类型安全、基于 futures 的异步 Web 框架,由 Tokio 团队开发,底层基于 Tokio 运行时。

定位:轻量、函数式风格、组合式路由,不像 Axum 那样偏向宏路由,Warp 大量使用链式组合,把路由、过滤器、中间件全部抽象成 Filter。

Warp核心特点 ​

  1. 完全异步:基于 Tokio 异步运行时,IO 全部非阻塞,高并发性能优秀。
  2. Filter(过滤器)为核心抽象 所有路由、参数提取、请求校验、中间件、响应生成,全部是 Filter,可以链式组合、复用。
  3. 类型安全:编译期就校验路由参数,不会出现运行时解析错误。
  4. 无宏:几乎不用过程宏,全部是 Rust 原生函数链式调用。
  5. 内置能力丰富:JSON、表单、Cookie、Header、WebSocket、文件服务、CORS、压缩、日志。
  6. 缺点:上手比 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 ​

toml
[dependencies]
warp = "0.3"
tokio = { version = "1.0", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }

main.rs ​

rust
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. 路径匹配 ​

rust
// /user/123
warp::path!("user" / u64)
// 匹配任意路径
warp::path::full()
// 匹配根路径 /
warp::path::end()

2. HTTP 方法 ​

rust
warp::get()
warp::post()
warp::put()
warp::delete()

3. Query 参数 ​

rust
use serde::Deserialize;

#[derive(Debug, Deserialize)]
struct QueryParam {
    name: String,
    age: Option<u32>,
}

let query_filter = warp::query::<QueryParam>();

4. JSON 请求体 ​

rust
use serde::Serialize, Deserialize;

#[derive(Debug, Deserialize)]
struct User {
    name: String,
}

let json_body = warp::body::json::<User>();

5. Header 提取 ​

rust
// 提取 Authorization header
let auth_header = warp::header::header("authorization");

6. 中间件(Filter 实现) ​

Warp 没有单独的中间件概念,中间件就是 Filter。 例如:简单日志中间件

rust
let log_filter = warp::filters::log::log();
// 组合:先日志,再匹配路由
let routes = log_filter.and(hello_route);

7. CORS 跨域 ​

rust
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,非常好用:

rust
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 提取数据之后,有两种处理方式:

  1. .map():同步函数,返回响应;
  2. .and_then():异步函数,返回 Result<Response, Rejection>。

Rejection:拒绝,代表过滤器不匹配,会返回错误。

错误处理 ​

Warp 使用 Rejection 来表示请求被拒绝,通过 .recover() 把拒绝转为 HTTP 响应。

rust
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) ​

rust
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 的优缺点 ​

✅ 优点

  1. 类型安全,编译期校验参数;
  2. 原生 WebSocket 支持优秀;
  3. Filter 组合模式非常适合复用逻辑;
  4. 基于 Tokio,性能很高;
  5. 内置 cors、压缩、日志、静态文件。

❌ 缺点

  1. 链式写法,复杂路由容易代码很长可读性差;
  2. 没有宏路由,写路由不如 Axum 直观;
  3. 社区活跃度不如 Axum,第三方库少;
  4. 错误处理(Rejection)学习成本高。

Warp适合什么场景 ​

  • 高性能 API 服务;
  • WebSocket 长连接服务;
  • 微服务、代理服务;
  • 需要大量复用过滤器逻辑的项目。

选型建议:

  • 如果你主要写 REST API,追求简单,优先 Axum;
  • 大量 WebSocket、需要高度复用过滤器逻辑,可以选择 Warp。

Warp常见坑 ​

  1. Filter 必须返回 Result:and_then 返回 Result<Reply, Rejection>,不能直接返回值;
  2. 过滤器组合顺序:and() 顺序会影响提取参数顺序;
  3. Rejection 不会自动转响应,必须写 recover;
  4. 不要在 filter 里面做重阻塞操作,全部异步;
  5. Warp 0.3 是稳定版本,0.4 还在开发。

Warp 路由与中间件机制 ​

Warp 没有传统意义上分开的「路由模块」和「中间件模块」,全部基于 Filter(过滤器)抽象实现。

核心思想:路由匹配、参数提取、鉴权、日志、跨域,本质都是 Filter,通过 and / or 组合拼接出完整处理链路。

路由机制 ​

Warp 的路由本质就是Filter 的组合。

  • 一个 Filter 代表一条匹配规则;
  • and():全部条件同时满足,串联执行,把提取的数据向下传递;
  • or():多选一,满足任意一条分支;
  • 路由是链式组合,不是注解宏。

1. 路径匹配 Filter ​

rust
// 静态路径
warp::path("api")

// 路径参数 /user/123
warp::path!("user" / u64)

// 路径结束,匹配根 /
warp::path::end()

// 获取完整请求路径
warp::path::full()

注意:path!("a"/b) 是宏,会生成 Filter,编译期就确定类型,路径参数的类型错误编译直接报错。

2. HTTP 方法过滤 ​

rust
warp::get()
warp::post()
warp::put()
warp::delete()

必须和 path 用 and 组合:

rust
let route = warp::path("hello")
    .and(warp::get())
    .map(|| "hello world");

3. 路由分支:or 多路路由 ​

or() 实现多路由分支,匹配成功就走该分支,不匹配继续尝试下一个。

rust
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,实现路由拆分。

rust
// 用户模块路由
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 从左往右尝试;

长路径要写在前面,短路径放后面,否则会被短路径优先匹配吃掉。

❌错误示例:

rust
// 错误!/user/info 会被 /user 匹配
let r1 = warp::path("user").map(|| "user");
let r2 = warp::path!("user"/"info").map(|| "info");
let routes = r1.or(r2);

✅正确:把更具体的路由放左边

rust
let routes = r2.or(r1);

6. 404 处理 ​

Warp 没有命中任何路由时,会产生 Rejection,通过 .recover() 捕获返回 404。

rust
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 可以做:

  1. 请求预处理(日志、鉴权、校验header)
  2. 修改请求上下文
  3. 直接拒绝请求(返回 Rejection,终止链路)
  4. 后置处理(响应修改,压缩,CORS)

Filter 的两种角色 ​

  1. 前置过滤器(中间件):在业务路由之前执行;
  2. 业务路由过滤器:匹配路径、提取参数,处理业务逻辑。

组合顺序很关键 ​

rust
// 顺序:先执行 log_filter,再执行业务路由
let routes = log_filter.and(business_route);

and 左边先执行,右边后执行。

内置中间件 Filter ​

  1. 日志中间件
rust
let log = warp::filters::log::log();
let routes = log.and(route);
  1. CORS跨域中间件
rust
let cors = warp::cors()
    .allow_any_origin()
    .allow_methods(vec!["GET", "POST"]);
let routes = cors.and(route);
  1. 压缩中间件 gzip
rust
let compress = warp::filters::compression::gzip();
let routes = compress.and(route);

自定义中间件 Filter(鉴权示例) ​

需求:校验 Authorization 请求头,没有则拒绝请求。

rust
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));

要点:

  1. 校验失败,调用 warp::reject::custom(xxx),抛出 Rejection,直接终止整个 Filter 链路,不会继续往下走业务逻辑;
  2. 成功则返回数据(token),传递给后面的 handler;
  3. 最后用 .recover() 把自定义 Rejection 转为 HTTP 响应。

中间件的两种写法 ​

  1. 全局中间件:global_filter.and(routes),所有路由全部经过;
  2. 局部中间件:只作用于某一组路由,只给部分接口使用。
rust
// 局部:只有 /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 不仅处理请求,也可以处理返回响应。

rust
// 给所有响应添加响应头
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 中间件 ​

项目WarpAxum
实现方式Filter组合,无专门中间件traitTower Layer + Service,独立中间件trait
路由链式Filter组合,or分支宏路由 #[get("/xxx")]
错误Rejection拒绝机制,需要recover转换响应Result返回,错误可以用Layer处理
复用Filter可以直接复用,组合很灵活Layer可以全局/局部挂载

常见坑 ​

  1. or 两边返回类型必须一致,否则编译报错;
  2. 路由顺序,更精确的路由放左边,否则被宽泛路由覆盖;
  3. 自定义中间件 reject 之后,必须写 recover 处理自定义 Rejection,否则返回默认 500;
  4. Filter 是编译期类型,每个 filter 输出类型都要匹配;
  5. 不要在 Filter 里面做阻塞IO,全部使用异步。

完整小示例 ​

完整小示例:全局日志 + 局部鉴权 + 多路由

rust
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;
}