导读:本文筛选了一些较为优秀的框架作比较(包含示例代码)。
框架列表
本列表统计于 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 平台仅提供信息存储空间服务。