入门级 ActixWeb 知识点摄取器

一、简介

actix:Rust 的 Actor 框架。
actix-web: Actix Web 是一个强大、实用且速度极快的 Rust Web 框架
Actor:一种并发执行的组件,通常用于处理异步任务和状态管理。Actors 在 actix 框架中起到了关键作用,它们是一种特殊的 Rust 类型,用于处理并发请求和状态管理。

二、HTTP 服务

HttpServer: 是 actix-web 库中的一个重要组件,它用于创建和配置 HTTP 服务器。

ts 复制代码
use actix_web::{ HttpServer };
  • 基本用法
rs 复制代码
HttpServer::new(/* your app*/)
.bind("127.0.0.1:8080")?
.workers(4) // 设置工作进程数量
.run()
.await

三、 App 应用

App 是一个核心结构,用于配置和组织您的 Web 应用程序的路由、中间件和其他功能。

  • 导入
rs 复制代码
use actix_web::{App};
  • 基本用法
rs 复制代码
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 函数单独定义

rs 复制代码
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 方法为例
rs 复制代码
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 函数成组定义

rs 复制代码
pub fn resource<T: IntoPatterns>(path: T) -> Resource {
    Resource::new(path)
}
  • 定义资源群 resource,以下示例是:
rs 复制代码
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 函数自定定义配置路由

rs 复制代码
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 函数定义作用域

rs 复制代码
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 路径中提取参数。

rust 复制代码
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:解析查询字符串中的参数。

rust 复制代码
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 请求体中的数据。

rust 复制代码
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:处理表单数据。

rust 复制代码
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:在处理程序之间共享应用程序的状态和数据。

rust 复制代码
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) 请求对象示例

rs 复制代码
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 使用示例

rs 复制代码
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

rs 复制代码
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 提供的能力,解析结构体:

rs 复制代码
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) 流式请求

rs 复制代码
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

rs 复制代码
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 依赖

rs 复制代码
[dependencies]  
serde = { version = "1.0", features = ["derive"] }

7.3) 响应 json

  • 序列化的结构体
rs 复制代码
use serde::Serialize;

#[derive(Serialize)]
struct MyObj {
    name: String,
}
  • 一个简单的示例

使用 web::Json 构建响应结构体实例:

rs 复制代码
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

rs 复制代码
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) 自定义错误

rs 复制代码
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

  • 安装依赖
sh 复制代码
[dependencies]  
env_logger = "0.8"  
log = "0.4"
  • 示例

本示例中使用 logger 中间,配合路由讲解 自定义错误实际使用方法。

rs 复制代码
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)依赖

tomal 复制代码
actix-web = "4.4.0"
actix-files = "0.6.2"

10.2) 一个简单的示例

rs 复制代码
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

rs 复制代码
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

rs 复制代码
/// 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

toml 复制代码
[dependencies]  
actix-web = { version = "4", features = ["openssl"] }  
openssl = { version = "0.10", features = ["v110"] }
rs 复制代码
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

rs 复制代码
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) 异步中间件

rs 复制代码
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) 异步响应

rs 复制代码
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 中

rs 复制代码
#[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 单元测试

rs 复制代码
#[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)自动重新启动

sh 复制代码
cargo install cargo-watch

cargo watch -x run

14.2) 数据库 mongodb

toml 复制代码
[dependencies]
mongodb = "2.0.0"
rs 复制代码
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 数据库。如果我们的文章能够帮助到你,不妨三连,不妨关注同名公众号。

相关推荐
@大迁世界4 分钟前
TypeScript 的本质并非类型,而是信任
开发语言·前端·javascript·typescript·ecmascript
GIS之路13 分钟前
GDAL 实现矢量裁剪
前端·python·信息可视化
勇哥java实战分享14 分钟前
短信平台 Pro 版本 ,比开源版本更强大
后端
是一个Bug16 分钟前
后端开发者视角的前端开发面试题清单(50道)
前端
Amumu1213818 分钟前
React面向组件编程
开发语言·前端·javascript
学历真的很重要19 分钟前
LangChain V1.0 Context Engineering(上下文工程)详细指南
人工智能·后端·学习·语言模型·面试·职场和发展·langchain
计算机毕设VX:Fegn089522 分钟前
计算机毕业设计|基于springboot + vue二手家电管理系统(源码+数据库+文档)
vue.js·spring boot·后端·课程设计
上进小菜猪38 分钟前
基于 YOLOv8 的智能杂草检测识别实战 [目标检测完整源码]
后端
持续升级打怪中40 分钟前
Vue3 中虚拟滚动与分页加载的实现原理与实践
前端·性能优化
GIS之路43 分钟前
GDAL 实现矢量合并
前端