RustのRocketのメモ(リクエスト)
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"}