From c8a17ee264a269a59651936b34a960f4d40a9074 Mon Sep 17 00:00:00 2001 From: Santhosh Thottingal Date: Sun, 19 Jul 2009 18:32:04 +0530 Subject: JSON RPC Based new architecture and corresponding changes --- silpa/jsonrpc/__init__.py | 26 +++++ silpa/jsonrpc/cgiwrapper.py | 45 ++++++++ silpa/jsonrpc/json.py | 230 ++++++++++++++++++++++++++++++++++++++++ silpa/jsonrpc/modpywrapper.py | 52 +++++++++ silpa/jsonrpc/proxy.py | 49 +++++++++ silpa/jsonrpc/serviceHandler.py | 152 ++++++++++++++++++++++++++ 6 files changed, 554 insertions(+) create mode 100644 silpa/jsonrpc/__init__.py create mode 100644 silpa/jsonrpc/cgiwrapper.py create mode 100644 silpa/jsonrpc/json.py create mode 100644 silpa/jsonrpc/modpywrapper.py create mode 100644 silpa/jsonrpc/proxy.py create mode 100644 silpa/jsonrpc/serviceHandler.py (limited to 'silpa/jsonrpc') diff --git a/silpa/jsonrpc/__init__.py b/silpa/jsonrpc/__init__.py new file mode 100644 index 0000000..71779a1 --- /dev/null +++ b/silpa/jsonrpc/__init__.py @@ -0,0 +1,26 @@ + +""" + 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 +""" + +from jsonrpc.json import loads, dumps, JSONEncodeException, JSONDecodeException +from jsonrpc.proxy import ServiceProxy, JSONRPCException +from jsonrpc.serviceHandler import ServiceMethod, ServiceHandler, ServiceMethodNotFound, ServiceException +from jsonrpc.cgiwrapper import handleCGI +from jsonrpc.modpywrapper import handler \ No newline at end of file diff --git a/silpa/jsonrpc/cgiwrapper.py b/silpa/jsonrpc/cgiwrapper.py new file mode 100644 index 0000000..a5bc2b0 --- /dev/null +++ b/silpa/jsonrpc/cgiwrapper.py @@ -0,0 +1,45 @@ +import sys, os +from jsonrpc import ServiceHandler + +class CGIServiceHandler(ServiceHandler): + def __init__(self, service): + if service == None: + import __main__ as service + + ServiceHandler.__init__(self, service) + + def handleRequest(self, fin=None, fout=None, env=None): + if fin==None: + fin = sys.stdin + if fout==None: + fout = sys.stdout + if env == None: + env = os.environ + + try: + contLen=int(env['CONTENT_LENGTH']) + data = fin.read(contLen) + except Exception, e: + data = "" + + resultData = ServiceHandler.handleRequest(self, data) + + response = "Content-Type: text/plain\n" + response += "Content-Length: %d\n\n" % len(resultData) + response += resultData + + #on windows all \n are converted to \r\n if stdout is a terminal and is not set to binary mode :( + #this will then cause an incorrect Content-length. + #I have only experienced this problem with apache on Win so far. + if sys.platform == "win32": + try: + import msvcrt + msvcrt.setmode(fout.fileno(), os.O_BINARY) + except: + pass + #put out the response + fout.write(response) + fout.flush() + +def handleCGI(service=None, fin=None, fout=None, env=None): + CGIServiceHandler(service).handleRequest(fin, fout, env) \ No newline at end of file diff --git a/silpa/jsonrpc/json.py b/silpa/jsonrpc/json.py new file mode 100644 index 0000000..4f945b2 --- /dev/null +++ b/silpa/jsonrpc/json.py @@ -0,0 +1,230 @@ + +""" + 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 +""" + +from types import * +import re + +CharReplacements ={ + '\t': '\\t', + '\b': '\\b', + '\f': '\\f', + '\n': '\\n', + '\r': '\\r', + '\\': '\\\\', + '/': '\\/', + '"': '\\"'} + +EscapeCharToChar = { + 't': '\t', + 'b': '\b', + 'f': '\f', + 'n': '\n', + 'r': '\r', + '\\': '\\', + '/': '/', + '"' : '"'} + +StringEscapeRE= re.compile(r'[\x00-\x19\\"/\b\f\n\r\t]') +Digits = ['0', '1', '2','3','4','5','6','7','8','9'] + + +class JSONEncodeException(Exception): + def __init__(self, obj): + Exception.__init__(self) + self.obj = obj + + def __str__(self): + return "Object not encodeable: %s" % self.obj + + +class JSONDecodeException(Exception): + def __init__(self, message): + Exception.__init__(self) + self.message = message + + def __str__(self): + return self.message + + +def escapeChar(match): + c=match.group(0) + try: + replacement = CharReplacements[c] + return replacement + except KeyError: + d = ord(c) + if d < 32: + return '\\u%04x' % d + else: + return c + +def dumps(obj): + return unicode("".join([part for part in dumpParts (obj)])) + +def dumpParts (obj): + objType = type(obj) + if obj == None: + yield u'null' + elif objType is BooleanType: + if obj: + yield u'true' + else: + yield u'false' + elif objType is DictionaryType: + yield u'{' + isFirst=True + for (key, value) in obj.items(): + if isFirst: + isFirst=False + else: + yield u"," + yield u'"' + StringEscapeRE.sub(escapeChar, key) +u'":' + for part in dumpParts (value): + yield part + yield u'}' + elif objType in StringTypes: + yield u'"' + StringEscapeRE.sub(escapeChar, obj) +u'"' + + elif objType in [TupleType, ListType, GeneratorType]: + yield u'[' + isFirst=True + for item in obj: + if isFirst: + isFirst=False + else: + yield u"," + for part in dumpParts (item): + yield part + yield u']' + elif objType in [IntType, LongType, FloatType]: + yield unicode(obj) + else: + raise JSONEncodeException(obj) + + +def loads(s): + stack = [] + chars = iter(s) + value = None + currCharIsNext=False + + try: + while(1): + skip = False + if not currCharIsNext: + c = chars.next() + while(c in [' ', '\t', '\r','\n']): + c = chars.next() + currCharIsNext=False + if c=='"': + value = '' + try: + c=chars.next() + while c != '"': + if c == '\\': + c=chars.next() + try: + value+=EscapeCharToChar[c] + except KeyError: + if c == 'u': + hexCode = chars.next() + chars.next() + chars.next() + chars.next() + value += unichr(int(hexCode,16)) + else: + raise JSONDecodeException("Bad Escape Sequence Found") + else: + value+=c + c=chars.next() + except StopIteration: + raise JSONDecodeException("Expected end of String") + elif c == '{': + stack.append({}) + skip=True + elif c =='}': + value = stack.pop() + elif c == '[': + stack.append([]) + skip=True + elif c == ']': + value = stack.pop() + elif c in [',',':']: + skip=True + elif c in Digits or c == '-': + digits=[c] + c = chars.next() + numConv = int + try: + while c in Digits: + digits.append(c) + c = chars.next() + if c == ".": + numConv=float + digits.append(c) + c = chars.next() + while c in Digits: + digits.append(c) + c = chars.next() + if c.upper() == 'E': + digits.append(c) + c = chars.next() + if c in ['+','-']: + digits.append(c) + c = chars.next() + while c in Digits: + digits.append(c) + c = chars.next() + else: + raise JSONDecodeException("Expected + or -") + except StopIteration: + pass + value = numConv("".join(digits)) + currCharIsNext=True + + elif c in ['t','f','n']: + kw = c+ chars.next() + chars.next() + chars.next() + if kw == 'null': + value = None + elif kw == 'true': + value = True + elif kw == 'fals' and chars.next() == 'e': + value = False + else: + raise JSONDecodeException('Expected Null, False or True') + else: + raise JSONDecodeException('Expected []{}," or Number, Null, False or True') + + if not skip: + if len(stack): + top = stack[-1] + if type(top) is ListType: + top.append(value) + elif type(top) is DictionaryType: + stack.append(value) + elif type(top) in StringTypes: + key = stack.pop() + stack[-1][key] = value + else: + raise JSONDecodeException("Expected dictionary key, or start of a value") + else: + return value + except StopIteration: + raise JSONDecodeException("Unexpected end of JSON source") + + diff --git a/silpa/jsonrpc/modpywrapper.py b/silpa/jsonrpc/modpywrapper.py new file mode 100644 index 0000000..3e61017 --- /dev/null +++ b/silpa/jsonrpc/modpywrapper.py @@ -0,0 +1,52 @@ +import sys, os +from jsonrpc import ServiceHandler, ServiceException + + +class ServiceImplementaionNotFound(ServiceException): + pass + + +class ModPyServiceHandler(ServiceHandler): + def __init__(self, req): + self.req = req + ServiceHandler.__init__(self, None) + + + def findServiceEndpoint(self, name): + req = self.req + + (modulePath, fileName) = os.path.split(req.filename) + (moduleName, ext) = os.path.splitext(fileName) + + if not os.path.exists(os.path.join(modulePath, moduleName + ".py")): + raise ServiceImplementaionNotFound() + else: + if not modulePath in sys.path: + sys.path.insert(0, modulePath) + + from mod_python import apache + module = apache.import_module(moduleName, log=1) + + if hasattr(module, "service"): + self.service = module.service + elif hasattr(module, "Service"): + self.service = module.Service() + else: + self.service = module + + return ServiceHandler.findServiceEndpoint(self, name) + + + def handleRequest(self, data): + self.req.content_type = "text/plain" + data = self.req.read() + resultData = ServiceHandler.handleRequest(self, data) + self.req.write(resultData) + self.req.flush() + +def handler(req): + from mod_python import apache + ModPyServiceHandler(req).handleRequest(req) + return apache.OK + + diff --git a/silpa/jsonrpc/proxy.py b/silpa/jsonrpc/proxy.py new file mode 100644 index 0000000..e61fd13 --- /dev/null +++ b/silpa/jsonrpc/proxy.py @@ -0,0 +1,49 @@ + +""" + 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 urllib +from jsonrpc.json import dumps, loads + +class JSONRPCException(Exception): + def __init__(self, rpcError): + Exception.__init__(self) + self.error = rpcError + +class ServiceProxy(object): + def __init__(self, serviceURL, serviceName=None): + self.__serviceURL = serviceURL + self.__serviceName = serviceName + + def __getattr__(self, name): + if self.__serviceName != None: + name = "%s.%s" % (self.__serviceName, name) + return ServiceProxy(self.__serviceURL, name) + + def __call__(self, *args): + postdata = dumps({"method": self.__serviceName, 'params': args, 'id':'jsonrpc'}) + respdata = urllib.urlopen(self.__serviceURL, postdata).read() + resp = loads(respdata) + if resp['error'] != None: + raise JSONRPCException(resp['error']) + else: + return resp['result'] + + diff --git a/silpa/jsonrpc/serviceHandler.py b/silpa/jsonrpc/serviceHandler.py new file mode 100644 index 0000000..3fb5675 --- /dev/null +++ b/silpa/jsonrpc/serviceHandler.py @@ -0,0 +1,152 @@ + +""" + 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 +""" + +from common import * +from jsonrpc import JSONEncodeException +from jsonrpc import dumps +from jsonrpc import loads +from utils import * + + +def ServiceMethod(fn): + fn.IsServiceMethod = True + return fn + +class ServiceException(Exception): + pass + +class ServiceRequestNotTranslatable(ServiceException): + pass + +class BadServiceRequest(ServiceException): + pass + +class ServiceMethodNotFound(ServiceException): + def __init__(self, name): + self.methodName = name + +class ServiceHandler(object): + + def __init__(self, service): + self.service = service + + def handleRequest(self, json): + err = None + result = None + id_ = '' + args = None + try: + req = self.translateRequest(json) + except ServiceRequestNotTranslatable, e: + err = e + req = {'id':id_} + + if err == None: + try: + id_ = req['id'] + methName = req['method'] + try: + args = req['params'] + except: + pass + except: + err = BadServiceRequest(json) + module_instance=None + if err == None: + try: + meth = self.locate(methName) + except Exception, e: + err = e + + if err == None: + try: + result = self.call(meth, args) + except Exception, e: + err = e + + resultdata = self.translateResult(result, err, id_) + return resultdata + + def translateRequest(self, data): + try: + req = loads(data) + except: + raise ServiceRequestNotTranslatable(data) + return req + + def locate(self, name): + try: + if name.startswith("system."): + return getattr(self, name.split(".")[1]) + module_manager = ModuleManager() + modules = module_manager.getAllModules() + for module in modules: + for key in dir(module): + try: + method = getattr(module, key) + if getattr(method, "IsServiceMethod"): + if ("modules."+module.__class__.__name__ + "." + key) == name : + meth = method + break + except AttributeError: + pass + if meth==None : + raise ServiceMethodNotFound(name) + except AttributeError: + raise ServiceMethodNotFound(name) + + return meth + def listMethods(self): + results = [] + module_manager = ModuleManager() + modules = module_manager.getAllModules() + for module in modules: + for key in dir(module): + method = getattr(module, key) + try: + if getattr(method, "IsServiceMethod"): + results.append("modules."+module.__class__.__name__ + "." + key) + except: + pass + results.sort() + return results + + def call(self, meth, args=None): + if args == None : + return meth() + else: + return meth(args) #return meth(*args) + + def translateResult(self, rslt, err, id_): + if err != None: + err = {"name": err.__class__.__name__, "message":err.message} + rslt = None + + try: + data = dumps({"result":rslt, "id":id_, "error":err}) + except JSONEncodeException, e: + err = {"name": "JSONEncodeException", "message":"Result Object Not Serializable"} + data = dumps({"result":None, "id":id_, "error":err}) + + return data +# -------------------------------------------------------------------- +# request dispatcher + -- cgit