django:StreamHttpResponse和FileResponse

你或許知道,我們上傳的文件默認放在media文件夾中的,且Django會為每個上傳的靜態(tài)文件分配一個靜態(tài)url。在模板中,你可以使用{{ mymodel.file.url }}獲取每個文件的鏈接(url),瀏覽器也是可以直接打開這個url的,如下所示。
<td><a href="/media/files/b1957d79f3.JPG/">/media/files/b1957d79f3.JPG</a></td>
然而當你碰到如下2種情況時,你需要編寫自己的視圖下載方法。
1、你希望用戶以附件形式獲得文件,而不是瀏覽器直接打開。
2、你希望允許用戶下載一些保密文件,而不希望在html模板中暴露它們。

具體思路
我們先新建一個file_download的app,添加如下urls。該URL了包含了一個文件的相對路徑file_path作為參數(shù), 其對應視圖是file_download方法。我們現(xiàn)在就開始嘗試用不同方法來處理文件下載。

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如下所示

{% for file in files %}
<tr>
    <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裝飾器,那么們就可以實現(xiàn)用戶需要先登錄才能下載某些文件的功能了。

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/時,你會發(fā)現(xiàn)我們連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
文件私有化的兩種方法

如果你想實現(xiàn)只有登錄過的用戶才能查看和下載某些文件,大概有兩種方法,這里僅提供思路。

  • 上傳文件放在media文件夾,文件名使用很長的隨機字符串命名(uuid), 讓用戶無法根據(jù)文件名猜出這是什么文件。視圖和模板里驗證用戶是否已登錄,登錄或通過權限驗證后才顯示具體的url。- 簡單易實現(xiàn),安全性不高,但對于一般項目已足夠。
  • 上傳文件放在非media文件夾,用戶即使知道了具體文件地址也無法訪問,因為Django只會給media文件夾里每個文件創(chuàng)建獨立url資源。視圖和模板里驗證用戶是否已登錄,登錄或通過權限驗證后通過自己編寫的下載方法下載文件。- 安全性高,但實現(xiàn)相對復雜。
    ————————————————
    版權聲明:本文為CSDN博主「大江狗」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權協(xié)議,轉載請附上原文出處鏈接及本聲明。
    原文鏈接:https://blog.csdn.net/weixin_42134789/article/details/83346714
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容