blob: e3e52f9856e8e8bd2965c78057d3c5efcfb344a0 [file] [log] [blame]
mblighe8819cd2008-02-15 16:48:40 +00001"""
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 traceback
22
Jakob Juelich59cfe542014-09-02 16:37:46 -070023from json import decoder
24
25try:
26 from django.core import exceptions as django_exceptions
27 # Django JSON encoder uses the standard json encoder but can handle DateTime
28 from django.core.serializers import json as django_encoder
29 json_encoder = django_encoder.DjangoJSONEncoder()
30except django_exceptions.ImproperlyConfigured:
31 from json import encoder
32 json_encoder = encoder.JSONEncoder()
33
Aviv Keshet14cac442016-11-20 21:44:11 -080034# TODO(akeshet): Eliminate this and replace with monarch metrics. (At the
35# moment, I don't think we can just easily swap out, because this module is
36# called by apache for rpc handling, and we don't have a ts_mon thread for that
37# yet).
Gabe Black1e1c41b2015-02-04 23:55:15 -080038from autotest_lib.client.common_lib.cros.graphite import autotest_stats
mblighe8819cd2008-02-15 16:48:40 +000039
Jakob Juelich59cfe542014-09-02 16:37:46 -070040
41json_decoder = decoder.JSONDecoder()
42
43
mblighe8819cd2008-02-15 16:48:40 +000044def customConvertJson(value):
45 """\
46 Recursively process JSON values and do type conversions.
47 -change floats to ints
48 -change unicodes to strs
49 """
50 if isinstance(value, float):
51 return int(value)
52 elif isinstance(value, unicode):
53 return str(value)
54 elif isinstance(value, list):
55 return [customConvertJson(item) for item in value]
56 elif isinstance(value, dict):
57 new_dict = {}
58 for key, val in value.iteritems():
59 new_key = customConvertJson(key)
60 new_val = customConvertJson(val)
61 new_dict[new_key] = new_val
62 return new_dict
63 else:
64 return value
65
mblighe8819cd2008-02-15 16:48:40 +000066
67def ServiceMethod(fn):
68 fn.IsServiceMethod = True
69 return fn
70
71class ServiceException(Exception):
72 pass
73
74class ServiceRequestNotTranslatable(ServiceException):
75 pass
76
77class BadServiceRequest(ServiceException):
78 pass
79
80class ServiceMethodNotFound(ServiceException):
81 pass
82
showard3d6ae112009-05-02 00:45:48 +000083
mblighe8819cd2008-02-15 16:48:40 +000084class ServiceHandler(object):
85
86 def __init__(self, service):
87 self.service=service
jadmanski0afbb632008-06-06 21:10:57 +000088
showard2df3c692009-09-11 18:41:07 +000089
90 @classmethod
91 def blank_result_dict(cls):
92 return {'id': None, 'result': None, 'err': None, 'err_traceback': None}
93
showard3d6ae112009-05-02 00:45:48 +000094 def dispatchRequest(self, request):
showard02ed4bd2009-09-09 15:30:11 +000095 """
96 Invoke a json RPC call from a decoded json request.
97 @param request: a decoded json_request
98 @returns a dictionary with keys id, result, err and err_traceback
99 """
showard2df3c692009-09-11 18:41:07 +0000100 results = self.blank_result_dict()
showard02ed4bd2009-09-09 15:30:11 +0000101
showard3d6ae112009-05-02 00:45:48 +0000102 try:
showard02ed4bd2009-09-09 15:30:11 +0000103 results['id'] = self._getRequestId(request)
showard3d6ae112009-05-02 00:45:48 +0000104 methName = request['method']
105 args = request['params']
106 except KeyError:
107 raise BadServiceRequest(request)
mblighe8819cd2008-02-15 16:48:40 +0000108
Gabe Black1e1c41b2015-02-04 23:55:15 -0800109 autotest_stats.Counter('rpc').increment(methName)
Dan Shi9d33e7c2014-12-16 14:03:30 -0800110
111 metadata = request.copy()
112 metadata['_type'] = 'rpc'
Gabe Black1e1c41b2015-02-04 23:55:15 -0800113 timer = autotest_stats.Timer('rpc', metadata=metadata)
Alex Miller1b249312013-11-12 15:45:37 -0800114
showard02ed4bd2009-09-09 15:30:11 +0000115 try:
Jiaxi Luo0f5f0442014-05-23 11:36:54 -0700116 timer.start()
Dan Shicd0a01d2014-06-11 20:53:35 -0700117 meth = self.findServiceEndpoint(methName)
showard02ed4bd2009-09-09 15:30:11 +0000118 results['result'] = self.invokeServiceEndpoint(meth, args)
119 except Exception, err:
120 results['err_traceback'] = traceback.format_exc()
121 results['err'] = err
Jiaxi Luo0f5f0442014-05-23 11:36:54 -0700122 finally:
123 timer.stop(methName)
showard02ed4bd2009-09-09 15:30:11 +0000124
125 return results
126
showard3d6ae112009-05-02 00:45:48 +0000127
128 def _getRequestId(self, request):
129 try:
130 return request['id']
131 except KeyError:
132 raise BadServiceRequest(request)
133
showard02ed4bd2009-09-09 15:30:11 +0000134
showard3d6ae112009-05-02 00:45:48 +0000135 def handleRequest(self, jsonRequest):
showard3d6ae112009-05-02 00:45:48 +0000136 request = self.translateRequest(jsonRequest)
showard02ed4bd2009-09-09 15:30:11 +0000137 results = self.dispatchRequest(request)
138 return self.translateResult(results)
jadmanski0afbb632008-06-06 21:10:57 +0000139
mblighe8819cd2008-02-15 16:48:40 +0000140
showardef6fe022009-03-27 20:55:16 +0000141 @staticmethod
142 def translateRequest(data):
mblighe8819cd2008-02-15 16:48:40 +0000143 try:
144 req = json_decoder.decode(data)
145 except:
146 raise ServiceRequestNotTranslatable(data)
showard3d6ae112009-05-02 00:45:48 +0000147 req = customConvertJson(req)
mblighe8819cd2008-02-15 16:48:40 +0000148 return req
showard5da17d42008-04-28 18:07:58 +0000149
mblighe8819cd2008-02-15 16:48:40 +0000150 def findServiceEndpoint(self, name):
151 try:
152 meth = getattr(self.service, name)
mblighe8819cd2008-02-15 16:48:40 +0000153 return meth
mblighe8819cd2008-02-15 16:48:40 +0000154 except AttributeError:
155 raise ServiceMethodNotFound(name)
156
157 def invokeServiceEndpoint(self, meth, args):
158 return meth(*args)
159
showardef6fe022009-03-27 20:55:16 +0000160 @staticmethod
showard02ed4bd2009-09-09 15:30:11 +0000161 def translateResult(result_dict):
162 """
163 @param result_dict: a dictionary containing the result, error, traceback
164 and id.
165 @returns translated json result
166 """
167 if result_dict['err'] is not None:
168 error_name = result_dict['err'].__class__.__name__
169 result_dict['err'] = {'name': error_name,
170 'message': str(result_dict['err']),
171 'traceback': result_dict['err_traceback']}
172 result_dict['result'] = None
mblighe8819cd2008-02-15 16:48:40 +0000173
174 try:
showard02ed4bd2009-09-09 15:30:11 +0000175 json_dict = {'result': result_dict['result'],
176 'id': result_dict['id'],
177 'error': result_dict['err'] }
178 data = json_encoder.encode(json_dict)
mblighe8819cd2008-02-15 16:48:40 +0000179 except TypeError, e:
showard5da17d42008-04-28 18:07:58 +0000180 err_traceback = traceback.format_exc()
181 print err_traceback
182 err = {"name" : "JSONEncodeException",
183 "message" : "Result Object Not Serializable",
184 "traceback" : err_traceback}
showard02ed4bd2009-09-09 15:30:11 +0000185 data = json_encoder.encode({"result":None, "id":result_dict['id'],
186 "error":err})
showard5da17d42008-04-28 18:07:58 +0000187
mblighe8819cd2008-02-15 16:48:40 +0000188 return data