Salvo —— Rust 编写的 Web 后端框架
Salvo 是一个极其简单易用却又功能强大的 Rust Web 后端框架. 目标是让 Rust 下的 Web 后端开发能像 Go 等其他语言里的一样简单.
源码地址: https://github.com/salvo-rs/salvo
🎯 功能特色
- 基于hyper, tokio 的异步 Web 后端框架;
- 支持 Websocket;
- 统一的中间件和句柄接口, 中间件系统支持在句柄之前或者之后运行;
- 简单易用的路由系统, 支持路由嵌套, 在任何嵌套层都可以添加中间件;
- 集成 multipart 表单处理, 处理上传文件变得非常简单;
- 支持从多个本地目录映射成一个虚拟目录提供服务.
⚡️ 快速开始
创建一个全新的项目:
cargo new hello_salvo --bin
添加依赖项到 Cargo.toml
[dependencies] salvo = { version = "0.13", features = ["full"] } tokio = { version = "1.5", features = ["full"] }
在 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_plain_text("Hello World"); }
对于 fn_handler, 可以根据需求和喜好有不同种写法.
- 可以将一些没有用到的参数省略掉, 比如这里的
_req
,_depot
.<pre>
#[fn_handler] async fn hello_world(res: &mut Response) { res.render_plain_text("Hello World"); }
</li> <li> 对于任何实现 Writer 的类型都是可以直接作为函数返回值. 比如, <code>&str</code> 实现了 <code>Writer</code>, 会直接按纯文本输出: <pre>
#[fn_handler] async fn hello_world(res: &mut Response) -> &'static str { "Hello World" }
</li> <li> 更常见的情况是, 我们需要通过返回一个 <code>Result<T, E></code> 来简化程序中的错误处理. 如果 <code>Result<T, E></code> 中 <code>T</code> 和 <code>E</code> 都实现 <code>Writer</code>, 则 <code>Result<T, E></code> 可以直接作为函数返回类型: <pre>
#[fn_handler] async fn hello_world(res: &mut Response) -> Result<&'static str, ()> { Ok("Hello World") }
</li>
在 main
函数中, 我们需要首先创建一个根路由, 然后创建一个 Server 并且调用它的 bind
函数:
use salvo::prelude::*; #[fn_handler] async fn hello_world() -> &'static str { "Hello World" } #[tokio::main] async fn main() { let router = Router::new().get(hello_world); let server = Server::new(router); server.bind(([0, 0, 0, 0], 7878)).await; }
中间件
Salvo 中的中间件其实就是 Handler, 没有其他任何特别之处.
树状路由系统
正常情况下我们是这样写路由的:
Router::with_path("articles").get(list_articles).post(create_article); Router::with_path("articles/") .get(show_article) .patch(edit_article) .delete(delete_article);
往往查看文章和文章列表是不需要用户登录的, 但是创建, 编辑, 删除文章等需要用户登录认证权限才可以. Salvo 中支持嵌套的路由系统可以很好地满足这种需求. 我们可以把不需要用户登录的路由写到一起:
Router::with_path("articles") .get(list_articles) .push(Router::with_path("").get(show_article));
然后把需要用户登录的路由写到一起, 并且使用相应的中间件验证用户是否登录:
Router::with_path("articles") .before(auth_check) .post(list_articles) .push(Router::with_path("").patch(edit_article).delete(delete_article));
虽然这两个路由都有这同样的 path("articles")
, 然而它们依然可以被同时添加到同一个父路由, 所以最后的路由长成了这个样子:
Router::new() .push( Router::with_path("articles") .get(list_articles) .push(Router::with_path("").get(show_article)), ) .push( Router::with_path("articles") .before(auth_check) .post(list_articles) .push(Router::with_path("").patch(edit_article).delete(delete_article)), );
匹配了路径中的一个片段, 正常情况下文章的 id
只是一个数字, 这是我们可以使用正则表达式限制 id
的匹配规则, r"id:/\\d+/"
.
对于这种数字类型, 还有一种更简单的方法是使用 id:num
, 具体写法为:
- , 匹配任意多个数字字符;
- , 只匹配固定特定数量的数字字符,这里的 10 代表匹配仅仅匹配 10 个数字字符;
- , 代表匹配 1 到 9 个数字字符;
- , 代表匹配 3 到 9 个数字字符;
- , 代表匹配 1 到 10 个数字字符;
- , 代表匹配 3 到 10 个数字字符;
- , 代表匹配至少 10 个数字字符.
还可以通过 <*>
或者 <**>
匹配所有剩余的路径片段. 为了代码易读性性强些, 也可以添加适合的名字, 让路径语义更清晰, 比如: <**file_path>
.
允许组合使用多个表达式匹配同一个路径片段, 比如 /articles/article_id:num/
.
文件上传
可以通过 Request 中的 get_file 异步获取上传的文件:
#[fn_handler] async fn upload(req: &mut Request, res: &mut Response) { let file = req.get_file("file").await; if let Some(file) = file { let dest = format!("temp/{}", file.filename().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_plain_text("Ok"); } } else { res.set_status_code(StatusCode::BAD_REQUEST); } }
多文件上传也是非常容易处理的:
#[fn_handler] async fn upload(req: &mut Request, res: &mut Response) { let files = req.get_files("files").await; if let Some(files) = files { let mut msgs = Vec::with_capacity(files.len()); for file in files { let dest = format!("temp/{}", file.filename().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); res.render_plain_text(&format!("file not found in request: {}", e.to_string())); } else { msgs.push(dest); } } res.render_plain_text(&format!("Files uploaded:\\n\\n{}", msgs.join("\\n"))); } else { res.set_status_code(StatusCode::BAD_REQUEST); res.render_plain_text("file not found in request"); } }
更多示例
您可以从 examples 文件夹下查看更多示例代码:
- basic_auth.rs
- compression.rs
- custom_error_page.rs
- custom_filter.rs
- file_list.rs
- handle_error.rs
- proxy.rs
- remote_addr.rs
- routing.rs
- size_limiter.rs
- sse_chat.rs
- sse.rs
- tls.rs
- todos.rs
- unix_socket.rs
- ws_chat.rs
- ws.rs
- work_with_tower.rs
您可以通过以下命令运行这些示例:
cargo run --example basic_auth
您可以使用任何你想运行的示例名称替代这里的 basic_auth
.
这里有一个真实的项目使用了 Salvo:https://github.com/driftluo/myblog.
🚀 性能
Benchmark 测试结果可以从这里查看:
https://web-frameworks-benchmark.netlify.app/result?l=rust
⚠️ 开源协议
Salvo 项目采用 MIT License (LICENSE-MIT or http://opensource.org/licenses/MIT)

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
每日一博 | 使用 Vue 搞定无法解决的“动态挂载”
在一些特殊场景下,使用组件的时机无法确定,或者无法在Vue的template中确定要我们要使用的组件,这时就需要动态的挂载组件,或者使用运行时编译动态创建组件并挂载。 今天我们将带大家从实际项目出发,看看在实际解决客户问题时,如何将组件进行动态挂载,并为大家展示一个完整的解决动态挂载问题的完整过程。 无法解决的“动态挂载” 我们的电子表格控件SpreadJS在运行时,存在这样一个功能:当用户双击单元格会显示一个输入框用于编辑单元格的内容,用户可以根据需求按照自定义单元格类型的规范自定义输入框的形式,集成任何Form表单输入类型。 这个输入框的创建销毁都是通过继承单元格类型对应方法实现的,因此这里就存在一个问题——这个动态的创建方式并不能简单在VUE template中配置,然后直接使用。 而就在前不久,客户问然询问我:你家控件的自定义单元格是否支持Vue组件比如ElementUI的AutoComplete? 由于前面提到的这个问题: 沉思许久,我认真给客户回复:“组件运行生命周期不一致,用不了”,但又话锋一转,表示可以使用通用组件解决这个问题。 问题呢,是顺利解决了。 但是这个无奈的"...
- 下一篇
高并发场景下JVM调优实践之路
一、背景 2021年2月,收到反馈,视频APP某核心接口高峰期响应慢,影响用户体验。 通过监控发现,接口响应慢主要是P99耗时高引起的,怀疑与该服务的GC有关,该服务典型的一个实例GC表现如下图: 可以看出,在观察周期里: 平均每10分钟YoungGC次数66次,峰值为470次; 平均每10分钟Full GC次数0.25次,峰值5次; 可见FullGC非常频繁,YoungGC在特定的时段也比较频繁,存在较大的优化空间。由于对GC停顿的优化是降低接口的P99时延一个有效的手段,所以决定对该核心服务进行JVM调优。 二、优化目标 接口P99时延降低30% 减少Young GC和Full GC次数、停顿时长、单次停顿时长 由于GC的行为与并发有关,例如当并发比较高时,不管如何调优,Young GC总会很频繁,总会有不该晋升的对象晋升触发Full GC,因此优化的目标根据负载分别制定: 目标1:高负载(单机1000 QPS以上) Young GC次数减少20%-30% ,Young GC累积耗时不恶化; Full GC次数减少50%以上,单次、累积Full GC耗时减少50%以上,服务发布不触...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Mario游戏-低调大师作品
- 2048小游戏-低调大师作品
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- CentOS8安装Docker,最新的服务器搭配容器使用
- CentOS7,CentOS8安装Elasticsearch6.8.6
- CentOS7安装Docker,走上虚拟化容器引擎之路
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果