Rust Web框架怎么選?研究本文就夠了!
Rust語言這兩年在“安全、并發(fā)、性能”方面吸足了眼球,但在主流的web應(yīng)用領(lǐng)域表現(xiàn)如何?有哪些可以推薦的Web框架?下面就這個(gè)話題深入展開。
背景
Web框架
我們先簡(jiǎn)單回顧下Web框架: Web框架主要用于動(dòng)態(tài)Web開發(fā),開發(fā)人員在框架基礎(chǔ)上實(shí)現(xiàn)自己的業(yè)務(wù)邏輯。 Web框架需要實(shí)現(xiàn)接收到請(qǐng)求后,能夠提供參數(shù)校驗(yàn)的能力,然后根據(jù)請(qǐng)求參數(shù)從底層拿到數(shù)據(jù),最后以特定格式返回。Web框架旨在簡(jiǎn)化web開發(fā)流程,讓開發(fā)人員更專注于自己的業(yè)務(wù)邏輯。
其他語言現(xiàn)狀
其他主流語言,Web框架都已經(jīng)發(fā)展非常成熟,大家耳熟能詳?shù)谋热纾?/p>
- PHP語言: Laravel
- JAVA語言:Spring MVC
- GO語言: Gin/Beego
這些框架介紹的文章已經(jīng)滿大街了,在此就不贅述。
Rust常見Web框架
Rust目前已知的Web框架也有幾十種,在flosse的rust-web-framework-comparison開源項(xiàng)目里面詳細(xì)列出(見文末參考資料的鏈接),感興趣的可以查看。但遺憾的是官方也沒有給出支持或者推薦的Web框架,所以我們就實(shí)際項(xiàng)目簡(jiǎn)單使用的情況,挑出幾個(gè)比較下,希望給大家選型框架時(shí)參考下。
Rust Web框架的難點(diǎn)
在比較這些框架之前,我們先回顧下Rust語言處理Web流程困難的地方。眾所周知,Rust近年發(fā)展迅猛,同時(shí)也帶來一些新的概念,比如生命周期等,另外rust沒有全局狀態(tài),或者說實(shí)現(xiàn)比較困難,以及編譯檢查比較嚴(yán)格,相對(duì)速度也比較慢,這樣對(duì)實(shí)現(xiàn)web框架帶來一些困難,下來我們看下這些框架的實(shí)現(xiàn)情況。
Rust Web框架分類
Rust Web框架中,hyper、h2、tiny-http屬于底層一些的框架,比如hyper,很多框架都是基于它開發(fā)的,它也是Rust語言中比較老牌的框架;Rocket框架相對(duì)比較專注, 大名鼎鼎的tokio的作者實(shí)現(xiàn)的Tower,目前跟warp交流較多,有可能會(huì)合并大家也可以持續(xù)關(guān)注;iron、gotham、nickel、rouille、actix-web功能相對(duì)全面些,就像其中Actix框架整個(gè)體系龐大,下面又拆分出許多子框架:web、http、net等。
Rust主流Web框架的比較
下面我們終于進(jìn)入正題,挑出幾個(gè)我們實(shí)際項(xiàng)目中使用過的框架進(jìn)行比較。當(dāng)然,可能有些框架的特性我們并未涉獵,文中有不妥之處歡迎指正。
Hyper
第一個(gè)出場(chǎng)的就是hyper,它的特點(diǎn)就是高性能,后面會(huì)給出的壓測(cè)結(jié)果,跟actix-web差不多;另外首先實(shí)現(xiàn)了Client組件,方便寫單元測(cè)試驗(yàn)證;前面也提到很多web框架基于hyper實(shí)現(xiàn),側(cè)面說明他底層的封裝還是不錯(cuò)的。不過它也有些缺點(diǎn):
- hyper應(yīng)用側(cè)的功能相對(duì)少,所以會(huì)導(dǎo)致很多框架又在他的基礎(chǔ)上繼續(xù)封裝;
- 通過match block實(shí)現(xiàn)路由,這個(gè)筆者認(rèn)為是一個(gè)比較明顯的缺點(diǎn); 比如下面的例子:
- async fn response_examples(
- req: Request<Body>,client: Client<HttpConnector>
- ) -> Result<Response<Body>> {
- match (req.method(), req.uri().path()) {
- (&Method::GET, "/index.html") => {
- Ok(Response::new(INDEX.into()))
- },
- (&Method::GET, "/json_api") => {
- api_get_response().await
- }
- _ => {
- Ok(Response::builder().status(StatusCode::NOT_FOUND)
- .body(NOTFOUND.into())
- .unwrap())
- }
- }
- }
這是一個(gè)典型的hyper的實(shí)現(xiàn),但實(shí)際項(xiàng)目中的match塊處理較復(fù)雜的流程時(shí)往往需要翻一兩頁,這樣開發(fā)和review都相對(duì)困難。
Actix-web
Actix-web是已知的所有web框架實(shí)現(xiàn)了actor模型,由微軟的工程師 Nikolay 開發(fā),Azure用的比較多;超級(jí)快是另一個(gè)優(yōu)點(diǎn),在web性能評(píng)測(cè)網(wǎng)站刷榜,但有取巧嫌疑,下面會(huì)展開說下他怎么做的;底層基于tokio。整體層級(jí)結(jié)構(gòu)如下:
- tokio && futures -> actix-net/actix-rt -> actix-net/其他子crate -> actix-web
對(duì)于整個(gè)actix來說,功能還是比較豐富;今年6月發(fā)布的的1.0,進(jìn)一步簡(jiǎn)化actor模塊,service替代handle,大量的簡(jiǎn)化代碼。
- 缺點(diǎn):大量的unsafe(如下圖),導(dǎo)致經(jīng)常有開發(fā)爆出堆棧溢出的bug;這也是他性能最好的原因之一;
他的另外一個(gè)缺點(diǎn),代碼質(zhì)量不高,頻繁變動(dòng),至少是web模塊這塊,文檔和實(shí)例也不全;比如0.7版的handle,到1.0版變成service,他封裝的responder,也不穩(wěn)定,下面跟rocket的實(shí)現(xiàn)一起展開比較。
Rocket
Rocket是目前Rust主流的Web框架之一,github項(xiàng)目有8.9k的star。而它的http處理部分就是基于前面提到的Hyper。從官方資料看,具有如下三個(gè)特點(diǎn):
- 類型安全;
- 上手簡(jiǎn)單,讓你更專注于自己的業(yè)務(wù);
- 組件豐富,且?guī)缀醵伎梢宰远x;
Rocket從筆者使用經(jīng)驗(yàn)來看:確實(shí)上手非???,對(duì)各種語言背景的開發(fā)人員都相對(duì)友好;擴(kuò)展容易,他的組件幾乎都可以自定義,requestGuard、state、fairing 都可以定制;另外,文檔、example都非常詳細(xì),預(yù)定義很多宏,非常方便; Rocket的缺點(diǎn):性能上會(huì)略差些,后面會(huì)給出壓測(cè)數(shù)據(jù)。不過他的async分支也快發(fā)布了,都打磨了幾個(gè)月,大家可以關(guān)注;
匯總
簡(jiǎn)單匯總一個(gè)表格(如下圖),總結(jié)下:從大家的關(guān)注度上,Rocket勝出;Actix-web的功能會(huì)多些,比如websocket等;從使用和應(yīng)用層的周邊支持上,Rocket做的最好;所以不太在意性能的話,建議選擇Rocket。下來我們就就詳細(xì)討論下Rocket。
Rocket
Rocket設(shè)計(jì)原則
首先看下rocket的設(shè)計(jì)原則,這也是其他框架沒有的,而且他們實(shí)際代碼落地上也履行的不錯(cuò),具體原則如下:
- Security, correctness, and developer experience are paramount.
- All request handling information should be typed and self-contained
- Decisions should not be forced
筆者的理解:
- 安全性,正確性和開發(fā)人員經(jīng)驗(yàn)至關(guān)重要。這就充分挖掘了rust安全方面的優(yōu)勢(shì),且對(duì)各語言開發(fā)人員友好, 后面講到request、guards這兩個(gè)組件再展開下;
- 所有被處理請(qǐng)求信息都必須指定類型。這樣也對(duì)開發(fā)人員有所約束, 比如在使用Responder組件就深有感觸;
- 不應(yīng)該強(qiáng)行限制。他的模板、序列化、會(huì)話等組件,幾乎所有的功能都是可選擇的插件形式。對(duì)于這些,Rocket都有官方庫和支持,完全可以自由選擇和替換。 所以,Rocket是Rust Web框架里面,完美的平衡了自由和約束。下面我們就幾個(gè)重要組件詳細(xì)展開。
RequestGuards
RequestGuards有些類似java的spring框架的velidator,是代表任意驗(yàn)證策略的類型,驗(yàn)證策略通過FromRequest實(shí)現(xiàn)。RequestGuards沒有數(shù)量限制,實(shí)際使用中根據(jù)自己需求添加,也可以自定義Guards。舉個(gè)例子:
- #[get("/<param>")]
- fn index(param: isize, a: A, b: B, c: C) -> ... { ... }
上述例子中的A、B、C都是具體的實(shí)現(xiàn),比如A驗(yàn)證auth,B驗(yàn)證計(jì)數(shù),C具體業(yè)務(wù)校驗(yàn)等;也可以用框架已經(jīng)實(shí)現(xiàn)的guard,或者自己定義,整體還是非常靈活。guard組件也是履行他的第一個(gè)設(shè)計(jì)原則:正確性、安全性;
Responder
我們直接看下Responder的定義:
- pub trait Responder {
- /// Returns `Ok` if a `Response` could be generated successfully. Otherwise,
- /// returns an `Err` with a failing `Status`.
- ///
- /// The `request` parameter is the `Request` that this `Responder` is
- /// responding to.
- ///
- /// When using Rocket's code generation, if an `Ok(Response)` is returned,
- /// the response will be written out to the client. If an `Err(Status)` is
- /// returned, the error catcher for the given status is retrieved and called
- /// to generate a final error response, which is then written out to the
- /// client.
- fn respond_to(self, request: &Request) -> response::Result;
- }
筆者翻了一下這個(gè)trait的代碼記錄,從16年最開始設(shè)計(jì)就已經(jīng)確定,非常穩(wěn)定,之后再?zèng)]有更新過。respond_to返回的Result,他封裝了一下,要么是OK,要么是一個(gè)Err的status。 另外內(nèi)置實(shí)現(xiàn)了常用的類型( str 、String、 [u8] 、 File、 Option、 Status ),基本覆蓋絕大部分業(yè)務(wù)場(chǎng)景;如果還不能滿足,那你也可以實(shí)現(xiàn)自定義的responder。 這也就體現(xiàn)他的第二設(shè)計(jì)原則:類型約束。不像其他的框架,比如Actix-web也有responder,但也是最近的版本才穩(wěn)定下來。如下要想自定義怎么辦? 這是一個(gè)自定義的例子:
- impl Responder for Person {
- fn respond_to(self, _: &Request) -> response::Result {
- Response::build()
- .sized_body(Cursor::new(format!("{}:{}", self.name, self.age)))
- .raw_header("X-Person-Name", self.name)
- .header(ContentType::new("application", "x-person"))
- .ok()
- }
- }
- #[get("/person")]
- fn person() -> Person { Person { name: "a".to_string(), age: 20 } }
這就是自定義的一個(gè)responder,直接返回一個(gè)Person對(duì)象;也可以加上err的處理;看起來還是比較簡(jiǎn)單吧。我們可以對(duì)比下Actix-web的responder的實(shí)現(xiàn):
- pub trait Responder {
- /// The associated error which can be returned.
- type Error: Into<Error>;
- /// The future response value.
- type Future: Future<Output = Result<Response, Self::Error>>;
- /// Convert itself to `AsyncResult` or `Error`.
- fn respond_to(self, req: &HttpRequest) -> Self::Future;
- fn with_status(self, status: StatusCode) -> CustomResponder<Self>
- where
- Self: Sized,
- {
- CustomResponder::new(self).with_status(status)
- }
- fn with_header<K, V>(self, key: K, value: V) -> CustomResponder<Self>
- … …
- }
Actix-web是19年3月份才有這個(gè)組件的,status,header還是后來加;實(shí)現(xiàn)的起來復(fù)雜的多;
State
我們看下Rocket的State組件,也是最后一個(gè)原則的體現(xiàn)。直接舉例說明:
- use rocket::State;
- use rocket::response::content;
- struct HitCount(AtomicUsize);
- #[get("/")]
- fn index(hit_count: State<HitCount>) -> content::Html<String> {
- hit_count.0.fetch_add(1, Ordering::Relaxed);
- let msg = "Your visit has been recorded!";
- let count = format!("Visits: {}", count(hit_count));
- content::Html(format!("{}<br /><br />{}", msg, count))
- }
- #[get("/count")]
- fn count(hit_count: State<HitCount>) -> String {
- hit_count.0.load(Ordering::Relaxed).to_string()
- }
- fn rocket() -> rocket::Rocket {
- rocket::ignite()
- .mount("/", routes![index, count])
- .manage(HitCount(AtomicUsize::new(0)))
- }
這是一個(gè)計(jì)數(shù)器的簡(jiǎn)單實(shí)現(xiàn),通過state注入到handle中,每次訪問/都加1;也可以通過/cout接口拿到當(dāng)前計(jì)數(shù);這兩接口是無狀態(tài)的,但count是全局的,這就是state的魅力; 當(dāng)然state可以干的事情很多,另外也內(nèi)置實(shí)現(xiàn)了Request-local state,類似于thread-local 可以做鏈路跟蹤;
Fairing
Rocket還有個(gè)Fairing組件,結(jié)合上面提到的state組件,可以實(shí)現(xiàn)應(yīng)用啟動(dòng)時(shí),或者state的attach時(shí),請(qǐng)求和響應(yīng)時(shí)的一些定制;比如定制filter,日志等插件都可以實(shí)現(xiàn);
- #[derive(Default)]
- struct Counter {
- get: AtomicUsize,
- post: AtomicUsize,
- }
- impl Fairing for Counter {
- fn on_request(&self, request: &mut Request, _: &Data) {
- … …}
- fn on_response(&self, request: Request, response: mut Response)
- {… …}
- }
- fn rocket() -> rocket::Rocket {
- rocket::ignite()
- .mount("/", routes![… , …])
- .attach(Counter::default())
- … …
- }
這是一個(gè)根據(jù)請(qǐng)求方法不同而分別計(jì)數(shù)功能,你也可以實(shí)現(xiàn)Counter的attach的事件處理;但跟filter不同,F(xiàn)airing更通用些。其特點(diǎn)如下:
- 掛載在Request的生命周期
- 不能終止或者直接響應(yīng)Request
- 不能將非請(qǐng)求數(shù)據(jù)注入Request
- 可以檢查或者修改配置
- 只有attach了才能觸發(fā)
- 順序很重要
Fairing可以應(yīng)用在一些動(dòng)態(tài)改配置的場(chǎng)景,或者多種環(huán)境的復(fù)雜配置場(chǎng)景;且官方貼心的內(nèi)置了Adhoc實(shí)現(xiàn),方便開發(fā)人員快速實(shí)現(xiàn)。
至此,Rocket的特性就講完了,所以建議大家根據(jù)自己的實(shí)際需求,來選擇自己合適的框架。
壓測(cè)數(shù)據(jù)參考
最后我們給出之前做的壓測(cè)結(jié)果:
上述表格中Rocket sync(master分支)并發(fā)只能到50,如果業(yè)務(wù)場(chǎng)景非常注重性能的話,那你就慎重考慮,或者你持續(xù)關(guān)注async分支的進(jìn)展;另外比較了hyper框架debug和release方式的差異,其他的差異比例也類似。
參考資料:
- Hyper官網(wǎng)
- Rocket官網(wǎng)
- Actix-web官網(wǎng)
- rust Web框架的比較
- the-best-rust-frameworks-to-check-out-in-2019
Westar實(shí)驗(yàn)室成立于2018年,是分層區(qū)塊鏈的先行者。團(tuán)隊(duì)致力于創(chuàng)建下一代分層區(qū)塊鏈及金融基礎(chǔ)設(shè)施。通過先進(jìn)技術(shù)和工程能力,構(gòu)建產(chǎn)品和基礎(chǔ)網(wǎng)絡(luò),從而減少對(duì)信任的依賴,提高金融市場(chǎng)的效率。對(duì)于區(qū)塊鏈生態(tài),通過分層區(qū)塊鏈技術(shù),在保證安全性的同時(shí),保證處理速度,達(dá)到可以商用的目的。