Actix‑Web 详细介绍
Actix‑Web 是 Rust 生态最主流、高性能的 Web 框架,基于 Actix 异步 Actor 模型,主打高性能、内存安全、异步IO、类型安全,生产环境广泛使用。
版本说明:现在主流是 actix‑web v4(v4 基于 tokio 运行时,不再使用旧的 actix‑actor 原生运行时),v3及更早已经淘汰。
一、核心特点
- 高性能 基于 Tokio 异步运行时,零拷贝、高效的HTTP解析,性能对标 Go、Nginx 级别的吞吐量,Rust 没有GC,内存开销极低。
- 类型安全 路由、请求提取、响应、中间件全部编译期检查,很多错误在编译就暴露,避免运行时异常。
- Actor 模型(可选) 底层源自 Actix Actor,支持并发状态管理;web层可以不用Actor,直接写普通异步函数。
- 内置丰富能力 路由、请求提取、表单/JSON解析、中间件、静态文件、websocket、HTTPS、cookie、会话、限流。
- 可扩展生态 大量第三方 crate:数据库(sea‑orm/sqlx)、身份认证、模板渲染、openapi文档等。
缺点:学习曲线比 Python/Go 高;生态相比其他语言Web框架,部分功能需要自己组合 crate。
二、基础环境配置
Cargo.toml
[package]
name = "actix-demo"
version = "0.1.0"
edition = "2021"
[dependencies]
actix-web = "4.9"
serde = { version = "1.0", features = ["derive"] } # JSON序列化最小Hello World示例
use actix_web::{get, App, HttpServer, Responder};
#[get("/")]
async fn hello() -> impl Responder {
"Hello Actix‑Web!"
}
#[actix_web::main] // 启动tokio运行时
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.service(hello) // 注册路由
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}运行:cargo run,访问 http://127.0.0.1:8080
#[actix_web::main]宏,等价于#[tokio::main],自动启动异步运行时。
三、核心概念
1. 路由(Route)
支持:#[get]、#[post]、#[put]、#[delete]、#[patch] 宏;也可以手动配置路由。
路径参数
#[get("/user/{id}")]
async fn get_user(id: web::Path<u64>) -> impl Responder {
format!("用户ID: {}", id)
}web::Path<T> 提取URL路径参数,T会自动反序列化,类型错误直接返回404。
查询参数
use actix_web::web;
#[get("/search")]
async fn search(query: web::Query<std::collections::HashMap<String, String>>) -> impl Responder {
format!("查询参数:{:?}", query)
}2. 请求提取器(Extractors)
Actix‑Web 最强大的设计:提取器,从Request里解析数据,编译期校验。 常用提取器:
| 提取器 | 作用 |
|---|---|
web::Path<T> | URL路径参数 |
web::Query<T> | URL查询参数 |
web::Json<T> | 请求Body JSON,需要T实现Deserialize |
web::Form<T> | 表单提交application/x‑www‑form‑urlencoded |
web::Bytes | 原始二进制body |
web::Data<T> | 全局共享状态(应用单例) |
HttpRequest | 原始请求对象 |
JSON 请求示例
use serde::Deserialize;
use actix_web::{post, web, Responder};
#[derive(Deserialize)]
struct User {
name: String,
age: u8,
}
#[post("/user")]
async fn create_user(body: web::Json<User>) -> impl Responder {
format!("收到用户:{},年龄{}", body.name, body.age)
}如果JSON格式不对,actix自动返回400错误,不需要自己写解析错误处理。
3. 响应 Responder
实现Responder trait的类型都可以返回:
&str/Stringweb::Json(结构体)返回JSON响应HttpResponse手动构造完整响应码、headerResult<impl Responder, Error>错误返回
手动构造响应:
use actix_web::HttpResponse;
#[get("/404")]
async fn not_found() -> HttpResponse {
HttpResponse::NotFound().body("页面不存在")
}返回JSON响应:
#[derive(Serialize)]
struct Resp {
code: i32,
msg: String,
}
#[get("/api")]
async fn api() -> impl Responder {
web::Json(Resp {
code: 0,
msg: "ok".into(),
})
}4. 应用状态 Data(全局共享)
多个handler共享数据库连接池、配置等全局对象,使用web::Data。
use actix_web::{web, App, HttpServer};
// 全局状态
struct AppState {
db_pool: String,
}
#[get("/state")]
async fn show_state(data: web::Data<AppState>) -> impl Responder {
data.db_pool.clone()
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.app_data(web::Data::new(AppState{db_pool:"pool1".into()}))
.service(show_state)
})
.bind(("127.0.0.1",8080))?
.run().await
}注意:
Data内部是Arc,线程安全;不要把可变状态直接放Data,要用锁Mutex/RwLock。
5. 中间件 Middleware
Actix‑Web中间件处理请求前后逻辑:日志、CORS、鉴权、限流。 内置中间件:
Logger:访问日志(必用)Compress:gzip压缩Cors:跨域处理
示例:开启日志和CORS
use actix_web::{middleware, App, HttpServer};
use actix_web_cors::Cors;
HttpServer::new(||{
App::new()
.wrap(middleware::Logger::default()) // 请求日志
.wrap(Cors::permissive()) // 开发环境允许所有跨域
.service(hello)
})6. 作用域 Scope(路由分组)
可以把路由分组,统一前缀,方便管理接口版本。
use actix_web::web;
// /api/v1/user
let api_v1 = web::scope("/api/v1")
.service(web::resource("/user").get(get_user).post(create_user));
App::new().service(api_v1);7. WebSocket
Actix‑Web原生支持websocket,基于Stream/Sink。
四、错误处理
Actix‑Web handler可以返回Result<T, E>,E需要实现ResponseError trait。
use actix_web::{error::ResponseError, HttpResponse};
#[derive(Debug)]
enum MyError {
DbError,
BadParam,
}
impl ResponseError for MyError {
fn error_response(&self) -> HttpResponse {
match self {
MyError::DbError => HttpResponse::InternalServerError().body("数据库错误"),
MyError::BadParam => HttpResponse::BadRequest().body("参数错误"),
}
}
}
#[get("/err")]
async fn test_err() -> Result<impl Responder, MyError> {
Err(MyError::BadParam)
}五、HttpServer 配置
HttpServer::new(|| App) 闭包每一个worker线程都会调用一次,每个worker拥有独立的App实例。
.workers(4)设置工作线程数,默认等于CPU核心数.bind()绑定地址.run().await启动服务
重要:App里面的资源不要在闭包外面创建,要在闭包内部创建,每个worker独立一份。
六、常用生态配套
- 数据库
sqlx:异步SQL,原生支持Postgres/MySQL/SQLite,推荐sea‑orm:ORM框架,功能完善
- 模板渲染:
tera - OpenAPI文档:
utoipa,自动生成swagger文档 - 认证:
actix‑web‑auth,JWT - 静态文件服务:
actix‑web::fs::Files - Session会话:
actix‑session
七、Actix‑Web 工作原理
- 底层Tokio异步运行时,多worker线程模型;
- 每个worker是独立事件循环,每个worker拥有自己App实例;
- 请求进来,匹配路由,调用提取器解析请求;
- 执行handler异步函数;
- handler返回Responder,框架自动转为HTTP响应。
注意:handler函数必须是
async,所有IO操作必须使用tokio异步API,不要在handler里面写阻塞代码,阻塞会卡住整个worker线程。
八、常见坑
- ❌ 在handler使用阻塞std::io,会阻塞整个worker;要使用tokio异步版本。
- ❌ 全局状态直接放
Data,没有加锁,多线程修改导致UB;使用Arc<Mutex<T>>。 - ❌ App实例在HttpServer闭包外面构造,导致多个worker共享同一个App,引发问题。
- ❌ 忘记
#[actix_web::main],直接写#[tokio::main],虽然可以运行,但不推荐。 - 提取器参数顺序不能乱:提取器必须放在handler参数前面。
九、对比其他Rust Web框架
- Actix‑Web:成熟,高性能,生态完善,生产首选;学习成本中等。
- Axum:tokio官方,API简洁,更轻量化,社区增长快;缺少内置中间件。
- Rocket:编译期宏多,开发体验好,性能略低。
示例项目
那我给你整理一份Actix‑Web 完整可运行RESTful小项目模板,包含:路由分组、JSON请求响应、全局状态、自定义错误、CORS、日志、静态文件,直接复制就能跑。
Cargo.toml
[package]
name = "actix-rest-demo"
version = "0.1.0"
edition = "2021"
[dependencies]
actix-web = "4.9"
serde = { version = "1.0", features = ["derive"] }
actix-cors = "0.6"src/main.rs
use actix_cors::Cors;
use actix_web::{
error::ResponseError, get, post, web, App, HttpResponse, HttpServer, Responder,
};
use serde::{Deserialize, Serialize};
use std::fmt;
// ========== 全局应用状态 ==========
#[derive(Clone)]
struct AppState {
app_name: String,
}
// ========== 自定义业务错误 ==========
#[derive(Debug)]
enum ApiError {
NotFound,
BadRequest(String),
}
impl fmt::Display for ApiError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ApiError::NotFound => write!(f, "资源不存在"),
ApiError::BadRequest(msg) => write!(f, "参数错误: {}", msg),
}
}
}
impl ResponseError for ApiError {
fn error_response(&self) -> HttpResponse {
match self {
ApiError::NotFound => HttpResponse::NotFound().body(self.to_string()),
ApiError::BadRequest(msg) => HttpResponse::BadRequest().body(msg),
}
}
}
// ========== 请求/响应结构体 ==========
#[derive(Deserialize)]
struct CreateUserReq {
name: String,
age: u8,
}
#[derive(Serialize)]
struct UserResp {
id: u64,
name: String,
age: u8,
}
// ========== Handler 处理器 ==========
#[get("/")]
async fn index(data: web::Data<AppState>) -> impl Responder {
format!("欢迎使用 {},Actix‑Web Demo", data.app_name)
}
#[get("/user/{id}")]
async fn get_user(path: web::Path<u64>) -> Result<impl Responder, ApiError> {
let id = path.into_inner();
if id > 100 {
return Err(ApiError::NotFound);
}
Ok(web::Json(UserResp {
id,
name: format!("user_{}", id),
age: 20,
}))
}
#[post("/user")]
async fn create_user(body: web::Json<CreateUserReq>) -> impl Responder {
web::Json(UserResp {
id: 1,
name: body.name.clone(),
age: body.age,
})
}
#[get("/search")]
async fn search(query: web::Query<std::collections::HashMap<String, String>>) -> impl Responder {
format!("查询参数: {:?}", query)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
println!("服务启动: http://127.0.0.1:8080");
HttpServer::new(|| {
// 每个worker都会新建App实例
let app_state = AppState {
app_name: "Actix‑REST‑Demo".to_string(),
};
App::new()
.app_data(web::Data::new(app_state))
// 跨域
.wrap(
Cors::default()
.allow_any_origin()
.allow_any_method()
.allow_any_header(),
)
// 访问日志
.wrap(actix_web::middleware::Logger::default())
// 根路由
.service(index)
// API分组 /api/v1
.service(
web::scope("/api/v1")
.service(get_user)
.service(create_user)
.service(search),
)
// 静态文件:访问 /static 读取 ./static 目录
.service(actix_web::fs::Files::new("/static", "./static").show_files_listing())
})
.bind(("127.0.0.1", 8080))?
.workers(2)
.run()
.await
}运行&测试
- 创建文件夹
static,放静态资源 cargo run- 测试接口:
GET http://127.0.0.1:8080GET http://127.0.0.1:8080/api/v1/user/10POST http://127.0.0.1:8080/api/v1/userbody{"name":"test","age":22}GET http://127.0.0.1:8080/api/v1/search?keyword=rust
重点要点回顾
web::Data全局状态,内部Arc,可变状态需要Mutex/RwLock- Handler 返回
Result<impl Responder, ApiError>统一错误处理 web::scope做接口版本分组- HttpServer闭包内部构建App,每个worker独立实例
- 不要在handler写阻塞IO,会拖垮整个worker线程
如果你需要,我可以继续拓展:
- 集成 sqlx 数据库连接池示例
- JWT鉴权中间件
- 自动生成OpenAPI文档(utoipa)
- websocket示例