使用 Rust 进行 Web 开发 — 03/x:创建 REST API

2025-05-27

使用 Rust 进行 Web 开发 — 03/x:创建 REST API

在Twitter上关注我,随时获取 Rust Web 开发的最新资讯。也可以查看本系列的GitHub 仓库。


内容

  1. HTTP 请求
  2. POST/PUT/PATCH/DELETE 是特殊的
  3. 框架的作用
  4. 创建 API 规范
  5. 制作 API
  6. 输入验证
  7. 概括

API 是现代快节奏 Web 环境的基石。前端应用程序、其他 Web 服务和物联网设备都需要能够与您的服务进行通信。API 端点就像一扇门,您可以决定哪些数据以何种格式进入。

由于 Rust 是一门静态类型语言,并且拥有强大的编译器,因此您不会遇到在生产环境中运行 Web 服务时常见的许多陷阱。尽管仍然有一些运行时错误需要您处理。

HTTP 请求

当我们谈论创建 API 时,我们基本上指的是一个 Web 应用程序,它会监听某些路径并做出相应的响应。但首先要说的是,两台设备要相互通信,必须建立 TCP 连接。

TCP 是一种协议,双方可以使用该协议建立连接。建立此连接后,您可以接收和发送消息给对方。HTTP 是另一种协议,它建立在 TCP 之上,并定义请求和响应的内容。

因此,从 Rust 的角度来看,TCP 已在 Rust 核心库中实现,而 HTTP 则未实现。无论您在上一篇文章中选择了哪个框架,它们都实现了 HTTP,因此能够接收和发送 HTTP 格式的消息。

例如,GET 请求的示例如下所示:

GET / HTTP/1.1
Host: api.awesomerustwebapp.com
Accept-Language: en
Enter fullscreen mode Exit fullscreen mode

其中包括:

  • GET:HTTP 方法
  • /:路径
  • HTTP/1.1:HTTP 协议的版本
  • HOST:我们要从中请求数据的服务器的主机/域
  • Accept-Language:我们喜欢和理解哪种语言

最常用的 HTTP 方法是:

  • 得到
  • 邮政
  • 修补
  • 删除

POST/PUT/PATCH/DELETE 是特殊的

GET我们每次浏览网页时都会用到它。然而,如果我们想要修改数据(比如将POST数据发送到另一台服务器),就需要更加谨慎和精确。

首先,并非所有服务器都允许直接向其他服务器发送一堆数据。例如,我们的 API 可以这样说:“我只接受来自主机名 的服务器的数据allowed.awesomerustapp.com。”

因此,当你POST向另一台服务器发送一个邮件时,实际发生的是CORS 工作流程

cors_workflow_with_reqwest

我们首先询问服务器允许什么,接受来自哪里的请求,以及接受的标头是什么。如果满足所有这些要求,我们就可以发送一个POST

例如,Web 框架actix有自己的cors 中间件

免责声明:并非所有框架(例如rockettite)都在其核心中实现了 CORS。然而,在专业的环境中,你会在DevOps 端处理 CORS ,例如将其添加到你的 NGINX 配置中。

框架的作用

我们利用其他人的辛勤劳动成果来创建 Web 应用程序。所有事情最终都需要在某个时刻实现,只是大多数时候并非由你来做。框架涵盖以下几个方面:

  • 启动 Web 服务器并打开 PORT
  • 监听此端口上的请求
  • 如果有请求进来,查看 HTTP 标头中的路径
  • handler根据 Path将请求路由到
  • 帮助您从请求中提取信息
  • 打包生成的dataHTTP StatusCode(由您创建的)并形成一个response
  • 退回response给寄件人

Rust Web 框架Tide包含http-service,它提供了处理 HTTP 调用时所需的基本抽象。crate http-service 构建于hyper之上,它将 TCP 流转换为有效的 HTTP 请求和响应。

架构概览潮流

你的任务是创建一个routes类似的函数/users/:id,并添加一个route_handler用于处理特定路径上请求的函数。框架会确保将传入的 HTTP 请求转发到这个特定的处理程序。

创建 API 规范

您必须首先定义您的资源,以了解您的应用程序需要处理哪些内容,并揭示它们之间的关系。因此,如果您想构建一个想法投票网站,您需要:

  • 用户
  • 想法
  • 投票

此场景的简单规范如下:

  • 用户
    • 邮政/users
    • 得到/users
    • /users/:user_id
    • 修补/users/:user_id
    • 删除/users/:user_id
    • 得到/users/:user_id

想法和投票都会有相应的表现。规范有两个好处:

  • 它为你提供指导,让你不会忘记一条路
  • 它有助于向 API 用户传达预期内容

您可以使用swagger之类的工具来编写完整的规范,其中还描述了数据的结构以及每条路径和路线的消息/响应。

更专业的规范应该包含每个路由的返回值以及请求和响应主体。不过,一旦你确定了 API 的外观和行为,就可以最终确定规范了。首先,一个简单的列表就足够了。

制作 API

