blob: 577b6dab20bca3827132d0fced4bebfe0538b196 [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
Dan Shi5d94b2b2017-02-23 12:40:50 -080021import socket
mblighe8819cd2008-02-15 16:48:40 +000022import traceback
23
Jakob Juelich59cfe542014-09-02 16:37:46 -070024from 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
Aviv Keshet14cac442016-11-20 21:44:11 -080035# 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).
Gabe Black1e1c41b2015-02-04 23:55:15 -080039from autotest_lib.client.common_lib.cros.graphite import autotest_stats
mblighe8819cd2008-02-15 16:48:40 +000040
Jakob Juelich59cfe542014-09-02 16:37:46 -070041
42json_decoder = decoder.JSONDecoder()
43
44
mblighe8819cd2008-02-15 16:48:40 +000045def 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
mblighe8819cd2008-02-15 16:48:40 +000067
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
showard3d6ae112009-05-02 00:45:48 +000084
mblighe8819cd2008-02-15 16:48:40 +000085class ServiceHandler(object):
86
87 def __init__(self, service):
88 self.service=service
jadmanski0afbb632008-06-06 21:10:57 +000089
showard2df3c692009-09-11 18:41:07 +000090
91 @classmethod
92 def blank_result_dict(cls):
93 return {'id': None, 'result': None, 'err': None, 'err_traceback': None}
94
showard3d6ae112009-05-02 00:45:48 +000095 def dispatchRequest(self, request):
showard02ed4bd2009-09-09 15:30:11 +000096 """
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 """
showard2df3c692009-09-11 18:41:07 +0000101 results = self.blank_result_dict()
showard02ed4bd2009-09-09 15:30:11 +0000102
showard3d6ae112009-05-02 00:45:48 +0000103 try:
showard02ed4bd2009-09-09 15:30:11 +0000104 results['id'] = self._getRequestId(request)
showard3d6ae112009-05-02 00:45:48 +0000105 methName = request['method']
106 args = request['params']
107 except KeyError:
108 raise BadServiceRequest(request)
mblighe8819cd2008-02-15 16:48:40 +0000109
Gabe Black1e1c41b2015-02-04 23:55:15 -0800110 autotest_stats.Counter('rpc').increment(methName)
Dan Shi9d33e7c2014-12-16 14:03:30 -0800111
112 metadata = request.copy()
113 metadata['_type'] = 'rpc'
Dan Shi5d94b2b2017-02-23 12:40:50 -0800114 metadata['rpc_server'] = socket.gethostname()
Gabe Black1e1c41b2015-02-04 23:55:15 -0800115 timer = autotest_stats.Timer('rpc', metadata=metadata)
Alex Miller1b249312013-11-12 15:45:37 -0800116
showard02ed4bd2009-09-09 15:30:11 +0000117 try:
Jiaxi Luo0f5f0442014-05-23 11:36:54 -0700118 timer.start()
Dan Shicd0a01d2014-06-11 20:53:35 -0700119 meth = self.findServiceEndpoint(methName)
showard02ed4bd2009-09-09 15:30:11 +0000120 results['result'] = self.invokeServiceEndpoint(meth, args)
121 except Exception, err:
122 results['err_traceback'] = traceback.format_exc()
123 results['err'] = err
Jiaxi Luo0f5f0442014-05-23 11:36:54 -0700124 finally:
125 timer.stop(methName)
showard02ed4bd2009-09-09 15:30:11 +0000126
127 return results
128
showard3d6ae112009-05-02 00:45:48 +0000129
130 def _getRequestId(self, request):
131 try:
132 return request['id']
133 except KeyError:
134 raise BadServiceRequest(request)
135
showard02ed4bd2009-09-09 15:30:11 +0000136
showard3d6ae112009-05-02 00:45:48 +0000137 def handleRequest(self, jsonRequest):
showard3d6ae112009-05-02 00:45:48 +0000138 request = self.translateRequest(jsonRequest)
showard02ed4bd2009-09-09 15:30:11 +0000139 results = self.dispatchRequest(request)
140 return self.translateResult(results)
jadmanski0afbb632008-06-06 21:10:57 +0000141
mblighe8819cd2008-02-15 16:48:40 +0000142
showardef6fe022009-03-27 20:55:16 +0000143 @staticmethod
144 def translateRequest(data):
mblighe8819cd2008-02-15 16:48:40 +0000145 try:
146 req = json_decoder.decode(data)
147 except:
148 raise ServiceRequestNotTranslatable(data)
showard3d6ae112009-05-02 00:45:48 +0000149 req = customConvertJson(req)
mblighe8819cd2008-02-15 16:48:40 +0000150 return req
showard5da17d42008-04-28 18:07:58 +0000151
mblighe8819cd2008-02-15 16:48:40 +0000152 def findServiceEndpoint(self, name):
153 try:
154 meth = getattr(self.service, name)
mblighe8819cd2008-02-15 16:48:40 +0000155 return meth
mblighe8819cd2008-02-15 16:48:40 +0000156 except AttributeError:
157 raise ServiceMethodNotFound(name)
158
159 def invokeServiceEndpoint(self, meth, args):
160 return meth(*args)
161
showardef6fe022009-03-27 20:55:16 +0000162 @staticmethod
showard02ed4bd2009-09-09 15:30:11 +0000163 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
mblighe8819cd2008-02-15 16:48:40 +0000175
176 try:
showard02ed4bd2009-09-09 15:30:11 +0000177 json_dict = {'result': result_dict['result'],
178 'id': result_dict['id'],
179 'error': result_dict['err'] }
180 data = json_encoder.encode(json_dict)
mblighe8819cd2008-02-15 16:48:40 +0000181 except TypeError, e:
showard5da17d42008-04-28 18:07:58 +0000182 err_traceback = traceback.format_exc()
183 print err_traceback
184 err = {"name" : "JSONEncodeException",
185 "message" : "Result Object Not Serializable",
186 "traceback" : err_traceback}
showard02ed4bd2009-09-09 15:30:11 +0000187 data = json_encoder.encode({"result":None, "id":result_dict['id'],
188 "error":err})
showard5da17d42008-04-28 18:07:58 +0000189
mblighe8819cd2008-02-15 16:48:40 +0000190 return data