~hudson-openstack/glance/milestone-proposed

« back to all changes in this revision

Viewing changes to glance/common/wsgi.py

  • Committer: Tarmac
  • Author(s): Brian Waldon, Yuriy Taraday, Justin Shepherd, Ewan Mellor, Thierry Carrez
  • Date: 2011-06-28 19:42:20 UTC
  • mfrom: (139.9.1 d2-merge)
  • Revision ID: tarmac-20110628194220-rhxw4nwelxeolztc
Merge diablo-2 development work

Show diffs side-by-side

added added

removed removed

Lines of Context:
34
34
import webob.dec
35
35
import webob.exc
36
36
 
 
37
from glance.common import exception
 
38
 
37
39
 
38
40
class WritableLogger(object):
39
41
    """A thin wrapper that responds to `write` and logs."""
205
207
        return app
206
208
 
207
209
 
208
 
class Controller(object):
209
 
    """
210
 
    WSGI app that reads routing information supplied by RoutesMiddleware
211
 
    and calls the requested action method upon itself.  All action methods
212
 
    must, in addition to their normal parameters, accept a 'req' argument
213
 
    which is the incoming webob.Request.  They raise a webob.exc exception,
214
 
    or return a dict which will be serialized by requested content type.
215
 
    """
216
 
 
217
 
    @webob.dec.wsgify
218
 
    def __call__(self, req):
219
 
        """
220
 
        Call the method specified in req.environ by RoutesMiddleware.
221
 
        """
222
 
        arg_dict = req.environ['wsgiorg.routing_args'][1]
223
 
        action = arg_dict['action']
224
 
        method = getattr(self, action)
225
 
        del arg_dict['controller']
226
 
        del arg_dict['action']
227
 
        arg_dict['req'] = req
228
 
        result = method(**arg_dict)
229
 
        if type(result) is dict:
230
 
            return self._serialize(result, req)
231
 
        else:
232
 
            return result
233
 
 
234
 
    def _serialize(self, data, request):
235
 
        """
236
 
        Serialize the given dict to the response type requested in request.
237
 
        Uses self._serialization_metadata if it exists, which is a dict mapping
238
 
        MIME types to information needed to serialize to that type.
239
 
        """
240
 
        _metadata = getattr(type(self), "_serialization_metadata", {})
241
 
        serializer = Serializer(request.environ, _metadata)
242
 
        return serializer.to_content_type(data)
243
 
 
244
 
 
245
 
class Serializer(object):
246
 
    """
247
 
    Serializes a dictionary to a Content Type specified by a WSGI environment.
248
 
    """
249
 
 
250
 
    def __init__(self, environ, metadata=None):
251
 
        """
252
 
        Create a serializer based on the given WSGI environment.
253
 
        'metadata' is an optional dict mapping MIME types to information
254
 
        needed to serialize a dictionary to that type.
255
 
        """
256
 
        self.environ = environ
257
 
        self.metadata = metadata or {}
258
 
        self._methods = {
259
 
            'application/json': self._to_json,
260
 
            'application/xml': self._to_xml}
261
 
 
262
 
    def to_content_type(self, data):
263
 
        """
264
 
        Serialize a dictionary into a string.  The format of the string
265
 
        will be decided based on the Content Type requested in self.environ:
266
 
        by Accept: header, or by URL suffix.
267
 
        """
268
 
        # FIXME(sirp): for now, supporting json only
269
 
        #mimetype = 'application/xml'
270
 
        mimetype = 'application/json'
271
 
        # TODO(gundlach): determine mimetype from request
272
 
        return self._methods.get(mimetype, repr)(data)
273
 
 
274
 
    def _to_json(self, data):
 
210
class Request(webob.Request):
 
211
    """Add some Openstack API-specific logic to the base webob.Request."""
 
212
 
 
213
    def best_match_content_type(self):
 
214
        """Determine the requested response content-type."""
 
215
        supported = ('application/json',)
 
216
        bm = self.accept.best_match(supported)
 
217
        return bm or 'application/json'
 
218
 
 
219
    def get_content_type(self, allowed_content_types):
 
220
        """Determine content type of the request body."""
 
221
        if not "Content-Type" in self.headers:
 
222
            raise exception.InvalidContentType(content_type=None)
 
223
 
 
224
        content_type = self.content_type
 
225
 
 
226
        if content_type not in allowed_content_types:
 
227
            raise exception.InvalidContentType(content_type=content_type)
 
228
        else:
 
229
            return content_type
 
230
 
 
231
 
 
232
class JSONRequestDeserializer(object):
 
233
    def has_body(self, request):
 
234
        """
 
235
        Returns whether a Webob.Request object will possess an entity body.
 
236
 
 
237
        :param request:  Webob.Request object
 
238
        """
 
239
        if 'transfer-encoding' in request.headers:
 
240
            return True
 
241
        elif request.content_length > 0:
 
242
            return True
 
243
 
 
244
        return False
 
245
 
 
246
    def from_json(self, datastring):
 
247
        return json.loads(datastring)
 
248
 
 
249
    def default(self, request):
 
250
        if self.has_body(request):
 
251
            return {'body': self.from_json(request.body)}
 
