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

Reply via email to