Skip to content

Actix‑Web 详细介绍 ​

Actix‑Web 是 Rust 生态最主流、高性能的 Web 框架,基于 Actix 异步 Actor 模型,主打高性能、内存安全、异步IO、类型安全,生产环境广泛使用。

版本说明:现在主流是 actix‑web v4(v4 基于 tokio 运行时,不再使用旧的 actix‑actor 原生运行时),v3及更早已经淘汰。

一、核心特点 ​

  1. 高性能 基于 Tokio 异步运行时,零拷贝、高效的HTTP解析,性能对标 Go、Nginx 级别的吞吐量,Rust 没有GC,内存开销极低。
  2. 类型安全 路由、请求提取、响应、中间件全部编译期检查,很多错误在编译就暴露,避免运行时异常。
  3. Actor 模型(可选) 底层源自 Actix Actor,支持并发状态管理;web层可以不用Actor,直接写普通异步函数。
  4. 内置丰富能力 路由、请求提取、表单/JSON解析、中间件、静态文件、websocket、HTTPS、cookie、会话、限流。
  5. 可扩展生态 大量第三方 crate:数据库(sea‑orm/sqlx)、身份认证、模板渲染、openapi文档等。

缺点:学习曲线比 Python/Go 高;生态相比其他语言Web框架,部分功能需要自己组合 crate。

二、基础环境配置 ​

Cargo.toml ​

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示例 ​

rust
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] 宏;也可以手动配置路由。

路径参数 ​

rust
#[get("/user/{id}")]
async fn get_user(id: web::Path<u64>) -> impl Responder {
    format!("用户ID: {}", id)
}

web::Path<T> 提取URL路径参数,T会自动反序列化,类型错误直接返回404。

查询参数 ​

rust
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 请求示例 ​

rust
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 / String
  • web::Json(结构体) 返回JSON响应
  • HttpResponse 手动构造完整响应码、header
  • Result<impl Responder, Error> 错误返回

手动构造响应:

rust
use actix_web::HttpResponse;

#[get("/404")]
async fn not_found() -> HttpResponse {
    HttpResponse::NotFound().body("页面不存在")
}

返回JSON响应:

rust
#[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。

rust
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

rust
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(路由分组) ​

可以把路由分组,统一前缀,方便管理接口版本。

rust
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。

rust
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独立一份。

六、常用生态配套 ​

  1. 数据库
    • sqlx:异步SQL,原生支持Postgres/MySQL/SQLite,推荐
    • sea‑orm:ORM框架,功能完善
  2. 模板渲染:tera
  3. OpenAPI文档:utoipa,自动生成swagger文档
  4. 认证:actix‑web‑auth,JWT
  5. 静态文件服务:actix‑web::fs::Files
  6. Session会话:actix‑session

七、Actix‑Web 工作原理 ​

  1. 底层Tokio异步运行时,多worker线程模型;
  2. 每个worker是独立事件循环,每个worker拥有自己App实例;
  3. 请求进来,匹配路由,调用提取器解析请求;
  4. 执行handler异步函数;
  5. handler返回Responder,框架自动转为HTTP响应。

注意:handler函数必须是async,所有IO操作必须使用tokio异步API,不要在handler里面写阻塞代码,阻塞会卡住整个worker线程。

八、常见坑 ​

  1. ❌ 在handler使用阻塞std::io,会阻塞整个worker;要使用tokio异步版本。
  2. ❌ 全局状态直接放Data,没有加锁,多线程修改导致UB;使用Arc<Mutex<T>>。
  3. ❌ App实例在HttpServer闭包外面构造,导致多个worker共享同一个App,引发问题。
  4. ❌ 忘记#[actix_web::main],直接写#[tokio::main],虽然可以运行,但不推荐。
  5. 提取器参数顺序不能乱:提取器必须放在handler参数前面。

九、对比其他Rust Web框架 ​

  • Actix‑Web:成熟,高性能,生态完善,生产首选;学习成本中等。
  • Axum:tokio官方,API简洁,更轻量化,社区增长快;缺少内置中间件。
  • Rocket:编译期宏多,开发体验好,性能略低。

示例项目 ​

那我给你整理一份Actix‑Web 完整可运行RESTful小项目模板,包含:路由分组、JSON请求响应、全局状态、自定义错误、CORS、日志、静态文件,直接复制就能跑。

Cargo.toml ​

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 ​

rust
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
}

运行&测试 ​

  1. 创建文件夹 static,放静态资源
  2. cargo run
  3. 测试接口:
  • GET http://127.0.0.1:8080
  • GET http://127.0.0.1:8080/api/v1/user/10
  • POST http://127.0.0.1:8080/api/v1/user body {"name":"test","age":22}
  • GET http://127.0.0.1:8080/api/v1/search?keyword=rust

重点要点回顾 ​

  1. web::Data 全局状态,内部Arc,可变状态需要Mutex/RwLock
  2. Handler 返回 Result<impl Responder, ApiError> 统一错误处理
  3. web::scope 做接口版本分组
  4. HttpServer闭包内部构建App,每个worker独立实例
  5. 不要在handler写阻塞IO,会拖垮整个worker线程

如果你需要,我可以继续拓展:

  • 集成 sqlx 数据库连接池示例
  • JWT鉴权中间件
  • 自动生成OpenAPI文档(utoipa)
  • websocket示例