在先前的文章中有提到用Rocket框架回應任意資料的方式,在另一篇文章中也有介紹用Rocket框架來實現HTTP的ETag快取機制,要怎麼樣把這兩個東西結合在一起使用呢?



Etagged Raw Response for Rocket Framework

「Etagged Raw Response for Rocket Framework」是筆者開發的套件,可以使Rocket框架依據HTTP請求的If-None-Match標頭欄位來判斷是否要直接回應一個Vec<u8>實體、一個Reader或是一個檔案,並且快速地替其加入Content-Type標頭和ETag欄位。

Crates.io

https://crates.io/crates/rocket-etagged-raw-response

Cargo.toml

rocket-etagged-raw-response = "*"

使用方法

rocket_etagged_raw_response這個crate提供了EtaggedRawResponse結構體,可以用來作為要回應的資料型別。EtaggedRawResponse結構體的用法有點類似rocket_raw_response這個crate提供的RawResponse結構體,以下來介紹它們不同的地方。

首先在使用EtaggedRawResponse結構體前,必須要產生其專屬的整流片,並註冊給Rocket框架來用,因為這個套件會把ETag值利用Rocket的應用程式狀態快取下來。最基本的註冊整流片的方式如下:

let rocket = rocket::ignite().attach(EtaggedRawResponse::fairing());

EtaggedRawResponse結構體提供的fairing關聯函數,可以回傳一個預設的整流片。這個整流片可以在Rocket應用程式狀態中建立一個大小為64(筆)的快取空間。快取要用來做什麼?若EtaggedRawResponse結構實體是用from_vec關聯函數來產生的話,要透過參數設定一個型別為字串的鍵值(key),用來辨認是哪個Vec<u8>實體的資料。EtaggedRawResponse結構實體在送出回應的階段,會去計算資料的CRC64-ECMA校驗值,並將其轉成16進制的字串再組合成ETag值。這個ETag值就會跟著鍵值一起被存入快取中,當下次要傳送同樣為這個鍵值時的資料時,EtaggedRawResponse結構實體就會去判斷HTTP請求中的If-None-Match標頭欄位值和快取中鍵值所對應到的ETag值是否能weak_eq,如果可以的話就直接回傳304(Not Modified)這個HTTP狀態碼;如果不行的話就回傳完整的HTTP回應。快取是採用LRU(Least Recently Used)演算法來決定當快取存滿時,要先刪除哪筆快取再存入新的快取。

EtaggedRawResponse結構實體是用from_file關聯函數來產生的話,則會直接使用檔案路徑當作是快取的鍵值(不過這個鍵值會和from_vec傳入的鍵值分開儲存,不會有重疊覆蓋的問題)。EtaggedRawResponse結構實體在送出回應的階段,會去計算檔案內容的CRC64-ECMA校驗值,並將其轉成16進制的字串再組合成ETag值。這個ETag值和檔案的修改日期就會跟著鍵值一起被存入快取中,之後的行為都和from_vec關聯函數產生出來的EtaggedRawResponse一樣。只不過,即便檔案的ETag值還正在被快取,EtaggedRawResponse結構實體還是會先去檢查檔案是否有被修改(依照檔案的修改日期),來決定是否需要重新計算ETag值來取代掉快取中的ETag值。不像用from_vec關聯函數來產生的EtaggedRawResponse結構實體,同一個鍵值只會在需要重新快取的時候才會去計算資料的ETag值。

EtaggedRawResponse結構實體是用from_reader關聯函數來產生的話,就要直接在參數中提供這筆資料的ETag值來進行ETag機制,而這個ETag值就不會進到快取中啦!

舉個例子,假設工作目錄中有image.jpg這張JPEG圖檔。若想要讓Rocket框架提供這個圖檔,並且讓網頁瀏覽器直接瀏覽這個圖檔,而不是將其下載到檔案系統,然後又要支援ETag機制的話,程式可以這樣寫:

#![feature(proc_macro_hygiene, decl_macro)]

#[macro_use]
extern crate rocket;

extern crate rocket_etagged_raw_response;

use std::path::Path;

use rocket_etagged_raw_response::EtaggedRawResponse;

#[get("/")]
fn view() -> EtaggedRawResponse {
    let path = Path::new("image(貓).jpg");

    EtaggedRawResponse::from_file(path, None::<String>, None)
}

fn main() {
    rocket::ignite().attach(EtaggedRawResponse::fairing()).mount("/", routes![view]).launch();
}

如果覺得預設的快取空間太小的話,可以使用EtaggedRawResponse結構體的fairing_cache關聯函數來產生整流片,這個fairing_cache關聯函數要傳入一個閉包,閉包可以直接回傳要在應用程式狀態中建立的快取空間太小。

例如:

let rocket = rocket::ignite().attach(EtaggedRawResponse::fairing_cache(|| 512));