Rocket的rocket_contrib
套件雖然有提供Template
結構體,可以套用Handlebars模板來回應HTML資料,但是這些模板檔案必須要和程式執行檔分開來儲存才行,所以如果想要實現單檔執行的Web應用程式,就需要靠其它的方式來使用模板引擎。
先前雖然介紹過把外部檔案和Rust程式編譯在一起的方式,但如果要直接用這樣的方式搭配Rocket框架,讓它能夠套用存在於程式執行檔內部的Handlebars模板的話,會遇到一些問題。如果是使用include_bytes
巨集的話,首當其衝會遇到編譯速度的問題,這個問題雖然也可以利用該篇文章介紹的「Lazy Static Include」套件獲得緩解,但還有個大問題是依然存在的。
Rocket應用程式所使用的Handlebars模板,在開發階段時也會跟著Rust程式不斷進行修改。如果將這些模板檔案編譯進程式執行檔中,那麼每當這些檔案被修改後,總是要重新編譯並重新執行Rocket應用程式才能看到修改後的結果,進而嚴重拖慢開發速度。
Include Handlebars Templates for Rocket Framework
「Include Handlebars Templates for Rocket Framework」是筆者開發的套件,是針對上述問題提出的解決方案。在非使用release
模式來編譯程式專案時,這個套件不會將Handlebars的模板檔案與Rust程式編譯在一起,取而代之的是,將其讀取到記憶體中,並且在Rocket框架接收到HTTP請求時,依照所有模板檔案的修改日期來判斷模板引擎是否需要重新載入這些模板。這個套件也能夠將產生出來的HTML語法壓縮或是快取起來,除此之外還提供了HTTP的ETag機制。
Crates.io
Cargo.toml
使用方法
rocket_include_handlebars
這個crate提供了HandlebarsResponse
結構體,可以直接作為路由處理程序的函式回傳值的型別。在使用這個套件時,首先要用HandlebarsResponse
結構體提供的fairing
關聯函數來產生整流片,並註冊給Rocket來使用,這個整流片可以在Rocket應用程式狀態中建立一個大小為64(筆)的快取空間。fairing
關聯函數必須傳入一個閉包,在這個閉包中,必須使用rocket_include_handlebars
這個crate提供的handlebars_resources_initialize
巨集,來設定要一同編譯進Rust程式的Handlebars模板檔案,而其設定的方式為:先指定模板檔案的鍵值,再指定模板檔案的路徑。在路由處理程序中,可以使用同樣由rocket_include_handlebars
這個crate提供的handlebars_response
巨集,傳入要回應的模板檔案的鍵值和上下文(context),就可以產生出對應的HandlebarsResponse
結構實體。
預設的handlebars_response
巨集所產生出來的HandlebarsResponse
結構實體會主動壓縮HTML語法,如果不想要壓縮HTML語法的話,可以在第一個參數前加上disable_minify
。如果只想要在release
模式下才壓縮HTML語法的話,可以在第一個參數前加上auto_minify
。
如果要快取產生出來的HandlebarsResponse
結構實體,可以再使用rocket_include_handlebars
這個crate提供的handlebars_response_cache
巨集。使用handlebars_response_cache
巨集時,要去取得被註冊在Rocket應用程式狀態中的HandlebarsContextManager
。
舉例來說,在Cargo程式專案的根目錄底下的views
目錄中,有index.hbs
和index2.hbs
這兩個Handlebars模板,若要使Rocket應用程式能夠套用這兩個模板檔案,並且使這兩個檔案能跟執行檔編譯在一起的話,程式可以寫成以下這樣:
#[macro_use]
extern crate rocket;
#[macro_use]
extern crate rocket_include_handlebars;
use std::collections::HashMap;
use rocket::State;
use rocket_include_handlebars::{EtagIfNoneMatch, HandlebarsContextManager, HandlebarsResponse};
use serde_json::json;
#[get("/")]
fn index(
handlebars_cm: &State<HandlebarsContextManager>,
etag_if_none_match: EtagIfNoneMatch,
) -> HandlebarsResponse {
let mut map = HashMap::new();
map.insert("title", "Title");
map.insert("body", "Hello, world!");
handlebars_response!(handlebars_cm, etag_if_none_match, "index", map)
}
#[get("/disable-minify")]
fn index_disable_minify(
handlebars_cm: &State<HandlebarsContextManager>,
etag_if_none_match: EtagIfNoneMatch,
) -> HandlebarsResponse {
let mut map = HashMap::new();
map.insert("title", "Title");
map.insert("body", "Hello, world!");
handlebars_response!(disable_minify handlebars_cm, etag_if_none_match, "index", map)
}
#[get("/2")]
fn index_2(
cm: &State<HandlebarsContextManager>,
etag_if_none_match: EtagIfNoneMatch,
) -> HandlebarsResponse {
handlebars_response_cache!(cm, etag_if_none_match, "index-2", {
println!("Generate index-2 and cache it...");
let json = json! ({
"title": "Title",
"placeholder": "Hello, \"world!\"",
"id": 0,
});
handlebars_response!(auto_minify cm, EtagIfNoneMatch::default(), "index2", json)
})
}
#[launch]
fn rocket() -> _ {
rocket::build()
.attach(HandlebarsResponse::fairing(|handlebars| {
handlebars_resources_initialize!(
handlebars,
"index" => "views/index.hbs",
"index2" => ("views", "index2.hbs")
);
handlebars_helper!(inc: |x: i64| x + 1);
handlebars.register_helper("inc", Box::new(inc));
// NOTE: The above `inc` helper can be alternately added by enabling the `helper_inc` feature for the rocket_include_handlebars crate.
}))
.mount("/", routes![index, index_disable_minify])
.mount("/", routes![index_2])
}
這邊要注意的是,在傳給HandlebarsResponse
結構體的fairing
關聯函數使用的閉包中,我們替handlebars
變數註冊了我們自訂的helper函數。以下是index.hbs
和index2.hbs
的原始碼,可以參考一下:
<!DOCTYPE html>
<html>
<head>
<meta charset=UTF-8>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>{{title}}</title>
</head>
<body>
{{body}}
</body>
<!DOCTYPE html>
<html>
<head>
<meta charset=UTF-8>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>{{title}}</title>
</head>
<body>
<input id="input-{{inc id}}" type="text" placeholder="{{placeholder}}">
</body>
實際上,這個套件已經有替Handlebars模板引擎提供一些內建的額外helper函數,可以在Cargo.toml
設定檔中啟用以下特色來直接引用有需要的helper函數,就不用自己再寫一次啦!
helper_inc
:啟用inc
函數,可以使數值加一。helper_dec
:啟用dec
函數,可以使數值減一。helper_eq_str
:啟用eq_str
函數,可以判斷兩個字串是否相同。helper_ne_str
:啟用ne_str
函數,可以判斷兩個字串是否不相同。
如果覺得預設的快取空間太小的話,可以使用HandlebarsResponse
結構體的fairing_cache
關聯函數來產生整流片,這個fairing_cache
關聯函數傳入的閉包可以直接回傳要在應用程式狀態中建立的快取空間太小。
例如:
rocket::build()
.attach(HandlebarsResponse::fairing_cache(|handlebars| {
handlebars_resources_initialize!(
handlebars,
"index" => "views/index.hbs",
"index2" => ("views", "index2.hbs")
);
handlebars_helper!(inc: |x: i64| x + 1);
handlebars.register_helper("inc", Box::new(inc));
512
}))