根据你使用的框架,你的实现会有所不同。你必须关注以下特性:

  • 为每种方法创建路由(如app.at("/users").post(post_users_handler)
  • 从请求中提取信息(例如请求主体中的标头、uri-params 和 JSON)
  • 使用适当的 HTTP代码创建响应(200201等)400404

我正在为这个网络系列使用最新版本的Tide 。你可以将它添加到你的Cargo.toml文件中,并将其用于你的 Web 应用:

[dependencies]
tide = "0.1.0"
Enter fullscreen mode Exit fullscreen mode

我们的第一个User实现将如下所示:

async fn handle_get_users(cx: Context<Database>) -> EndpointResult {
    Ok(response::json(cx.app_data().get_all()))
}

async fn handle_get_user(cx: Context<Database>) -> EndpointResult {
    let id = cx.param("id").client_err()?;
    if let Some(user) = cx.app_data().get(id) {
        Ok(response::json(user))
    } else {
        Err(StatusCode::NOT_FOUND)?
    }
}

async fn handle_update_user(mut cx: Context<Database>) -> EndpointResult<()> {
    let user = await!(cx.body_json()).client_err()?;
    let id = cx.param("id").client_err()?;

    if cx.app_data().set(id, user) {
        Ok(())
    } else {
        Err(StatusCode::NOT_FOUND)?
    }
}

async fn handle_create_user(mut cx: Context<Database>) -> EndpointResult<String> {
    let user = await!(cx.body_json()).client_err()?;
    Ok(cx.app_data().insert(user).to_string())
}

async fn handle_delete_user(cx: Context<Database>) -> EndpointResult<String> {
    let id = cx.param("id").client_err()?;
    Ok(cx.app_data().delete(id).to_string())
}

fn main() {
    // We create a new application with a basic, local database
    // You can use your own implementation, or none: App::new(())
    let mut app = App::new(Database::default());
    app.at("/users")
        .post(handle_create_user)
        .get(handle_get_users);
    app.at("/users/:id")
        .get(handle_get_user)
        .patch(handle_update_user)
        .delete(handle_delete_user);

    app.serve("127.0.0.1:8000").unwrap();
}
Enter fullscreen mode Exit fullscreen mode

您可以在本系列的GitHub 存储库中找到代码的完整实现。

我们看到我们首先要创建一个新的应用程序

let mut app = App::new(())
Enter fullscreen mode Exit fullscreen mode

添加路线

app.at("/users")
Enter fullscreen mode Exit fullscreen mode

并为每个路由添加我们想要处理的 HTTP 请求

app.at("/users").get(handle_get_users)
Enter fullscreen mode Exit fullscreen mode

每个框架都有不同的方法来提取参数和 JSON 主体。Actix 使用Extractors,Rocket 使用Query Guards

使用 Tide,您可以通过 访问请求参数、请求主体以及数据库连接。因此,当我们想要使用特定的Context更新 时,我们会将 发送。然后,我们调用 的方法。UseridPATCH/users/:idhandle_update_user

id在这个方法中,我们可以像这样从 URI访问:

let id = cx.param("id").client_err()?;
Enter fullscreen mode Exit fullscreen mode

每个框架也都有自己的方式将响应发送回发送者。Tide 使用EndpointResult,rocket 使用Response,actix 使用HttpResponse

其他一切都完全由你决定。框架可能会帮助你进行会话管理和身份验证,但你也可以自己实现。

我的建议是:用你选择的框架构建应用程序的第一个框架,弄清楚如何从请求中提取信息以及如何形成响应。一旦这些工作完成,你就可以使用你的 Rust 技能来构建你想要的小型或大型应用程序。

输入验证

在 Rust 的世界里,你最好的朋友就是serde 了。它不仅能帮你解析 JSON 和其他格式,还能让你序列化数据。

当我们讨论输入验证时,我们希望确保获取的数据具有正确的格式。假设我们从请求中提取 JSON 主体:

let user: User = serde_json::from_str(&request_body);
Enter fullscreen mode Exit fullscreen mode

我们在这里使用serde_json将 JSON 字符串转换为我们选择的结构体。因此,如果我们创建了这个结构体:

struct User {
    name: String,
    height: u32,
}
Enter fullscreen mode Exit fullscreen mode

我们要确保发送者包含name height。如果我们只是执行serde_json::from_str,而发送者忘记传递身高,应用程序就会崩溃并关闭,因为我们预期的响应是 user: let user: User

我们可以像这样改进错误处理:

let user: User = match serde_json::from_str(&request_body) {
    Ok(user) => user,
    Err(error) => handle_error_case(error),
};
Enter fullscreen mode Exit fullscreen mode

我们捕获错误并调用我们的handle_error_case方法来优雅地处理它。

概括

  1. 选择你喜欢的框架
    • 火箭是夜间的
    • actix稳定
    • Tide在 Rust Core 附近培育,并且每晚都可以在 Rust 上运行
  2. 需要注意的是,目前还没有通用的 CORS 处理方案。建议在 DevOps 端处理这个问题(例如 NGINX)。
  3. 选择框架后,指定您的资源(/users:GET,POST等)
  4. 弄清楚你选择的框架如何处理从请求中提取参数和 JSON 以及如何形成响应
  5. match通过serde_json验证您的输入
文章来源:https://dev.to/gruberb/web-development-with-rust-03-x-create-a-rest-api-3i82
PREV
🚨💥 您必须了解的 5 大热门开源 LLM 工具和框架 ✨🚀 TL;DR 1. DeepEval - LLM 评估框架 2. LlamaIndex - LLM 应用程序的数据框架 3. Ollama - 使用大型语言模型启动和运行 4. Guidance 5. DSPy - 通过算法优化 LM 提示和权重
NEXT
Docker 和 Fast API 入门