接上一篇繼續,今天學習如何從Request請求中提取想要的內容,用axum里的概念叫Extract。
預備知識:json序列化/反序列化
鑒於現在web開發中,json格式被廣泛使用,先熟悉下rust中如何進行json序列化/反序列化。
[dependencies] serde_json = "1"
先加入serde_json依賴項,然后就可以使用了,先定義1個struct:
#[derive(Debug, Serialize, Deserialize)] struct Order { //訂單號 order_no: String, //總金額 amount: f32, //收貨地址 address: String, }
注意:別忘了加#[derive(Debug, Serialize, Deserialize)],這個表示被修飾的struct,實現了序列化/反序列化,以及"{:?}"調試輸出的能力,當然最開頭要use一下:
use serde::{Deserialize, Serialize}; use serde_json as sj;
接下來就可以使用了:
//序列化 let order = Order{ order_no:"1234567".to_string(), amount:100.0, address:"test".to_string() }; let order_json =sj::to_string(&order).unwrap(); println!("{}",order_json); //反序列化 let order_json = r#" { "order_no": "1234567", "amount": 100.0, "address": "test" } "#; let order:Order = sj::from_str(order_json).unwrap(); println!("{:?}",order); //下面少2個字段賦值,反序列化時,會報錯 let order_json = r#" { "order_no": "1234567" } "#; let order:Order = sj::from_str(order_json).unwrap(); println!("{:?}",order);
輸出:
****************************
{"order_no":"1234567","amount":100.0,"address":"test"}
Order { order_no: "1234567", amount: 100.0, address: "test" }
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Error("missing field `amount`", line: 4, column: 9)', request/src/main.rs:198:48
****************************
可以看到,相比於java等其它語言的jackson, gson之類的json類庫,rust中的serde非常嚴格,少1個字段反序列化時都會報錯,因此建議定義struct時,對於可能為空的字段,最好加Option
#[derive(Debug, Serialize, Deserialize)] struct Order { //訂單號 order_no: String, //總金額 amount: Option<f32>, //收貨地址 address: Option<String>, }
這回再反序列化時,就不會報錯了:
//下面少2個字段賦值,反序列化時,會報錯 let order_json = r#" { "order_no": "1234567" } "#; let order: Order = sj::from_str(order_json).unwrap(); println!("{:?}", order);
輸出:
Order { order_no: "1234567", amount: None, address: None }
一、從path中提取內容
1.1 單一參數提取
路由:
.route("/user/:id", get(user_info))
處理函數:
// eg: /user/30,將解析出id=30 async fn user_info(Path(id): Path<i32>) -> String { format!("user id:{}", id) }
也可以這樣:
// eg: /user2/30,將解析出id=30 async fn user_info_2(id: Path<i32>) -> String { format!("user id:{}", id.0) }
1.2 多參數提取
路由:
.route("/person/:id/:age", get(person))
處理函數:
// eg: /person/123/30,將解析出id=123, age=30 async fn person(Path((id, age)): Path<(i32, i32)>) -> String { format!("id:{},age:{}", id, age) }
用(X,Y)之類的tuple來提取參數,但是如果參數很多,通常會將參數對象化,封裝成一個struct
1.3 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) }
不過這種方法,必須要求所有參數都有,比如:http://localhost:3000/path_req/abc/2/yjmyzz/4,如果少1個參數,比如:http://localhost:3000/path_req/abc/2/yjmyzz 則會路由匹配失敗
二、從queryString里提取內容
路由:
.route("/query_req", get(query_req))
處理函數:
//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) }
注意:按上面的處理方式,QueryString里必須同時有a, b, c, d這幾個參數,否則會報錯。如果希望有些參數可為空,則需要把SomeRequest按前面提到的,相應的字段改成Option
#[derive(Deserialize)] struct SomeRequest2 { a: Option<String>, b: Option<i32>, c: Option<String>, d: Option<u32>, } //eg: query_req2?a=abc&c=中華人民共和國&d=123 async fn query_req2(Query(args): Query<SomeRequest2>) -> String { format!( "a:{},b:{},c:{},d:{}", args.a.unwrap_or_default(), args.b.unwrap_or(-1), //b缺省值指定為-1 args.c.unwrap_or_default(), args.d.unwrap_or_default() ) }
有時候,可能想獲取所有的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))
處理函數:
// 表單提交 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) }
五、提取HttpHeader
5.1 提取所有header頭
路由:
.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) }
5.2 提取指定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讀寫
路由:
.route("/set_cookie", get(set_cookie_and_redirect)) .route("/get_cookie", get(get_cookie));
處理函數:
/** * 設置cookie並跳轉到新頁面 */ async fn set_cookie_and_redirect(mut headers: HeaderMap) -> (StatusCode, HeaderMap, ()) { //設置cookie,blog_url為cookie的key headers.insert( axum::http::header::SET_COOKIE, HeaderValue::from_str("blog_url=http://yjmyzz.cnblogs.com/").unwrap(), ); //重設LOCATION,跳到新頁面 headers.insert( axum::http::header::LOCATION, HeaderValue::from_str("/get_cookie").unwrap(), ); //302重定向 (StatusCode::FOUND, headers, ()) } /** * 讀取cookie */ async fn get_cookie(headers: HeaderMap) -> (StatusCode, String) { //讀取cookie,並轉成字符串 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() { println!("cookie is empty!"); return (StatusCode::OK, "cookie is empty".to_string()); } //將cookie拆成列表 let cookies: Vec<&str> = cookies.split(';').collect(); println!("{:?}", cookies); for cookie in &cookies { //將內容拆分成k=v的格式 let cookie_pair: Vec<&str> = cookie.split('=').collect(); if cookie_pair.len() == 2 { let cookie_name = cookie_pair[0].trim(); let cookie_value = cookie_pair[1].trim(); println!("{:?}", cookie_pair); //判斷其中是否有剛才設置的blog_url if cookie_name == "blog_url" && !cookie_value.is_empty() { println!("found:{}", cookie_value); return (StatusCode::OK, cookie_value.to_string()); } } } return (StatusCode::OK, "empty".to_string()); }
最后,附上述示例完整代碼:
cargo.toml依賴項:
[dependencies] axum = { version="0.4.3", features = ["headers"] } tokio = { version="1", features = ["full"] } serde = { version="1", features = ["derive"] } serde_json = "1" http = "0.2.1" headers = "0.3"
main.rs
use std::collections::HashMap; use axum::{ extract::{Form, Path, Query, TypedHeader}, http::header::{HeaderMap, HeaderValue}, response::Json, routing::{get, post}, Router, }; use http::StatusCode; use serde::Deserialize; // eg: /user/30,將解析出id=30 async fn user_info(Path(id): Path<i32>) -> String { format!("user id:{}", id) } // eg: /user2/30,將解析出id=30 async fn user_info_2(id: Path<i32>) -> String { format!("user id:{}", id.0) } // eg: /person/123/30,將解析出id=123, age=30 async fn person(Path((id, age)): Path<(i32, i32)>) -> String { format!("id:{},age:{}", id, age) } #[derive(Deserialize)] struct SomeRequest2 { a: Option<String>, b: Option<i32>, c: Option<String>, d: Option<u32>, } #[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) } //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) } //eg: query_req2?a=abc&c=中華人民共和國&d=123 async fn query_req2(Query(args): Query<SomeRequest2>) -> String { format!( "a:{},b:{},c:{},d:{}", args.a.unwrap_or_default(), args.b.unwrap_or(-1), //b缺省值指定為-1 args.c.unwrap_or_default(), args.d.unwrap_or_default() ) } //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) } // 表單提交 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() ) } // json提交 async fn json_request(Json(model): Json<SomeRequest>) -> String { format!("a:{},b:{},c:{},d:{}", model.a, model.b, model.c, model.d) } /** * 獲取所有請求頭 */ async fn get_all_header(headers: HeaderMap) -> String { for (key, value) in &headers { println!("key:{:?} , value:{:?}", key, value); } format!("{:?}", headers) } /** * 獲取http headers中的user_agent頭 */ async fn get_user_agent_header(TypedHeader(user_agent): TypedHeader<headers::UserAgent>) -> String { user_agent.to_string() } /** * 設置cookie並跳轉到新頁面 */ async fn set_cookie_and_redirect(mut headers: HeaderMap) -> (StatusCode, HeaderMap, ()) { //設置cookie,blog_url為cookie的key headers.insert( axum::http::header::SET_COOKIE, HeaderValue::from_str("blog_url=http://yjmyzz.cnblogs.com/").unwrap(), ); //重設LOCATION,跳到新頁面 headers.insert( axum::http::header::LOCATION, HeaderValue::from_str("/get_cookie").unwrap(), ); //302重定向 (StatusCode::FOUND, headers, ()) } /** * 讀取cookie */ async fn get_cookie(headers: HeaderMap) -> (StatusCode, String) { //讀取cookie,並轉成字符串 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() { println!("cookie is empty!"); return (StatusCode::OK, "cookie is empty".to_string()); } //將cookie拆成列表 let cookies: Vec<&str> = cookies.split(';').collect(); println!("{:?}", cookies); for cookie in &cookies { //將內容拆分成k=v的格式 let cookie_pair: Vec<&str> = cookie.split('=').collect(); if cookie_pair.len() == 2 { let cookie_name = cookie_pair[0].trim(); let cookie_value = cookie_pair[1].trim(); println!("{:?}", cookie_pair); //判斷其中是否有剛才設置的blog_url if cookie_name == "blog_url" && !cookie_value.is_empty() { println!("found:{}", cookie_value); return (StatusCode::OK, cookie_value.to_string()); } } } return (StatusCode::OK, "empty".to_string()); } #[tokio::main] async fn main() { // our router let app = Router::new() .route("/user/:id", get(user_info)) .route("/user2/:id", get(user_info_2)) .route("/person/:id/:age", get(person)) .route("/path_req/:a/:b/:c/:d", get(path_req)) .route("/query_req", get(query_req)) .route("/query_req2", get(query_req2)) .route("/query", get(query)) .route("/form", post(form_request)) .route("/json", post(json_request)) .route("/header", get(get_all_header)) .route("/user_agent", get(get_user_agent_header)) .route("/set_cookie", get(set_cookie_and_redirect)) .route("/get_cookie", get(get_cookie)); // run it with hyper on localhost:3000 axum::Server::bind(&"0.0.0.0:3000".parse().unwrap()) .serve(app.into_make_service()) .await .unwrap(); }
參考文檔: