zoukankan      html  css  js  c++  java
  • 使用StreamHttpResponse和FileResponse下载文件的注意事项及文件私有化

    为什么需要编写下载视图方法?

    你或许知道,我们上传的文件默认放在media文件夹中的,且Django会为每个上传的静态文件分配一个静态url。在模板中,你可以使用{{ mymodel.file.url }}获取每个文件的链接(url),浏览器也是可以直接打开这个url的,如下所示。

    <td><a href="/media/files/b1957d79f3.JPG/">/media/files/b1957d79f3.JPG</a></td>

    然而当你碰到如下2种情况时,你需要编写自己的视图下载方法。

    你希望用户以附件形式获得文件,而不是浏览器直接打开。

    你希望允许用户下载一些保密文件,而不希望在html模板中暴露它们。

    具体思路

    我们先新建一个file_download的app,添加如下urls。该URL了包含了一个文件的相对路径file_path作为参数, 其对应视图是file_download方法。我们现在就开始尝试用不同方法来处理文件下载。

    from django.urls import path, re_path
    from . import views

    # namespace
    app_name = 'file_download'

    urlpatterns = [

       re_path(r'^download/(?P<file_path>.*)/$', views.file_download, name='file_download'),

    ]
    模板templates/file_upload/file_list.html改为如下所示, 而不再采用option 1。

    {% for file in files %}
    <tr>
       <!-- Option 1 <td><a href="{{ file.file.url }}/">{{ file.file.url }}</a></td> -->
       <td><a href="/file/download{{ file.file.url }}/">{{ file.file.url }}</a></td>
       <td>{{ file.file.size | filesizeformat }}</td>
       <td>{{ file.upload_method }}</td>
    </tr>
    {% endfor %}
     

    方法一: 使用HttpResonse

    下面方法从url获取file_path, 打开文件,读取文件,然后通过HttpResponse方法输出。

    import os
    from django.http import HttpResponse

    def file_download(request, file_path):
       # do something...
       with open(file_path) as f:
           c = f.read()
       return HttpResponse(c)
    然而该方法有个问题,如果文件是个二进制文件,HttpResponse输出的将会是乱码。对于一些二进制文件(图片,pdf),我们更希望其直接作为附件下载。当文件下载到本机后,用户就可以用自己喜欢的程序(如Adobe)打开阅读文件了。这时我们可以对上述方法做出如下改进, 给response设置content_type和Content_Disposition。

    import os
    from django.http import HttpResponse, Http404


    def media_file_download(request, file_path):
       with open(file_path, 'rb') as f:
           try:
               response = HttpResponse(f)
               response['content_type'] = "application/octet-stream"
               response['Content-Disposition'] = 'attachment; filename=' + os.path.basename(file_path)
               return response
           except Exception:
               raise Http404
    HttpResponse有个很大的弊端,其工作原理是先读取文件,载入内存,然后再输出。如果下载文件很大,该方法会占用很多内存。对于下载大文件,Django更推荐StreamingHttpResponse和FileResponse方法,这两个方法将下载文件分批(Chunks)写入用户本地磁盘,先不将它们载入服务器内存。

    方法二: 使用SteamingHttpResonse

    import os
    from django.http import HttpResponse, Http404, StreamingHttpResponse

    def stream_http_download(request, file_path):
       try:
           response = StreamingHttpResponse(open(file_path, 'rb'))
           response['content_type'] = "application/octet-stream"
           response['Content-Disposition'] = 'attachment; filename=' + os.path.basename(file_path)
           return response
       except Exception:
           raise Http404
     

    方法三: 使用FileResonse

    FileResponse方法是SteamingHttpResponse的子类,是小编我推荐的文件下载方法。如果我们给file_response_download加上@login_required装饰器,那么我们就可以实现用户需要先登录才能下载某些文件的功能了。

    import os
    from django.http import HttpResponse, Http404, FileResponse


    def file_response_download1(request, file_path):
       try:
           response = FileResponse(open(file_path, 'rb'))
           response['content_type'] = "application/octet-stream"
           response['Content-Disposition'] = 'attachment; filename=' + os.path.basename(file_path)
           return response
       except Exception:
           raise Http404


    然而即使加上了@login_required的装饰器,用户只要获取了文件的链接地址, 他们依然可以通过浏览器直接访问那些文件。我们等会再谈保护文件的链接地址和文件私有化,因为此时我们还有个更大的问题需要担忧。我们定义的下载方法可以下载所有文件,不仅包括.py文件,还包括不在media文件夹里的文件(比如非用户上传的文件)。比如当我们直接访问127.0.0.1:8000/file/download/file_project/settings.py/时,你会发现我们连file_project目录下的settings.py都下载了。如果哪个程序员这么蠢,你可以将他直接fire了。所以我们在编写下载方法时,我们一定要限定那些文件可以下,哪些不能下或者限定用户只能下载media文件夹里的东西。

    def file_response_download(request, file_path):
       ext = os.path.basename(file_path).split('.')[-1].lower()
       # cannot be used to download py, db and sqlite3 files.
       if ext not in ['py', 'db',  'sqlite3']:
           response = FileResponse(open(file_path, 'rb'))
           response['content_type'] = "application/octet-stream"
           response['Content-Disposition'] = 'attachment; filename=' + os.path.basename(file_path)
           return response
       else:
           raise Http404
     

    文件私有化的两种方法

    如果你想实现只有登录过的用户才能查看和下载某些文件,大概有两种方法,这里仅提供思路。

    上传文件放在media文件夹,文件名使用很长的随机字符串命名(uuid), 让用户无法根据文件名猜出这是什么文件。视图和模板里验证用户是否已登录,登录或通过权限验证后才显示具体的url。- 简单易实现,安全性不高,但对于一般项目已足够。

    上传文件放在非media文件夹,用户即使知道了具体文件地址也无法访问,因为Django只会给media文件夹里每个文件创建独立url资源。视图和模板里验证用户是否已登录,登录或通过权限验证后通过自己编写的下载方法下载文件。- 安全性高,但实现相对复杂。

  • 相关阅读:
    个人工作总结2
    个人工作总结1
    各种颜色代码
    第七周学习进度
    安卓的SlidingMenu配置
    第六周学习进度
    ecshop 首页调用指定分类下的销售排行
    EcShop首页显示特定分类的精品新品热销特价等推荐商品
    ecshop调用指定分类热销-新品-精品
    ecshop transport.js 和 jquery 冲突解决办法
  • 原文地址:https://www.cnblogs.com/ellisonzhang/p/11422265.html
Copyright © 2011-2022 走看看