当前位置: 首页 > news >正文

在actix-web中创建一个提取器

1. 核心业务与测试

背景: 技术负责人兼任产品负责人,可直接主导项目决策。订阅功能需改进为收集“可用于邮件问候的标识符”(如昵称),而非强制真实姓名。

数据传递与编码

  • 前端通过 HTML 表单使用 POST 请求提交数据。

  • 编码格式为 application/x-www-form-urlencoded

    • 键值对格式为 key=value,多个键值对以 & 分隔。

    • 特殊字符采用百分号编码(如空格→%20@%40)。

  • 示例:name=le%20guin&email=ursula_le_guin%40gmail.com

后端响应规则

  • nameemail 均有效 → 返回 200 OK

  • 若任一字段缺失 → 返回 400 BAD REQUEST

测试要求: 在 tests/health_check.rs 中新增集成测试,验证端点行为:

  • 提交有效数据时应得到 200。

  • 提交不完整数据时应得到 400。

接口的核心实现(待修改)

async fn subscriptions() -> impl Responder {HttpResponse::Ok().finish()
}

服务器中注册路由

App::new().route("/subscriptions", web::post().to(subscriptions))

测试文件:tests/health_check.rs

// tests/health_check.rsuse std::net::TcpListener;
use actix_web::Responder; // 补充actix_web必要的引用
use tokio; // 补充tokio必要的引用
// 假设 zero2prod::run 函数已定义在 crate 的 lib.rs 或 main.rs 中// # 表单数据完整时,返回 200
#[tokio::test]
async fn subscribe_returns_a_200_for_valid_form_data() {let address = spawn_app();let client = reqwest::Client::new();let body = "name=cang%20li&email=gl0wniapar%40gmail.com";let response = client.post(&format!("{}/subscriptions", &address)).header("Content-Type", "application/x-www-form-urlencoded").body(body).send().await.expect("Failed to execute request.");assert_eq!(response.status().as_u16(), 200);
}// # 数据缺失时,应返回 400
#[tokio::test]
async fn subscribe_returns_a_400_when_data_is_missing() {let app_address = spawn_app();let client = reqwest::Client::new();let test_cases = vec![("name=cangli", "Did not have email"), // 优化错误信息("email=gl0wniapar%40gmail.com", "Did not have name"), // 优化错误信息("", "Did not have both name and email"),];for (invalid_body, error_message) in test_cases {let response = client.post(format!("{}/subscriptions", app_address)).header("Content-Type", "application/x-www-form-urlencoded").body(invalid_body).send().await.expect("Failed to execute request.");assert_eq!(response.status().as_u16(), 400, "The API did not return 400 when {} was expected.", error_message);}
}fn spawn_app() -> String {// ... 启动应用实例的逻辑,需要 actix_web::server::HttpServer 和 tokio::spawn// 假设 zero2prod::run 是一个返回 actix_web::server::HttpServer 的函数let listener = TcpListener::bind("127.0.0.1:0").expect("Failed to bind random port");let port = listener.local_addr().expect("Failed to get local address").port();let server = zero2prod::run(listener).expect("Failed to run server"); // 假设 zero2prod::run 可用tokio::spawn(server);format!("http://127.0.0.1:{}", port)
}

2. 提取器 (Extractor) 优化

提取器 提取器用于从传入的请求中智能地提取特定信息,并将其转化为 Rust 类型。 actix-web 提供了多种内置提取器,如:

  • Path: 用于从请求路径中获取动态路径参数。

  • Query: 用于获取 URL 查询参数。

  • Json: 用于解析 application/json 编码的请求体。

  • Form: 用于解析 application/x-www-form-urlencoded 编码的请求体(即 HTML 表单提交数据)。

满足需求的提取器 使用 web::Form<T> 提取器。它会自动处理表单数据的反序列化和验证。

提取器示例

// 1. 定义一个结构体来匹配表单数据的字段
#[derive(serde::Deserialize)]
struct FormData {// 字段名必须与表单中的 'key' 匹配name: String, // 用于邮件问候的标识符(昵称)email: String,
}// 2. 将 Form 提取器作为处理器函数的参数
/// 仅当请求头 Content-Type 为 application/x-www-form-urlencoded 
/// 且请求体能反序列化为 `FormData` 结构体时,才会调用此处理器。
async fn subscriptions(form: web::Form<FormData>) -> HttpResponse {// 成功提取数据,返回 200 OK// form.0 或 form.into_inner() 可以访问内部的 FormData 实例HttpResponse::Ok().finish()
}

为什么测试通过了?
原始的 subscriptions 函数实现是:

async fn subscriptions() -> impl Responder {HttpResponse::Ok().finish()
}

这个函数不接受任何参数(提取器),因此它不会检查请求体或 Content-Type,直接返回 200 OK

  • 有效数据测试:返回 200,通过。

  • 不完整数据测试:返回 200,但测试期望 400,因此这个测试实际上会失败!

  • 如果使用新的 web::Form<FormData> 提取器,不完整数据测试才会如预期般成功返回 400

    正确的解释: 当使用 新的、带有 web::Form<FormData> 提取器subscriptions 函数时:

    1. 有效数据Form 提取成功,处理器执行并返回 200 OK

    2. 不完整/无效数据Form 提取失败(serde 无法反序列化),Form 提取器根据 FromRequest 实现的默认行为,返回 400 BAD REQUEST,处理器函数甚至不会被调用。

3. 提取器背后的机制:FormFromRequest (修正和精炼)

Form 结构体

#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
pub struct Form<T>(pub T);

Form<T> 只是一个围绕泛型类型 T 的包装器。其核心功能通过实现 FromRequest trait 来实现。

FromRequest Trait 在 actix-web 中,所有作为处理器函数参数的类型都必须实现 FromRequest trait。该 trait 允许 actix-web 在处理传入的 HTTP 请求时,从请求头 (HttpRequest) 和有效载荷 (Payload) 中提取数据。

pub trait FromRequest: Sized {type Error: Into<actix_web::Error>; // 错误类型必须能转换为 actix_web::Error// 核心异步方法,尝试从请求中提取自身async fn from_request(req: &HttpRequest, payload: &mut Payload) -> Result<Self, Self::Error>;// [...]
}

工作流程

  1. actix-web 接收请求,并确定要调用的处理器函数。

  2. actix-web 会依次调用处理器函数中每个参数from_request 异步方法。

  3. 成功:所有参数都成功提取,执行处理器函数。

  4. 失败:任何一个参数提取失败,则将提取器返回的错误 (Self::Error) 转换为 actix_web::Error,再由框架将其转换为相应的 HttpResponse (通常是 400 BAD REQUEST413 PAYLOAD TOO LARGE),并返回给客户端,处理器函数不会被调用。

Form<T>FromRequest 实现 Form<T> 的实现依赖于 T 必须实现 serde::de::DeserializeOwned trait。

impl<T> FromRequest for Form<T>
where T: DeserializeOwned + 'static, // ... 忽略其他约束
{type Error = actix_web::Error;async fn from_request(/* ... */) -> Result<Self, Self::Error> {// ... (内部使用 actix_web 提供的逻辑来处理 URL 编码)match UrlEncoded::new(req, payload).await {Ok(item) => Ok(Form(item)),// 默认情况下,解析失败(如数据缺失或格式错误)// 会被转换为 actix_web::Error,默认返回 400 BAD REQUESTErr(e) => Err(error_handle(e)) }}
}

其中的关键步骤是:

  1. 读取整个请求体字节流。

  2. 使用 serde_urlencoded::from_bytes::<T>(&body) 进行反序列化。

    • 成功:包装成 Form<T> 并返回。

    • 失败:返回一个 urlencodedError::Parse 或其他错误,该错误最终被转换为 400 BAD REQUEST

4. serde:数据序列化与反序列化的通用框架 (精炼和修正)

为什么需要 serde
serde (Serializer/Deserializer) 是 Rust 生态中高效且通用的数据结构序列化和反序列化框架。它本身不处理任何特定数据格式(如 JSON、YAML),而是作为数据格式库和 Rust 类型之间的翻译中间层

serde 的核心机制

  1. 数据模型serde 定义了一套通用的数据模型(如布尔、整数、字符串、序列、映射、结构体等),涵盖了 Rust 类型可能的所有结构。

  2. 核心 Trait

    • Serialize:定义了如何将 Rust 类型分解为 Serde 数据模型。

    • Deserialize:定义了如何将 Serde 数据模型构建为 Rust 类型。

    • Serializer/Deserializer:由具体数据格式库(如 serde_jsonserde_urlencoded)实现,用于处理实际的格式编码/解码。

工作流程(序列化为例)

  • Rust 类型(如 Vec<T>)实现 Serialize trait。

  • serialize 方法中,类型调用 Serializer 提供的接口(如 serialize_seqserialize_element),将自身结构描述给序列化器。

  • 格式库(如 serde_urlencoded)实现了 Serializer,负责根据接收到的结构描述,生成最终的 URL 编码字符串或字节。

效率与零成本抽象 serde 在编译期利用 Rust 的 单态化(Monomorphization) 特性,为每种具体类型生成独立的函数实现。这消除了运行时的类型检查和反射开销,实现了零成本抽象(Zero-cost Abstraction)。特定格式(反)序列化信息在编译期即可完全确定,无需运行时查找,保证了高性能。

便利性:#[derive(Serialize)]#[derive(Deserialize)] 这两个过程宏是 serde 的核心便捷工具。它们自动解析用户定义的 structenum,并生成对应 SerializeDeserialize trait 的实现代码。这极大地简化了开发工作,避免了手动编写冗长、易错的序列化逻辑。

http://www.dtcms.com/a/487084.html

相关文章:

  • 一个CTO的一天:Indie Team Kickoff
  • C++ 链表技巧
  • 贪心:Stall Reservations S(重写)
  • 商城网站有免费建设的吗网站开发的主要内容
  • 泉州企业网站设计招商网站建设网
  • 数据库主从同步原理等信息
  • 广西网站建设公司招聘iapp制作软件
  • 人工智能简史(2)
  • 5 网站建设的基本步骤是申请公司邮箱
  • android 16kb 内存适配
  • 数据安全指南-合规治理 2025 等保2.0测评实施 全球数据保护法规对比 数据分类分级管理 ISO27001与SOC2认证 跨境数据传输合规
  • LongVU论文阅读
  • h5游戏免费下载:开心消消乐
  • 做暖暖网站网站建设公司的市场定位
  • (ACP广源盛)DD3118(S)---USB3.0读卡器,支持双卡单待模式,产品规格介绍
  • 特朗普的比特币战略对加密市场周期的影响:从矿业到 Meme 生态的传导机制
  • 江苏省城乡住房建设厅网站模板出售网站源码
  • 你们的LoRaWAN网关能传多远?
  • 腾讯有服务器如何做网站专业网站建设团队
  • 【K8S】学习(一) 基础概念
  • 网站建设公司广告 晴天娃娃政务网站建设建议
  • iOS八股文之 Runtime
  • Transformer ViT 架构(转载)
  • 算法学习 05
  • 注册网站空间邵阳 网站开发 招聘
  • 技术准备一:gflags
  • 高端个性化网站开发如何避免网站被攻击
  • 怎样创建网站或者网址网页设计的模板
  • 深圳网站定制建设网站推广运营
  • 力扣2401. 最长优雅子数组