#web-framework #async #server #http #server-framework #framework

salvo_core

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

180 个版本 (66 个重大更改)

新功能 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 日

#1483HTTP 服务器

Download history 35618/week @ 2024-05-05 37262/week @ 2024-05-12 38190/week @ 2024-05-19 30838/week @ 2024-05-26 41340/week @ 2024-06-02 37520/week @ 2024-06-09 37306/week @ 2024-06-16 38263/week @ 2024-06-23 38812/week @ 2024-06-30 37363/week @ 2024-07-07 38569/week @ 2024-07-14 35453/week @ 2024-07-21 37543/week @ 2024-07-28 36997/week @ 2024-08-04 41633/week @ 2024-08-11 31123/week @ 2024-08-18

149,385 每月下载量
49 个软件包 中使用 (直接使用 18 个)

MIT/Apache

645KB
14K SLoC

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

🎯 功能

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

⚡️ 快速入门

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

使用 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("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)
            .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 根据

依赖项

~25–42MB
~892K SLoC