generic JSON-RPC implementation using JSONP (JSON with Padding).  the central part of this change consists of:
* refactored JsonRpcProxy to extract all XmlHttpRequest-specific logic into a new XhrHttpRequest subclass, and made JsonRpcProxy abstract
* introduced new PaddedJsonRpcProxy subclass of XmlHttpRequest that uses JSONP instead of XHR
* added new handle_jsonp_rpc_request() method to rpc_handler.py, to handle JSONP requests on the server side

This enables the entire frontend (either AFE or TKO) to operate via JSONP instead of XHR.  I didn't make them do that now, since there's no reason, but it will be critical when we go to make components embeddable in other pages (on other domains).  Other changes here include:

* made TestDetailView use PaddedJsonRpcProxy (it previous had its own custom JSONP logic, which was all moved into PaddedJsonRpcProxy).
* made retrieve_logs.cgi and jsonp_fetcher.cgi support JSONP requests, so that log fetching requests could go through the shared JsonRpcProxy codepath.  retrieve_logs.cgi still supports the old GET params interface for backwards compatibility (at least old TKO still uses it, and possible other scripts).

Signed-off-by: Steve Howard <showard@google.com>



git-svn-id: http://test.kernel.org/svn/autotest/trunk@2943 592f7852-d20e-0410-864c-8624ca9c26a4
diff --git a/frontend/afe/rpc_handler.py b/frontend/afe/rpc_handler.py
index 70ef6cd..b6bd697 100644
--- a/frontend/afe/rpc_handler.py
+++ b/frontend/afe/rpc_handler.py
@@ -5,8 +5,8 @@
 
 __author__ = 'showard@google.com (Steve Howard)'
 
+import traceback, pydoc, re, urllib
 import django.http
-import traceback, pydoc
 
 from frontend.afe.json_rpc import serviceHandler
 from frontend.afe import rpc_utils
@@ -19,8 +19,7 @@
 class RpcHandler(object):
     def __init__(self, rpc_interface_modules, document_module=None):
         self._rpc_methods = RpcMethodHolder()
-        self._dispatcher = serviceHandler.ServiceHandler(
-            self._rpc_methods)
+        self._dispatcher = serviceHandler.ServiceHandler(self._rpc_methods)
 
         # store all methods from interface modules
         for module in rpc_interface_modules:
@@ -33,18 +32,41 @@
         self.html_doc = pydoc.html.document(document_module)
 
 
-    def handle_rpc_request(self, request):
-        response = django.http.HttpResponse()
-        if len(request.POST):
-            response.write(self._dispatcher.handleRequest(
-                request.raw_post_data))
-        else:
-            response.write(self.html_doc)
-
+    def _raw_response(self, response_data, content_type=None):
+        response = django.http.HttpResponse(response_data)
         response['Content-length'] = str(len(response.content))
+        if content_type:
+            response['Content-Type'] = content_type
         return response
 
 
+    def get_rpc_documentation(self):
+        return self._raw_response(self.html_doc)
+
+
+    def _raw_request_data(self, request):
+        if request.method == 'POST':
+            return request.raw_post_data
+        return urllib.unquote(request.META['QUERY_STRING'])
+
+
+    def handle_rpc_request(self, request):
+        request_data = self._raw_request_data(request)
+        result = self._dispatcher.handleRequest(request_data)
+        return self._raw_response(result)
+
+
+    def handle_jsonp_rpc_request(self, request):
+        request_data = request.GET['request']
+        callback_name = request.GET['callback']
+        # callback_name must be a simple identifier
+        assert re.search(r'^\w+$', callback_name)
+
+        result = self._dispatcher.handleRequest(request_data)
+        padded_result = '%s(%s)' % (callback_name, result)
+        return self._raw_response(padded_result, content_type='text/javascript')
+
+
     @staticmethod
     def _allow_keyword_args(f):
         """\
@@ -67,6 +89,5 @@
             attribute = getattr(module, name)
             if not callable(attribute):
                 continue
-            decorated_function = (
-                RpcHandler._allow_keyword_args(attribute))
+            decorated_function = RpcHandler._allow_keyword_args(attribute)
             setattr(self._rpc_methods, name, decorated_function)