การมี Django ให้บริการไฟล์ที่ดาวน์โหลดได้


245

ฉันต้องการให้ผู้ใช้ในเว็บไซต์สามารถดาวน์โหลดไฟล์ที่มีเส้นทางถูกบดบังเพื่อให้พวกเขาไม่สามารถดาวน์โหลดได้โดยตรง

ตัวอย่างเช่นฉันต้องการให้ URL เป็นอย่างนี้: http://example.com/download/?f=somefile.txt

/home/user/files/และบนเซิร์ฟเวอร์ฉันรู้ว่าไฟล์ที่ดาวน์โหลดได้ทั้งหมดอาศัยอยู่ในโฟลเดอร์

มีวิธีที่จะทำให้ Django ให้บริการไฟล์สำหรับการดาวน์โหลดซึ่งต่างจากการพยายามค้นหา URL และดูเพื่อแสดงหรือไม่?


2
ทำไมคุณไม่ใช้ Apache เพื่อทำสิ่งนี้ Apache ให้บริการเนื้อหาแบบคงที่เร็วกว่าและเรียบง่ายกว่า Django
S.Lott

22
ฉันไม่ได้ใช้ Apache เพราะฉันไม่ต้องการให้ไฟล์ที่สามารถเข้าถึงได้โดยไม่ต้องมีการอนุญาตที่ใช้ใน Django
damon

3
หากคุณต้องการคำนึงถึงการอนุญาตของผู้ใช้คุณจะต้องให้บริการไฟล์ผ่านมุมมองของ Django
--ukasz

127
ตรงซึ่งเป็นเหตุผลที่ฉันถามคำถามนี้
damon

คำตอบ:


189

สำหรับ "ดีที่สุดของทั้งสองโลก" คุณสามารถรวมโซลูชันของ S.Lott เข้ากับโมดูล xsendfile : django สร้างเส้นทางไปยังไฟล์ (หรือไฟล์เอง) แต่การให้บริการไฟล์จริงนั้นจัดการโดย Apache / Lighttpd เมื่อคุณตั้งค่า mod_xsendfile แล้วการรวมเข้ากับมุมมองของคุณจะต้องใช้โค้ดสองสามบรรทัด:

from django.utils.encoding import smart_str

response = HttpResponse(mimetype='application/force-download') # mimetype is replaced by content_type for django 1.7
response['Content-Disposition'] = 'attachment; filename=%s' % smart_str(file_name)
response['X-Sendfile'] = smart_str(path_to_file)
# It's usually a good idea to set the 'Content-Length' header too.
# You can also set any other required headers: Cache-Control, etc.
return response

แน่นอนว่าจะใช้งานได้เฉพาะเมื่อคุณควบคุมเซิร์ฟเวอร์ของคุณหรือ บริษัท โฮสติ้งของคุณมี mod_xsendfile ตั้งค่าไว้แล้ว

แก้ไข:

mimetype ถูกแทนที่ด้วย content_type สำหรับ django 1.7

response = HttpResponse(content_type='application/force-download')  

แก้ไข: เพื่อnginxตรวจสอบสิ่งนี้มันใช้X-Accel-Redirectแทนapacheส่วนหัวของX-Sendfile


6
หากชื่อไฟล์ของคุณหรือ path_to_file มีอักขระที่ไม่ใช่ ASCII เช่น "ä" หรือ "ö" ไฟล์smart_strจะไม่ทำงานตามที่ต้องการเนื่องจากโมดูล Apache X-Sendfile ไม่สามารถถอดรหัสสตริงที่เข้ารหัส smart_str ดังนั้นไฟล์ "Örinää.mp3" จึงไม่สามารถแสดงได้ และหากมีการละเว้น smart_str, Django จะส่งข้อผิดพลาดในการเข้ารหัส ascii เนื่องจากส่วนหัวทั้งหมดจะถูกเข้ารหัสเป็นรูปแบบ ascii ก่อนที่จะส่ง วิธีเดียวที่ฉันรู้ที่จะหลีกเลี่ยงปัญหานี้คือการลดชื่อไฟล์ X-sendfile เป็นคนที่ประกอบด้วย ascii เท่านั้น
Ciantic

