RustのRocketのメモ(リクエスト)

2023/07/11

Rust のRocket のリクエスト関連のメモです。ここ を読みながら確認してみたこと等をメモします。

目次

リクエスト

パラメータの型

下記のようにage:u8となっている場合、/bob/32/trueとかだと OK。でも/bob/a/trueとか u8 型を求めているのに文字列とかだと 404 エラーになる。bool はtruefalseのいずれかなら 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には、SuccessForward以外には、下記のように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"
}
Rust🦀, Network⚡, PostgreSQL🐘, Unity🎮

Tags

rust  (9)
rocket  (7)
svelte  (5)
c++  (4)
vscode  (3)
sqlx  (3)
glfw  (2)
opengl  (2)
nestjs  (2)
render  (2)
wsl2  (2)
goerli  (1)
geth  (1)
nft  (1)
gui  (1)
tetris  (1)
jwt  (1)
prisma  (1)
urql  (1)
mdsvex  (1)
tmux  (1)
nvim  (1)
axum  (1)
vim  (1)
pacman  (1)
Cursor  (1)
VSCode  (1)
PHP  (1)