路由请求

Volo-HTTP 路由请求参数提取

路由参数的提取

Volo-HTTP 的 handler 可以接受多个 extractor 作为参数, 例如:

use volo_http::Address;

async fn who_am_i(peer: Address) -> String {
    format!("You are `{peer}`")
}

async fn post_something(data: String) -> string {
    format!("data: `{data}`")
}

除此之外,handler 可以使用 json, form, query 等可以被反序列化的对象作为参数

这里使用了 Rust 模式匹配的特性来接收参数:

use volo_http::{
    http::StatusCode,
    server::{
        extract::{Form, Query},
        route::{get, Router},
    },
};
use serde::Deserialize;

#[derive(Deserialize, Debug)]
struct Login {
    username: String,
    password: String,
}

fn process_login(info: Login) -> Result<String, StatusCode> {
    if info.username == "admin" && info.password == "password" {
        Ok("Login Success!".to_string())
    } else {
        Err(StatusCode::IM_A_TEAPOT)
    }
}

// test with:
//     curl "http://localhost:8080/user/login?username=admin&password=admin"
//     curl "http://localhost:8080/user/login?username=admin&password=password"
async fn get_with_query(Query(info): Query<Login>) -> Result<String, StatusCode> {
    process_login(info)
}

// test with:
//     curl http://localhost:8080/user/login -X POST -d 'username=admin&password=admin'
//     curl http://localhost:8080/user/login -X POST -d 'username=admin&password=password'
async fn post_with_form(Form(info): Form<Login>) -> Result<String, StatusCode> {
    process_login(info)
}

pub fn user_login_router() -> Router {
    Router::new().route("/user/login", get(get_with_query).post(post_with_form))
}

什么是 extractor?

可以作为 handler 参数的类型都实现了 FromContextFromRequest, 这种类型我们通常称为 extractor

其中,FromContext 不会消费请求的 body,即 POST 等方法传入的数据,

FromRequest 会消费请求的 body,所以 handler 的参数中最多只能有一个实现了 FromRequest 的类型。

默认实现了 extractor 的类型

FromContext

  • Address
  • Uri
  • Method
  • Option<T>
  • Result<T, T::Rejection>
  • Query<T>
  • WebSocketUpgrade

FromRequest

  • Option<T>
  • Result<T, T::Rejection>
  • ServerRequest<B>
  • Vec<u8>
  • Bytes
  • String
  • FastStr
  • MaybeInvalid<T>
  • Form<T>

为自己的类型实现 extractor

我们可以将自己的类型作为 handler 的参数直接接收,这需要为自己的类型实现 FromContextFromRequest

例如,我们可能会从请求的 header 中获取 LogID,这种情况下可以定义一个类型,然后为其实现 FromContext:

use std::convert::Infallible;

use volo_http::{
    context::ServerContext,
    http::request::Parts,
    server::extract::FromContext,
};

const LOGID_KEY: &str = "x-logid";

pub enum LogID {
    ID(String),
    None,
}

impl FromContext for LogID {
    type Rejection = Infallible;

    async fn from_context(
        _: &mut ServerContext,
        parts: &mut Parts,
    ) -> Result<Self, Self::Rejection> {
        let id = match parts.headers.get(LOGID_KEY) {
            Some(log_id) => LogID::ID(log_id.to_str().unwrap().to_owned()),
            None => LogID::None,
        };

        Ok(id)
    }
}

实现了 LogID 这个类型后,就可以将其作为 extractor,直接使用 handler 接收了:

async fn show_logid(id: LogID) -> String {
    match id {
        LogID::ID(s) => format!("{s}"),
        LogID::None => "LogID not found".to_owned(),
    }
}

pub fn logid_router() -> Router {
    Router::new().route("/extract/logid", get(print_logid))
}

需要注意的一点是,在实现一个 handler 时,对于 Uri, Method, Address 等这一类通过 FromContext 提取, 不会消费 Body 等类型可以在 handler 的参数中任意排列, 但由于 Body 只能被消费一次,所以通过 FromRequest 提取的如 String, Bytes, From, Json 等类型, 只能放在 handler 最后一个参数的位置


最后修改 September 18, 2024 : Update typo in hertz docs (#1138) (12492e4)