Browse Source

Added concept of response backends to image resizer, so it's now trivial to use something like X-Sendfile and X-Accel-Redirect rather than serving the image data directly through the app.

Moved exceptions into their own module to avoid circular imports
master
Andrew Ingram 15 years ago
parent
commit
163e856480

+ 20
- 37
oscar/apps/image/dynamic/__init__.py View File

@@ -1,32 +1,21 @@
1
-from oscar.apps.image.dynamic.cache import NullCache, DiskCache
1
+from oscar.apps.image.dynamic.cache import DiskCache
2
+from oscar.apps.image.dynamic.exceptions import ResizerConfigurationException, \
3
+    ResizerSyntaxException, ResizerFormatException
2 4
 from oscar.apps.image.dynamic.mods import AutotrimMod, CropMod, ResizeMod
5
+from oscar.apps.image.dynamic.response_backends import DirectResponse
3 6
 from wsgiref.util import request_uri, application_uri
4 7
 import Image
5 8
 import cStringIO
6
-import sys
7
-import os
8
-import math
9 9
 import datetime
10
-
10
+import math
11
+import os
12
+import sys
11 13
 
12 14
 try:
13 15
     import cStringIO as StringIO
14 16
 except:
15 17
     import StringIO
16 18
 
17
-
18
-class ResizerConfigurationException(Exception):
19
-    pass
20
-
21
-
22
-class ResizerSyntaxException(Exception):
23
-    pass
24
-
25
-
26
-class ResizerFormatException(Exception):
27
-    pass
28
-
29
-
30 19
 def get_class(kls):
31 20
     try:
32 21
         parts = kls.split('.')
@@ -90,7 +79,6 @@ class ImageModifier(object):
90 79
     quality = 80
91 80
 
92 81
     def __init__(self, url, config):
93
-
94 82
         if config.get('installed_mods'):
95 83
             mod_overrides = []
96 84
             for v in config['installed_mods']:
@@ -133,8 +121,7 @@ class ImageModifier(object):
133 121
                 self._params = dict(
134 122
                     [(x.split("-")[0], x.split("-")[1]) for x in param_parts])
135 123
                 self._params['type'] = parts[3]
136
-
137
-            except IndexError, e:
124
+            except IndexError:
138 125
                 raise ResizerSyntaxException("Invalid filename syntax")
139 126
         else:
140 127
             raise ResizerSyntaxException("Invalid filename syntax")
@@ -168,6 +155,9 @@ class ImageModifier(object):
168 155
 
169 156
     def get_type(self):
170 157
         return self.output_formats[self._params['type']]
158
+    
159
+    def get_mime_type(self):
160
+        return self.get_type()[1]
171 161
 
172 162
 
173 163
 class BaseImageHandler(object):