3
เพื่อให้ชัดเจนยิ่งขึ้น: S.Lott มีตัวอย่างง่ายๆเพียงแสดงไฟล์จาก django โดยตรงไม่จำเป็นต้องตั้งค่าอื่น elo80ka มีตัวอย่างที่มีประสิทธิภาพมากขึ้นโดยที่เว็บเซิร์ฟเวอร์จัดการกับไฟล์คงที่และ django ไม่จำเป็นต้องทำ หลังมีประสิทธิภาพที่ดีขึ้น แต่อาจต้องติดตั้งเพิ่มเติม ทั้งสองมีสถานที่ของพวกเขา
rocketmonkeys

1
@Ciantic ดูคำตอบของ btimby สำหรับสิ่งที่ดูเหมือนว่าจะเป็นทางออกสำหรับปัญหาการเข้ารหัส
mlissner

โซลูชันนี้ใช้งานกับการกำหนดค่าเว็บเซิร์ฟเวอร์ต่อไปนี้ได้หรือไม่ แบ็คเอนด์: เซิร์ฟเวอร์ Apache + mod_wsgi (VPS) 2 เครื่องขึ้นไปตั้งค่าให้ทำซ้ำกัน Front-end: เซิร์ฟเวอร์ 1 nginx proxy (VPS) ที่ใช้การอัปโหลดบาลานซ์อัปสตรีม, ทำ round-robin
Daniel

12
mimetype ถูกแทนที่ด้วย content_type สำหรับ django 1.7
ismailsunni

88

"ดาวน์โหลด" เป็นเพียงการเปลี่ยนส่วนหัว HTTP

ดูhttp://docs.djangoproject.com/en/dev/ref/request-response/#telling-the-browser-to-treat-the-response-as-a-file-attachmentสำหรับวิธีการตอบกลับด้วยการดาวน์โหลด .

คุณจะต้องนิยาม URL "/download"หนึ่งสำหรับ

คำขอGETหรือPOSTพจนานุกรมจะมี"f=somefile.txt"ข้อมูล

ฟังก์ชั่นมุมมองของคุณจะผสานเส้นทางฐานกับค่า " f" เปิดไฟล์สร้างและส่งคืนวัตถุตอบกลับ ควรน้อยกว่า 12 บรรทัดของรหัส


49
นี่คือคำตอบที่ถูกต้อง (ง่าย) แต่ข้อควรระวังอย่างหนึ่ง - การส่งชื่อไฟล์เป็นพารามิเตอร์หมายความว่าผู้ใช้สามารถดาวน์โหลดไฟล์ใด ๆ (เช่นถ้าคุณผ่าน "f = / etc / passwd") มีจำนวนมาก สิ่งต่าง ๆ ที่ช่วยป้องกันสิ่งนี้ (การอนุญาตผู้ใช้ ฯลฯ ) แต่ให้ระวังความเสี่ยงด้านความปลอดภัยที่เห็นได้ชัด แต่เป็นเรื่องปกติ โดยพื้นฐานแล้วมันเป็นเพียงส่วนหนึ่งของการตรวจสอบความถูกต้องของอินพุต: หากคุณผ่านชื่อไฟล์ไปยังมุมมองให้ตรวจสอบชื่อไฟล์ในมุมมองนั้น!
rocketmonkeys

9
การแก้ไขที่ง่ายมากสำหรับปัญหาด้านความปลอดภัยนี้:filepath = filepath.replace('..', '').replace('/', '')
duality_

7
หากคุณใช้ตารางเพื่อจัดเก็บข้อมูลไฟล์รวมถึงผู้ใช้ที่จะสามารถดาวน์โหลดได้สิ่งที่คุณต้องส่งคือคีย์หลักไม่ใช่ชื่อไฟล์และแอปจะตัดสินใจว่าจะทำอย่างไร
Edward Newell

