Router

什么是 Router

Router 定义了一个 HTTP 请求会被哪些中间件和 Handler 处理. 这个是 Salvo 里面最基础也是最核心的功能.

扁平式定义

我们可以用扁平式的风格定义路由:

Router::with_path("writers").get(list_writers).post(create_writer);
Router::with_path("writers/<id>").get(show_writer).patch(edit_writer).delete(delete_writer);
Router::with_path("writers/<id>/articles").get(list_writer_articles);

树状式定义

我们也可以把路由定义成树状, 这也是推荐的定义方式:

Router::with_path("writers").get(list_writers).post(create_writer).push(
    Router::with_path("<id>")
        .get(show_writer)
        .patch(edit_writer)
        .delete(delete_writer)
        .push(Router::with_path("articles").get(list_writer_articles)),
);

从路由中获取参数

在上面的代码中, <id> 定义了一个参数. 我们可以通过 Request 实例获取到它的值:

#[fn_handler]
async fn show_writer(req: &mut Request) {
    let id = req.get_param::<i64>("id").unwrap();
}

<id>匹配了路径中的一个片段, 正常情况下文章的 id 只是一个数字, 这是我们可以使用正则表达式限制 id 的匹配规则, r"<id:/\d+/>".

对于这种数字类型, 还有一种更简单的方法是使用 <id:num>, 具体写法为:

  • <id:num>, 匹配任意多个数字字符;
  • <id:num[10]>, 只匹配固定特定数量的数字字符,这里的 10 代表匹配仅仅匹配 10 个数字字符;
  • <id:num(..10)>, 代表匹配 1 到 9 个数字字符;
  • <id:num(3..10)>, 代表匹配 3 到 9 个数字字符;
  • <id:num(..=10)>, 代表匹配 1 到 10 个数字字符;
  • <id:num(3..=10)>, 代表匹配 3 到 10 个数字字符;
  • <id:num(10..)>, 代表匹配至少 10 个数字字符.

还可以通过 <*> 或者 <**> 匹配所有剩余的路径片段. 为了代码易读性性强些, 也可以添加适合的名字, 让路径语义更清晰, 比如: <**file_path>.

允许组合使用多个表达式匹配同一个路径片段, 比如 /articles/article_<id:num>/.

添加中间件

可以通过路由上的 hoop 函数添加中间件:

Router::new()
    .hoop(check_authed)
    .path("writers")
    .get(list_writers)
    .post(create_writer)
    .push(
        Router::with_path("<id>")
            .get(show_writer)
            .patch(edit_writer)
            .delete(delete_writer)
            .push(Router::with_path("articles").get(list_writer_articles)),
    );

在这个例子, 根路由使用 check_authed 检查当前用户是否已经登录了. 所有子孙路由都会受此中间件影响.

如果用户只是浏览 writer 的信息和文章, 我们更希望他们无需登录即可浏览. 我们可以把路由定义成这个样子:

Router::new()
    .push(
        Router::new()
            .before(check_authed)
            .path("writers")
            .post(create_writer)
            .push(Router::with_path("<id>").patch(edit_writer).delete(delete_writer)),
    )
    .push(
        Router::new().path("writers").get(list_writers).push(
            Router::with_path("<id>")
                .get(show_writer)
                .push(Router::with_path("articles").get(list_writer_articles)),
        ),
    );

尽管有两个路由都有相同的路径定义 path("articles"), 他们依然可以被添加到同一个父路由里.

过滤器

Router 中有许多方法调用后会返回自己, 以便于链式书写代码. 有时候, 你需要根据某些条件决定如何路由, 路由系统也提供了一些判断的方式, 也很容易使用.

Router 内部都是通过过滤器来确定路由是否匹配. 过滤器支持使用 or 或者 and 做基本逻辑运算. 一个路由可以包含多个过滤器, 当所有的过滤器都匹配成功时, 路由匹配成功.

网站的路径信息是一个树状机构, 这个树状机构并不等同于组织路由的树状结构. 网站的一个路径可能对于多个路由节点. 比如, 在 articles/ 这个路径下的某些内容需要登录才可以查看, 而某些有不需要登录. 我们可以把需要登录查看的子路径组织到一个包含登录验证的中间件的路由下面. 不需要登录验证的组织到另一个没有登录验证的路由下面:

Router::new()
    .push(
        Router::new()
            .path("articles")
            .get(list_articles)
            .push(Router::new().path("<id>").get(show_article)),
    )
    .push(
        Router::new()
            .path("articles")
            .before(auth_check)
            .post(list_articles)
            .push(Router::new().path("<id>").patch(edit_article).delete(delete_article)),
    );

路由是使用过滤器过滤请求并且发送给对应的中间件和 Handler 处理的.

pathmethod 是两个最为常用的过滤器. path 用于匹配路径信息; method 用于匹配请求的 Method, 比如: GET, POST, PATCH 等.

我们可以使用 and, or 连接路由的过滤器:

Router::new().filter(filter::path("hello").and(filter::get()));

Path filter

The Path filter supports regular expression matching. You can use <*rest> or <**rest> to match all remaining paths.

Method filter

Comming soon…