使用 Actix Web 构建 Web 应用
使用 Actix Web 构建 Web 应用
1. 添加依赖
首先,在 Cargo.toml 中添加 actix-web 和 tokio 依赖:
[dependencies]
actix-web = "4.11.0"
tokio = { version = "1.47.1", features = ["macros", "rt-multi-thread"] }
说明:
actix-web
是 Rust 中高性能的异步 Web 框架。tokio
提供异步运行时,rt-multi-thread
启用多线程执行器,macros
支持#[tokio::main]
等宏。
2. 编写一个简单的 Web 服务
use actix_web::{App, HttpRequest, HttpServer, Responder, web};async fn greet(req: HttpRequest) -> impl Responder {let name = req.match_info().get("name").unwrap_or("world");format!("Hello {}!", name)
}#[tokio::main]
async fn main() -> std::io::Result<()> {HttpServer::new(|| {App::new().route("/", web::get().to(greet)).route("/{name}", web::get().to(greet))}).bind("127.0.0.1:8000")?.run().await
}
该程序启动一个监听 127.0.0.1:8000
的 HTTP 服务器:
- 访问
http://127.0.0.1:8000/
返回Hello world!
- 访问
http://127.0.0.1:8000/Alice
返回Hello Alice!
3. Actix Web 应用程序核心组件解析
3.1 HttpServer
:传输层的入口
HttpServer
负责处理底层网络通信,是整个应用的“门面”。其主要职责包括:
- 监听地址配置: 绑定 TCP 地址(如 127.0.0.1:8000)或 Unix 域套接字;
- 连接管理:控制最大并发连接数、连接速率等;
- 传输安全: 支持通过 TLS(如 rustls 或 openssl)启用 HTTPS。
关键点:
HttpServer
不处理业务逻辑,仅负责接收连接并将其交给上层应用。
3.2 App:应用逻辑的容器
当 HttpServer 接收到新连接后,请求会被交由 App 处理。App 是业务逻辑的核心载体,包含:
- 路由(Routes):定义 URL 路径与处理函数的映射;
- 中间件(Middleware): 用于日志、认证、错误处理等横切关注点;
- 服务配置: 如状态共享、数据注入等。
示例中的App
配置如下:
App::new().route("/", web::get().to(greet)).route("/{name}", web::get().to(greet))
App::new()
创建一个空的应用实例;.route(path, handler)
采用链式调用方式注册多个端点。
3.3 路由(Route)与处理器(Handler)
路由的组成
每个 .route()
调用包含两个要素:
- 路径(Path)
- 静态路径:如 “/”
- 动态模板:如 “/{name}”,其中 {name} 是路径参数。
- 路由守卫(Guard) + 处理器(Handler)
web::get()
是Route::new().guard(guard::Get())
的简写,表示仅匹配 GET 请求;- 守卫(Guard)是一组条件(如 HTTP 方法、Header 等),只有全部满足时才会调用处理器。
匹配机制:
当请求到达时,Actix Web 会按注册顺序遍历所有路由,第一个同时满足路径模板和守卫条件的路由将被选中,并调用其处理器。
处理器函数签名
处理器是一个异步函数,其典型签名如下:
async fn greet(req: HttpRequest) -> impl Responder {let name = req.match_info().get("name").unwrap_or("world");format!("Hello {}!", name)
}
扩展性:你可以为自定义类型实现
Responder
,从而直接返回结构化响应(如 JSON 对象)
4. #[tokio::main]
的作用详解
在 Rust 中,#[tokio::main] 是一个非常常见但又容易被忽略的过程宏。它看似只是让 main 函数能使用 async,但背后其实做了很多底层工作。
4.1 为什么需要异步 main 函数?
我们通常希望在异步环境中运行程序的入口函数,例如:
#[tokio::main]
async fn main() {HttpServer::new(|| App::new().route("/", web::get().to(greet))).bind("127.0.0.1:8000").unwrap().run().await.unwrap();
}
这里的 HttpServer::run()
是一个 异步方法,而在 Rust 中,只能在异步函数中调用异步函数。
这就意味着 main
也必须是异步的。
但问题是:
Rust 的 main 函数不能直接是异步的。
为什么?这要从 Rust 的异步机制说起。
4.2 Rust 异步编程的底层原理
Rust 的异步编程基于 Future
trait。
Future
代表一个可能尚未完成的计算,它通过一个 poll
方法逐步推进执行。
换句话说,Rust 的 Future
是惰性的:
除非有外部“驱动者”调用 poll
,它不会自动执行。这种机制称为 “被动型(pull)” 异步模型。
Rust 本身虽然支持 async
语法,但并没有内置异步运行时(runtime)。
这意味着:
-
编译器能帮你生成
Future
, -
但不会帮你驱动它执行。
4.3 谁来执行 Future
?
既然 Rust 没有内置运行时,那就需要你在项目中引入一个异步运行时(runtime)库,比如:
[dependencies]
tokio = { version = "1", features = ["full"] }
Tokio 就是最常用的异步运行时之一。它负责:
-
启动多线程调度器;
-
管理任务执行;
-
驱动
Future
的poll
; -
提供异步 IO、定时器等功能。
4.4 main 为何不能直接是异步的?
main
函数是程序的入口点,操作系统只会调用一个普通的同步函数 fn main()
.
而异步函数返回的是一个 Future
对象,不会自动运行。
因此编译器并不知道:
“谁应该来执行这个 Future(也就是谁负责调用 poll)?”
Rust 没有规定运行时标准接口,所以这就需要我们自己来启动一个运行时,然后在其中运行异步任务。
4.5 #[tokio::main] 到底做了什么?
你可能已经猜到了:
#[tokio::main]
的作用就是——
在 main 函数中自动创建并启动一个 Tokio 运行时,然后在其中执行你的异步逻辑。
为了验证这一点,我们可以使用工具 cargo-expand
展开宏:
cargo install cargo-expand
cargo expand
运行后你会看到类似的展开结果:
fn main() -> std::io::Result<()> {let body = async {HttpServer::new(|| {App::new().route("/", web::get().to(greet)).route("/{name}", web::get().to(greet))}).bind("127.0.0.1:8000")?.run().await};tokio::runtime::Builder::new_multi_thread().enable_all().build().expect("Failed building the Runtime").block_on(body)
}
可以看到:
-
展开后 main 变成了一个普通同步函数;
-
宏自动生成了一个 Tokio 运行时;
-
用
block_on(body)
来驱动异步代码执行。
这就解释了为什么 #[tokio::main]
能“让异步的 main 函数编译通过”——
其实它只是帮你包了一层运行时初始化代码。
5. 实现健康检查处理器(Health Check Handler)
在 Web 服务中,健康检查(health check) 通常用于验证服务器是否处于可用状态。
当客户端向 /health_check
发送 GET
请求时,服务器应返回 状态码 200,且无响应体。
5.1 编写健康检查处理器
我们首先创建一个异步请求处理函数。参考之前的 greet
函数,先定义如下:
async fn health_check(req: HttpRequest) -> impl Responder {todo!()
}
这里的 req
表示传入的 HTTP 请求对象。
不过在健康检查中,我们并不需要使用任何请求信息,因此该参数其实是多余的。
5.2 构建响应对象
由于 HttpResponse 类型实现了 Responder trait,我们可以直接返回一个 HttpResponse。
HttpResponse::Ok()
会返回一个以 200 OK 为状态码的 HttpResponseBuilder
,
我们可以调用 .finish()
来构建一个无响应体的 HttpResponse
。
async fn health_check() -> impl Responder {HttpResponse::Ok().finish()
}
说明:
HttpResponseBuilder
本身也实现了Responder trait
,
因此即使不调用.finish()
也能正常工作。
不过,为了语义更明确、风格一致,推荐保留.finish()
。
5.3 注册路由
接下来,我们在应用中将 /health_check
路由与刚定义的处理器绑定:
App::new().route("/health_check", web::get().to(health_check))
这样,当客户端访问 /health_check
时,服务器就会返回:
HTTP/1.1 200 OK
Content-Length: 0
5.4 最终代码示例
use actix_web::{web, App, HttpResponse, HttpServer, Responder};async fn health_check() -> impl Responder {HttpResponse::Ok().finish()
}#[actix_web::main]
async fn main() -> std::io::Result<()> {HttpServer::new(|| {App::new().route("/health_check", web::get().to(health_check))}).bind("127.0.0.1:8000")?.run().await
}
运行后,访问 http://127.0.0.1:8000/health_check
即可看到一个返回 200 OK 且无响应体的健康检查接口