欲練此功
爲什麼要寫這個框架
因爲我笨,無法學會使用 actix-web 等現存的框架.當我想把以前的 go 的 web 服務使用 rust 實現時,一眼看去,似乎每個框架都比 go 裏存在框架複雜, 本來 Rust 的學習曲線就夠陡峭的了, 又何苦把 Web 框架整得那麼複雜?
Salvo(賽風) 是否適合你
Salvo 雖然簡單, 但是功能足夠全面強大, 基本可以認爲是 Rust 界最強的, 然而, 就是這麼強大的系統, 實際上學習和使用都是很簡單的. 絕對不會讓你有任何揮刀自宮的痛苦.
它適合剛剛在學習 Rust 的初級入門者, CRUD 應該是極其平常且常用的功能, 如果使用 Salvo 做類似的工作, 你會發現它和你使用過的其他語言的 Web 框架一樣的簡單 (比如: Express, Koa, gin, flask...), 甚至在某些方面更抽象簡潔;
它適合希望將 Rust 用於生產環境, 提供給穩健快速的服務器. 雖然 Salvo 並未發佈 1.0 版本, 但是, 它的核心功能已經經過幾年的迭代, 足夠穩定, 且問題修復及時;
它適合毛髮已經不再茂密但是還每天不停掉髮的你.
如何做到足夠簡單
很多底層的實現 Hyper 都已經實現,所以,一般需求,基於 Hyper 實現應該沒有錯. Salvo 也是一樣. 核心功能是一個功能強大並且靈活的路由系統以及很多常用的功能, 比如 Acme, OpenAPI, JWT Auth 等.
Salvo 裏統一了 Handler 和 Middleware. Middleware 就是 Handler. 通過路由的 hoop 添加到 Router 上. 本質上, Middleware 和 Handler 都是處理 Request 請求,並且可能向 Response 寫入數據. 而 Handler 接收的參數是 Request, Depot, Response 三個, 其中 Depot 用於存儲請求處理過程中的臨時數據.
爲方便書寫, 在用不着的情況下可以省略掉某些參數, 也可以無視參數的傳入順序.
use salvo::prelude::*;
#[handler]
async fn hello_world(_req: &mut Request, _depot: &mut Depot, res: &mut Response) {
res.render("Hello world");
}
#[handler]
async fn hello_world(res: &mut Response) {
res.render("Hello world");
}
另外路由系統提供的 API 也是極其簡單的, 但是, 功能卻是強大的. 正常使用需求下, 基本上就是隻關注 Router 一個類型即可.
路由系統
我自己感覺路由系統是跟其他的框架不太一樣的. Router 可以寫平,也可以寫成樹狀.這裏區業務邏輯樹與訪問目錄樹.業務邏輯樹是根據業務邏輯需求,劃分 router 結構,形成 router 樹,它不一定與訪問目錄樹一致.
正常情況下我們是這樣寫路由的:
Router::new().path("articles").get(list_articles).post(create_article);
Router::new()
.path("articles/<id>")
.get(show_article)
.patch(edit_article)
.delete(delete_article);
往往查看文章和文章列表是不需要用戶登錄的, 但是創建, 編輯, 刪除文章等需要用戶登錄認證權限纔可以. Salvo 中支持嵌套的路由系統可以很好地滿足這種需求. 我們可以把不需要用戶登錄的路由寫到一起:
Router::new()
.path("articles")
.get(list_articles)
.push(Router::new().path("<id>").get(show_article));
然後把需要用戶登錄的路由寫到一起, 並且使用相應的中間件驗證用戶是否登錄:
Router::new()
.path("articles")
.hoop(auth_check)
.post(list_articles)
.push(Router::new().path("<id>").patch(edit_article).delete(delete_article));
雖然這兩個路由都有這同樣的 path("articles")
, 然而它們依然可以被同時添加到同一個父路由, 所以最後的路由長成了這個樣子:
Router::new()
.push(
Router::new()
.path("articles")
.get(list_articles)
.push(Router::new().path("<id>").get(show_article)),
)
.push(
Router::new()
.path("articles")
.hoop(auth_check)
.post(list_articles)
.push(Router::new().path("<id>").patch(edit_article).delete(delete_article)),
);
<id>
匹配了路徑中的一個片段, 正常情況下文章的 id
只是一個數字, 這是我們可以使用正則表達式限制 id
的匹配規則, r"<id:/\d+/>"
.