<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          SalvoRust 編寫的 Web 后端框架

          聯(lián)合創(chuàng)作 · 2023-09-20 01:35

          Salvo 是一個(gè)極其簡單且功能強(qiáng)大的 Rust Web 后端框架. 僅僅需要基礎(chǔ) Rust 知識即可開發(fā)后端服務(wù).

          中國用戶可以添加我微信(chrislearn), 拉微信討論群.

          功能特色

          • 基于 Hyper, Tokio 開發(fā);
          • 支持 HTTP1, HTTP2 和 HTTP3;
          • 統(tǒng)一的中間件和句柄接口;
          • 路由支持無限層次嵌套;
          • 每一個(gè)路由都可以擁有一個(gè)或者多個(gè)中間件;
          • 集成 Multipart 表單處理;
          • 支持 WebSocket, WebTransport;
          • 支持 OpenAPI;
          • 支持 Acme, 自動從 let's encrypt 獲取 TLS 證書.

           快速開始

          你可以查看實(shí)例代碼, 或者訪問官網(wǎng).

          Hello World

          main.rs 中創(chuàng)建一個(gè)簡單的函數(shù)句柄, 命名為hello, 這個(gè)函數(shù)只是簡單地打印文本 "Hello World".

          use salvo::prelude::*;
          
          #[handler]
          async fn hello(_req: &mut Request, _depot: &mut Depot, res: &mut Response) {
              res.render(Text::Plain("Hello World"));
          }
          
          #[tokio::main]
          async fn main() {
              let acceptor = TcpListener::new("127.0.0.1:5800").bind().await;
              let router =  Router::new().get(hello);
              Server::new(acceptor).serve(router).await;
          }

          中間件

          Salvo 中的中間件其實(shí)就是 Handler, 沒有其他任何特別之處. 所以書寫中間件并不需要像其他某些框架需要掌握泛型關(guān)聯(lián)類型等知識. 只要你會寫函數(shù)就會寫中間件, 就是這么簡單!!!

          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)
           

          這就是一個(gè)簡單的中間件, 它向 Response 的頭部添加了 Header, 查看完整源碼.

          可鏈?zhǔn)綍鴮懙臉錉盥酚上到y(tǒng)

          正常情況下我們是這樣寫路由的:

          Router::with_path("articles").get(list_articles).post(create_article);
          Router::with_path("articles/<id>")
              .get(show_article)
              .patch(edit_article)
              .delete(delete_article);
           

          往往查看文章和文章列表是不需要用戶登錄的, 但是創(chuàng)建, 編輯, 刪除文章等需要用戶登錄認(rèn)證權(quán)限才可以. Salvo 中支持嵌套的路由系統(tǒng)可以很好地滿足這種需求. 我們可以把不需要用戶登錄的路由寫到一起:

          Router::with_path("articles")
              .get(list_articles)
              .push(Router::with_path("<id>").get(show_article));
           

          然后把需要用戶登錄的路由寫到一起, 并且使用相應(yīng)的中間件驗(yàn)證用戶是否登錄:

          Router::with_path("articles")
              .hoop(auth_check)
              .push(Router::with_path("<id>").patch(edit_article).delete(delete_article));
           

          雖然這兩個(gè)路由都有這同樣的 path("articles"), 然而它們依然可以被同時(shí)添加到同一個(gè)父路由, 所以最后的路由長成了這個(gè)樣子:

          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> 匹配了路徑中的一個(gè)片段, 正常情況下文章的 id 只是一個(gè)數(shù)字, 這是我們可以使用正則表達(dá)式限制 id 的匹配規(guī)則, r"<id:/\d+/>".

          還可以通過 <*> 或者 <**> 匹配所有剩余的路徑片段. 為了代碼易讀性性強(qiáng)些, 也可以添加適合的名字, 讓路徑語義更清晰, 比如: <**file_path>.

          有些用于匹配路徑的正則表達(dá)式需要經(jīng)常被使用, 可以將它事先注冊, 比如 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(),
          );
           

          這樣在需要路徑匹配時(shí)就變得更簡潔:

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

          提取請求數(shù)據(jù)

          可以輕松地從多個(gè)不同數(shù)據(jù)源獲取數(shù)據(jù), 并且組裝為你想要的類型. 可以先定義一個(gè)自定義的類型, 比如:

          #[derive(Serialize, Deserialize, Extractible, Debug)]
          /// 默認(rèn)從 body 中獲取數(shù)據(jù)字段值
          #[salvo(extract(default_source(from = "body")))]
          struct GoodMan<'a> {
              /// 其中, id 號從請求路徑參數(shù)中獲取, 并且自動解析數(shù)據(jù)為 i64 類型.
              #[salvo(extract(source(from = "param")))]
              id: i64,
              /// 可以使用引用類型, 避免內(nèi)存復(fù)制.
              username: &'a str,
              first_name: String,
              last_name: String,
          }
           

          然后在 Handler 中可以這樣獲取數(shù)據(jù):

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

          甚至于可以直接把類型作為參數(shù)傳入函數(shù), 像這樣:

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

          查看完整源碼

          OpenAPI 支持

          無需對項(xiàng)目做大的改動,即可實(shí)現(xiàn)對 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;
          }

          更多示例

          您可以從 examples 文件夾下查看更多示例代碼, 您可以通過以下命令運(yùn)行這些示例:

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

          您可以使用任何你想運(yùn)行的示例名稱替代這里的 basic-auth.

          性能

          Benchmark 測試結(jié)果可以從這里查看:

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

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

           

          開源協(xié)議

          Salvo 項(xiàng)目采用 MIT License (LICENSE-MIT or http://opensource.org/licenses/MIT)

          瀏覽 44
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          編輯 分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          編輯 分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日本a片免费视频 | 色秘 乱码一区二区三区男奴-百度 | 亚洲人色| 伊人在线9999 | 草b小视频 |