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())
效果:页面会一直卡住,直到文件下载完成才会在下载记录页面显示 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()
中文文件名已可正常下载
显示下载速度、已下载大小
关键代码
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()
- 下载速度显示正常
- 已下载大小显示正常
- 文件总大小不显示
- 剩余时间
- 进度条是假的
下载过程中显示文件总大小、剩余时间、进度条正常前进
关键代码
# 文件大小
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()
- 下载速度显示正常
- 已下载大小显示正常
- 显示文件总大小
- 剩余时间
- 进度条正常