From 1f8588f6691bae503f7deb3e07278f1df8f48b00 Mon Sep 17 00:00:00 2001
From: Iustin Pop <iustin@google.com>
Date: Fri, 13 Feb 2009 11:38:26 +0000
Subject: [PATCH] RAPI: format error messages as JSON

This patch changes the format of the HTTP error messages from text/html, which
is hard to parse from RAPI clients, to JSON which can be automatically parsed.

The error message is an object, which contains always three keys:
  - code, an integer with the error code
  - message, a short description
  - explain, holding (if available) a description of the error

In order to implement this, there is a bit of change to the http server
and executor classes. I've tested and the error handling still works
(but less optimal, no error message) in case the error formatting itself
raises an exception.

Reviewed-by: imsnah
---
 daemons/ganeti-rapi | 23 ++++++++++++++++++++++-
 lib/http/server.py  | 27 +++++++++++++++++++++++----
 2 files changed, 45 insertions(+), 5 deletions(-)

diff --git a/daemons/ganeti-rapi b/daemons/ganeti-rapi
index bd87b19b0..195d5c2c9 100755
--- a/daemons/ganeti-rapi
+++ b/daemons/ganeti-rapi
@@ -36,6 +36,7 @@ from ganeti import daemon
 from ganeti import ssconf
 from ganeti import utils
 from ganeti import luxi
+from ganeti import serializer
 from ganeti.rapi import connector
 
 import ganeti.http.auth
@@ -52,6 +53,24 @@ class RemoteApiRequestContext(object):
     self.handler_access = None
 
 
+class JsonErrorRequestExecutor(http.server.HttpServerRequestExecutor):
+  """Custom Request Executor class that formats HTTP errors in JSON.
+
+  """
+  error_content_type = "application/json"
+
+  def _FormatErrorMessage(self, values):
+    """Formats the body of an error message.
+
+    @type values: dict
+    @param values: dictionary with keys code, message and explain.
+    @rtype: string
+    @return: the body of the message
+
+    """
+    return serializer.DumpJson(values, indent=True)
+
+
 class RemoteApiHttpServer(http.auth.HttpServerRequestAuthentication,
                           http.server.HttpServer):
   """REST Request Handler Class.
@@ -233,7 +252,9 @@ def main():
   try:
     mainloop = daemon.Mainloop()
     server = RemoteApiHttpServer(mainloop, "", options.port,
-                                 ssl_params=ssl_params, ssl_verify_peer=False)
+                                 ssl_params=ssl_params, ssl_verify_peer=False,
+                                 request_executor_class=
+                                 JsonErrorRequestExecutor)
     server.Start()
     try:
       mainloop.Run()
diff --git a/lib/http/server.py b/lib/http/server.py
index d7ac0e188..b74eb3674 100644
--- a/lib/http/server.py
+++ b/lib/http/server.py
@@ -207,7 +207,7 @@ class _HttpClientToServerMessageReader(http.HttpMessageReader):
     return http.HttpClientToServerStartLine(method, path, version)
 
 
-class _HttpServerRequestExecutor(object):
+class HttpServerRequestExecutor(object):
   """Implements server side of HTTP.
 
   This class implements the server side of HTTP. It's based on code of
@@ -405,8 +405,18 @@ class _HttpServerRequestExecutor(object):
     headers[http.HTTP_CONTENT_TYPE] = self.error_content_type
     self.response_msg.headers = headers
 
-    self.response_msg.body = self.error_message_format % values
+    self.response_msg.body = self._FormatErrorMessage(values)
 
+  def _FormatErrorMessage(self, values):
+    """Formats the body of an error message.
+
+    @type values: dict
+    @param values: dictionary with keys code, message and explain.
+    @rtype: string
+    @return: the body of the message
+
+    """
+    return self.error_message_format % values
 
 class HttpServer(http.HttpBase):
   """Generic HTTP server class
@@ -417,7 +427,8 @@ class HttpServer(http.HttpBase):
   MAX_CHILDREN = 20
 
   def __init__(self, mainloop, local_address, port,
-               ssl_params=None, ssl_verify_peer=False):
+               ssl_params=None, ssl_verify_peer=False,
+               request_executor_class=None):
     """Initializes the HTTP server
 
     @type mainloop: ganeti.daemon.Mainloop
@@ -431,10 +442,18 @@ class HttpServer(http.HttpBase):
     @type ssl_verify_peer: bool
     @param ssl_verify_peer: Whether to require client certificate
         and compare it with our certificate
+    @type request_executor_class: class
+    @param request_executor_class: an class derived from the
+        HttpServerRequestExecutor class
 
     """
     http.HttpBase.__init__(self)
 
+    if request_executor_class is None:
+      self.request_executor = HttpServerRequestExecutor
+    else:
+      self.request_executor = request_executor_class
+
     self.mainloop = mainloop
     self.local_address = local_address
     self.port = port
@@ -505,7 +524,7 @@ class HttpServer(http.HttpBase):
     if pid == 0:
       # Child process
       try:
-        _HttpServerRequestExecutor(self, connection, client_addr)
+        self.request_executor(self, connection, client_addr)
       except Exception:
         logging.exception("Error while handling request from %s:%s",
                           client_addr[0], client_addr[1])
-- 
GitLab