 8994ffcab1
			
		
	
	8994ffcab1
	
	
	
		
			
			2. Added router to Route /datacenters/ and /services/ URLs 3. Added stubs for windc/core/api. 4. Fixed start-up process for service ------------------------------------------------- Now it is working service which will reply for curl http://localhost:8181/tenant_id/datacenters/ curl http://localhost:8181/tenant_id/datacenters/dc_id/services curl http://localhost:8181/tenant_id/datacenters/dc_id/services/service_id
		
			
				
	
	
		
			718 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			718 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # vim: tabstop=4 shiftwidth=4 softtabstop=4
 | |
| 
 | |
| # Copyright 2011 OpenStack LLC.
 | |
| # All Rights Reserved.
 | |
| #
 | |
| #    Licensed under the Apache License, Version 2.0 (the "License"); you may
 | |
| #    not use this file except in compliance with the License. You may obtain
 | |
| #    a copy of the License at
 | |
| #
 | |
| #         http://www.apache.org/licenses/LICENSE-2.0
 | |
| #
 | |
| #    Unless required by applicable law or agreed to in writing, software
 | |
| #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | |
| #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | |
| #    License for the specific language governing permissions and limitations
 | |
| #    under the License.
 | |
| 
 | |
| """Utility methods for working with WSGI servers."""
 | |
| 
 | |
| import datetime
 | |
| import eventlet
 | |
| import eventlet.wsgi
 | |
| 
 | |
| eventlet.patcher.monkey_patch(all=False, socket=True)
 | |
| 
 | |
| import json
 | |
| import logging
 | |
| import sys
 | |
| import routes
 | |
| import routes.middleware
 | |
| import webob.dec
 | |
| import webob.exc
 | |
| from xml.dom import minidom
 | |
| from xml.parsers import expat
 | |
| 
 | |
| from openstack.common import exception
 | |
| 
 | |
| 
 | |
| LOG = logging.getLogger('wsgi')
 | |
| 
 | |
| 
 | |
| class WritableLogger(object):
 | |
|     """A thin wrapper that responds to `write` and logs."""
 | |
| 
 | |
|     def __init__(self, logger, level=logging.DEBUG):
 | |
|         self.logger = logger
 | |
|         self.level = level
 | |
| 
 | |
|     def write(self, msg):
 | |
|         self.logger.log(self.level, msg.strip("\n"))
 | |
| 
 | |
| 
 | |
| def run_server(application, port):
 | |
|     """Run a WSGI server with the given application."""
 | |
|     sock = eventlet.listen(('0.0.0.0', port))
 | |
|     eventlet.wsgi.server(sock, application)
 | |
| 
 | |
| 
 | |
| class Server(object):
 | |
|     """Server class to manage multiple WSGI sockets and applications."""
 | |
| 
 | |
|     def __init__(self, threads=1000):
 | |
|         self.pool = eventlet.GreenPool(threads)
 | |
| 
 | |
|     def start(self, application, port, host='0.0.0.0', backlog=128):
 | |
|         """Run a WSGI server with the given application."""
 | |
|         socket = eventlet.listen((host, port), backlog=backlog)
 | |
|         self.pool.spawn_n(self._run, application, socket)
 | |
| 
 | |
|     def wait(self):
 | |
|         """Wait until all servers have completed running."""
 | |
|         try:
 | |
|             self.pool.waitall()
 | |
|         except KeyboardInterrupt:
 | |
|             pass
 | |
| 
 | |
|     def _run(self, application, socket):
 | |
|         """Start a WSGI server in a new green thread."""
 | |
|         logger = logging.getLogger('eventlet.wsgi.server')
 | |
|         eventlet.wsgi.server(socket, application, custom_pool=self.pool,
 | |
|                              log=WritableLogger(logger))
 | |
| 
 | |
| 
 | |
| class Middleware(object):
 | |
|     """
 | |
|     Base WSGI middleware wrapper. These classes require an application to be
 | |
|     initialized that will be called next.  By default the middleware will
 | |
|     simply call its wrapped app, or you can override __call__ to customize its
 | |
|     behavior.
 | |
|     """
 | |
| 
 | |
|     def __init__(self, application):
 | |
|         self.application = application
 | |
| 
 | |
|     def process_request(self, req):
 | |
