17611538698
webmaster@21cto.com

Rust Web 框架怎么选

架构 0 3142 2023-06-13 10:32:56

图片

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

框架列表

本列表统计于 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

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

  • [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~50057951234
warp~6k~18424134421
axum~3.6k~50366192


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

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

相关链接:

  1. 原文: https://kerkour.com/rust-web-framework-2022

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

  3. Rust Web 框架: https:// rustmagazine.github.io/rust_magazine_2022/Q1/opensource/web.html


作者:刘玉坤

评论