我希望网站上的用户能够下载文件,但文件路径被模糊处理,以防止其直接下载。
例如,我希望URL看起来像这样:http://example.com/download/?f=somefile.txt
在服务器上,我知道所有可下载的文件都存储在文件夹/home/user/files/
中。
是否有一种方法可以让Django提供该文件以供下载,而不是试图查找URL和View以将其显示出来?
我希望网站上的用户能够下载文件,但文件路径被模糊处理,以防止其直接下载。
例如,我希望URL看起来像这样:http://example.com/download/?f=somefile.txt
在服务器上,我知道所有可下载的文件都存储在文件夹/home/user/files/
中。
是否有一种方法可以让Django提供该文件以供下载,而不是试图查找URL和View以将其显示出来?
为了实现"两全其美",你可以将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
代替apache
的X-Sendfile头。
smart_str
无法正常工作,因为Apache模块X-Sendfile无法解码smart_str
编码的字符串。因此,例如“Örinää.mp3”文件无法提供服务。如果省略smart_str
,Django本身会抛出ASCII编码错误,因为在发送之前,所有头都会被编码为ASCII格式。我所知道避免这个问题的唯一方法是将X-sendfile文件名减少到只包含ASCII字符。 - Ciantic一个 "下载" 就是一个HTTP头的改变。
请查看 http://docs.djangoproject.com/en/dev/ref/request-response/#telling-the-browser-to-treat-the-response-as-a-file-attachment 以了解如何回复下载文件。
你只需要一个"/download"
的URL定义。
请求的GET
或POST
字典中将会有"f=somefile.txt"
的信息。
你的视图函数将简单地将基础路径与“f
”值合并,打开文件,创建并返回响应对象。它应该不超过12行代码。
filepath = filepath.replace('..', '').replace('/', '')
这行代码会将所有的'..'和'/'字符替换为空字符串,从而避免任何可能的路径穿越攻击。 - duality_对于一个非常简单但不高效或可扩展的解决方案,您可以只使用内置的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))
S.Lott提供了“好”/简单的解决方案,elo80ka提供了“最佳”/高效的解决方案。这里是一个“更好”的/中间的解决方案-无需服务器设置,但对于大文件比朴素的修复方法更有效:
http://djangosnippets.org/snippets/365/
基本上,Django仍然处理文件的服务,但不会一次性将整个文件加载到内存中。这使得您的服务器可以(慢慢地)为大文件提供服务而不会增加内存使用情况。
同样,S.Lott的X-SendFile仍然适用于较大的文件。但如果您不能或不想费心去做那个,则此中间解决方案将使您获得更好的效率而无需麻烦。
django.core.servers.httpbase
,在代码开头有个大警示标志 “DON'T USE FOR PRODUCTION USE!!!”,而且自从这个文件创建以来,就一直有这个警示。无论如何,这段代码依赖的FileWrapper
功能已在Django 1.9中删除。 - eykanal在 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'),
...
尝试了@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 的答案,我结合了所有答案得到了完美的解决方案=)
filename="%s"
周围添加双引号,以避免文件名中的空格问题。参考资料:下载文件名带有空格时被截断,如何在HTTP中编码Content-Disposition头的文件名参数? - Christian LongFileWrapper(open(path.abspath(file_name), 'rb'))
解决了这个问题。 - Mark MishynFileWrapper
已被移除。 - freethebeesfrom wsgiref.util import FileWrapper
。 - Kriss如上面提到的,mod_xsendfile方法不允许在文件名中使用非ASCII字符。
因此,我有一个针对mod_xsendfile的补丁,可以允许发送任何文件,只要名称是URL编码,并且附加了额外的头信息:
X-SendFile-Encoding: url
同样也被发送。
apache
或nginx
这样的流行服务器提供的sendfile api来保护文件。多年来,我一直在使用这些服务器的sendfile api来保护文件。然后创建了一个简单的基于django的中间件应用程序,适用于开发和生产目的。您可以在此处访问源代码。python
提供程序如果可用,将使用django FileResponse
,并添加对从lighthttp、caddy到hiawatha的许多服务器实现的支持。
用法
pip install django-fileprovider
fileprovider
应用添加到INSTALLED_APPS
设置中,fileprovider.middleware.FileProviderMiddleware
添加到MIDDLEWARE_CLASSES
设置中FILEPROVIDER_NAME
设置为nginx
或apache
,默认情况下,它是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'
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