Có Django phục vụ các tệp có thể tải xuống


245

Tôi muốn người dùng trên trang web có thể tải xuống các tệp có đường dẫn bị che khuất để họ không thể tải xuống trực tiếp.

Chẳng hạn, tôi muốn URL giống như thế này: http://example.com/download/?f=somefile.txt

Và trên máy chủ, tôi biết rằng tất cả các tệp có thể tải xuống nằm trong thư mục /home/user/files/.

Có cách nào để khiến Django phục vụ tệp đó để tải xuống trái ngược với việc cố gắng tìm URL và Xem để hiển thị không?


2
Tại sao bạn không đơn giản sử dụng Apache để làm điều này? Apache phục vụ nội dung tĩnh nhanh hơn và đơn giản hơn Django từng có thể.
S.Lott

22
Tôi không sử dụng Apache vì tôi không muốn các tệp có thể truy cập mà không có quyền dựa trên Django.
damon

3
Nếu bạn muốn tính đến quyền của người dùng, bạn phải phân phát tệp qua chế độ xem của Django
ukasz

127
Chính xác, đó là lý do tại sao tôi hỏi câu hỏi này.
damon

Câu trả lời:


189

Để "tốt nhất của cả hai thế giới", bạn có thể kết hợp giải pháp của S.Lott với mô-đun xsendfile : django tạo đường dẫn đến tệp (hoặc chính tệp), nhưng việc phục vụ tệp thực tế được xử lý bởi Apache / Lighttpd. Khi bạn đã thiết lập mod_xsendfile, việc tích hợp với chế độ xem của bạn sẽ mất một vài dòng mã:

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

Tất nhiên, điều này sẽ chỉ hoạt động nếu bạn có quyền kiểm soát máy chủ của mình hoặc công ty lưu trữ của bạn đã cài đặt mod_xsendfile.

BIÊN TẬP:

mimetype được thay thế bởi content_type cho django 1.7

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

EDIT: Để nginxkiểm tra điều này , nó sử dụng X-Accel-Redirectthay vì apachetiêu đề X-Sendfile.


6
Nếu tên tệp của bạn, hoặc path_to_file bao gồm các ký tự không phải mã ascii như "ä" hoặc "ö", smart_strthì không hoạt động như dự định vì mô-đun apache X-Sendfile không thể giải mã chuỗi được mã hóa smart_str. Do đó, ví dụ tập tin "Örinää.mp3" không thể được phục vụ. Và nếu một người bỏ qua smart_str, Django sẽ tự ném lỗi mã hóa ascii vì tất cả các tiêu đề được mã hóa sang định dạng ascii trước khi gửi. Cách duy nhất mà tôi biết để khắc phục vấn đề này là giảm tên tệp X-sendfile thành tên chỉ bao gồm ascii.
Ciantic

3
Để rõ ràng hơn: S.Lott có ví dụ đơn giản, chỉ cần phục vụ các tệp trực tiếp từ django, không cần thiết lập nào khác. elo80ka có ví dụ hiệu quả hơn, nơi máy chủ web xử lý các tệp tĩnh và django không phải. Cái sau có hiệu suất tốt hơn, nhưng có thể yêu cầu thiết lập nhiều hơn. Cả hai đều có chỗ đứng của họ.
lửa

1
@Ciantic, xem câu trả lời của btimby cho những gì trông giống như một giải pháp cho vấn đề mã hóa.
mlissner

Giải pháp này có hoạt động với cấu hình máy chủ web sau không? Back-end: 2 hoặc nhiều máy chủ cá nhân (VPS) Apache + mod_wsgi được thiết lập để sao chép lẫn nhau. Front-end: Máy chủ proxy 1 nginx (VPS) sử dụng cân bằng tải ngược dòng, thực hiện quay vòng.
Daniel

12
mimetype được thay thế bằng content_type cho django 1.7
ismailsunni

88

"Tải xuống" chỉ đơn giản là thay đổi tiêu đề HTTP.

Xem http://docs.djangoproject.com/en/dev/ref/request-response/#telling-the-browser-to-treat-the-response-as-a-file-attachment để biết cách phản hồi với bản tải xuống .

Bạn chỉ cần một định nghĩa URL cho "/download".

Yêu cầu GEThoặc POSTtừ điển sẽ có "f=somefile.txt"thông tin.

Hàm xem của bạn sẽ đơn giản hợp nhất đường dẫn cơ sở với fgiá trị "", mở tệp, tạo và trả về một đối tượng phản hồi. Nó nên có ít hơn 12 dòng mã.


49
Đây thực chất là câu trả lời đúng (đơn giản), nhưng một lưu ý - truyền tên tệp dưới dạng tham số có nghĩa là người dùng có thể tải xuống bất kỳ tệp nào (ví dụ: nếu bạn vượt qua "f = / etc / passwd" thì sao? về những điều giúp ngăn chặn điều này (quyền của người dùng, v.v.), nhưng chỉ cần nhận thức được rủi ro bảo mật rõ ràng nhưng phổ biến này. Về cơ bản, đây chỉ là một tập hợp con của xác thực đầu vào: Nếu bạn chuyển tên tệp vào dạng xem, hãy kiểm tra tên tệp trong dạng xem đó!
lửa

9
Một sửa chữa rất đơn giản cho mối quan tâm bảo mật này:filepath = filepath.replace('..', '').replace('/', '')
duality_

7
Nếu bạn sử dụng bảng để lưu trữ thông tin tệp, bao gồm người dùng nào có thể tải xuống, thì tất cả những gì bạn cần gửi là khóa chính, không phải tên tệp và ứng dụng quyết định phải làm gì.
Edward Newell

30

Đối với một giải pháp rất đơn giản nhưng không hiệu quả hoặc có thể mở rộng , bạn chỉ có thể sử dụng chế serveđộ xem django tích hợp . Điều này là tuyệt vời cho các nguyên mẫu nhanh hoặc công việc một lần, nhưng như đã được đề cập trong suốt câu hỏi này, bạn nên sử dụng một cái gì đó như apache hoặc nginx trong sản xuất.

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

Cũng rất hữu ích để cung cấp dự phòng để thử nghiệm trên Windows.
Amir Ali Akbari

Tôi đang làm một dự án django độc lập, dự định hoạt động giống như một máy khách máy tính để bàn, và điều này hoạt động hoàn hảo. Cảm ơn!
daigorocub

1
Tại sao nó không hiệu quả?
zinking

2
@zinking vì các tệp thường được phục vụ thông qua một cái gì đó như apache hơn là thông qua quy trình django
Cory

1
Những loại nhược điểm hiệu suất mà chúng ta đang nói ở đây? Các tập tin có được tải vào RAM hoặc một cái gì đó thuộc loại nếu chúng được phục vụ thông qua django không? Tại sao django không có khả năng phục vụ với hiệu quả tương đương với nginx?
Gershom

27

S.Lott có giải pháp "tốt" / đơn giản và elo80ka có giải pháp "tốt nhất" / hiệu quả. Đây là một giải pháp "tốt hơn" / trung bình - không cần thiết lập máy chủ, nhưng hiệu quả hơn đối với các tệp lớn hơn so với sửa lỗi ngây thơ:

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

Về cơ bản, Django vẫn xử lý việc phục vụ tệp nhưng không tải toàn bộ nội dung vào bộ nhớ cùng một lúc. Điều này cho phép máy chủ của bạn (từ từ) phục vụ một tệp lớn mà không cần tăng cường sử dụng bộ nhớ.

Một lần nữa, X-SendFile của S.Lott vẫn tốt hơn cho các tệp lớn hơn. Nhưng nếu bạn không thể hoặc không muốn làm phiền với điều đó, thì giải pháp trung gian này sẽ giúp bạn đạt được hiệu quả tốt hơn mà không gặp rắc rối.


4
Đoạn trích đó không tốt. Việc bắn tỉa đó phụ thuộc vào django.core.servers.httpbasemô-đun riêng không có giấy tờ, có dấu hiệu cảnh báo lớn ở đầu mã " KHÔNG SỬ DỤNG ĐỂ SỬ DỤNG SẢN XUẤT !!! ", đã có trong tệp kể từ lần đầu tiên được tạo . Trong mọi trường hợp, FileWrapperchức năng đoạn trích này dựa vào đã bị xóa trong django 1.9.
Eykanal

16

Đã thử giải pháp @Rocketmonkeys nhưng các tệp đã tải xuống đang được lưu trữ dưới dạng * .bin và được đặt tên ngẫu nhiên. Tất nhiên điều đó không ổn. Thêm một dòng khác từ @ elo80ka đã giải quyết vấn đề.
Đây là mã tôi đang sử dụng bây giờ:

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

Bây giờ bạn có thể lưu trữ các tệp trong một thư mục riêng (không phải bên trong / media hay / public_html) và hiển thị chúng qua django cho một số người dùng nhất định hoặc trong một số trường hợp nhất định.
Hy vọng nó giúp.

Cảm ơn @ elo80ka, @ S.Lott và @Rocketmonkeys cho câu trả lời, đã có giải pháp hoàn hảo kết hợp tất cả chúng =)


1
Cảm ơn bạn, đây chính xác là những gì tôi đang tìm kiếm!
ihatecache

1
Thêm dấu ngoặc kép xung quanh tên tệp filename="%s"trong tiêu đề Bố trí nội dung, để tránh các vấn đề với khoảng trắng trong tên tệp. Tham khảo: Tên tệp có dấu cách bị cắt bớt khi tải xuống , Làm cách nào để mã hóa tham số tên tệp của tiêu đề Nội dung xử lý trong HTTP?
Christian Long

1
Giải pháp của bạn làm việc cho tôi. Nhưng tôi đã gặp lỗi "byte bắt đầu không hợp lệ ..." cho tệp của mình. Đã giải quyết nó vớiFileWrapper(open(path.abspath(file_name), 'rb'))
Mark Mishyn

FileWrapperđã bị xóa kể từ Django 1.9
freethebees

Có thể sử dụngfrom wsgiref.util import FileWrapper
Kriss

15

Chỉ cần đề cập đến FileResponse đối tượng có sẵn trong Django 1.10

Chỉnh sửa: Chỉ cần chạy vào câu trả lời của riêng tôi trong khi tìm kiếm một cách dễ dàng để truyền phát tệp qua Django, vì vậy đây là một ví dụ đầy đủ hơn (với tôi trong tương lai). Nó giả định rằng tên FileField làimported_file

lượt xem

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

url

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

1
Cảm ơn rât nhiều! Đây là giải pháp đơn giản nhất để tải xuống tệp nhị phân và nó hoạt động.
Julia Zhao

13

Nó đã được đề cập ở trên rằng phương thức mod_xsendfile không cho phép các ký tự không phải ASCII trong tên tệp.

Vì lý do này, tôi có một bản vá có sẵn cho mod_xsendfile sẽ cho phép bất kỳ tệp nào được gửi, miễn là tên được mã hóa url và tiêu đề bổ sung:

X-SendFile-Encoding: url

Được gửi là tốt.

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


Bản vá bây giờ được gấp vào thư viện corer.
mlissner

7

Hãy thử: https://pypi.python.org/pypi/django-sendfile/

"Trừu tượng để giảm tải tệp tải lên máy chủ web (ví dụ: Apache với mod_xsendfile) khi Django đã kiểm tra quyền, v.v."


2
Vào thời điểm đó (1 năm trước), ngã ba cá nhân của tôi có tệp không phải là Apache phục vụ dự phòng, kho lưu trữ ban đầu chưa được đưa vào.
Roberto Rosario

Tại sao bạn xóa liên kết?
kiok46

@ kiok46 Xung đột với chính sách của Github. Chỉnh sửa để chỉ đến địa chỉ chính tắc.
Roberto Rosario

6

Bạn nên sử dụng apis sendfile được cung cấp bởi các máy chủ phổ biến như apachehoặc nginx trong sản xuất. Nhiều năm tôi đã sử dụng api sendfile của các máy chủ này để bảo vệ các tập tin. Sau đó, tạo ra một ứng dụng django dựa trên phần mềm trung gian đơn giản cho mục đích này phù hợp cho cả mục đích phát triển và sản xuất. Bạn có thể truy cập mã nguồn tại đây .
CẬP NHẬT: trong phiên bản mới, pythonnhà cung cấp sử dụng django FileResponsenếu có và cũng thêm hỗ trợ cho nhiều triển khai máy chủ từ lighthttp, caddy đến hiawatha

Sử dụng

pip install django-fileprovider
  • thêm fileproviderứng dụng vào INSTALLED_APPScài đặt,
  • thêm fileprovider.middleware.FileProviderMiddlewarevào MIDDLEWARE_CLASSEScài đặt
  • đặt FILEPROVIDER_NAMEcài đặt thành nginxhoặc apachetrong sản xuất, theo mặc định, nó pythondành cho mục đích phát triển.

trong các khung nhìn lớp hoặc hàm của bạn đặt X-Filegiá trị tiêu đề phản hồi thành đường dẫn tuyệt đối đến tệp. Ví dụ,

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 theo cách mà mã của bạn sẽ chỉ cần sửa đổi tối thiểu.

Cấu hình Nginx

Để bảo vệ tệp khỏi truy cập trực tiếp, bạn có thể đặt cấu hình là

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

Ở đây nginxđặt url vị trí /files/chỉ truy cập vào quốc tế, nếu bạn đang sử dụng cấu hình ở trên, bạn có thể đặt X-File là,

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

Bằng cách thực hiện với cấu hình nginx, tệp sẽ được bảo vệ và bạn cũng có thể kiểm soát tệp từ django views


2

Django khuyên bạn nên sử dụng một máy chủ khác để phục vụ phương tiện tĩnh (một máy chủ khác chạy trên cùng một máy vẫn ổn.) Họ khuyên bạn nên sử dụng các máy chủ đó như lighttp .

Điều này rất đơn giản để thiết lập. Tuy nhiên. nếu 'somefile.txt' được tạo theo yêu cầu (nội dung là động) thì bạn có thể muốn django phục vụ nó.

Django Docs - Tệp tĩnh


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

Một dự án khác để xem tại: http://readthedocs.org/docs/django-private-files/en/latest/usage.html Trông có vẻ hứa hẹn, bản thân chưa thử nghiệm nó.

Về cơ bản, dự án trừu tượng hóa cấu hình mod_xsendfile và cho phép bạn thực hiện những việc như:

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 là một phương thức, không phải là một thuộc tính. (không request.user.is_anonymous ()) giống hệt như request.user.is_authenticated () vì is_authenticated là nghịch đảo của is_anonymous.
phát nổ

@explodes Thậm chí tệ nhất, mã đó là đúng từ các tài liệu của django-private-files...
Armando Pérez Marqués



0

Tôi đã làm một dự án về điều này. Bạn có thể nhìn vào repo github của tôi:

https://github.com/ Vecant-boro/django-rest-framework-doad-expert

Mô-đun này cung cấp một cách đơn giản để phục vụ các tệp để tải xuống trong khung phần còn lại django bằng mô-đun Xsendfile của Apache. Nó cũng có một tính năng bổ sung là chỉ phục vụ tải xuống cho người dùng thuộc một nhóm cụ thể

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.