Rust のRocketのリクエスト関連のメモです。ここを読みながら確認してみたこと等をメモします。
リクエスト
パラメータの型
下記のようにage:u8
となっている場合、/bob/32/true
とかだと OK。でも/bob/a/true
とか u8 型を求めているのに文字列とかだと 404 エラーになる。bool はtrue
かfalse
のいずれかなら OK。
#[get("/<name>/<age>/<cool>")] fn hello(name: &str, age: u8, cool: bool) -> String { ... }
同じパス・メソッド
2 つ以上のルートが同じパスとメソッドになっていると、下記エラーが発生する。同じパス・メソッドでも、型を変えたらよいかも?と思ったけど、そういうのはダメらしい。
Error: Rocket failed to launch due to the following route collisions
と思ったら、rank
をつけると、同じパス・メソッドでも型違うやつを下記のように複数作れた。rank が小さい程優先度が高くなる。
#[get("/<hoge>")] fn hoge(hoge: u8) -> String { format!("Hoge! {}", hoge) } #[get("/<hoge>", rank = 2)] fn hoge2(hoge: &str) -> String { format!("Hoge2! {}", hoge) }
複数のパスを 1 つの変数で扱う
下記のように..
を使うと複数のパスを一つの変数として扱える。下記だとpath
は、static/hoge.txt
とかになりますが、この場合プロジェクトルートの static を探すらしい。(下記ファイルがある場所ではない。)
ちなみに、下記にブラウザでアクセスすると、テキストとか画像がそのまま表示された。レスポンスヘッダの content-type は、画像だったら、image/webp
とかにちゃんとなってた。
#[get("/<file..>")] async fn files(file: PathBuf) -> Option<NamedFile> { let path = Path::new("static/").join(file); NamedFile::open(path).await.ok() }
リクエストガード (Request Guards)
Guard は、FromRequestトレイトを実装したやつをハンドラの引数に置くと、自動的にガードで設定されている条件をチェックしてくれる。引数の左から順にチェックする。複数のリクエストガードを引数に設定できて、全てのガードのチェックが通らないとアクセスできない。アクセスできるルートがないと 404 エラーが出る。
下記は「ヘッダにhoge
があって値が100
」の場合のみ OK を出すガードです。
use rocket::request::{FromRequest, Request, Outcome}; pub struct GuardA; #[rocket::async_trait] impl<'r> FromRequest<'r> for GuardA { type Error = String; async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error> { match req.headers().get_one("hoge") { Some(val) if val == "100" => Outcome::Success(GuardA), _ => Outcome::Forward(()), } } }
Outcome
には、Success
とForward
以外には、下記のようにFailure
もある。Failure
だと即座にエラー出して終わるけど、Forward
の場合は、他にマッチするルートがないかを確認視てくれるっぽい。1 つのルートに複数のリクエストガードを設定できるけど、複数設定した場合は、全部のガードが OK にならないとアクセスできない。左から順にチェックしていくから、OK だったら次のガードを確認していき、OK じゃなかった(マッチしなかった)場合、Failure
ならその場でエラーが出て終了するし、Forward
であれば、次のルートをチェックしにいく。
#[rocket::async_trait] impl<'r> FromRequest<'r> for GuardA { type Error = String; async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error> { match req.headers().get_one("hoge") { Some(val) if val == "100" => Outcome::Success(GuardA), _ => Outcome::Failure((Status::BadRequest, "Bad Request".to_string())), } } }
次のルートのチェックというのは、下記のように rank を使うことで、同じメソッド・パスのものを複数作ることで実現できる。
#[get("/<hoge>")] fn hoge(hoge: u8, _a: GuardA, _b: GuardB, _c:GuardC) -> String { format!("Hoge! {}", hoge) } #[get("/<hoge>", rank = 2)] fn hoge2(hoge: u8, _a: GuardA) -> String { format!("Hoge 2! {}", hoge) } #[get("/<hoge>", rank = 3)] fn hoge3(hoge: u8, _b: GuardB) -> String { format!("Hoge 3! {}", hoge) }
Cookie
CookieJar
を使うと Cookie が取得できます。get()
は、Option<&Cookie>
を返します。
use rocket::http::CookieJar; #[get("/")] fn index(cookies: &CookieJar<'_>) -> Option<String> { cookies.get("hoge").map(|hoge| format!("HOGE: {}", hoge.value())) }
尚、ハンドラが下記のように None を返すと 404 エラーになりました。
#[get("/none")] fn none() -> Option<String> { None }
リクエストデータ
format
で、リクエストヘッダのcontent-type
をチェックできる。data
は Body のデータの変数名を設定できる。ハンドラの引数でその変数名の型を設定したら、型に合わない場合はエラーが出る。ちなみに下記の場合、{hoge:10}
のようにnum
が含まれないデータを送ったら、「422 Unprocessable Entity」というエラーが出た。
use rocket::serde::json::Json; use serde::Deserialize; #[derive(Deserialize)] struct Hoge { num: u8, } #[post("/", format = "json", data = "<hoge>")] fn index(hoge: Json<Hoge>) -> String { format!("hoge {}", hoge.num) }
エラー
デフォルトだとエラーは HTML で返されます。ただ、リクエストヘッダにAccept: application/json
が設定されている場合は、json 形式で返してくれます。
また、エラー内容をカスタマイズするには下記のcatch
を使います。
use rocket::http::Status; use rocket::Request; #[catch(default)] fn default_catcher(status: Status, _request: &Request) -> String { let msg = match status.code { 404 => "Not Found", 500 => "Internal Server Error", _ => "Error", }; format!("{} {}", status.code, msg) } #[launch] fn rocket() -> _ { rocket::build().register("/", catchers![default_catcher]) }
下記のcatch(404)
のようにステータスコード毎に設定することもできます。
#[catch(404)] fn not_found() -> &'static str { "404 Not Found" }