208
class Controller(object):
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.
218
def __call__(self, req):
220
Call the method specified in req.environ by RoutesMiddleware.
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)
234
def _serialize(self, data, request):
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.
240
_metadata = getattr(type(self), "_serialization_metadata", {})
241
serializer = Serializer(request.environ, _metadata)
242
return serializer.to_content_type(data)
245
class Serializer(object):
247
Serializes a dictionary to a Content Type specified by a WSGI environment.
250
def __init__(self, environ, metadata=None):
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.
256
self.environ = environ
257
self.metadata = metadata or {}
259
'application/json': self._to_json,
260
'application/xml': self._to_xml}
262
def to_content_type(self, data):
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.
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)
274
def _to_json(self, data):
210
class Request(webob.Request):
211
"""Add some Openstack API-specific logic to the base webob.Request."""
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'
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)
224
content_type = self.content_type
226
if content_type not in allowed_content_types:
227
raise exception.InvalidContentType(content_type=content_type)
232
class JSONRequestDeserializer(object):
233
def has_body(self, request):
235
Returns whether a Webob.Request object will possess an entity body.
237
:param request: Webob.Request object
239
if 'transfer-encoding' in request.headers:
241
elif request.content_length > 0:
246
def from_json(self, datastring):
247
return json.loads(datastring)
249
def default(self, request):
250
if self.has_body(request):
251
return {'body': self.from_json(request.body)}
256
class JSONResponseSerializer(object):
258
def to_json(self, data):
275
259
def sanitizer(obj):
276
260
if isinstance(obj, datetime.datetime):
277
261
return obj.isoformat()
280
264
return json.dumps(data, default=sanitizer)
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=' ')
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)
297
if nodename.endswith('s'):
298
singular = nodename[:-1]
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():
308
result.setAttribute(k, str(v))
310
node = self._to_xml_node(doc, metadata, k, v)
311
result.appendChild(node)
313
node = doc.createTextNode(str(data))
314
result.appendChild(node)
266
def default(self, response, result):
267
response.headers.add('Content-Type', 'application/json')
268
response.body = self.to_json(result)
271
class Resource(object):
273
WSGI app that handles (de)serialization and controller dispatch.
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.
288
def __init__(self, controller, deserializer, serializer):
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
296
self.controller = controller
297
self.serializer = serializer
298
self.deserializer = deserializer
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)
306
deserialized_request = self.dispatch(self.deserializer,
308
action_args.update(deserialized_request)
310
action_result = self.dispatch(self.controller, action,
311
request, **action_args)
313
response = webob.Response()
314
self.dispatch(self.serializer, action, response, action_result)
317
# return unserializable result (typically a webob exc)
321
def dispatch(self, obj, action, *args, **kwargs):
322
"""Find action-specific method on self and call it."""
324
method = getattr(obj, action)
325
except AttributeError:
326
method = getattr(obj, 'default')
328
return method(*args, **kwargs)
330
def get_action_args(self, request_environment):
331
"""Parse dictionary created by routes library."""
333
args = request_environment['wsgiorg.routing_args'][1].copy()
338
del args['controller']