actix-web

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Actix-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

核心原则

  1. Type-Safe Extraction: Use extractors (
    web::Json
    ,
    web::Path
    ,
    web::Query
    ) for request data
  2. Thin Handlers: Handlers delegate to services; no business logic in handlers
  3. Structured Errors: Implement
    ResponseError
    for all error types; never return raw strings
  4. Shared State via
    web::Data
    : Application state is injected, never global
  5. Middleware for Cross-Cutting: Auth, logging, CORS belong in middleware, not handlers
  1. 类型安全提取:使用提取器(
    web::Json
    web::Path
    web::Query
    )处理请求数据
  2. 轻量处理器:处理器仅负责委托给服务层,不在处理器中编写业务逻辑
  3. 结构化错误:为所有错误类型实现
    ResponseError
    ;绝不返回原始字符串
  4. 通过
    web::Data
    实现共享状态
    :应用状态通过注入方式提供,绝不使用全局状态
  5. 横切关注点中间件:认证、日志、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/
  • main.rs
    bootstraps server, loads config, creates pool, wires routes
  • Business logic lives in
    services/
    , not in handlers
  • Database access isolated in
    repositories/
  • One error enum per crate with
    ResponseError
    impl
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
    web::ServiceConfig
    for modular route registration
  • 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.
ExtractorPurposeExample
web::Json<T>
Deserialize JSON body
body: web::Json<CreateDto>
web::Path<T>
URL path parameters
path: web::Path<Uuid>
web::Query<T>
Query string parameters
query: web::Query<PaginationQuery>
web::Data<T>
Shared application state
state: web::Data<AppState>
web::Form<T>
URL-encoded form data
form: web::Form<LoginForm>
HttpRequest
Raw request (headers, extensions)
req: HttpRequest
提取器是Actix-web将请求数据注入处理器函数的方式。
提取器用途示例
web::Json<T>
反序列化JSON请求体
body: web::Json<CreateDto>
web::Path<T>
URL路径参数
path: web::Path<Uuid>
web::Query<T>
查询字符串参数
query: web::Query<PaginationQuery>
web::Data<T>
共享应用状态
state: web::Data<AppState>
web::Form<T>
URL编码表单数据
form: web::Form<LoginForm>
HttpRequest
原始请求(请求头、扩展信息)
req: HttpRequest

Extractor Rules

提取器规则

  • Always validate extracted data (use
    validator
    crate with
    Validate
    derive)
  • Prefer typed extractors over manually parsing
    HttpRequest
  • Use
    web::Data
    for immutable shared state (wrapped in
    Arc
    internally)
  • Limit JSON body size with
    .app_data(web::JsonConfig::default().limit(4096))
  • 始终验证提取的数据(使用
    validator
    crate并派生
    Validate
    trait)
  • 优先使用类型化提取器,而非手动解析
    HttpRequest
  • 使用
    web::Data
    存储不可变共享状态(内部通过
    Arc
    包装)
  • 通过
    .app_data(web::JsonConfig::default().limit(4096))
    限制JSON请求体大小

Handlers

处理器

Handlers are async functions that take extractors and return
impl Responder
.
rust
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 Responder
的异步函数。
rust
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
    Result<HttpResponse, AppError>
    (aliased as
    AppResult<HttpResponse>
    )
  • Use
    .into_inner()
    to unwrap extractors before passing to services
  • 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
    web::Data
    (internally uses
    Arc
    )
  • Clone
    web::Data
    in
    HttpServer::new
    closure (cheap Arc clone)
  • Keep state immutable; use
    tokio::sync::RwLock
    if mutation is required
  • Do not store request-scoped data in
    AppState
  • 使用
    web::Data
    包装(内部使用
    Arc
  • HttpServer::new
    闭包中克隆
    web::Data
    (Arc克隆成本低)
  • 保持状态不可变;若需修改,使用
    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
    ResponseError
    trait for all custom error types
  • Use
    From<T>
    impls for automatic conversion:
    sqlx::Error
    ,
    validator::ValidationErrors
  • Log internal/database errors server-side; return generic messages to clients
  • Define
    AppResult<T>
    type alias to reduce boilerplate
  • 为所有自定义错误类型实现
    ResponseError
    trait
  • 使用
    From<T>
    实现自动转换:
    sqlx::Error
    validator::ValidationErrors
  • 在服务器端记录内部/数据库错误;向客户端返回通用错误信息
  • 定义
    AppResult<T>
    类型别名以减少样板代码

Middleware

中间件

Actix-web middleware uses the
Transform
+
Service
traits.
rust
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中间件使用
Transform
+
Service
trait。
rust
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
    Transform
    trait for middleware factories,
    Service
    for middleware logic
  • 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
    ,
    NormalizePath
    ,
    DefaultHeaders
  • Order matters: middleware wraps from bottom to top (last
    .wrap()
    runs first)
  • 使用
    Transform
    trait实现中间件工厂,
    Service
    trait实现中间件逻辑
  • 将已认证用户数据存储在请求扩展中(
    req.extensions_mut().insert(...)
  • 在作用域级别应用中间件:
    .service(web::scope("/protected").wrap(AuthMiddleware))
  • 使用内置中间件:
    Logger
    Compress
    NormalizePath
    DefaultHeaders
  • 中间件顺序重要:从下往上包装(最后调用的
    .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
undefined
bash
undefined

Development

开发环境

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
undefined
sqlx migrate run # 执行数据库迁移 sqlx migrate add <name> # 创建新迁移脚本
undefined

Best Practices

最佳实践

Application Structure

应用结构

  • Use
    web::Data
    for shared application state
  • Organize routes with
    web::scope
    and
    ServiceConfig
  • 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 (
    sqlx::PgPool
    or
    deadpool
    )
  • Enable
    middleware::Compress
    for response compression
  • 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
    validator
    crate
  • Use HTTPS in production (terminate at load balancer or use
    actix-tls
    )
  • Implement rate limiting (
    actix-governor
    or custom middleware)
  • Sanitize error messages: never expose internal details to clients
  • Use
    actix-cors
    with explicit origins in production (not
    allow_any_origin
    )
  • 使用
    validator
    crate验证所有输入
  • 生产环境使用HTTPS(在负载均衡器终止或使用
    actix-tls
  • 实现速率限制(
    actix-governor
    或自定义中间件)
  • 净化错误信息:绝不向客户端暴露内部细节
  • 生产环境使用
    actix-cors
    时指定明确的源(而非
    allow_any_origin

Testing

测试策略

  • Use
    actix_web::test
    module for integration tests
  • Create test app with
    test::init_service
    and shared test state
  • 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、测试模式

External References

外部参考