Django Web应用在为用户生成个性化文件时,往往使用在本地生成文件,存入本地临时目录,再通过Django的views.file_download
、django.http.FileHttpResponse
让用户下载。因为文件是动态生成的,在用户下载文件后需要删除文件。这样做主要有几个弊端:
- 应用需要本地磁盘写权限。应用有本地磁盘写权限是有一定的安全风险的,入侵者可以通过应用在磁盘写入可执行文件来进行入侵。如果应用只有只读权限则安全很多。
- 存在磁盘泄露风险。未知异常可能会导致已生成的磁盘文件在使用后未删除,继而引发磁盘泄露导致磁盘空间被占满。
- 迁移风险。应用迁移时,新主机的的需要创建和旧主机一致的本地临时目录,如果无法创建则需要修改代码。
- 无法同时下载同名文件。一个目录里无法存在两个同名文件,很好理解了。
当然,上面这些问题,可以在使用views.file_download
、django.http.FileHttpResponse
的情况下解决,但是我们又更好的方法来实现这一需求。
1. 需求描述
提供一个打包文件下载接口,文件下载后文件名为my_script.tar.gz
。包中包含两个文件,分别是run.py
和config.py
。其中run.py
为固定文件,config.py
是一个模板文件 ,模板参数来源于数据库。
2. 使用的标准库
io.BytesIO
:用来生成file like对象tarfile
:创建tar文件
3. 示例代码
config.py.template
1
user_key = {}
main.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22import time
from io import BytesIO
from django.http import HttpResponse
from . import get_user_key
def get(request):
user_key = get_user_key()
tar_file = BytesIO()
tar = tarfile.open(fileobj=tar_file, mode='w:gz')
tar.add('./run.py.template', './run.py')
with open('./config.py.template', 'rt') as f:
s = f.read().format(user_key)
b = s.encode('utf-8')
tar_info = tarfile.TarInfo(name='./config.py')
tar_info.mtime = time.time()
tar_info.size = len(b)
tar.addfile(tar_info, BytesIO(b))
tar.close()
tar_file.seek(0)
response = HttpResponse(tar_file, content_type='application/octet-stream')
response['Content-Disposition'] = 'attachment; filename="my_script.tar.gz"'
return response
4. 注意事项
- 创建filelike对象后一定要记得将文件指针移至文件头,Django默认会从当前指针位置开始返回文件。不移动文件指针会导致下载的文件是空文件。
- 次方法不依赖磁盘,但会在内存中创建文件,所以仅适用于小文件。