#web-framework #async-http #async #框架 #openapi #web

salvo_extra

Salvo是一个强大的Web框架,可以使您的工作更轻松

171个版本 (68个破坏性版本)

新版本 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日

#140HTTP服务器

Download history 37826/week @ 2024-05-02 35285/week @ 2024-05-09 36906/week @ 2024-05-16 32630/week @ 2024-05-23 37963/week @ 2024-05-30 37943/week @ 2024-06-06 35151/week @ 2024-06-13 40769/week @ 2024-06-20 38877/week @ 2024-06-27 36090/week @ 2024-07-04 37941/week @ 2024-07-11 35925/week @ 2024-07-18 36448/week @ 2024-07-25 38249/week @ 2024-08-01 41752/week @ 2024-08-08 27394/week @ 2024-08-15

148,949 每月下载量
12 个库中使用(通过 salvo

MIT/Apache

750KB
16K SLoC

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

🎯 特性

  • 使用Hyper 1Tokio 构建;
  • HTTP1、HTTP2和 HTTP3
  • 统一的中间件和处理接口;
  • 路由器可以无限嵌套,可以将多个中间件附加到任何路由器;
  • 集成多部分表单处理;
  • 支持WebSocket、WebTransport;
  • 支持OpenAPI,自动生成OpenAPI数据;
  • 支持Acme,自动从 lets 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)

这是一个非常简单的中间件,它向响应中添加了 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 采用以下许可证之一:

依赖项

~18–32MB
~564K SLoC