Actix Web使用指南
介绍
欢迎来到 Actix
Actix Web 能让你在 Rust 语言中快速且有把握地开发 Web 服务,本指南将助你迅速上手。
本网站的文档主要聚焦于 Actix Web 框架。若你需要了解名为 Actix 的 Actor 框架相关信息,可查看 Actix 章节(或底层的 actix API 文档)。除此之外,你也可以直接前往入门指南。要是你已熟悉相关操作,且需要特定信息,或许可以阅读 Actix Web API 文档。
Actix Web 是 crate 生态系统的一部分
很久以前,Actix Web 构建于 actix actor 框架之上。如今,Actix Web 与该 actor 框架已基本无关,而是基于另一种系统构建。尽管 actix 仍在维护中,但随着 futures 与 async/await 生态的成熟,其作为通用工具的实用性正逐渐降低。目前,仅在使用 WebSocket 端点时才需要用到 actix。
我们称 Actix Web 为一款功能强大且注重实用的框架。实际上,它本质上是一款略带特色的微框架。如果你已是一名 Rust 开发者,大概率能快速上手;即便你来自其他编程语言,也会发现 Actix Web 很容易掌握。
使用 Actix Web 开发的应用会对外提供一个内嵌于原生可执行文件中的 HTTP 服务器。你既可以将其部署在 nginx 等其他 HTTP 服务器之后,也可以直接使用它提供服务。即便完全不依赖其他 HTTP 服务器,Actix Web 也具备足够强大的功能,支持 HTTP/1、HTTP/2 以及 TLS(即 HTTPS)。这使得它非常适合构建可直接投入生产环境的小型服务。
最重要的一点是:Actix Web 可运行于 Rust 1.72 及更高版本,且支持稳定版 Rust。
基础
安装 Rust
若你尚未安装 Rust,建议使用 rustup 管理你的 Rust 安装。官方 Rust 指南中有非常详尽的入门章节。
Actix Web 目前的最低支持 Rust 版本(MSRV)为 1.72。运行 rustup update 可确保你拥有当前可用的最新版 Rust。因此,本指南默认你使用的是 Rust 1.72 或更高版本。
Hello, world!(你好,世界!)
首先创建一个新的二进制 Cargo 项目,并进入该新项目目录:
1 | cargo new hello-world |
在项目的 Cargo.toml 文件中添加以下内容,将 actix-web 作为项目依赖:
1 | [dependencies] |
请求处理器使用异步函数(async functions)实现,这类函数可接收零个或多个参数。这些参数可从请求中提取(参见 FromRequest 特质),且函数返回值需是可转换为 HttpResponse 的类型(参见 Responder 特质):
将 src/main.rs 文件的内容替换为以下代码:
1 | use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder}; |
注意,部分处理器通过内置宏直接附加了路由信息。这些宏允许你指定处理器对应的请求方法和路径。下文将介绍如何注册 manual_hello(即不使用路由宏的路由)。
接下来,创建一个 App 实例并注册请求处理器:
- 对于使用路由宏的处理器,使用 App::service 方法注册;
- 对于手动路由的处理器,使用 App::route 方法注册,同时声明路径和请求方法。
最后,在 HttpServer 中启动应用 ——HttpServer 会将你的 App 作为 “应用工厂”,用于处理传入的请求。
在 src/main.rs 中补充以下 main 函数:
1 |
|
至此,所有步骤已完成!运行 cargo run 编译并启动程序。其中,#[actix_web::main] 宏会在 Actix 运行时(runtime)中执行异步 main 函数。现在,你可以访问 http://127.0.0.1:8080/ 或其他你定义的路由,查看运行结果。
编写应用 | Actix Web
actix-web 提供了多种基础组件,助力开发者使用 Rust 构建 Web 服务器与应用,涵盖路由、中间件、请求预处理、响应后处理等功能。
所有 actix-web 服务器均围绕 App 实例构建。App 不仅用于为资源和中间件注册路由,还会存储同一作用域内所有处理器(handler)可共享的应用状态(state)。
应用作用域(Scope)
应用的 作用域(scope) 相当于所有路由的 “命名空间”—— 即某一特定应用作用域下的所有路由,都拥有相同的 URL 路径前缀。
应用前缀必须以 / 斜杠开头:若传入的前缀不含开头斜杠,框架会自动补充。此外,前缀需由有效路径段(value path segments)组成。
以作用域为 /app 的应用为例:
- 路径为 /app、/app/ 或 /app/test 的请求会匹配该作用域;
- 路径为 /application 的请求则不会匹配。在上述示例中,我们创建了一个前缀为 /app 且包含 index.html 资源的应用。该资源可通过 /app/index.html 这个 URL 访问。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20use actix_web::{web, App, HttpServer, Responder};
async fn index() -> impl Responder {
"Hello world!"
}
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new().service(
// 为所有附加到该作用域的资源和路由添加前缀...
web::scope("/app")
// ...因此该路由会处理 `GET /app/index.html` 的请求
.route("/index.html", web::get().to(index)),
)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
状态(State)
应用状态在同一作用域内的所有路由和资源间共享。通过 web::Data
下面编写一个简单应用,将应用名称存储在状态中:
1 | use actix_web::{get, web, App, HttpServer}; |
接下来,在初始化 App 时传入状态,并启动应用:
1 |
|
一个应用中可注册任意数量不同类型的状态。
共享可变状态(Shared Mutable State)
HttpServer
接收的是应用工厂(application factory),而非应用实例。HttpServer
会为每个线程创建一个应用实例,因此应用数据(包括状态)会被多次构建。
若需在不同线程间共享数据,需使用支持共享的对象(需实现 Rust 的 Send + Sync 特质)。
web::Data
内部使用了 Rust 的 Arc
(原子引用计数指针)实现共享,为避免创建双重 Arc
,建议在调用 App::app_data()
注册状态前,先通过 web::Data::new()
创建状态实例。
以下示例将实现一个带有 “可变共享状态” 的应用,步骤如下:
- 定义状态结构体与处理器:
1
2
3
4
5
6
7
8
9
10
11
12
13
14use actix_web::{web, App, HttpServer};
use std::sync::Mutex;
// 带计数器的状态结构体
struct AppStateWithCounter {
counter: Mutex<i32>, // <- Mutex 是跨线程安全修改计数器的必要组件
}
async fn index(data: web::Data<AppStateWithCounter>) -> String {
// 获取 Mutex 守卫(MutexGuard),用于安全访问计数器
let mut counter = data.counter.lock().unwrap();
*counter += 1; // <- 在 MutexGuard 保护下修改计数器
format!("Request number: {counter}") // <- 返回当前请求次数
} - 在
App
中注册共享状态:核心要点1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
async fn main() -> std::io::Result<()> {
// 注意:在 HttpServer::new 闭包**外部**创建 web::Data 实例
let counter = web::Data::new(AppStateWithCounter {
counter: Mutex::new(0), // 初始计数器值为 0
});
HttpServer::new(move || {
// 将 counter 移入闭包(move 关键字确保所有权转移)
App::new()
.app_data(counter.clone()) // <- 注册共享状态(克隆 Arc)
.route("/", web::get().to(index))
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
- 在传递给 HttpServer::new 的闭包内部初始化的状态,属于 “工作线程本地状态”,若被修改可能导致线程间状态不同步;
- 若需实现 “全局共享状态”,必须在 HttpServer::new 闭包外部创建状态,再通过 move 或 clone 将其传入闭包。
使用应用作用域组合应用
web::scope()
方法可用于设置 “资源组前缀”—— 该前缀会自动添加到所有通过该作用域注册的资源模式(resource pattern)前。
这一特性可帮助开发者将一组路由 “挂载” 到与原设计不同的路径下,同时保持资源名称不变。示例如下:
1 |
|
在上述示例中:
show_users
路由的 “实际路由模式” 会从/show
变为/users/show
(作用域前缀/users
被添加到原模式前);- 仅当
URL
路径为/users/show
时,该路由才会匹配请求; - 调用
HttpRequest.url_for("show_users")
生成URL
时,也会自动拼接前缀,生成/users/show
路径。
应用守卫与虚拟主机(Application Guards & Virtual Hosting)
守卫(Guard)
可理解为一个简单函数:接收请求对象引用,返回 true
或 false
(表示 “允许” 或 “拒绝” 请求)。从形式上看,任何实现了 Guard 特质的对象都是守卫。
actix-web
提供了多种内置守卫(可查看 API 文档的 “函数” 章节了解详情),其中 Host 守卫可基于请求头信息过滤请求,常用于实现虚拟主机。
示例:基于请求的 Host 头区分不同虚拟主机:
1 | use actix_web::{guard, web, App, HttpResponse, HttpServer}; // 需导入 guard |
配置(Configure)
为提升配置的简洁性与可复用性,App 和 web::Scope 均提供了 configure 方法。该方法可将部分配置逻辑移至其他模块甚至外部库中(例如,将某组资源的配置抽离到单独模块)。
示例:通过 configure 拆分配置逻辑:
1 | use actix_web::{web, App, HttpResponse, HttpServer}; |
上述示例的路由匹配结果如下:
- 访问 / → 返回 “https://actix.rs/docs/application“
- 访问 /app → 返回 “app”
- 访问 /api/test → 返回 “test”
每个 ServiceConfig 实例均可独立配置自身的 data(数据)、routes(路由)和 services(服务)。
HTTP 服务器 | Actix Web
HTTP 服务器概述
HttpServer 类型负责处理 HTTP 请求的接收与响应。
HttpServer 接收应用工厂(application factory)作为参数,且该应用工厂必须满足 Send + Sync 约束(更多细节见「多线程」章节)。
启动 Web 服务器前,需先将其绑定到一个网络端口。可使用 HttpServer::bind() 方法,传入 socket 地址元组或字符串(如 (“127.0.0.1”, 8080) 或 0.0.0.0:8080)。若该端口已被其他应用占用,绑定操作会失败。
绑定成功后,调用 HttpServer::run() 会返回一个 Server 实例。需通过 await 或 spawn 启动该实例以处理请求,服务器会持续运行直至收到关闭信号(默认情况下,Ctrl+C 即可触发关闭,更多细节见此处)。
1 | use actix_web::{web, App, HttpResponse, HttpServer}; |
多线程(Multi-Threading)
HttpServer 会自动启动多个 HTTP 工作线程(workers),默认数量等于系统的物理 CPU 核心数。可通过 HttpServer::workers() 方法手动指定工作线程数量。
1 | use actix_web::{web, App, HttpResponse, HttpServer}; |
工作线程创建后,每个线程会持有一个独立的应用实例来处理请求:
- 线程间不共享应用状态,处理器(handler)可自由修改自身持有的状态副本,无需担心并发安全问题;
- 应用状态无需满足 Send 或 Sync 约束,但应用工厂必须满足 Send + Sync。
线程间共享状态
若需在多个工作线程间共享状态,需使用 Arc(原子引用计数指针)或 web::Data(Actix Web 封装的共享工具,内部基于 Arc)。引入共享状态和同步机制时需特别注意:
修改共享状态时的加锁操作,往往会无意中引入性能开销。某些场景下,可通过更高效的锁策略降低开销(例如用「读写锁(RwLock)」替代「互斥锁(Mutex)」实现非独占锁定),但性能最优的实现通常是完全避免加锁。
避免阻塞工作线程
每个工作线程会顺序处理请求,若处理器中存在阻塞当前线程的操作,会导致该工作线程无法处理新请求:
1 | // 错误示例:阻塞线程的处理器(不推荐!) |
因此,所有耗时且非 CPU 密集型的操作(如 I/O 操作、数据库查询等),都应通过「 futures(未来对象)」或「异步函数」实现。异步处理器会被工作线程并发执行,不会阻塞线程:
1 | // 正确示例:异步处理器(推荐) |
该限制同样适用于「提取器(Extractor)」:若处理器的参数实现了 FromRequest 特质,且其实现中存在阻塞线程的逻辑,工作线程执行处理器时会被阻塞。因此实现提取器时需特别注意,必要时应采用异步方式实现。
TLS / HTTPS 配置
Actix Web 开箱即支持两种 TLS 实现:rustls(纯 Rust 实现)和 openssl(基于 OpenSSL 库)。
- 启用 rustls 需添加 rustls 特性;
- 启用 openssl 需添加 openssl 特性。
以 OpenSSL 为例配置 HTTPS
- 添加依赖:在 Cargo.toml 中声明 actix-web 的 openssl 特性及 openssl 库:
1
2
3[dependencies]
actix-web = { version = "4", features = ["openssl"] }
openssl = { version = "0.10" } - 编写 HTTPS 服务器代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26use actix_web::{get, App, HttpRequest, HttpServer, Responder};
use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod};
// 处理器:返回欢迎信息
async fn index(_req: HttpRequest) -> impl Responder {
"Welcome!"
}
async fn main() -> std::io::Result<()> {
// 加载 TLS 密钥和证书
// 生成测试用自签名证书的命令:
// `openssl req -x509 -newkey rsa:4096 -nodes -keyout key.pem -out cert.pem -days 365 -subj '/CN=localhost'`
let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
// 设置私钥文件(key.pem)
builder.set_private_key_file("key.pem", SslFiletype::PEM).unwrap();
// 设置证书链文件(cert.pem)
builder.set_certificate_chain_file("cert.pem").unwrap();
// 启动 HTTPS 服务器(使用 bind_openssl 绑定端口和 TLS 配置)
HttpServer::new(|| App::new().service(index))
.bind_openssl("127.0.0.1:8080", builder)?
.run()
.await
} - 生成 TLS 证书和私钥:
执行以下命令生成自定义主题(subject)的证书和私钥(需替换示例中的主题信息):若需移除私钥的密码保护(测试场景常用),可执行:1
2$ openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem \
-days 365 -sha256 -subj "/C=CN/ST=Fujian/L=Xiamen/O=TVlinux/OU=Org/CN=muro.lxd"1
2$ openssl rsa -in key.pem -out nopass.pem
$ cp nopass.pem key.pem # 将无密码的私钥替换原文件
长连接(Keep-Alive)
Actix Web 会保持连接处于打开状态,以等待后续请求。长连接的行为由服务器配置决定,支持以下三种模式:
- Duration::from_secs(75) 或 KeepAlive::Timeout(75):启用长连接,超时时间为 75 秒;
- KeepAlive::Os:使用操作系统(OS)默认的长连接配置;
- None 或 KeepAlive::Disabled:禁用长连接。长连接的默认行为与强制关闭
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21use actix_web::{http::KeepAlive, HttpServer};
use std::time::Duration;
// 假设 app 是已定义的应用工厂
fn app() -> App<()> {
App::new().route("/", web::get().to(|| async { "Hello Keep-Alive" }))
}
async fn main() -> std::io::Result<()> {
// 1. 设置长连接超时为 75 秒
let _one = HttpServer::new(app).keep_alive(Duration::from_secs(75));
// 2. 使用操作系统默认的长连接配置(通常超时时间较长)
let _two = HttpServer::new(app).keep_alive(KeepAlive::Os);
// 3. 禁用长连接
let _three = HttpServer::new(app).keep_alive(None);
Ok(())
} - HTTP 版本差异:HTTP/1.0 默认禁用长连接,HTTP/1.1 和 HTTP/2.0 默认启用长连接;
- 响应头控制:若响应明确禁止长连接(如设置 Connection: Close 或 Connection: Upgrade),则 HTTP/1.1 的长连接会被关闭;
- 强制关闭连接:可通过 HttpResponseBuilder 的 force_close() 方法强制关闭当前连接:
1
2
3
4
5
6
7
8
9
10
11
12
13use actix_web::{http, HttpRequest, HttpResponse};
async fn index(_req: HttpRequest) -> HttpResponse {
// 方式 1:通过 HttpResponseBuilder 强制关闭连接
let mut resp = HttpResponse::Ok()
.force_close() // <- 强制关闭连接
.finish();
// 方式 2:通过 HttpResponse 结构体修改连接类型
resp.head_mut().set_connection_type(http::ConnectionType::Close);
resp
}
优雅关闭(Graceful Shutdown)
HttpServer 支持「优雅关闭」:收到停止信号后,工作线程会有特定时间完成正在处理的请求;超时后仍存活的工作线程会被强制终止。
- 默认关闭超时时间为 30 秒,可通过 HttpServer::shutdown_timeout() 方法修改;
- HttpServer 支持处理多种操作系统信号:
- SIGINT(所有系统支持):强制关闭工作线程(如 Ctrl+C);
- SIGTERM(类 Unix 系统支持):优雅关闭工作线程;
- SIGQUIT(类 Unix 系统支持):强制关闭工作线程;
- 可通过 HttpServer::disable_signals() 方法禁用信号处理功能。
类型安全的请求信息提取 | Actix Web
Actix Web 提供了一套类型安全的请求信息访问机制,称为 “提取器”(即 impl FromRequest)。框架内置了多种提取器实现(详见「实现列表」)。
提取器可作为处理器函数(handler function)的参数使用。Actix Web 支持每个处理器函数最多接收 12 个提取器。
多数情况下,提取器的参数位置不影响功能;但需注意:若某个提取器需要读取请求体(即从请求体流中读取字节),则仅第一个此类提取器能成功获取数据。若需实现 “降级逻辑”(例如 “尝试将请求体解析为 JSON,解析失败则返回原始字节”),可使用 Either 提取器(示例:Either<Json
此外,部分需要 “两次读取请求体” 的特殊场景可通过以下方式实现:
- 若需同时获取 “请求体 + 其哈希值 / 摘要”,可使用 actix-hash 库;
- 若需同时获取 “请求体 + 自定义请求签名”,可使用 actix-web-lab 库中的 RequestSignature 提取器。
简单示例:提取路径参数与 JSON 体
以下示例展示如何提取两个 “位置型动态路径段” 和一个 JSON 请求体:1
2
3
4
5
6
7async fn index(
path: web::Path<(String, String)>, // 提取路径参数(元组形式)
json: web::Json<MyInfo> // 提取 JSON 请求体(自定义结构体)
) -> impl Responder {
let path = path.into_inner(); // 解包路径参数元组
format!("{} {} {} {}", path.0, path.1, json.id, json.username)
}
Path 提取器
Path 提取器用于从请求路径中提取信息。路径中可被提取的部分称为 “动态段”(dynamic segments),用花括号 {} 标记。开发者可将任意动态段反序列化为指定类型。
例如,若资源路由注册为 /users/{user_id}/{friend},则可提取两个动态段(user_id 和 friend),并按声明顺序用元组接收(示例:Path<(u32, String)>)。
示例 1:用元组提取路径参数
1 | use actix_web::{get, web, App, HttpServer, Result}; |
示例 2:用自定义结构体提取路径参数
若需按 “动态段名称” 匹配提取(而非位置),可定义实现 serde::Deserialize 特质的结构体(需启用 serde 的 derive 特性)。以下是与示例 1 功能等效的实现:
1 | use actix_web::{get, web, Result}; |
非类型安全的替代方案
若无需类型安全,也可在处理器中通过 match_info() 方法按名称查询路径参数(详见 match_info 文档):
1 | use actix_web::{get, HttpRequest, Result}; |
Query 提取器
Query
示例:提取查询参数
1 | use actix_web::{get, web, App, HttpServer}; |
例如,访问 http://127.0.0.1:8080/?username=Alice,会返回 Welcome Alice!。
Json 提取器
Json
示例 1:基础 Json 提取
1 | use actix_web::{post, web, App, HttpServer, Result}; |
示例 2:配置 Json 提取器
部分提取器支持自定义 “提取过程”,Json 提取器可通过 JsonConfig 配置:
- 限制 JSON 负载(payload)的最大大小;
- 设置自定义错误处理函数。
以下示例将 JSON 负载最大大小限制为 4KB,并自定义错误响应:
1 | use actix_web::{error, web, App, HttpResponse, HttpServer, Responder}; |
URL 编码表单提取器(URL-Encoded Forms)
与 Json
可通过 FormConfig 配置表单提取过程(如限制负载大小)。
示例:提取 URL 编码表单
1 | use actix_web::{post, web, App, HttpServer, Result}; |
其他常用提取器
Actix Web 还提供多种实用提取器,以下是核心提取器列表:
提取器 | 用途说明 |
---|---|
Data |
访问应用状态(需提前通过 app_data 注册),详见「应用状态提取」章节。 |
HttpRequest | 自身也是提取器,若需访问请求的完整信息(如 headers、method 等),可直接使用。 |
String | 将请求体转换为 String 类型(示例见 Rust 文档)。 |
Bytes | 将请求体转换为 Bytes 类型(适合处理二进制数据,示例见 Rust 文档)。 |
Payload | 底层负载提取器,主要用于自定义其他提取器(示例见 Rust 文档)。 |
应用状态提取器(Application State Extractor)
通过 web::Data
示例 1:基础状态提取(单线程统计)
以下示例用 Cell 存储 “请求处理次数”,但仅能统计单个工作线程的请求数:
1 | use actix_web::{web, App, HttpServer, Responder}; |
示例 2:跨线程状态共享(全局统计)
若需统计所有工作线程的总请求数,需使用 Arc(原子引用计数指针)和 “原子类型”(如 AtomicUsize)实现线程安全的共享:
1 | use actix_web::{get, web, App, HttpServer, Responder}; |
⚠️注意事项
- 若需 “全线程共享整个状态”,需按「共享可变状态」章节的说明,结合 web::Data 和 app_data 实现;
- 避免在应用状态中滥用 Mutex 或 RwLock 等阻塞式同步原语:Actix Web 以异步方式处理请求,若临界区(critical section)过大或包含 await 点,会阻塞工作线程,影响性能;
- 若需在异步代码中使用同步原语,建议参考 Tokio 官方文档中 “异步代码使用阻塞 Mutex” 的建议。
补充术语说明
英文术语 | 中文翻译 | 补充说明 |
---|---|---|
Extractor | 提取器 | Actix Web 中用于从请求中提取信息的组件,需实现 FromRequest 特质。 |
Dynamic Segments | 动态段 | 路径中用 {} 标记的可变量部分,如 /users/{user_id} 中的 user_id。 |
Payload | 负载 | 指请求体中的数据(如 JSON、表单内容),通常有大小限制。 |
Deserialize | 反序列化 | 将 JSON / 表单等结构化数据转换为 Rust 结构体的过程,依赖 serde 库。 |
Arc (Atomic Reference Count) | 原子引用计数指针 | Rust 中用于跨线程共享数据的智能指针,线程安全。 |
Atomic Type | 原子类型 | 支持线程安全读写的基础类型(如 AtomicUsize),无需额外锁保护。 |