Index: django/http/__init__.py
===================================================================
--- django/http/__init__.py	(revision 8135)
+++ django/http/__init__.py	(working copy)
@@ -424,6 +424,40 @@
     def __init__(self, *args, **kwargs):
         HttpResponse.__init__(self, *args, **kwargs)
 
+
+class FileWrapper(object):
+    def __init__(self, file_path, block_size, byte_count, offset):
+        self.__dict__.update(locals())
+
+    def __iter__(self):
+        self.file = open(self.file_path, 'rb')
+        self.close = self.file.close
+        if self.offset:
+            self.file.seek(self.offset)
+        self.bytes_left = self.byte_count
+        return self
+        
+    def next(self):
+        if self.bytes_left > 0:
+            data_size = min(self.block_size, self.bytes_left)
+            data = self.file.read(data_size)
+            if data:
+                self.bytes_left -= data_size
+                return data
+        raise StopIteration
+
+
+class HttpResponseFileWrapper(HttpResponse):
+    def __init__(self, file_path, block_size=8192, length=None, offset=0, **kwargs):
+        content_length = length or (os.path.getsize(file_path) - offset)
+        HttpResponse.__init__(self, content=FileWrapper(file_path, block_size, content_length, offset), **kwargs)
+        self.file_path = file_path
+        self.block_size = block_size
+        self.length = length
+        self.offset = offset
+        self['Content-Length'] = content_length
+
+
 # A backwards compatible alias for HttpRequest.get_host.
 def get_host(request):
     return request.get_host()
Index: django/core/servers/basehttp.py
===================================================================
--- django/core/servers/basehttp.py	(revision 8135)
+++ django/core/servers/basehttp.py	(working copy)
@@ -312,7 +312,7 @@
         in the event loop to iterate over the data, and to call
         'self.close()' once the response is finished.
         """
-        if not self.result_is_file() and not self.sendfile():
+        if not self.result_is_file() or not self.sendfile():
             for data in self.result:
                 self.write(data)
             self.finish_content()
Index: django/core/handlers/wsgi.py
===================================================================
--- django/core/handlers/wsgi.py	(revision 8135)
+++ django/core/handlers/wsgi.py	(working copy)
@@ -1,4 +1,5 @@
 from threading import Lock
+import os
 from pprint import pformat
 try:
     from cStringIO import StringIO
@@ -194,6 +195,9 @@
     initLock = Lock()
     request_class = WSGIRequest
 
+    def wsgi_adaptor_honours_content_length(self, environ):
+        return 'mod_wsgi.callable_object' in environ
+    
     def __call__(self, environ, start_response):
         from django.conf import settings
 
@@ -232,5 +236,15 @@
         for c in response.cookies.values():
             response_headers.append(('Set-Cookie', str(c.output(header=''))))
         start_response(status, response_headers)
-        return response
+        if (isinstance(response, http.HttpResponseFileWrapper) and
+            'wsgi.file_wrapper'in environ and
+            (response.length is None or
+             self.wsgi_adaptor_honours_content_length(environ) or
+             response.length == os.path.getsize(response.file_path) - response.offset)):
+            f = open(response.file_path, 'rb')
+            if response.offset:
+                f.seek(response.offset)
+            return environ['wsgi.file_wrapper'](f, response.block_size)
+        else:
+            return response
 
Index: django/core/handlers/modpython.py
===================================================================
--- django/core/handlers/modpython.py	(revision 8135)
+++ django/core/handlers/modpython.py	(working copy)
@@ -199,11 +199,17 @@
             req.headers_out.add('Set-Cookie', c.output(header=''))
         req.status = response.status_code
         try:
-            for chunk in response:
-                req.write(chunk)
+            if isinstance(response, http.HttpResponseFileWrapper):
+                if response.length is None:
+                    length = -1
+                else:
+                    length = response.length
+                req.sendfile(response.file_path, response.offset, length)
+            else:
+                for chunk in response:
+                    req.write(chunk)
         finally:
             response.close()
-
         return 0 # mod_python.apache.OK
 
 def handler(req):
Index: django/views/static.py
===================================================================
--- django/views/static.py	(revision 8135)
+++ django/views/static.py	(working copy)
@@ -12,7 +12,7 @@
 from email.Utils import parsedate_tz, mktime_tz
 
 from django.template import loader
-from django.http import Http404, HttpResponse, HttpResponseRedirect, HttpResponseNotModified
+from django.http import Http404, HttpResponse, HttpResponseRedirect, HttpResponseNotModified, HttpResponseFileWrapper
 from django.template import Template, Context, TemplateDoesNotExist
 from django.utils.http import http_date
 
@@ -60,10 +60,8 @@
                               statobj[stat.ST_MTIME], statobj[stat.ST_SIZE]):
         return HttpResponseNotModified()
     mimetype = mimetypes.guess_type(fullpath)[0] or 'application/octet-stream'
-    contents = open(fullpath, 'rb').read()
-    response = HttpResponse(contents, mimetype=mimetype)
+    response = HttpResponseFileWrapper(fullpath, mimetype=mimetype)
     response["Last-Modified"] = http_date(statobj[stat.ST_MTIME])
-    response["Content-Length"] = len(contents)
     return response
 
 DEFAULT_DIRECTORY_INDEX_TEMPLATE = """