|         """
 | |
|         Called on each request.
 | |
| 
 | |
|         If this returns None, the next application down the stack will be
 | |
|         executed. If it returns a response then that response will be returned
 | |
|         and execution will stop here.
 | |
|         """
 | |
|         return None
 | |
| 
 | |
|     def process_response(self, response):
 | |
|         """Do whatever you'd like to the response."""
 | |
|         return response
 | |
| 
 | |
|     @webob.dec.wsgify
 | |
|     def __call__(self, req):
 | |
|         response = self.process_request(req)
 | |
|         if response:
 | |
|             return response
 | |
|         response = req.get_response(self.application)
 | |
|         return self.process_response(response)
 | |
| 
 | |
| 
 | |
| class Debug(Middleware):
 | |
|     """
 | |
|     Helper class that can be inserted into any WSGI application chain
 | |
|     to get information about the request and response.
 | |
|     """
 | |
| 
 | |
|     @webob.dec.wsgify
 | |
|     def __call__(self, req):
 | |
|         print ("*" * 40) + " REQUEST ENVIRON"
 | |
|         for key, value in req.environ.items():
 | |
|             print key, "=", value
 | |
|         print
 | |
|         resp = req.get_response(self.application)
 | |
| 
 | |
|         print ("*" * 40) + " RESPONSE HEADERS"
 | |
|         for (key, value) in resp.headers.iteritems():
 | |
|             print key, "=", value
 | |
|         print
 | |
| 
 | |
|         resp.app_iter = self.print_generator(resp.app_iter)
 | |
| 
 | |
|         return resp
 | |
| 
 | |
|     @staticmethod
 | |
|     def print_generator(app_iter):
 | |
|         """
 | |
|         Iterator that prints the contents of a wrapper string iterator
 | |
|         when iterated.
 | |
|         """
 | |
|         print ("*" * 40) + " BODY"
 | |
|         for part in app_iter:
 | |
|             sys.stdout.write(part)
 | |
|             sys.stdout.flush()
 | |
|             yield part
 | |
|         print
 | |
| 
 | |
| 
 | |
| class Router(object):
 | |
| 
 | |
|     """
 | |
|     WSGI middleware that maps incoming requests to WSGI apps.
 | |
|     """
 | |
| 
 | |
|     def __init__(self, mapper):
 | |
|         """
 | |
|         Create a router for the given routes.Mapper.
 | |
| 
 | |
|         Each route in `mapper` must specify a 'controller', which is a
 | |
|         WSGI app to call.  You'll probably want to specify an 'action' as
 | |
|         well and have your controller be a wsgi.Controller, who will route
 | |
|         the request to the action method.
 | |
| 
 | |
|         Examples:
 | |
|           mapper = routes.Mapper()
 | |
|           sc = ServerController()
 | |
| 
 | |
|           # Explicit mapping of one route to a controller+action
 | |
|           mapper.connect(None, "/svrlist", controller=sc, action="list")
 | |
| 
 | |
|           # Actions are all implicitly defined
 | |
|           mapper.resource("server", "servers", controller=sc)
 | |
| 
 | |
|           # Pointing to an arbitrary WSGI app.  You can specify the
 | |
|           # {path_info:.*} parameter so the target app can be handed just that
 | |
|           # section of the URL.
 | |
|           mapper.connect(None, "/v1.0/{path_info:.*}", controller=BlogApp())
 | |
|         """
 | |
|         self.map = mapper
 | |
|         self._router = routes.middleware.RoutesMiddleware(self._dispatch,
 | |
|                                                           self.map)
 | |
| 
 | |
|     @webob.dec.wsgify
 | |
|     def __call__(self, req):
 | |
|         """
 | |
|         Route the incoming request to a controller based on self.map.
 | |
|         If no match, return a 404.
 | |
|         """
 | |
|         return self._router
 | |
| 
 | |
|     @staticmethod
 | |
|     @webob.dec.wsgify
 | |
|     def _dispatch(req):
 | |
|         """
 | |
|         Called by self._router after matching the incoming request to a route
 | |
|         and putting the information into req.environ.  Either returns 404
 | |
|         or the routed WSGI app's response.
 | |
|         """
 | |
|         match = req.environ['wsgiorg.routing_args'][1]
 | |
|         if not match:
 | |
|             return webob.exc.HTTPNotFound()
 | |
|         app = match['controller']
 | |
|         return app
 | |
| 
 | |
| 
 | |
