Rust:axum學習筆記(3) extract 


上一篇繼續,今天學習如何從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 &params {
        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 &params {
        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();
}

  

參考文檔:

https://docs.rs/axum/latest/axum/#extractors

https://github.com/tokio-rs/axum/tree/main/examples


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM