🦀
axumでファイルダウンロード処理
はじめに
プライベートでRustをコツコツ書いてます。
概要
axum(というかRust)でファイルダウンロード機能のサンプル等がググっても見つからなかったので、実装してみました。
簡単に機能だけざっくり説明すると、DBから取得したデータをJsonに変換し、jsonファイルとしてダウンロードさせるAPIです。
ライブラリ
axum
axumで適当にアプリ作ってるので、そのアプリに実装するため、使いました。
tokio_util
ストリームとして扱うのに、使用しました。
実装
処理
ざっくり下記になります。
- DBから取得したデータをres変数(戻り値はResult<Option<Vec<構造体>>>)に格納
- レスポンス用の構造体(Vec<JsonHistoriesDownload>)に変換
- レスポンス用の構造体をバイトベクターへシリアライズ
- Cursorにラップし、AsyncRead扱いにする
- ReadStreamに変換し、非同期のストリームにする
- レスポンスボディに設定
- ヘッダーにcontent-typeとdispositionを設定
- ヘッダーとボディをレスポンス
JSONファイルダウンロード
ストリームにするのに、tokio-util
が必要で、さらにDBから取得したデータをストリームに読み込ますのに、バイト列にする必要があり、Cursor
を使いました。
※細かいエラーハンドリング等はプライベート開発なので、適当してます。
use tokio_util::io::ReaderStream;
pub async fn download_histories(
Extension(modules): Extension<Arc<Modules>>,
Path(id): Path<String>,
) -> Result<impl IntoResponse, StatusCode> {
let res = modules.bank_manager_use_case().download_histories(id).await; // DBからデータ取得
match res {
Ok(dl_histories) => dl_histories
.map(|data| {
let json: Vec<JsonHistoriesDownload> = data.into_iter().map(|d| d.into()).collect();
let json_data = serde_json::to_vec(&json).expect("error");
let cursor = Cursor::new(json_data);
let stream = ReaderStream::new(cursor);
let body = Body::from_stream(stream);
let mut headers = HeaderMap::new();
headers.insert(header::CONTENT_TYPE, HeaderValue::from_static("application/json"));
headers.insert(
header::CONTENT_DISPOSITION,
HeaderValue::from_static("attachment; filename=\"deposit_histories.json\""),
);
(headers, body).into_response()
})
.ok_or_else(|| StatusCode::NOT_FOUND),
Err(_) => {
Err(StatusCode::INTERNAL_SERVER_ERROR)
}
まとめ
tokio-util
が必要だったりと、完成するまで調査が必要でしたが、いろいろ勉強になりました。
もし、ファイルダウンロード実装したいときは、ご参考までに!
Discussion