• 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

  • 相关阅读:
    RESTful API 介绍,设计
    golang web框架设计7:整合框架
    golang web框架设计6:上下文设计
    golang web框架设计5:配置设计
    golang web框架设计4:日志设计
    golang web框架设计3:controller设计
    golang web框架设计2:自定义路由
    golang web框架设计1:框架规划
    深入理解golang: channels
    服务端高并发分布式十四次架构演进之路
  • 原文地址:https://www.cnblogs.com/yjmyzz/p/axum_tutorial_3_extractor.html
Copyright © 2020-2023  润新知