Index: tests/regressiontests/handlers/__init__.py
===================================================================
Index: tests/regressiontests/handlers/tests.py
===================================================================
--- tests/regressiontests/handlers/tests.py	(revision 0)
+++ tests/regressiontests/handlers/tests.py	(revision 0)
@@ -0,0 +1,84 @@
+"""
+#############################################
+# WSGI handler with HttpResponseFileWrapper #
+#############################################
+
+>>> h = wsgi.WSGIHandler()
+
+Patch handler to shortcut dispatch process and provide a mocked response
+
+>>> import os
+>>> test_file_path = os.path.join(os.path.dirname(__file__), 'test_file.txt')
+>>> r = http.HttpResponseFileWrapper(test_file_path)
+>>> h.get_response = lambda self: r
+
+Mock the environ and start_response
+
+>>> environ = {'REQUEST_METHOD': 'GET'}
+>>> start_response = lambda status, headers: None
+
+No wsgi.file_wrapper in the environ defaults to returning the response
+
+>>> r is h(environ, start_response)
+True
+
+A wsgi.file_wrapper in the environ with no length or offset makes the handler call the file_wrapper with the open file and suggested block_size
+
+>>> file_wrapper = lambda f, block_size: (f, block_size)
+>>> environ = {'REQUEST_METHOD': 'GET', 'wsgi.file_wrapper': file_wrapper}
+
+>>> f, block_size = h(environ, start_response)
+>>> f
+<open file '.../tests/regressiontests/handlers/test_file.txt', mode 'rb' at ...>
+>>> f.tell() == 0
+True
+>>> block_size == r.block_size
+True
+>>> f.close()
+
+An response with an offset causes a file seek before the handler returns
+
+>>> r = http.HttpResponseFileWrapper(test_file_path, offset=4)
+>>> h.get_response = lambda self: r
+
+>>> f, block_size = h(environ, start_response)
+>>> f.tell() == 4
+True
+>>> f.close()
+
+A response with a length that isn't the remaining file length in a wsgi adaptor that isn't recognised as supporting this returns the response
+
+>>> r = http.HttpResponseFileWrapper(test_file_path, length=10)
+>>> h.get_response = lambda self: r
+
+>>> r is h(environ, start_response)
+True
+
+If the length is the remaining file length then all adaptors should support this
+
+>>> r = http.HttpResponseFileWrapper(test_file_path, block_size=8192, length=26)
+>>> h.get_response = lambda self: r
+>>> h(environ, start_response)
+(<open file '.../tests/regressiontests/handlers/test_file.txt', mode 'rb' at ...>, 8192)
+
+>>> r = http.HttpResponseFileWrapper(test_file_path, block_size=8192, offset=1, length=25)
+>>> h.get_response = lambda self: r
+>>> h(environ, start_response)
+(<open file '.../tests/regressiontests/handlers/test_file.txt', mode 'rb' at ...>, 8192)
+
+If the wsgi adaptor is recognised as honouring the response's Content-Length header then the file_wrapper is used even if the length isn't the remaining file length
+
+>>> environ = {'REQUEST_METHOD': 'GET', 'wsgi.file_wrapper': file_wrapper, 'mod_wsgi.callable_object': 'foo'}
+>>> r = http.HttpResponseFileWrapper(test_file_path, block_size=8192, length=5)
+>>> h.get_response = lambda self: r
+>>> h(environ, start_response)
+(<open file '.../tests/regressiontests/handlers/test_file.txt', mode 'rb' at ...>, 8192)
+
+"""
+
+from django.core.handlers import wsgi
+from django import http
+
+if __name__ == "__main__":
+    import doctest
+    doctest.testmod()
Index: tests/regressiontests/handlers/models.py
===================================================================
Index: tests/regressiontests/handlers/test_file.txt
===================================================================
--- tests/regressiontests/handlers/test_file.txt	(revision 0)
+++ tests/regressiontests/handlers/test_file.txt	(revision 0)
@@ -0,0 +1 @@
+123456789 Heres some data!
\ No newline at end of file
Index: tests/regressiontests/httpwrappers/tests.py
===================================================================
--- tests/regressiontests/httpwrappers/tests.py	(revision 8135)
+++ tests/regressiontests/httpwrappers/tests.py	(working copy)
@@ -426,10 +426,61 @@
 Traceback (most recent call last):
 ...
 UnicodeEncodeError: ..., HTTP response headers must be in US-ASCII format
