Rust:axum学习笔记(3) extract
接上一篇继续,今天学习如何从Request请求中提取想要的内容,用axum里的概念叫Extract。
预备知识:json序列化/反序列化
鉴于现在web开发中,json格式被广泛使用,先熟悉下rust中如何进行json序列化/反序列化。
1 2 | [dependencies] serde_json = "1" |
先加入serde_json依赖项,然后就可以使用了,先定义1个struct:
1 2 3 4 5 6 7 8 9 | #[derive(Debug, Serialize, Deserialize)] struct Order { //订单号 order_no: String, //总金额 amount: f32, //收货地址 address: String, } |
注意:别忘了加#[derive(Debug, Serialize, Deserialize)],这个表示被修饰的struct,实现了序列化/反序列化,以及"{:?}"调试输出的能力,当然最开头要use一下:
1 2 | use serde::{Deserialize, Serialize}; use serde_json as sj; |
接下来就可以使用了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | //序列化 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
1 2 3 4 5 6 7 8 9 | #[derive(Debug, Serialize, Deserialize)] struct Order { //订单号 order_no: String, //总金额 amount: Option<f32>, //收货地址 address: Option<String>, } |
这回再反序列化时,就不会报错了:
1 2 3 4 5 6 7 8 | //下面少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 单一参数提取
路由:
1 | .route( "/user/:id" , get(user_info)) |
处理函数:
1 2 3 4 | // eg: /user/30,将解析出id=30 async fn user_info(Path(id): Path<i32>) -> String { format!( "user id:{}" , id) } |
也可以这样:
1 2 3 4 | // eg: /user2/30,将解析出id=30 async fn user_info_2(id: Path<i32>) -> String { format!( "user id:{}" , id.0) } |
1.2 多参数提取
路由:
1 | .route( "/person/:id/:age" , get(person)) |
处理函数:
1 2 3 4 | // 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提取
路由:
1 | .route( "/path_req/:a/:b/:c/:d" , get(path_req)) |
处理函数:
1 2 3 4 5 6 7 8 9 10 11 12 | #[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里提取内容
路由:
1 | .route( "/query_req" , get(query_req)) |
处理函数:
1 2 3 4 | //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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #[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,参考下面的代码:
路由:
1 | .route( "/query" , get(query)) |
处理函数:
1 2 3 4 5 6 7 | //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表单提交提取内容
路由:
1 | .route( "/form" , post(form_request)) |
处理函数:
1 2 3 4 5 6 7 8 9 10 | // 表单提交 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提取内容
路由:
1 | .route( "/json" , post(json_request)) |
处理函数:
1 2 3 4 | // 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头
路由:
1 | .route( "/header" , get(get_all_header)) |
处理函数:
1 2 3 4 5 6 7 8 9 | /** * 获取所有请求头 */ 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
路由:
1 | .route( "/user_agent" , get(get_user_agent_header)) |
处理函数 :
1 2 3 4 5 6 | /** * 获取http headers中的user_agent头 */ async fn get_user_agent_header(TypedHeader(user_agent): TypedHeader<headers::UserAgent>) -> String { user_agent.to_string() } |
五、cookie读写
路由:
1 2 | .route( "/set_cookie" , get(set_cookie_and_redirect)) .route( "/get_cookie" , get(get_cookie)); |
处理函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | /** * 设置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依赖项:
1 2 3 4 5 6 7 | [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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 | 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(); } |
参考文档:
出处:http://yjmyzz.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· 字符编码:从基础到乱码解决
· Open-Sora 2.0 重磅开源!
2014-01-01 maven学习(上)- 基本入门用法
2008-01-01 [转贴]C#中实现窗体间传值