actix-web
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseActix-web Framework Guide
Actix-web框架指南
Applies to: Actix-web 4+, Rust Web APIs, High-Performance Services Complements:.claude/skills/rust-guide/SKILL.md
适用范围:Actix-web 4+、Rust Web API、高性能服务 补充文档:.claude/skills/rust-guide/SKILL.md
Core Principles
核心原则
- Type-Safe Extraction: Use extractors (,
web::Json,web::Path) for request dataweb::Query - Thin Handlers: Handlers delegate to services; no business logic in handlers
- Structured Errors: Implement for all error types; never return raw strings
ResponseError - Shared State via : Application state is injected, never global
web::Data - Middleware for Cross-Cutting: Auth, logging, CORS belong in middleware, not handlers
- 类型安全提取:使用提取器(、
web::Json、web::Path)处理请求数据web::Query - 轻量处理器:处理器仅负责委托给服务层,不在处理器中编写业务逻辑
- 结构化错误:为所有错误类型实现;绝不返回原始字符串
ResponseError - 通过实现共享状态:应用状态通过注入方式提供,绝不使用全局状态
web::Data - 横切关注点中间件:认证、日志、CORS等逻辑应放在中间件中,而非处理器内
Project Structure
项目结构
myproject/
├── Cargo.toml
├── src/
│ ├── main.rs # Entry point, server bootstrap
│ ├── config.rs # Configuration loading
│ ├── routes.rs # Route registration
│ ├── handlers/
│ │ ├── mod.rs
│ │ ├── users.rs # User-related handlers
│ │ └── health.rs # Health check endpoint
│ ├── models/
│ │ ├── mod.rs
│ │ └── user.rs # Domain models + DTOs
│ ├── services/
│ │ ├── mod.rs
│ │ └── user_service.rs # Business logic layer
│ ├── repositories/
│ │ ├── mod.rs
│ │ └── user_repository.rs # Database access layer
│ ├── middleware/
│ │ ├── mod.rs
│ │ └── auth.rs # Authentication middleware
│ └── errors/
│ ├── mod.rs
│ └── app_error.rs # Centralized error types
├── tests/
│ └── integration_tests.rs
└── migrations/- bootstraps server, loads config, creates pool, wires routes
main.rs - Business logic lives in , not in handlers
services/ - Database access isolated in
repositories/ - One error enum per crate with impl
ResponseError
myproject/
├── Cargo.toml
├── src/
│ ├── main.rs # Entry point, server bootstrap
│ ├── config.rs # Configuration loading
│ ├── routes.rs # Route registration
│ ├── handlers/
│ │ ├── mod.rs
│ │ ├── users.rs # User-related handlers
│ │ └── health.rs # Health check endpoint
│ ├── models/
│ │ ├── mod.rs
│ │ └── user.rs # Domain models + DTOs
│ ├── services/
│ │ ├── mod.rs
│ │ └── user_service.rs # Business logic layer
│ ├── repositories/
│ │ ├── mod.rs
│ │ └── user_repository.rs # Database access layer
│ ├── middleware/
│ │ ├── mod.rs
│ │ └── auth.rs # Authentication middleware
│ └── errors/
│ ├── mod.rs
│ └── app_error.rs # Centralized error types
├── tests/
│ └── integration_tests.rs
└── migrations/- :入口文件,负责服务器启动
main.rs - 业务逻辑位于目录,而非处理器中
services/ - 数据库访问逻辑隔离在目录
repositories/ - 每个 crate 定义一个错误枚举并实现
ResponseError
Dependencies (Cargo.toml)
依赖项(Cargo.toml)
toml
[dependencies]
actix-web = "4"
actix-rt = "2"
actix-cors = "0.7"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
sqlx = { version = "0.7", features = ["runtime-tokio", "postgres", "uuid", "chrono"] }
validator = { version = "0.16", features = ["derive"] }
thiserror = "1.0"
anyhow = "1.0"
log = "0.4"
env_logger = "0.10"
tracing = "0.1"
tracing-actix-web = "0.7"
uuid = { version = "1.0", features = ["v4", "serde"] }
chrono = { version = "0.4", features = ["serde"] }
[dev-dependencies]
actix-rt = "2"toml
[dependencies]
actix-web = "4"
actix-rt = "2"
actix-cors = "0.7"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
sqlx = { version = "0.7", features = ["runtime-tokio", "postgres", "uuid", "chrono"] }
validator = { version = "0.16", features = ["derive"] }
thiserror = "1.0"
anyhow = "1.0"
log = "0.4"
env_logger = "0.10"
tracing = "0.1"
tracing-actix-web = "0.7"
uuid = { version = "1.0", features = ["v4", "serde"] }
chrono = { version = "0.4", features = ["serde"] }
[dev-dependencies]
actix-rt = "2"Application Entry Point
应用入口
rust
use actix_cors::Cors;
use actix_web::{middleware, web, App, HttpServer};
use sqlx::postgres::PgPoolOptions;
pub struct AppState {
pub pool: sqlx::PgPool,
pub config: config::Config,
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
dotenvy::dotenv().ok();
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
let config = config::Config::load().expect("Failed to load configuration");
let pool = PgPoolOptions::new()
.max_connections(10)
.connect(&config.database_url)
.await
.expect("Failed to create database pool");
sqlx::migrate!("./migrations")
.run(&pool)
.await
.expect("Failed to run migrations");
let app_state = web::Data::new(AppState {
pool,
config: config.clone(),
});
HttpServer::new(move || {
let cors = Cors::default()
.allow_any_origin()
.allow_any_method()
.allow_any_header()
.max_age(3600);
App::new()
.app_data(app_state.clone())
.wrap(cors)
.wrap(middleware::Logger::default())
.wrap(middleware::Compress::default())
.configure(routes::configure)
})
.bind(format!("{}:{}", config.host, config.port))?
.run()
.await
}rust
use actix_cors::Cors;
use actix_web::{middleware, web, App, HttpServer};
use sqlx::postgres::PgPoolOptions;
pub struct AppState {
pub pool: sqlx::PgPool,
pub config: config::Config,
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
dotenvy::dotenv().ok();
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
let config = config::Config::load().expect("Failed to load configuration");
let pool = PgPoolOptions::new()
.max_connections(10)
.connect(&config.database_url)
.await
.expect("Failed to create database pool");
sqlx::migrate!("./migrations")
.run(&pool)
.await
.expect("Failed to run migrations");
let app_state = web::Data::new(AppState {
pool,
config: config.clone(),
});
HttpServer::new(move || {
let cors = Cors::default()
.allow_any_origin()
.allow_any_method()
.allow_any_header()
.max_age(3600);
App::new()
.app_data(app_state.clone())
.wrap(cors)
.wrap(middleware::Logger::default())
.wrap(middleware::Compress::default())
.configure(routes::configure)
})
.bind(format!("{}:{}", config.host, config.port))?
.run()
.await
}Routing
路由配置
rust
use actix_web::web;
pub fn configure(cfg: &mut web::ServiceConfig) {
cfg.service(
web::scope("/api/v1")
.route("/health", web::get().to(handlers::health::health_check))
.service(
web::scope("/auth")
.route("/register", web::post().to(handlers::users::register))
.route("/login", web::post().to(handlers::users::login)),
)
.service(
web::scope("/users")
.wrap(crate::middleware::auth::AuthMiddleware)
.route("", web::get().to(handlers::users::list_users))
.route("/{id}", web::get().to(handlers::users::get_user))
.route("/{id}", web::put().to(handlers::users::update_user))
.route("/{id}", web::delete().to(handlers::users::delete_user)),
),
);
}rust
use actix_web::web;
pub fn configure(cfg: &mut web::ServiceConfig) {
cfg.service(
web::scope("/api/v1")
.route("/health", web::get().to(handlers::health::health_check))
.service(
web::scope("/auth")
.route("/register", web::post().to(handlers::users::register))
.route("/login", web::post().to(handlers::users::login)),
)
.service(
web::scope("/users")
.wrap(crate::middleware::auth::AuthMiddleware)
.route("", web::get().to(handlers::users::list_users))
.route("/{id}", web::get().to(handlers::users::get_user))
.route("/{id}", web::put().to(handlers::users::update_user))
.route("/{id}", web::delete().to(handlers::users::delete_user)),
),
);
}Routing Guardrails
路由规范
- Group routes by resource under
web::scope - Apply middleware at the scope level, not per-route
- Use for modular route registration
web::ServiceConfig - Version API routes ()
/api/v1/... - Keep route definitions separate from handler implementations
- 按资源分组路由,使用
web::scope - 在作用域级别应用中间件,而非单一路由
- 使用实现模块化路由注册
web::ServiceConfig - 为API路由添加版本标识()
/api/v1/... - 将路由定义与处理器实现分离
Extractors
提取器
Extractors are how Actix-web injects request data into handler functions.
| Extractor | Purpose | Example |
|---|---|---|
| Deserialize JSON body | |
| URL path parameters | |
| Query string parameters | |
| Shared application state | |
| URL-encoded form data | |
| Raw request (headers, extensions) | |
提取器是Actix-web将请求数据注入处理器函数的方式。
| 提取器 | 用途 | 示例 |
|---|---|---|
| 反序列化JSON请求体 | |
| URL路径参数 | |
| 查询字符串参数 | |
| 共享应用状态 | |
| URL编码表单数据 | |
| 原始请求(请求头、扩展信息) | |
Extractor Rules
提取器规则
- Always validate extracted data (use crate with
validatorderive)Validate - Prefer typed extractors over manually parsing
HttpRequest - Use for immutable shared state (wrapped in
web::Datainternally)Arc - Limit JSON body size with
.app_data(web::JsonConfig::default().limit(4096))
- 始终验证提取的数据(使用crate并派生
validatortrait)Validate - 优先使用类型化提取器,而非手动解析
HttpRequest - 使用存储不可变共享状态(内部通过
web::Data包装)Arc - 通过限制JSON请求体大小
.app_data(web::JsonConfig::default().limit(4096))
Handlers
处理器
Handlers are async functions that take extractors and return .
impl Responderrust
use actix_web::{web, HttpResponse};
pub async fn register(
state: web::Data<AppState>,
body: web::Json<CreateUserDto>,
) -> AppResult<HttpResponse> {
let service = UserService::new(state.pool.clone(), state.config.clone());
let response = service.register(body.into_inner()).await?;
Ok(HttpResponse::Created().json(response))
}
pub async fn get_user(
state: web::Data<AppState>,
path: web::Path<Uuid>,
) -> AppResult<HttpResponse> {
let service = UserService::new(state.pool.clone(), state.config.clone());
let user = service.get_user(path.into_inner()).await?;
Ok(HttpResponse::Ok().json(user))
}处理器是接收提取器并返回的异步函数。
impl Responderrust
use actix_web::{web, HttpResponse};
pub async fn register(
state: web::Data<AppState>,
body: web::Json<CreateUserDto>,
) -> AppResult<HttpResponse> {
let service = UserService::new(state.pool.clone(), state.config.clone());
let response = service.register(body.into_inner()).await?;
Ok(HttpResponse::Created().json(response))
}
pub async fn get_user(
state: web::Data<AppState>,
path: web::Path<Uuid>,
) -> AppResult<HttpResponse> {
let service = UserService::new(state.pool.clone(), state.config.clone());
let user = service.get_user(path.into_inner()).await?;
Ok(HttpResponse::Ok().json(user))
}Handler Rules
处理器规则
- Return (aliased as
Result<HttpResponse, AppError>)AppResult<HttpResponse> - Use to unwrap extractors before passing to services
.into_inner() - No business logic in handlers -- delegate to service layer
- One handler per HTTP method per resource action
- 返回(别名为
Result<HttpResponse, AppError>)AppResult<HttpResponse> - 在传递给服务层前,使用解包提取器
.into_inner() - 处理器中不编写业务逻辑——委托给服务层
- 每个资源操作对应一个HTTP方法的处理器
Application State
应用状态
rust
pub struct AppState {
pub pool: sqlx::PgPool,
pub config: Config,
}
// Register in HttpServer closure:
let app_state = web::Data::new(AppState { pool, config });
App::new().app_data(app_state.clone())
// Access in handlers:
pub async fn handler(state: web::Data<AppState>) -> impl Responder {
let pool = &state.pool;
// use pool...
}rust
pub struct AppState {
pub pool: sqlx::PgPool,
pub config: Config,
}
// 在HttpServer闭包中注册:
let app_state = web::Data::new(AppState { pool, config });
App::new().app_data(app_state.clone())
// 在处理器中访问:
pub async fn handler(state: web::Data<AppState>) -> impl Responder {
let pool = &state.pool;
// use pool...
}State Rules
状态规则
- Wrap in (internally uses
web::Data)Arc - Clone in
web::Dataclosure (cheap Arc clone)HttpServer::new - Keep state immutable; use if mutation is required
tokio::sync::RwLock - Do not store request-scoped data in
AppState
- 使用包装(内部使用
web::Data)Arc - 在闭包中克隆
HttpServer::new(Arc克隆成本低)web::Data - 保持状态不可变;若需修改,使用
tokio::sync::RwLock - 不要在中存储请求作用域的数据
AppState
Error Handling
错误处理
rust
use actix_web::{http::StatusCode, HttpResponse, ResponseError};
#[derive(Debug)]
pub enum AppError {
NotFound(String),
BadRequest(String),
Unauthorized(String),
Forbidden(String),
Conflict(String),
Validation(String),
Internal(String),
Database(sqlx::Error),
}
impl ResponseError for AppError {
fn error_response(&self) -> HttpResponse {
let (status, message) = match self {
AppError::NotFound(msg) => (StatusCode::NOT_FOUND, msg.clone()),
AppError::BadRequest(msg) => (StatusCode::BAD_REQUEST, msg.clone()),
AppError::Unauthorized(msg) => (StatusCode::UNAUTHORIZED, msg.clone()),
AppError::Forbidden(msg) => (StatusCode::FORBIDDEN, msg.clone()),
AppError::Conflict(msg) => (StatusCode::CONFLICT, msg.clone()),
AppError::Validation(msg) => (StatusCode::UNPROCESSABLE_ENTITY, msg.clone()),
AppError::Internal(_) => (StatusCode::INTERNAL_SERVER_ERROR, "Internal error".into()),
AppError::Database(_) => (StatusCode::INTERNAL_SERVER_ERROR, "Database error".into()),
};
HttpResponse::build(status).json(serde_json::json!({
"success": false,
"error": { "code": status.as_u16(), "message": message }
}))
}
}
pub type AppResult<T> = Result<T, AppError>;rust
use actix_web::{http::StatusCode, HttpResponse, ResponseError};
#[derive(Debug)]
pub enum AppError {
NotFound(String),
BadRequest(String),
Unauthorized(String),
Forbidden(String),
Conflict(String),
Validation(String),
Internal(String),
Database(sqlx::Error),
}
impl ResponseError for AppError {
fn error_response(&self) -> HttpResponse {
let (status, message) = match self {
AppError::NotFound(msg) => (StatusCode::NOT_FOUND, msg.clone()),
AppError::BadRequest(msg) => (StatusCode::BAD_REQUEST, msg.clone()),
AppError::Unauthorized(msg) => (StatusCode::UNAUTHORIZED, msg.clone()),
AppError::Forbidden(msg) => (StatusCode::FORBIDDEN, msg.clone()),
AppError::Conflict(msg) => (StatusCode::CONFLICT, msg.clone()),
AppError::Validation(msg) => (StatusCode::UNPROCESSABLE_ENTITY, msg.clone()),
AppError::Internal(_) => (StatusCode::INTERNAL_SERVER_ERROR, "Internal error".into()),
AppError::Database(_) => (StatusCode::INTERNAL_SERVER_ERROR, "Database error".into()),
};
HttpResponse::build(status).json(serde_json::json!({
"success": false,
"error": { "code": status.as_u16(), "message": message }
}))
}
}
pub type AppResult<T> = Result<T, AppError>;Error Handling Rules
错误处理规则
- Implement trait for all custom error types
ResponseError - Use impls for automatic conversion:
From<T>,sqlx::Errorvalidator::ValidationErrors - Log internal/database errors server-side; return generic messages to clients
- Define type alias to reduce boilerplate
AppResult<T>
- 为所有自定义错误类型实现trait
ResponseError - 使用实现自动转换:
From<T>、sqlx::Errorvalidator::ValidationErrors - 在服务器端记录内部/数据库错误;向客户端返回通用错误信息
- 定义类型别名以减少样板代码
AppResult<T>
Middleware
中间件
Actix-web middleware uses the + traits.
TransformServicerust
pub struct AuthMiddleware;
impl<S, B> Transform<S, ServiceRequest> for AuthMiddleware
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
B: 'static,
{
type Response = ServiceResponse<B>;
type Error = Error;
type Transform = AuthMiddlewareService<S>;
type InitError = ();
type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
ok(AuthMiddlewareService { service: Rc::new(service) })
}
}Actix-web中间件使用 + trait。
TransformServicerust
pub struct AuthMiddleware;
impl<S, B> Transform<S, ServiceRequest> for AuthMiddleware
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
B: 'static,
{
type Response = ServiceResponse<B>;
type Error = Error;
type Transform = AuthMiddlewareService<S>;
type InitError = ();
type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
ok(AuthMiddlewareService { service: Rc::new(service) })
}
}Middleware Rules
中间件规则
- Use trait for middleware factories,
Transformfor middleware logicService - Store authenticated user data in request extensions ()
req.extensions_mut().insert(...) - Apply middleware at scope level:
.service(web::scope("/protected").wrap(AuthMiddleware)) - Use built-in middleware: ,
Logger,Compress,NormalizePathDefaultHeaders - Order matters: middleware wraps from bottom to top (last runs first)
.wrap()
- 使用trait实现中间件工厂,
Transformtrait实现中间件逻辑Service - 将已认证用户数据存储在请求扩展中()
req.extensions_mut().insert(...) - 在作用域级别应用中间件:
.service(web::scope("/protected").wrap(AuthMiddleware)) - 使用内置中间件:、
Logger、Compress、NormalizePathDefaultHeaders - 中间件顺序重要:从下往上包装(最后调用的最先执行)
.wrap()
Configuration
配置管理
rust
use serde::Deserialize;
#[derive(Clone, Deserialize)]
pub struct Config {
pub host: String,
pub port: u16,
pub database_url: String,
pub jwt_secret: String,
pub jwt_expiration_hours: i64,
}
impl Config {
pub fn load() -> anyhow::Result<Self> {
let config = config::Config::builder()
.add_source(config::Environment::default().separator("__").try_parsing(true))
.set_default("host", "127.0.0.1")?
.set_default("port", 8080)?
.build()?;
Ok(config.try_deserialize()?)
}
}rust
use serde::Deserialize;
#[derive(Clone, Deserialize)]
pub struct Config {
pub host: String,
pub port: u16,
pub database_url: String,
pub jwt_secret: String,
pub jwt_expiration_hours: i64,
}
impl Config {
pub fn load() -> anyhow::Result<Self> {
let config = config::Config::builder()
.add_source(config::Environment::default().separator("__").try_parsing(true))
.set_default("host", "127.0.0.1")?
.set_default("port", 8080)?
.build()?;
Ok(config.try_deserialize()?)
}
}Commands
常用命令
bash
undefinedbash
undefinedDevelopment
开发环境
cargo run # Start server
cargo watch -x run # Watch mode (requires cargo-watch)
RUST_LOG=actix_web=debug cargo run # Debug logging
cargo run # 启动服务器
cargo watch -x run # 监听模式(需要cargo-watch)
RUST_LOG=actix_web=debug cargo run # 调试日志模式
Build
构建
cargo build --release # Production build
cargo check # Fast type-check
cargo build --release # 生产环境构建
cargo check # 快速类型检查
Quality
代码质量
cargo fmt # Format code
cargo clippy -- -D warnings # Lint with deny
cargo test # Run all tests
cargo tarpaulin # Coverage
cargo fmt # 代码格式化
cargo clippy -- -D warnings # 代码检查(拒绝警告)
cargo test # 运行所有测试
cargo tarpaulin # 测试覆盖率
Database
数据库操作
sqlx migrate run # Run migrations
sqlx migrate add <name> # Create migration
undefinedsqlx migrate run # 执行数据库迁移
sqlx migrate add <name> # 创建新迁移脚本
undefinedBest Practices
最佳实践
Application Structure
应用结构
- Use for shared application state
web::Data - Organize routes with and
web::scopeServiceConfig - Service layer for business logic, repository layer for data access
- Keep handlers thin: extract, delegate, respond
- 使用管理共享应用状态
web::Data - 用和
web::scope组织路由ServiceConfig - 服务层处理业务逻辑,仓储层处理数据访问
- 保持处理器轻量:提取数据、委托处理、返回响应
Performance
性能优化
- Use connection pooling (or
sqlx::PgPool)deadpool - Enable for response compression
middleware::Compress - Use async/await throughout; avoid blocking operations
- Configure worker count for production:
.workers(num_cpus::get()) - Set JSON payload limits to prevent abuse
- 使用连接池(或
sqlx::PgPool)deadpool - 启用进行响应压缩
middleware::Compress - 全程使用async/await;避免阻塞操作
- 为生产环境配置工作线程数:
.workers(num_cpus::get()) - 设置JSON请求体大小限制以防止滥用
Security
安全防护
- Validate all inputs with crate
validator - Use HTTPS in production (terminate at load balancer or use )
actix-tls - Implement rate limiting (or custom middleware)
actix-governor - Sanitize error messages: never expose internal details to clients
- Use with explicit origins in production (not
actix-cors)allow_any_origin
- 使用crate验证所有输入
validator - 生产环境使用HTTPS(在负载均衡器终止或使用)
actix-tls - 实现速率限制(或自定义中间件)
actix-governor - 净化错误信息:绝不向客户端暴露内部细节
- 生产环境使用时指定明确的源(而非
actix-cors)allow_any_origin
Testing
测试策略
- Use module for integration tests
actix_web::test - Create test app with and shared test state
test::init_service - Assert on status codes, response bodies, and headers
- Test middleware independently from handlers
- 使用模块进行集成测试
actix_web::test - 创建测试应用时使用和共享测试状态
test::init_service - 断言状态码、响应体和请求头
- 独立测试中间件与处理器
Advanced Topics
进阶主题
For detailed patterns and examples, see:
- references/patterns.md -- Handler examples, database integration, authentication, actors, testing patterns
如需详细模式与示例,请参考:
- references/patterns.md —— 处理器示例、数据库集成、认证、Actor、测试模式