30

สำหรับโซลูชันที่เรียบง่ายแต่ไม่มีประสิทธิภาพหรือปรับขนาดได้คุณสามารถใช้serveมุมมองdjango ในตัว สิ่งนี้ยอดเยี่ยมสำหรับต้นแบบที่รวดเร็วหรือการทำงานแบบครั้งเดียว แต่ดังที่ได้กล่าวไปแล้วในคำถามนี้คุณควรใช้บางอย่างเช่น apache หรือ nginx ในการผลิต

from django.views.static import serve
filepath = '/some/path/to/local/file.txt'
return serve(request, os.path.basename(filepath), os.path.dirname(filepath))

ยังมีประโยชน์อย่างมากสำหรับการให้ทางเลือกสำหรับการทดสอบบน Windows
Amir Ali Akbari

ฉันทำโครงการ django แบบสแตนด์อโลนตั้งใจทำงานแบบไคลเอนต์เดสก์ท็อป aa และมันทำงานได้อย่างสมบูรณ์แบบ ขอบคุณ!
daigorocub

1
ทำไมมันไม่มีประสิทธิภาพ
zinking

2
@zinking เพราะไฟล์ทั่วไปควรทำหน้าที่ผ่านบางสิ่งบางอย่างเช่น Apache มากกว่าผ่านกระบวนการ Django
คอรี

1
เรากำลังพูดถึงข้อบกพร่องเกี่ยวกับประสิทธิภาพประเภทใดอยู่ที่นี่ ไฟล์ได้รับการโหลดลงใน RAM หรืออะไรบางอย่างถ้ามันถูกส่งผ่าน django หรือไม่? เหตุใด django จึงไม่สามารถให้บริการด้วยประสิทธิภาพเดียวกันกับ nginx
Gershom

27

S.Lott มีวิธีแก้ปัญหา "ที่ดี" / แบบง่ายและ elo80ka มีวิธีที่ดีที่สุด "/" ที่มีประสิทธิภาพ นี่คือโซลูชัน "ดีกว่า" / กลาง - ไม่มีการตั้งค่าเซิร์ฟเวอร์ แต่มีประสิทธิภาพมากขึ้นสำหรับไฟล์ขนาดใหญ่กว่าการแก้ไขแบบไร้เดียงสา:

http://djangosnippets.org/snippets/365/

โดยพื้นฐานแล้ว Django ยังคงรองรับการให้บริการไฟล์ แต่ไม่โหลดสิ่งทั้งหมดลงในหน่วยความจำทันที สิ่งนี้ทำให้เซิร์ฟเวอร์ของคุณ (ช้า) ให้บริการไฟล์ขนาดใหญ่โดยไม่เพิ่มการใช้หน่วยความจำ

อีกครั้ง X-SendFile ของ S.Lott ยังคงดีกว่าสำหรับไฟล์ขนาดใหญ่ แต่ถ้าคุณไม่สามารถหรือไม่อยากยุ่งกับสิ่งนั้นโซลูชันกลางนี้จะเพิ่มประสิทธิภาพให้คุณโดยไม่ต้องวุ่นวาย


4
ตัวอย่างนั้นไม่ดี snipped ที่อาศัยในdjango.core.servers.httpbaseโมดูลส่วนตัวที่ไม่มีเอกสารซึ่งมีสัญญาณเตือนภัยขนาดใหญ่ที่ด้านบนของรหัสว่า " ไม่ได้ใช้สำหรับใช้ผลิต !!! " ซึ่งได้รับในแฟ้มตั้งแต่มันถูกสร้างขึ้นครั้งแรก ไม่ว่าจะในกรณีใดFileWrapperฟังก์ชันการทำงานที่ข้อมูลโค้ดนี้อาศัยถูกลบออกใน django 1.9
eykanal

