Request
For web applications it’s crucial to react to the data a client sends to the server. In Salvo this information is provided by the request:
#[handler]
async fn hello(req: &mut Request) -> String {
req.params().get("id").cloned().unwrap_or_default()
}
About query string
We can get query string from request object:
req.query::<String>("id");
About form
req.form::<String>("id").await;
About json payload
req.parse_json::<User>().await;
Extract Data
Request can be parsed into strongly typed structures by providing several functions through Request
.
parse_params
: parse the requested router params into a specific data type;parse_queries
: parse the requested URL queries into a specific data type;parse_headers
: parse the requested HTTP haders into a specific data type;parse_json
: Parse the data in the HTTP body part of the request as JSON format to a specific type;parse_form
: Parse the data in the HTTP body part of the request as a Form form to a specific type;parse_body
: Parse the data in the HTTP body section to a specific type according to the type of the requestedcontent-type
.extract
: can combine different data sources to parse a specific type.
Parsing principle
The customized serde::Deserializer
will be extract data similar to HashMap<String, String>
and HashMap<String, Vec<String>>
into a specific data type.
For example: URL queries
is actually extracted as a MultiMap type, MultiMap
can think of it as a data structure like HashMap<String, Vec<String>>
. If the requested URL is http://localhost/users?id=123&id=234
, we provide The target type is:
#[derive(Deserialize)]
struct User {
id: i64
}
Then the first id=123
will be parsed, and id=234
will be discarded:
let user: User = req.parse_queries().unwrap();
assert_eq!(user.id, 123);
If the type we provide is:
#[derive(Deserialize)]
struct Users {
id: Vec<i64>
}
Then id=123&id=234
will be parsed:
let users: Users = req.parse_queries().unwrap();
assert_eq!(user.ids, vec![123, 234]);
Multiple data sources can be merged to parse out a specific type. You can define a custom type first, for example:
#[derive(Serialize, Deserialize, Extractible, Debug)]
/// Get the data field value from the body by default.
#[extract(default_source(from = "body"))]
struct GoodMan<'a> {
/// The id number is obtained from the request path parameter, and the data is automatically parsed as i64 type.
#[extract(source(from = "param"))]
id: i64,
/// Reference types can be used to avoid memory copying.
username: &'a str,
first_name: String,
last_name: String,
}
Then in Handler
you can get the data like this:
#[handler]
async fn edit(req: &mut Request) -> String {
let good_man: GoodMan<'_> = req.extract().await.unwrap();
}
You can even pass the type directly to the function as a parameter, like this:
#[handler]
async fn edit<'a>(good_man: GoodMan<'a>) -> String {
res.render(Json(good_man));
}
There is considerable flexibility in the definition of data types, and can even be resolved into nested structures as needed:
#[derive(Serialize, Deserialize, Extractible, Debug)]
#[extract(default_source(from = "body", format = "json"))]
struct GoodMan<'a> {
#[extract(source(from = "param"))]
id: i64,
#[extract(source(from = "query"))]
username: &'a str,
first_name: String,
last_name: String,
lovers: Vec<String>,
/// The nested field is completely reparsed from Request.
#[extract(source(from = "request"))]
nested: Nested<'a>,
}
#[derive(Serialize, Deserialize, Extractible, Debug)]
#[extract(default_source(from = "body", format = "json"))]
struct Nested<'a> {
#[extract(source(from = "param"))]
id: i64,
#[extract(source(from = "query"))]
username: &'a str,
first_name: String,
last_name: String,
#[extract(rename = "lovers")]
#[serde(default)]
pets: Vec<String>,
}
For specific examples, see: extract-nested.