在這個章節中,會繼續介紹rocket_contrib
這個Rocket官方的擴充套件所提供的有關HTTP回應的功能,包括JSON和MessagePack格式的回應,以及Handlebars和Tera這兩個HTML模板引擎。此外還會介紹Rocket框架本身提供的「快閃訊息」(Flash Message)機制。
JSON
在之前的章節中有提到rocket_contrib
的json
模組提供的Json
結構體可以作為路由處理程序的函式的回傳值型別,所以該怎麼樣才能建立一個Json
結構實體呢?
我們可以替自己實作出來的型別加上Serialize
這個derive
屬性的參數,使這個型別的資料可以直接被Rocket框架序列化。使用rocket_contrib::json::Json
結構體來包裹可被Rocket框架序列化的資料型別,就可以將包裹的資料序列化成JSON格式的字串了!
例如:
#[macro_use]
extern crate serde_derive;
extern crate rocket_contrib;
use rocket_contrib::json::Json;
#[derive(Serialize)]
struct User {
id: i32,
name: String,
}
#[get("/david")]
fn handler() -> Json<User> {
Json(User {
id: 5,
name: "David".to_string()
})
}
這邊要注意的地方是,Serialize
這個derive
屬性的參數來自於serde_derive這個crate。在啟用rocket_contrib
的json
特色時,記得也要將serde和serde_derive這兩個crate給引用至程式專案中。
當路由處理程序的函數是回傳rocket_contrib::json::Json
時,HTTP回應中的標頭欄位除了之前提到的三個基本欄位外,還會多出Content-Type
欄位,且該欄位的值會被設定為application/json
。
MessagePack
在之前的章節中有提到rocket_contrib
的msgpack
模組提供的MsgPack
結構體可以作為路由處理程序的函式的回傳值型別,所以該怎麼樣才能建立一個MsgPack
結構實體呢?
我們可以替自己實作出來的型別加上Serialize
這個derive
屬性的參數,使這個型別的資料可以直接被Rocket框架序列化。使用rocket_contrib::msgpack::MsgPack
結構體來包裹可被Rocket框架序列化的資料型別,就可以將包裹的資料序列化成MessagePack格式的資料了!
例如:
#[macro_use]
extern crate serde_derive;
extern crate rocket_contrib;
use rocket_contrib::msgpack::MsgPack;
#[derive(Serialize)]
struct User {
id: i32,
name: String,
}
#[get("/david")]
fn handler() -> MsgPack<User> {
MsgPack(User {
id: 5,
name: "David".to_string()
})
}
這邊要注意的地方是,Serialize
這個derive
屬性的參數來自於serde_derive這個crate。在啟用rocket_contrib
的msgpack
特色時,記得也要將serde和serde_derive這兩個crate給引用至程式專案中。
當路由處理程序的函數是回傳rocket_contrib::msgpack::MsgPack
時,HTTP回應中的標頭欄位除了之前提到的三個基本欄位外,還會多出Content-Type
欄位,且該欄位的值會被設定為application/msgpack
。
HTML模板引擎
rocket_contrib
可以使Rocket框架支援Handlebars和Tera這兩種HTML模板引擎。前者被廣泛用於JavaScript生態圈;後者則是參考了在Python生態圈中被廣泛使用的Jinja2,再運用Rust程式語言所重新開發出來的HTML模板引擎。
Handlebars的官方網站:
Tera的官方網站:
雖然Tera的效能會比Handlebars還要好很多,不過會用Tera的前端工程師實在不多呀!執行效能固然重要,但也要考慮到開發及後續維護的難易度,所以在權衡之下,很多時候我們還是得使用Handlebars模板。
透過rocket_contrib
來套用Handlebars和Tera的模板時,只需要使用一種抽象的rocket_contrib::templates::Template
結構體來操作即可。
若要使rocket_contrib::templates::Template
結構體支援Handlebars,必須要在Cargo程式專案的設定檔中啟用rocket_contrib
的handlebars_templates
特色。如下:
[dependencies.rocket_contrib]
version = "*"
default-features = false
features = ["handlebars_templates"]
若要使rocket_contrib::templates::Template
結構體支援Tera,必須要在Cargo程式專案的設定檔中啟用rocket_contrib
的tera_templates
特色。如下:
[dependencies.rocket_contrib]
version = "*"
default-features = false
features = ["tera_templates"]
模板引擎的整流片(Fairing)
要在Rocket中使用rocket_contrib::templates::Template
結構體,需要先使用rocket_contrib::templates::Template
結構體的fairing
關聯函數來取得必要的整流片,並註冊給Rocket來使用。
程式如下:
extern crate rocket_contrib;
use rocket_contrib::templates::Template;
...
let rocket = rocket::ignite();
let rocket = rocket.attach(Template::fairing());
...
這個整流片會在Attatch
事件發生時,去解析Rocket應用程式根目錄底下的templates
目錄中的HTML模板檔案。Handlebars的模板檔案使用的副檔名為.hbs
,Tera的模板檔案使用的副檔名為.tera
,如果同時使用相同檔案名稱(例如:index.hbs
和index.tera
)的Handlebars和Tera模板檔案,在這個解析的過程中就會被檢查出來,而使Rocket出現警告訊息。
在不使用Release模式來編譯程式專案的情況下,這個整流片還會去監聽Request
事件,在每次接收到HTTP請求時都會去檢查templates
目錄中的HTML模板檔案有無改變,如果有的話就重新解析。如此一來,在開發HTML模板時,即便不重啟Rocket應用程式也可以直接套用新的模板變更,方便開發者直接在網頁瀏覽器上刷新頁面。
如果需要針對模板引擎來進行設定的話,在取得整流片時,應使用rocket_contrib::templates::Template
結構體的custom
關聯函數來客製出自己的整流片。
例如以下程式,可以讓Handlebars模板能夠使用我們自己製作好的幫手(Helper)。
#[macro_use]
extern crate handlebars;
extern crate rocket_contrib;
use rocket_contrib::templates::Template;
...
let rocket = rocket::ignite();
let rocket = rocket.attach(custom(|engines| {
handlebars_helper!(inc: |x: i64| x + 1);
handlebars_helper!(dec: |x: i64| x - 1);
handlebars_helper!(eq_str: |x: str, y: str| x == y);
handlebars_helper!(ne_str: |x: str, y: str| x != y);
engines.handlebars.register_helper("inc", Box::new(inc));
engines.handlebars.register_helper("dec", Box::new(dec));
engines.handlebars.register_helper("eq_str", Box::new(eq_str));
engines.handlebars.register_helper("ne_str", Box::new(ne_str));
}));
...
注意這邊的handlebars_helper
巨集是由handlebars這個crate提供的哦!記得也要將它給引用至程式專案中。
用模板引擎來產生HTML類型的HTTP回應
要將rocket_contrib::templates::Template
結構體作為路由處理程式函數的回傳值型別,程式寫法如下:
extern crate rocket_contrib;
use std::collections::HashMap;
use rocket_contrib::templates::Template;
#[get("/")]
fn index() -> Template {
let context: HashMap<String, String> = HashMap::new();
Template::render("index", &context)
}
利用rocket_contrib::templates::Template
結構體提供的render
關聯函數,我們可以指定一個位於Rocket應用程式根目錄底下的templates
目錄中的HTML模板檔案的檔案相對路徑(不含副檔名),以及要用來進行模板填空或是流程控制的「上下文」(context)。這個「上下文」必須要可以被Rocket序列化(換句話說就是必須實作serde框架的Serialize
特性)。
當路由處理程序的函數是回傳rocket_contrib::templates::Template
時,HTTP回應中的標頭欄位除了之前提到的三個基本欄位外,還會多出Content-Type
欄位,且該欄位的值會被設定為text/html; charset=utf-8
。
快閃訊息(Flash Message)
快閃訊息是Rocket框架提供的一種利用Cookie來進行一次性訊息傳遞的機制。什麼時候我們會用到「一次性訊息」呢?例如會員的網頁後台系統,在會員登入前,只能讓他們看到登入表單而無法進入會員的網頁後台,登入表單必須要填入正確的帳號密碼才可以登入,如果登入失敗,就會顯示錯誤訊息;如果登入成功,就在會員後台顯示歡迎訊息。上述所提到的「顯示錯誤訊息」和「顯示歡迎訊息」,都屬於「一次性訊息」,需要在重新整理頁面後使訊息消失。
當我們想透過後端來傳送一次性訊息給前端時,就可以利用Rocket的快閃訊息機制,先將要顯示的一次性訊息從伺服器端設定進客戶端的Cookie中,當客戶端取得某個特定的網頁時,伺服器端就會去查看客戶端傳來的Cookie中是否有預期會有的一次性訊息,如果有的話就讀取出來,並順便把該Cookie記錄刪除掉。
例如以下程式,簡單模擬出了擁有快閃訊息機制的登入頁面和後台頁面:
#![feature(proc_macro_hygiene, decl_macro)]
#[macro_use]
extern crate rocket;
extern crate rocket_contrib;
use std::collections::HashMap;
use rocket::http::{Cookies, Cookie};
use rocket::request::{Form, FlashMessage};
use rocket::response::{Flash, Redirect};
use rocket_contrib::templates::Template;
#[get("/login")]
fn login(flash: Option<FlashMessage>) -> Template {
let mut context: HashMap<&str, &str> = HashMap::new();
if let Some(flash) = flash.as_ref() {
context.insert("invalid_message", flash.msg());
}
Template::render("login", &context)
}
#[derive(FromForm)]
struct LoginPostModel {
password: String
}
#[post("/login", data = "<model>")]
fn login_action(model: Form<LoginPostModel>, mut cookies: Cookies) -> Flash<Redirect> {
let pass = model.password.eq("1234");
if pass {
cookies.add_private(Cookie::new("login", "true"));
Flash::success(Redirect::to("/"), "Welcome!")
} else {
Flash::error(Redirect::to("/login"), "Invalid Password")
}
}
#[get("/")]
fn index(flash: Option<FlashMessage>, mut cookies: Cookies) -> Result<Template, Redirect> {
let has_auth = cookies.get_private("login").map(|cookie| cookie.value().eq("true")).unwrap_or(false);
drop(cookies);
if has_auth {
let mut context: HashMap<&str, &str> = HashMap::new();
if let Some(flash) = flash.as_ref() {
context.insert("welcome_message", flash.msg());
}
Ok(Template::render("index", &context))
} else {
Err(Redirect::to("/login"))
}
}
fn main() {
rocket::ignite().attach(Template::fairing()).mount("/", routes![login, login_action, index]).launch();
}
request::FlashMessage
結構體可以當作請求守衛來使用,而request::FlashMessage
結構實體的msg
方法可以用來取得訊息內容並且順便將其從Cookie中刪除(方法內部會使用到http::Cookies
來操作Cookie)。在先前的章節有提到,Rocket有限制http::Cookies
在同一個時間只能夠有一個實體,所以這個例子中,我們在程式第50行手動使用drop來消滅用不到的http::Cookies
實體,這樣才能確保request::FlashMessage
結構實體的msg
方法可以正常工作。
response::Flash
結構體可以作為路由處理程序的函數的回傳值型別,用來將訊息存入客戶端的Cookie中。要建立出response::Flash
結構實體,可以使用response::Flash
結構體的new
關聯函數,這個函數必須要手動替訊息設定一個「名稱」(name),這個名稱可以使用request::FlashMessage
結構實體的name
方法來取得。不管request::FlashMessage
結構實體是只使用到name
方法或是msg
方法,還是兩個都有用,訊息都會從Cookie中被刪除。如果不想自己設定訊息的名稱,可以使用response::Flash
結構體的success
、warning
或error
關聯函數來產生已套用了預設的名稱的訊息,預設的名稱即分別為success
、warning
或error
。
而以下是index
和login
這兩個HTML模板的內容,可以參考看看。
<html>
<head>
<meta charset=UTF-8>
<title>Admin</title>
</head>
<body>
{{#if welcome_message}}
<p>{{welcome_message}}</p>
{{/if}}
<p>Hello, old friend!</p>
</body>
</html>
<html>
<head>
<meta charset=UTF-8>
<title>Login</title>
</head>
<body>
<form action="/login" method="POST">
<input type="password" name="password" placeholder="Input your password">
<button type="submit">Login</button>
</form>
{{#if invalid_message}}
<p>{{invalid_message}}</p>
{{/if}}
</body>
</html>
總結
這個章節介紹了rocket_contrib
這個Rocket官方提供的擴充套件中,有關處理HTTP回應的功能,以及Rocket框架提供的快閃訊息機制。下一個章節要繼續使用rocket_contrib
,來讓我們的Rocket能夠搭配資料庫來使用!
下一章:資料庫的存取。