| class Request(webob.Request):
 | |
|     """Add some Openstack API-specific logic to the base webob.Request."""
 | |
| 
 | |
|     default_request_content_types = ('application/json', 'application/xml')
 | |
|     default_accept_types = ('application/json', 'application/xml')
 | |
|     default_accept_type = 'application/json'
 | |
| 
 | |
|     def best_match_content_type(self, supported_content_types=None):
 | |
|         """Determine the requested response content-type.
 | |
| 
 | |
|         Based on the query extension then the Accept header.
 | |
|         Defaults to default_accept_type if we don't find a preference
 | |
| 
 | |
|         """
 | |
|         supported_content_types = (supported_content_types or
 | |
|                                    self.default_accept_types)
 | |
| 
 | |
|         parts = self.path.rsplit('.', 1)
 | |
|         if len(parts) > 1:
 | |
|             ctype = 'application/{0}'.format(parts[1])
 | |
|             if ctype in supported_content_types:
 | |
|                 return ctype
 | |
| 
 | |
|         bm = self.accept.best_match(supported_content_types)
 | |
|         return bm or self.default_accept_type
 | |
| 
 | |
|     def get_content_type(self, allowed_content_types=None):
 | |
|         """Determine content type of the request body.
 | |
| 
 | |
|         Does not do any body introspection, only checks header
 | |
| 
 | |
|         """
 | |
|         if not "Content-Type" in self.headers:
 | |
|             return None
 | |
| 
 | |
|         content_type = self.content_type
 | |
|         allowed_content_types = (allowed_content_types or
 | |
|                                  self.default_request_content_types)
 | |
| 
 | |
|         if content_type not in allowed_content_types:
 | |
|             raise exception.InvalidContentType(content_type=content_type)
 | |
|         return content_type
 | |
| 
 | |
| 
 | |
| class Resource(object):
 | |
|     """
 | |
|     WSGI app that handles (de)serialization and controller dispatch.
 | |
| 
 | |
|     Reads routing information supplied by RoutesMiddleware and calls
 | |
|     the requested action method upon its deserializer, controller,
 | |
|     and serializer. Those three objects may implement any of the basic
 | |
|     controller action methods (create, update, show, index, delete)
 | |
|     along with any that may be specified in the api router. A 'default'
 | |
|     method may also be implemented to be used in place of any
 | |
|     non-implemented actions. Deserializer methods must accept a request
 | |
|     argument and return a dictionary. Controller methods must accept a
 | |
|     request argument. Additionally, they must also accept keyword
 | |
|     arguments that represent the keys returned by the Deserializer. They
 | |
|     may raise a webob.exc exception or return a dict, which will be
 | |
|     serialized by requested content type.
 | |
|     """
 | |
|     def __init__(self, controller, deserializer=None, serializer=None):
 | |
|         """
 | |
|         :param controller: object that implement methods created by routes lib
 | |
|         :param deserializer: object that supports webob request deserialization
 | |
|                              through controller-like actions
 | |
|         :param serializer: object that supports webob response serialization
 | |
|                            through controller-like actions
 | |
|         """
 | |
|         self.controller = controller
 | |
|         self.serializer = serializer or ResponseSerializer()
 | |
|         self.deserializer = deserializer or RequestDeserializer()
 | |
| 
 | |
|     @webob.dec.wsgify(RequestClass=Request)
 | |
|     def __call__(self, request):
 | |
|         """WSGI method that controls (de)serialization and method dispatch."""
 | |
| 
 | |
|         try:
 | |
|             action, action_args, accept = self.deserialize_request(request)
 | |
|         except exception.InvalidContentType:
 | |
|             msg = _("Unsupported Content-Type")
 | |
|             return webob.exc.HTTPUnsupportedMediaType(explanation=msg)
 | |
|         except exception.MalformedRequestBody:
 | |
|             msg = _("Malformed request body")
 | |
|             return webob.exc.HTTPBadRequest(explanation=msg)
 | |
| 
 | |
|         action_result = self.execute_action(action, request, **action_args)
 | |
|         try:
 | |
|             return self.serialize_response(action, action_result, accept)
 | |
|         # return unserializable result (typically a webob exc)
 | |
|         except Exception:
 | |
|             return action_result
 | |
| 
 | |
|     def deserialize_request(self, request):
 | |
|         return self.deserializer.deserialize(request)
 | |