16

วิธีการแก้ปัญหา @Rocketmonkeys พยายาม แต่ไฟล์ที่ดาวน์โหลดถูกจัดเก็บเป็น * .bin และให้ชื่อแบบสุ่ม ไม่เป็นไรแน่นอน การเพิ่มอีกบรรทัดจาก @ elo80ka แก้ปัญหาได้แล้ว
นี่คือรหัสที่ฉันใช้ตอนนี้:

from wsgiref.util import FileWrapper
from django.http import HttpResponse

filename = "/home/stackoverflow-addict/private-folder(not-porn)/image.jpg"
wrapper = FileWrapper(file(filename))
response = HttpResponse(wrapper, content_type='text/plain')
response['Content-Disposition'] = 'attachment; filename=%s' % os.path.basename(filename)
response['Content-Length'] = os.path.getsize(filename)
return response

ตอนนี้คุณสามารถจัดเก็บไฟล์ในไดเรกทอรีส่วนตัว (ไม่ใช่ภายใน / สื่อหรือ / public_html) และแสดงไฟล์ผ่าน django กับผู้ใช้บางคนหรือภายใต้สถานการณ์บางอย่าง
หวังว่ามันจะช่วย

ขอบคุณ @ elo80ka, @ S.Lott และ @Rocketmonkeys สำหรับคำตอบทำให้ได้โซลูชั่นที่สมบูรณ์แบบที่รวมพวกเขาทั้งหมดเข้าด้วยกัน =)


1
ขอบคุณนี่คือสิ่งที่ฉันกำลังมองหา!
ihatecache

1
เพิ่มเครื่องหมายคำพูดคู่ล้อมรอบชื่อไฟล์filename="%s"ในส่วนหัวของ Content-Disposition เพื่อหลีกเลี่ยงปัญหาช่องว่างในชื่อไฟล์ อ้างอิง: ชื่อไฟล์ที่มีช่องว่างจะถูกตัดเมื่อดาวน์โหลด , วิธีการเข้ารหัสพารามิเตอร์ชื่อไฟล์ของส่วนหัว Disposition เนื้อหาใน HTTP?
Christian Long

1
โซลูชันของคุณใช้ได้สำหรับฉัน แต่ฉันมีข้อผิดพลาด "เริ่มต้นไบต์ไม่ถูกต้อง ... " สำหรับไฟล์ของฉัน แก้ไขมันด้วยFileWrapper(open(path.abspath(file_name), 'rb'))
Mark Mishyn

FileWrapperถูกลบตั้งแต่ Django 1.9
freethebees

เป็นไปได้ที่จะใช้from wsgiref.util import FileWrapper
Kriss

15

เพียงแค่พูดถึงวัตถุFileResponse ที่มีใน Django 1.10

แก้ไข: เพิ่งพบคำตอบของฉันเองในขณะที่ค้นหาวิธีง่ายๆในการสตรีมไฟล์ผ่าน Django ดังนั้นนี่เป็นตัวอย่างที่สมบูรณ์มากขึ้น (สำหรับฉันในอนาคต) จะถือว่าชื่อ FileField คือimported_file

views.py

from django.views.generic.detail import DetailView   
from django.http import FileResponse
class BaseFileDownloadView(DetailView):
  def get(self, request, *args, **kwargs):
    filename=self.kwargs.get('filename', None)
    if filename is None:
      raise ValueError("Found empty filename")
    some_file = self.model.objects.get(imported_file=filename)
    response = FileResponse(some_file.imported_file, content_type="text/csv")
    # https://docs.djangoproject.com/en/1.11/howto/outputting-csv/#streaming-large-csv-files
    response['Content-Disposition'] = 'attachment; filename="%s"'%filename
    return response

class SomeFileDownloadView(BaseFileDownloadView):
    model = SomeModel

urls.py

...
url(r'^somefile/(?P<filename>[-\w_\\-\\.]+)$', views.SomeFileDownloadView.as_view(), name='somefile-download'),
...

1
ขอบคุณมาก! มันเป็นทางออกที่ง่ายที่สุดสำหรับการดาวน์โหลดไฟล์ไบนารีและใช้งานได้
Julia Zhao

13

ดังกล่าวข้างต้นว่าวิธี mod_xsendfile ไม่อนุญาตให้ใช้สำหรับอักขระที่ไม่ใช่ ASCII ในชื่อไฟล์

ด้วยเหตุนี้ฉันจึงมี patch สำหรับ mod_xsendfile ที่จะอนุญาตให้ส่งไฟล์ใด ๆ ตราบใดที่ชื่อนั้นมีการเข้ารหัส url และส่วนหัวเพิ่มเติม:

X-SendFile-Encoding: url

ก็ส่งมาเช่นกัน

http://ben.timby.com/?p=149


แพทช์จะถูกพับเข้าไปในห้องสมุด corer
mlissner

7

ลอง: https://pypi.python.org/pypi/django-sendfile/

"Abstraction to offload file อัพโหลดไปยังเว็บเซิร์ฟเวอร์ (เช่น Apache กับ mod_xsendfile) เมื่อ Django ตรวจสอบการอนุญาตแล้ว"


2
ในเวลา (1 ปีที่ผ่านมา) ทางแยกส่วนตัวของฉันมีไฟล์ Apache ที่ให้บริการทางเลือกสำรองที่เก็บต้นฉบับยังไม่รวม
Roberto Rosario

ทำไมคุณถึงลบลิงค์?
kiok46

@ kiok46 ขัดแย้งกับนโยบายของ Github แก้ไขแล้วให้ชี้ไปยังที่อยู่แบบบัญญัติ
Roberto Rosario

6

คุณควรใช้ sendfile apis ที่กำหนดโดยเซิร์ฟเวอร์ยอดนิยมเช่นapacheหรือnginx ในการผลิต หลายปีที่ฉันใช้ sendfile api ของเซิร์ฟเวอร์เหล่านี้เพื่อป้องกันไฟล์ จากนั้นสร้างมิดเดิลแวร์ที่เรียบง่ายตาม Django app สำหรับวัตถุประสงค์นี้เหมาะสำหรับทั้งการพัฒนาและการผลิต purpose.You สามารถเข้าถึงรหัสที่มาที่นี่
ปรับปรุง: ในpythonผู้ให้บริการรุ่นใหม่ใช้ django FileResponseถ้ามีและยังเพิ่มการสนับสนุนสำหรับการใช้งานเซิร์ฟเวอร์จำนวนมากจาก lighthttp, แคดดี้ไป hiawatha

การใช้

pip install django-fileprovider
  • เพิ่มfileproviderแอพในINSTALLED_APPSการตั้งค่า
  • เพิ่มfileprovider.middleware.FileProviderMiddlewareการMIDDLEWARE_CLASSESตั้งค่า
  • ตั้งFILEPROVIDER_NAMEค่าการตั้งค่าเป็นnginxหรือapacheกำลังการผลิตตามค่าเริ่มต้นpythonเพื่อการพัฒนา

ในมุมมอง classbased หรือฟังก์ชั่นของคุณตั้งค่าการตอบสนองส่วนหัวX-Fileเพื่อเส้นทางที่แน่นอนไปยังไฟล์ ตัวอย่างเช่น,

def hello(request):  
   // code to check or protect the file from unauthorized access
   response = HttpResponse()  
   response['X-File'] = '/absolute/path/to/file'  
   return response  

django-fileprovider impemented ในทางที่รหัสของคุณจะต้องมีการปรับเปลี่ยนขั้นต่ำเท่านั้น

การกำหนดค่า Nginx

เพื่อป้องกันไฟล์จากการเข้าถึงโดยตรงคุณสามารถตั้งค่าเป็น

 location /files/ {
  internal;
  root   /home/sideffect0/secret_files/;
 }

