tornado 下载文件,显示下载速度、已下载大小、剩余时间、进度条、文件总大小

tornado 下载文件,显示下载速度、已下载大小、剩余时间、进度条、文件总大小

初版

import asyncio
import os

import aiofiles
import tornado.web


class FileHandler(tornado.web.RequestHandler):

    async def get(self):
        file_path = self.get_argument("file_path")

        # 验证文件路径是否合法
        if not os.path.exists(file_path):
            self.set_status(404)
            self.write("File not found.")
            return

        if not os.path.isfile(file_path):
            self.set_status(400)
            self.write("Invalid file path: must be a file path.")
            return

        # 获取文件名
        file_name = os.path.basename(file_path)

        self.set_header("Content-Disposition", f"attachment; filename={file_name}")
        self.set_header("Content-Type", "application/octet-stream")

        # 读取文件内容并写入响应
        async with aiofiles.open(file_path, 'rb') as f:
            while True:
                data = await f.read(4096)
                if not data:
                    break
                self.write(data)
        # 结束响应
        self.finish()


def make_app():
    return tornado.web.Application([
        (r"/", FileHandler),
    ])


async def main():
    app = make_app()
    app.listen(8888)
    await asyncio.Event().wait()


if __name__ == "__main__":
    asyncio.run(main())

1

效果:页面会一直卡住,直到文件下载完成才会在下载记录页面显示 0 B/s - 61.0 MB, 共 61.0 MB

  • 下载速度 0 B/s
  • 已下载大小 61.0 MB
  • 文件总大小 61.0 MB
  • 进度条直接 100%,且不会动
  • 剩余时间

显示的都是正确的但都是静态值,不会动。

如果文件名中包含中文会报错如下:

Uncaught exception GET /?file_path=C:\Users\zhaobs\Downloads\%E6%B5%8B%E8%AF%952.exe (::1)
HTTPServerRequest(protocol='http', host='localhost:8888', method='GET', uri='/?file_path=C:\\Users\\zhaobs\\Downloads\\%E6%B5%8B%E8%AF%952.exe', version='HTTP/1.1', remote_ip='::1')
Traceback (most recent call last):
  File "D:\software\anaconda3\envs_dirs\file_download\Lib\site-packages\tornado\web.py", line 1790, in _execute
    result = await result
             ^^^^^^^^^^^^
  File "D:\project\python\file_download\main.py", line 38, in get
    self.finish()
  File "D:\software\anaconda3\envs_dirs\file_download\Lib\site-packages\tornado\web.py", line 1238, in finish
    future = self.flush(include_footers=True)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "D:\software\anaconda3\envs_dirs\file_download\Lib\site-packages\tornado\web.py", line 1175, in flush
    return self.request.connection.write_headers(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "D:\software\anaconda3\envs_dirs\file_download\Lib\site-packages\tornado\http1connection.py", line 450, in write_headers
    lines.extend(line.encode("latin1") for line in header_lines)
  File "D:\software\anaconda3\envs_dirs\file_download\Lib\site-packages\tornado\http1connection.py", line 450, in <genexpr>
    lines.extend(line.encode("latin1") for line in header_lines)
                 ^^^^^^^^^^^^^^^^^^^^^
UnicodeEncodeError: 'latin-1' codec can't encode characters in position 42-43: ordinal not in range(256)
Cannot send error response after headers written
Failed to flush partial response

解决中文文件名报错

关键代码

# 解决中文文件名报错的问题
safe_filename = tornado.escape.url_escape(file_name, plus=False)

self.set_header('Content-Disposition', f'attachment; filename*=UTF-8\'\'{safe_filename}')
class FileHandler(tornado.web.RequestHandler):

    async def get(self):
        file_path = self.get_argument("file_path")

        # 验证文件路径是否合法
        if not os.path.exists(file_path):
            self.set_status(404)
            self.write("File not found.")
            return

        if not os.path.isfile(file_path):
            self.set_status(400)
            self.write("Invalid file path: must be a file path.")
            return

        # 获取文件名
        file_name = os.path.basename(file_path)
        # 解决中文文件名报错的问题
        safe_filename = tornado.escape.url_escape(file_name, plus=False)

        self.set_header('Content-Disposition', f'attachment; filename*=UTF-8\'\'{safe_filename}')
        self.set_header("Content-Type", "application/octet-stream")

        # 读取文件内容并写入响应
        async with aiofiles.open(file_path, 'rb') as f:
            while True:
                data = await f.read(4096)
                if not data:
                    break
                self.write(data)
        # 结束响应
        self.finish()

2

中文文件名已可正常下载

显示下载速度、已下载大小

关键代码

await self.flush()
class FileHandler(tornado.web.RequestHandler):

    async def get(self):
        file_path = self.get_argument("file_path")

        # 验证文件路径是否合法
        if not os.path.exists(file_path):
            self.set_status(404)
            self.write("File not found.")
            return

        if not os.path.isfile(file_path):
            self.set_status(400)
            self.write("Invalid file path: must be a file path.")
            return

        # 获取文件名
        file_name = os.path.basename(file_path)
        # 解决中文文件名报错的问题
        safe_filename = tornado.escape.url_escape(file_name, plus=False)

        self.set_header('Content-Disposition', f'attachment; filename*=UTF-8\'\'{safe_filename}')
        self.set_header("Content-Type", "application/octet-stream")

        # 读取文件内容并写入响应
        async with aiofiles.open(file_path, 'rb') as f:
            while True:
                data = await f.read(4096)
                if not data:
                    break
                self.write(data)
                # 必须添加 flush
                await self.flush()
        # 结束响应
        self.finish()

3

  • 下载速度显示正常
  • 已下载大小显示正常
  • 文件总大小不显示
  • 剩余时间
  • 进度条是假的

下载过程中显示文件总大小、剩余时间、进度条正常前进

关键代码

# 文件大小
file_size = os.path.getsize(file_path)

self.set_header('Content-Length', file_size)
class FileHandler(tornado.web.RequestHandler):

    async def get(self):
        file_path = self.get_argument("file_path")

        # 验证文件路径是否合法
        if not os.path.exists(file_path):
            self.set_status(404)
            self.write("File not found.")
            return

        if not os.path.isfile(file_path):
            self.set_status(400)
            self.write("Invalid file path: must be a file path.")
            return

        # 获取文件名
        file_name = os.path.basename(file_path)
        # 解决中文文件名报错的问题
        safe_filename = tornado.escape.url_escape(file_name, plus=False)
        # 文件大小
        file_size = os.path.getsize(file_path)

        self.set_header('Content-Disposition', f'attachment; filename*=UTF-8\'\'{safe_filename}')
        self.set_header("Content-Type", "application/octet-stream")
        self.set_header('Content-Length', file_size)

        # 读取文件内容并写入响应
        async with aiofiles.open(file_path, 'rb') as f:
            while True:
                data = await f.read(4096)
                if not data:
                    break
                self.write(data)
                # 必须添加 flush
                await self.flush()
        # 结束响应
        self.finish()

4

  • 下载速度显示正常
  • 已下载大小显示正常
  • 显示文件总大小
  • 剩余时间
  • 进度条正常
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值