| 
 | |
|     def serialize_response(self, action, action_result, accept):
 | |
|         return self.serializer.serialize(action_result, accept, action)
 | |
| 
 | |
|     def execute_action(self, action, request, **action_args):
 | |
|         return self.dispatch(self.controller, action, request, **action_args)
 | |
| 
 | |
|     def dispatch(self, obj, action, *args, **kwargs):
 | |
|         """Find action-specific method on self and call it."""
 | |
|         try:
 | |
|             method = getattr(obj, action)
 | |
|         except AttributeError:
 | |
|             method = getattr(obj, 'default')
 | |
| 
 | |
|         return method(*args, **kwargs)
 | |
| 
 | |
|     def get_action_args(self, request_environment):
 | |
|         """Parse dictionary created by routes library."""
 | |
|         try:
 | |
|             args = request_environment['wsgiorg.routing_args'][1].copy()
 | |
|         except Exception:
 | |
|             return {}
 | |
| 
 | |
|         try:
 | |
|             del args['controller']
 | |
|         except KeyError:
 | |
|             pass
 | |
| 
 | |
|         try:
 | |
|             del args['format']
 | |
|         except KeyError:
 | |
|             pass
 | |
| 
 | |
|         return args
 | |
| 
 | |
| 
 | |
| class ActionDispatcher(object):
 | |
|     """Maps method name to local methods through action name."""
 | |
| 
 | |
|     def dispatch(self, *args, **kwargs):
 | |
|         """Find and call local method."""
 | |
|         action = kwargs.pop('action', 'default')
 | |
|         action_method = getattr(self, str(action), self.default)
 | |
|         return action_method(*args, **kwargs)
 | |
| 
 | |
|     def default(self, data):
 | |
|         raise NotImplementedError()
 | |
| 
 | |
| 
 | |
| class DictSerializer(ActionDispatcher):
 | |
|     """Default request body serialization"""
 | |
| 
 | |
|     def serialize(self, data, action='default'):
 | |
|         return self.dispatch(data, action=action)
 | |
| 
 | |
|     def default(self, data):
 | |
|         return ""
 | |
| 
 | |
| 
 | |
| class JSONDictSerializer(DictSerializer):
 | |
|     """Default JSON request body serialization"""
 | |
| 
 | |
|     def default(self, data):
 | |
|         def sanitizer(obj):
 | |
|             if isinstance(obj, datetime.datetime):
 | |
|                 _dtime = obj - datetime.timedelta(microseconds=obj.microsecond)
 | |
|                 return _dtime.isoformat()
 | |
|             return obj
 | |
|         return json.dumps(data, default=sanitizer)
 | |
| 
 | |
| 
 | |
| class XMLDictSerializer(DictSerializer):
 | |
| 
 | |
|     def __init__(self, metadata=None, xmlns=None):
 | |
|         """
 | |
|         :param metadata: information needed to deserialize xml into
 | |
|                          a dictionary.
 | |
|         :param xmlns: XML namespace to include with serialized xml
 | |
|         """
 | |
|         super(XMLDictSerializer, self).__init__()
 | |
|         self.metadata = metadata or {}
 | |
|         self.xmlns = xmlns
 | |
| 
 | |
|     def default(self, data):
 | |
|         # We expect data to contain a single key which is the XML root.
 | |
|         root_key = data.keys()[0]
 | |
|         doc = minidom.Document()
 | |
|         node = self._to_xml_node(doc, self.metadata, root_key, data[root_key])
 | |
| 
 | |
|         return self.to_xml_string(node)
 | |
| 
 | |
|     def to_xml_string(self, node, has_atom=False):
 | |
|         self._add_xmlns(node, has_atom)
 | |
|         return node.toprettyxml(indent='    ', encoding='UTF-8')
 | |
| 
 | |
|     #NOTE (ameade): the has_atom should be removed after all of the
 | |
|     # xml serializers and view builders have been updated to the current
 | |
|     # spec that required all responses include the xmlns:atom, the has_atom
 | |
|     # flag is to prevent current tests from breaking
 | |
|     def _add_xmlns(self, node, has_atom=False):
 | |
|         if self.xmlns is not None:
 | |
|             node.setAttribute('xmlns', self.xmlns)
 | |
|         if has_atom:
 | |
|             node.setAttribute('xmlns:atom', "http://www.w3.org/2005/Atom")
 | |
