فریمورک Actix Web چیست؟ سریع ترین فریمورک وب Rust
حدود ۶ دقیقه مطالعه
فریمورک Actix Web یکی از قدیمیترین و پراستفادهترین فریمورکهای وب توی دنیای Rustه. اولین نسخهش سال ۲۰۱۷ اومد و آخرین نسخهی پایدارش موقع نوشتن این مطلب ۴.۱۵ه که مرداد ۱۴۰۵ منتشر شد. بیا با هم ببینیم این فریمورک دقیقاً چیه، چطوری کار میکنه و کی میارزه بری سراغش.
اسمش از کجا اومده؟
کلمهی Actix از Actor میاد. پروژه اولش یه کتابخونهی مدل اکتور (actor model) بود و فریمورک وب روی همون ساخته شد. اگه مقالههای قدیمی رو بخونی، میبینی همهجا دارن از اکتورها حرف میزنن.
ولی الان دیگه داستان فرق کرده. نسخهی ۴ برای کارهای معمولی وب اصلاً اکتور لازم نداره و روی Tokio اجرا میشه، همون runtime آسنکرون معروف Rust. پس اگه از اسمش ترسیدی که حالا باید اول بشینی مدل اکتور یاد بگیری، خیالت راحت. برای ساختن یه API معمولی اصلاً بهش برنمیخوری.
بیا یه سرور ساده بسازیم
بهترین راه برای فهمیدنش اینه که کد رو ببینی. اول وابستگی رو به فایل Cargo اضافه کن:
[dependencies]
actix-web = "4"
حالا یه سرور با دو تا مسیر:
use actix_web::{get, web, App, HttpServer, Responder};
#[get("/")]
async fn index() -> impl Responder {
"Hello, World!"
}
#[get("/{name}")]
async fn hello(name: web::Path<String>) -> impl Responder {
format!("Hello {}!", &name)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| App::new().service(index).service(hello))
.bind(("127.0.0.1", 8080))?
.run()
.await
}
چندتا چیز اینجا مهمه. اون ماکرویی که بالای هر تابع گذاشتیم، مسیر و متد HTTP رو مشخص میکنه. تابعها هم async هستن و هر چیزی که این trait رو پیاده کرده باشه میتونن برگردونن:
Responder
یعنی رشته، JSON یا یه پاسخ کاملاً دستساز، همهشون قابل برگشتان.
اکستراکتورها، قلب ماجرا
به نظر من باحالترین بخش این فریمورک اکستراکتورها (extractors) هستن. لازم نیست خودت بشینی بدنهی درخواست رو بخونی و parse کنی؛ فقط نوع ورودی رو توی امضای تابع مینویسی و تمام:
use actix_web::{post, web, Responder};
use serde::Deserialize;
#[derive(Deserialize)]
struct NewUser {
name: String,
age: u8,
}
#[post("/users")]
async fn create_user(user: web::Json<NewUser>) -> impl Responder {
format!("Created {} ({})", user.name, user.age)
}
حالا فرض کن کلاینت بهجای عدد، برای سن یه رشته بفرسته. تابع تو اصلاً اجرا نمیشه و خود فریمورک یه خطای 400 برمیگردونه. همین ایده برای پارامتر مسیر، کوئریاسترینگ، فرم و هدرها هم جواب میده. خلاصه کلی از کد اعتبارسنجی که توی فریمورکهای دیگه دستی مینوشتی، اینجا کلاً حذف میشه.
سرعت، و چرا نباید زیادی روش حساب باز کنی
همهجا میشنوی که این فریمورک توی بنچمارک TechEmpower جزو سریعترینهاست. درسته، سالها توی صدر بوده. ولی بذار رک بگم: توی بیشتر پروژهها گلوگاهت دیتابیس و شبکهست، نه فریمورک. وقتی هر درخواست ۲۰ میلیثانیه منتظر PostgreSQL میمونه، فرق چند ده هزار درخواست در ثانیه دیگه خیلی معنی نداره.
چیزی که واقعاً حسش میکنی، مصرف رم پایین و تأخیر پایداره. سرویسی که با این فریمورک نوشتی معمولاً با چند ده مگابایت رم بالا میاد و زیر بار هم رفتارش قابل پیشبینیه. اگه سرور ارزون داری یا کلی کانتینر بالا میاری، این خیلی بیشتر از عدد بنچمارک به دردت میخوره.
چالهای که همه یه بار توش میافتن
یه نکته: سازندهی سرور یه closure میگیره، نه یه App آماده. چرا؟ چون فریمورک برای هر worker (معمولاً هر هستهی CPU) یه نمونهی جدا از App میسازه.
نتیجهش چی میشه؟ اگه داخل همون closure یه شمارنده یا کش بسازی، هر worker نسخهی خودش رو داره و دادهها بینشون شریک نمیشه. بعد میشینی دو ساعت دیباگ میکنی که چرا شمارندهت هی صفر میشه. راه درستش اینه که state رو بیرون بسازی و اینطوری به اپ بدی:
let counter = web::Data::new(AppState { hits: Mutex::new(0) });
HttpServer::new(move || {
App::new()
.app_data(counter.clone())
.service(index)
})
این نوع در واقع یه Arcه، پس کلون کردنش ارزونه و همهی workerها به یه داده اشاره میکنن:
web::Data<T>
مقایسه با Axum و Rocket
این روزها رقیب اصلیش Axumه که تیم Tokio نگهش میداره. فریمورک Axum روی اکوسیستم Tower ساخته شده و middlewareهاش با بقیهی کتابخونههای Tower جورن. فریمورک Rocket هم سینتکس خوشگلتری داره، ولی جامعهش کوچیکتره.
خب، با انتخاب Actix Web چی از دست میدی؟ سازگاری مستقیم با Tower. این فریمورک برای middleware سیستم خودش رو داره و نوشتنش یه کم پیچیدهتره. در عوض یه فریمورک جاافتاده با مستندات کامل، کلی مثال و ابزارهای آماده برای session، CORS، فایل استاتیک و WebSocket دستت میاد.
اگه نظر من رو بخوای: اگه داری یه پروژهی جدید از صفر شروع میکنی و قبلاً با Tokio و Tower کار کردی، Axum انتخاب طبیعیتریه. ولی اگه دنبال چیزی هستی که همهچی توش باشه و سالها توی پروداکشن تست شده باشه، این فریمورک هنوزم گزینهی خیلی خوبیه.
یه هشدار تاریخی
سال ۲۰۲۰ این پروژه یه دعوای معروف رو از سر گذروند. استفادهی زیاد از کد unsafe کلی انتقاد به پا کرد و سازندهی اصلی یه مدت پروژه رو ول کرد. بعدش نگهداریش افتاد دست یه تیم جدید و بخش زیادی از اون کدها بازنویسی شد.
چرا اینو میگم؟ چون اگه توی فرومها پستهای قدیمی دیدی که میگن «سمت Actix نرو، unsafeه»، بدونی مال پنج سال پیشه. پروژه الان فعاله، مرتب نسخهی جدید میده و حداقل نسخهی Rust که لازم داره ۱.۸۸ه.
از کجا شروع کنی؟
اگه تازه Rust یاد گرفتی، اول با همون مثال بالا یه API کوچیک CRUD بساز و با اکستراکتورها و state ور برو. مخزن examples خود پروژه هم پر از نمونهی آماده برای دیتابیس، احراز هویت و WebSocketه.
ولی یه سؤال هست که خودت باید جوابش رو بدی: واقعاً به سرعت Rust نیاز داری، یا فقط کنجکاوی؟ هر دوش دلیل قابلقبولیه. فقط بدون که زمان کامپایل و کلنجار رفتن با borrow checker هزینهی واقعی دارن، و این فریمورک قرار نیست این هزینه رو کم کنه؛ فقط خوب ازش استفاده میکنه.
منابع:
