跳转到内容

Actix Web — Rust 上长期占据 TechEmpower 榜首的 web 框架

是什么

Actix Web 是一个用 Rust 写后端服务器的库,主打”又快又类型安全”。日常类比:像一辆手动挡跑车——比 Python/Node 那种自动挡的(FastAPI、Express)要多挂几次档(写类型、处理生命周期),换来的是车快得多。

你用 Actix Web 写一个 Hello World:

use actix_web::{get, App, HttpServer, Responder};
#[get("/hello/{name}")]
async fn greet(name: actix_web::web::Path<String>) -> impl Responder {
format!("Hello {name}!")
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| App::new().service(greet))
.bind(("127.0.0.1", 8080))?.run().await
}

它跑在 Tokio(Rust 的异步运行时,类似 Node 的事件循环)之上,TechEmpower 这种公开吞吐榜单上常年排前三,2026 年仍然是 Rust 后端的两大主流之一(另一个是 axum)。

为什么重要

不理解 Actix Web,下面这些事都没法解释:

  • 为什么”Rust 写 web”在 2017 年之后突然成了一个真选项——以前都说 Rust 难写,是它证明可以
  • 为什么 Rust 后端社区分裂成了 actix vs axum 两派——一派要”自由发挥”,一派要”严格泛型”
  • 为什么”actor 模型”这个词总被提起又总被绕开——actix 的名字带着它,但日常用法里又没它
  • 为什么 Rust 函数 handler 写起来像简单 Express,但报错动辄几十行——背后是类型系统在替你查活儿

核心要点

Actix Web 的设计可以拆成 三件事

  1. 多 worker 并行处理请求:HttpServer 启动后按 CPU 核数 spawn 多个 worker 线程(默认 = 核数),每个 worker 都跑一个独立的 App。类比:开了 8 个收银台,每个台都有完整的扫码枪和现金抽屉,互不干扰。

  2. 强类型 extractor 自动抽参数:handler 的参数类型决定了从请求里取什么。web::Path<String> 取 URL 路径段,web::Query<T> 取 querystring,web::Json<T> 取 JSON body。类比:你写一张领料单,写”我要 3 个螺丝 + 1 把扳手”,仓库自己按单子配齐,不需要你跑一趟仓库。

  3. App 是请求处理的”配置层”:路由、中间件、共享状态都挂在 App 上,每个 worker 启动时各自构造一份。状态如果要共享必须用 web::Data<T>(内部是 Arc)包一下,否则编译器不让你跨 worker。

三件事加起来叫”Actix Web 模型”。

实践案例

案例 1:最小的 GET 路由

use actix_web::{get, App, HttpServer, Responder};
#[get("/hello/{name}")]
async fn greet(name: actix_web::web::Path<String>) -> impl Responder {
format!("Hello {name}!")
}

逐部分解释

  • #[get("/hello/{name}")] 是宏,等同 Express 里 app.get('/hello/:name', ...)
  • web::Path<String> 这个类型告诉框架:“请把 URL 里 {name} 那段抽出来当字符串给我”
  • async fn 表示它是异步函数,返回 impl Responder(任何能转成 HTTP 响应的东西)

比 Go 的 net/http 紧凑(不用手动写 r.URL.Path),比 Express 多了类型保证。

案例 2:JSON API + 共享计数器

use actix_web::{get, post, web, App, HttpServer};
use std::sync::Mutex;
struct AppState { count: Mutex<u32> }
#[post("/inc")]
async fn inc(data: web::Data<AppState>) -> String {
let mut c = data.count.lock().unwrap();
*c += 1;
format!("now={}", *c)
}

逐部分解释

  • AppState 是自定义状态结构体,里面用 Mutexu32(多 worker 同时访问要加锁)
  • web::Data<AppState> 是 actix 提供的”共享句柄”,内部用 Arc 实现,所有 worker 拿到的是同一份
  • main 里要 App::new().app_data(web::Data::new(state.clone())) 注册一次

这是真实生产代码里 90% 的状态管理写法。

案例 3:JSON 表单自动反序列化

use actix_web::{post, web};
use serde::Deserialize;
#[derive(Deserialize)]
struct Signup { email: String, password: String }
#[post("/signup")]
async fn signup(form: web::Json<Signup>) -> String {
format!("registered: {}", form.email)
}

逐部分解释

  • #[derive(Deserialize)] 让 serde 库帮 Signup 实现 JSON 反序列化
  • web::Json<Signup> 让 actix 自动读 body,按 Signup 形状反序列化,类型不对自动 400

手写解析要 30 行(读 body + serde_json::from_slice + 错误处理),actix 一行搞定。

踩过的坑

  1. 共享状态忘了 web::Data 包装——直接传 Arc<Mutex<T>> 编译器会让你写一堆 trait bound,正确写法是 App::new().app_data(web::Data::new(state)),handler 里用 data: web::Data<T> 接。

  2. handler 里跑同步阻塞调用会卡死整个 worker——每个 worker 是单线程事件循环,std::thread::sleepstd::fs::read 大文件都会让该 worker 上所有连接饿死,应该用 tokio::fs 或把活儿丢给 web::block

  3. actor 模型不是日常用法——名字叫 actix 容易误导,新人以为必须懂 actor 才能写路由,其实 4.x 起函数 handler 才是主线,actor crate 只在 WebSocket 长连接才偶尔登场。

  4. 中间件顺序反直觉——wrap() 是栈式注册,最后注册的中间件最先执行进入请求,容易把日志加在认证之后导致未认证请求没被记录。

适用 vs 不适用场景

适用

  • 需要极限吞吐的内部服务(网关、API 转发、CDN 边缘)——TechEmpower 实测数据可信
  • 已经在用 Rust 的团队,想要一个老牌生产级 web 层
  • 需要长期支持 HTTP/2、WebSocket、流式响应的场景

不适用

  • 团队没人会 Rust——学习成本(生命周期、async)会让 MVP 推迟一两个月
  • 业务还在快速试错、接口天天改——Rust 改类型 + actix 改 extractor 比 Python 改字典慢得多,这种早期项目用 fastapiexpress 更合适
  • 新项目没历史负担、想要更现代 API ——直接选 axum(基于 tower 中间件生态);actix 适合既有 actix 生态的复用

历史小故事(可跳过)

  • 2017 年:Nikolay Kim(昵称 fafhrd91)个人项目起步,先做了 actix actor crate(自己写的 Erlang 风格 actor 库,参见 erlang-otp),再在它上面建 actix-web,故得名
  • 2018-2020 年:屡次在 TechEmpower benchmark 夺冠,Rust 社区知名度爆发,被认为是”Rust 能写 web”的活证据
  • 2020 年:因 unsafe 代码使用引发社区争议,fafhrd91 一度删库退出,社区接手维护,actix-web 4.0 起转为社区驱动,重心从 actor 模型转向函数 handler + extractor
  • 2024 年后:axum 凭借 tower 生态和更简洁 API 抢走了不少新项目,但 actix-web 仍是老牌生产级选择

学到什么

  1. 快不快不只看语言,还看运行时模型——同样 Rust,actix 的”多 worker × Tokio”组合比单线程同步快几十倍
  2. 类型驱动的 extractor 是 Rust web 框架的核心抽象——一行 web::Json<T> 替代了几十行手写解析
  3. 共享状态要显式 web::Data + 框架名字 ≠ 当前主流用法——比 Express 的 req.app.locals 啰嗦但编译期保证;actix-web 4.x 已经把 actor 推到边缘,新人不必先学 actor

延伸阅读

  • 官方文档:actix.rs(教程章节最适合零基础上手)
  • GitHub README:actix/actix-web(看一遍 README 就能跑起来)
  • 性能对照:TechEmpower Round 22+ actix vs axum 实测
  • fastapi —— 思想最像的 Python 框架(也是类型注解 → 自动抽参数)
  • express —— Node 老牌框架,对比体会”动态 vs 静态”两条路线

关联

  • gin —— Go 高性能 web 框架,定位接近,没 Rust 的类型推导但写法更轻
  • fiber —— Go 仿 Express 风格框架,比较”actix 类型重 vs fiber 链式轻”
  • fastapi —— Python 上类型驱动 web 的代表,extractor 思路相似
  • express —— JS 上动态类型 web 框架的鼻祖,反衬 actix 的”为什么要静态”
  • chi —— Go 极简路由库,对比 actix 的”宏 + 类型”重量级风格
  • encore —— 一站式 Go 后端框架,对比 actix 的”只管 web 层”
  • erlang-otp —— actor 模型的源头,actix 早期借鉴过 OTP 思想

反向链接

  • axum —— axum — 用 Rust 类型系统当『路由参数表』的 Web 框架
  • chi —— chi — Go 标准库友好的轻量 HTTP router
  • deno —— Deno — 安全优先的 JS/TS 运行时
  • encore —— Encore — 类型安全 Go/TS 后端框架,基础设施即代码
  • erlang-otp —— Erlang OTP — 容错并发系统设计
  • express —— Express — Node.js 最经典的 Web 框架
  • fastapi —— FastAPI — 用 Python 类型注解写 API
  • fiber —— Fiber — 把 Express 写法搬到 Go 上的高性能 web 框架
  • gin —— Gin — Go 写 web API 的事实标准框架
  • helidon —— Helidon — 让 Java 微服务用同步代码写出反应式性能
  • lucia —— Lucia — 主动把自己降级为”学习资源”的 TS 认证库
  • matrix-rust-sdk —— matrix-rust-sdk — Matrix 客户端的”共享发动机”
  • micronaut —— Micronaut — 编译期搞定 DI 的 JVM 云原生框架
  • plug —— Plug — 把 HTTP 中间件写成『conn 进 conn 出』的纯函数
  • poem —— poem — 一份 impl 块同时变 HTTP API + OpenAPI 文档站的 Rust 框架
  • robyn —— Robyn — Rust 运行时的高性能 Python Web 框架
  • rocket —— Rocket — 用 Rust attribute macro 把路由当函数签名写的 web 框架
  • salvo —— Salvo — 把中间件和处理器统一成一个 Handler trait 的 Rust web 框架
  • slim-framework —— Slim — PHP 圈最轻的 web 框架,专给小 API 用
  • spring-boot —— Spring Boot — 用 Auto-configuration 把 Java 后端从 XML 地狱里救出来的事实标准框架
  • tide —— Tide — async-std 阵营里 koa 风格的极简 Rust web 框架
  • warp —— warp — Rust 里把请求处理拼成 Filter 积木的 web 框架