| 
 | |
|     def _to_xml_node(self, doc, metadata, nodename, data):
 | |
|         """Recursive method to convert data members to XML nodes."""
 | |
|         result = doc.createElement(nodename)
 | |
| 
 | |
|         # Set the xml namespace if one is specified
 | |
|         # TODO(justinsb): We could also use prefixes on the keys
 | |
|         xmlns = metadata.get('xmlns', None)
 | |
|         if xmlns:
 | |
|             result.setAttribute('xmlns', xmlns)
 | |
| 
 | |
|         #TODO(bcwaldon): accomplish this without a type-check
 | |
|         if type(data) is list:
 | |
|             collections = metadata.get('list_collections', {})
 | |
|             if nodename in collections:
 | |
|                 metadata = collections[nodename]
 | |
|                 for item in data:
 | |
|                     node = doc.createElement(metadata['item_name'])
 | |
|                     node.setAttribute(metadata['item_key'], str(item))
 | |
|                     result.appendChild(node)
 | |
|                 return result
 | |
|             singular = metadata.get('plurals', {}).get(nodename, None)
 | |
|             if singular is None:
 | |
|                 if nodename.endswith('s'):
 | |
|                     singular = nodename[:-1]
 | |
|                 else:
 | |
|                     singular = 'item'
 | |
|             for item in data:
 | |
|                 node = self._to_xml_node(doc, metadata, singular, item)
 | |
|                 result.appendChild(node)
 | |
|         #TODO(bcwaldon): accomplish this without a type-check
 | |
|         elif type(data) is dict:
 | |
|             collections = metadata.get('dict_collections', {})
 | |
|             if nodename in collections:
 | |
|                 metadata = collections[nodename]
 | |
|                 for k, v in data.items():
 | |
|                     node = doc.createElement(metadata['item_name'])
 | |
|                     node.setAttribute(metadata['item_key'], str(k))
 | |
|                     text = doc.createTextNode(str(v))
 | |
|                     node.appendChild(text)
 | |
|                     result.appendChild(node)
 | |
|                 return result
 | |
|             attrs = metadata.get('attributes', {}).get(nodename, {})
 | |
|             for k, v in data.items():
 | |
|                 if k in attrs:
 | |
|                     result.setAttribute(k, str(v))
 | |
|                 else:
 | |
|                     node = self._to_xml_node(doc, metadata, k, v)
 | |
|                     result.appendChild(node)
 | |
|         else:
 | |
|             # Type is atom
 | |
|             node = doc.createTextNode(str(data))
 | |
|             result.appendChild(node)
 | |
|         return result
 | |
| 
 | |
|     def _create_link_nodes(self, xml_doc, links):
 | |
|         link_nodes = []
 | |
|         for link in links:
 | |
|             link_node = xml_doc.createElement('atom:link')
 | |
|             link_node.setAttribute('rel', link['rel'])
 | |
|             link_node.setAttribute('href', link['href'])
 | |
|             if 'type' in link:
 | |
|                 link_node.setAttribute('type', link['type'])
 | |
|             link_nodes.append(link_node)
 | |
|         return link_nodes
 | |
| 
 | |
| 
 | |
| class ResponseHeadersSerializer(ActionDispatcher):
 | |
|     """Default response headers serialization"""
 | |
| 
 | |
|     def serialize(self, response, data, action):
 | |
|         self.dispatch(response, data, action=action)
 | |
| 
 | |
|     def default(self, response, data):
 | |
|         response.status_int = 200
 | |
| 
 | |
| 
 | |
| class ResponseSerializer(object):
 | |
|     """Encode the necessary pieces into a response object"""
 | |
| 
 | |
|     def __init__(self, body_serializers=None, headers_serializer=None):
 | |
|         self.body_serializers = {
 | |
|             'application/xml': XMLDictSerializer(),
 | |
|             'application/json': JSONDictSerializer(),
 | |
|         }
 | |
|         self.body_serializers.update(body_serializers or {})
 | |
| 
 | |
|         self.headers_serializer = headers_serializer or \
 | |
|                                     ResponseHeadersSerializer()
 | |
| 
 | |
|     def serialize(self, response_data, content_type, action='default'):
 | |
|         """Serialize a dict into a string and wrap in a wsgi.Request object.
 | |
| 
 | |
|         :param response_data: dict produced by the Controller
 | |
|         :param content_type: expected mimetype of serialized response body
 | |
| 
 | |
|         """
 | |
