Rocket框架提供了維持狀態(State)的機制,將狀態分為「應用程式狀態」和「請求狀態」兩種。前者可以將任何型別當作狀態來使用,而且可以自由、便利地使這些狀態「載入」到任意路由處理程序中;而後者則是因一個HTTP請求可能會由多個路由處理程序嘗試匹配而誕生出來的快取機制。
應用程式狀態
應用程式狀態的有效範圍是整個Rocket應用程式。使用方式很簡單,只需將想要作為狀態使用的實體(可以是任意型別),全都透過Rocket
結構實體的manage
方法來註冊給Rocket使用就好了。
例如:
use std::sync::Mutex;
struct Config {
domain: String,
}
struct HitCount {
count: usize,
}
let config = Config { domain: "magiclen.org".to_string() };
let hit_count = Mutex::new(HitCount { count: 0 });
rocket::build().rocket.manage(config).manage(hit_count)
對於會在Rocket應用程式運行時被改變的狀態,我們可以用Mutex
結構體來包裹它,使其可以在多個執行緒下被安全存取。
當我們需要在路由處理程序中讀取這些狀態時,可以在路由處理程序的函數中,加上型別為&State<T>
的參數,T
即為我們註冊給Rocket當狀態使用的實體的型別。
例如:
#[macro_use]
extern crate rocket;
use std::sync::Mutex;
use rocket::State;
#[derive(Debug)]
struct Config {
domain: String,
}
struct HitCount {
count: usize,
}
#[get("/hit")]
fn hit(config: &State<Config>, hit_count: &State<Mutex<HitCount>>) -> String {
let s = format!("{:?}", config);
let mut hit_count = hit_count.lock().unwrap();
hit_count.count += 1;
s
}
#[get("/hit-count")]
fn hit_count(hit_count: &State<Mutex<HitCount>>) -> String {
format!("{}", hit_count.lock().unwrap().count)
}
#[launch]
fn rocket() -> _ {
let config = Config {
domain: "magiclen.org".to_string()
};
let hit_count = Mutex::new(HitCount {
count: 0
});
rocket::build().manage(config).manage(hit_count).mount("/", routes![hit, hit_count])
}
原子(Atomic)狀態
對於基本資料型別(如i8
、u32
、f64
、bool
等),我們可以利用Rust標準函式庫內建的std::sync::atomic
模組所提供的原子結構體。這些結構體包含所有基本資料型別的原子(Atomic)版本,例如AtomicI8
、AtomicU32
、AtomicF64
、AtomicBool
等。
不過由於Rocket框架是以資料型別來區分應用程式狀態,因此若要使用原子結構體,最好另外自訂一個型別將其包裹起來,以免後續無法再使用相同的原子結構體。
承前一個小節的程式範例,可以用原子結構體改寫如下:
#[macro_use]
extern crate rocket;
use std::sync::atomic::{AtomicU64, Ordering};
use rocket::State;
#[derive(Debug)]
struct Config {
domain: String,
}
struct HitCount {
count: AtomicU64,
}
#[get("/hit")]
fn hit(config: &State<Config>, hit_count: &State<HitCount>) -> String {
let s = format!("{:?}", config);
hit_count.count.fetch_add(1, Ordering::Relaxed);
s
}
#[get("/hit-count")]
fn hit_count(hit_count: &State<HitCount>) -> String {
format!("{}", hit_count.count.load(Ordering::Relaxed))
}
#[launch]
fn rocket() -> _ {
let config = Config {
domain: "magiclen.org".to_string()
};
let hit_count = HitCount {
count: AtomicU64::new(0)
};
rocket::build().manage(config).manage(hit_count).mount("/", routes![hit, hit_count])
}
在臨界區段(Critical Section)的資源數量未滿4個的情況下,使用原子結構體來代替Mutex
結構體可以使程式效能更好!
請求狀態
請求狀態的有效範圍是一個HTTP請求。由於Rocket框架使用請求守衛來進行路由處理程序的匹配,因此為了避免同一個請求守衛在同一個HTTP請求中被匹配多次,我們可以使用請求的快取機制來暫存請求狀態。
例如:
#[macro_use]
extern crate rocket;
use rocket::request::{FromRequest, Outcome, Request};
struct MyGuard<'a> {
authorization: Option<&'a str>,
}
#[rocket::async_trait]
impl<'r> FromRequest<'r> for MyGuard<'r> {
type Error = ();
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
let authorization = request.headers().get("authorization").next();
info!("Create a new MyGuard instance.");
Outcome::Success(MyGuard {
authorization,
})
}
}
#[get("/auth?<id>", rank = 1)]
fn handler_1(guard: MyGuard, id: i32) -> String {
format!("ID: {}, Auth: {}", id, guard.authorization.unwrap_or(""))
}
#[get("/auth?<token>", rank = 2)]
fn handler_2(guard: MyGuard, token: String) -> String {
format!("Token: {}, Auth: {}", token, guard.authorization.unwrap_or(""))
}
#[launch]
fn rocket() -> _ {
rocket::build().mount("/", routes![handler_1, handler_2])
}
有關於info!
巨集的用法可以參考這篇文章:
以上程式在對一個HTTP請求尋找可匹配的路由處理程序時,MyGuard
這個請求守衛的from_request
關聯函數可能會被執行多次,而建立出好幾個MyGuard
結構實體。若要解決這個問題,可以用請求狀態將以上程式改寫成:
#[macro_use]
extern crate rocket;
use rocket::request::{FromRequest, Outcome, Request};
struct MyGuard {
authorization: Option<String>,
}
#[rocket::async_trait]
impl<'r> FromRequest<'r> for &'r MyGuard {
type Error = ();
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
Outcome::Success(request.local_cache(|| {
let authorization = request.headers().get("authorization").next();
info!("Create a new MyGuard instance.");
MyGuard {
authorization: authorization.map(|s| s.to_string())
}
}))
}
}
#[get("/auth?<id>", rank = 1)]
fn handler_1(guard: &MyGuard, id: i32) -> String {
format!("ID: {}, Auth: {}", id, guard.authorization.as_deref().unwrap_or(""))
}
#[get("/auth?<token>", rank = 2)]
fn handler_2(guard: &MyGuard, token: String) -> String {
format!("Token: {}, Auth: {}", token, guard.authorization.as_deref().unwrap_or(""))
}
#[launch]
fn rocket() -> _ {
rocket::build().mount("/", routes![handler_1, handler_2])
}
雖然這個作法可以使傳入Request
結構實體的request
方法的閉包不被多次執行,但這也會使得from_request
函數回傳的資料型別不能使用Rocket的生命周期參數(因為傳入local_cache
方法的閉包必須回傳實體,或是生命周期為'static
的參考),各有利弊。
總結
「應用程式狀態」和「請求狀態」分別針對全Rocket應用程式以及單一的HTTP請求提供額外的資料暫存功能。而「應用程式狀態」其實還有更為強大的應用方式,那就是可以用來儲存Rocket應用程式所用到的資料庫的實體!有關於Rocket框架與資料庫的搭配用法,將會在之後的章節做介紹。
下一章:中介軟體(Middleware)與整流片(Fairing)。