ARTICLES

【Rust】Rocket入門 - ルーティング / ダイナミックパス / フォワーディング 編

By Kaisei Yokoyama

前回に引き続き、RustのWeb Framework、Rocketの解説をしていきます。

Overview

この記事で取り扱う内容は以下の通りです。

  • 前回作成したコードの解説
    • 簡単なレスポンス
    • ルーティング
  • Rocketのルーティングまわりの機能の解説
    • ダイナミックパス
    • フォワーディング

前回作成したコードの解説

fn index()

#[get("/")]
fn index() -> &'static str {
  "Hello, World!"
}

関数そのものは、常に決まった文字列を返すだけの単純なものです。 最も重要なのは関数のアトリビュート、#[get("/")]の部分。

大なり小なりウェブアプリを作った人ならば、getというキーワードに心当たりがあると思います。 そう、GETリクエストのことです。

もちろん、/にも見覚えがありますよね。

すなわち、このアトリビュートが意味するところは

(指定されたパス以下で)/GETリクエストが来た場合は、この関数が動作する

ということになります。

指定されたパス以下で という条件がいきなり出てきました。 この仕組みには、次に説明するルーティングが関わっています。

fn main()

fn main() {
  rocket::ignite()
    .mount("/", routes![index])
    .launch();
}

rocketのアプリケーションを起動するとき、ignite()からlaunch()までの間にメソッドチェーンで色々な情報を付け加えることができます。

mount()もその設定のためのメソッドのひとつ1です。 このメソッドは第一引数にベースとなるパスをとり、第二引数に示された関数をマウント(取り付け)します。

前回のコードでは、index()はベースのパス//に取り付けられていました。 だから、/にアクセスしたときHello, World!が表示されたのです。

/ばかりが出てきてちょっと例が悪いので、src/main.rsに関数を追加しましょう。

#[get("/bar")]
fn bar() -> &'static str {
  "FooBar"
}

この関数 にマウントをとる をマウントするために、main()も変更します。

fn main() {
  rocket::ignite()
    .mount("/", routes![index])
    .mount("/foo", routes![bar])
    .launch();
}

できました! index()に代わって、bar()関数で説明しましょう。

このコードでは、bar()関数はベースとなるパス/fooの中の/barに取り付けられています。URLにすると、/foo/barです。

これは、同じベースのパスに複数の関数を対応付けるための機能です。 下記のコードのように、同じパスの直下に複数の関数をマウントしたいときに本領を発揮します。

bar1(),bar2(),bar3(),のそれぞれに#[get("/foo/bar1")],#[get("/foo/bar2")],#[get("/foo/bar3")],と似た内容のアトリビュートを書きまくる愚を犯す2ことなく、#[get("/bar1")],#[get("/bar2")],#[get("/bar3")]で事足りるようになります。

fn main() {
  rocket::ignite()
    .mount("/foo", routes![foo::bar1, foo::bar2, foo::bar3])
    .launch();
}

Rocketのルーティング

Dynamic Path

Rocketのルーティングにおける取り柄は、mount()だけではありません。次の例はどうでしょう?

#[get("/hello/<name>/<age>")]
fn hello(name: String, age: u8) -> String {
  format!("Hello, {}({})", name, age)
}

この関数が/にマウントされた状態で/hello/yokoyama/21にアクセスすると、Hello, yokoyama(21)のレスポンスが返されます。

このように、Rocketのルーティングでは、パスから関数の引数を切り取ることができます。
引数の型はFromParam traitを実装している必要があります。 もっとも、Rustを使う上でよく見かける型(String, i32, f32, Option<T>など)はあらかじめライブラリー側がFromParam traitを実装しているので、単純なことをしているうちは気にする必要はないでしょう。

Fowarding

#[get("/<name>")]
fn hello_with_name(name: String) -> String { ... }

#[get("/<age>")]
fn hello_with_age(age: String) -> String { ... }

ここに二つの関数があります。

二つの受け持ちするパスは、完全には分けられません。 /223344/7777などは文字列としても数字としても認識できるため、どちらの関数のルーティングの条件にも当てはまるからです。

もちろん、Rustコンパイラはこのようなコードを許しません。cargo buildコマンドを平気で叩くような人間には、コンピュータの無慈悲な鉄槌が下ります。コンパイルエラーです。
誠に残念ながら、このコードはコンパイルを通ります。 ただし、実行するとpanicして終了します。

thread 'main' panicked at 'route collisions detected', ~

route collision、つまり担当領域の衝突が検知されました、というエラーメッセージです。

二つの関数の担当領域が重複する場合でも再現性のあるルーティングを実現するのに最も簡単だと思われる方法は、関数に序列をつけてしまうことです。 Rocketには、そのための機能があります。

#[get("/<name>", rank = 2)]
fn hello_with_name(name: String) -> String { ... }

#[get("/<age>")]
fn hello_with_age(age: String) -> String { ... }

これでよし。 cargo runしてもpanicは起こらず、再現性のあるルーティングを実装できました。

この場合、ルーティングにおいてはまずhello_with_age()にマッチするかを判定され、次にhello_with_name()にマッチするかを判定されます3
その結果、/0/9999に対するGETリクエストはhello_with_age()によって処理され、/foobar/mojiretsuに対するGETリクエストはhello_with_name()によって処理されることになります。

次回予告

お疲れ様でした。
ルーティング / ダイナミックパス / フォワーディング 編と銘打ってはいますが、要はそれっぽく動くというだけのことです。 一度知ったなら、細かいことは忘れてしまって支障はないと思います。 なにか問題があっても、エラーメッセージでググればたいていは解決することでしょう。

実は、nodejsのexpressにも似たような機能があります。Rocketでは、その使用感をそのままに、なおかつヒューマンフレンドリーな静的型付けのモダンかつ高速な言語で実装できます。

Rust、やっていきましょう。

今回はGETリクエストについて扱ったので、次回はPOSTリクエスト、気が向いたらJsonについても解説しようと思います。 それでは。


  1. 同種の他のメソッドについては、Rocketのドキュメントをどうぞ。 ↩︎

  2. プログラミングにおいては、共通化できるところはできるだけまとめるのが基本と聞いています。 ↩︎

  3. 今回の例に限らず、どのパターンにもマッチしない場合は勝手に404: NotFoundエラーを返してくれます ↩︎

SHARE THIS POST