Request

Volo-HTTP Request parameters extraction

Extraction of Routing Parameters

Volo-HTTP’s handler can take multiple extractors as arguments, e.g.,

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}`")
}

In addition to this, handlers can take deserializable objects such as json, form, query, etc. as arguments.

The Rust pattern matching feature is used here to receive parameters: json, form, query, and so on.

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))
}

What is extractor?

The types that can be used as handler arguments implement FromContext or FromRequest, which are often referred to as extractor.

Where FromContext doesn’t consume the body of the request, i.e. the data passed in by methods such as POST,

Whereas FromRequest consumes the body of the request, so the handler can only have at most one parameter of a type that implements FromRequest.

Types that Implement extractor By Default

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>

Implementing Extractors for Your Own Types

We can receive our own types directly as arguments to a handler, which requires implementing FromContext or FromRequest for our own types.

For example, we might get the LogID from the header of a request, in which case we can define a type and implement FromContext for it:

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)
    }
}

Once you have implemented the LogID type, you can use it as an extractor and receive it directly using a 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))
}

One thing to note is that when implementing a handler, the types Uri, Method, Address, etc. are extracted via FromContext. Types such as Body are not consumed, and can be listed in any of the handler’s parameters. However, since Body can only be consumed once, types such as String, Bytes, From, Json, etc. extracted by FromRequest So it can only be placed in the last parameter of the handler.