1""" 2 Copyright (c) 2007 Jan-Klaas Kollhof 3 4 This file is part of jsonrpc. 5 6 jsonrpc is free software; you can redistribute it and/or modify 7 it under the terms of the GNU Lesser General Public License as published by 8 the Free Software Foundation; either version 2.1 of the License, or 9 (at your option) any later version. 10 11 This software is distributed in the hope that it will be useful, 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU Lesser General Public License for more details. 15 16 You should have received a copy of the GNU Lesser General Public License 17 along with this software; if not, write to the Free Software 18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19""" 20 21import socket 22import traceback 23 24from json import decoder 25 26try: 27 from django.core import exceptions as django_exceptions 28 # Django JSON encoder uses the standard json encoder but can handle DateTime 29 from django.core.serializers import json as django_encoder 30 json_encoder = django_encoder.DjangoJSONEncoder() 31except django_exceptions.ImproperlyConfigured: 32 from json import encoder 33 json_encoder = encoder.JSONEncoder() 34 35# TODO(akeshet): Eliminate this and replace with monarch metrics. (At the 36# moment, I don't think we can just easily swap out, because this module is 37# called by apache for rpc handling, and we don't have a ts_mon thread for that 38# yet). 39from autotest_lib.client.common_lib.cros.graphite import autotest_stats 40 41 42json_decoder = decoder.JSONDecoder() 43 44 45def customConvertJson(value): 46 """\ 47 Recursively process JSON values and do type conversions. 48 -change floats to ints 49 -change unicodes to strs 50 """ 51 if isinstance(value, float): 52 return int(value) 53 elif isinstance(value, unicode): 54 return str(value) 55 elif isinstance(value, list): 56 return [customConvertJson(item) for item in value] 57 elif isinstance(value, dict): 58 new_dict = {} 59 for key, val in value.iteritems(): 60 new_key = customConvertJson(key) 61 new_val = customConvertJson(val) 62 new_dict[new_key] = new_val 63 return new_dict 64 else: 65 return value 66 67 68def ServiceMethod(fn): 69 fn.IsServiceMethod = True 70 return fn 71 72class ServiceException(Exception): 73 pass 74 75class ServiceRequestNotTranslatable(ServiceException): 76 pass 77 78class BadServiceRequest(ServiceException): 79 pass 80 81class ServiceMethodNotFound(ServiceException): 82 pass 83 84 85class ServiceHandler(object): 86 87 def __init__(self, service): 88 self.service=service 89 90 91 @classmethod 92 def blank_result_dict(cls): 93 return {'id': None, 'result': None, 'err': None, 'err_traceback': None} 94 95 def dispatchRequest(self, request): 96 """ 97 Invoke a json RPC call from a decoded json request. 98 @param request: a decoded json_request 99 @returns a dictionary with keys id, result, err and err_traceback 100 """ 101 results = self.blank_result_dict() 102 103 try: 104 results['id'] = self._getRequestId(request) 105 methName = request['method'] 106 args = request['params'] 107 except KeyError: 108 raise BadServiceRequest(request) 109 110 autotest_stats.Counter('rpc').increment(methName) 111 112 metadata = request.copy() 113 metadata['_type'] = 'rpc' 114 metadata['rpc_server'] = socket.gethostname() 115 timer = autotest_stats.Timer('rpc', metadata=metadata) 116 117 try: 118 timer.start() 119 meth = self.findServiceEndpoint(methName) 120 results['result'] = self.invokeServiceEndpoint(meth, args) 121 except Exception, err: 122 results['err_traceback'] = traceback.format_exc() 123 results['err'] = err 124 finally: 125 timer.stop(methName) 126 127 return results 128 129 130 def _getRequestId(self, request): 131 try: 132 return request['id'] 133 except KeyError: 134 raise BadServiceRequest(request) 135 136 137 def handleRequest(self, jsonRequest): 138 request = self.translateRequest(jsonRequest) 139 results = self.dispatchRequest(request) 140 return self.translateResult(results) 141 142 143 @staticmethod 144 def translateRequest(data): 145 try: 146 req = json_decoder.decode(data) 147 except: 148 raise ServiceRequestNotTranslatable(data) 149 req = customConvertJson(req) 150 return req 151 152 def findServiceEndpoint(self, name): 153 try: 154 meth = getattr(self.service, name) 155 return meth 156 except AttributeError: 157 raise ServiceMethodNotFound(name) 158 159 def invokeServiceEndpoint(self, meth, args): 160 return meth(*args) 161 162 @staticmethod 163 def translateResult(result_dict): 164 """ 165 @param result_dict: a dictionary containing the result, error, traceback 166 and id. 167 @returns translated json result 168 """ 169 if result_dict['err'] is not None: 170 error_name = result_dict['err'].__class__.__name__ 171 result_dict['err'] = {'name': error_name, 172 'message': str(result_dict['err']), 173 'traceback': result_dict['err_traceback']} 174 result_dict['result'] = None 175 176 try: 177 json_dict = {'result': result_dict['result'], 178 'id': result_dict['id'], 179 'error': result_dict['err'] } 180 data = json_encoder.encode(json_dict) 181 except TypeError, e: 182 err_traceback = traceback.format_exc() 183 print err_traceback 184 err = {"name" : "JSONEncodeException", 185 "message" : "Result Object Not Serializable", 186 "traceback" : err_traceback} 187 data = json_encoder.encode({"result":None, "id":result_dict['id'], 188 "error":err}) 189 190 return data 191