入门级 ActixWeb 知识点摄取器
一、简介
actix:Rust 的 Actor 框架。
actix-web: Actix Web 是一个强大、实用且速度极快的 Rust Web 框架
Actor:一种并发执行的组件,通常用于处理异步任务和状态管理。Actors 在 actix 框架中起到了关键作用,它们是一种特殊的 Rust 类型,用于处理并发请求和状态管理。
二、HTTP 服务
HttpServer: 是 actix-web 库中的一个重要组件,它用于创建和配置 HTTP 服务器。
use actix_web::{ HttpServer };
- 基本用法
HttpServer::new(/* your app*/)
.bind("127.0.0.1:8080")?
.workers(4) // 设置工作进程数量
.run()
.await
三、 App 应用
App
是一个核心结构,用于配置和组织您的 Web 应用程序的路由、中间件和其他功能。
- 导入
use actix_web::{App};
- 基本用法
use actix_web::{get, web, App, HttpServer, Responder};
#[get("/")]
async fn index() -> impl Responder {
"Hello, World!"
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.service(index)
})
.bind("127.0.0.1:8080")?
.run()
.await
}
四、路由
4.1)api
API | 描述 |
---|---|
actix_web::web::Route | 用于定义单个路由。 |
actix_web::web::Resource | 用于定义资源和路由。 |
actix_web::web::Scope | 用于创建路由范围和子路由。 |
actix_web::web::Service | 用于定义 HTTP 服务。 |
4.2)route
函数单独定义
use actix_web::{App, web, HttpResponse, HttpServer, Responder};
async fn hello() -> impl Responder {
HttpResponse::Ok().body("Hello world!")
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.route("/", web::get().to(hello))
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
此处使用 route
函数进行定义,需要 web 配合指定,方法是 get
已及它的处理函数是 hello
。
4.3)service
函数单独定义
- 以 post 方法为例
use actix_web::{post, App, HttpResponse, HttpServer, Responder};
#[post("/")]
async fn echo(req_body: String) -> impl Responder {
HttpResponse::Ok().body(req_body)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| App::new().service(echo))
.bind(("127.0.0.1", 8080))?
.run()
.await
}
service 与 route 不同的是, service 是通过 rust 注解的形式获取定义请求的方式和路由参数。
4.4)resource
函数成组定义
pub fn resource<T: IntoPatterns>(path: T) -> Resource {
Resource::new(path)
}
- 定义资源群 resource,以下示例是:
use actix_web::{web, App, HttpResponse, HttpServer, Responder};
async fn users_list() -> impl Responder {
HttpResponse::Ok().body("List of users")
}
async fn user_info(path: web::Path<(String,)>) -> impl Responder {
let username = &path.0;
HttpResponse::Ok().body(format!("User info for: {}", username))
}
async fn create_user() -> impl Responder {
HttpResponse::Created().body("User created successfully")
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.service(
web::resource("/users")
.route(web::get().to(users_list)) // GET /users
.route(web::post().to(create_user)), // POST /users
)
.service(
web::resource("/users/{username}")
.route(web::get().to(user_info)), // GET /users/{username}
)
})
.bind("127.0.0.1:8080")?
.run()
.await
}
resource 定义一个路由资源,此时就可以使用 route
方法处理不同方法的请求。在此实例中,使用 service 方法 与 web::resource 进行配合可以成组使用。service 单独使用的案例之前已经说过了。
4.5)configure
函数自定定义配置路由
use actix_web::{web, App, HttpResponse, HttpServer};
fn config(cfg: &mut web::ServiceConfig) {
cfg.service(
web::resource("/app")
.route(web::get().to(|| async { HttpResponse::Ok().body("app") }))
.route(web::head().to(HttpResponse::MethodNotAllowed)),
);
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.configure(config)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
configure 方法能够定义 config
函数,在 config 函数中,获取 cfg 对象,cfg 中存在 service
, 相当于一个子服务。在模块拆分的时候非常有用。
4.6) scope
函数定义作用域
use actix_web::{web, App, HttpServer, Responder};
async fn index() -> impl Responder {
"Hello world!"
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new().service(
// prefixes all resources and routes attached to it...
web::scope("/app")
// ...so this handles requests for `GET /app/index.html`
.route("/index", web::get().to(index)),
)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
web::scope 的用法与 web::resource 用法类似,后者针对于当前资源使用不同的方式处理,前者可以定义子路由,并指定不同的方法。scope 更加的灵活。
五、提取器
5.1)API 整理
提取器 | 描述 | 示例 |
---|---|---|
web::Path | 从 URL 路径中提取参数 | web::Path<(u32, String)> |
web::Query | 解析查询字符串中的参数 | web::Query<HashMap<String, String>> |
web::Json | 解析 JSON 请求体中的数据 | web::Json<MyData> |
web::Form | 处理表单数据 | web::Form<MyFormData> |
web::Data | 在处理程序之间共享应用程序的状态和数据 | web::Data<AppState> |
web::Cookie | 用于处理 HTTP Cookie | web::Cookie |
web::Payload | 用于处理 HTTP 请求体数据 | web::Payload |
web::HttpRequest | 获取 HTTP 请求对象,可用于访问请求元数据 | web::HttpRequest |
web::HttpResponse | 构建 HTTP 响应对象,用于返回响应数据 | web::HttpResponse |
5.2)web::Path:从 URL 路径中提取参数。
use actix_web::{web, HttpResponse};
#[get("/user/{id}/{name}")]
async fn user_info(path: web::Path<(u32, String)>) -> HttpResponse {
let (id, name) = path.into_inner();
HttpResponse::Ok().body(format!("User ID: {}, Name: {}", id, name))
}
5.3)web::Query:解析查询字符串中的参数。
use actix_web::{web, HttpResponse};
#[get("/search")]
async fn search(query: web::Query<HashMap<String, String>>) -> HttpResponse {
let params = &query; // 从查询字符串中获取参数
// 处理查询参数
}
5.4)web::Json:解析 JSON 请求体中的数据。
use actix_web::{web, HttpResponse};
#[derive(serde::Deserialize)]
struct MyData {
// 定义结构以匹配 JSON 数据
}
#[post("/json")]
async fn json_handler(data: web::Json<MyData>) -> HttpResponse {
let my_data = data.into_inner(); // 获取解析后的数据
// 处理 JSON 数据
}
5.5)web::Form:处理表单数据。
use actix_web::{web, HttpResponse};
#[post("/form")]
async fn form_handler(form: web::Form<MyFormData>) -> HttpResponse {
let my_form_data = form.into_inner(); // 获取解析后的表单数据
// 处理表单数据
}
5.6)web::Data:在处理程序之间共享应用程序的状态和数据。
use actix_web::{web, App, HttpResponse};
struct AppState {
// 定义应用程序状态
}
#[get("/")]
async fn index(data: web::Data<AppState>) -> HttpResponse {
let app_state = data.get_ref(); // 获取应用程序状态
// 使用应用程序状态
}
六、请求对象
6.1) 请求对象示例
use actix_web::{HttpRequest, HttpResponse};
async fn handle_request(req: HttpRequest) -> HttpResponse {
// 使用 req 对象访问请求信息
let method = req.method();
let path = req.path();
HttpResponse::Ok()
.body(format!("Received {} request for path: {}", method, path))
}
6.2) 请求对象 api 整理
属性 | 描述 |
---|---|
method() | 获取HTTP请求的方法(GET、POST、PUT等)。 |
uri() | 获取HTTP请求的URI(包括路径和查询参数)。 |
path() | 获取HTTP请求的路径部分(不包括查询参数)。 |
query_string() | 获取HTTP请求的查询参数部分。 |
headers() | 获取HTTP请求的头部信息,返回一个&HeaderMap 对象。 |
extensions() | 获取HTTP请求的扩展数据。 |
cookies() | 获取HTTP请求中的Cookie信息,返回一个&CookieJar 对象。 |
body() | 获取HTTP请求的主体(body)数据,返回一个web::Bytes 对象。 |
6.3) api 使用示例
let method = req.method();
let uri = req.uri();
let path = req.path();
let query_param = req.query_string();
let user_agent = req.headers().get("User-Agent");
let my_data = req.extensions().get::<MyData>().unwrap();
let cookie_value = req.cookies().get("my_cookie").map(|c| c.value().to_string());
let body = req.body();
6.4)json
use actix_web::{App, HttpRequest, HttpResponse, web};
use serde::{Deserialize};
#[derive(Debug, Deserialize)]
struct MyJsonData {
field1: String,
field2: i32,
}
async fn handle_json_request(data: web::Json<MyJsonData>) -> HttpResponse {
let received_data = &data.0; // 访问反序列化后的数据
println!("{:?}", received_data);
// 在这里处理接收到的JSON数据,然后返回适当的响应
HttpResponse::Ok()
.json(received_data) // 响应中返回接收到的JSON数据
}
6.5)Urlencoded body
在 Rust 中我们需要先安装 serde
包,使用 serde 提供的能力,解析结构体:
use actix_web::{post, web, HttpResponse};
use serde::Deserialize;
#[derive(Deserialize)]
struct FormData {
username: String,
}
#[post("/")]
async fn index(form: web::Form<FormData>) -> HttpResponse {
HttpResponse::Ok().body(format!("username: {}", form.username))
}
条件:
- Content-Type 不是
application/x-www-form-urlencoded
- 转换成 chunked
- 内容长度不超过 256k
6.6) 流式请求
use actix_web::{get, web, Error, HttpResponse};
use futures::StreamExt;
#[get("/")]
async fn index(mut body: web::Payload) -> Result<HttpResponse, Error> {
let mut bytes = web::BytesMut::new();
while let Some(item) = body.next().await {
let item = item?;
println!("Chunk: {:?}", &item);
bytes.extend_from_slice(&item);
}
Ok(HttpResponse::Ok().finish())
}
七、响应对象
7.1) ContentType
use actix_web::{http::header::ContentType, HttpResponse};
async fn index() -> HttpResponse {
HttpResponse::Ok()
.content_type(ContentType::plaintext())
.insert_header(("X-Hdr", "sample"))
.body("data")
}
7.2) json 依赖
[dependencies]
serde = { version = "1.0", features = ["derive"] }
7.3) 响应 json
- 序列化的结构体
use serde::Serialize;
#[derive(Serialize)]
struct MyObj {
name: String,
}
- 一个简单的示例
使用 web::Json 构建响应结构体实例:
use actix_web::{get, web, Responder, Result};
#[get("/a/{name}")]
async fn index(name: web::Path<String>) -> Result<impl Responder> {
let obj = MyObj {
name: name.to_string(),
};
Ok(web::Json(obj))
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
use actix_web::{App, HttpServer};
HttpServer::new(|| App::new().service(index))
.bind(("127.0.0.1", 8080))?
.run()
.await
}
八、错误处理
8.1) trait 和实现 trait 的 Responder
pub trait ResponseError {
fn error_response(&self) -> Response<Body>;
fn status_code(&self) -> StatusCode;
}
// 为 Result 实现 Response
impl<T: Responder, E: Into<Error>> Responder for Result<T, E>
8.2) 自定义错误
use actix_web::{error, Result};
use derive_more::{Display, Error};
#[derive(Debug, Display, Error)]
#[display(fmt = "my error: {}", name)]
struct MyError {
name: &'static str,
}
// Use default implementation for `error_response()` method
impl error::ResponseError for MyError {}
async fn index() -> Result<&'static str, MyError> {
Err(MyError { name: "test" })
}
8.3) 错误 logger
- 安装依赖
[dependencies]
env_logger = "0.8"
log = "0.4"
- 示例
本示例中使用
logger
中间,配合路由讲解 自定义错误实际使用方法。
use actix_web::{error, get, middleware::Logger, App, HttpServer, Result};
use derive_more::{Display, Error};
use log::info;
#[derive(Debug, Display, Error)]
#[display(fmt = "my error: {}", name)]
pub struct MyError {
name: &'static str,
}
// Use default implementation for `error_response()` method
impl error::ResponseError for MyError {}
#[get("/")]
async fn index() -> Result<&'static str, MyError> {
let err = MyError { name: "test error" };
info!("{}", err);
Err(err)
}
#[rustfmt::skip]
#[actix_web::main]
async fn main() -> std::io::Result<()> {
std::env::set_var("RUST_LOG", "info");
std::env::set_var("RUST_BACKTRACE", "1");
env_logger::init();
HttpServer::new(|| {
let logger = Logger::default();
App::new()
.wrap(logger)
.service(index)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
九、中间件
actix-web 中间件遵循洋葱模型
9.1) 中间件的作用
- 预处理请求
- 后处理响应
- 修改应用程序状态
- 访问外部服务(redis、日志记录、会话)
9.2)中间类型
中间件类型 | 描述 |
---|---|
结构体中间件 | - 通常实现了 actix_service::Transform trait 的结构体。 - 能够创建中间件服务以处理请求和响应。 |
函数中间件 | - 简单的函数,接受请求并返回响应或传递请求给下一个中间件或路由处理函数。 - 用于执行简单的操作。 |
闭包中间件 | - 匿名函数闭包,可以直接传递给中间件包装函数(如 .wrap_fn() )。 - 适用于简单的、一次性操作。 |
Trait 对象中间件 | - 实现特定中间件 trait 的任何类型的实例,这些 trait 定义了中间件的行为。 - 允许动态选择中间件。 |
内置中间件 | - Actix Web 提供的一些内置中间件,如日志记录中间件 Logger 、压缩中间件 Compress 等。 - 可直接使用而无需自行实现。 |
组合中间件 | - 可以将多个中间件按顺序组合,以构建复杂的中间件处理管道。 - 通常通过 App::wrap() 或 App::wrap_fn() 方法添加到应用程序中。 |
9.3) 中间件结构体的实现步骤
步骤 | 描述 |
---|---|
1. 创建一个结构体 | 创建一个结构体来表示中间件,可以包含配置或初始化参数。 |
2. 实现 actix_service::Transform trait | 在结构体上实现 actix_service::Transform trait,包括 new_transform 方法。 |
3. 创建中间件服务结构体 | 创建一个结构体,表示中间件服务,实现 actix_service::Service trait。这个结构体将处理请求和响应。 |
4. 在中间件服务的 call 方法中处理请求 | 在 call 方法中处理请求、响应或执行其他操作。这里是中间件的核心逻辑。 |
5. 在应用程序中使用中间件 | 在 Actix Web 应用程序中使用中间件,通常在 HttpServer::new() 中使用 .wrap() 方法添加中间件。 |
6. 启动 Actix Web 服务器 | 创建并绑定 HTTP 服务器,然后运行服务器,使中间件生效。 |
十、静态文件
10.1)依赖
actix-web = "4.4.0"
actix-files = "0.6.2"
10.2) 一个简单的示例
use actix_files as fs;
use actix_web::{App, HttpServer};
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new().service(
// 配置静态文件服务,"/static"是URL路径前缀,"./static"是文件系统目录
fs::Files::new("/static", "./static").show_files_listing(), // 显示目录列表(可选)
)
// 添加其他路由和服务
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
十一、其它协议
11.1) websockets
use actix_web_actors::ws;
#[get("/ws")]
async fn index(req: HttpRequest, stream: web::Payload) -> Result<HttpResponse, Error> {
ws::start(MyWs, &req, stream)
}
index 中包含三个参数 req, stream, MyWs,其中 MyWs
结构体是需要自己实现:
定义: MyWs 结构体,实现 Actor 和 StreamHandler
/// Define Websocket actor
struct MyWs;
impl Actor for MyWs {
type Context = ws::WebsocketContext<Self>;
}
/// Handler for ws::Message message
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::Ping(msg)) => ctx.pong(&msg),
Ok(ws::Message::Text(text)) => ctx.text(text),
Ok(ws::Message::Binary(bin)) => ctx.binary(bin),
_ => (),
}
}
}
11.2) http/2
[dependencies]
actix-web = { version = "4", features = ["openssl"] }
openssl = { version = "0.10", features = ["v110"] }
use actix_web::{web, App, HttpRequest, HttpServer, Responder};
use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod};
async fn index(_req: HttpRequest) -> impl Responder {
"Hello."
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
builder
.set_private_key_file("key.pem", SslFiletype::PEM)
.unwrap();
builder.set_certificate_chain_file("cert.pem").unwrap();
HttpServer::new(|| App::new().route("/", web::get().to(index)))
.bind_openssl("127.0.0.1:8080", builder)?
.run()
.await
}
十二、异步
12.1)异步请求 async fn
use actix_web::{get, web, App, HttpServer, Result};
#[get("/hello")]
async fn hello() -> Result<String> {
// 异步操作,例如数据库查询或HTTP请求
let result = async_operation().await;
Ok(result)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.service(hello)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
12.2) 异步中间件
use actix_web::{Error, HttpRequest, HttpResponse, middleware, App, HttpServer};
// 异步中间件函数
async fn my_middleware(
req: HttpRequest,
srv: web::Data<AppState>,
) -> Result<HttpRequest, Error> {
// 执行异步操作,例如身份验证
let is_authenticated = async_authentication(req.clone(), srv).await;
if is_authenticated {
Ok(req)
} else {
Err(ErrorUnauthorized("Unauthorized"))
}
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.wrap(middleware::Logger::default())
.wrap_fn(my_middleware) // 添加异步中间件
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
12.3) 异步响应
use actix_web::{get, HttpResponse, Result};
#[get("/async_response")]
async fn async_response() -> Result<HttpResponse> {
// 异步构建响应
let body = async_generate_response_body().await;
Ok(HttpResponse::Ok().body(body))
}
十三、测试
13.1) 集成测试
将测试集成到 app 中
#[cfg(test)]
mod tests {
use actix_web::{http::header::ContentType, test, App};
use super::*;
#[actix_web::test]
async fn test_index_get() {
let app = test::init_service(App::new().service(index)).await;
let req = test::TestRequest::default()
.insert_header(ContentType::plaintext())
.to_request();
let resp = test::call_service(&app, req).await;
assert!(resp.status().is_success());
}
}
13.2 单元测试
#[cfg(test)]
mod tests {
use super::*;
use actix_web::{
http::{self, header::ContentType},
test,
};
#[actix_web::test]
async fn test_index_ok() {
let req = test::TestRequest::default()
.insert_header(ContentType::plaintext())
.to_http_request();
let resp = index(req).await;
assert_eq!(resp.status(), http::StatusCode::OK);
}
#[actix_web::test]
async fn test_index_not_ok() {
let req = test::TestRequest::default().to_http_request();
let resp = index(req).await;
assert_eq!(resp.status(), http::StatusCode::BAD_REQUEST);
}
}
十四、其他
14.1)自动重新启动
cargo install cargo-watch
cargo watch -x run
14.2) 数据库 mongodb
[dependencies]
mongodb = "2.0.0"
use actix_web::{App, HttpResponse, HttpServer, Responder};
use mongodb::{Client, options::ClientOptions};
use std::env;
async fn index() -> impl Responder {
// 连接到MongoDB数据库
let database_url = "mongodb://localhost:27017";
let client_options = ClientOptions::parse(database_url)
.await
.expect("Failed to parse client options");
let client = Client::with_options(client_options)
.expect("Failed to create client");
// 选择数据库和集合
let db = client.database("mydb");
let collection = db.collection("mycollection");
// 在此处执行MongoDB操作
// 例如,插入文档或查询文档
HttpResponse::Ok().body("Connected to MongoDB")
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new().route("/", actix_web::web::get().to(index))
})
.bind("127.0.0.1:8080")?
.run()
.await
}
小结
本文主要梳理了 actix-web 的知识点。有充分的用例,从http-server 到路由,再到请求响应对象,错误处理,中间件,静态文件处理,其他的协议(http2/websocket), 在 actix-web 中处理异步,actix-web 测试其他工具(自动重启),最后讲解了连接 mongodb 数据库。如果我们的文章能够帮助到你,不妨三连,不妨关注同名公众号。
转载自:https://juejin.cn/post/7284513473316732989