- 
+
+
+###########################
+# HttpResponseFileWrapper # 
+###########################
+
+Get the path of the test file
+
+>>> import os
+>>> test_file_path = os.path.join(os.path.dirname(__file__), 'test_file.txt')
+
+Create a response with the file path
+
+>>> r = HttpResponseFileWrapper(test_file_path, mimetype='text/plain')
+
+The response's content defaults to an iterator over the file data
+
+>>> ''.join(list(r))
+'123456789 Heres some data!'
+
+The Content-Length header is set to size of the file
+
+>>> r['Content-Length']
+'26'
+
+An offset and length can be provided to limit the data read from the file
+
+>>> r = HttpResponseFileWrapper(test_file_path, offset=2, length=5)
+>>> ''.join(list(r))
+'34567'
+>>> r['Content-Length']
+'5'
+>>> r = HttpResponseFileWrapper(test_file_path, offset=2)
+>>> r['Content-Length']
+'24'
+
+A block_size can also be passed in as a suggestion for the block size to read
+
+>>> r = HttpResponseFileWrapper(test_file_path, offset=2, length=5, block_size=4024)
+
+The handler can choose to deliver the file's data using alternative means by using the response's properties rather than treating it as an iterator
+
+>>> r.file_path == test_file_path
+True
+
+>>> r.offset
+2
+>>> r.length
+5
+>>> r.block_size
+4024
+
 """
 
-from django.http import QueryDict, HttpResponse
+from django.http import QueryDict, HttpResponse, HttpResponseFileWrapper
 
 if __name__ == "__main__":
     import doctest
Index: tests/regressiontests/httpwrappers/test_file.txt
===================================================================
--- tests/regressiontests/httpwrappers/test_file.txt	(revision 0)
+++ tests/regressiontests/httpwrappers/test_file.txt	(revision 0)
@@ -0,0 +1 @@
+123456789 Heres some data!
\ No newline at end of file
