先前的文章中,有介紹過用Rust程式語言搭配MongoDB來儲存檔案的方式,那麼要如何將其應用到Rocket框架中,使Rocket框架能夠回應檔案中心儲存的資料呢?



File Center Raw Response on MongoDB for Rocket Framework

「File Center Raw Response on MongoDB for Rocket Framework」是筆者開發的套件,可以讓「Mongo File Center」套件融入Rocket框架中,能直接從檔案中心回應檔案資料,並且支援HTTP的ETag機制。

Crates.io

Cargo.toml

rocket-mongo-file-center-raw-response = "*"

使用方法

先將mongo_file_center這個crate下的FileCenter結構實體註冊至Rocket框架的應用程式狀態中。在路由處理程序中,可以利用State<FileCenter>inner方法取得FileCenter結構實體的參考。

rocket_mongo_file_center_raw_response這個crate提供了FileCenterRawResponse結構體和EtagIfNoneMatch結構體(來自rocket_etag_if_none_match這個crate,請參考這篇文章來了解EtagIfNoneMatch結構體)。

FileCenterRawResponse結構體有三個關聯函數可以用來產生FileCenterRawResponse結構體的實體。

首先介紹最常用的from_id_token函數。呼叫from_id_token函數時,要從參數傳入FileCenter結構實體的參考、EtagIfNoneMatch結構實體的參考、檔案中心加密過的Object ID(ID token)和檔案名稱(如果不設定則使用檔案中心儲存的檔案名稱)。

以下是一個完整的例子:

#[macro_use]
extern crate rocket;

use std::error::Error;
use std::path::Path;

use rocket_mongo_file_center_raw_response::mongo_file_center::{mime, FileCenter};
use rocket_mongo_file_center_raw_response::{EtagIfNoneMatch, FileCenterRawResponse};

use rocket::http::Status;
use rocket::State;

const URL: &str = "mongodb://localhost:27017";

#[get("/<id_token>")]
async fn view(
    etag_if_none_match: &EtagIfNoneMatch<'_>,
    file_center: &State<FileCenter>,
    id_token: String,
) -> Result<Option<FileCenterRawResponse>, Status> {
    FileCenterRawResponse::from_id_token(
        file_center.inner(),
        etag_if_none_match,
        id_token,
        None::<String>,
    )
    .await
    .map_err(|_| Status::InternalServerError)
}


#[rocket::main]
async fn main() -> Result<(), Box<dyn Error>> {
    let file_center = FileCenter::new(URL).await?;

    let path = Path::new("examples").join("images").join("image(貓).jpg");

    let file_id =
        file_center.put_file_by_path(path, None::<String>, Some(mime::IMAGE_JPEG)).await?;

    let id_token = file_center.encrypt_id(file_id);

    println!("The ID token is: {}", id_token);

    println!();

    rocket::build().manage(file_center).mount("/", routes![view]).launch().await?;

    Ok(())
}

程式一開始,會將工作目錄下的image.jpg這張JPEG圖檔儲存到檔案中心中,然後在螢幕上顯示出這筆加密過的Object ID(也就是「ID token」)。接著將檔案中心註冊給Rocket,並啟動Rocket。在view這個路由處理程序中,會讀取網址路徑中帶的ID token。(ShortCryptUrlComponent結構體來自於validators這個crate,有關於validators的介紹可以參考這篇文章。)from_id_token會根據這個ID token來查詢檔案中心裡對應的檔案,並回傳Result<Option<FileCenterRawResponse>, FileCenterError>,注意因為這邊的FileCenterError並沒有實作rocket::response::Responder特性,所以如果直接拿來回傳的話,等同於發生500錯誤。

使用from_id_token關聯函數來建立FileCenterRawResponse結構實體會強制使用ETag機制,ETag值即為ID token。如果是用from_object_id關聯函數來建立FileCenterRawResponse結構實體,若要使用ETag機制,就要自行去計算ETag值並透過參數傳給from_object_id關聯函數來使用。例如把以上的view路由處理程序用from_object_id關聯函數改寫的話就會變成以下這樣:

#[get("/<id_token>")]
async fn view(
    etag_if_none_match: &EtagIfNoneMatch<'_>,
    file_center: &State<FileCenter>,
    id_token: String,
) -> Result<Option<FileCenterRawResponse>, Status> {
    let object_id =
        file_center.decrypt_id_token(&id_token).map_err(|_| Status::InternalServerError)?;

    let etag = FileCenterRawResponse::create_etag_by_id_token(id_token);

    FileCenterRawResponse::from_object_id(
        file_center.inner(),
        Some(etag_if_none_match),
        Some(etag),
        object_id,
        None::<String>,
    )
    .await
    .map_err(|_| Status::InternalServerError)
}

以上程式中,FileCenterRawResponse結構體的create_etag_by_id_token關聯函數可以拿來將ID token轉成rocket_etag_if_none_match::EntityTag結構實體。

如果已經從檔案中心拿到FileItem了,可以使用FileCenterRawResponse結構體的from_file_item關聯函數來產生FileCenterRawResponse結構實體,如下:

#[get("/<id_token>")]
async fn view(
    etag_if_none_match: &EtagIfNoneMatch<'_>,
    file_center: &State<FileCenter>,
    id_token: String,
) -> Result<Option<FileCenterRawResponse>, Status> {
    let object_id =
        file_center.decrypt_id_token(&id_token).map_err(|_| Status::InternalServerError)?;

    let etag = FileCenterRawResponse::create_etag_by_id_token(id_token);

    if etag_if_none_match.weak_eq(&etag) {
        Err(Status::NotModified)
    } else {
        let file_item = file_center
            .get_file_item_by_id(object_id)
            .await
            .map_err(|_| Status::InternalServerError)?;

        match file_item {
            Some(file_item) => {
                Ok(Some(FileCenterRawResponse::from_file_item(
                    Some(etag),
                    file_item,
                    None::<String>,
                )))
            }
            None => Ok(None),
        }
    }
}

from_file_item關聯函數不能幫忙判斷HTTP請求中的If-None-Match欄位,因為這個動作需在呼叫from_file_item關聯函數之前就完成才比較有意義(如果判斷結果是304,就不需要再浪費時間查詢資料庫)。因此以上程式是在路由處理程序中另外撰寫程式碼來判斷ETag。