""" Copyright (c) 2007 Jan-Klaas Kollhof This file is part of jsonrpc. jsonrpc is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this software; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ import socket import traceback from json import decoder try: from django.core import exceptions as django_exceptions # Django JSON encoder uses the standard json encoder but can handle DateTime from django.core.serializers import json as django_encoder json_encoder = django_encoder.DjangoJSONEncoder() except django_exceptions.ImproperlyConfigured: from json import encoder json_encoder = encoder.JSONEncoder() # TODO(akeshet): Eliminate this and replace with monarch metrics. (At the # moment, I don't think we can just easily swap out, because this module is # called by apache for rpc handling, and we don't have a ts_mon thread for that # yet). from autotest_lib.client.common_lib.cros.graphite import autotest_stats json_decoder = decoder.JSONDecoder() def customConvertJson(value): """\ Recursively process JSON values and do type conversions. -change floats to ints -change unicodes to strs """ if isinstance(value, float): return int(value) elif isinstance(value, unicode): return str(value) elif isinstance(value, list): return [customConvertJson(item) for item in value] elif isinstance(value, dict): new_dict = {} for key, val in value.iteritems(): new_key = customConvertJson(key) new_val = customConvertJson(val) new_dict[new_key] = new_val return new_dict else: return value def ServiceMethod(fn): fn.IsServiceMethod = True return fn class ServiceException(Exception): pass class ServiceRequestNotTranslatable(ServiceException): pass class BadServiceRequest(ServiceException): pass class ServiceMethodNotFound(ServiceException): pass class ServiceHandler(object): def __init__(self, service): self.service=service @classmethod def blank_result_dict(cls): return {'id': None, 'result': None, 'err': None, 'err_traceback': None} def dispatchRequest(self, request): """ Invoke a json RPC call from a decoded json request. @param request: a decoded json_request @returns a dictionary with keys id, result, err and err_traceback """ results = self.blank_result_dict() try: results['id'] = self._getRequestId(request) methName = request['method'] args = request['params'] except KeyError: raise BadServiceRequest(request) autotest_stats.Counter('rpc').increment(methName) metadata = request.copy() metadata['_type'] = 'rpc' metadata['rpc_server'] = socket.gethostname() timer = autotest_stats.Timer('rpc', metadata=metadata) try: timer.start() meth = self.findServiceEndpoint(methName) results['result'] = self.invokeServiceEndpoint(meth, args) except Exception, err: results['err_traceback'] = traceback.format_exc() results['err'] = err finally: timer.stop(methName) return results def _getRequestId(self, request): try: return request['id'] except KeyError: raise BadServiceRequest(request) def handleRequest(self, jsonRequest): request = self.translateRequest(jsonRequest) results = self.dispatchRequest(request) return self.translateResult(results) @staticmethod def translateRequest(data): try: req = json_decoder.decode(data) except: raise ServiceRequestNotTranslatable(data) req = customConvertJson(req) return req def findServiceEndpoint(self, name): try: meth = getattr(self.service, name) return meth except AttributeError: raise ServiceMethodNotFound(name) def invokeServiceEndpoint(self, meth, args): return meth(*args) @staticmethod def translateResult(result_dict): """ @param result_dict: a dictionary containing the result, error, traceback and id. @returns translated json result """ if result_dict['err'] is not None: error_name = result_dict['err'].__class__.__name__ result_dict['err'] = {'name': error_name, 'message': str(result_dict['err']), 'traceback': result_dict['err_traceback']} result_dict['result'] = None try: json_dict = {'result': result_dict['result'], 'id': result_dict['id'], 'error': result_dict['err'] } data = json_encoder.encode(json_dict) except TypeError, e: err_traceback = traceback.format_exc() print err_traceback err = {"name" : "JSONEncodeException", "message" : "Result Object Not Serializable", "traceback" : err_traceback} data = json_encoder.encode({"result":None, "id":result_dict['id'], "error":err}) return data