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作為參數, 其對應視圖是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如下所示

{% 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裝飾器蔑赘,那么們就可以實現用戶需要先登錄才能下載某些文件的功能了。

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ā)現我們連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文件夾里每個文件創(chuàng)建獨立url資源蔚龙。視圖和模板里驗證用戶是否已登錄,登錄或通過權限驗證后通過自己編寫的下載方法下載文件映胁。- 安全性高木羹,但實現相對復雜。
    ————————————————
    版權聲明:本文為CSDN博主「大江狗」的原創(chuàng)文章解孙,遵循CC 4.0 BY-SA版權協議坑填,轉載請附上原文出處鏈接及本聲明。
    原文鏈接:https://blog.csdn.net/weixin_42134789/article/details/83346714
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末弛姜,一起剝皮案震驚了整個濱河市脐瑰,隨后出現的幾起案子,更是在濱河造成了極大的恐慌廷臼,老刑警劉巖苍在,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異荠商,居然都是意外死亡寂恬,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門结啼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來掠剑,“玉大人屈芜,你說我怎么就攤上這事郊愧。” “怎么了井佑?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵属铁,是天一觀的道長。 經常有香客問我躬翁,道長焦蘑,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任盒发,我火速辦了婚禮例嘱,結果婚禮上狡逢,老公的妹妹穿的比我還像新娘。我一直安慰自己拼卵,他們只是感情好奢浑,可當我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著腋腮,像睡著了一般雀彼。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上即寡,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天徊哑,我揣著相機與錄音,去河邊找鬼聪富。 笑死莺丑,一個胖子當著我的面吹牛,可吹牛的內容都是我干的善涨。 我是一名探鬼主播窒盐,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼钢拧!你這毒婦竟也來了蟹漓?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤源内,失蹤者是張志新(化名)和其女友劉穎葡粒,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體膜钓,經...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡嗽交,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了颂斜。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片夫壁。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖沃疮,靈堂內的尸體忽然破棺而出盒让,到底是詐尸還是另有隱情,我是刑警寧澤司蔬,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布邑茄,位于F島的核電站,受9級特大地震影響俊啼,放射性物質發(fā)生泄漏肺缕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望同木。 院中可真熱鬧浮梢,春花似錦、人聲如沸彤路。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽斩萌。三九已至缝裤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間颊郎,已是汗流浹背憋飞。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留姆吭,地道東北人榛做。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像内狸,于是被迫代替她去往敵國和親检眯。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內容