健忘録としてRustのWebフレームワークであるRocketのガイド、Rocket Guideの内容について翻訳していきます。
内容は省略する箇所もあるかもしれませんし、翻訳の正確さについては保証しません。
なお、まえがき部分は省略し、Overviewセクションから始めます。
内容に間違いなどありましたらコメントにて指摘いただけると幸いです。
#Lifecycle
Rocketの主なタスクは、リクエストを受信し、内容をアプリケーションの処理中に処理したのちに、生成されたレスポンスをクライアントに返すことです。私たちはリクエストからレスポンスまでの一連の流れを"ライフサイクル"と読んでいます。ライフサイクルについて、以下にまとめます。
1.Routing
受信したHTTPリクエストを、コード上で操作するための構造にパースし、宣言されたルートの記述に一致するリクエストハンドラーを呼び出します。
2.Validation
受信したリクエストと型や条件が一致するルートが存在するか検査します。バリデーションに失敗した場合、Rocketは次にマッチするルートを検証するか、もしくはエラーハンドラーを返します。
3.Processing
ルートが関連付けられたリクエストハンドラーは、バリデートされた引数とともに呼び出されます。これは、アプリケーションにおける主要なビジネスロジックを担当します。処理が無事に完了した場合は、レスポンスを生成します。
4.Response
Processingで生成されたレスポンスが処理されます。適切なHTTPレスポンスを生成し、それをクライアント側に返します。これでライフサイクルは完了です。引き続きリクエストの受信を待機し、受信した場合、再びライフサイクルを処理します。
#Routing
Rocketのアプリケーションは、ルートとハンドラーを中心として回ります。ルートとは、
・ 受信したリクエストと紐づけるための、パラメーターの集合。(URLパラメータなど)
・ リクエストを処理し、レスポンスを返すためのハンドラー。
の組み合わせのことを指します。
ハンドラーは、任意の数の引数を受け取り、任意の型を返すシンプルな関数です。
照合するためのパラメータには、静的パス、動的パス、パスセグメント、フォーム、クエリ文字列、フォーマット指定子、そして本文のデータなどがあります。Rocketではルートを宣言する際に、その他の言語においての関数を修飾するようなやり方(注:Javaにおいてのアノテーションなど)を採用しています。その方が簡単にルートを宣言できるからです。ルートは、照合するためのパラメータの集合とともに宣言されます。ルートの宣言の例は以下の通りです。
#[get("/world")] // <- ルートの宣言
fn world() -> &'static str { // <- リクエストハンドラー
"Hello, world!"
}
この world
のルート宣言は、静的パス"/world"
からのGET
リクエストに照合します。world
の例はシンプルですが、より興味深いアプリケーションを作る際には、ルートのパラメータを追加する必要が出てくるでしょう。次回のRequestセクションでは、ルートを設定する際に利用できるオプションについても説明します。
#Mounting
Rocketがリクエストを、ルートを元に受信できるようにするためにはマウントを行う必要があります。
fn main() {
rocket::ignite().mount("/hello", routes![world]);
}
マウントの関数は入力として、
1.ルートの下層に位置する、ベースとなるパス(この例では"/hello"
)。
2.派生するルートへの経路を記述するroutes!
マクロ(この例ではroutes![world]
。複数宣言する場合はroutes![a, b, c]
のように記述する)。
これにより、ignite
関数を介してRocket
インスタンスが生成され、"/hello"
パスにworld
のルートがマウントされます。"/hello/world"
に対するGETリクエストはworld関数の中で処理されるようになります。
大抵のケースでは、ベースとなるパスはシンプルに"/"
となるでしょう。
###NameSpacing
ルートをモジュールなど、ルートディレクトリ以外の中で宣言する際に、予期せぬエラーに遭遇するかもしれません。
mod other {
#[get("/world")]
pub fn world() -> &'static str {
"Hello, world!"
}
}
#[get("/hello")]
pub fn hello() -> &'static str {
"Hello, outside world!"
}
use other::world;
fn main() {
// error[E0425]: cannot find value `static_rocket_route_info_for_world` in this scope
rocket::ignite().mount("/hello", routes![hello, world]);
}
これが発生する原因は、routes!
マクロはRokectがコードを生成する際に、ルートの名前を構造体の名前として
暗黙的に変換するからです。解決策としては、上記の代わりにこのようにルートの名前を宣言することです。
rocket::ignite().mount("/hello", routes![hello, other::world]);
#Launching
いまやRocketはルートについて把握し、リクエストの待機を開始するためにlaunch
関数を呼ぶことができます。この関数は、サーバーを起動し、リクエストの受信まで待機します。リクエストが到達した段階で、Rocketは一致するルートを探し、ルートのハンドラへとリクエストを送ります。
通常のケースではlaunch
関数はmain
関数の中で呼ばれるでしょう。先ほどのHello, World!の例の完成形は以下の通りです、
#![feature(proc_macro_hygiene, decl_macro)]
#[macro_use] extern crate rocket;
#[get("/world")]
fn world() -> &'static str {
"Hello, world!"
}
fn main() {
rocket::ignite().mount("/hello", routes![world]).launch();
}
#![feature]
が記述された行は、Rustの今後リリース予定である機能で、試験的なnightlyチャネルで利用可能です。この行は、crateのルートでは必須となります。(通常はmain.rs
に当たります)。同時に、#[macro_use] extern crate rocket
を記述することで、rocket
のクレートと、すべてのマクロをインポートします。最終的に、main
関数で、launch
を呼ぶことで、アプリケーションが実行されます。コンソールには以下の表示がされているはずです。
🔧 Configured for development.
=> address: localhost
=> port: 8000
=> log: normal
=> workers: [logical cores * 2]
=> secret key: generated
=> limits: forms = 32KiB
=> keep-alive: 5s
=> tls: disabled
🛰 Mounting '/hello':
=> GET /hello/world (world)
🚀 Rocket has launched from http://localhost:8000
localhost:8000/hello/world
に接続すると、Hello, world!
の表示が確認できるはずです。