在一個 Web 應用程式中,傳送非純文字資料,如:圖片、檔案等是很常見的操作,但是在一個以 JSON 為導向的 API 設計中,我們會希望可以透過 JSON 來傳送這些檔案,在這篇文章會有如何將文件轉成 base64 上傳並在後端重新轉換回文件並寫入資料:

前置動作

安裝依賴套件

# Cargo.toml
# 這個相依負責 Encode 或 Decode base64 編碼的資料
[dependencies.base64]
version="0.12.3"

# 這個相依負責將 vec[u8] 等資料寫入文件中
[dependencies.file]
version="1.1.2"

# 這個文件負責產生隨機字串用於檔案名稱
[dependencies.rand]
version = "0.7.3"

# 用於檢查送上來的 Base64 image 的格式
[dependencies.regex]
version = "1"

範例

因為後端接收的是 Json 資料,如果還不知道怎麼在 Rust 中處理 Json 可以先參照:Rocket.rs 初探 - Json Request 和 Json Response 這篇文章,裡面有範例。

extern crate file;

use base64::{encode, decode};
use rand::{self, Rng};
use rand::distributions::Alphanumeric;
use regex::Regex;

#[derive(Debug, Serialize, Deserialize)]
struct UploadedImage {
    // 用於儲存 base64 編碼的檔案
    data: String
}

#[post("/images", format = "json", data="<image>")]
fn upload_image(image: Json<UploadedImage>) -> String {
    
    // 檢查格式
    let re = Regex::new(r"^data:image/jpeg;base64,[a-zA-Z0-9+/=]+$").unwrap();

    if !re.is_match(image.data.as_str()) {
        return String::from("File format invalid");
    }
    
	// 因為 base64 格式的會以 data:[MIME_TYPE];base64,來作為開頭
    // 但是前面是必要資料,其中包含檔案的 MIME_TYPE 
    // 所以我們把 ;base64, 作為切分資料的依據
    // 此外也可以僅用 , 作為切分依據,
    // 因為 base64 的字元集僅包含:26個大小寫字母、10個數字,
    // 以及加號(+)、斜槓(/) 還有等號(=)
    let image_data = image.data.split(";base64,");
    let vec: Vec<&str> = image_data.collect();
    
    // 這邊第 2 個 element 就會是 base64 的資料
    let image_bytes = base64::decode(vec[1]).unwrap();
    
    // 取得當前位置
    let mut filename = String::from(std::env::current_dir().unwrap().into_os_string().into_string().unwrap());
    // 上傳的資料夾
    filename.push_str("/images/");
    
    // 文件名稱
    let file_key = rand::thread_rng()
        .sample_iter(&Alphanumeric)
        .take(10)
        .collect::<String>();

    filename.push_str(file_key.as_str());
    filename.push_str(".jpg");
    
    // 寫入指定檔案,寫入至<CURRENT_DIR>/images/<RANDOM_STRING>.jpg
    file::put(filename, &image_bytes);
    return String::from("Upload successful");
}

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

接著你可以試著向/images發起POST請求:

{
    "data": "data:image/jpg;base64,..."
}

這樣就可以上傳到指定current_dir底下的images了。