Home | History | Annotate | Download | only in json_rpc
      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 
     21 import socket
     22 import traceback
     23 
     24 from json import decoder
     25 
     26 try:
     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()
     31 except 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).
     39 from autotest_lib.client.common_lib.cros.graphite import autotest_stats
     40 
     41 
     42 json_decoder = decoder.JSONDecoder()
     43 
     44 
     45 def 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 
     68 def ServiceMethod(fn):
     69     fn.IsServiceMethod = True
     70     return fn
     71 
     72 class ServiceException(Exception):
     73     pass
     74 
     75 class ServiceRequestNotTranslatable(ServiceException):
     76     pass
     77 
     78 class BadServiceRequest(ServiceException):
     79     pass
     80 
     81 class ServiceMethodNotFound(ServiceException):
     82     pass
     83 
     84 
     85 class 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