Commits

Sven Hendriks committed 586b6b0

Initial commit

Comments (0)

Files changed (7)

jrpc/__init__.py

Empty file added.
+import httplib2
+import simplejson
+
+# Borrowed from xmlrpclib
+class RemoteMethod(object):
+	def __init__(self, send, name):
+		self.__send = send
+		self.__name = name
+
+	def __call__(self, *args, **kwargs):
+		nargs = [self.__name]
+		nargs.extend(args)
+		return self.__send(*nargs, **kwargs)
+	
+class ServerError(Exception): pass
+
+class JRPCClient(object):
+	def __init__(self, host, port):
+		self.host = host
+		self.port = port
+
+	def __getattr__(self, attr):
+		return RemoteMethod(self._send, attr)
+
+	def _send(self, *args, **kwargs):
+		path = '/'.join([str(arg) for arg in args])
+
+		url = 'http://%s:%d/%s' % (self.host, self.port, path)
+
+		h = httplib2.Http()
+		resp, content = h.request(url)
+
+		status = resp.get('status')
+
+		if status in ('400', '404', '500'):
+			raise ServerError(content)
+
+		return simplejson.loads(content)
+

jrpc/examples/example_client.py

+from client import JRPCClient
+
+j = JRPCClient('localhost', 9000)
+j.sum(1,2)
+

jrpc/examples/example_server.py

+class ExampleHandler():
+	def sum(self, a, b, c=0):
+		a = int(a)
+		b = int(b)
+		c = int(c)
+		return a+b+c
+
+import sys
+import traceback
+import StringIO
+
+import cherrypy
+import simplejson as json
+
+class MethodNotFound(Exception): pass
+class NoMethodError(Exception): pass
+class MethodException(Exception): pass
+
+def _handle_error():
+	type, value, tb = sys.exc_info()
+
+	type_str = type.__name__
+	tb_str = StringIO.StringIO()
+	traceback.print_tb(tb, tb_str)
+	tb_str = tb_str.getvalue()
+
+	if isinstance(type, MethodNotFound):
+		cherrypy.response.status = 404
+		cherrypy.response.body = ["Remote Exception:\n%s: %s" % (type_str, value)]
+	elif isinstance(type, NoMethodError):
+		cherrypy.response.status = 400
+		cherrypy.response.body = ["Remote Exception:\n%s: %s" % (type_str, value)]
+	elif isinstance(type, MethodException):
+		cherrypy.response.status = 400
+		cherrypy.response.body = ["Remote Exception:\n%s: %s\nTraceback:\n%s" % (type_str, value, tb_str)]
+	else:
+		cherrypy.response.status = 500
+		cherrypy.response.body = ["Remote Exception:\n%s: %s\nTraceback:\n%s" % (type_str, value, tb_str)]
+
+class JRPCServer(object):
+	def __init__(self, handler):
+		self._handler = handler
+
+	@cherrypy.expose
+	def default(self, *args, **kwargs):
+		# check if we have a method name
+		if len(args) == 0:
+			raise NoMethodError
+
+		# extract the medthod name
+		methodname = args[0]
+
+		# try to get the method from the handler
+		try:
+			method = getattr(self._handler, methodname)
+		except AttributeError, msg:
+			raise MethodNotFound
+
+		# call method on handler
+		result = method(*args[1:], **kwargs)
+
+		# return result as JSON
+		cherrypy.response.headers['Content-Type'] = 'application/json'
+		return json.dumps(result)
+
+# entry point
+def serve(http_port, handler):
+	# Start HTTP server
+	cherrypy.config.update({'server.socket_host': '0.0.0.0',
+        	                'server.socket_port': http_port,
+				'request.error_response': _handle_error,
+	                       })
+	cherrypy.quickstart(JRPCServer(handler()))
+

jrpc/tests/test_client_server.py

+from multiprocessing import Process
+import time
+
+from jrpc.client import JRPCClient, ServerError
+from jrpc.server import serve
+
+# the class whose methods we're going to expose
+class Handler(object):
+	def sum(self, a, b):
+		a = int(a)
+		b = int(b)
+		return a + b
+
+def test_client_server():
+	# start server in separate process
+	serverproc = Process(target=serve, args=(9000, Handler))
+	serverproc.start()
+
+	# Give the server time to start up
+	time.sleep(1)
+
+	# create cleint and call method
+	c = JRPCClient('localhost', 9000)
+	sum = c.sum(1, 2)
+
+	assert sum == 3, 'Expected 1 + 2 = 3, but got %s' % sum
+
+	try:
+		c.sum(1, 2, 3)
+		raise Assertion("Expected ServerError")
+	except ServerError:
+		pass
+
+	try:
+		c.somefunctionthatdoesnotexist()
+		raise Assertion("Expected ServerError")
+	except ServerError:
+		pass
+
+	# terminate server process
+	serverproc.terminate()
+
+from setuptools import setup, find_packages
+
+setup(
+	name="jrpc",
+	version='dev',
+	author="Sven Hendriks",
+	maintainer="Sven Hendriks",
+	maintainer_email="hendriks.sven@googlemail.com",
+	packages=find_packages(),
+	install_requires=[
+		'simplejson',
+		'cherrypy',
+	],
+)
+