mirror of
https://github.com/django/django.git
synced 2025-07-04 09:49:12 +00:00
[soc2009/http-wsgi-improvements] Initial HttpResponseSendFile support, changes pulled from 03/21/09 patch on refs #2131.
This does not pass the included regression tests. However, since this feature will be entirely based on these changes, which have already gone through a great number of iterations, I thought it would be sensible to start here. All of the work here is ymasuda, mizatservercave, and mrts (apologies if I missed anyone). I hope to take their work down the final stretch. git-svn-id: http://code.djangoproject.com/svn/django/branches/soc2009/http-wsgi-improvements@11131 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
4df7e8ed9f
commit
2d29d6c4f8
@ -236,6 +236,10 @@ MEDIA_ROOT = ''
|
|||||||
# Example: "http://media.lawrence.com"
|
# Example: "http://media.lawrence.com"
|
||||||
MEDIA_URL = ''
|
MEDIA_URL = ''
|
||||||
|
|
||||||
|
# Header to use in HttpResponseSendFile to inform the handler to serve the
|
||||||
|
# file with efficient handler-specific routines.
|
||||||
|
HTTPRESPONSE_SENDFILE_HEADER = 'X-Sendfile'
|
||||||
|
|
||||||
# List of upload handler classes to be applied in order.
|
# List of upload handler classes to be applied in order.
|
||||||
FILE_UPLOAD_HANDLERS = (
|
FILE_UPLOAD_HANDLERS = (
|
||||||
'django.core.files.uploadhandler.MemoryFileUploadHandler',
|
'django.core.files.uploadhandler.MemoryFileUploadHandler',
|
||||||
|
@ -200,11 +200,14 @@ class ModPythonHandler(BaseHandler):
|
|||||||
for c in response.cookies.values():
|
for c in response.cookies.values():
|
||||||
req.headers_out.add('Set-Cookie', c.output(header=''))
|
req.headers_out.add('Set-Cookie', c.output(header=''))
|
||||||
req.status = response.status_code
|
req.status = response.status_code
|
||||||
try:
|
if isinstance(response, http.HttpResponseSendFile):
|
||||||
for chunk in response:
|
req.sendfile(response.sendfile_filename)
|
||||||
req.write(chunk)
|
else:
|
||||||
finally:
|
try:
|
||||||
response.close()
|
for chunk in response:
|
||||||
|
req.write(chunk)
|
||||||
|
finally:
|
||||||
|
response.close()
|
||||||
|
|
||||||
return 0 # mod_python.apache.OK
|
return 0 # mod_python.apache.OK
|
||||||
|
|
||||||
|
@ -241,5 +241,16 @@ class WSGIHandler(base.BaseHandler):
|
|||||||
for c in response.cookies.values():
|
for c in response.cookies.values():
|
||||||
response_headers.append(('Set-Cookie', str(c.output(header=''))))
|
response_headers.append(('Set-Cookie', str(c.output(header=''))))
|
||||||
start_response(status, response_headers)
|
start_response(status, response_headers)
|
||||||
|
|
||||||
|
if isinstance(response, http.HttpResponseSendFile):
|
||||||
|
filelike = open(response.sendfile_filename, 'rb')
|
||||||
|
if 'wsgi.file_wrapper' in environ:
|
||||||
|
return environ['wsgi.file_wrapper'](filelike,
|
||||||
|
response.block_size)
|
||||||
|
else:
|
||||||
|
# wraps close() as well
|
||||||
|
from django.core.servers.basehttp import FileWrapper
|
||||||
|
return FileWrapper(filelike, response.block_size)
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
@ -314,10 +314,9 @@ class ServerHandler(object):
|
|||||||
to iterate over the data, and to call 'self.close()' once the response
|
to iterate over the data, and to call 'self.close()' once the response
|
||||||
is finished.
|
is finished.
|
||||||
"""
|
"""
|
||||||
if not self.result_is_file() or not self.sendfile():
|
for data in self.result:
|
||||||
for data in self.result:
|
self.write(data)
|
||||||
self.write(data)
|
self.finish_content()
|
||||||
self.finish_content()
|
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
def get_scheme(self):
|
def get_scheme(self):
|
||||||
|
@ -415,6 +415,27 @@ class HttpResponse(object):
|
|||||||
raise Exception("This %s instance cannot tell its position" % self.__class__)
|
raise Exception("This %s instance cannot tell its position" % self.__class__)
|
||||||
return sum([len(chunk) for chunk in self._container])
|
return sum([len(chunk) for chunk in self._container])
|
||||||
|
|
||||||
|
class HttpResponseSendFile(HttpResponse):
|
||||||
|
def __init__(self, path_to_file, content_type=None, block_size=8192):
|
||||||
|
if not content_type:
|
||||||
|
from mimetypes import guess_type
|
||||||
|
content_type = guess_type(path_to_file)[0]
|
||||||
|
if content_type is None:
|
||||||
|
content_type = "application/octet-stream"
|
||||||
|
super(HttpResponseSendFile, self).__init__(None,
|
||||||
|
content_type=content_type)
|
||||||
|
self.sendfile_filename = path_to_file
|
||||||
|
self.block_size = block_size
|
||||||
|
self['Content-Length'] = os.path.getsize(path_to_file)
|
||||||
|
self['Content-Disposition'] = ('attachment; filename=%s' %
|
||||||
|
os.path.basename(path_to_file))
|
||||||
|
self[settings.HTTPRESPONSE_SENDFILE_HEADER] = path_to_file
|
||||||
|
|
||||||
|
def _get_content(self):
|
||||||
|
return open(self.sendfile_filename)
|
||||||
|
|
||||||
|
content = property(_get_content)
|
||||||
|
|
||||||
class HttpResponseRedirect(HttpResponse):
|
class HttpResponseRedirect(HttpResponse):
|
||||||
status_code = 302
|
status_code = 302
|
||||||
|
|
||||||
|
@ -560,9 +560,22 @@ Methods
|
|||||||
HttpResponse subclasses
|
HttpResponse subclasses
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
||||||
Django includes a number of ``HttpResponse`` subclasses that handle different
|
Django includes a number of :class:`HttpResponse` subclasses that handle
|
||||||
types of HTTP responses. Like ``HttpResponse``, these subclasses live in
|
different types of HTTP responses. Like :class:`HttpResponse`, these subclasses
|
||||||
:mod:`django.http`.
|
live in :mod:`django.http`.
|
||||||
|
|
||||||
|
.. class:: HttpResponseSendFile
|
||||||
|
|
||||||
|
.. versionadded:: 1.1
|
||||||
|
|
||||||
|
A special response class for efficient file serving. It informs the HTTP
|
||||||
|
protocol handler to use platform-specific file serving mechanism (if
|
||||||
|
available). The constructor takes three arguments -- the file path and,
|
||||||
|
optionally, the file's content type and block size hint for handlers that
|
||||||
|
need it.
|
||||||
|
|
||||||
|
Note that response middleware will be bypassed if you use
|
||||||
|
:class:`HttpResponseSendFile`.
|
||||||
|
|
||||||
.. class:: HttpResponseRedirect
|
.. class:: HttpResponseRedirect
|
||||||
|
|
||||||
|
0
tests/regressiontests/sendfile/__init__.py
Normal file
0
tests/regressiontests/sendfile/__init__.py
Normal file
0
tests/regressiontests/sendfile/models.py
Normal file
0
tests/regressiontests/sendfile/models.py
Normal file
36
tests/regressiontests/sendfile/tests.py
Normal file
36
tests/regressiontests/sendfile/tests.py
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import urllib, os
|
||||||
|
|
||||||
|
from django.test import TestCase
|
||||||
|
from django.conf import settings
|
||||||
|
from django.core.files import temp as tempfile
|
||||||
|
|
||||||
|
FILE_SIZE = 2 ** 10
|
||||||
|
CONTENT = 'a' * FILE_SIZE
|
||||||
|
|
||||||
|
class SendFileTests(TestCase):
|
||||||
|
def test_sendfile(self):
|
||||||
|
tdir = tempfile.gettempdir()
|
||||||
|
|
||||||
|
file1 = tempfile.NamedTemporaryFile(suffix=".pdf", dir=tdir)
|
||||||
|
file1.write(CONTENT)
|
||||||
|
file1.seek(0)
|
||||||
|
|
||||||
|
response = self.client.get('/sendfile/serve_file/%s/' %
|
||||||
|
urllib.quote(file1.name))
|
||||||
|
|
||||||
|
file1.close()
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(response[settings.HTTPRESPONSE_SENDFILE_HEADER],
|
||||||
|
file1.name)
|
||||||
|
self.assertEqual(response['Content-Disposition'],
|
||||||
|
'attachment; filename=%s' % os.path.basename(file1.name))
|
||||||
|
self.assertEqual(response['Content-Length'], str(FILE_SIZE))
|
||||||
|
self.assertEqual(response['Content-Type'], 'application/pdf')
|
||||||
|
|
||||||
|
# *if* the degraded case is to be supported, add this instead:
|
||||||
|
# self.assertEqual(response.content, CONTENT)
|
||||||
|
get_content = lambda: response.content
|
||||||
|
self.assertRaises(TypeError, get_content)
|
||||||
|
|
||||||
|
# TODO: test middleware bypass etc
|
7
tests/regressiontests/sendfile/urls.py
Normal file
7
tests/regressiontests/sendfile/urls.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
from django.conf.urls.defaults import patterns
|
||||||
|
|
||||||
|
import views
|
||||||
|
|
||||||
|
urlpatterns = patterns('',
|
||||||
|
(r'^serve_file/(?P<filename>.*)/$', views.serve_file),
|
||||||
|
)
|
7
tests/regressiontests/sendfile/views.py
Normal file
7
tests/regressiontests/sendfile/views.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import urllib
|
||||||
|
|
||||||
|
from django.http import HttpResponseSendFile
|
||||||
|
|
||||||
|
def serve_file(request, filename):
|
||||||
|
filename = urllib.unquote(filename)
|
||||||
|
return HttpResponseSendFile(filename)
|
@ -33,6 +33,9 @@ urlpatterns = patterns('',
|
|||||||
# test urlconf for syndication tests
|
# test urlconf for syndication tests
|
||||||
(r'^syndication/', include('regressiontests.syndication.urls')),
|
(r'^syndication/', include('regressiontests.syndication.urls')),
|
||||||
|
|
||||||
|
# HttpResponseSendfile tests
|
||||||
|
(r'^sendfile/', include('regressiontests.sendfile.urls')),
|
||||||
|
|
||||||
# conditional get views
|
# conditional get views
|
||||||
(r'condition/', include('regressiontests.conditional_processing.urls')),
|
(r'condition/', include('regressiontests.conditional_processing.urls')),
|
||||||
)
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user