Rust
Iron

[Rust] Ironで1ファイルWebアプリを実現する

More than 1 year has passed since last update.

IronでWebアプリ書いてクロスコンパイルして、ラズパイとかに送り込んで実行する際、jsやcssファイルが実行形式の中に入ってたらいいかも、ということで実際にやってみた。

追記:ほぼ同目的のパッケージがすでにcrate.ioに上がってました(https://crates.io/crates/include_dir)
後述してますが、Ironのハンドラまで含めて作ってみたので興味あれば使ってみてください。
https://crates.io/crates/embed_staticfile

ディレクトリに対してinclude_bytes!()を実行するコンパイラプラグインinclude_dir!()を作った

https://github.com/maueki/include_dir.rs

rust標準でinclude_bytes!()というコンパイラプラグインがある。
これは引数のファイルをコンパイル時に読み込んでバイト配列にしてくれるものである。
今回作ったマクロは引数ディレクトリ内ファイルのパスとバイト配列をHashMapに入れて返すinclude_dir!()というコンパイラプラグインである。

1ファイルWebアプリ実装

https://github.com/maueki/iron-onefile

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

extern crate iron;
extern crate router;
extern crate mount;
extern crate conduit_mime_types as mime_types;
extern crate hyper;

#[macro_use] extern crate lazy_static;

use iron::prelude::*;
use iron::status;
use mount::Mount;
use std::collections::HashMap;
use std::path::Path;
use hyper::mime::Mime;

lazy_static! {
    static ref ASSETS: HashMap<String, Vec<u8>> = include_dir!("../assets");
    static ref MIME_TYPES: mime_types::Types = mime_types::Types::new().unwrap();
}

fn request_assets(req: &mut Request) -> IronResult<Response> {
    let path:String = req.url.path().pop().unwrap().to_string();
    match ASSETS.get(&path) {
        Some(b) => {
            let mut res = Response::with((status::Ok, b.as_slice()));

            let mime_str = MIME_TYPES.mime_for_path(Path::new(&path));
            let _ = mime_str.parse().map(|mime: Mime| res.set_mut(mime));
            Ok(res)
        },
        None => {
            Ok(Response::with((status::NotFound)))
        }
    }
}

fn main() {
    let mut mount = Mount::new();

    mount.mount("/assets/", request_assets);

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

ASSETSinclude_dir!()で作ったパス文字列とバイト配列のHashMapが格納されている。
request_assets()ではリクエストのパスがASSETSにあれば内容を返し、なければ404を返している。
MIME_TYPESはパスからContentTypeを判定するたしレスポンスにセットするために使用している。

ビルドするとassets以下が取り込まれた実行形式が作成される。

追記(2017/10/25): 上記コードは include_dir_bytes ver0.1でのみ有効。ver0.2からはHashMap<&'static Path, Vec<u8>>を返すようになった。

追記(2017/10/20): ハンドラ部分もlib化した

https://github.com/maueki/embed_staticfile.rs

Cargo.tomlのdependenciesを追加して

[dependencies]
include_dir_bytes = "0.2"
embed_staticfile = "0.2"

次のように書ける

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

extern crate iron;
extern crate mount;

extern crate embed_staticfile;

use iron::prelude::*;
use mount::Mount;
use embed_staticfile::EmbedStatic;

fn main() {
    let mut mount = Mount::new();
    mount.mount("/assets/", EmbedStatic::new(include_dir!("../assets")));

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

課題

cargo buildをassets以下の変更に対応させたい

小さなプロジェクトならcargo-docargo do clean, buildすいればいいか