导读:本文筛选了一些较为优秀的框架作比较(包含示例代码)。
框架列表
本列表统计于 2022 年 2 月:
| 框架名称 | 版本号 | 总下载量 | 框架简介 | 
|---|---|---|---|
| actix-web | 4.0.0-rc.3 | 5,134,720 | 一个强大、实用、高效的 Rust web 框架 | 
| warp | 0.3.2 | 4,114,095 | 构建极其高效的 Web 服务 | 
| axum | 0.4.5 | 235,150 | 专注于人机工程学和模块化的 Web 框架 | 
注:截止2023 年 4 月,actix-web 版本v4.3.1 下载量10,698,368 ,axum 版本v0.6.16 下载量11,592,805
还有许多其他优秀的框架:
[rocket] (https://crates.io/crates/rocket)
[hyper] (https://crates.io/crates/hyper)
[tide] (https://crates.io/crates/tide)
[rouille] (https://crates.io/crates/rouille)
[iron] (https://crates.io/crates/iron)
....
但它们并没有被包括在本次评测范围内,因为它们可能太年轻、太底层、缺乏异步支持、不支持 tokio 或是目前已经不再维护。
你可以在这里找到最新的 Web 框架列表:https://www.arewewebyet.org/topics/frameworks/
性能
上述的 3 款框架在基本应用的情况下都拥有足够强大的性能。因此我们不需要把时间浪费在这微不足道的测试上,它对于我们的日常开发起不到任何作用。如果您有特定的需求,比如每秒处理数百万请求,那么建议您自行完成相关测试。
生态环境与社区
一个好的 Web 框架需要一个优秀的社区为开发者提供帮助,以及一些第三方库帮助开发者节省时间,使开发者将注意力放在有意义的事情上。
本列表统计于 2022 年 2 月:
| 框架名 | Github Stars | 第三方库 | 官方示例数 | 开启的 Issue 数 | 完成的 Issue 数 | 
|---|---|---|---|---|---|
| actix-web | ~13.3k | ~500 | 57 | 95 | 1234 | 
| warp | ~6k | ~184 | 24 | 134 | 421 | 
| axum | ~3.6k | ~50 | 36 | 6 | 192 | 
获胜者:  actix-web 是目前拥有最好生态环境和社区的框架!(且它在 TechEmpower Web Framework Benchmarks 也拥有极高的排名!)
话虽如此,但是因为 axum 来自于 tokio 团队,所以说它的生态发展也非常快。
注:截止2023 年 4 月,actix-web 版本v4.3.1 下载量10,698,368 ,关注数17.3k, axum 版本v0.6.16 下载量11,592,805,关注数9.9k,
Json 反序列化
actix-web
#[derive(Debug, Serialize, Deserialize)]
struct Hello {
    message: String,
}
async fn index(item: web::Json) -> HttpResponse {
    HttpResponse::Ok().json(item.message) // <- send response
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .service(web::resource("/").route(web::post().to(index)))
    })
    .bind(("127.0.0.1", 8080))?
    .run()
    .await
}
 warp
#[derive(Debug, Serialize, Deserialize)]
struct Hello {
    message: String,
}
async fn index(item: Hello) -> Result<impl warp::Reply, Infallible> {
    Ok(warp::reply::json(&hello.message))
}
#[tokio::main]
async fn main() {
    let promote = warp::post()
        .and(warp::body::json())
        .map(index);
    warp::serve(promote).run(([127, 0, 0, 1], 8080)).await
}
axum
#[derive(Debug, Serialize, Deserialize)]
struct Hello {
    message: String,
}
async fn index(item: Json) ->impl IntoResponse { {
    Json(item.message)
}
#[tokio::main]
async fn main() {
    let app = Router::new().route("/", post(index));
    let addr = SocketAddr::from(([127, 0, 0, 1], 8080));
    axum::Server::bind(&addr)
        .serve(app.into_make_service())
        .await
        .unwrap();
}
 获胜者
它们都使用泛型提供了简单的 JSON 反序列化功能。
不过我认为 axum 和 actix-web 对于 JSON 的自动处理会更加方便直接。
路由
actix-web
fn main() {
    App::new()
        .service(web::resource("/").route(web::get().to(api::list)))
        .service(web::resource("/todo").route(web::post().to(api::create)))
        .service(web::resource("/todo/{id}")
          .route(web::post().to(api::update))
          .route(web::delete().to(api::delete)),
        );
}
warp
pub fn todos() -> impl Filterimpl warp::Reply, Error = warp::Rejection> + Clone {
    todos_list(db.clone())
        .or(todos_create(db.clone()))
        .or(todos_update(db.clone()))
        .or(todos_delete(db))
}
pub fn todos_list() -> impl Filterimpl warp::Reply, Error = warp::Rejection> + Clone {
    warp::path!("todos")
        .and(warp::get())
        .and(warp::query::())
        .and_then(handlers::list_todos)
}
pub fn todos_create() -> impl Filterimpl warp::Reply, Error = warp::Rejection> + Clone {
    warp::path!("todos")
        .and(warp::post())
        .and(json_body())
        .and_then(handlers::create_todo)
}
pub fn todos_update() -> impl Filterimpl warp::Reply, Error = warp::Rejection> + Clone {
    warp::path!("todos" / u64)
        .and(warp::put())
        .and(json_body())
        .and_then(handlers::update_todo)
}
pub fn todos_delete() -> impl Filterimpl warp::Reply, Error = warp::Rejection> + Clone {
    warp::path!("todos" / u64)
        .and(warp::delete())
        .and_then(handlers::delete_todo)
}
fn main() {
  let api = filters::todos(db);
  warp::serve(api).run(([127, 0, 0, 1], 8080)).await
}
      axum
    let app = Router::new()
        .route("/todos", get(todos_list).post(todos_create))
        .route("/todos/:id", patch(todos_update).delete(todos_delete));
获胜者
axum 是最简洁的,接下来便是 actix-web 了。
最后便是 warp ,它更偏向于通过函数的方式声明路由,这与其他框架的设计方案有一些区别。
中间件
actix-web
pub struct SayHi;
impl Transform for SayHi
where
    S: Service, Error = Error>,
    S::Future: 'static,
    B: 'static,
{
    type Response = ServiceResponse;
    type Error = Error;
    type InitError = ();
    type Transform = SayHiMiddleware;
    type Future = Ready<Result<Self::Transform, Self::InitError>>;
    fn new_transform(&self, service: S) -> Self::Future {
        ready(Ok(SayHiMiddleware { service }))
    }
}
pub struct SayHiMiddleware {
    service: S,
}
impl Service for SayHiMiddleware
where
    S: Service, Error = Error>,
    S::Future: 'static,
    B: 'static,
{
    type Response = ServiceResponse;
    type Error = Error;
    type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
    dev::forward_ready!(service);
    fn call(&self, req: ServiceRequest) -> Self::Future {
        println!("before");
        let fut = self.service.call(req);
        Box::pin(async move {
            let res = fut.await?;
            println!("after");
            Ok(res)
        })
    }
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
    App::new()
        .wrap(simple::SayHi)
        .service(
            web::resource("/").to(|| async {
                "Hello, middleware! Check the console where the server is run."
            }),
        )
}
   warp
pub fn json_bodySend>() -> impl Filter + Clone {
    warp::body::content_length_limit(1024 * 16).and(warp::body::json())
}
fn main() {
    let api = api.and(warp::path("jobs"))
      .and(warp::path::end())
      .and(warp::post())
      .and(json_body())
      .and_then(create_job);
}
  axum
#[derive(Clone)]
struct MyMiddleware {
    inner: S,
}
impl Service> for MyMiddleware
where
    S: Service, Response = Response> + Clone + Send + 'static,
    S::Future: Send + 'static,
{
    type Response = S::Response;
    type Error = S::Error;
    type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;
    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
        self.inner.poll_ready(cx)
    }
    fn call(&mut self, mut req: Request) -> Self::Future {
        println!("before");
        // best practice is to clone the inner service like this
        // see https://github.com/tower-rs/tower/issues/547 for details
        let clone = self.inner.clone();
        let mut inner = std::mem::replace(&mut self.inner, clone);
        Box::pin(async move {
            let res: Response = inner.call(req).await?;
            println!("after");
            Ok(res)
        })
    }
}
fn main() {
  let app = Router::new()
    .route("/", get(|| async { /* ... */ }))
    .layer(layer_fn(|inner| MyMiddleware { inner }));
}
  获胜者
这部分毫无疑问当然是:warp
共享状态
当开发者在构建 Web 服务时,常常需要共享一些变量,比如数据库连接池或是外部服务的一些连接。
actix-web
struct State {}
async fn index(
    state: Data>,
    req: HttpRequest,
) -> HttpResponse {
  // ...
}
#[actix_web::main]
async fn main() -> io::Result<()> {
    let state = Arc::new(State {});
    HttpServer::new(move || {
        App::new()
            .app_data(state.clone())
            .service(web::resource("/").to(index))
    })
    .bind(("127.0.0.1", 8080))?
    .run()
    .await
}
 warp
struct State {}
pub fn with_state(
    state: Arc,
) -> impl Filter,), Error = std::convert::Infallible> + Clone {
    warp::any().map(move || state.clone())
}
pub async fn create_job(
    state: Arc,
) -> Result<impl warp::Reply, warp::Rejection> {
    // ...
}
fn main() {
    let state = Arc::new(State{});
    let api = api.and(warp::path("jobs"))
      .and(warp::path::end())
      .and(warp::post())
      .and(with_state(state))
      .and_then(create_job);
}
   axum
struct State {}
async fn handler(
    Extension(state): Extension>,
) {
    // ...
}
fn main() {
    let shared_state = Arc::new(State {});
    let app = Router::new()
        .route("/", get(handler))
        .layer(AddExtensionLayer::new(shared_state));
}
 获胜者
这轮平局!它们在设计上都非常类似,使用起来都相对比较容易。
总结
我更倾向于 axum 框架,因为它有较为易用的 API 设计,且它是基于 hyper 构建的,且它是 tokio 开发组的产物。(它是一个非常年轻的框架,这点会使很多人不敢尝试)
对于大型项目来说 actix-web 是最好的选择!这也是为什么我在开发 Bloom 时选择了它。
对于较小型的项目来说 warp 就很棒了,尽管它的 API 较于原始,但它也是基于 hyper 开发的,所以说性能和安全性都有保障。
无论如何,只要拥有一个优秀的项目架构,从一个框架切换到另一个框架都会是很容易的事情,所以不用想太多,开干吧 :)
相关链接:
原文: https://kerkour.com/rust-web-framework-2022
翻译: https://github.com/mrxiaozhuox (YuKun Liu)
Rust Web 框架: https:// rustmagazine.github.io/rust_magazine_2022/Q1/opensource/web.html
作者:刘玉坤 
本篇文章为 @ 场长 创作并授权 21CTO 发布,未经许可,请勿转载。
内容授权事宜请您联系 webmaster@21cto.com或关注 21CTO 公众号。
该文观点仅代表作者本人,21CTO 平台仅提供信息存储空间服务。
 
                    请扫描二维码,使用微信支付哦。