Rust语言这两年在“安全、并发、性能”方面备受关注,但它在主流Web应用领域表现如何呢?有哪些web框架可以推荐?以下是对这个话题的深入讨论。背景Web框架先简单回顾一下Web框架:Web框架主要用于动态Web开发,开发者在框架的基础上实现自己的业务逻辑。Web框架需要实现接收请求后提供参数校验的能力,然后根据请求参数从底层获取数据,最后以特定的格式返回。Web框架旨在简化Web开发过程,让开发人员更专注于他们的业务逻辑。其他语言的现状其他主流语言和web框架已经发展的很成熟了,比如:PHP语言:LaravelJAVA语言:SpringMVCGO语言:Gin/Beego这些框架介绍的文章已经满篇,所以我不会在这里重复它们。常见的RustWeb框架Rust目前有几十个已知的Web框架,在flosse的rust-web-framework-comparison开源项目中有详细列出(参考文末链接),有兴趣的可以一探究竟。但遗憾的是,目前还没有官方支持和推荐的web框架,所以我们根据实际项目的简单使用,挑出几个对比,希望能给大家在选择框架时提供参考。RustWebFramework的难点在比较这些框架之前,我们先来回顾一下Rust语言处理Web进程的难点。众所周知,Rust这几年发展很快,也带来了一些新的概念,比如生命周期等。另外,rust没有全局状态,或者很难实现,编译检查是比较严格,相对速度也比较慢。带着一些困难,我们来看看这些框架的实现。RustWeb框架分类在RustWeb框架中,hyper、h2、tiny-http属于更底层的框架,比如hyper,很多框架都是基于它开发的。也是Rust语言中比较老的框架;Rocket框架比较专注。大名鼎鼎的tokio作者实现的Tower目前与warp沟通较多,可能会合并,大家可以持续关注;iron、gotham、nickel、rouille、actix-web功能比较全面,就像Actix框架整个体系庞大,下面又拆分出很多子框架:web、http、net等。Rust主流Web框架对比最后进入正题,挑出几个我们在实际项目中使用过的框架进行对比。当然,可能还有一些框架的特点我们没有涉足,如果文章中有什么不足之处,欢迎指正。Hyper是最先登场的,它的特点是高性能。后面给出的压测结果与actix-web类似;另外先实现了Client组件,方便编写单元测试验证;很多web框架都是基于hyper实现的,可见其底层封装还是不错的。但是它也有一些缺点:hyperapplication端的功能比较少,所以很多框架会在它的基础上继续封装;作者认为通过matchblock路由是一个比较明显的缺点;例如,以下示例:asyncfnresponse_examples(req:Request,client:Client)->Result>{match(req.method(),req.uri().path()){(&Method::GET,"/index.html")=>{确定(Response::new(INDEX.into()))},(&Method::GET,"/json_api")=>{api_get_response().await}_=>{Ok(Response::builder().status(StatusCode::NOT_FOUND).body(NOTFOUND.into()).unwrap())}}}这是一个典型的超级实现,但是实际项目中的matchblock处理对于比较复杂的流程,往往需要翻一两页,开发和review都比较困难。Actix-webActix-web以在所有web框架中实现actor模型而闻名。它由微软工程师Nikolay开发,在Azure中广泛使用。超快是另一个优势。上了web性能测评网站,却有作弊嫌疑下面就展开说说他是怎么做到的;底层基于tokio。整体层次结构如下:tokio&&futures->actix-net/actix-rt->actix-net/othersub-crates->actix-web对于整个actix来说,功能还是比较丰富的;今年6月发布的1.0,进一步精简了actor模块,service替换了handle,简化了大量代码。缺点:大量unsafe(如下图所示),导致栈溢出bug频繁开发;这也是他表现最好的原因之一;他的另一个缺点是代码质量不高,变动频繁,至少web模块这部分,文档和例子不完整;比如0.7版本的handle在1.0版本变成了service,它封装的responder也是不稳定的。让我们将其与火箭实现进行比较。RocketRocket是主流的Rustweb框架之一,github项目有8.9kstars。而它的http处理部分是基于前面提到的Hyper。根据官方资料,它具有以下三个特点:类型安全;易于使用,让您专注于自己的业务;元器件丰富,几乎都可以定制;从笔者的体验来看,Rocket使用起来确实很快,对各种语言背景的开发者都比较友好;易于扩展,几乎所有的组件都可以定制,requestGuard、state、fairing都可以定制;另外,文档和例子都非常详细,预定义了很多宏,非常方便;Rocket缺点:性能会稍差,后面会给出压测数据。不过他的async分支马上就要发布了,打磨了好几个月。你可以关注一下;总结归纳一个简单的表格(如下图),总结一下:从大家关注度来看,火箭胜;Actix-web的功能会更多,比如websocket等;在应用层的使用和外设支持方面,Rocket是最好的;所以如果你不太在意性能,建议选择Rocket。接下来,让我们详细讨论一下Rocket。RocketRocket设计原则先来看看rocket的设计原则,这是其他框架没有的,实际代码实现也不错。具体原则如下:安全性、正确性和开发者体验至上。所有的请求处理信息都应该是类型化的并且是自包含的Decisionsshouldn'tbeforeed作者的理解:安全性、正确性和开发者体验是至关重要的。这充分发挥了Rust的安全优势,对各种语言的开发者都很友好。request和guards这两个组件后面会讲到;所有处理过的请求信息都必须指定一个类型。这也给开发者带来了约束,比如在使用Responder组件时;它不应该被强制执行。他的template、serialization、session等组件几乎所有的功能都是以可选插件的形式出现。对于这些,Rocket都有官方的库和支持,完全可以自由选择和替换。因此,Rocket是一个完美平衡了自由和约束的RustWeb框架。下面我们将详细展开几个重要的组件。RequestGuardsRequestGuards有点类似于javaspring框架的validator,是一个代表任何验证策略的类型,通过FromRequest实现验证策略。RequestGuard的数量没有限制。实际使用中可以根据自己的需要添加,也可以自定义Guards。例如:#[get("/")]fnindex(param:isize,a:A,b:B,c:C)->...{...}A,B和C都是具体实现,如A验证auth,B验证计数,C具体业务验证等;也可以使用框架已经实现的guard,也可以自己定义,总体还是很灵活的。守卫组件也实现了他的第一个设计原则:正确性和安全性;Responder我们直接看Responder的定义:pubtraitResponder{///Returns`Ok`ifa`Response`可以生成成功。否则,///returnsan`Err`withfailing`Status`。//////`request`参数是`Request`thatthis`Responder`///respondingto.//////当使用Rocket的代码生成时,ifan`Ok(Response)`被返回,//响应将被写出给客户端。如果`Err(Status)`被///返回,则检索并调用给定状态的错误捕捉器///以生成最终错误响应,然后将其写入///client.fnrespond_to(self,request:&Request)->response::Result;}trait的代码记录已被2016年设计之初就确定了,非常稳定,之后就没有更新过。respond_to返回的Result是封装好的,要么是OK,要么是Err状态。此外,内置了常用的类型(str,String,[u8],File,Option,Status),覆盖了绝大部分业务场景;如果您不满意,您还可以实现自定义响应程序。这也体现了他的第二个设计原则:类型约束。不像其他的框架,比如Actix-web也有responder,但只是最新版本才稳定。如果你想自定义如下呢?这是一个自定义示例:implResponderforPerson{fnrespond_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")]fnperson()->Person{Person{name:"a".to_string(),age:20}}这个是自定义响应者,直接返回一个Person对象;您还可以添加错误处理;看起来比较简单。我们可以对比下Actix-web的responder的实现:pubtraitResponder{///Theassociatederrorwhichcanbereturned.typeError:Into;///Thefutureresponsevalue.typeFuture:Future