FastApi下載檔案

米洛丶發表於2021-11-12

FastApi下載檔案

記得之前我們講過生成excel檔案的事情,那麼如何把伺服器生成的excel檔案正確傳送給使用者呢?

今天我們就來說說在FastApi中如何正確讓使用者下載到想要的檔案。

基本流程

其實檔案下載的場景還是挺多的,比如我想要拿到我這個使用者最近10天建立的測試用例資料,那麼我們服務端應該怎麼做呢?

  1. 根據條件篩選出正確的資料
  2. 處理資料,生成對應的目標格式檔案,比如csv,xlsx等等
  3. 返回http響應,其中指定response的內容和型別

溫馨提示

現在假設我們已經完成了之前的步驟,並且生成了一個臨時檔案

需要注意的是,臨時檔案的名字為了確保唯一性,最好是用時間戳+隨機字串,或者懶一點可以用uuid

這裡為了方便,我就編寫一個簡便的方法:

import time
import random

random_str = list("abcdefgh")
random.shuffle(random_str)
filename = f"{time.time_ns()}_{''.join(random_str)}"

取當前時間戳(精確到納秒),這時候還是有可能會有同一請求發生,所以我們再用random.shuffle對我們想要加的字串進行隨機排序(打亂順序)。

這樣一來,檔案重名的概率就小了非常多,如果要嚴謹的話,可以把字串放長點,但是檔名也會拖很長。

記得一定要儲存這個隨機的檔名,並且加上檔案字尾哈~!

FastApi怎麼做呢

其實檔案也是HTTP的響應之一,只不過它相對特殊。

在FastApi中,響應有Response和FileResponse等多種,我們暫時看Response和FileResponse即可。

Response是我們常見的型別,當然fastapi比較友好,你如果return 一個字典,會預設將之轉換為JSON Response。

但當你要設定返回的http狀態碼,那就需要去操作這個Response物件了。

我們常見的比如403 forbidden,401未認證,都可以用Response來實現。


那麼對於FileResponse,我們怎麼用呢?

其實用法比較簡單,我們來看實戰:

from fastapi import FastAPI
from starlette.responses import FileResponse


app = FastAPI(name="monitor")

@app.get("/download")
async def download():
    # 處理完畢檔案以後,生成了檔案路徑
    filename = "你要下載的檔案路徑.xls"
    return FileResponse(
            filename,  # 這裡的檔名是你要傳送的檔名
            filename="lol.exe", # 這裡的檔名是你要給使用者展示的下載的檔名,比如我這裡叫lol.exe
        )

這樣,前端頁面提供一個a標籤,href地址填對應的介面地址就好了。

<!DOCTYPE html>
<html>
<head>
	<title>測試</title>
</head>
<body>
  <!-- 這個地址用你的host:port加上介面地址-->
	<a href="http://localhost:7777/download">下載檔案</a>
</body>
</html>

等等 好像少了點啥

我們這個生成的檔案雖然說都是隨機的,沒啥影響。但是如果一直有人生成,那不刪除真的大丈夫嗎?

所以我們得考慮下怎麼刪除檔案~

  • 理所當然認為try finally

    答案是行不通的,因為finally的內容會在return之前進行。如果這時候你刪除了檔案,那麼Response就返回不了檔案了,會報錯。

    還好我們FastApi原生提供了background功能,幕後工作人員會在執行完畢之後進行一些暗箱操作

    所以我們可以這麼改動:

from starlette.background import BackgroundTask

        return FileResponse(
            filename,
            filename="application.xls",
            background=BackgroundTask(lambda: os.remove(filename)),
        )

使用background接受一個引數BackgroundTask,裡面引數是一個無參方法:

lambda: os.remove(filename)

也就是刪除這個檔案的方法。

最後

flask的相關檔案下載可以看博主幾年前寫的文章,原理都通用。

https://www.cnblogs.com/we8fans/p/7107353.html