Author: ccahoon Date: 2009-06-30 21:51:48 -0500 (Tue, 30 Jun 2009) New Revision: 11131
Added: django/branches/soc2009/http-wsgi-improvements/tests/regressiontests/sendfile/ django/branches/soc2009/http-wsgi-improvements/tests/regressiontests/sendfile/__init__.py django/branches/soc2009/http-wsgi-improvements/tests/regressiontests/sendfile/models.py django/branches/soc2009/http-wsgi-improvements/tests/regressiontests/sendfile/tests.py django/branches/soc2009/http-wsgi-improvements/tests/regressiontests/sendfile/urls.py django/branches/soc2009/http-wsgi-improvements/tests/regressiontests/sendfile/views.py Modified: django/branches/soc2009/http-wsgi-improvements/django/conf/global_settings.py django/branches/soc2009/http-wsgi-improvements/django/core/handlers/modpython.py django/branches/soc2009/http-wsgi-improvements/django/core/handlers/wsgi.py django/branches/soc2009/http-wsgi-improvements/django/core/servers/basehttp.py django/branches/soc2009/http-wsgi-improvements/django/http/__init__.py django/branches/soc2009/http-wsgi-improvements/docs/ref/request-response.txt django/branches/soc2009/http-wsgi-improvements/tests/urls.py Log: [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. Modified: django/branches/soc2009/http-wsgi-improvements/django/conf/global_settings.py =================================================================== --- django/branches/soc2009/http-wsgi-improvements/django/conf/global_settings.py 2009-07-01 02:04:35 UTC (rev 11130) +++ django/branches/soc2009/http-wsgi-improvements/django/conf/global_settings.py 2009-07-01 02:51:48 UTC (rev 11131) @@ -236,6 +236,10 @@ # Example: "http://media.lawrence.com" 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. FILE_UPLOAD_HANDLERS = ( 'django.core.files.uploadhandler.MemoryFileUploadHandler', Modified: django/branches/soc2009/http-wsgi-improvements/django/core/handlers/modpython.py =================================================================== --- django/branches/soc2009/http-wsgi-improvements/django/core/handlers/modpython.py 2009-07-01 02:04:35 UTC (rev 11130) +++ django/branches/soc2009/http-wsgi-improvements/django/core/handlers/modpython.py 2009-07-01 02:51:48 UTC (rev 11131) @@ -200,11 +200,14 @@ for c in response.cookies.values(): req.headers_out.add('Set-Cookie', c.output(header='')) req.status = response.status_code - try: - for chunk in response: - req.write(chunk) - finally: - response.close() + if isinstance(response, http.HttpResponseSendFile): + req.sendfile(response.sendfile_filename) + else: + try: + for chunk in response: + req.write(chunk) + finally: + response.close() return 0 # mod_python.apache.OK Modified: django/branches/soc2009/http-wsgi-improvements/django/core/handlers/wsgi.py =================================================================== --- django/branches/soc2009/http-wsgi-improvements/django/core/handlers/wsgi.py 2009-07-01 02:04:35 UTC (rev 11130) +++ django/branches/soc2009/http-wsgi-improvements/django/core/handlers/wsgi.py 2009-07-01 02:51:48 UTC (rev 11131) @@ -241,5 +241,16 @@ for c in response.cookies.values(): response_headers.append(('Set-Cookie', str(c.output(header='')))) 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 Modified: django/branches/soc2009/http-wsgi-improvements/django/core/servers/basehttp.py =================================================================== --- django/branches/soc2009/http-wsgi-improvements/django/core/servers/basehttp.py 2009-07-01 02:04:35 UTC (rev 11130) +++ django/branches/soc2009/http-wsgi-improvements/django/core/servers/basehttp.py 2009-07-01 02:51:48 UTC (rev 11131) @@ -314,10 +314,9 @@ to iterate over the data, and to call 'self.close()' once the response is finished. """ - if not self.result_is_file() or not self.sendfile(): - for data in self.result: - self.write(data) - self.finish_content() + for data in self.result: + self.write(data) + self.finish_content() self.close() def get_scheme(self): Modified: django/branches/soc2009/http-wsgi-improvements/django/http/__init__.py =================================================================== --- django/branches/soc2009/http-wsgi-improvements/django/http/__init__.py 2009-07-01 02:04:35 UTC (rev 11130) +++ django/branches/soc2009/http-wsgi-improvements/django/http/__init__.py 2009-07-01 02:51:48 UTC (rev 11131) @@ -415,6 +415,27 @@ raise Exception("This %s instance cannot tell its position" % self.__class__) 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): status_code = 302 Modified: django/branches/soc2009/http-wsgi-improvements/docs/ref/request-response.txt =================================================================== --- django/branches/soc2009/http-wsgi-improvements/docs/ref/request-response.txt 2009-07-01 02:04:35 UTC (rev 11130) +++ django/branches/soc2009/http-wsgi-improvements/docs/ref/request-response.txt 2009-07-01 02:51:48 UTC (rev 11131) @@ -560,10 +560,23 @@ HttpResponse subclasses ----------------------- -Django includes a number of ``HttpResponse`` subclasses that handle different -types of HTTP responses. Like ``HttpResponse``, these subclasses live in -:mod:`django.http`. +Django includes a number of :class:`HttpResponse` subclasses that handle +different types of HTTP responses. Like :class:`HttpResponse`, these subclasses +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 The constructor takes a single argument -- the path to redirect to. This Added: django/branches/soc2009/http-wsgi-improvements/tests/regressiontests/sendfile/__init__.py =================================================================== Added: django/branches/soc2009/http-wsgi-improvements/tests/regressiontests/sendfile/models.py =================================================================== Added: django/branches/soc2009/http-wsgi-improvements/tests/regressiontests/sendfile/tests.py =================================================================== --- django/branches/soc2009/http-wsgi-improvements/tests/regressiontests/sendfile/tests.py (rev 0) +++ django/branches/soc2009/http-wsgi-improvements/tests/regressiontests/sendfile/tests.py 2009-07-01 02:51:48 UTC (rev 11131) @@ -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 Added: django/branches/soc2009/http-wsgi-improvements/tests/regressiontests/sendfile/urls.py =================================================================== --- django/branches/soc2009/http-wsgi-improvements/tests/regressiontests/sendfile/urls.py (rev 0) +++ django/branches/soc2009/http-wsgi-improvements/tests/regressiontests/sendfile/urls.py 2009-07-01 02:51:48 UTC (rev 11131) @@ -0,0 +1,7 @@ +from django.conf.urls.defaults import patterns + +import views + +urlpatterns = patterns('', + (r'^serve_file/(?P<filename>.*)/$', views.serve_file), +) \ No newline at end of file Added: django/branches/soc2009/http-wsgi-improvements/tests/regressiontests/sendfile/views.py =================================================================== --- django/branches/soc2009/http-wsgi-improvements/tests/regressiontests/sendfile/views.py (rev 0) +++ django/branches/soc2009/http-wsgi-improvements/tests/regressiontests/sendfile/views.py 2009-07-01 02:51:48 UTC (rev 11131) @@ -0,0 +1,7 @@ +import urllib + +from django.http import HttpResponseSendFile + +def serve_file(request, filename): + filename = urllib.unquote(filename) + return HttpResponseSendFile(filename) \ No newline at end of file Modified: django/branches/soc2009/http-wsgi-improvements/tests/urls.py =================================================================== --- django/branches/soc2009/http-wsgi-improvements/tests/urls.py 2009-07-01 02:04:35 UTC (rev 11130) +++ django/branches/soc2009/http-wsgi-improvements/tests/urls.py 2009-07-01 02:51:48 UTC (rev 11131) @@ -33,6 +33,9 @@ # test urlconf for syndication tests (r'^syndication/', include('regressiontests.syndication.urls')), + # HttpResponseSendfile tests + (r'^sendfile/', include('regressiontests.sendfile.urls')), + # conditional get views (r'condition/', include('regressiontests.conditional_processing.urls')), ) --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Django updates" group. To post to this group, send email to django-updates@googlegroups.com To unsubscribe from this group, send email to django-updates+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/django-updates?hl=en -~----------~----~----~----~------~----~------~--~---