|         response = webob.Response()
 | |
|         self.serialize_headers(response, response_data, action)
 | |
|         self.serialize_body(response, response_data, content_type, action)
 | |
|         return response
 | |
| 
 | |
|     def serialize_headers(self, response, data, action):
 | |
|         self.headers_serializer.serialize(response, data, action)
 | |
| 
 | |
|     def serialize_body(self, response, data, content_type, action):
 | |
|         response.headers['Content-Type'] = content_type
 | |
|         if data is not None:
 | |
|             serializer = self.get_body_serializer(content_type)
 | |
|             response.body = serializer.serialize(data, action)
 | |
| 
 | |
|     def get_body_serializer(self, content_type):
 | |
|         try:
 | |
|             return self.body_serializers[content_type]
 | |
|         except (KeyError, TypeError):
 | |
|             raise exception.InvalidContentType(content_type=content_type)
 | |
| 
 | |
| 
 | |
| class RequestHeadersDeserializer(ActionDispatcher):
 | |
|     """Default request headers deserializer"""
 | |
| 
 | |
|     def deserialize(self, request, action):
 | |
|         return self.dispatch(request, action=action)
 | |
| 
 | |
|     def default(self, request):
 | |
|         return {}
 | |
| 
 | |
| 
 | |
| class RequestDeserializer(object):
 | |
|     """Break up a Request object into more useful pieces."""
 | |
| 
 | |
|     def __init__(self, body_deserializers=None, headers_deserializer=None,
 | |
|                  supported_content_types=None):
 | |
| 
 | |
|         self.supported_content_types = supported_content_types
 | |
| 
 | |
|         self.body_deserializers = {
 | |
|             'application/xml': XMLDeserializer(),
 | |
|             'application/json': JSONDeserializer(),
 | |
|         }
 | |
|         self.body_deserializers.update(body_deserializers or {})
 | |
| 
 | |
|         self.headers_deserializer = headers_deserializer or \
 | |
|                                         RequestHeadersDeserializer()
 | |
| 
 | |
|     def deserialize(self, request):
 | |
|         """Extract necessary pieces of the request.
 | |
| 
 | |
|         :param request: Request object
 | |
|         :returns tuple of expected controller action name, dictionary of
 | |
|                  keyword arguments to pass to the controller, the expected
 | |
|                  content type of the response
 | |
| 
 | |
|         """
 | |
|         action_args = self.get_action_args(request.environ)
 | |
|         action = action_args.pop('action', None)
 | |
| 
 | |
|         action_args.update(self.deserialize_headers(request, action))
 | |
|         action_args.update(self.deserialize_body(request, action))
 | |
| 
 | |
|         accept = self.get_expected_content_type(request)
 | |
| 
 | |
|         return (action, action_args, accept)
 | |
| 
 | |
|     def deserialize_headers(self, request, action):
 | |
|         return self.headers_deserializer.deserialize(request, action)
 | |
| 
 | |
|     def deserialize_body(self, request, action):
 | |
|         if not len(request.body) > 0:
 | |
|             LOG.debug(_("Empty body provided in request"))
 | |
|             return {}
 | |
| 
 | |
|         try:
 | |
|             content_type = request.get_content_type()
 | |
|         except exception.InvalidContentType:
 | |
|             LOG.debug(_("Unrecognized Content-Type provided in request"))
 | |
|             raise
 | |
| 
 | |
|         if content_type is None:
 | |
|             LOG.debug(_("No Content-Type provided in request"))
 | |
|             return {}
 | |
| 
 | |
|         try:
 | |
|             deserializer = self.get_body_deserializer(content_type)
 | |
|         except exception.InvalidContentType:
 | |
|             LOG.debug(_("Unable to deserialize body as provided Content-Type"))
 | |
|             raise
 | |
| 
 | |
|         return deserializer.deserialize(request.body, action)
 | |
| 
 | |
|     def get_body_deserializer(self, content_type):
 | |
|         try:
 | |
|             return self.body_deserializers[content_type]
 | |
|         except (KeyError, TypeError):
 | |
|             raise exception.InvalidContentType(content_type=content_type)
 | |
| 
 | |
|     def get_expected_content_type(self, request):
 | |
|         return request.best_match_content_type(self.supported_content_types)
 | |
