1
0
mirror of https://github.com/django/django.git synced 2025-07-04 01:39:20 +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:
Chris Cahoon 2009-07-01 02:51:48 +00:00
parent 4df7e8ed9f
commit 2d29d6c4f8
12 changed files with 116 additions and 12 deletions

View File

@ -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',

View File

@ -200,6 +200,9 @@ 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
if isinstance(response, http.HttpResponseSendFile):
req.sendfile(response.sendfile_filename)
else:
try: try:
for chunk in response: for chunk in response:
req.write(chunk) req.write(chunk)

View File

@ -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

View File

@ -314,7 +314,6 @@ 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()

View File

@ -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

View File

@ -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

View File

View 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

View File

@ -0,0 +1,7 @@
from django.conf.urls.defaults import patterns
import views
urlpatterns = patterns('',
(r'^serve_file/(?P<filename>.*)/$', views.serve_file),
)

View File

@ -0,0 +1,7 @@
import urllib
from django.http import HttpResponseSendFile
def serve_file(request, filename):
filename = urllib.unquote(filename)
return HttpResponseSendFile(filename)

View File

@ -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')),
) )