Refactor wsgi.Serializer away from handling Requests directly; now require Content-Type in all requests; fix tests according to new code
This commit is contained in:
@@ -88,6 +88,10 @@ class InvalidInputException(Error):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidContentType(Error):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class TimeoutException(Error):
|
class TimeoutException(Error):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ class DirectTestCase(test.TestCase):
|
|||||||
req.headers['X-OpenStack-User'] = 'user1'
|
req.headers['X-OpenStack-User'] = 'user1'
|
||||||
req.headers['X-OpenStack-Project'] = 'proj1'
|
req.headers['X-OpenStack-Project'] = 'proj1'
|
||||||
resp = req.get_response(self.auth_router)
|
resp = req.get_response(self.auth_router)
|
||||||
|
self.assertEqual(resp.status_int, 200)
|
||||||
data = json.loads(resp.body)
|
data = json.loads(resp.body)
|
||||||
self.assertEqual(data['user'], 'user1')
|
self.assertEqual(data['user'], 'user1')
|
||||||
self.assertEqual(data['project'], 'proj1')
|
self.assertEqual(data['project'], 'proj1')
|
||||||
@@ -69,6 +70,7 @@ class DirectTestCase(test.TestCase):
|
|||||||
req.method = 'POST'
|
req.method = 'POST'
|
||||||
req.body = 'json=%s' % json.dumps({'data': 'foo'})
|
req.body = 'json=%s' % json.dumps({'data': 'foo'})
|
||||||
resp = req.get_response(self.router)
|
resp = req.get_response(self.router)
|
||||||
|
self.assertEqual(resp.status_int, 200)
|
||||||
resp_parsed = json.loads(resp.body)
|
resp_parsed = json.loads(resp.body)
|
||||||
self.assertEqual(resp_parsed['data'], 'foo')
|
self.assertEqual(resp_parsed['data'], 'foo')
|
||||||
|
|
||||||
@@ -78,6 +80,7 @@ class DirectTestCase(test.TestCase):
|
|||||||
req.method = 'POST'
|
req.method = 'POST'
|
||||||
req.body = 'data=foo'
|
req.body = 'data=foo'
|
||||||
resp = req.get_response(self.router)
|
resp = req.get_response(self.router)
|
||||||
|
self.assertEqual(resp.status_int, 200)
|
||||||
resp_parsed = json.loads(resp.body)
|
resp_parsed = json.loads(resp.body)
|
||||||
self.assertEqual(resp_parsed['data'], 'foo')
|
self.assertEqual(resp_parsed['data'], 'foo')
|
||||||
|
|
||||||
|
|||||||
96
nova/wsgi.py
96
nova/wsgi.py
@@ -36,6 +36,7 @@ import webob.exc
|
|||||||
|
|
||||||
from paste import deploy
|
from paste import deploy
|
||||||
|
|
||||||
|
from nova import exception
|
||||||
from nova import flags
|
from nova import flags
|
||||||
from nova import log as logging
|
from nova import log as logging
|
||||||
from nova import utils
|
from nova import utils
|
||||||
@@ -102,6 +103,14 @@ class Request(webob.Request):
|
|||||||
|
|
||||||
return bm or "application/json"
|
return bm or "application/json"
|
||||||
|
|
||||||
|
def get_content_type(self):
|
||||||
|
try:
|
||||||
|
ct = self.headers["Content-Type"]
|
||||||
|
assert ct in ("application/xml", "application/json")
|
||||||
|
return ct
|
||||||
|
except Exception:
|
||||||
|
raise webob.exc.HTTPBadRequest("Invalid content type")
|
||||||
|
|
||||||
|
|
||||||
class Application(object):
|
class Application(object):
|
||||||
"""Base WSGI application wrapper. Subclasses need to implement __call__."""
|
"""Base WSGI application wrapper. Subclasses need to implement __call__."""
|
||||||
@@ -343,30 +352,41 @@ class Controller(object):
|
|||||||
del arg_dict['format']
|
del arg_dict['format']
|
||||||
arg_dict['req'] = req
|
arg_dict['req'] = req
|
||||||
result = method(**arg_dict)
|
result = method(**arg_dict)
|
||||||
|
|
||||||
if type(result) is dict:
|
if type(result) is dict:
|
||||||
return self._serialize(result, req)
|
content_type = req.best_match()
|
||||||
|
body = self._serialize(result, content_type)
|
||||||
|
|
||||||
|
response = webob.Response()
|
||||||
|
response.headers["Content-Type"] = content_type
|
||||||
|
response.body = body
|
||||||
|
return response
|
||||||
|
|
||||||
else:
|
else:
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def _serialize(self, data, request):
|
def _serialize(self, data, content_type):
|
||||||
"""
|
"""
|
||||||
Serialize the given dict to the response type requested in request.
|
Serialize the given dict to the provided content_type.
|
||||||
Uses self._serialization_metadata if it exists, which is a dict mapping
|
Uses self._serialization_metadata if it exists, which is a dict mapping
|
||||||
MIME types to information needed to serialize to that type.
|
MIME types to information needed to serialize to that type.
|
||||||
"""
|
"""
|
||||||
_metadata = getattr(type(self), "_serialization_metadata", {})
|
_metadata = getattr(type(self), "_serialization_metadata", {})
|
||||||
serializer = Serializer(request.environ, _metadata)
|
serializer = Serializer(_metadata)
|
||||||
return serializer.to_content_type(data)
|
try:
|
||||||
|
return serializer.serialize(data, content_type)
|
||||||
|
except exception.InvalidContentType:
|
||||||
|
raise webob.exc.HTTPNotAcceptable()
|
||||||
|
|
||||||
def _deserialize(self, data, request):
|
def _deserialize(self, data, content_type):
|
||||||
"""
|
"""
|
||||||
Deserialize the request body to the response type requested in request.
|
Deserialize the request body to the specefied content type.
|
||||||
Uses self._serialization_metadata if it exists, which is a dict mapping
|
Uses self._serialization_metadata if it exists, which is a dict mapping
|
||||||
MIME types to information needed to serialize to that type.
|
MIME types to information needed to serialize to that type.
|
||||||
"""
|
"""
|
||||||
_metadata = getattr(type(self), "_serialization_metadata", {})
|
_metadata = getattr(type(self), "_serialization_metadata", {})
|
||||||
serializer = Serializer(request.environ, _metadata)
|
serializer = Serializer(_metadata)
|
||||||
return serializer.deserialize(data)
|
return serializer.deserialize(data, content_type)
|
||||||
|
|
||||||
|
|
||||||
class Serializer(object):
|
class Serializer(object):
|
||||||
@@ -374,50 +394,52 @@ class Serializer(object):
|
|||||||
Serializes and deserializes dictionaries to certain MIME types.
|
Serializes and deserializes dictionaries to certain MIME types.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, environ, metadata=None):
|
def __init__(self, metadata=None):
|
||||||
"""
|
"""
|
||||||
Create a serializer based on the given WSGI environment.
|
Create a serializer based on the given WSGI environment.
|
||||||
'metadata' is an optional dict mapping MIME types to information
|
'metadata' is an optional dict mapping MIME types to information
|
||||||
needed to serialize a dictionary to that type.
|
needed to serialize a dictionary to that type.
|
||||||
"""
|
"""
|
||||||
self.metadata = metadata or {}
|
self.metadata = metadata or {}
|
||||||
req = Request.blank('', environ)
|
|
||||||
suffix = req.path_info.split('.')[-1].lower()
|
|
||||||
if suffix == 'json':
|
|
||||||
self.handler = self._to_json
|
|
||||||
elif suffix == 'xml':
|
|
||||||
self.handler = self._to_xml
|
|
||||||
elif 'application/json' in req.accept:
|
|
||||||
self.handler = self._to_json
|
|
||||||
elif 'application/xml' in req.accept:
|
|
||||||
self.handler = self._to_xml
|
|
||||||
else:
|
|
||||||
# This is the default
|
|
||||||
self.handler = self._to_json
|
|
||||||
|
|
||||||
def to_content_type(self, data):
|
def _get_serialize_handler(self, content_type):
|
||||||
|
handlers = {
|
||||||
|
"application/json": self._to_json,
|
||||||
|
"application/xml": self._to_xml,
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
return handlers[content_type]
|
||||||
|
except Exception:
|
||||||
|
raise exception.InvalidContentType()
|
||||||
|
|
||||||
|
def serialize(self, data, content_type):
|
||||||
"""
|
"""
|
||||||
Serialize a dictionary into a string.
|
Serialize a dictionary into a string of the specified content type.
|
||||||
|
|
||||||
The format of the string will be decided based on the Content Type
|
|
||||||
requested in self.environ: by Accept: header, or by URL suffix.
|
|
||||||
"""
|
"""
|
||||||
return self.handler(data)
|
return self._get_serialize_handler(content_type)(data)
|
||||||
|
|
||||||
def deserialize(self, datastring):
|
def deserialize(self, datastring, content_type):
|
||||||
"""
|
"""
|
||||||
Deserialize a string to a dictionary.
|
Deserialize a string to a dictionary.
|
||||||
|
|
||||||
The string must be in the format of a supported MIME type.
|
The string must be in the format of a supported MIME type.
|
||||||
"""
|
"""
|
||||||
datastring = datastring.strip()
|
return self.get_deserialize_handler(content_type)(datastring)
|
||||||
|
|
||||||
|
def get_deserialize_handler(self, content_type):
|
||||||
|
handlers = {
|
||||||
|
"application/json": self._from_json,
|
||||||
|
"application/xml": self._from_xml,
|
||||||
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
is_xml = (datastring[0] == '<')
|
return handlers[content_type]
|
||||||
if not is_xml:
|
except Exception:
|
||||||
return utils.loads(datastring)
|
raise exception.InvalidContentType()
|
||||||
return self._from_xml(datastring)
|
|
||||||
except:
|
def _from_json(self, datastring):
|
||||||
return None
|
return utils.loads(datastring)
|
||||||
|
|
||||||
def _from_xml(self, datastring):
|
def _from_xml(self, datastring):
|
||||||
xmldata = self.metadata.get('application/xml', {})
|
xmldata = self.metadata.get('application/xml', {})
|
||||||
|
|||||||
Reference in New Issue
Block a user