本文筛选了一些较为优异的结构作比较(包括示例代码)

结构列表

本列表计算于 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
  • hyper
  • tide
  • rouille
  • iron
  • ….

但它们并没有被包括在本次评测范围内,由于它们可能太年青、太底层、缺乏异步支撑、不支撑tokio或是现在现已不再保护。

你能够在这里找到最新的 Web 结构列表:www.arewewebyet.org/topics/fram…

功用

上述的 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

注:截止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 反序列化功用。

不过我以为axumactix-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)