"""
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