meet Last Continue today to learn how to Extract the desired content from the Request, which is called Extract with the concept in axum.
Preliminary knowledge: json serialization / deserialization
Since json format is widely used in web development, first get familiar with how to serialize / deserialize json in t rust.
[dependencies] serde_json = "1"
Add serde first_ JSON dependency, and then it can be used. First define a struct:
#[derive(Debug, Serialize, Deserialize)] struct Order { //order number order_no: String, //Total amount amount: f32, //Receiving address address: String, }
Note: don't forget to add #[derive(Debug, Serialize, Deserialize)], which represents the modified struct, realizes serialization / deserialization, and "{:?}" The ability to debug output, of course, should be use d at the beginning:
use serde::{Deserialize, Serialize}; use serde_json as sj;
Next you can use:
//serialize 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); //Deserialization let order_json = r#" { "order_no": "1234567", "amount": 100.0, "address": "test" } "#; let order:Order = sj::from_str(order_json).unwrap(); println!("{:?}",order); //The following two fields are less assigned, and an error will be reported during deserialization let order_json = r#" { "order_no": "1234567" } "#; let order:Order = sj::from_str(order_json).unwrap(); println!("{:?}",order);
Output:
****************************
{"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
****************************
It can be seen that, compared with json class libraries such as Jackson and gson in java and other languages, serde in t rust is very strict, and an error will be reported when deserializing one less field. Therefore, it is recommended to add Option for fields that may be empty when defining struct
#[derive(Debug, Serialize, Deserialize)] struct Order { //order number order_no: String, //Total amount amount: Option<f32>, //Receiving address address: Option<String>, }
When deserializing this time, no error will be reported:
//The following two fields are less assigned, and an error will be reported during deserialization let order_json = r#" { "order_no": "1234567" } "#; let order: Order = sj::from_str(order_json).unwrap(); println!("{:?}", order);
Output:
Order { order_no: "1234567", amount: None, address: None }
1, Extract content from path
1.1 single parameter extraction
route:
.route("/user/:id", get(user_info))
Processing function:
// eg: /user/30, id=30 will be parsed async fn user_info(Path(id): Path<i32>) -> String { format!("user id:{}", id) }
You can also do this:
// eg: /user2/30, id=30 will be parsed async fn user_info_2(id: Path<i32>) -> String { format!("user id:{}", id.0) }
1.2 multi parameter extraction
route:
.route("/person/:id/:age", get(person))
Processing function:
// eg: /person/123/30, id=123, age=30 async fn person(Path((id, age)): Path<(i32, i32)>) -> String { format!("id:{},age:{}", id, age) }
Tuples such as (X,Y) are used to extract parameters, but if there are many parameters, the parameters are usually objectified and encapsulated into a struct
1.3 struct extraction
route:
.route("/path_req/:a/:b/:c/:d", get(path_req))
Processing function:
#[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) }
However, this method requires all parameters, such as: http://localhost:3000/path_req/abc/2/yjmyzz/4 , if 1 parameter is missing, for example: http://localhost:3000/path_req/abc/2/yjmyzz Route matching fails
2, Extract content from queryString
route:
.route("/query_req", get(query_req))
Processing function:
//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) }
Note: according to the above processing method, QueryString must have parameters a, B, C and D at the same time, otherwise an error will be reported. If you want some parameters to be empty, you need to change SomeRequest's corresponding field to Option as mentioned earlier
#[derive(Deserialize)] struct SomeRequest2 { a: Option<String>, b: Option<i32>, c: Option<String>, d: Option<u32>, } //eg: query_ req2? A = ABC & C = People's Republic of China & 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 the default value is specified as - 1 args.c.unwrap_or_default(), args.d.unwrap_or_default() ) }
Sometimes, if you want to obtain all QueryString parameters, you can use HashMap and refer to the following code:
route:
.route("/query", get(query))
Processing function:
//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) }
3, Submit extracted content from Form
route:
.route("/form", post(form_request))
Processing function:
// Form submission 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 the default value is specified as - 1 model.c.unwrap_or_default(), model.d.unwrap_or_default() ) }
4, Extract content from applicatiaion / JSON
route:
.route("/json", post(json_request))
Processing function:
// json submission async fn json_request(Json(model): Json<SomeRequest>) -> String { format!("a:{},b:{},c:{},d:{}", model.a, model.b, model.c, model.d) }
5, Extract HttpHeader
5.1 extract all header headers
route:
.route("/header", get(get_all_header))
Processing function:
/** * Get all request headers */ async fn get_all_header(headers: HeaderMap) -> String { for (key, value) in &headers { println!("key:{:?} , value:{:?}", key, value); } format!("{:?}", headers) }
5.2 extract the specified header, such as user agent
route:
.route("/user_agent", get(get_user_agent_header))
Processing function:
/** * Get user in http headers_ Agent header */ async fn get_user_agent_header(TypedHeader(user_agent): TypedHeader<headers::UserAgent>) -> String { user_agent.to_string() }
5, cookie read and write
route:
.route("/set_cookie", get(set_cookie_and_redirect)) .route("/get_cookie", get(get_cookie));
Processing function:
/** * Set the cookie and jump to a new page */ async fn set_cookie_and_redirect(mut headers: HeaderMap) -> (StatusCode, HeaderMap, ()) { //Set cookies, blog_ The URL is the key of the cookie headers.insert( axum::http::header::SET_COOKIE, HeaderValue::from_str("blog_url=http://yjmyzz.cnblogs.com/").unwrap(), ); //Reset the LOCATION and jump to a new page headers.insert( axum::http::header::LOCATION, HeaderValue::from_str("/get_cookie").unwrap(), ); //302 redirection (StatusCode::FOUND, headers, ()) } /** * Read cookie s */ async fn get_cookie(headers: HeaderMap) -> (StatusCode, String) { //Read the cookie and convert it into a string 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 null judgment if cookies.is_empty() { println!("cookie is empty!"); return (StatusCode::OK, "cookie is empty".to_string()); } //Split cookie s into lists let cookies: Vec<&str> = cookies.split(';').collect(); println!("{:?}", cookies); for cookie in &cookies { //Split content into k=v format 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); //Judge whether there is the blog just set_ 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()); }
Finally, the complete code of the above example is attached:
cargo.toml dependencies:
[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 will be parsed async fn user_info(Path(id): Path<i32>) -> String { format!("user id:{}", id) } // eg: /user2/30, id=30 will be parsed 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 = People's Republic of China & 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 the default value is specified as - 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) } // Form submission 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 the default value is specified as - 1 model.c.unwrap_or_default(), model.d.unwrap_or_default() ) } // json submission async fn json_request(Json(model): Json<SomeRequest>) -> String { format!("a:{},b:{},c:{},d:{}", model.a, model.b, model.c, model.d) } /** * Get all request headers */ async fn get_all_header(headers: HeaderMap) -> String { for (key, value) in &headers { println!("key:{:?} , value:{:?}", key, value); } format!("{:?}", headers) } /** * Get user in http headers_ Agent header */ async fn get_user_agent_header(TypedHeader(user_agent): TypedHeader<headers::UserAgent>) -> String { user_agent.to_string() } /** * Set the cookie and jump to a new page */ async fn set_cookie_and_redirect(mut headers: HeaderMap) -> (StatusCode, HeaderMap, ()) { //Set cookies, blog_ The URL is the key of the cookie headers.insert( axum::http::header::SET_COOKIE, HeaderValue::from_str("blog_url=http://yjmyzz.cnblogs.com/").unwrap(), ); //Reset the LOCATION and jump to a new page headers.insert( axum::http::header::LOCATION, HeaderValue::from_str("/get_cookie").unwrap(), ); //302 redirection (StatusCode::FOUND, headers, ()) } /** * Read cookie s */ async fn get_cookie(headers: HeaderMap) -> (StatusCode, String) { //Read the cookie and convert it into a string 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 null judgment if cookies.is_empty() { println!("cookie is empty!"); return (StatusCode::OK, "cookie is empty".to_string()); } //Split cookie s into lists let cookies: Vec<&str> = cookies.split(';').collect(); println!("{:?}", cookies); for cookie in &cookies { //Split content into k=v format 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); //Judge whether there is the blog just set_ 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(); }
Reference documents: