让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的视图服务文件
卢卡斯

127
正是,这就是为什么我问这个问题。
damon

Answers:


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的情况下,这才起作用。

编辑:

django 1.7将mimetype替换为content_type

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

编辑: 对于nginx检查,它使用X-Accel-Redirect而不是apacheX-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有一个更有效的示例,其中Web服务器处理静态文件,而django则不必。后者具有更好的性能,但可能需要更多的设置。两者都有自己的位置。
火箭猴2010年

1
@Ciantic,请参阅btimby的答案,以了解编码问题的解决方案。
mlissner

此解决方案是否可与以下Web服务器配置一起使用?后端:2台或更多台Apache + mod_wsgi个体(VPS)服务器设置为可以相互复制。前端:1台使用上游负载均衡的nginx代理(VPS)服务器,进行轮询。
丹尼尔(Daniel)

12
django 1.7的mimetype被content_type取代
ismailsunni 2015年

88

“下载”只是HTTP标头更改。

请参阅http://docs.djangoproject.com/en/dev/ref/request-response/#telling-the-browser-to-treat-the-response-as-a-file-attachment了解如何通过下载进行响应。

您只需要一个URL定义即可"/download"

请求GETPOST字典将包含该"f=somefile.txt"信息。

您的视图函数将简单地将基本路径与“ f”值合并,打开文件,创建并返回响应对象。它应该少于12行代码。


49
这本质上是正确的(简单的)答案,但是要小心-将文件名作为参数传递意味着用户可以下载任何文件(即,如果传递“ f = / etc / passwd”该怎么办?)可以防止这种情况发生的事情(用户权限等),但请注意这种明显但常见的安全风险。它基本上只是验证输入的一个子集:如果将文件名传递给视图,请在该视图中检查文件名!
rocketmonkeys 2010年

9
针对此安全问题的一个非常简单的解决方法:filepath = filepath.replace('..', '').replace('/', '')
duality_

7
如果您使用表来存储文件信息(包括哪些用户应该能够下载文件),则您需要发送的只是主键而不是文件名,然后由应用程序决定要执行的操作。
爱德华·纽维尔

30

对于一个非常简单但效率不高或可扩展的解决方案,您可以仅使用内置的django serve视图。这对于快速原型或一次性工作非常有用,但是正如在整个问题中已经提到的那样,在生产中应该使用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上提供测试的后备功能也非常有用。
阿米尔·阿里·阿克巴里

我正在做一个独立的django项目,旨在像台式机客户端一样工作,并且效果很好。谢谢!
daigorocub 2014年

1
为什么效率不高?
zinking 2014年

2
@zinking,因为通常应通过apache之类的文件而不是通过django进程来提供文件
Cory

1
我们在这里谈论什么样的性能缺陷?文件是否通过django加载到RAM或某种形式的文件中?为什么django无法提供与nginx相同的效率?
Gershom 2015年

27

S.Lott具有“良好” /简单的解决方案,而elo80ka具有“最佳” /高效的解决方案。这是一个“更好” /中间的解决方案-没有服务器设置,但是对于大型文件,比简单的修复更有效率:

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

基本上,Django仍会处理文件的提供,但不会立即将整个内容加载到内存中。这使您的服务器可以(缓慢地)提供一个大文件,而不会增加内存使用量。

同样,S.Lott的X-SendFile对于较大的文件仍然更好。但是,如果您不能或不想为此烦恼,那么此中间解决方案将为您带来更高的效率,而无需麻烦。


4
那段代码不好。该片段依赖于django.core.servers.httpbase未记录的私有模块,该私有模块自首次创建以来就已存在于该文件中,在代码“ 请勿用于生产用途! ” 的顶部带有一个大警告标志。无论如何,此代码段所依赖的功能已在django 1.9中删除。FileWrapper
eykanal 2015年

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

现在,您可以将文件存储在私有目录中(不在/ media或/ public_html内部),并通过django将它们公开给某些用户或在某些情况下。
希望能帮助到你。

感谢@ elo80ka,@ S.Lott和@Rocketmonkeys的答案,得到了结合所有这些的完美解决方案=)


1
谢谢,这正是我想要的!
ihatecache

1
filename="%s"在Content-Disposition标头中的文件名周围添加双引号,以避免文件名中的空格出现问题。参考:下载时带有空格的文件名将被截断如何在HTTP中编码Content-Disposition标头的filename参数?
Christian Long

1
您的解决方案对我有用。但是我的文件出现“无效的起始字节...”错误。解决了FileWrapper(open(path.abspath(file_name), 'rb'))
马克·米辛

FileWrapper自Django 1.9起已被删除
freethebees

可以使用from wsgiref.util import FileWrapper
Kriss

15

仅提及Django 1.10中可用的FileResponse对象

编辑:在搜索通过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
非常感谢你!它是下载二进制文件的最简单解决方案,并且可以正常工作。
朱莉娅·赵

13

上面提到过,mod_xsendfile方法不允许文件名中包含非ASCII字符。

出于这个原因,我有一个可用于mod_xsendfile的补丁,只要名称是url编码的,它将允许发送任何文件,以及附加的标头:

X-SendFile-Encoding: url

也发送。

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


现在,补丁已折叠到corer库中。
mlissner '02

7

试试:https//pypi.python.org/pypi/django-sendfile/

“一旦Django检查了权限,将文件上传到Web服务器(例如,带有mod_xsendfile的Apache)的抽象提取。”


2
当时(1年前),我的个人分叉有非Apache文件,可用于回退原始存储库尚未包含的文件。
罗伯托·罗萨里奥

为什么删除链接?
kiok46 2015年

@ kiok46与Github政策冲突。编辑以指向规范地址。
罗伯托·罗萨里奥

6

您应该使用流行服务器(例如 生产环境)apachenginx生产环境中提供的sendfile api 。多年以来,我一直在使用这些服务器的sendfile api保护文件。然后创建了一个简单的基于django的中间件应用程序,适合开发和生产目的。您可以在此处访问源代码。
更新:在新版本中,python提供程序使用django(FileResponse如果可用),并且还增加了对从lighthttp,caddy到hiawatha的许多服务器实现的支持

用法

pip install django-fileprovider
  • fileprovider应用添加到INSTALLED_APPS设置,
  • 添加fileprovider.middleware.FileProviderMiddlewareMIDDLEWARE_CLASSES设置
  • FILEPROVIDER_NAME设置设置为生产nginxapache在生产中使用,默认情况下是python出于开发目的。

在基于类或函数的视图中,将响应头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 以仅需最小程度修改代码的方式实现。

Nginx配置

为了防止文件直接访问,您可以将配置设置为

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

在这里nginx设置位置网址/files/只供内部访问,如果您使用上述配置,则可以将X-File设置为

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

通过使用nginx配置执行此操作,文件将受到保护,您也可以从Django控制文件 views


2

Django建议您使用另一台服务器来提供静态媒体(在同一台计算机上运行的另一台服务器就可以了。)他们建议使用诸如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...
阿曼多·佩雷斯·马尔克斯



By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.