ต่อไปนี้nginxเป็นการตั้งค่า URL ตำแหน่ง/files/การเข้าถึงเฉพาะภายในถ้าคุณใช้การกำหนดค่าข้างต้นคุณสามารถตั้งค่า X-File เป็น

response['X-File'] = '/files/filename.extension' 

เมื่อทำสิ่งนี้กับการกำหนดค่า nginx ไฟล์จะได้รับการปกป้องและคุณสามารถควบคุมไฟล์จาก django ได้ views


2

Django แนะนำให้คุณใช้เซิร์ฟเวอร์อื่นเพื่อให้บริการสื่อแบบคงที่ (เซิร์ฟเวอร์อื่นที่ทำงานบนเครื่องเดียวกันนั้นใช้ได้) พวกเขาแนะนำให้ใช้เซิร์ฟเวอร์เช่นlighttp lighttp

การตั้งค่านี้ง่ายมาก อย่างไรก็ตาม หากมีการสร้าง 'somefile.txt' ตามคำขอ (เนื้อหาเป็นแบบไดนามิก) คุณอาจต้องการให้ django แสดงผล

Django Docs - ไฟล์คงที่


2
def qrcodesave(request): 
    import urllib2;   
    url ="http://chart.apis.google.com/chart?cht=qr&chs=300x300&chl=s&chld=H|0"; 
    opener = urllib2.urlopen(url);  
    content_type = "application/octet-stream"
    response = HttpResponse(opener.read(), content_type=content_type)
    response["Content-Disposition"]= "attachment; filename=aktel.png"
    return response 

0

อีกโครงการที่ต้องดู: http://readthedocs.org/docs/django-private-files/en/latest/usage.html ดูดีไม่ได้ทดสอบด้วยตัวเอง

โดยทั่วไปโครงการจะสรุปการกำหนดค่า mod_xsendfile และให้คุณทำสิ่งต่าง ๆ เช่น:

from django.db import models
from django.contrib.auth.models import User
from private_files import PrivateFileField

def is_owner(request, instance):
    return (not request.user.is_anonymous()) and request.user.is_authenticated and
                   instance.owner.pk = request.user.pk

class FileSubmission(models.Model):
    description = models.CharField("description", max_length = 200)
        owner = models.ForeignKey(User)
    uploaded_file = PrivateFileField("file", upload_to = 'uploads', condition = is_owner)

1
request.user.is_authenticated เป็นวิธีการไม่ใช่แอตทริบิวต์ (ไม่ใช่ request.user.is_anonymous ()) เหมือนกันทุกประการกับ request.user.is_authenticated () เนื่องจาก is_authenticated เป็นส่วนกลับของ is_anonymous
ระเบิดเมื่อ

@explodes แม้เลวร้ายที่สุดรหัสที่ถูกต้องจากเอกสารของdjango-private-files...
อาร์มันโดPérezMarqués

0

ผมได้ประสบปัญหาเดียวกันมากขึ้นแล้วครั้งหนึ่งและดำเนินการเพื่อให้ใช้โมดูล xsendfile และดู auth งานตกแต่งDjango-filelibrary รู้สึกอิสระที่จะใช้มันเป็นแรงบันดาลใจสำหรับการแก้ปัญหาของคุณเอง

https://github.com/danielsokolowski/django-filelibrary



0

ฉันทำโครงการนี้ คุณสามารถดู repo GitHub ของฉัน:

https://github.com/nishant-boro/django-rest-framework-download-expert

โมดูลนี้จัดเตรียมวิธีง่ายๆในการให้บริการไฟล์สำหรับการดาวน์โหลดในกรอบงาน django โดยใช้ Apache module Xsendfile นอกจากนี้ยังมีคุณสมบัติเพิ่มเติมในการให้บริการดาวน์โหลดสำหรับผู้ใช้ที่อยู่ในกลุ่มใดกลุ่มหนึ่งโดยเฉพาะ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.