#web-framework #server #async #server-framework #back-end #web

salvo

Salvo是一个功能强大的Web框架,可以让你工作变得更简单

152个版本 (59个重大更改)

新版本 0.71.1 2024年8月18日
0.70.0 2024年8月13日
0.68.5 2024年7月9日
0.66.2 2024年3月6日
0.1.6 2020年2月23日

HTTP服务器类别中排名 11

Download history 34638/week @ 2024-05-03 35131/week @ 2024-05-10 37426/week @ 2024-05-17 31950/week @ 2024-05-24 37643/week @ 2024-05-31 37759/week @ 2024-06-07 35265/week @ 2024-06-14 37729/week @ 2024-06-21 38854/week @ 2024-06-28 37094/week @ 2024-07-05 37520/week @ 2024-07-12 32468/week @ 2024-07-19 35638/week @ 2024-07-26 36108/week @ 2024-08-02 42241/week @ 2024-08-09 30143/week @ 2024-08-16

每月下载量 150,525
33 个Crate中 使用(直接使用 30 个)

MIT/Apache

2MB
19K SLoC

Salvo是一个极其简单且强大的Rust Web后端框架。只需具备基本的Rust知识,就可以开发后端服务。

🎯 特性

  • 使用Hyper 1Tokio构建;
  • HTTP1、HTTP2和HTTP3
  • 统一的中间件和处理器接口;
  • 路由器可以无限嵌套,可以将多个中间件附加到任何路由器;
  • 集成Multipart表单处理;
  • 支持WebSocket、WebTransport;
  • 支持OpenAPI,自动生成OpenAPI数据;
  • 支持Acme,自动从Let's Encrypt获取TLS证书;
  • 支持Tower服务和层;

⚡️ 快速开始

您可以在这里查看示例,或查看官方网站

使用ACME和HTTP3的Hello World

只需几行代码即可实现支持ACME自动获取证书并支持HTTP1、HTTP2、HTTP3协议的服务器。

use salvo::prelude::*;

#[handler]
async fn hello(res: &mut Response) {
    res.render(Text::Plain("Hello World"));
}

#[tokio::main]
async fn main() {
    let mut router = Router::new().get(hello);
    let listener = TcpListener::new("0.0.0.0:443")
        .acme()
        .add_domain("test.salvo.rs") // Replace this domain name with your own.
        .http01_challege(&mut router).quinn("0.0.0.0:443");
    let acceptor = listener.join(TcpListener::new("0.0.0.0:80")).bind().await;
    Server::new(acceptor).serve(router).await;
}

中间件

处理器和中间件之间没有区别,中间件只是处理器。 因此,如果您能编写函数,就可以编写中间件!您不需要了解关联类型、泛型类型等概念。

use salvo::http::header::{self, HeaderValue};
use salvo::prelude::*;

#[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)

这是一个非常简单的中间件,它向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)
    .push(Router::with_path("<id>").patch(edit_article).delete(delete_article));

尽管这两条路由具有相同的path,但它们仍然可以同时添加到同一父路由,因此最终的路由看起来是这样的

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)
            .push(Router::with_path("<id>").patch(edit_article).delete(delete_article)),
    );

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

您还可以使用<**><*+><*?>来匹配所有剩余的路径片段。为了使代码更易读,您还可以添加适当的名称,使路径语义更清晰,例如:<**file_path>

匹配路径的正则表达式需要经常使用,可以预先注册,例如GUID

PathFilter::register_wisp_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函数异步获取文件

#[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) = tokio::fs::copy(&file.path, Path::new(&dest)).await {
            res.status_code(StatusCode::INTERNAL_SERVER_ERROR);
        } else {
            res.render("Ok");
        }
    } else {
        res.status_code(StatusCode::BAD_REQUEST);
    }
}

从请求中提取数据

您可以从多个不同的数据源轻松获取数据,并将其组装成您想要的类型。您可以先定义一个自定义类型,例如

#[derive(Serialize, Deserialize, Extractible, Debug)]
/// Get the data field value from the body by default.
#[salvo(extract(default_source(from = "body")))]
struct GoodMan<'a> {
    /// The id number is obtained from the request path parameter, and the data is automatically parsed as i64 type.
    #[salvo(extract(source(from = "param")))]
    id: i64,
    /// Reference types can be used to avoid memory copying.
    username: &'a str,
    first_name: String,
    last_name: String,
}

然后在Handler中,您可以像这样获取数据

#[handler]
async fn edit(req: &mut Request) {
    let good_man: GoodMan<'_> = req.extract().await.unwrap();
}

您甚至可以直接将类型作为参数传递给函数,如下所示

#[handler]
async fn edit<'a>(good_man: GoodMan<'a>) {
    res.render(Json(good_man));
}

查看完整源代码

支持OpenAPI

无需对项目进行重大更改,即可实现完美的OpenAPI支持。

#[derive(Serialize, Deserialize, ToSchema, Debug)]
struct MyObject<T: ToSchema + std::fmt::Debug> {
    value: T,
}

#[endpoint]
async fn use_string(body: JsonBody<MyObject<String>>) -> String {
    format!("{:?}", body)
}
#[endpoint]
async fn use_i32(body: JsonBody<MyObject<i32>>) -> String {
    format!("{:?}", body)
}
#[endpoint]
async fn use_u64(body: JsonBody<MyObject<u64>>) -> String {
    format!("{:?}", body)
}

#[tokio::main]
async fn main() {
    tracing_subscriber::fmt().init();

    let router = Router::new()
        .push(Router::with_path("i32").post(use_i32))
        .push(Router::with_path("u64").post(use_u64))
        .push(Router::with_path("string").post(use_string));

    let doc = OpenApi::new("test api", "0.0.1").merge_router(&router);

    let router = router
        .push(doc.into_router("/api-doc/openapi.json"))
        .push(SwaggerUi::new("/api-doc/openapi.json").into_router("swagger-ui"));

    let acceptor = TcpListener::new("127.0.0.1:5800").bind().await;
    Server::new(acceptor).serve(router).await;
}

🛠️ Salvo CLI

Salvo CLI是一个命令行工具,用于简化新Salvo项目的创建,支持Web API、网站、数据库(包括SQLite、PostgreSQL和MySQL通过SQLx、SeaORM、Diesel、Rbatis)和基本中间件的模板。您可以使用salvo-cli创建新的Salvo项目

安装

cargo install salvo-cli

创建一个新的salvo项目

salvo new project_name

更多示例

您可以在examples文件夹中找到更多示例。您可以使用以下命令运行这些示例

cd examples
cargo run --bin example-basic-auth

您可以使用任何示例名称运行,而不是这里的basic-auth

🚀 性能

基准测试结果可从这里找到

https://web-frameworks-benchmark.netlify.app/result?l=rust

https://www.techempower.com/benchmarks/#section=data-r22

🩸 贡献者

☕️ 捐赠

Salvo 是一个开源项目。如果您想支持 Salvo,您可以 ☕️ 在这里给我买一杯咖啡

⚠️ 许可证

Salvo 使用以下任何一个许可证:

依赖

~18–37MB
~690K SLoC