はじめに
Rust の Web アプリケーションフレームワークとしては actix-web
rocket
がメジャーで、 tide
warp
がそれに続くといったところが現在の状況でしょうか。
ここでは Tide について簡単に見ていくことにします。
Tide とは
Tide は Rust によるシンプルな Web アプリケーションフレームワークです。
以下のように説明されています。
Tide は、迅速な開発のために作られた、ミニマルで実用的な Rust の Web アプリケーションフレームワークです。非同期のWebアプリケーションやAPIの構築をより簡単に、より楽しくするための堅牢な機能を備えています。
初回コミットは2018年8月とまだ若いプロジェクトではあります。
async-std
を基盤とした非同期Webアプリケーション という点がポイントになるかと思います。
プロジェクトの作成
cargo でプロジェクトを作成します。
$ cargo new tide-example --bin
IDEA で Command Line Launcher を設定しており、Rust プラグインが入っている場合は、以下でプロジェクトを開くことができます。
$ idea tide-example/Cargo.toml
Command Line Launcher については以下を参照してください。
Cargo.toml を開き、依存を以下のように追加します。
[dependencies] tide = "0.16.0" async-std = { version = "1.9.0", features = ["attributes"] }
async-std
は非同期処理のランタイムを提供します。
Hello World
main.rs
を以下のように編集します。
#[async_std::main] async fn main() -> tide::Result<()> { let mut app = tide::new(); app.at("/").get(|_| async { Ok("Hello, world!") }); app.listen("127.0.0.1:8080").await?; Ok(()) }
#[async_std::main]
は、先程導入した async-std
の attributes
で利用できるアトリビュートです。
これにより、続く関数が非同期処理のランタイム上で動きます。
関数内では、最初に tide::new()
で tide のオブジェクトを作成しています。
続いて tide.at()
にてURLのパスを指定し、HTTP GET に応答する処理をクロージャで定義しています。
async
の非同期ブロックで Result
を返す形となります。
最後に listen()
で指定ポートでリスンを開始しています。
await
でサーバの完了を待機します。
実行してみましょう。
$ cargo run
http://localhost:8080/
にアクセスすれば、以下のような結果が得られます。
Response::builder
を使って自由にレスポンスを作成することもできます。
HTMLを返却するよう、以下のように書き換えてみましょう。
app.at("/").get(|_| async { Ok(tide::Response::builder(200) .body("<html><h2>Hello, world!</h2></html>") .header("Server", "tide") .content_type(tide::http::mime::HTML) .build()) });
以下のような結果が得られます。
パスパラメータを受け取る
url として :user
のように名前をつけることで、req.param("user")
のようにパラメータを取得することができます。
先程の例をパラメータを受け取るように変更します。
app.at("/:user").get(|req: tide::Request<()>| async move { Ok(tide::Response::builder(200) .body(format!("<html><h2>Hello, {}!</h2></html>", req.param("user")?)) .header("Server", "tide") .content_type(tide::http::mime::HTML) .build()) });
http://localhost:8080/Thom
にアクセスすれば、以下のような結果が得られます。
ここでは直接HTMLを返していますが、以下のように外部の静的なファイルを扱うこともできます。
app.at("/src/*").serve_dir("src/")?; app.at("/index").serve_file("index.html")?;
JSON を返す
レスポンスに JSON を返すには json!
マクロが用意されています。
マクロをパスに追加します。
use tide::prelude::json;
json!
マクロにより以下のようにJSONのレスポンスを返すことができます。
app.at("/animals").get(|_| async { Ok(json!([ { "name": "chashu", "age": 8 }, { "name": "nori", "age": 3 } ])) });
http://localhost:8080/animals
にアクセスすれば、以下のような結果が得られます。
serde
を使ってシリアライズした結果として返すこともできます。依存に追加します。
[dependencies] ... serde = { version = "1.0", features = ["derive"] }
ソースを以下のように変更します。
use tide::prelude::json; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize)] struct Animal { name: String, age: u8, } #[async_std::main] async fn main() -> tide::Result<()> { let mut app = tide::new(); app.at("/animals").get(|_| async { let animals = vec![ Animal { name: "chashu".into(), age: 8 }, Animal { name: "nori".into(), age: 3 }, ]; Ok(tide::Body::from_json(&animals)?) }); app.listen("127.0.0.1:8080").await?; Ok(()) }
Ok(json!(animals))
と json!
マクロを使うこともできます。
http://localhost:8080/animals
にアクセスすれば、先程と同様の結果が得られます。
クエリパラメータを受け取る
クエリパラメータは serde を経由して Request.query()
で簡単にインスタンスを取得することができます。
app.at("/animal").get(|req: tide::Request<()>| async move { let animal: Animal = req.query()?; Ok(tide::Body::from_json(&animal)?) });
http://localhost:8080/animal?name=chashu&age=8
にアクセスすれば、クエリパラメータから構築された JSON を得ることができます。
POSTパラメータを受け取る
ポストパラメータを受け取る場合も同じような構成で可能です。
JSON をリクエストボディとして受け取る場合は以下のようにします。
app.at("/animal").post(|mut req: tide::Request<()>| async move { let animal: Animal = req.body_json().await?; Ok(tide::Body::from_json(&animal)?) });
curl で JSON を POST してみましょう。
$ curl -X POST -H "Content-Type: application/json" -d '{"name":"chashu","age":8}' localhost:8080/animal {"name":"chashu","age":8}
POST した内容がエコーされていますね。