Code

Ticket #7894: file_wrapper_response2.diff

File file_wrapper_response2.diff, 6.0 KB (added by graham.carlyle@…, 6 years ago)

Updated patch with API updated to allows mod_python support and optional length & offset

Line 
1Index: django/http/__init__.py
2===================================================================
3--- django/http/__init__.py     (revision 8068)
4+++ django/http/__init__.py     (working copy)
5@@ -424,6 +424,40 @@
6     def __init__(self, *args, **kwargs):
7         HttpResponse.__init__(self, *args, **kwargs)
8 
9+
10+class FileWrapper(object):
11+    def __init__(self, file_path, block_size, byte_count, offset):
12+        self.__dict__.update(locals())
13+
14+    def __iter__(self):
15+        self.file = open(self.file_path, 'rb')
16+        self.close = self.file.close
17+        if self.offset:
18+            self.file.seek(self.offset)
19+        self.bytes_left = self.byte_count
20+        return self
21+       
22+    def next(self):
23+        if self.bytes_left > 0:
24+            data_size = min(self.block_size, self.bytes_left)
25+            data = self.file.read(data_size)
26+            if data:
27+                self.bytes_left -= data_size
28+                return data
29+        raise StopIteration
30+
31+
32+class HttpResponseFileWrapper(HttpResponse):
33+    def __init__(self, file_path, block_size=8192, length=None, offset=0, **kwargs):
34+        content_length = length or os.path.getsize(file_path)
35+        HttpResponse.__init__(self, content=FileWrapper(file_path, block_size, content_length, offset), **kwargs)
36+        self.file_path = file_path
37+        self.block_size = block_size
38+        self.length = length
39+        self.offset = offset
40+        self['Content-Length'] = content_length
41+
42+
43 # A backwards compatible alias for HttpRequest.get_host.
44 def get_host(request):
45     return request.get_host()
46Index: django/core/servers/basehttp.py
47===================================================================
48--- django/core/servers/basehttp.py     (revision 8068)
49+++ django/core/servers/basehttp.py     (working copy)
50@@ -312,7 +312,7 @@
51         in the event loop to iterate over the data, and to call
52         'self.close()' once the response is finished.
53         """
54-        if not self.result_is_file() and not self.sendfile():
55+        if not self.result_is_file() or not self.sendfile():
56             for data in self.result:
57                 self.write(data)
58             self.finish_content()
59Index: django/core/handlers/wsgi.py
60===================================================================
61--- django/core/handlers/wsgi.py        (revision 8068)
62+++ django/core/handlers/wsgi.py        (working copy)
63@@ -1,4 +1,5 @@
64 from threading import Lock
65+import os
66 from pprint import pformat
67 try:
68     from cStringIO import StringIO
69@@ -194,6 +195,9 @@
70     initLock = Lock()
71     request_class = WSGIRequest
72 
73+    def wsgi_adaptor_honours_content_length(self, environ):
74+        return 'mod_wsgi.callable_object' in environ
75+   
76     def __call__(self, environ, start_response):
77         from django.conf import settings
78 
79@@ -232,5 +236,15 @@
80         for c in response.cookies.values():
81             response_headers.append(('Set-Cookie', str(c.output(header=''))))
82         start_response(status, response_headers)
83-        return response
84+        if (isinstance(response, http.HttpResponseFileWrapper) and
85+            'wsgi.file_wrapper'in environ and
86+            (response.length is None or
87+             self.wsgi_adaptor_honours_content_length(environ) or
88+             response.length == os.path.getsize(response.file_path))):
89+            f = open(response.file_path, 'rb')
90+            if response.offset:
91+                f.seek(response.offset)
92+            return environ['wsgi.file_wrapper'](f, response.block_size)
93+        else:
94+            return response
95 
96Index: django/core/handlers/modpython.py
97===================================================================
98--- django/core/handlers/modpython.py   (revision 8068)
99+++ django/core/handlers/modpython.py   (working copy)
100@@ -199,11 +199,17 @@
101             req.headers_out.add('Set-Cookie', c.output(header=''))
102         req.status = response.status_code
103         try:
104-            for chunk in response:
105-                req.write(chunk)
106+            if isinstance(response, http.HttpResponseFileWrapper):
107+                if response.length is None:
108+                    length = -1
109+                else:
110+                    length = response.length
111+                req.sendfile(response.file_path, response.offset, length)
112+            else:
113+                for chunk in response:
114+                    req.write(chunk)
115         finally:
116             response.close()
117-
118         return 0 # mod_python.apache.OK
119 
120 def handler(req):
121Index: tests/regressiontests/httpwrappers/tests.py
122===================================================================
123--- tests/regressiontests/httpwrappers/tests.py (revision 8068)
124+++ tests/regressiontests/httpwrappers/tests.py (working copy)
125@@ -426,10 +426,53 @@
126 Traceback (most recent call last):
127 ...
128 UnicodeEncodeError: ..., HTTP response headers must be in US-ASCII format
129-
130+
131+
132+###########################
133+# HttpResponseFileWrapper #
134+###########################
135+
136+Get the path of the test file
137+
138+>>> import os
139+>>> test_file_path = os.path.join(os.path.dirname(__file__), 'test_file.txt')
140+
141+Create a response with the file path
142+
143+>>> r = HttpResponseFileWrapper(test_file_path, mimetype='text/plain')
144+
145+The response's content defaults to an iterator over the file data
146+
147+>>> ''.join(list(r))
148+'123456789 Heres some data!'
149+
150+An offset and length can be provided limit the data read from the file
151+
152+>>> r = HttpResponseFileWrapper(test_file_path, offset=2, length=5)
153+>>> ''.join(list(r))
154+'34567'
155+
156+A block_size can also be passed in as a suggestion for the block size to read
157+
158+>>> r = HttpResponseFileWrapper(test_file_path, offset=2, length=5, block_size=4024)
159+
160+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
161+
162+>>> r.file_path == test_file_path
163+True
164+
165+>>> r.offset
166+2
167+
168+>>> r.length
169+5
170+
171+>>> r.block_size
172+4024
173+
174 """
175 
176-from django.http import QueryDict, HttpResponse
177+from django.http import QueryDict, HttpResponse, HttpResponseFileWrapper
178 
179 if __name__ == "__main__":
180     import doctest