Rocket的rocket_contrib套件雖然有提供StaticFiles結構體,可以直接將它的實體註冊給Rocket,使Rocket能夠提供靜態檔案(如JS、CSS、圖片檔案等),但是這些檔案必須要和程式執行檔分開來儲存才行,所以如果想要實現單檔執行的Web應用程式,就需要靠其它的方式來實作程式。



先前雖然介紹過把外部檔案和Rust程式編譯在一起的方式,但如果要直接用這樣的方式搭配Rocket框架,讓它能夠提供存在於程式執行檔內部的靜態檔案的話,會遇到一些問題。如果是使用include_bytes巨集的話,首當其衝會遇到編譯速度的問題,這個問題雖然也可以利用該篇文章介紹的「Lazy Static Include」套件獲得緩解,但還有個大問題是依然存在的。

Rocket應用程式提供的靜態檔案,在開發階段時也會跟著Rust程式不斷進行修改。如果將這些檔案編譯進程式執行檔中,那麼每當這些檔案被修改後,總是要重新編譯並重新執行Rocket應用程式才能看到修改後的結果,進而嚴重拖慢開發速度。

Include Static Resources for Rocket Framework

「Include Static Resources for Rocket Framework」是筆者開發的套件,是針對上述問題提出的解決方案。在非使用release模式來編譯程式專案時,這個套件不會將靜態檔案與Rust程式編譯在一起,取而代之的是,將其讀取到記憶體中,並且在每次取用時,依照檔案的修改日期來檢查檔案是否有被修改過,如果有的話就會讀取新檔案的內容。這個套件會自動依照檔案路徑中的檔案副檔名來設定要回應的內容類型(Content-Type),除此之外還提供了HTTP的ETag機制。

Crates.io

https://crates.io/crates/rocket-include-static-resources

Cargo.toml

rocket-include-static-resources = "*"

使用方法

rocket_include_static_resources這個crate提供了StaticResponse結構體,可以直接作為路由處理程序的函式回傳值的型別。在使用這個套件時,首先要用StaticResponse結構體提供的fairing關聯函數來產生整流片,並註冊給Rocket來使用。fairing關聯函數必須傳入一個閉包,在這個閉包中,必須使用rocket_include_static_resources這個crate提供的static_resources_initialize巨集,來設定要一同編譯進Rust程式的靜態檔案,而其設定的方式為:先指定靜態檔案的鍵值,再指定靜態檔案的路徑。在路由處理程序中,可以使用同樣由rocket_include_static_resources這個crate提供的static_response巨集,傳入要回應的靜態檔案的鍵值,就可以產生出對應的StaticResponse結構實體。

舉例來說,在Cargo程式專案的根目錄底下的front-end目錄中,有favicon.icofavicon-16.png這兩個PNG圖檔,若要使Rocket應用程式能夠提供這兩個靜態檔案,並且使這兩個檔案能跟執行檔編譯在一起的話,程式可以寫成以下這樣:

#[macro_use]
extern crate rocket;

#[macro_use]
extern crate rocket_include_static_resources;

use rocket::State;

use rocket_include_static_resources::{EtagIfNoneMatch, StaticContextManager, StaticResponse};

static_response_handler! {
    "/favicon.ico" => favicon => "favicon",
    "/favicon-16.png" => favicon_png => "favicon-png",
}

#[get("/")]
fn index(
    static_resources: &State<StaticContextManager>,
    etag_if_none_match: EtagIfNoneMatch,
) -> StaticResponse {
    static_resources.build(&etag_if_none_match, "html-readme")
}

#[launch]
fn rocket() -> _ {
    rocket::build()
        .attach(static_resources_initializer!(
            "favicon" => "examples/front-end/images/favicon.ico",
            "favicon-png" => "examples/front-end/images/favicon-16.png",
            "html-readme" => ("examples", "front-end", "html", "README.html"),
        ))
        .mount("/", routes![favicon, favicon_png])
        .mount("/", routes![index])
}