入门级 ActixWeb 知识点摄取器

4,864 阅读12分钟

一、简介

ActixWeb 入门.png

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 Cookieweb::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 数据库。如果我们的文章能够帮助到你,不妨三连,不妨关注同名公众号。