IronでWebアプリ書いてクロスコンパイルして、ラズパイとかに送り込んで実行する際、jsやcssファイルが実行形式の中に入ってたらいいかも、ということで実際にやってみた。
追記:ほぼ同目的のパッケージがすでにcrate.ioに上がってました(https://crates.io/crates/include_dir)
後述してますが、Ironのハンドラまで含めて作ってみたので興味あれば使ってみてください。
https://crates.io/crates/embed_staticfile
ディレクトリに対してinclude_bytes!()
を実行するコンパイラプラグインinclude_dir!()
を作った
rust標準でinclude_bytes!()
というコンパイラプラグインがある。
これは引数のファイルをコンパイル時に読み込んでバイト配列にしてくれるものである。
今回作ったマクロは引数ディレクトリ内ファイルのパスとバイト配列をHashMapに入れて返すinclude_dir!()
というコンパイラプラグインである。
1ファイルWebアプリ実装
#![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();
}
ASSETS
にinclude_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化した
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-doでcargo do clean, build
すいればいいか