1 """\ 2 RPC request handler Django. Exposed RPC interface functions should be 3 defined in rpc_interface.py. 4 """ 5 6 __author__ = 'showard (at] google.com (Steve Howard)' 7 8 import traceback, pydoc, re, urllib, logging, logging.handlers, inspect 9 from autotest_lib.frontend.afe.json_rpc import serviceHandler 10 from autotest_lib.frontend.afe import models, rpc_utils 11 from autotest_lib.client.common_lib import global_config 12 from autotest_lib.frontend.afe import rpcserver_logging 13 14 LOGGING_REGEXPS = [r'.*add_.*', 15 r'delete_.*', 16 r'.*remove_.*', 17 r'modify_.*', 18 r'create.*', 19 r'set_.*'] 20 FULL_REGEXP = '(' + '|'.join(LOGGING_REGEXPS) + ')' 21 COMPILED_REGEXP = re.compile(FULL_REGEXP) 22 23 24 def should_log_message(name): 25 return COMPILED_REGEXP.match(name) 26 27 28 class RpcMethodHolder(object): 29 'Dummy class to hold RPC interface methods as attributes.' 30 31 32 class RpcHandler(object): 33 def __init__(self, rpc_interface_modules, document_module=None): 34 self._rpc_methods = RpcMethodHolder() 35 self._dispatcher = serviceHandler.ServiceHandler(self._rpc_methods) 36 37 # store all methods from interface modules 38 for module in rpc_interface_modules: 39 self._grab_methods_from(module) 40 41 # get documentation for rpc_interface we can send back to the 42 # user 43 if document_module is None: 44 document_module = rpc_interface_modules[0] 45 self.html_doc = pydoc.html.document(document_module) 46 47 48 def get_rpc_documentation(self): 49 return rpc_utils.raw_http_response(self.html_doc) 50 51 52 def raw_request_data(self, request): 53 if request.method == 'POST': 54 return request.raw_post_data 55 return urllib.unquote(request.META['QUERY_STRING']) 56 57 58 def execute_request(self, json_request): 59 return self._dispatcher.handleRequest(json_request) 60 61 62 def decode_request(self, json_request): 63 return self._dispatcher.translateRequest(json_request) 64 65 66 def dispatch_request(self, decoded_request): 67 return self._dispatcher.dispatchRequest(decoded_request) 68 69 70 def log_request(self, user, decoded_request, decoded_result, 71 remote_ip, log_all=False): 72 if log_all or should_log_message(decoded_request['method']): 73 msg = '%s| %s:%s %s' % (remote_ip, decoded_request['method'], 74 user, decoded_request['params']) 75 if decoded_result['err']: 76 msg += '\n' + decoded_result['err_traceback'] 77 rpcserver_logging.rpc_logger.error(msg) 78 else: 79 rpcserver_logging.rpc_logger.info(msg) 80 81 82 def encode_result(self, results): 83 return self._dispatcher.translateResult(results) 84 85 86 def handle_rpc_request(self, request): 87 remote_ip = self._get_remote_ip(request) 88 user = models.User.current_user() 89 json_request = self.raw_request_data(request) 90 decoded_request = self.decode_request(json_request) 91 92 decoded_request['remote_ip'] = remote_ip 93 decoded_result = self.dispatch_request(decoded_request) 94 result = self.encode_result(decoded_result) 95 if rpcserver_logging.LOGGING_ENABLED: 96 self.log_request(user, decoded_request, decoded_result, 97 remote_ip) 98 return rpc_utils.raw_http_response(result) 99 100 101 def handle_jsonp_rpc_request(self, request): 102 request_data = request.GET['request'] 103 callback_name = request.GET['callback'] 104 # callback_name must be a simple identifier 105 assert re.search(r'^\w+$', callback_name) 106 107 result = self.execute_request(request_data) 108 padded_result = '%s(%s)' % (callback_name, result) 109 return rpc_utils.raw_http_response(padded_result, 110 content_type='text/javascript') 111 112 113 @staticmethod 114 def _allow_keyword_args(f): 115 """\ 116 Decorator to allow a function to take keyword args even though 117 the RPC layer doesn't support that. The decorated function 118 assumes its last argument is a dictionary of keyword args and 119 passes them to the original function as keyword args. 120 """ 121 def new_fn(*args): 122 assert args 123 keyword_args = args[-1] 124 args = args[:-1] 125 return f(*args, **keyword_args) 126 new_fn.func_name = f.func_name 127 return new_fn 128 129 130 def _grab_methods_from(self, module): 131 for name in dir(module): 132 if name.startswith('_'): 133 continue 134 attribute = getattr(module, name) 135 if not inspect.isfunction(attribute): 136 continue 137 decorated_function = RpcHandler._allow_keyword_args(attribute) 138 setattr(self._rpc_methods, name, decorated_function) 139 140 141 def _get_remote_ip(self, request): 142 """Get the ip address of a RPC caller. 143 144 Returns the IP of the request, accounting for the possibility of 145 being behind a proxy. 146 If a Django server is behind a proxy, request.META["REMOTE_ADDR"] will 147 return the proxy server's IP, not the client's IP. 148 The proxy server would provide the client's IP in the 149 HTTP_X_FORWARDED_FOR header. 150 151 @param request: django.core.handlers.wsgi.WSGIRequest object. 152 153 @return: IP address of remote host as a string. 154 Empty string if the IP cannot be found. 155 """ 156 remote = request.META.get('HTTP_X_FORWARDED_FOR', None) 157 if remote: 158 # X_FORWARDED_FOR returns client1, proxy1, proxy2,... 159 remote = remote.split(',')[0].strip() 160 else: 161 remote = request.META.get('REMOTE_ADDR', '') 162 return remote 163