axum中的各种响应
本章主要讨论 axum 的响应。axum 已经实现了多种响应,比如纯文本、HTML、JSON 及 自定义响应头(response header)。除了这些 axum 内置的响应之外,我们还将讨论如何将自己定义的结构体,作为响应返回给客户端。在axum中获取请求数据
在日常开发中,我们需要与用户进行交互,从各种渠道获取用户输入,包括但不限于:表单、URL 参数、URL Path 以及 JSON 等。axum 为我们提供了这些获取用户输入的支持。axum的状态共享
**状态共享**是指,在整个应用或不同路由之间,共享一份数据。axum 提供了方便的状态共享机制,但可能也会踩坑。本章将带你学习如何在 axum web 应用中共享状态。路由
axum 提供了常用的 HTTP 请求方式对应的路由,比如 `get`, `post`, `put`, `delete` 等。除此之外,axum 还提供了“嵌套路由”。路由,通常和 `handler(处理函数)` 结合在一起。中间件
中间件是一类提供系统软件和应用软件之间连接、便于软件各部件之间的沟通的软件,应用软件可以借助中间件在不同的技术架构之间共享信息与资源。本章将讨论如何在axum中使用中间件,以及如何自定义中间件。axum处理静态文件
和其它 Web 框架一样,axum 也会对所有请求进行处理。对于 CSS、JS 及图片等静态文件,并不需要 axum 的 handler 进行处理,而是只需要简单的把它们的内容进行返回即可。axum 提供了处理静态文件的中间件。axum处理cookie
Cookie 是通过 HTTP Header 进行传递的。由某个响应头进行设置,然后其它请求头就可以获取到了。本章将通过模拟用户中心来用 axum 操作 HTTP Header 演示 Cookie 的读写操作。axum 操作 redis
通过 redis-rs 这个 crate,可以很方便的操作 redis。它提供了同步和异步两种连接,由于我们要集成到 axum 中,所以这里使用异步连接。本章将展示如何获取 redis 异步连接、如何将字符串保存到 redis、如何获取到保存在 redis 里的字符串以及如何通过 redis 保存和读取自定义结构体。axum 操作 Postgres 数据库
PostgreSQL 是一款天然支持异步操作的高性能开源关系型数据库。本章将讨论如何在 axum 中使用 PostgreSQL。包括:数据的增加、修改、删除、查找以及开始事务保证业务的原子性。axum 实现 Session
由于 HTTP 是无状态的,所以我们可以通过Cookie来维护状态。但 cookie 是直接保存到客户端,所以对于敏感数据,不能直接保存到 cookie。我们可以把敏感数据保存到服务端,然后把对应的 ID 保存到 cookie,这就是 Session。本章我们将使用 Cookie 和 Redis 实现一个简单的 Session。axum 集成 JWT
Json web token(JWT)是为了网络应用环境间传递声明而执行的一种基于 JSON 的开发标准(RFC 7519),该 token 被设计为紧凑且安全的,特别适用于分布式站点的单点登陆(SSO)场景。JWT 的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该 token 也可直接被用于认证,也可被加密。axum 中使用模板引擎
利用模板引擎,我们不需要再把 HTML 代码写在 Rust 代码中了,而是将其独立保存为`*.html`文件。既方便维护,也有利用开发。axum 上传文件
文件上传是 Web 开发中常见的功能,本章将演示如何在 axum 实现文件上传。axum 集成hcaptcha验证码进行人机验证
在机器人采集、恶意攻击的今天,人机验证筑起了一道保护网。从之前的图片验证码,到 Google 提供的 reCaptcha,人机验证经历了一段漫长的演进过程。配置文件:让axum app可配置
将数据库连接信息、redis 连接信息以及 Web 应用监听地址等信息通过配置文件进行单独管理是一个比较好的开发实践。这样就无须在更改配置的时候重新编译整个项目,同时也可以针对不同环境使用不同的配置文件。axum错误处理
本章主要讨论 axum 的错误处理
配置文件:让axum app可配置
将数据库连接信息、redis 连接信息以及 Web 应用监听地址等信息通过配置文件进行单独管理是一个比较好的开发实践。这样就无须在更改配置的时候重新编译整个项目,同时也可以针对不同环境使用不同的配置文件。
本章以 PostgreSQL 和 Redis 进行演示如何使用配置文件。首先看一下新接触的两个 crate:
dotenv
:解析.env
,并将里面的键值对映射为环境变量
.env
文件
首先,在项目根目录(和Cargo.toml
同级)创建一个.env
文件,并输入以下内容:
WEB.ADDR=127.0.0.1:9527
REDIS.DSN=redis://127.0.0.1:6379/
PG.USER=axum_rs
PG.PASSWORD=axum.rs
PG.DBNAME=axum_rs
PG.PORT=5432
PG.HOST=pg.axum.rs
PG.POOL.MAX_SIZE=30
其中:
-
WEB.*
:对应 Web 的配置,比如监听地址 -
REDIS.*
:对应 Redis 的配置,比如连接字符串 -
PG.*
:对应 PostgreSQL 的配置
WEB.*
:对应 Web 的配置,比如监听地址
REDIS.*
:对应 Redis 的配置,比如连接字符串
PG.*
:对应 PostgreSQL 的配置
实现自定义配置
项目配置信息的结构体
通过分析,我们项目配置的信息应该是:
/// Web配置
#[derive(Deserialize)]
pub struct WebConfig {
/// Web服务监听地址
pub addr: String,
}
/// Redis 配置
#[derive(Deserialize)]
pub struct RedisConfig {
/// 连接字符串
pub dsn: String,
}
/// 项目配置
#[derive(Deserialize)]
pub struct Config {
pub web: WebConfig,
pub redis: RedisConfig,
}
根本上说,这是一个反序列化的过程,所以需要 Deserialize
。另外每一个子配置都抽取为一个单独的结构体。
/// 项目配置
#[derive(Deserialize)]
pub struct Config {
pub web: WebConfig,
pub redis: RedisConfig,
pub pg: deadpool_postgres::Config,
}
我们的配置信息都是保存在 .env
里,它最终会变成环境变量(通过dotenv
),所以我们的项目配置结构体需要实现一个从环境变量中加载信息的方法。
impl Config {
/// 从环境变量中初始化配置
pub fn from_env() -> Result<Self, config::ConfigError> {
let mut cfg = config::Config::new();
// 尝试合并环境变量设置
cfg.merge(config::Environment::new())?;
// 转换成我们自己的Config对象
cfg.try_into()
}
}
状态共享结构体
我们的目的是要在多个 handler 之间共享数据库和 redis 连接,回忆一下《axum 的状态共享》提到的方法。
由于我们要共享多个数据,所以需要把它们“打包”成一个单独的结构体。
#[derive(Clone)]
pub struct AppState {
pub pool: deadpool_postgres::Pool,
pub rdc: redis::Client,
}
-
pool
:数据库连接池 -
rdc
:redis 客户端
pool
:数据库连接池
rdc
:redis 客户端
初始化配置及共享对象
在main()
中,初始化配置及共享对象
初始化配置
dotenv().ok(); // 解析 .env 文件
let cfg = Config::from_env().expect("初始化项目配置失败");
初始化共享对象
let pool = cfg
.pg
.create_pool(tokio_postgres::NoTls)
.expect("创建Postgres连接池失败");
let rdc = redis::Client::open(cfg.redis.dsn).expect("创建redis连接失败");
现在可以在路由定义中添加状态共享了。
.layer(AddExtensionLayer::new(AppState { pool, rdc }));
在 handler 获取
/// 尝试获取 Postgres Client
async fn try_pg(Extension(state): Extension<AppState>) -> Result<&'static str, String> {
let _client: deadpool_postgres::Client =
state.pool.get().await.map_err(|err| err.to_string())?;
Ok("Successfully got database client from postgresql pool in AppState")
}
/// 尝试获取 Redis 异步连接
async fn try_redis(Extension(state): Extension<AppState>) -> Result<&'static str, String> {
let _conn = state
.rdc
.get_async_connection()
.await
.map_err(|err| err.to_string())?;
Ok("Successfully got async connection via redis client in AppState")
}
监听地址也是配置文件里的
axum::Server::bind(&cfg.web.addr.parse().unwrap())
本章我们讨论了如何使用配置文件让 axum 项目变的可配置,完整代码可以在代码库中找到。