blob: b6bd697c5acfa8b998b31193b730dbcf211f18fd [file] [log] [blame]
mblighe8819cd2008-02-15 16:48:40 +00001"""\
2RPC request handler Django. Exposed RPC interface functions should be
3defined in rpc_interface.py.
4"""
5
6__author__ = 'showard@google.com (Steve Howard)'
7
showardef6fe022009-03-27 20:55:16 +00008import traceback, pydoc, re, urllib
mblighe8819cd2008-02-15 16:48:40 +00009import django.http
mblighe8819cd2008-02-15 16:48:40 +000010
11from frontend.afe.json_rpc import serviceHandler
showard7c785282008-05-29 19:45:12 +000012from frontend.afe import rpc_utils
mblighe8819cd2008-02-15 16:48:40 +000013
mblighe8819cd2008-02-15 16:48:40 +000014
15class RpcMethodHolder(object):
jadmanski0afbb632008-06-06 21:10:57 +000016 'Dummy class to hold RPC interface methods as attributes.'
mblighe8819cd2008-02-15 16:48:40 +000017
18
showard7c785282008-05-29 19:45:12 +000019class RpcHandler(object):
jadmanski0afbb632008-06-06 21:10:57 +000020 def __init__(self, rpc_interface_modules, document_module=None):
21 self._rpc_methods = RpcMethodHolder()
showardef6fe022009-03-27 20:55:16 +000022 self._dispatcher = serviceHandler.ServiceHandler(self._rpc_methods)
mblighe8819cd2008-02-15 16:48:40 +000023
jadmanski0afbb632008-06-06 21:10:57 +000024 # store all methods from interface modules
25 for module in rpc_interface_modules:
26 self._grab_methods_from(module)
showard7c785282008-05-29 19:45:12 +000027
jadmanski0afbb632008-06-06 21:10:57 +000028 # get documentation for rpc_interface we can send back to the
29 # user
30 if document_module is None:
31 document_module = rpc_interface_modules[0]
32 self.html_doc = pydoc.html.document(document_module)
showard7c785282008-05-29 19:45:12 +000033
34
showardef6fe022009-03-27 20:55:16 +000035 def _raw_response(self, response_data, content_type=None):
36 response = django.http.HttpResponse(response_data)
jadmanski0afbb632008-06-06 21:10:57 +000037 response['Content-length'] = str(len(response.content))
showardef6fe022009-03-27 20:55:16 +000038 if content_type:
39 response['Content-Type'] = content_type
jadmanski0afbb632008-06-06 21:10:57 +000040 return response
showard7c785282008-05-29 19:45:12 +000041
42
showardef6fe022009-03-27 20:55:16 +000043 def get_rpc_documentation(self):
44 return self._raw_response(self.html_doc)
45
46
47 def _raw_request_data(self, request):
48 if request.method == 'POST':
49 return request.raw_post_data
50 return urllib.unquote(request.META['QUERY_STRING'])
51
52
53 def handle_rpc_request(self, request):
54 request_data = self._raw_request_data(request)
55 result = self._dispatcher.handleRequest(request_data)
56 return self._raw_response(result)
57
58
59 def handle_jsonp_rpc_request(self, request):
60 request_data = request.GET['request']
61 callback_name = request.GET['callback']
62 # callback_name must be a simple identifier
63 assert re.search(r'^\w+$', callback_name)
64
65 result = self._dispatcher.handleRequest(request_data)
66 padded_result = '%s(%s)' % (callback_name, result)
67 return self._raw_response(padded_result, content_type='text/javascript')
68
69
jadmanski0afbb632008-06-06 21:10:57 +000070 @staticmethod
71 def _allow_keyword_args(f):
72 """\
73 Decorator to allow a function to take keyword args even though
74 the RPC layer doesn't support that. The decorated function
75 assumes its last argument is a dictionary of keyword args and
76 passes them to the original function as keyword args.
77 """
78 def new_fn(*args):
79 assert args
80 keyword_args = args[-1]
81 args = args[:-1]
82 return f(*args, **keyword_args)
83 new_fn.func_name = f.func_name
84 return new_fn
showard7c785282008-05-29 19:45:12 +000085
86
jadmanski0afbb632008-06-06 21:10:57 +000087 def _grab_methods_from(self, module):
88 for name in dir(module):
89 attribute = getattr(module, name)
90 if not callable(attribute):
91 continue
showardef6fe022009-03-27 20:55:16 +000092 decorated_function = RpcHandler._allow_keyword_args(attribute)
jadmanski0afbb632008-06-06 21:10:57 +000093 setattr(self._rpc_methods, name, decorated_function)