Mercurial > hg > AuthRPC
changeset 4:ad5a8748afcf
Add test framework
author | Ben Croston <ben@croston.org> |
---|---|
date | Wed, 31 Aug 2011 21:35:14 +0100 |
parents | 43595981978d |
children | f5e3ba8cfcd0 |
files | INSTALL LICENCE README.txt client/ServerProxy.py client/ServerProxy2.py client/__init__.py client/thing.py server/JsonRpcApp.py server/__init__.py setup.py wibble/__init__.py wibble/client/ServerProxy.py wibble/client/ServerProxy3.py wibble/client/__init__.py wibble/client/thing.py wibble/server/__init__.py wibble/tests.py |
diffstat | 13 files changed, 615 insertions(+), 492 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/INSTALL Wed Aug 31 21:35:14 2011 +0100 @@ -0,0 +1,4 @@ +python setup.py install + or +python3 setup.py install +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/LICENCE Wed Aug 31 21:35:14 2011 +0100 @@ -0,0 +1,20 @@ +Copyright (c) 2011 Ben Croston + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +
--- a/README.txt Tue Aug 30 23:40:42 2011 +0100 +++ b/README.txt Wed Aug 31 21:35:14 2011 +0100 @@ -1,1 +1,4 @@ -readme file +With python 3.x, only the client package is available at the moment, until WebOb has been ported to python 3. + +The server depends on WebOb 1.0.0 and above. This is automatically installed if you have an internet connection, otherwise download and install from http://pypi.python.org/pypi/WebOb +
--- a/client/ServerProxy.py Tue Aug 30 23:40:42 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,172 +0,0 @@ -#!/usr/bin/env python3 -from uuid import uuid4 -from urllib.parse import urlparse -import json -import http.client -import copy -import socket -import hashlib - -class _Method(object): - def __init__(self, call, name, username=None, password=None): - self.call = call - self.name = name - self._username = username - self._password = password - - def __call__(self, *args, **kwargs): - request = {} - request['id'] = str(uuid4()) - request['method'] = self.name - - if len(kwargs) is not 0: - params = copy.copy(kwargs) - index = 0 - for arg in args: - params[str(index)] = arg - index = index + 1 - elif len(args) is not 0: - params = copy.copy(args) - else: - params = None - request['params'] = params - - if self._username is not None: - request['username'] = self._username - if self._password is not None: - request['password'] = hashlib.md5(self._password.encode()).hexdigest() - - resp = self.call(json.dumps(request)) - if resp is not None and resp['error'] is None and resp['id'] == request['id']: - return resp['result'] - else: - raise Exception('This is not supposed to happen -- btc') ######## - - def __getattr__(self, name): - return _Method(self.call, "%s.%s" % (self.name, name), self._username, self._password) - -class _JSONRPCTransport(object): - headers = {'Content-Type':'application/json', - 'Accept':'application/json'} - - def __init__(self, uri, proxy_uri=None, user_agent=None): - self.headers['User-Agent'] = user_agent if user_agent is not None else 'jsonrpclib' - if proxy_uri is not None: - self.connection_url = urlparse(proxy_uri) - self.request_path = uri - else: - self.connection_url = urlparse(uri) - self.request_path = self.connection_url.path - - def request(self, request_body): - if self.connection_url.scheme == 'http': - if self.connection_url.port is None: - port = 80 - else: - port = self.connection_url.port - connection = http.client.HTTPConnection(self.connection_url.hostname+':'+str(port)) - elif self.connection_url.scheme == 'https': - if self.connection_url.port is None: - port = 443 - else: - port = self.connection_url.port - connection = http.client.HTTPSConnection(self.connection_url.hostname+':'+str(port)) - else: - raise Exception('unsupported transport') - - connection.request('POST', self.request_path, body=request_body, headers=self.headers) - return connection.getresponse() - -class BadRequestException(Exception): - """HTTP 400 - Bad Request""" - def __init__(self): - Exception.__init__(self,'HTTP 400 - Bad Request') - -class UnauthorisedException(Exception): - """HTTP 401 - Unauthorised""" - def __init__(self): - Exception.__init__(self,'HTTP 401 - Unauthorised') - -class ForbiddenException(Exception): - """HTTP 403 - Forbidden""" - def __init__(self): - Exception.__init__(self,'HTTP 403 - Forbidden') - -class NotFoundException(Exception): - """HTTP 404 - Not Found""" - def __init__(self): - Exception.__init__(self,'HTTP 404 - Not Found') - -class NetworkSocketException(Exception): - def __init__(self): - Exception.__init__(self,'Network socket exception') - -class BadGatewayException(Exception): - """HTTP 502 - Bad Gateway""" - def __init__(self): - Exception.__init__(self,'HTTP 502 - Bad Gateway') - -class ServerProxy(object): - def __init__(self, uri=None, transport=None, proxy_uri=None, user_agent=None, username=None, password=None): - if uri is None and transport is None: - raise Exception('either uri or transport needs to be specified') - - if transport is None: - transport = _JSONRPCTransport(uri, proxy_uri=proxy_uri, user_agent=user_agent) - self.__transport = transport - self._username = username - self._password = password - - def __request(self, request): - # call a method on the remote server - try: - response = self.__transport.request(request) - except socket.error: - raise NetworkSocketException - if response.status == 200: - return json.loads(response.read().decode()) - elif response.status == 400: - raise BadRequestException - elif response.status == 401: - raise UnauthorisedException - elif response.status == 403: - raise ForbiddenException - elif response.status == 404: - raise NotFoundException - elif response.status == 500: - msg = json.loads(response.read().decode()) - raise Exception('JSONRPCError\n%s'%msg['error']['error']) - elif response.status == 502: - raise BadGatewayException - else: - raise Exception('HTTP Status %s'%response.status) - - def __repr__(self): - return ( - "<ServerProxy for %s%s>" % - (self.__host, self.__handler) - ) - - __str__ = __repr__ - - def __getattr__(self, name): - # magic method dispatcher - return _Method(self.__request, name, self._username, self._password) - -if __name__ == '__main__': - #### btc fixme - jsonrpc_client = ServerProxy('http://localhost:1337/', username='testuser', password='', user_agent='Py3NotInternetExploiter') - #jsonrpc_client = ServerProxy('https://www.croston.org/test/index.py', - # username='testuser', - # password='', - # user_agent='Py3NotInternetExploiter') - assert jsonrpc_client.api.mymethod() == jsonrpc_client.mymethod() - try: - print(jsonrpc_client.wibble('this should fail')) - except BadRequestException: - pass # test passed - else: - raise Exception('Test failed (calling unknown method)') - - print('All tests passed') -
--- a/client/ServerProxy2.py Tue Aug 30 23:40:42 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,174 +0,0 @@ -#!/usr/bin/env python2.7 -from uuid import uuid4 -from urlparse import urlparse -try: - import json -except: - import simplejson as json -import httplib -import copy -import socket -import hashlib - -class _Method(object): - def __init__(self, call, name, username=None, password=None): - self.call = call - self.name = name - self._username = username - self._password = password - - def __call__(self, *args, **kwargs): - request = {} - request['id'] = str(uuid4()) - request['method'] = self.name - - if len(kwargs) is not 0: - params = copy.copy(kwargs) - index = 0 - for arg in args: - params[str(index)] = arg - index = index + 1 - elif len(args) is not 0: - params = copy.copy(args) - else: - params = None - request['params'] = params - - if self._username is not None: - request['username'] = self._username - if self._password is not None: - request['password'] = hashlib.md5(self._password).hexdigest() - - resp = self.call(json.dumps(request)) - if resp is not None and resp['error'] is None and resp['id'] == request['id']: - return resp['result'] - else: - raise Exception('This is not supposed to happen -- btc') ######## - - def __getattr__(self, name): - return _Method(self.call, "%s.%s" % (self.name, name), self._username, self._password) - -class _JSONRPCTransport(object): - headers = {'Content-Type':'application/json', - 'Accept':'application/json'} - - def __init__(self, uri, proxy_uri=None, user_agent=None): - self.headers['User-Agent'] = user_agent if user_agent is not None else 'jsonrpclib' - if proxy_uri is not None: - self.connection_url = urlparse(proxy_uri) - self.request_path = uri - else: - self.connection_url = urlparse(uri) - self.request_path = self.connection_url.path - - def request(self, request_body): - if self.connection_url.scheme == 'http': - if self.connection_url.port is None: - port = 80 - else: - port = self.connection_url.port - connection = httplib.HTTPConnection(self.connection_url.hostname+':'+str(port)) - elif self.connection_url.scheme == 'https': - if self.connection_url.port is None: - port = 443 - else: - port = self.connection_url.port - connection = httplib.HTTPSConnection(self.connection_url.hostname+':'+str(port)) - else: - raise Exception('unsupported transport') - connection.request('POST', self.request_path, body=request_body, headers=self.headers) - return connection.getresponse() - -class BadRequestException(Exception): - """HTTP 400 - Bad Request""" - def __init__(self): - Exception.__init__(self,'HTTP 400 - Bad Request') - -class UnauthorisedException(Exception): - """HTTP 401 - Unauthorised""" - def __init__(self): - Exception.__init__(self,'HTTP 401 - Unauthorised') - -class ForbiddenException(Exception): - """HTTP 403 - Forbidden""" - def __init__(self): - Exception.__init__(self,'HTTP 403 - Forbidden') - -class NotFoundException(Exception): - """HTTP 404 - Not Found""" - def __init__(self): - Exception.__init__(self,'HTTP 404 - Not Found') - -class NetworkSocketException(Exception): - def __init__(self): - Exception.__init__(self,'Network socket exception') - -class BadGatewayException(Exception): - """HTTP 502 - Bad Gateway""" - def __init__(self): - Exception.__init__(self,'HTTP 502 - Bad Gateway') - -class ServerProxy2(object): - def __init__(self, uri=None, transport=None, proxy_uri=None, user_agent=None, username=None, password=None): - if uri is None and transport is None: - raise Exception('either uri or transport needs to be specified') - - if transport is None: - transport = _JSONRPCTransport(uri, proxy_uri=proxy_uri, user_agent=user_agent) - self.__transport = transport - self._username = username - self._password = password - - def __request(self, request): - # call a method on the remote server - try: - response = self.__transport.request(request) - except socket.error: - raise NetworkSocketException - if response.status == 200: - return json.loads(response.read()) - elif response.status == 400: - raise BadRequestException - elif response.status == 401: - raise UnauthorisedException - elif response.status == 403: - raise ForbiddenException - elif response.status == 404: - raise NotFoundException - elif response.status == 500: - msg = json.loads(response.read()) - raise Exception('JSONRPCError\n%s'%msg['error']['error']) - elif response.status == 502: - raise BadGatewayException - else: - raise Exception('HTTP Status %s'%response.status) - - def __repr__(self): - return ( - "<ServerProxy for %s%s>" % - (self.__host, self.__handler) - ) - - __str__ = __repr__ - - def __getattr__(self, name): - # magic method dispatcher - return _Method(self.__request, name, self._username, self._password) - -if __name__ == '__main__': - ##### btc fixme - jsonrpc_client = ServerProxy2('http://localhost:1337/', username='testuser', password='', user_agent='Py2NotInternetExploiter') - #jsonrpc_client = ServerProxy2('https://www.croston.org/test/index.py', - # username='testuser', - # password='', - # user_agent='Py2NotInternetExploiter') - assert jsonrpc_client.api.mymethod() == jsonrpc_client.mymethod() - try: - print(jsonrpc_client.wibble('this should fail')) - except BadRequestException: - pass # test passed - else: - raise Exception('Test failed (calling unknown method)') - - print 'All tests passed' -
--- a/client/thing.py Tue Aug 30 23:40:42 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,3 +0,0 @@ -#!/usr/bin/python -print 'thing' -
--- a/server/JsonRpcApp.py Tue Aug 30 23:40:42 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,126 +0,0 @@ -from webob import Request, Response, exc -try: - from json import loads, dumps -except: - from simplejson import loads, dumps -import traceback -import sys - -class JsonRpcApp(object): - """ - Serve the given object via json-rpc (http://json-rpc.org/) - """ - - def __init__(self, obj, auth=None): - """ - obj - a class of functions available using jsonrpc - auth - an authentication function (optional) - """ - self.obj = obj - self.auth = auth - - def __call__(self, environ, start_response): - req = Request(environ) - try: - resp = self.process(req) - except ValueError, e: - resp = exc.HTTPBadRequest(str(e)) - except exc.HTTPException, e: - resp = e - return resp(environ, start_response) - - def process(self, req): - if not req.method == 'POST': - raise exc.HTTPMethodNotAllowed("Only POST allowed").exception - - try: - json = loads(req.body) - except ValueError, e: - raise ValueError('Bad JSON: %s' % e) - - try: - method = json['method'] - params = json['params'] - id = json['id'] - username = json['username'] if 'username' in json else None - password = json['password'] if 'password' in json else None - except KeyError, e: - raise ValueError("JSON body missing parameter: %s" % e) - - if params is None: - params = [] - if not isinstance(params, list): - raise ValueError("Bad params %r: must be a list" % params) - text = traceback.format_exc() - exc_value = sys.exc_info()[1] - error_value = dict( - name='JSONRPCError', - code=100, - message=str(exc_value), - error=text) - return Response( - status=500, - content_type='application/json', - body=dumps(dict(result=None, - error=error_value, - id=id))) - - obj = self.obj - if isinstance(self.obj,tuple) or isinstance(self.obj,list): - for x in self.obj: - if method.startswith('%s.'%x.__class__.__name__): - obj = x - method = method.replace('%s.'%obj.__class__.__name__,'',1) - break - elif method.startswith('%s.'%self.obj.__class__.__name__): - method = method.replace('%s.'%self.obj.__class__.__name__,'',1) - if method.startswith('_'): - raise exc.HTTPForbidden("Bad method name %s: must not start with _" % method).exception - try: - method = getattr(obj, method) - except AttributeError: - raise ValueError("No such method %s" % method) - - if self.auth is not None: - try: - auth_result = self.auth(username, password, req.user_agent) - except: - text = traceback.format_exc() - exc_value = sys.exc_info()[1] - error_value = dict( - name='JSONRPCError', - code=100, - message=str(exc_value), - error=text) - return Response( - status=500, - content_type='application/json', - body=dumps(dict(result=None, - error=error_value, - id=id))) - if not auth_result: - raise exc.HTTPUnauthorized().exception - - try: - result = method(*params) - except: - text = traceback.format_exc() - exc_value = sys.exc_info()[1] - error_value = dict( - name='JSONRPCError', - code=100, - message=str(exc_value), - error=text) - return Response( - status=500, - content_type='application/json', - body=dumps(dict(result=None, - error=error_value, - id=id))) - - return Response( - content_type='application/json', - body=dumps(dict(result=result, - error=None, - id=id))) -
--- a/setup.py Tue Aug 30 23:40:42 2011 +0100 +++ b/setup.py Wed Aug 31 21:35:14 2011 +0100 @@ -5,28 +5,32 @@ import platform classifiers = ['Development Status :: 3 - Alpha', - 'Operating System :: Microsoft :: Windows', - 'Operating System :: Unix', - "Operating System :: MacOS :: MacOS X", + 'Operating System :: OS Independent', 'License :: OSI Approved :: MIT License', + 'Intended Audience :: Developers', + 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Topic :: Software Development', - 'Topic :: Internet :: WWW/HTTP :: WSGI :: Middleware', - 'Topic :: Internet :: WWW/HTTP :: HTTP Servers'] + 'Topic :: Internet :: WWW/HTTP :: WSGI'] + +install_requires = [] +exclude = [] +extra = {} if platform.python_version().startswith('2'): - # python 2.x - packages = find_packages() - install_requires = ['webob>=1.0.0'] -else: - # assume python 3.x - packages = find_packages(exclude=['server']) - install_requires = [] + # we can build server with python 2 + install_requires.append('webob>=1.0.0') + extra['test_suite'] = 'wibble.tests.suite' + +if platform.python_version().startswith('3'): + # we can't build server with python 3 + exclude.append('wibble.server') + extra['use_2to3'] = True setup(name = 'Wibble', version = '0.0.1a', - packages = packages, + packages = find_packages(exclude=exclude), install_requires = install_requires, author = 'Ben Croston', author_email = 'ben@croston.org', @@ -34,7 +38,8 @@ long_description = open('README.txt').read(), license = 'MIT', keywords = 'jsonrpc', - url = 'http://www.wyre-it.co.uk/wibble/', - classifiers = classifiers, - use_2to3 = True) + url = 'http://www.wyre-it.co.uk/wibble/', + classifiers = classifiers, + platforms = ['Any'], + **extra)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/wibble/client/ServerProxy.py Wed Aug 31 21:35:14 2011 +0100 @@ -0,0 +1,171 @@ +#!/usr/bin/env python +from uuid import uuid4 +from urlparse import urlparse +import json +import httplib +import copy +import socket +import hashlib + +class _Method(object): + def __init__(self, call, name, username=None, password=None): + self.call = call + self.name = name + self._username = username + self._password = password + + def __call__(self, *args, **kwargs): + request = {} + request['id'] = str(uuid4()) + request['method'] = self.name + + if len(kwargs) is not 0: + params = copy.copy(kwargs) + index = 0 + for arg in args: + params[str(index)] = arg + index = index + 1 + elif len(args) is not 0: + params = copy.copy(args) + else: + params = None + request['params'] = params + + if self._username is not None: + request['username'] = self._username + if self._password is not None: + request['password'] = hashlib.md5(self._password).hexdigest() + + resp = self.call(json.dumps(request)) + if resp is not None and resp['error'] is None and resp['id'] == request['id']: + return resp['result'] + else: + raise Exception('This is not supposed to happen -- btc') ######## + + def __getattr__(self, name): + return _Method(self.call, "%s.%s" % (self.name, name), self._username, self._password) + +class _JSONRPCTransport(object): + headers = {'Content-Type':'application/json', + 'Accept':'application/json'} + + def __init__(self, uri, proxy_uri=None, user_agent=None): + self.headers['User-Agent'] = user_agent if user_agent is not None else 'jsonrpclib' + if proxy_uri is not None: + self.connection_url = urlparse(proxy_uri) + self.request_path = uri + else: + self.connection_url = urlparse(uri) + self.request_path = self.connection_url.path + + def request(self, request_body): + if self.connection_url.scheme == 'http': + if self.connection_url.port is None: + port = 80 + else: + port = self.connection_url.port + connection = httplib.HTTPConnection(self.connection_url.hostname+':'+str(port)) + elif self.connection_url.scheme == 'https': + if self.connection_url.port is None: + port = 443 + else: + port = self.connection_url.port + connection = httplib.HTTPSConnection(self.connection_url.hostname+':'+str(port)) + else: + raise Exception('unsupported transport') + connection.request('POST', self.request_path, body=request_body, headers=self.headers) + return connection.getresponse() + +class BadRequestException(Exception): + """HTTP 400 - Bad Request""" + def __init__(self): + Exception.__init__(self,'HTTP 400 - Bad Request') + +class UnauthorisedException(Exception): + """HTTP 401 - Unauthorised""" + def __init__(self): + Exception.__init__(self,'HTTP 401 - Unauthorised') + +class ForbiddenException(Exception): + """HTTP 403 - Forbidden""" + def __init__(self): + Exception.__init__(self,'HTTP 403 - Forbidden') + +class NotFoundException(Exception): + """HTTP 404 - Not Found""" + def __init__(self): + Exception.__init__(self,'HTTP 404 - Not Found') + +class NetworkSocketException(Exception): + def __init__(self): + Exception.__init__(self,'Network socket exception') + +class BadGatewayException(Exception): + """HTTP 502 - Bad Gateway""" + def __init__(self): + Exception.__init__(self,'HTTP 502 - Bad Gateway') + +class ServerProxy(object): + def __init__(self, uri=None, transport=None, proxy_uri=None, user_agent=None, username=None, password=None): + if uri is None and transport is None: + raise Exception('either uri or transport needs to be specified') + + if transport is None: + transport = _JSONRPCTransport(uri, proxy_uri=proxy_uri, user_agent=user_agent) + self.__transport = transport + self._username = username + self._password = password + + def __request(self, request): + # call a method on the remote server + try: + response = self.__transport.request(request) + except socket.error: + raise NetworkSocketException + if response.status == 200: + return json.loads(response.read()) + elif response.status == 400: + raise BadRequestException + elif response.status == 401: + raise UnauthorisedException + elif response.status == 403: + raise ForbiddenException + elif response.status == 404: + raise NotFoundException + elif response.status == 500: + msg = json.loads(response.read()) + raise Exception('JSONRPCError\n%s'%msg['error']['error']) + elif response.status == 502: + raise BadGatewayException + else: + raise Exception('HTTP Status %s'%response.status) + + def __repr__(self): + return ( + "<ServerProxy for %s%s>" % + (self.__host, self.__handler) + ) + + __str__ = __repr__ + + def __getattr__(self, name): + # magic method dispatcher + return _Method(self.__request, name, self._username, self._password) + +if __name__ == '__main__': + ##### btc fixme + jsonrpc_client = ServerProxy2('http://localhost:1337/', username='testuser', password='', user_agent='Py2NotInternetExploiter') + #jsonrpc_client = ServerProxy2('https://www.croston.org/test/index.py', + # username='testuser', + # password='', + # user_agent='Py2NotInternetExploiter') + assert jsonrpc_client.api.mymethod() == jsonrpc_client.mymethod() + try: + print(jsonrpc_client.wibble('this should fail')) + except BadRequestException: + pass # test passed + else: + raise Exception('Test failed (calling unknown method)') + + print 'All tests passed' +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/wibble/client/ServerProxy3.py Wed Aug 31 21:35:14 2011 +0100 @@ -0,0 +1,172 @@ +#!/usr/bin/env python3 +from uuid import uuid4 +from urllib.parse import urlparse +import json +import http.client +import copy +import socket +import hashlib + +class _Method(object): + def __init__(self, call, name, username=None, password=None): + self.call = call + self.name = name + self._username = username + self._password = password + + def __call__(self, *args, **kwargs): + request = {} + request['id'] = str(uuid4()) + request['method'] = self.name + + if len(kwargs) is not 0: + params = copy.copy(kwargs) + index = 0 + for arg in args: + params[str(index)] = arg + index = index + 1 + elif len(args) is not 0: + params = copy.copy(args) + else: + params = None + request['params'] = params + + if self._username is not None: + request['username'] = self._username + if self._password is not None: + request['password'] = hashlib.md5(self._password.encode()).hexdigest() + + resp = self.call(json.dumps(request)) + if resp is not None and resp['error'] is None and resp['id'] == request['id']: + return resp['result'] + else: + raise Exception('This is not supposed to happen -- btc') ######## + + def __getattr__(self, name): + return _Method(self.call, "%s.%s" % (self.name, name), self._username, self._password) + +class _JSONRPCTransport(object): + headers = {'Content-Type':'application/json', + 'Accept':'application/json'} + + def __init__(self, uri, proxy_uri=None, user_agent=None): + self.headers['User-Agent'] = user_agent if user_agent is not None else 'jsonrpclib' + if proxy_uri is not None: + self.connection_url = urlparse(proxy_uri) + self.request_path = uri + else: + self.connection_url = urlparse(uri) + self.request_path = self.connection_url.path + + def request(self, request_body): + if self.connection_url.scheme == 'http': + if self.connection_url.port is None: + port = 80 + else: + port = self.connection_url.port + connection = http.client.HTTPConnection(self.connection_url.hostname+':'+str(port)) + elif self.connection_url.scheme == 'https': + if self.connection_url.port is None: + port = 443 + else: + port = self.connection_url.port + connection = http.client.HTTPSConnection(self.connection_url.hostname+':'+str(port)) + else: + raise Exception('unsupported transport') + + connection.request('POST', self.request_path, body=request_body, headers=self.headers) + return connection.getresponse() + +class BadRequestException(Exception): + """HTTP 400 - Bad Request""" + def __init__(self): + Exception.__init__(self,'HTTP 400 - Bad Request') + +class UnauthorisedException(Exception): + """HTTP 401 - Unauthorised""" + def __init__(self): + Exception.__init__(self,'HTTP 401 - Unauthorised') + +class ForbiddenException(Exception): + """HTTP 403 - Forbidden""" + def __init__(self): + Exception.__init__(self,'HTTP 403 - Forbidden') + +class NotFoundException(Exception): + """HTTP 404 - Not Found""" + def __init__(self): + Exception.__init__(self,'HTTP 404 - Not Found') + +class NetworkSocketException(Exception): + def __init__(self): + Exception.__init__(self,'Network socket exception') + +class BadGatewayException(Exception): + """HTTP 502 - Bad Gateway""" + def __init__(self): + Exception.__init__(self,'HTTP 502 - Bad Gateway') + +class ServerProxy(object): + def __init__(self, uri=None, transport=None, proxy_uri=None, user_agent=None, username=None, password=None): + if uri is None and transport is None: + raise Exception('either uri or transport needs to be specified') + + if transport is None: + transport = _JSONRPCTransport(uri, proxy_uri=proxy_uri, user_agent=user_agent) + self.__transport = transport + self._username = username + self._password = password + + def __request(self, request): + # call a method on the remote server + try: + response = self.__transport.request(request) + except socket.error: + raise NetworkSocketException + if response.status == 200: + return json.loads(response.read().decode()) + elif response.status == 400: + raise BadRequestException + elif response.status == 401: + raise UnauthorisedException + elif response.status == 403: + raise ForbiddenException + elif response.status == 404: + raise NotFoundException + elif response.status == 500: + msg = json.loads(response.read().decode()) + raise Exception('JSONRPCError\n%s'%msg['error']['error']) + elif response.status == 502: + raise BadGatewayException + else: + raise Exception('HTTP Status %s'%response.status) + + def __repr__(self): + return ( + "<ServerProxy for %s%s>" % + (self.__host, self.__handler) + ) + + __str__ = __repr__ + + def __getattr__(self, name): + # magic method dispatcher + return _Method(self.__request, name, self._username, self._password) + +if __name__ == '__main__': + #### btc fixme + jsonrpc_client = ServerProxy('http://localhost:1337/', username='testuser', password='', user_agent='Py3NotInternetExploiter') + #jsonrpc_client = ServerProxy('https://www.croston.org/test/index.py', + # username='testuser', + # password='', + # user_agent='Py3NotInternetExploiter') + assert jsonrpc_client.api.mymethod() == jsonrpc_client.mymethod() + try: + print(jsonrpc_client.wibble('this should fail')) + except BadRequestException: + pass # test passed + else: + raise Exception('Test failed (calling unknown method)') + + print('All tests passed') +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/wibble/client/thing.py Wed Aug 31 21:35:14 2011 +0100 @@ -0,0 +1,3 @@ +#!/usr/bin/python +print 'thing' +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/wibble/server/__init__.py Wed Aug 31 21:35:14 2011 +0100 @@ -0,0 +1,149 @@ +#!/usr/bin/env python + +# Copyright (c) 2011 Ben Croston +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +# of the Software, and to permit persons to whom the Software is furnished to do +# so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from json import loads, dumps +import traceback +import sys +from webob import Request, Response, exc + +class JsonRpcApp(object): + """ + Serve the given object via json-rpc (http://json-rpc.org/) + """ + + def __init__(self, obj, auth=None): + """ + obj - a class containing functions available using jsonrpc + auth - an authentication function (optional) + """ + self.obj = obj + self.auth = auth + + def __call__(self, environ, start_response): + req = Request(environ) + try: + resp = self._process(req) + except ValueError, e: + resp = exc.HTTPBadRequest(str(e)) + except exc.HTTPException, e: + resp = e + return resp(environ, start_response) + + def _process(self, req): + """ + Process the JSONRPC request. + req - a webob Request object + """ + if not req.method == 'POST': + raise exc.HTTPMethodNotAllowed("Only POST allowed").exception + + try: + json = loads(req.body) + except ValueError, e: + raise ValueError('Bad JSON: %s' % e) + + try: + method = json['method'] + params = json['params'] + id = json['id'] + username = json['username'] if 'username' in json else None + password = json['password'] if 'password' in json else None + except KeyError, e: + raise ValueError("JSON body missing parameter: %s" % e) + + if params is None: + params = [] + if not isinstance(params, list): + raise ValueError("Bad params %r: must be a list" % params) + text = traceback.format_exc() + exc_value = sys.exc_info()[1] + error_value = dict( + name='JSONRPCError', + code=100, + message=str(exc_value), + error=text) + return Response( + status=500, + content_type='application/json', + body=dumps(dict(result=None, + error=error_value, + id=id))) + + obj = self.obj + if isinstance(self.obj,tuple) or isinstance(self.obj,list): + for x in self.obj: + if method.startswith('%s.'%x.__class__.__name__): + obj = x + method = method.replace('%s.'%obj.__class__.__name__,'',1) + break + elif method.startswith('%s.'%self.obj.__class__.__name__): + method = method.replace('%s.'%self.obj.__class__.__name__,'',1) + if method.startswith('_'): + raise exc.HTTPForbidden("Bad method name %s: must not start with _" % method).exception + try: + method = getattr(obj, method) + except AttributeError: + raise ValueError("No such method %s" % method) + + if self.auth is not None: + try: + auth_result = self.auth(username, password, req.user_agent) + except: + text = traceback.format_exc() + exc_value = sys.exc_info()[1] + error_value = dict( + name='JSONRPCError', + code=100, + message=str(exc_value), + error=text) + return Response( + status=500, + content_type='application/json', + body=dumps(dict(result=None, + error=error_value, + id=id))) + if not auth_result: + raise exc.HTTPUnauthorized().exception + + try: + result = method(*params) + except: + text = traceback.format_exc() + exc_value = sys.exc_info()[1] + error_value = dict( + name='JSONRPCError', + code=100, + message=str(exc_value), + error=text) + return Response( + status=500, + content_type='application/json', + body=dumps(dict(result=None, + error=error_value, + id=id))) + + return Response( + content_type='application/json', + body=dumps(dict(result=result, + error=None, + id=id))) +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/wibble/tests.py Wed Aug 31 21:35:14 2011 +0100 @@ -0,0 +1,71 @@ +#!/usr/bin/env python +import unittest +import hashlib +from threading import Thread +import time +from wsgiref import simple_server +import platform + +##### server vvv ##### +class api(object): + def mymethod(self): + #raise Exception("This is a test error") + return 'wibbler woz ere' + +def myauth(username, password, useragent=None): + #raise Exception("This is a test error in auth") + return username == 'testuser' and hashlib.md5('s3cr3t').hexdigest() == password and useragent == 'wibble_unittest' + +def make_server(): + from wibble.server import JsonRpcApp + class myhandler(simple_server.WSGIRequestHandler): + def log_request(self, *a, **b): + pass # do not output log messages + application = JsonRpcApp(api(), auth=myauth) + return simple_server.make_server('localhost', 1337, application, handler_class=myhandler) +##### server ^^^ ##### + +##### client vvv ##### +class WibbleTests(unittest.TestCase): + def setUp(self): + from wibble.client.ServerProxy import ServerProxy + self.client = ServerProxy('http://localhost:1337/', + username='testuser', + password='s3cr3t', + user_agent='wibble_unittest') + +class IgnoreModuleNameTest(WibbleTests): + def runTest(self): + self.assertEqual(self.client.api.mymethod(),self.client.mymethod()) + +#def client_tests(): +# try: +# print(jsonrpc_client.wibble('this should fail')) +# except BadRequestException: +# pass # test passed +# else: +# raise Exception('Test failed (calling unknown method)') +# +# print 'All tests passed' + +##### client ^^^ ##### + +def suite(): + if platform.python_version().startswith('3'): + # no tests for python 3 because server not ported yet + return unittest.TestSuite() + def test_wrapper(): + server = make_server() + server.log_request = None + server.serve_forever() + thread = Thread(target=test_wrapper) + thread.start() + time.sleep(0.1) # wait for server thread to start + suite = unittest.TestSuite() + suite.addTest(IgnoreModuleNameTest()) + return suite + +if __name__ == '__main__': +# unittest.main() + main() +