252
        else:
 
253
            return {}
 
254
 
 
255
 
 
256
class JSONResponseSerializer(object):
 
257
 
 
258
    def to_json(self, data):
275
259
        def sanitizer(obj):
276
260
            if isinstance(obj, datetime.datetime):
277
261
                return obj.isoformat()
279
263
 
280
264
        return json.dumps(data, default=sanitizer)
281
265
 
282
 
    def _to_xml(self, data):
283
 
        metadata = self.metadata.get('application/xml', {})
284
 
        # We expect data to contain a single key which is the XML root.
285
 
        root_key = data.keys()[0]
286
 
        from xml.dom import minidom
287
 
        doc = minidom.Document()
288
 
        node = self._to_xml_node(doc, metadata, root_key, data[root_key])
289
 
        return node.toprettyxml(indent='    ')
290
 
 
291
 
    def _to_xml_node(self, doc, metadata, nodename, data):
292
 
        """Recursive method to convert data members to XML nodes."""
293
 
        result = doc.createElement(nodename)
294
 
        if type(data) is list:
295
 
            singular = metadata.get('plurals', {}).get(nodename, None)
296
 
            if singular is None:
297
 
                if nodename.endswith('s'):
298
 
                    singular = nodename[:-1]
299
 
                else:
300
 
                    singular = 'item'
301
 
            for item in data:
302
 
                node = self._to_xml_node(doc, metadata, singular, item)
303
 
                result.appendChild(node)
304
 
        elif type(data) is dict:
305
 
            attrs = metadata.get('attributes', {}).get(nodename, {})
306
 
            for k, v in data.items():
307
 
                if k in attrs:
308
 
                    result.setAttribute(k, str(v))
309
 
                else:
310
 
                    node = self._to_xml_node(doc, metadata, k, v)
311
 
                    result.appendChild(node)
312
 
        else:  # atom
313
 
            node = doc.createTextNode(str(data))
314
 
            result.appendChild(node)
315
 
        return result
 
266
    def default(self, response, result):
 
267
        response.headers.add('Content-Type', 'application/json')
 
268
        response.body = self.to_json(result)
 
269
 
 
270
 
 
271
class Resource(object):
 
272
    """
 
273
    WSGI app that handles (de)serialization and controller dispatch.
 
274
 
 
275
    Reads routing information supplied by RoutesMiddleware and calls
 
276
    the requested action method upon its deserializer, controller,
 
277
    and serializer. Those three objects may implement any of the basic
 
278
    controller action methods (create, update, show, index, delete)
 
279
    along with any that may be specified in the api router. A 'default'
 
280
    method may also be implemented to be used in place of any
 
281
    non-implemented actions. Deserializer methods must accept a request
 
282
    argument and return a dictionary. Controller methods must accept a
 
283
    request argument. Additionally, they must also accept keyword
 
284
    arguments that represent the keys returned by the Deserializer. They
 
285
    may raise a webob.exc exception or return a dict, which will be
 
286
    serialized by requested content type.
 
287
    """
 
288
    def __init__(self, controller, deserializer, serializer):
 
289
        """
 
290
        :param controller: object that implement methods created by routes lib
 
291
        :param deserializer: object that supports webob request deserialization
 
292
                             through controller-like actions
 
293
        :param serializer: object that supports webob response serialization
 
294
                           through controller-like actions
 
295
        """
 
296
        self.controller = controller
 
297
        self.serializer = serializer
 
298
        self.deserializer = deserializer
 
299
 
 
300
    @webob.dec.wsgify(RequestClass=Request)
 
301
    def __call__(self, request):
 
302
        """WSGI method that controls (de)serialization and method dispatch."""
 
303
        action_args = self.get_action_args(request.environ)
 
304
        action = action_args.pop('action', None)
 
305
 
 
306
        deserialized_request = self.dispatch(self.deserializer,
 
307
                                             action, request)
 
308
        action_args.update(deserialized_request)
 
309
 
 
310
        action_result = self.dispatch(self.controller, action,
 
311
                                      request, **action_args)
 
312
        try:
 
313
            response = webob.Response()
 
314
            self.dispatch(self.serializer, action, response, action_result)
 
315
            return response
 
316
 
 
317
        # return unserializable result (typically a webob exc)
 
318
        except Exception:
 
319
            return action_result
 
320
 
 
321
    def dispatch(self, obj, action, *args, **kwargs):
 
322
        """Find action-specific method on self and call it."""
 
323
        try:
 
324
            method = getattr(obj, action)
 
325
        except AttributeError:
 
326
            method = getattr(obj, 'default')
 
327
 
 
328
        return method(*args, **kwargs)
 
329
 
 
330
    def get_action_args(self, request_environment):
 
331
        """Parse dictionary created by routes library."""
 
332
        try:
 
333
            args = request_environment['wsgiorg.routing_args'][1].copy()
 
334
        except Exception:
 
335
            return {}
 
336
 
 
337
        try:
 
338
            del args['controller']
 
339
        except KeyError:
 
340
            pass
 
341
 
 
342
        try:
 
343
            del args['format']
 
344
        except KeyError:
 
345
            pass
 
346
 
 
347
        return args