News
====
+Version 2.2.0
+-------------
+
+- RAPI now requires a Content-Type header for requests with a body (e.g.
+ ``PUT`` or ``POST``) which must be set to ``application/json`` (see
+ RFC2616 (HTTP/1.1), section 7.2.1)
+
Version 2.1.0
-------------
from ganeti import http
from ganeti import utils
from ganeti import storage
+from ganeti import serializer
import ganeti.http.server # pylint: disable-msg=W0611
raise http.HttpNotFound()
try:
- rvalue = method(req.request_body)
- return True, rvalue
+ result = (True, method(serializer.LoadJson(req.request_body)))
except backend.RPCFail, err:
# our custom failure exception; str(err) works fine if the
# exception was constructed with a single argument, and in
# this case, err.message == err.args[0] == str(err)
- return (False, str(err))
+ result = (False, str(err))
except errors.QuitGanetiException, err:
# Tell parent to quit
logging.info("Shutting down the node daemon, arguments: %s",
os.kill(self.noded_pid, signal.SIGTERM)
# And return the error's arguments, which must be already in
# correct tuple format
- return err.args
+ result = err.args
except Exception, err:
logging.exception("Error in RPC call")
- return False, "Error while executing backend function: %s" % str(err)
+ result = (False, "Error while executing backend function: %s" % str(err))
+
+ return serializer.DumpJson(result, indent=False)
# the new block devices --------------------------
self.handler = None
self.handler_fn = None
self.handler_access = None
+ self.body_data = None
class JsonErrorRequestExecutor(http.server.HttpServerRequestExecutor):
"""Custom Request Executor class that formats HTTP errors in JSON.
"""
- error_content_type = "application/json"
+ error_content_type = http.HttpJsonConverter.CONTENT_TYPE
def _FormatErrorMessage(self, values):
"""Formats the body of an error message.
if ctx.handler_access is None:
raise AssertionError("Permissions definition missing")
+ # This is only made available in HandleRequest
+ ctx.body_data = None
+
req.private = ctx
return req.private
"""
ctx = self._GetRequestContext(req)
+ # Deserialize request parameters
+ if req.request_body:
+ # RFC2616, 7.2.1: Any HTTP/1.1 message containing an entity-body SHOULD
+ # include a Content-Type header field defining the media type of that
+ # body. [...] If the media type remains unknown, the recipient SHOULD
+ # treat it as type "application/octet-stream".
+ req_content_type = req.request_headers.get(http.HTTP_CONTENT_TYPE,
+ http.HTTP_APP_OCTET_STREAM)
+ if (req_content_type.lower() !=
+ http.HttpJsonConverter.CONTENT_TYPE.lower()):
+ raise http.HttpUnsupportedMediaType()
+
+ try:
+ ctx.body_data = serializer.LoadJson(req.request_body)
+ except Exception:
+ raise http.HttpBadRequest(message="Unable to parse JSON data")
+ else:
+ ctx.body_data = None
+
try:
result = ctx.handler_fn()
except luxi.TimeoutError:
logging.exception("Error while handling the %s request", method)
raise
- return result
+ req.resp_headers[http.HTTP_CONTENT_TYPE] = \
+ http.HttpJsonConverter.CONTENT_TYPE
+
+ return serializer.DumpJson(result)
def CheckRapi(options, args):
self.start_line = None
self.headers = None
self.body = None
- self.decoded_body = None
class HttpClientToServerStartLine(object):
assert self.parser_status == self.PS_COMPLETE
assert not buf, "Parser didn't read full response"
+ # Body is complete
msg.body = self.body_buffer.getvalue()
- # TODO: Content-type, error handling
- if msg.body:
- msg.decoded_body = HttpJsonConverter().Decode(msg.body)
- else:
- msg.decoded_body = None
-
- if msg.decoded_body:
- logging.debug("Message body: %s", msg.decoded_body)
-
def _ContinueParsing(self, buf, eof):
"""Main function for HTTP message state machine.
self.request_method = request_msg.start_line.method
self.request_path = request_msg.start_line.path
self.request_headers = request_msg.headers
- self.request_body = request_msg.decoded_body
+ self.request_body = request_msg.body
# Response attributes
self.resp_headers = {}
logging.exception("Unknown exception")
raise http.HttpInternalServerError(message="Unknown error")
- # TODO: Content-type
- encoder = http.HttpJsonConverter()
+ if not isinstance(result, basestring):
+ raise http.HttpError("Handler function didn't return string type")
+
self.response_msg.start_line.code = http.HTTP_OK
- self.response_msg.body = encoder.Encode(result)
self.response_msg.headers = handler_context.resp_headers
- self.response_msg.headers[http.HTTP_CONTENT_TYPE] = encoder.CONTENT_TYPE
+ self.response_msg.body = result
finally:
# No reason to keep this any longer, even for exceptions
handler_context.private = None
@param name: the required parameter
"""
- if name in self.req.request_body:
- return self.req.request_body[name]
- elif args:
- return args[0]
- else:
- raise http.HttpBadRequest("Required parameter '%s' is missing" %
- name)
+ try:
+ return self.req.private.body_data[name]
+ except KeyError:
+ if args:
+ return args[0]
+
+ raise http.HttpBadRequest("Required parameter '%s' is missing" % name)
def useLocking(self):
"""Check if the request specifies locking.
@return: a job id
"""
- if not isinstance(self.req.request_body, basestring):
+ if not isinstance(self.req.private.body_data, basestring):
raise http.HttpBadRequest("Invalid body contents, not a string")
node_name = self.items[0]
- role = self.req.request_body
+ role = self.req.private.body_data
if role == _NR_REGULAR:
candidate = False
@return: a job id
"""
- if not isinstance(self.req.request_body, dict):
+ if not isinstance(self.req.private.body_data, dict):
raise http.HttpBadRequest("Invalid body contents, not a dictionary")
- beparams = baserlib.MakeParamsDict(self.req.request_body,
+ beparams = baserlib.MakeParamsDict(self.req.private.body_data,
constants.BES_PARAMETERS)
- hvparams = baserlib.MakeParamsDict(self.req.request_body,
+ hvparams = baserlib.MakeParamsDict(self.req.private.body_data,
constants.HVS_PARAMETERS)
fn = self.getBodyParameter