携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第1天,点击查看活动详情
最近工作颇为清(摸)闲(鱼),既然如此,那就开个新坑。学习一下如何在rust进行简单的web后端crud。
首先上主角,当然是看我们需要用到哪些crate库:
tokio = {version = "1.20.0", features = ["full"]}
axum = {version = "0.5.13", features = ["headers"]}
sqlx = { version = "0.6.0", features = [ "runtime-tokio-rustls" ,"mysql","chrono"] }
once_cell = "1.13.0"
serde = {version = "1.0.140", features = ['derive']}
serde_json = "1.0.82"
tracing = "0.1.36"
tracing-subscriber = {version = "0.3.15", features = ["env-filter"]}
dotenv = "0.15.0"
jsonwebtoken = "8.1.1"
chrono = { version = "0.4.19",features = ["serde"] }
- Tokio异步运行时,rust目前最流行,最有名气的异步库。
- axun,Tokio系旗下的web框架,这次就是要体验一下Tokio全家桶
- sqlx 异步数据库框架,并不是orm框架那种,没有DSL,用户自己编写sql语句,将查询结果按列取出或映射到struct上
- once_cell初始化全局变量库
- serde、serde_json序列化专用库
- tracing、tracing-subscriber,日志库
- dotenv环境变量库
- jsonwebtoken jwt认证库
- chrono时间库
在正式开始之前,先看一下axum官方的例子:
use axum::{
routing::{get, post},
http::StatusCode,
response::IntoResponse,
Json, Router,
};
use serde::{Deserialize, Serialize};
use std::net::SocketAddr;
#[tokio::main]
async fn main() {
// initialize tracing
//启动日志记录
tracing_subscriber::fmt::init();
// build our application with a route
//构建路由
let app = Router::new()
// `GET /` goes to `root`
.route("/", get(root))
// `POST /users` goes to `create_user`
.route("/users", post(create_user));
// run our app with hyper
// `axum::Server` is a re-export of `hyper::Server`
//服务端口
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
tracing::debug!("listening on {}", addr);
//启动服务
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await
.unwrap();
}
// basic handler that responds with a static string
//handler函数
async fn root() -> &'static str {
"Hello, World!"
}
//handler函数
async fn create_user(
// this argument tells axum to parse the request body
// as JSON into a `CreateUser` type
Json(payload): Json<CreateUser>,
) -> impl IntoResponse {
// insert your application logic here
let user = User {
id: 1337,
username: payload.username,
};
// this will be converted into a JSON response
// with a status code of `201 Created`
(StatusCode::CREATED, Json(user))
}
// the input to our `create_user` handler
#[derive(Deserialize)]
struct CreateUser {
username: String,
}
// the output to our `create_user` handler
#[derive(Serialize)]
struct User {
id: u64,
username: String,
}
通过官方例子,我们对axum的基本结构有了一个大概的了解。用route函数设置路由,参数为路由路径和handler函数。至于什么是handler函数? 再来看axum文档中对handler的定义:
全是英文看不懂?其实我也不懂,靠着翻译和自己的理解。用人话来说,handler是一个异步函数用来处理程序逻辑并且返回响应内容。也就是说,我们在handler中处理路由的逻辑并返回内容或者是报错信息。
接下来我们的crud基本就是围绕handler函数的处理了。从create_user函数中,能看到一个完整的handler处理函数。
先看入参,也就是axum从Request请求中获取参数的办法,用axum里的概念叫Extract(提取解析)。
一、从path中提取参数
.route("/user/:id", get(user_info))
// eg: /user/1,将解析出id=1
async fn user_info(Path(id): Path<i32>) -> String { format!("user id:{}", id) }
通过正则匹配获取路由路径中的值,不仅可以获取单个值,也可以匹配多个值
.route("/person/:id/:age", get(person))
// eg: /person/12/3,将解析出id=12, age=30
async fn person(Path((id, age)): Path<(i32, i32)>) -> String { format!("id:{},age:{}", id, age) }
也可以封装成一个struct进行匹配
.route("/path_req/:a/:b/:c/:d", get(path_req))
#[derive(Deserialize)]
struct SomeRequest{
a: String,
b: i32,
c: String,
d: u32,
}
// eg: path_req/a1/b1/c1/d1
async fn path_req(Path(req): Path<SomeRequest>) -> String {
format!("a:{},b:{},c:{},d:{}", req.a, req.b, req.c, req.d)
}
二、从Url获取参数
.route("/query_req", get(query_req))
#[derive(Deserialize)]
struct SomeRequest{
a: String,
b: i32,
c: String,
d: Option<u32>,
}
//eg: query_req/?a=test&b=2&c=abc&d=80
async fn query_req(Query(args): Query<SomeRequest>) -> String {
format!("a:{},b:{},c:{},d:{}", args.a, args.b, args.c, args.d)
}
聪明的小伙伴肯定发现了,怎么这个struct好像不对劲?里面有值是Option<T>?因为如果希望有些参数可为空,可以把相应的字段改成Option。
如果想获取所有的QueryString参数,可以用HashMap进行映射:
.route("/query", get(query))
//eg: query?a=1&b=1.0&c=xxx
async fn query(Query(params): Query<HashMap<String, String>>) -> String {
for (key, value) in ¶ms {
println!("key:{},value:{}", key, value);
}
format!("{:?}", params)
}
三、从Form表单获取提取参数
.route("/form", post(form_request))
#[derive(Deserialize)] struct SomeRequest2 {
a: Option<String>,
b: Option<i32>,
c: Option<String>,
d: Option<u32>,
}
// 表单提交
async fn form_request(Form(model): Form<SomeRequest2>) -> String {
format!( "a:{},b:{},c:{},d:{}",
model.a.unwrap_or_default(),
model.b.unwrap_or(-1),
//b缺省值指定为-1 model.c.unwrap_or_default(),
model.d.unwrap_or_default())
}
四、从applicataion/json获取参数
.route("/json", post(json_request))
// json提交
async fn json_request(Json(model): Json<SomeRequest>) -> String {
format!("a:{},b:{},c:{},d:{}", model.a, model.b, model.c, model.d)
}
五、获取所有请求头
.route("/header", get(get_all_header))
/** * 获取所有请求头 */
async fn get_all_header(headers: HeaderMap) -> String {
for (key, value) in &headers {
println!("key:{:?} , value:{:?}", key, value);
}
format!("{:?}", headers) }
如果像获取指定请求头参数,可以这样。提取指定header头,比如user-agent
.route("/user_agent", get(get_user_agent_header))
/** * 获取http headers中的user_agent头 */
async fn get_user_agent_header(TypedHeader(user_agent): TypedHeader<headers::UserAgent>) -> String {
user_agent.to_string()
}
如何想要获取cookie,可以这样写:
async fn user_center(headers: HeaderMap) -> Result<Html<String>, &'static str> {
let cookies = headers .get(axum::http::header::COOKIE)
.and_then(|v| v.to_str().ok())
.map(|v| v.to_string())
.unwrap_or("".to_string());
// 从请求头获取所有COOKIE
if cookies.is_empty() {
return Err("NO COOKIE SETTED");
// 没有 Cookie }
let mut logined_username: Option<String> = None; let cookies: Vec<&str> = cookies.split(';').collect();
// 多个cookie用;分割
for cookie in cookies {
let cookie_pair: Vec<&str> = cookie.split('=').collect();
// 每个cookie都是用=分割的键值对
let cookie_name = cookie_pair[0].trim(); let cookie_value = cookie_pair[1].trim();
// 如果 cookie 的名称是我们希望的,并且值不为空
if cookie_name == COOKIE_NAME && !cookie_value.is_empty() {
logined_username = Some(String::from(cookie_value));
// 设置已登录用户的用户名 break; }
} if logined_username.is_none() {
return Err("COOKIE IS EMPTY");
// 没有我们需要的cookie
}
let html = format!( r#"
<!DOCTYPE html> <html lang="zh-Hans">
<head> <meta charset="utf-8" />
<meta name="author" content="axum.rs (team@axum.rs)" />
<title> 用户中心-AXUM中文网 </title>
</head>
<body> <p>你好,<strong>{}</strong>!你已成功登录。[<a href="/logout">退出登录</a>] </body> </html>
"#,
logined_username.unwrap() );
Ok(Html(html)) }
总结:我们对axum的使用方法有了一个基本的了解,主要是通过路由上的handler函数对逻辑进行处理,而handler函数获取路由参数的方法共有path、query、form、json、header请求头等5种。
下一章,我们将对handler函数的返回内容即请求响应内容或者报错信息进行学习。
最后特别鸣谢大佬: 菩提树下的杨过
因为作者太懒,本文中大部分的代码例子都是从大佬的文章中复制粘贴的。