文章目录
前言
嘿,Rust 爱好者们!今天我要分享的是 Actix-web —— 一个强大而高效的 Rust Web 框架。如果你正在考虑使用 Rust 构建 Web 应用,Actix-web 绝对是一个值得了解的选择!
我第一次接触 Actix-web 时被它的性能和安全性所震撼。作为一个基于 actor 系统构建的框架,它充分利用了 Rust 的优势,提供了令人印象深刻的并发处理能力和内存安全保障。
接下来,让我们一起深入探索这个令人兴奋的框架!
Actix-web 是什么?
Actix-web 是一个用 Rust 编写的高性能 Web 框架,它最初基于 actor 模型(尽管现在的版本已经减少了对 actor 系统的依赖)。它提供了构建 Web 服务所需的所有基础设施,从路由处理到中间件系统,再到 WebSocket 支持。
它的核心特点包括:
- 极高的性能:多项基准测试中表现出色,可以处理大量并发请求
- 类型安全:充分利用 Rust 的类型系统提供编译时保障
- 异步处理:原生支持 Rust 的异步编程模型
- 灵活的中间件系统:可以轻松扩展框架功能
- 强大的扩展性:可以与各种 Rust 生态系统中的库集成
环境准备
在开始之前,确保你已经安装了 Rust 和 Cargo(Rust 的包管理器)。如果还没有,可以通过 rustup 轻松安装。
创建一个新项目:
cargo new actix_hello_world
cd actix_hello_world
然后在 Cargo.toml 文件中添加 Actix-web 依赖:
[dependencies]
actix-web = "4.3.1"
版本号可能会随时间变化,建议查看 Actix-web 官方文档 获取最新版本信息。
Hello World 示例
先从最简单的例子开始!下面是一个基础的 Actix-web 应用程序,它只做一件事:当你访问根路径 “/” 时,返回 “Hello, World!”。
修改 src/main.rs 文件:
use actix_web::{get, App, HttpResponse, HttpServer, Responder};
#[get("/")]
async fn hello() -> impl Responder {
HttpResponse::Ok().body("Hello, World!")
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.service(hello)
})
.bind("127.0.0.1:8080")?
.run()
.await
}
启动应用:
cargo run
现在打开浏览器访问 http://localhost:8080,你应该能看到 “Hello, World!” 的消息。恭喜!你刚刚创建了你的第一个 Actix-web 应用!
这个简单示例中有几个重要概念:
#[get("/")]是一个路由宏,它告诉 Actix-web 这个函数应该处理 GET 请求到 “/” 路径- 我们的处理函数是异步的(
async),这对性能很重要 HttpServer负责处理 HTTP 请求App实例用于配置路由和中间件
路由系统
Actix-web 提供了一套灵活的路由系统。你可以通过多种方式定义路由:
使用宏
宏是最直观的方式(就像我们在 Hello World 示例中看到的):
#[get("/users/{id}")]
async fn get_user(path: web::Path<(u32,)>) -> impl Responder {
let user_id = path.into_inner().0;
HttpResponse::Ok().body(format!("User ID: {}", user_id))
}
#[post("/users")]
async fn create_user(user: web::Json<User>) -> impl Responder {
// 处理创建用户的逻辑
HttpResponse::Created().json(user.into_inner())
}
这里的 {id} 是路径参数,可以通过 web::Path 提取。
使用资源配置
另一种方式是通过 App 的方法链式调用来配置路由:
App::new()
.route("/", web::get().to(index))
.route("/users", web::post().to(create_user))
.service(
web::resource("/users/{id}")
.route(web::get().to(get_user))
.route(web::delete().to(delete_user))
)
这种方式更加灵活,特别是当你需要为同一路径配置多种 HTTP 方法时。
请求处理
Actix-web 提供了多种方式来处理和提取请求数据:
路径参数
从路径中提取参数:
#[get("/users/{id}")]
async fn get_user(path: web::Path<(u32,)>) -> impl Responder {
let user_id = path.into_inner().0;
// ...
}
查询字符串
提取查询参数:
use serde::Deserialize;
#[derive(Deserialize)]
struct Info {
page: Option<u32>,
per_page: Option<u32>,
}
#[get("/users")]
async fn list_users(query: web::Query<Info>) -> impl Responder {
let page = query.page.unwrap_or(1);
let per_page = query.per_page.unwrap_or(10);
// ...
}
JSON 请求体
处理 JSON 请求体(需要添加 serde 和 serde_json 依赖):
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize)]
struct User {
name: String,
email: String,
}
#[post("/users")]
async fn create_user(user: web::Json<User>) -> impl Responder {
// 处理用户数据
HttpResponse::Created().json(user.into_inner())
}
响应处理
Actix-web 提供了多种方式来构建响应:
基本响应
HttpResponse::Ok().body("Hello world!")
JSON 响应
HttpResponse::Ok().json(user) // 自动序列化为 JSON
自定义状态码和头部
HttpResponse::Created()
.content_type("text/html")
.header("X-Custom-Header", "custom-value")
.body("<h1>Created</h1>")
中间件
中间件允许你在请求处理前后执行代码,这对于日志记录、认证、压缩等功能非常有用。
使用内置中间件
use actix_web::middleware::{Logger, Compress};
App::new()
.wrap(Logger::default()) // 启用请求日志
.wrap(Compress::default()) // 启用响应压缩
.service(hello)
创建自定义中间件
你可以创建自己的中间件来执行特定的任务:
use actix_web::{
dev::{self, Service, ServiceRequest, ServiceResponse, Transform},
Error,
};
use futures::future::{ok, Ready};
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
// 定义中间件结构
pub struct MyMiddleware;
// 中间件工厂实现
impl<S, B> Transform<S, ServiceRequest> for MyMiddleware
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{
type Response = ServiceResponse<B>;
type Error = Error;
type InitError = ();
type Transform = MyMiddlewareService<S>;
type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
ok(MyMiddlewareService { service })
}
}
pub struct MyMiddlewareService<S> {
service: S,
}
impl<S, B> Service<ServiceRequest> for MyMiddlewareService<S>
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{
type Response = ServiceResponse<B>;
type Error = Error;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.service.poll_ready(cx)
}
fn call(&self, req: ServiceRequest) -> Self::Future {
println!("请求进入: {}", req.path());
let fut = self.service.call(req);
Box::pin(async move {
let res = fut.await?;
println!("响应状态: {}", res.status());
Ok(res)
})
}
}
然后在应用中使用它:
App::new()
.wrap(MyMiddleware)
.service(hello)
状态管理
在 Web 应用中,你经常需要在请求之间共享状态(如数据库连接池)。Actix-web 提供了一种简洁的方式来处理这个问题:
struct AppState {
db_pool: DbPool,
app_name: String,
}
#[get("/")]
async fn index(data: web::Data<AppState>) -> String {
let app_name = &data.app_name;
format!("Hello from {}!", app_name)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
// 创建数据库连接池
let db_pool = create_db_pool().await;
// 创建应用状态
let state = AppState {
db_pool,
app_name: "Actix Web App".to_string(),
};
HttpServer::new(move || {
App::new()
.app_data(web::Data::new(state.clone()))
.service(index)
})
.bind("127.0.0.1:8080")?
.run()
.await
}
实际案例:构建 REST API
让我们把所学知识结合起来,创建一个简单的 REST API 来管理待办事项:
use actix_web::{get, post, put, delete, web, App, HttpResponse, HttpServer, Responder};
use serde::{Deserialize, Serialize};
use std::sync::Mutex;
// 数据模型
#[derive(Debug, Serialize, Deserialize, Clone)]
struct Todo {
id: Option<u32>,
title: String,
completed: bool,
}
// 应用状态
struct AppState {
todo_list: Mutex<Vec<Todo>>,
next_id: Mutex<u32>,
}
// 获取所有待办事项
#[get("/todos")]
async fn get_todos(data: web::Data<AppState>) -> impl Responder {
let todo_list = data.todo_list.lock().unwrap();
HttpResponse::Ok().json(&*todo_list)
}
// 获取单个待办事项
#[get("/todos/{id}")]
async fn get_todo(path: web::Path<u32>, data: web::Data<AppState>) -> impl Responder {
let id = path.into_inner();
let todo_list = data.todo_list.lock().unwrap();
if let Some(todo) = todo_list.iter().find(|t| t.id == Some(id)) {
HttpResponse::Ok().json(todo)
} else {
HttpResponse::NotFound().body("Todo not found")
}
}
// 创建新的待办事项
#[post("/todos")]
async fn create_todo(new_todo: web::Json<Todo>, data: web::Data<AppState>) -> impl Responder {
let mut todo = new_todo.into_inner();
let mut next_id = data.next_id.lock().unwrap();
let mut todo_list = data.todo_list.lock().unwrap();
todo.id = Some(*next_id);
*next_id += 1;
todo_list.push(todo.clone());
HttpResponse::Created().json(todo)
}
// 更新待办事项
#[put("/todos/{id}")]
async fn update_todo(
path: web::Path<u32>,
updated_todo: web::Json<Todo>,
data: web::Data<AppState>,
) -> impl Responder {
let id = path.into_inner();
let mut todo_list = data.todo_list.lock().unwrap();
if let Some(todo) = todo_list.iter_mut().find(|t| t.id == Some(id)) {
todo.title = updated_todo.title.clone();
todo.completed = updated_todo.completed;
HttpResponse::Ok().json(todo)
} else {
HttpResponse::NotFound().body("Todo not found")
}
}
// 删除待办事项
#[delete("/todos/{id}")]
async fn delete_todo(path: web::Path<u32>, data: web::Data<AppState>) -> impl Responder {
let id = path.into_inner();
let mut todo_list = data.todo_list.lock().unwrap();
let len = todo_list.len();
todo_list.retain(|t| t.id != Some(id));
if todo_list.len() != len {
HttpResponse::NoContent().finish()
} else {
HttpResponse::NotFound().body("Todo not found")
}
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
// 初始化应用状态
let app_state = web::Data::new(AppState {
todo_list: Mutex::new(vec![]),
next_id: Mutex::new(1),
});
println!("服务器运行在 http://localhost:8080");
HttpServer::new(move || {
App::new()
.app_data(app_state.clone())
.service(get_todos)
.service(get_todo)
.service(create_todo)
.service(update_todo)
.service(delete_todo)
})
.bind("127.0.0.1:8080")?
.run()
.await
}
使用 curl 测试 API:
# 创建待办事项
curl -X POST http://localhost:8080/todos -H "Content-Type: application/json" -d '{"title":"学习 Actix-web","completed":false}'
# 获取所有待办事项
curl http://localhost:8080/todos
# 获取单个待办事项
curl http://localhost:8080/todos/1
# 更新待办事项
curl -X PUT http://localhost:8080/todos/1 -H "Content-Type: application/json" -d '{"title":"学习 Actix-web","completed":true}'
# 删除待办事项
curl -X DELETE http://localhost:8080/todos/1
进阶主题
如果你想深入了解 Actix-web,以下是一些值得探索的进阶主题:
异步数据库访问
通常会使用 sqlx 或 diesel 等库来处理数据库操作。
use sqlx::{postgres::PgPoolOptions, Pool, Postgres};
struct AppState {
db_pool: Pool<Postgres>,
}
#[get("/users/{id}")]
async fn get_user(
path: web::Path<i32>,
state: web::Data<AppState>,
) -> impl Responder {
let id = path.into_inner();
match sqlx::query_as::<_, User>("SELECT * FROM users WHERE id = $1")
.bind(id)
.fetch_one(&state.db_pool)
.await
{
Ok(user) => HttpResponse::Ok().json(user),
Err(_) => HttpResponse::NotFound().body("User not found"),
}
}
文件上传
处理文件上传需要使用 actix-multipart 包:
use actix_multipart::Multipart;
use futures::{StreamExt, TryStreamExt};
use std::io::Write;
#[post("/upload")]
async fn upload(mut payload: Multipart) -> Result<HttpResponse, Error> {
// 迭代处理多部分表单字段
while let Ok(Some(mut field)) = payload.try_next().await {
let content_disposition = field.content_disposition();
if let Some(filename) = content_disposition.get_filename() {
let filepath = format!("./uploads/{}", sanitize_filename::sanitize(filename));
// 创建文件
let mut f = web::block(|| std::fs::File::create(filepath))
.await
.unwrap();
// 字段就像一个流,我们一块一块地读取它
while let Some(chunk) = field.next().await {
let data = chunk.unwrap();
// 写入文件
f = web::block(move || f.write_all(&data).map(|_| f))
.await
.unwrap();
}
}
}
Ok(HttpResponse::Ok().body("文件上传成功"))
}
WebSockets 支持
Actix-web 提供了强大的 WebSocket 支持:
use actix_web::{web, App, Error, HttpRequest, HttpResponse, HttpServer};
use actix_web_actors::ws;
use actix::{Actor, StreamHandler};
// 定义 WebSocket actor
struct MyWs;
impl Actor for MyWs {
type Context = ws::WebsocketContext<Self>;
}
// 处理 WebSocket 消息
impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for MyWs {
fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) {
match msg {
Ok(ws::Message::Text(text)) => {
// 简单地将消息回显给客户端
ctx.text(text)
}
Ok(ws::Message::Ping(msg)) => ctx.pong(&msg),
_ => (),
}
}
}
// WebSocket 路由处理函数
async fn ws_index(req: HttpRequest, stream: web::Payload) -> Result<HttpResponse, Error> {
ws::start(MyWs {}, &req, stream)
}
总结
Actix-web 是一个功能强大、性能卓越的 Rust Web 框架!它不仅提供了构建现代 Web 应用所需的所有功能,还充分利用了 Rust 的并发模型和安全保障。
本文介绍了 Actix-web 的基础知识,从简单的 Hello World 应用到构建完整的 REST API。我们探讨了路由系统、请求和响应处理、中间件、状态管理等核心概念,并通过实际案例展示了如何将这些知识点结合起来。
如果你正在寻找一个既能提供高性能又能保证安全性的 Web 框架,Actix-web 绝对值得一试!它不仅是 Rust 生态系统中最受欢迎的 Web 框架之一,也是业界性能基准测试中的佼佼者。
开始你的 Actix-web 之旅吧,相信你会爱上这个强大而优雅的框架!
相关资源
- Actix-web 官方文档
- Actix-web GitHub 仓库
- Rust 异步编程
- Tokio 运行时(Actix-web 使用的异步运行时)
Happy coding! 祝你在 Rust Web 开发的旅程中取得成功!
1630

被折叠的 条评论
为什么被折叠?



