リクエスト

SalvoではRequestを使用してユーザーリクエストのデータを取得できます:

クイックガイド

RequestはHTTPリクエストを表す構造体で、包括的なリクエスト処理機能を提供します:

  • 基本属性操作(URI、メソッド、バージョン)
  • リクエストヘッダーとCookieの処理
  • 各種パラメータの解析(パス、クエリ、フォーム)
  • リクエストボディ処理とファイルアップロード対応
  • 多様なデータ解析方法(JSON、フォームなど)
  • extractメソッドによる型安全な統一データ抽出
#[handler]
async fn hello(req: &mut Request) -> String {
    req.params().get("id").cloned().unwrap_or_default()
}

クエリパラメータの取得

get_queryでクエリパラメータを取得できます:

req.query::<String>("id");

フォームデータの取得

get_formでフォームデータを取得できます(非同期関数):

req.form::<String>("id").await;

JSONデシリアライズデータの取得

req.parse_json::<User>().await;

リクエストデータの抽出

Requestはこれらのデータを厳密な型構造に解析する複数のメソッドを提供します。

  • parse_params: ルーターパラメータを特定のデータ型に解析
  • parse_queries: URLクエリを特定のデータ型に解析
  • parse_headers: HTTPヘッダーを特定のデータ型に解析
  • parse_json: HTTPボディデータをJSON形式として解析
  • parse_form: HTTPボディデータをフォームとして解析
  • parse_body: コンテントタイプに基づきHTTPボディデータを解析
  • extract: 異なるデータソースを統合して特定の型を解析

解析原理

ここではカスタムserde::Deserializerを使用して、HashMap<String, String>HashMap<String, Vec<String>>のようなデータを特定のデータ型に抽出します。

例えば、URL queriesは実際にはMultiMap型として抽出され、MultiMapHashMap<String, Vec<String>>に似たデータ構造です。リクエストURLがhttp://localhost/users?id=123&id=234で、対象型が以下の場合:

#[derive(Deserialize)]
struct User {
  id: i64
}

最初のid=123が解析され、id=234は破棄されます:

let user: User = req.parse_queries().unwrap();
assert_eq!(user.id, 123);

対象型が以下の場合:

#[derive(Deserialize)]
struct Users {
  ids: Vec<i64>
}

id=123&id=234の両方が解析されます:

let users: Users = req.parse_queries().unwrap();
assert_eq!(user.ids, vec![123, 234]);

組み込みエクストラクタ

フレームワークにはリクエストパラメータエクストラクタが組み込まれており、HTTPリクエスト処理コードを大幅に簡素化できます。

TIP

使用するにはCargo.toml"oapi" featureを追加する必要があります

salvo = {version = "*", features = ["oapi"]}

その後、エクストラクタをインポートできます

use salvo::{oapi::extract::JsonBody, prelude::*};

JsonBody

リクエストボディからJSONデータを抽出し、指定した型にデシリアライズします。

#[handler]
async fn create_user(json: JsonBody<User>) -> String {
    let user = json.into_inner();
    format!("ID {} のユーザーを作成しました", user.id)
}

FormBody

リクエストボディからフォームデータを抽出し、指定した型にデシリアライズします。

#[handler]
async fn update_user(form: FormBody<User>) -> String {
    let user = form.into_inner();
    format!("ID {} のユーザーを更新しました", user.id)
}

CookieParam

リクエストのCookieから特定の値を抽出します。

//第2引数がtrueの場合、値が存在しないとinto_inner()はpanicします。
//falseの場合、into_inner()はOption<T>を返します。
#[handler]
fn get_user_from_cookie(user_id: CookieParam<i64,true>) -> String {
    format!("Cookieから取得したユーザーID: {}", user_id.into_inner())
}

HeaderParam

リクエストヘッダーから特定の値を抽出します。

#[handler]
fn get_user_from_header(user_id: HeaderParam<i64,true>) -> String {
    format!("ヘッダーから取得したユーザーID: {}", user_id.into_inner())
}

PathParam

URLパスからパラメータを抽出します。

#[handler]
fn get_user(id: PathParam<i64>) -> String {
    format!("パスから取得したユーザーID: {}", id.into_inner())
}

QueryParam

URLクエリストリングからパラメータを抽出します。

#[handler]
fn search_user(id: QueryParam<i64,true>) -> String {
    format!("ID {} のユーザーを検索中", id.into_inner())
}

高度な使用法

複数のデータソースを統合して特定の型を解析できます。まずカスタム型を定義します:

#[derive(Serialize, Deserialize, Extractible, Debug)]
/// デフォルトでボディからデータフィールド値を取得
#[salvo(extract(default_source(from = "body"))]
struct GoodMan<'a> {
    /// idはリクエストパスパラメータから取得し、自動的にi64型に解析
    #[salvo(extract(source(from = "param"))]
    id: i64,
    /// メモリコピーを避けるため参照型を使用可能
    username: &'a str,
    first_name: String,
    last_name: String,
}

