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 21from __future__ import absolute_import 22from __future__ import division 23from __future__ import print_function 24 25import socket 26import traceback 27 28from json import decoder 29 30import six 31 32try: 33 from django.core import exceptions as django_exceptions 34 # Django JSON encoder uses the standard json encoder but can handle DateTime 35 from django.core.serializers import json as django_encoder 36 json_encoder = django_encoder.DjangoJSONEncoder() 37except django_exceptions.ImproperlyConfigured: 38 from json import encoder 39 json_encoder = encoder.JSONEncoder() 40 41 42json_decoder = decoder.JSONDecoder() 43 44 45def 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, six.text_type): 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 six.iteritems(value): 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 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 84 85class 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 metadata = request.copy() 111 metadata['_type'] = 'rpc' 112 metadata['rpc_server'] = socket.gethostname() 113 try: 114 meth = self.findServiceEndpoint(methName) 115 results['result'] = self.invokeServiceEndpoint(meth, args) 116 except Exception as err: 117 results['err_traceback'] = traceback.format_exc() 118 results['err'] = err 119 120 return results 121 122 123 def _getRequestId(self, request): 124 try: 125 return request['id'] 126 except KeyError: 127 raise BadServiceRequest(request) 128 129 130 def handleRequest(self, jsonRequest): 131 request = self.translateRequest(jsonRequest) 132 results = self.dispatchRequest(request) 133 return self.translateResult(results) 134 135 136 @staticmethod 137 def translateRequest(data): 138 try: 139 req = json_decoder.decode(data) 140 except: 141 raise ServiceRequestNotTranslatable(data) 142 req = customConvertJson(req) 143 return req 144 145 def findServiceEndpoint(self, name): 146 try: 147 meth = getattr(self.service, name) 148 return meth 149 except AttributeError: 150 raise ServiceMethodNotFound(name) 151 152 def invokeServiceEndpoint(self, meth, args): 153 return meth(*args) 154 155 @staticmethod 156 def translateResult(result_dict): 157 """ 158 @param result_dict: a dictionary containing the result, error, traceback 159 and id. 160 @returns translated json result 161 """ 162 if result_dict['err'] is not None: 163 error_name = result_dict['err'].__class__.__name__ 164 result_dict['err'] = {'name': error_name, 165 'message': str(result_dict['err']), 166 'traceback': result_dict['err_traceback']} 167 result_dict['result'] = None 168 169 try: 170 json_dict = {'result': result_dict['result'], 171 'id': result_dict['id'], 172 'error': result_dict['err'] } 173 data = json_encoder.encode(json_dict) 174 except TypeError as e: 175 err_traceback = traceback.format_exc() 176 print(err_traceback) 177 err = {"name" : "JSONEncodeException", 178 "message" : "Result Object Not Serializable", 179 "traceback" : err_traceback} 180 data = json_encoder.encode({"result":None, "id":result_dict['id'], 181 "error":err}) 182 183 return data 184