我需要使用 FastAPI 下载存储在 MongoDB(而不是文件系统)中的文件。 我为此使用 FastAPI Response,因为只有文件位于文件系统中时才能使用 FileResponse。下载可以,但是下载时找不到指定文件名的方法。
可以下载,但文件名默认为response_SOME-NUMBER.txt(来自Swagger)。
这是代码。
@app.get("/files/{file_name}", responses = {200: {"content": {"text/html": {}}}}, response_class = Response)
def download_file(file_name: str):
with MongoClient() as client:
files_collection = client[DB][FILES_COLLECTION]
result = files_collection.find_one({"file_name": file_name})
content = result["file_data"]
if result is not None:
return Response(content=content, media_type="text/html")
else:
raise HTTPException(status_code=404, detail="File not found")
我想我可以在标题中添加名称以供 UI 检索,如下所示:
headers = {'Content-Disposition': f'attachment; filename="{download_file_name}"'}
return Response(content=content, headers=headers, media_type="text/html")
但是我想知道是否有一种方法可以像 FileResponse 一样指定名称。
正如之前在以下答案中所描述的:
当返回
Response
实例时,可以设置 Content-Disposition
响应标头,指示文件是否应该在浏览器/网页中 displayed (使用 inline
作为标头中第一个参数的值,这是默认值)或 downloaded(使用 attachment
),以及在文件下载到客户端设备时指定 filename
(可选)。
注意“
filename
后面的字符串应始终放入引号中;但是,出于兼容性原因,许多浏览器尝试解析包含空格的未加引号的名称”(如果filename
包含unicode字符,则应使用filename*
参数并对文件名值进行编码 - 请参阅这个答案)。
from FastAPI import Response
...
# use this, if you would like to have the file displayed inside the browser instead
# headers = {'Content-Disposition': 'inline; filename="out.pdf"'}
headers = {'Content-Disposition': 'attachment; filename="out.pdf"'}
return Response(content=content, headers=headers, media_type='application/pdf')
FileResponse
(继承自 Response
,与 FastAPI/Starlette 中的所有其他响应类一样)并设置 filename
参数时,FastAPI/Starlette 实际上在幕后设置 Content-Disposition
为您提供标题。这个可以看FileResponse
这里的实现类:
class FileResponse(Response):
chunk_size = 64 * 1024
def __init__(
self,
...
headers: typing.Mapping[str, str] | None = None,
filename: str | None = None,
content_disposition_type: str = "attachment",
) -> None:
...
self.filename = filename
...
self.init_headers(headers)
if self.filename is not None:
content_disposition_filename = quote(self.filename)
if content_disposition_filename != self.filename:
content_disposition = "{}; filename*=utf-8''{}".format(
content_disposition_type, content_disposition_filename
)
else:
content_disposition = '{}; filename="{}"'.format(
content_disposition_type, self.filename
)
self.headers.setdefault("content-disposition", content_disposition)
如前所述,如上面
FileResponse
的实现所示,Starlette 会检查您传递的 filename
值是否包含非 ascii/unicode 字符,如果是,它将使用 filename*
参数来代替发送编码值。
因此,在您的情况下,您可以自己设置
Content-Disposition
标头(这可能是最合适的解决方案),如前面的示例所示,或者实现您自己的自定义响应类,继承自 Starlette 的 Response
,它将接受字节 content
和用于设置标头的 filename
参数,如上面的 FileResponse
所示。