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)
}
下記のように..
を使うと複数のパスを一つの変数として扱える。下記だと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()
}
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)
}
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"
}