Django Web应用在为用户生成个性化文件时,往往使用在本地生成文件,存入本地临时目录,再通过Django的views.file_downloaddjango.http.FileHttpResponse让用户下载。因为文件是动态生成的,在用户下载文件后需要删除文件。这样做主要有几个弊端:

  1. 应用需要本地磁盘写权限。应用有本地磁盘写权限是有一定的安全风险的,入侵者可以通过应用在磁盘写入可执行文件来进行入侵。如果应用只有只读权限则安全很多。
  2. 存在磁盘泄露风险。未知异常可能会导致已生成的磁盘文件在使用后未删除,继而引发磁盘泄露导致磁盘空间被占满。
  3. 迁移风险。应用迁移时,新主机的的需要创建和旧主机一致的本地临时目录,如果无法创建则需要修改代码。
  4. 无法同时下载同名文件。一个目录里无法存在两个同名文件,很好理解了。

当然,上面这些问题,可以在使用views.file_downloaddjango.http.FileHttpResponse的情况下解决,但是我们又更好的方法来实现这一需求。

1. 需求描述

提供一个打包文件下载接口,文件下载后文件名为my_script.tar.gz。包中包含两个文件,分别是run.pyconfig.py。其中run.py为固定文件,config.py是一个模板文件 ,模板参数来源于数据库。

2. 使用的标准库

  1. io.BytesIO:用来生成file like对象
  2. tarfile:创建tar文件

3. 示例代码

  1. config.py.template

    1
    user_key = {}
  2. main.py

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    import 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. 注意事项

  1. 创建filelike对象后一定要记得将文件指针移至文件头,Django默认会从当前指针位置开始返回文件。不移动文件指针会导致下载的文件是空文件。
  2. 次方法不依赖磁盘,但会在内存中创建文件,所以仅适用于小文件。