@@ -178,20 +168,10 @@ class BaseImageHandler(object):
178 168
     """
179 169
     modifier = ImageModifier
180 170
     cache = DiskCache
181
-
182
-    def build_response(self, data, modifier, start_response):
183
-        """
184
-        Serves the (now) cached image off the disc. It is assumed that the file
185
-        actually exists as it's non-existence should have been picked up while
186
-        checking to see if the cached version is valid.
187
-        """
188
-        status = '200 OK'
189
-
190
-        response_headers = [('Content-type', modifier.get_type()[1]),
191
-                            ('Content-Length', str(len(data)))]
192
-        start_response(status, response_headers)
193
-
194
-        return [data]
171
+    response_backend = DirectResponse
172
+    
173
+    def build_sendfile_response(self, metadata, modifier, start_response):
174
+        pass
195 175
 
196 176
     def __call__(self, environ, start_response):
197 177
         path = environ.get('PATH_INFO')
@@ -204,6 +184,9 @@ class BaseImageHandler(object):
204 184
 
205 185
         if config.get('cache_backend'):
206 186
             self.cache = get_class(config['cache_backend'])
187
+        if config.get('response_backend'):
188
+            self.response_backend = get_class(config['response_backend'])
189
+            
207 190
         try:
208 191
             c = self.cache(path, config)
209 192
             m = self.modifier(path, config)
@@ -213,7 +196,7 @@ class BaseImageHandler(object):
213 196
             if not c.check(m.source_path()):
214 197
                 data = m.generate_image()
215 198
                 c.write(data)
216
-            return self.build_response(c.read(), m, start_response)
199
+            return self.response_backend(m.get_mime_type(),c,start_response).build_response()
217 200
         except Exception, e:
218 201
             return error404(path, start_response)
219 202
 
@@ -235,7 +218,7 @@ class DjangoImageHandler(BaseImageHandler):
235 218
         django_response = HttpResponse()
236 219
 
237 220
         def start_response(status, headers):
238
-            status, reason = status.split(' ', 1)
221
+            status = status.split(' ', 1)[0]
239 222
             django_response.status_code = int(status)
240 223
             for header, value in headers:
241 224
                 django_response[header] = value

+ 8
- 0
oscar/apps/image/dynamic/cache.py View File

@@ -79,6 +79,14 @@ class DiskCache(BaseCache):
79 79
         f = open(path, 'w')
80 80
         f.write(data)
81 81
         f.close()
82
+        
83
+    def file_info(self):
84
+        """
85
+        If we're using X-Sendfile or X-Accel-Redirect we want to return info
86
+        about the file, rather than the actual file content
87
+        """
88
+        size = os.path.getsize(self._cache_path())
89
+        return (self._cache_path(), size)
82 90
 
83 91
     def read(self):
84 92
         f = open(self._cache_path(), "r")

+ 10
- 0
oscar/apps/image/dynamic/exceptions.py View File

@@ -0,0 +1,10 @@
1
+class ResizerConfigurationException(Exception):
2
+    pass
3
+
4
+
5
+class ResizerSyntaxException(Exception):
6
+    pass
7
+
8
+
9
+class ResizerFormatException(Exception):
10
+    pass

+ 71
- 0
oscar/apps/image/dynamic/response_backends.py View File

@@ -0,0 +1,71 @@
1
+from oscar.apps.image.dynamic.exceptions import ResizerConfigurationException
2
+
3
+class BaseResponse(object):
4
+    def __init__(self,mime_type,cache,start_response):
5
+        self.mime_type = mime_type
6
+        self.cache = cache
7
+        self.start_response = start_response
8
+        
9
+    def build_response(self):
10
+        pass
11
+
12
+class DirectResponse(BaseResponse):
13
+    """
14
+    Serve the file directly, can use any caching mechanism
15
+    """
16
+    def build_response(self):
17
+        """
18
+        Serves the (now) cached image off the disc. It is assumed that the file
19
+        actually exists as it's non-existence should have been picked up while
20
+        checking to see if the cached version is valid.
21
+        """
22
+        status = '200 OK'
23
+
24
+        data = self.cache.read()
25
+        
26
+        response_headers = [('Content-Type', self.mime_type),
27
+                            ('Content-Length', str(len(data)))]
28
+        self.start_response(status, response_headers)
29
+
30
+        return [data]
31
+    
32
+class NginxSendfileResponse(BaseResponse):
33
+    """
34
+    This can only work with a disk-based caching system since it only returns
35
+    headers about the file rather than the file data itself
36
+    """
37
+    
38
+    def build_response(self):
39
+        if not hasattr(self.cache, 'file_info'):
40
+            msg = "Cache doesn't implements the method 'file_info'"
41
+            raise ResizerConfigurationException(msg)
42
+
43
+        print 'moooo'
44
+        
45
+        status = '200 OK'
46
+        
47
+        filename, content_length = self.cache.file_info()
48
+        
49
+        response_headers = [('Content-Type', self.mime_type),
50
+                            ('Content-Length', content_length),
51
+                            ('X-Accel-Redirect', filename)]
52
+        
53
+        self.start_response(status, response_headers)
54
+        return ['']
55
+    
56
+class ApacheSendfileResponse(BaseResponse):
57
+    def build_response(self):
58
+        if not hasattr(self.cache, 'file_info'):
59
+            msg = "ApacheSendfileResponse requires a cache that implements the method 'file_data'"
60
+            raise ResizerConfigurationException(msg)
61
+        
62
+        status = '200 OK'
63
+        
64
+        filename, content_length = self.cache.file_info()
65
+        
66
+        response_headers = [('Content-Type', self.mime_type),
67
+                            ('Content-Length', content_length),
68
+                            ('X-Sendfile', filename)]
69
+        
70
+        self.start_response(status, response_headers)
71
+        return ['']

Loading…
Cancel
Save