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當狀態使用的實體的型別。
例如:
extern crate rocket; | |
use std::sync::Mutex; | |
use rocket::State; | |
struct Config { | |
domain: String, | |
} | |
struct HitCount { | |
count: usize, | |
} | |
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 | |
} | |
fn hit_count(hit_count: &State<Mutex<HitCount>>) -> String { | |
format!("{}", hit_count.lock().unwrap().count) | |
} | |
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框架是以資料型別來區分應用程式狀態,因此若要使用原子結構體,最好另外自訂一個型別將其包裹起來,以免後續無法再使用相同的原子結構體。
承前一個小節的程式範例,可以用原子結構體改寫如下:
extern crate rocket; | |
use std::sync::atomic::{AtomicU64, Ordering}; | |
use rocket::State; | |
struct Config { | |
domain: String, | |
} | |
struct HitCount { | |
count: AtomicU64, | |
} | |
fn hit(config: &State<Config>, hit_count: &State<HitCount>) -> String { | |
let s = format!("{:?}", config); | |
hit_count.count.fetch_add(1, Ordering::Relaxed); | |
s | |
} | |
fn hit_count(hit_count: &State<HitCount>) -> String { | |
format!("{}", hit_count.count.load(Ordering::Relaxed)) | |
} | |
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請求中被匹配多次,我們可以使用請求的快取機制來暫存請求狀態。
例如:
extern crate rocket; | |
use rocket::request::{FromRequest, Outcome, Request}; | |
struct MyGuard<'a> { | |
authorization: Option<&'a str>, | |
} | |
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, | |
}) | |
} | |
} | |
fn handler_1(guard: MyGuard, id: i32) -> String { | |
format!("ID: {}, Auth: {}", id, guard.authorization.unwrap_or("")) | |
} | |
fn handler_2(guard: MyGuard, token: String) -> String { | |
format!("Token: {}, Auth: {}", token, guard.authorization.unwrap_or("")) | |
} | |
fn rocket() -> _ { | |
rocket::build().mount("/", routes![handler_1, handler_2]) | |
} |
有關於info!
巨集的用法可以參考這篇文章:
以上程式在對一個HTTP請求尋找可匹配的路由處理程序時,MyGuard
這個請求守衛的from_request
關聯函數可能會被執行多次,而建立出好幾個MyGuard
結構實體。若要解決這個問題,可以用請求狀態將以上程式改寫成:
extern crate rocket; | |
use rocket::request::{FromRequest, Outcome, Request}; | |
struct MyGuard { | |
authorization: Option<String>, | |
} | |
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()) | |
} | |
})) | |
} | |
} | |
fn handler_1(guard: &MyGuard, id: i32) -> String { | |
format!("ID: {}, Auth: {}", id, guard.authorization.as_deref().unwrap_or("")) | |
} | |
fn handler_2(guard: &MyGuard, token: String) -> String { | |
format!("Token: {}, Auth: {}", token, guard.authorization.as_deref().unwrap_or("")) | |
} | |
fn rocket() -> _ { | |
rocket::build().mount("/", routes![handler_1, handler_2]) | |
} |
雖然這個作法可以使傳入Request
結構實體的request
方法的閉包不被多次執行,但這也會使得from_request
函數回傳的資料型別不能使用Rocket的生命周期參數(因為傳入local_cache
方法的閉包必須回傳實體,或是生命周期為'static
的參考),各有利弊。
總結
「應用程式狀態」和「請求狀態」分別針對全Rocket應用程式以及單一的HTTP請求提供額外的資料暫存功能。而「應用程式狀態」其實還有更為強大的應用方式,那就是可以用來儲存Rocket應用程式所用到的資料庫的實體!有關於Rocket框架與資料庫的搭配用法,將會在之後的章節做介紹。
下一章:中介軟體(Middleware)與整流片(Fairing)。