Thursday, April 25, 2013

JSONRPC Server & Client For Python on Google App Engine

Now that google cloud endpoints is around the corner it will and probably should be a standard way of creating web services for any types of clients for mobile, desktop or even your ajax requests. It's still experimental as the time of this writing and I will not really talk about how to use it since their documentation has some good example on it already.

I will be sharing on how and what I've used to create my own web services for android clients I have created and for ajax calls.

I have created my own jsonrpc client/server class for python. My own full implementation of jsonrpc standards. I have included this on my app-engine-starter code with some sample if you run it and click the JSONRPC Demo dropdown. Feel free to use it. It is still a nice simple library to use creating web services.

I will give a quick sample code here on how it's used:


import logging
from google.appengine.ext import webapp, ndb
import jsonrpc


class Calculator():

    def add(self, a, b):
        return a + b

    def subtract(self, a, b):
        return a - b


# Here is the RPC Handler for your calculator
class CalculatorHandler(webapp.RequestHandler):

    def post(self):
        # just pass the class you want to expose
        server = jsonrpc.Server(Calculator())
        # passing request & response handles all necessary headers
        server.handle(self.request, self.response)


# Here is the RPC Client for your calculator
# Demonstrating an async & synchronous way
# Although you wouldn't really wanna use it on same server
# this is just demo purposes. (Not true for ajax calls which is included on app-starter demo)
class CalculatorClientHandler(webapp.RequestHandler):

    def get(self):
        # this is an async rpc client so you don't need to wait for any calls to finish
        # it's also sampled in a blog post about searching google
        # it uses ndb context again so you can batch it with other ndb async calls
        # remember that if the server supports batching, you should make use of that
        # uses for async fetches are helpful on different domain rpc calls
        calc_async = jsonrpc.ClientAsync('http://localhost:8080/rpc/calculator')
        futures = [calc_async.add(i, 1) for i in range(5)]
        # now we solve another async call without waiting for the others
        calc = jsonrpc.Client('http://localhost:8080/rpc/calculator')
        answer = calc.add(1, 2)
        logging.info('We got answer before requests! %s' % answer)
        # now we wait for all to finish
        ndb.Future.wait_all(futures)
        # Then we respond the answer
        return self.response.write('%s %s'  % (answer, [future.get_result() for future in futures]))


app = webapp.WSGIApplication([('/rpc/calculator', CalculatorHandler),
                              ('/calculator', CalculatorClientHandler)],
                             debug=True)

# to make sure all unhandled async task are finished
app = ndb.toplevel(app)

This is specifically designed for google app engine because of the use of ndb context for asynchronous calls for the client. The server should work normally on any other environment. But it shouldn't be hard to change the client to work with a normal tasklet, it's just simple replace of the library that is used for urlfetch. This is helpful so that if you use a lot of async calls with ndb you are taking advantage of its auto batch feature which will try to group all possible requests as small network hop as possible.

Here is a direct link if you just want the jsonrpc.py

An update base on Rober King's suggestion, it would be more convenient to just create a base ApiHandler so that you can easily just extend it and not pass all session variables and anything you setup on a request scope. Here is a way to do it with current jsonrpc module.
class ApiHandler(webapp.RequestHandler):
    # usually this should really be extending your base handler
    def post(self):
        server = jsonrpc.Server(self)
        server.handle(self.request, self.response)

# Now you directly put all your methods in the handler
class CalculatorHandler(ApiHandler):

    def add(self, a, b):
        return a + b

    def subtract(self, a, b):
        return a - b