【问题标题】:How to download a large file from server via FastAPI?如何通过 FastAPI 从服务器下载大文件?
【发布时间】:2022-09-25 16:59:23
【问题描述】:

我试图请求从服务器接收一个大文件/tar.gz 文件,在服务器的代码中我只是验证文件路径,然后使用 Starlette.FileResponse 返回整个文件——就像我已经在这里搜索了很多问题。

服务器:

return FileResponse(path=file_name, media_type=\'application/octet-stream\', filename=file_name)

之后它在 pydantic 中发现错误:

  File \"/usr/local/lib/python3.10/dist-packages/fastapi/routing.py\", line 149, in serialize_response
    return jsonable_encoder(response_content)
  File \"/usr/local/lib/python3.10/dist-packages/fastapi/encoders.py\", line 130, in jsonable_encoder
    return ENCODERS_BY_TYPE[type(obj)](obj)
  File \"pydantic/json.py\", line 52, in pydantic.json.lambda
UnicodeDecodeError: \'utf-8\' codec can\'t decode byte 0x8b in position 1: invalid start byte

我也尝试使用 StreamResponse 但出现了一些错误。还有其他方法吗?

我的代码中的流式响应:

@x.post(\"/download\")
async def download(file_name=Body(), token: str | None = Header(default=None)):
    file_name = file_name[\"file_name\"]
    # should be something like xx.tar
    def iterfile():
        with open(file_name,\"rb\") as f:
            yield from f
    return StreamingResponse(iterfile(),media_type=\'application/octet-stream\')

好的,这里是这个问题的更新。 我发现这个api上没有发生错误,但是这个api正在做转发请求。

@(\"/\")
def f():
    req = requests.post(url =\"/download\")
    return req.content

在这里,如果我返回一个带有 tar 文件的流响应,它会导致(可能)编码问题。

使用其他请求时,请记住设置相同的媒体类型。这里是 media_type=\'application/octet-stream\'。它有效!

  • 这回答了你的问题了吗? How to make a large file accessible to external APIs?
  • 我检查了这个答案并使用了 StreamingResponse。由于文件类型不同,我没有设置具体的 media_type。代码就像return StreamingResponse(iterfile()) 我仍然得到错误:No json object could be decoded 下载 tar 文件时
  • 您是否尝试为 StreamingResponse 设置 media_type=\'application/octet-stream\' 以表明它是二进制数据?你有失败的示例代码吗?
  • 这只是我放入数据主体的内容。实际名称是绝对文件路径,如 /opt/123.tar。我尝试了其他一些文件,如 syslog 或 json 文件,它们都有效。
  • yield from f 中,我发现这可能会占用大量 CPU。我该如何解决?可能是因为chunk size小导致文件操作量大?我可以在这里增加块大小吗?

标签: python fastapi pydantic starlette


【解决方案1】:

如果您发现yield from fusing StreamingResponse with file-like objects 时相当慢,您可以创建一个生成器,在其中使用指定的块大小以块的形式读取文件;因此,加快了进程。可以在下面找到示例。

请注意,StreamingResponse 可以采用 async 生成器或普通生成器/迭代器来流式传输响应正文。如果您使用不支持async/await 的标准open() 方法,则必须使用普通def 声明生成器函数。无论如何,FastAPI/Starlette 仍将异步工作,因为它会检查您传递的生成器是否是异步的(如 source code 所示),如果不是,它将在单独的线程中运行生成器,使用 @987654323 @,然后等待。

您可以在响应中设置Content-Disposition 标头(如this answer 中所述,以及herehere)以指示内容是否应在浏览器中显示inline(如果您是流,例如,.mp4 视频、.mp3 音频文件等),或作为attachment 下载并保存在本地(使用指定的filename)。

至于media_type(也称为MIME 类型),有两种主要的MIME 类型(参见Common MIME types):

  • text/plain 是文本文件的默认值。文本文件应该是人类可读的,并且不能包含二进制数据。
  • application/octet-stream 是所有其他情况的默认值。一个未知文件类型应使用此类型.

对于带有.tar 扩展名的文件,如您的问题所示,您还可以使用不同的亚型来自octet-stream,即x-tar。有关常见 MIME 类型的列表,请参阅上面的链接文档。

选项 1 - 使用普通发电机

from fastapi import FastAPI
from fastapi.responses import StreamingResponse

CHUNK_SIZE = 1024 * 1024  # = 1MB - adjust the chunk size as desired
some_file_path = 'large_file.tar'
app = FastAPI()

@app.get('/')
def main():
    def iterfile():
        with open(some_file_path, 'rb') as f:
            while chunk := f.read(CHUNK_SIZE):
                yield chunk

    headers = {'Content-Disposition': 'attachment; filename="large_file.tar"'}
    return StreamingResponse(iterfile(), headers=headers, media_type='application/x-tar')

选项 2 - 使用 async 生成器和 aiofiles

from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import aiofiles

CHUNK_SIZE = 1024 * 1024  # = 1MB - adjust the chunk size as desired
some_file_path = 'large_file.tar'
app = FastAPI()

@app.get('/')
async def main():
    async def iterfile():
       async with aiofiles.open(some_file_path, 'rb') as f:
            while chunk := await f.read(CHUNK_SIZE):
                yield chunk

    headers = {'Content-Disposition': 'attachment; filename="large_file.tar"'}
    return StreamingResponse(iterfile(), headers=headers, media_type='application/x-tar')

【讨论】:

    猜你喜欢
    • 2014-08-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多