likes
comments
collection
share

Rust Web 框架怎么选

作者站长头像
站长
· 阅读数 3

本文筛选了一些较为优秀的框架作比较(包含示例代码)

框架列表

本列表统计于 2022 年 2 月:

框架名称版本号总下载量框架简介
actix-web4.0.0-rc.35,134,720一个强大、实用、高效的 Rust web 框架
warp0.3.24,114,095构建极其高效的 Web 服务
axum0.4.5235,150专注于人机工程学和模块化的 Web 框架

注:截止2023 年 4 月,actix-web 版本v4.3.1 下载量10,698,368 , axum 版本v0.6.16  下载量11,592,805

还有许多其他优秀的框架:

但它们并没有被包括在本次评测范围内,因为它们可能太年轻、太底层、缺乏异步支持、不支持 tokio 或是目前已经不再维护。

你可以在这里找到最新的 Web 框架列表:www.arewewebyet.org/topics/fram…

性能

上述的 3 款框架在基本应用的情况下都拥有足够强大的性能。因此我们不需要把时间浪费在这微不足道的测试上,它对于我们的日常开发起不到任何作用。如果您有特定的需求,比如每秒处理数百万请求,那么建议您自行完成相关测试。

生态环境与社区

一个好的 Web 框架需要一个优秀的社区为开发者提供帮助,以及一些第三方库帮助开发者节省时间,使开发者将注意力放在有意义的事情上。

本列表统计于 2022 年 2 月:

框架名Github Stars第三方库官方示例数开启的 Issue 数完成的 Issue 数
actix-web~13.3k~50057951234
warp~6k~18424134421
axum~3.6k~50366192

注:截止2023 年 4 月,actix-web 版本v4.3.1 下载量10,698,368 ,关注数17.3k, axum 版本v0.6.16  下载量11,592,805,关注数9.9k,

获胜者:  actix-web 是目前拥有最好生态环境和社区的框架!(且它在 TechEmpower Web Framework Benchmarks 也拥有极高的排名!)

话虽如此,但是因为 axum 来自于 tokio 团队,所以说它的生态发展也非常快。

Json 反序列化

actix-web

#[derive(Debug, Serialize, Deserialize)]
struct Hello {
    message: String,
}

async fn index(item: web::Json<Hello>) -> 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<Hello>) ->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 Filter<Extract = impl 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 Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
    warp::path!("todos")
        .and(warp::get())
        .and(warp::query::<ListOptions>())
        .and_then(handlers::list_todos)
}

pub fn todos_create() -> impl Filter<Extract = impl 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 Filter<Extract = impl 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 Filter<Extract = impl 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<S, B> Transform<S, ServiceRequest> for SayHi
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
    S::Future: 'static,
    B: 'static,
{
    type Response = ServiceResponse<B>;
    type Error = Error;
    type InitError = ();
    type Transform = SayHiMiddleware<S>;
    type Future = Ready<Result<Self::Transform, Self::InitError>>;

    fn new_transform(&self, service: S) -> Self::Future {
        ready(Ok(SayHiMiddleware { service }))
    }
}

pub struct SayHiMiddleware<S> {
    service: S,
}

impl<S, B> Service<ServiceRequest> for SayHiMiddleware<S>
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
    S::Future: 'static,
    B: 'static,
{
    type Response = ServiceResponse<B>;
    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_body<T: DeserializeOwned + Send>() -> impl Filter<Extract = (T,), Error = warp::Rejection> + 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<S> {
    inner: S,
}

impl<S> Service<Request<Body>> for MyMiddleware<S>
where
    S: Service<Request<Body>, 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<Body>) -> 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<Arc<State>>,
    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<State>,
) -> impl Filter<Extract = (Arc<State>,), Error = std::convert::Infallible> + Clone {
    warp::any().map(move || state.clone())
}

pub async fn create_job(
    state: Arc<AppState>,
) -> 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<Arc<State>>,
) {
    // ...
}

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 开发的,所以说性能和安全性都有保障。

无论如何,只要拥有一个优秀的项目架构,从一个框架切换到另一个框架都会是很容易的事情,所以不用想太多,开干吧 :)

原文: kerkour.com/rust-web-fr…

翻译: github.com/mrxiaozhuox (YuKun Liu)