Comparison of Rust Web Frameworks

Apr 29, 2017   #Rust  #Web 

はじめに

 Webアプリを作る上で、RustのWebフレームワークの中でどれが良そうかを調べてみた。ironrocketnickelのサンプルコード等をみた感じだと、現時点(2017/04/30)では、RustでWebアプリを書くならironが良さそう。

RustのWebフレームワーク

 低レベルのWebフレームワークとしてはhyperが有名で、このhyperをラップした高レベルのWebフレームワークがたくさんある1。ベンチマークテスト2やWebを見てると、高レベルのWebフレームワークは、前述したiron、rocket、nickelが良さそうだと感じたので、この3種類のフレームワークを少し触ってみた。

Iron

 拡張性と平行性に焦点を当てたフレームワークだけあり、Iron自体の機能は少ない。ルーティング、静的ファイルのマウント、セッション、JSONパースなどの拡張機能が別クレートとして提供されている。テンプレートエンジンとかもない。3種類のフレームワークの中では、最も開発者多く現時点でもメンテされている。チュートリアル的なものが無さそうだったので、ironのexamples3を見ていたのだけど、抽象化された拡張機能がクレートであるので、まずは使えそうな拡張機能を探すところから始めた方が良いと思う。

nickel.rs

 expressのAPIを参考にしたフレームワークで、Ironよりは機能が多い。nickel.rs4を見るとわかる通り、nickel自体が、ルーティングや静的ファイルのハンドル、JSONパース、テンプレートエンジン等の機能を提供している。ORマッパーまでは無いので、フルスタックフレームワークほど大きくはない。nickelは単一のクレートで、そこそこの機能を提供しているので、フレームワーク自体としては一番使いやすそう(サンプルを見てる範囲では)。ただし、GithubやIRCを見ていると開発があまり活発ではないので、採用するならエネルギーが必要。

#[macro_use] extern crate nickel;

use std::collections::HashMap;
use nickel::{Nickel, HttpRouter, StaticFilesHandler};

fn main() {
    let mut server = Nickel::new();

    server.get("/", middleware! { |_, response|
        // templatesにHashMapを渡してrenderする
        let mut data = HashMap::new();
        data.insert("name", "user");
        return response.render("templates/index.tpl", &data);
    });

    // getリクエストで/user/*にマッチし、*をuseridパラメタとして抽出
    server.get("/user/:userid", middleware! { |request|
        format!("This is user: {:?}", request.param("userid"))
    });

    // 静的ファイルのハンドラを設定
    server.utilize(StaticFilesHandler::new("assets/"));

    server.listen("127.0.0.1:6767");
}

Rocket

 3種類のフレームワークの中で、最も新しく(たぶん)、開発もわりと活発。機能的にはironくらいの量であるが、ルーティングの指定やパラメタのハンドルにRustのアトリビュートを使用しており、少し変わっている。またRocketでは、nightlyの機能を使っている。

#![feature(plugin)]
#![plugin(rocket_codegen)]

extern crate rocket;

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

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

比較

 Webフレームワークの機能自体にはお大きな差を感じなかったが、個人的にはnickelが一番使いやすかった。ただし、開発コミュニティの活発差を鑑みると、RustでWebアプリケーションを開発するのなら、現時点ではironが最適だと思う。

ironによるWebアプリケーション開発

 ironと、ironが提供しているミドルウェアの拡張機能Routing、Mounting、Static File Servingのクレートと、PythonのJinja2ライクなテンプレートエンジンTeraを使ったサンプルコードを書いた。

[dependencies]
iron = "0.5.*"
router = "*"
mount = "*"
staticfile = "*"
tera = "*"
lazy_static = "*"
extern crate iron;
extern crate mount;
extern crate router;
extern crate staticfile;
#[macro_use] extern crate tera;
#[macro_use] extern crate lazy_static;

use std::path::Path;
use iron::prelude::*;
use iron::headers::ContentType;
use iron::status;
use mount::Mount;
use router::Router;
use staticfile::Static;
use tera::{Tera, Context};


lazy_static! {
    pub static ref TEMPLATES: Tera = {
        let mut tera = compile_templates!("templates/*");
        tera.autoescape_on(vec!["html"]);
        tera
    };
}


fn handler(req: &mut Request) -> IronResult<Response> {
    let query = req.extensions.get::<Router>()
                                  .unwrap()
                                  .find("query")
                                  .unwrap_or("/");

    let mut context = Context::new();
    context.add("query", &query.to_string());
    let html = TEMPLATES.render("index.html", &context).unwrap();
    Ok(Response::with((ContentType::html().0, status::Ok, html)))
}

fn main() {

    let mut router = Router::new();
    router.get("/", handler, "index");
    router.get("/:query", handler, "query");

    let mut mount = Mount::new();
    mount.mount("/", router);
    mount.mount("/css", Static::new(Path::new("assets/css")));

    Iron::new(mount).http("localhost:3000").unwrap();
}

おわりに

 RustとironでWebアプリを書く場合、iron自体が小さいので拡張機能となるクレートの選別が大変そう。

更新

  • 2017/04/30: レスポンスにContentTypeの指定を忘れていたので追記

参考

関連記事