Handlerでは以下のようにデータを取得できます:

#[handler]
async fn edit(req: &mut Request) {
    let good_man: GoodMan<'_> = req.extract().await.unwrap();
}

または型を直接関数パラメータとして渡すことも可能です:

#[handler]
async fn edit<'a>(good_man: GoodMan<'a>) {
    res.render(Json(good_man));
}

データ型の定義は非常に柔軟で、必要に応じてネストされた構造に解析することも可能です:

#[derive(Serialize, Deserialize, Extractible, Debug)]
#[salvo(extract(default_source(from = "body"))]
struct GoodMan<'a> {
    #[salvo(extract(source(from = "param"))]
    id: i64,
    #[salvo(extract(source(from = "query"))]
    username: &'a str,
    first_name: String,
    last_name: String,
    lovers: Vec<String>,
    /// このnestedフィールドは完全にRequestから再解析
    #[salvo(extract(flatten))]
    nested: Nested<'a>,
}

#[derive(Serialize, Deserialize, Extractible, Debug)]
#[salvo(extract(default_source(from = "body")))]
struct Nested<'a> {
    #[salvo(extract(source(from = "param"))]
    id: i64,
    #[salvo(extract(source(from = "query")))]
    username: &'a str,
    first_name: String,
    last_name: String,
    #[salvo(extract(rename = "lovers"))]
    #[serde(default)]
    pets: Vec<String>,
}

具体例はextract-nestedを参照してください。

#[salvo(extract(flatten))] VS#[serde(flatten))]

上記の例でNested<'a>が親と同じフィールドを持たない場合、#[serde(flatten))]を使用できますが、同じフィールドがある場合は#[salvo(extract(flatten))]を使用する必要があります。

#[salvo(extract(source(parse)))]

実際にはsourceparseパラメータを追加して特定の解析方法を指定できます。このパラメータを指定しない場合、解析はRequestの情報に基づいてBody部分の解析方法を決定します。フォームの場合はMuiltMap方式で、jsonペイロードの場合はjson形式で解析します。通常はこのパラメータを指定する必要はありませんが、特殊な機能を実現するために指定する場合があります。

#[tokio::test]
async fn test_de_request_with_form_json_str() {
    #[derive(Deserialize, Eq, PartialEq, Debug)]
    struct User<'a> {
        name: &'a str,
        age: usize,
    }
    #[derive(Deserialize, Extractible, Eq, PartialEq, Debug)]
    #[salvo(extract(default_source(from = "body", parse = "json"))]
    struct RequestData<'a> {
        #[salvo(extract(source(from = "param")))]
        p2: &'a str,
        user: User<'a>,
    }
    let mut req = TestClient::get("http://127.0.0.1:5800/test/1234/param2v")
        .raw_form(r#"user={"name": "chris", "age": 20}"#)
        .build();
    req.params.insert("p2".into(), "921".into());
    let data: RequestData = req.extract().await.unwrap();
    assert_eq!(
        data,
        RequestData {
            p2: "921",
            user: User { name: "chris", age: 20 }
        }
    );
}

例えば、実際のリクエストがFormで送信されても、特定のフィールドの値がjsonテキストである場合、parseを指定することでこの文字列をjson形式で解析できます。

API一部紹介、最新詳細はcreates apiドキュメントを参照

Request構造体メソッド概要

カテゴリメソッド説明
リクエスト情報uri()/uri_mut()/set_uri()URI操作
method()/method_mut()HTTPメソッド操作
version()/version_mut()HTTPバージョン操作
scheme()/scheme_mut()プロトコルスキーム操作
remote_addr()/local_addr()アドレス情報
リクエストヘッダーheaders()/headers_mut()全ヘッダー取得
header<T>()/try_header<T>()特定ヘッダー取得・解析
add_header()ヘッダー追加
content_type()/accept()コンテントタイプ/受け入れタイプ取得
パラメータ処理params()/param<T>()パスパラメータ操作
queries()/query<T>()クエリパラメータ操作
form<T>()/form_or_query<T>()フォームデータ操作
リクエストボディbody()/body_mut()リクエストボディ取得
replace_body()/take_body()ボディ変更/抽出
payload()/payload_with_max_size()生データ取得
ファイル処理file()/files()/all_files()アップロードファイル取得
first_file()最初のファイル取得
データ解析extract<T>()統一データ抽出
parse_json<T>()/parse_form<T>()JSON/フォーム解析
parse_body<T>()インテリジェントボディ解析
parse_params<T>()/parse_queries<T>()パラメータ/クエリ解析
特殊機能cookies()/cookie()Cookie操作(cookie feature必要)
extensions()/extensions_mut()拡張データストレージ
set_secure_max_size()セキュリティサイズ制限設定