| 
 | |
|     def get_action_args(self, request_environment):
 | |
|         """Parse dictionary created by routes library."""
 | |
|         try:
 | |
|             args = request_environment['wsgiorg.routing_args'][1].copy()
 | |
|         except Exception:
 | |
|             return {}
 | |
| 
 | |
|         try:
 | |
|             del args['controller']
 | |
|         except KeyError:
 | |
|             pass
 | |
| 
 | |
|         try:
 | |
|             del args['format']
 | |
|         except KeyError:
 | |
|             pass
 | |
| 
 | |
|         return args
 | |
| 
 | |
| 
 | |
| class TextDeserializer(ActionDispatcher):
 | |
|     """Default request body deserialization"""
 | |
| 
 | |
|     def deserialize(self, datastring, action='default'):
 | |
|         return self.dispatch(datastring, action=action)
 | |
| 
 | |
|     def default(self, datastring):
 | |
|         return {}
 | |
| 
 | |
| 
 | |
| class JSONDeserializer(TextDeserializer):
 | |
| 
 | |
|     def _from_json(self, datastring):
 | |
|         try:
 | |
|             return json.loads(datastring)
 | |
|         except ValueError:
 | |
|             msg = _("cannot understand JSON")
 | |
|             raise exception.MalformedRequestBody(reason=msg)
 | |
| 
 | |
|     def default(self, datastring):
 | |
|         return {'body': self._from_json(datastring)}
 | |
| 
 | |
| 
 | |
| class XMLDeserializer(TextDeserializer):
 | |
| 
 | |
|     def __init__(self, metadata=None):
 | |
|         """
 | |
|         :param metadata: information needed to deserialize xml into
 | |
|                          a dictionary.
 | |
|         """
 | |
|         super(XMLDeserializer, self).__init__()
 | |
|         self.metadata = metadata or {}
 | |
| 
 | |
|     def _from_xml(self, datastring):
 | |
|         plurals = set(self.metadata.get('plurals', {}))
 | |
| 
 | |
|         try:
 | |
|             node = minidom.parseString(datastring).childNodes[0]
 | |
|             return {node.nodeName: self._from_xml_node(node, plurals)}
 | |
|         except expat.ExpatError:
 | |
|             msg = _("cannot understand XML")
 | |
|             raise exception.MalformedRequestBody(reason=msg)
 | |
| 
 | |
|     def _from_xml_node(self, node, listnames):
 | |
|         """Convert a minidom node to a simple Python type.
 | |
| 
 | |
|         :param listnames: list of XML node names whose subnodes should
 | |
|                           be considered list items.
 | |
| 
 | |
|         """
 | |
| 
 | |
|         if len(node.childNodes) == 1 and node.childNodes[0].nodeType == 3:
 | |
|             return node.childNodes[0].nodeValue
 | |
|         elif node.nodeName in listnames:
 | |
|             return [self._from_xml_node(n, listnames) for n in node.childNodes]
 | |
|         else:
 | |
|             result = dict()
 | |
|             for attr in node.attributes.keys():
 | |
|                 result[attr] = node.attributes[attr].nodeValue
 | |
|             for child in node.childNodes:
 | |
|                 if child.nodeType != node.TEXT_NODE:
 | |
|                     result[child.nodeName] = self._from_xml_node(child,
 | |
|                                                                  listnames)
 | |
|             return result
 | |
| 
 | |
|     def find_first_child_named(self, parent, name):
 | |
|         """Search a nodes children for the first child with a given name"""
 | |
|         for node in parent.childNodes:
 | |
|             if node.nodeName == name:
 | |
|                 return node
 | |
|         return None
 | |
| 
 | |
|     def find_children_named(self, parent, name):
 | |
|         """Return all of a nodes children who have the given name"""
 | |
|         for node in parent.childNodes:
 | |
|             if node.nodeName == name:
 | |
|                 yield node
 | |
| 
 | |
|     def extract_text(self, node):
 | |
|         """Get the text field contained by the given node"""
 | |
|         if len(node.childNodes) == 1:
 | |
|             child = node.childNodes[0]
 | |
|             if child.nodeType == child.TEXT_NODE:
 | |
|                 return child.nodeValue
 | |
|         return ""
 | |
| 
 | |
|     def default(self, datastring):
 | |
|         return {'body': self._from_xml(datastring)}
 |