Salvo 0.23.3 更新内容:
1. 改进中间件执行流程.
2. 改进依赖包结构
github: https://github.com/salvo-rs/salvo
Salvo 是一个极其简单且功能强大的 Rust Web 后端框架. 仅仅需要基础 Rust 知识即可开发后端服务.
功能特色
基于 Hyper, Tokio 开发;
统一的中间件和句柄接口;
路由支持多层次嵌套, 在任何层都可以添加中间件;
集成 Multipart 表单处理;
支持 Websocket;
支持 Acme, 自动从 let's encrypt 获取 TLS 证书;
支持从多个本地目录映射成一个虚拟目录提供服务.
快速开始
你可以查看实例代码, 或者访问官网.
创建一个全新的项目:
cargo new hello_salvo --bin
添加依赖项到 Cargo.toml
[dependencies]salvo = "0.23"tokio = "1"
在 main.rs 中创建一个简单的函数句柄, 命名为hello_world, 这个函数只是简单地打印文本 "Hello World".
use salvo::prelude::*;#[fn_handler]async fn hello_world(_req: &mut Request, _depot: &mut Depot, res: &mut Response { res.render(Text::Plain("Hello World"; }
中间件
Salvo 中的中间件其实就是 Handler, 没有其他任何特别之处. 所以书写中间件并不需要像其他某些框架需要掌握泛型关联类型等知识. 只要你会写函数就会写中间件, 就是这么简单!!!
use salvo::http::header::{self, HeaderValue};
use salvo::prelude::*;#[fn_handler]async fn add_header(res: &mut Response {
res.headers_mut( .insert(header::SERVER, HeaderValue::from_static("Salvo"; }
然后将它添加到路由中:
Router::new(.hoop(add_header.get(hello_world
这就是一个简单的中间件, 它向 Response 的头部添加了 Header, 查看完整源码.
可链式书写的树状路由系统
正常情况下我们是这样写路由的:
Router::with_path("articles".get(list_articles.post(create_article; Router::with_path("articles/<id>" .get(show_article .patch(edit_article .delete(delete_article;
往往查看文章和文章列表是不需要用户登录的, 但是创建, 编辑, 删除文章等需要用户登录认证权限才可以. Salvo 中支持嵌套的路由系统可以很好地满足这种需求. 我们可以把不需要用户登录的路由写到一起:
Router::with_path("articles".get(list_articles
.push(Router::with_path("<id>".get(show_article;
然后把需要用户登录的路由写到一起, 并且使用相应的中间件验证用户是否登录:
Router::with_path("articles".hoop(auth_check
.post(list_articles
.push(Router::with_path("<id>".patch(edit_article.delete(delete_article;
虽然这两个路由都有这同样的 path("articles"
, 然而它们依然可以被同时添加到同一个父路由, 所以最后的路由长成了这个样子:
Router::new(.push(
Router::with_path("articles"
.get(list_articles
.push(Router::with_path("<id>".get(show_article,
.push(
Router::with_path("articles"
.hoop(auth_check
.post(list_articles
.push(Router::with_path("<id>".patch(edit_article.delete(delete_article,
;
<id>匹配了路径中的一个片段, 正常情况下文章的 id 只是一个数字, 这是我们可以使用正则表达式限制 id 的匹配规则, r"<id:/\d+/>".
还可以通过 <*> 或者 <**> 匹配所有剩余的路径片段. 为了代码易读性性强些, 也可以添加适合的名字, 让路径语义更清晰, 比如: <**file_path>.
有些用于匹配路径的正则表达式需要经常被使用, 可以将它事先注册, 比如 GUID:
PathFilter::register_part_regex( "guid", Regex::new("[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-{3}[0-9a-fA-F]{12}".unwrap(, ;
这样在需要路径匹配时就变得更简洁:
Router::with_path("<id:guid>".get(index
查看完整源码
文件上传
可以通过 Request 中的 file 异步获取上传的文件:
#[fn_handler]async fn upload(req: &mut Request, res: &mut Response {
let file = req.file("file".await;
if let Some(file = file {
let dest = format!("temp/{}", file.name(.unwrap_or_else(|| "file".into(;
if let Err(e = std::fs::copy(&file.path, Path::new(&dest {
res.set_status_code(StatusCode::INTERNAL_SERVER_ERROR; } else { res.render("Ok"; } } else { res.set_status_code(StatusCode::BAD_REQUEST; } }
更多示例
您可以从 examples 文件夹下查看更多示例代码, 您可以通过以下命令运行这些示例:
cargo run --bin --example-basic_auth
您可以使用任何你想运行的示例名称替代这里的 basic_auth.
这里有一个真实的项目使用了 Salvo:https://github.com/driftluo/myblog.