rust web 开发之axum使用手册 - 知乎


本站和网页 https://zhuanlan.zhihu.com/p/678636443 的作者无关,不对其内容负责。快照谨为网络故障时之索引,不代表被搜索网站的即时页面。

Rust Web 开发之Axum使用手册 - 知乎
切换模式 写文章 登录/注册
Rust Web 开发之Axum使用手册
前端小魔女
日拱一卒
❝生活的刁难,并不是要你变得气急败坏,而是要你变得更加从容
大家好,我是
「柒八九」
。一个
「专注于前端开发技术/
Rust
AI
应用知识分享」
Coder
前言
之前,我们在很多文章都提到过
Rust Web
框架。例如
用 Rust 搭建 React Server Components 的 Web 服务器
你应该知晓的Rust Web 框架
其中有一个
框架的出现频率都很高 -- 那就是axum。
并且在crate trend的下载量来看
axum
也是遥遥领先。
所以,我们今天这篇文章就来简单介绍一下
的用法。
好了,天不早了,干点正事哇。
我们能所学到的知识点
前置知识点
Axum 中的路由
在 Axum 中添加数据库
在 Axum 中的应用状态
Axum 中的提取器
Axum 中的中间件
在 Axum 中提供静态文件
部署 Axum
1. 前置知识点
「前置知识点」
,只是做一个概念的介绍,不会做深度解释。因为,这些概念在下面文章中会有出现,为了让行文更加的顺畅,所以将本该在文内的概念解释放到前面来。
「如果大家对这些概念熟悉,可以直接忽略」
同时,由于阅读我文章的群体有很多,所以有些知识点可能
「我视之若珍宝,尔视只如草芥,弃之如敝履」
。以下知识点,请
「酌情使用」
REST
❝REST is an acronym for REpresentational State Transfer and an architectural style for distributed hypermedia systems.
翻译成中文就是:
REpresentational State Transfer
的首字母缩写,也是
分布式超媒体系统
的架构风格。
不是一种协议或标准,而是一种
「架构风格」
。它通常基于
HTTP
协议,使用标准的HTTP方法(如
GET
POST
PUT
DELETE
)进行通信。
RESTful
API的设计目标是简单、可扩展、易于理解,并与现有的Web标准兼容。
REST 基于一些约束和原则,这些约束和原则促进了设计中的简单性、可伸缩性和无状态性。
架构的六个指导原则或约束是:
2. Axum 中的路由
不知道大家使用过Express构建过应用没?如果没有,那也没关系。
const express = require("express");
const app = express();
// 当向主页发出 GET 请求时,以 "hello front789"作为回应
app.get("/", (req, res) => {
res.send("hello front789");
});
这是截取
Express
官网的关于路由的例子。仅用寥寥几行代码就构建了一个网络服务。
而,我们今天的主角
Axum
同样拥有和
的神奇功能。它们都遵循类
的 API 设计。我们可以创建
处理程序函数
handler
)并将它们附加到
axum::Router
上。
async fn hello_front789() -> &'static str {
"前端柒八九!"
然后我们可以像下面这样将其添加到
Router
中:
use axum::{Router, routing::get};
fn init_router() -> Router {
Router::new()
.route("/", get(hello_front789))
上面的例子和
达到了相同的效果
当向主页发出 GET 请求时,以 "前端柒八九"作为回应
对于
来说,它需要是一个
axum::response::Response
类型,或者实现
axum::response::IntoResponse
。这对于大多数
基本类型
(可以参考Rust 学习之数据类型)
例如,如果我们想向用户发送一些
JSON
数据,我们可以使用
类型,使用
axum::Json
类型封装我们要发送回的数据。
use axum::Json;
async fn json() -> Json<Vec<String>> {
Json(vec!["front".to_owned(), "789".to_owned()])
像我们刚开始提供的代码,我们也可以返回直接返回一个
字符串切片
&'static str
)。
我们也可以直接使用
impl IntoResponse
。但是,直接使用也意味着需要
「确保所有返回类型都是相同的类型」
!也就是我们可能会
遇到不必要的错误
。所以,我们可以为返回类型实现一个
enum
struct
来达到
「返回类型都是相同类型」
的制约条件。
use axum::{
response::{
Response,
IntoResponse
},
Json,
http::StatusCode
};
use serde::Serialize;
// 用于封装 `JSON` 响应体的数据。
#[derive(Serialize)]
struct Message {
message: String
// 定义了几种 `API` 的响应类型。
// 1. `OK` 和 `Created` 对应不同的 `HTTP` 状态码;
// 2. `JsonData` 包装了 `Vec<Message>` 的 `JSON` 数据。
enum ApiResponse {
OK,
Created,
JsonData(Vec<Message>),
// 这让 `ApiResponse` 可以被自动转换成一个 `axum Response`。
impl IntoResponse for ApiResponse {
fn into_response(self) -> Response {
// 检查枚举变量,返回相应的 HTTP 状态码和数据。
match self {
Self::OK => (StatusCode::OK).into_response(),
Self::Created => (StatusCode::CREATED).into_response(),
Self::JsonData(data) => (StatusCode::OK, Json(data)).into_response()
所以通过
ApiResponse
枚举和
实现,可以非常方便的生成符合结构的
API 响应。并可以轻松的
「兼容不同类型的响应状态码」
然后在
中实现该
async fn my_function() -> ApiResponse {
ApiResponse::JsonData(vec![Message {
message: "hello 789".to_owned()
}])
当然,我们也可以对返回值使用 Result 类型!尽管错误类型在技术上也可以接受任何可以转化为
响应的内容,但我们也可以实现一个错误类型来表示
请求在我们的应用程序中可能失败的几种不同方式,就像我们对成功的
请求
所做的那样。例如:
enum ApiError {
BadRequest,
Forbidden,
Unauthorised,
InternalServerError
// ... 省略ApiResponse的代码
async fn my_function() -> Result<ApiResponse, ApiError> {
//
这样我们的路由就可以区分错误和成功的请求了。
3. 在 Axum 中添加数据库
中使用数据库,那么sqlx肯定是绕不过的。
通常在设置数据库时,我们可能需要设置数据库连接:
use sqlx::PgPoolOptions;
#[derive(Clone)]
struct AppState {
db: PgPool
#[tokio::main]
async fn main() {
let pool = PgPoolOptions::new()
.max_connections(5)
.connect(<数据库地址>).await;
let state = AppState { pool };
let router = Router::new().route("/", get(hello_world)).with_state(state);
//... 其余代码
我们需要提供自己的 Postgres 实例,无论是在本地计算机上本地安装,还是通过
Docker
设置或者其他方式。但是,这里,我们使用 Shuttle 可以简化我们的操作。
#[shuttle_runtime::main]
async fn axum(
#[shuttle_shared_db::Postgres] pool: PgPool,
) -> shuttle_axum::ShuttleAxum {
// .. 其余代码
4. 在 Axum 中的应用状态
中我们可以使用axum::Extension来处理应用全局变量存储的问题。但是,它唯一的缺点就是
类型不安全
。在大多数 Rust Web 框架(包括
)中,我们使用所谓的
「应用状态」
app state
) - 一个专门用于在应用程序的路由之间共享的所有变量的结构体。 在
中完成此操作的唯一要求是该结构体需要实现
Clone
use sqlx::PgPool; // 这是一个Postgres连接池
pool: PgPool,
要使用它,我们将其插入路由器中,并通过将状态作为参数传递给处理函数中:
use axum::{Router, routing::get, extract::State};
.route("/", get(hello_front))
.route("/do_something", get(do_something))
.with_state(state)
// 注意添加应用状态不是强制的 - 仅在想要使用它时
async fn hello_front() -> &'static str {
"Hello 789!"
async fn do_something(
State(state): State<AppState>
) -> Result<ApiResponse, ApiError> {
// .. 我们的代码
除了使用
之外,我们还可以使用原子引用计数器(
std::sync::Arc
)封装应用状态结构体。
Arcs
是一种垃圾收集形式,可以跟踪克隆的数量,并且只有当没有副本时才会删除:
use std::sync::Arc;
let state = Arc::new(AppState { db });
现在当我们将状态添加到应用程序时,我们需要确保引用
State
提取器类型为
State<Arc<AppState>>
而不是
State<AppState>
我们还可以
「从应用程序状态派生子状态」
! 当我们需要来自主状态的一些变量但想限制给定路由可以访问的内容的访问控制权限时,这非常有用。例如:
// 应用程序状态
// 保存一些api特定状态
api_state: ApiState,
// api特定状态
struct ApiState {}
// 支持将一个 `AppState` 转换为一个 `ApiState`
impl FromRef<AppState> for ApiState {
fn from_ref(app_state: &AppState) -> ApiState {
app_state.api_state.clone()
5. Axum 中的提取器
提取器(
Extractors
)正如其名:它们从
请求中提取内容,并且将它们作为参数传递给处理程序函数来工作。目前,它已经对常规数据都有了原生支持,比如获取单独的
header
、路径、查询、表单和
例如,我们可以使用
类型通过从
请求中提取
请求体来处理
请求。
use serde_json::Value;
async fn my_function(
Json(json): Json<Value>
// ...我们的代码
上面代码虽然能够获取到数据,但是因为我们使用的是
serde_json::Value
,它的结构的动态多变的,可以包含任何内容。(在
Rust 赋能前端-开发一款属于我们的前端脚手架
中我们使用
serde_json
处理
json
文件)
为了能够达到我们想要的目标,我们尝试使用一个实现了
serde::Deserialize
的 Rust 结构体——这是将
「原始数据转化为结构体本身所必需」
的:
use serde::Deserialize;
#[derive(Deserialize)]
pub struct Submission {
Json(json): Json<Submission>
println!("{}", json.message);
表单和 URL 查询参数也可以通过将适当的类型添加到处理程序函数来以相同的方式处理 - 例如,表单提取器可能如下所示:
Form(form): Form<Submission>
在发送
请求到
API
HTML
端,当然我们还需要确保发送了正确的内容类型。
也可以以相同的方式处理,只是
不会消耗请求体!我们可以使用 TypedHeader 类型来做到这一点。 对于
Axum 0.6
,我们需要启用
headers
功能,但是在
0.7
中,它已移至 axum-extra crate,我们需要添加
typed-header
功能,如下所示:
cargo add axum-extra -F typed-header
使用类型化
可以简单地将其作为参数添加到处理程序函数中:
use headers::ContentType;
use axum::{TypedHeader, headers::Origin}; // 在axum 0.6上使用
use axum_extra::{TypedHeader, headers::Origin}; // 在axum 0.7上使用
TypedHeader(origin): TypedHeader<Origin>
println!("{}", origin.hostname);
除了
TypedHeaders
之外,
axum-extra
还提供了许多其他有用的类型可以使用。例如,它有一个
CookieJar
提取器,可以帮助管理
cookie
Axum 中的自定义提取器
现在我们对提取器有了更多了解,我们可能希望知道我们如何创建自己的提取器 - 例如,让我们假设我们需要创建一个提取器,根据请求体是
Json
还是表单进行解析。让我们设置我们的结构和处理程序函数:
#[derive(Debug, Serialize, Deserialize)]
struct Payload {
foo: String,
async fn handler(JsonOrForm(payload): JsonOrForm<Payload>) {
dbg!(payload);
struct JsonOrForm<T>(T);
现在我们可以为
JsonOrForm
结构实现
FromRequest<S, B>
//实现 `FromRequest` trait。这让 `JsonOrForm` 可以作为 `axum extractor` 使用。
#[async_trait]
impl<S, B, T> FromRequest<S, B> for JsonOrForm<T>
where
B: Send + 'static,
S: Send + Sync,
Json<T>: FromRequest<(), B>,
Form<T>: FromRequest<(), B>,
T: 'static,
type Rejection = Response;
async fn from_request(req: Request<B>, _state: &S) -> Result<Self, Self::Rejection> {
// 首先获取 `content-type` 请求头。
let content_type_header = req.headers().get(CONTENT_TYPE);
let content_type = content_type_header.and_then(|value| value.to_str().ok());
if let Some(content_type) = content_type {
如果是 `application/json`,使用 `req.extract()` extractor 提取为 `Json<T>`。
if content_type.starts_with("application/json") {
let Json(payload) = req.extract().await.map_err(IntoResponse::into_response)?;
return Ok(Self(payload));
// 如果是 `application/x-www-form-urlencoded`,提取为 `Form<T>`。
if content_type.starts_with("application/x-www-form-urlencoded") {
let Form(payload) = req.extract().await.map_err(IntoResponse::into_response)?;
// 返回 `Unsupported Media Type` 的错误。
Err(StatusCode::UNSUPPORTED_MEDIA_TYPE.into_response())
所以这段代码让我们可以灵活的处理
Form
格式的请求
Body
,作为一个方便的
extractor
中使用。
这避免了针对不同请求重复提取解析的代码。并且也统一了 handler 的签名。
Axum 0.7
中,这略有修改。
axum::body::Body
不再重新导出
hyper::body::Body
,而是自己的类型 - 这意味着它不再是泛型的,并且
Request
类型将始终使用
。这实质上意味着我们只需要删除
泛型:
impl<S, T> FromRequest<S> for JsonOrForm<T>
Json<T>: FromRequest<()>,
Form<T>: FromRequest<()>,
// ...同上
6. Axum 中的中间件
如前所述,与其他框架相比,
的一个巨大优势在于它与
tower
crates 兼容,这意味着我们可以为我们的 Rust API 使用
「任何想要的 Tower 中间件」
!例如,我们可以添加一个 Tower 中间件来压缩响应:
use tower_http::compression::CompressionLayer;
use axum::{routing::get, Router};
.route("/", get(hello_world))
.layer(CompressionLayer::new)
有许多由
Tower
中间件组成的
crate
可供使用,而无需我们自己编写任何中间件!如果我们已经在任何应用程序中使用
中间件,这是一种很好的方式来重用我们的中间件,而无需编写更多代码,因为兼容性可确保没有问题。
我们也可以通过编写函数来创建自己的中间件。该函数需要对
Next
类型进行
<B>
泛型绑定,因为
body
类型在 0.6 中是泛型的。下面是一个例子:
use axum::{http::Request, middleware::Next};
async fn check_hello_world<B>(
req: Request<B>,
next: Next<B>
) -> Result<Response, StatusCode> {
// 需要http crate来获取header名称
if req.headers().get(CONTENT_TYPE).unwrap() != "application/json" {
return Err(StatusCode::BAD_REQUEST);
Ok(next.run(req).await)
在 Axum 0.7 中,我们会删除
约束,因为
类型不再是泛型的:
async fn check_hello_world(
req: Request,
next: Next
要在我们的应用程序中实现新的中间件,我们要使用
axum::middleware::from_fn
函数,它允许我们将函数用作处理程序。在实践中,它看起来像这样:
use axum::middleware::self;
.layer(middleware::from_fn(check_hello_world))
如果我们需要向中间件添加应用程序状态,可以将其添加到处理程序函数中,然后使用
middleware::from_fn_with_state
let state = setup_state(); // 初始化应用状态
.layer(middleware::from_fn_with_state(state.clone(), check_hello_world))
总而言之,
通过其与
的兼容性,为在 Rust API 中使用强大的中间件提供了极大的便利。
7. 在 Axum 中提供静态文件
假设我们想在
中提供一些静态文件 —— 或者我们使用了像
React
这样的前端
JavaScript
框架来构建应用程序,并且想将其与 Rust Axum 后端结合成一个大型应用程序,而不是分别托管前端和后端。
本身没有提供这方面的功能;然而,它具有与
tower-http
相同的功能,后者提供了为我们自己的静态文件提供服务的方式,无论我们是运行
SPA
,还是使用
Next.js
等框架生成的静态文件,又或者是简单的
CSS
都可以与
进行融合。
如果我们使用静态生成的文件,我们可以轻松地将它插入路由器中(假设我们的静态文件在项目根目录的
dist
文件夹中):
use tower_http::services::ServeDir;
.nest_service("/", ServeDir::new("dist"))
如果我们使用
Vue
,可以将
bundle
构建到相关文件夹中,然后使用以下内容:
use tower_http::services::{ServeDir, ServeFile};
Router::new().nest_service(
"/", ServeDir::new("dist")
.not_found_service(ServeFile::new("dist/index.html")),
我们还可以使用 askama、tera 和 maud (在用 Rust 搭建 React Server Components 的 Web 服务器 之类的轻量级 JavaScript 库相结合,以加快投产速度。
8. 部署 Axum
由于需要使用
Dockerfile
,使用
后端程序进行部署总是有点麻烦。 但是,如果我们使用
Shuttle
,只需使用
cargo shuttle deploy
即可完成部署。无需设置。
后记
「分享是一种态度」
「全文完,既然看到这里了,如果觉得不错,随手点个赞和“在看”吧。」
Reference
[1]
axum:
https://
github.com/tokio-rs/axu
[2]
crate trend:
crate-trends.vercel.app
[3]
REST:
restfulapi.net/
[4]
Express:
expressjs.com/
[5]
Rust 学习之数据类型:
mp.weixin.qq.com/s/-opB
DDALNANR2-fCpwzOVQ
[6]
Result:
docs.rs/axum/0.7.4/axum
/response/type.Result.html
[7]
sqlx:
crates.io/crates/sqlx
[8]
Postgres:
https://www.
postgresqltutorial.com/
postgresql-getting-started/what-is-postgresql/
[9]
Shuttle:
shuttle.rs/
[10]
axum::Extension:
/struct.Extension.html
[11]
TypedHeader:
docs.rs/axum-extra/late
st/axum_extra/struct.TypedHeader.html
[12]
axum-extra:
crates.io/crates/axum-e
xtra
[13]
askama:
github.com/djc/askama
[14]
tera:
github.com/Keats/tera
[15]
maud:
maud.lambda.xyz/
[16]
用 Rust 搭建 React Server Components 的 Web 服务器:
mp.weixin.qq.com/s/zEx-
bnnT667wYocWs5CAGg
发布于 2024-01-19 08:57
・IP 属地北京
Rust(编程语言)
前端框架
赞同 6
1 条评论
分享
喜欢
收藏
申请转载