(It’s been a very long time since this RSS feed has had an update. Um. Suprise!)

At work, as a Lunch Time Project™, myself, Ed and David have designed an ultra-ultra-lightweight protocol for exposing a few methods returning simple structures, with the aim of creating rapid AJAX prototypes for internal and “fun” projects.

(In the spirit of internal and fun project, Pembo developed a system for tracking coffee output, Neil built a system for managing our lunch orders, and a starting goal for this new system is tracking our office currency; owed cups of tea.)

The basic premise is that there exists a central registry of thing that can be called, indexed by name and version. The registry knows about the URLs that these services live at, but keeps them a secret so that all calls pass through the proxy.

The proxy receives a request at the wellknown URI “/discovery/1/discover” and receives a response like

[['discovery', 1], ['trac', 1], ['time', 1], ['auth', 1]]

representing each of the services available, and their maximum version available.

Requests to http://[proxy]/[service]/[version]/[method] get passed verbatim through, meaning you could implement the proxy as a mod_rewrite / mod_proxy pair in Apache. (At least, at the moment, if we don’t build in more smarts).

The basic architecture is:

Basic Architecture

Building a service is truly a doddle. Each service must provide a “listMethods” method which returns a JSON list of all of the methods available. You could, in PHP, just place some files in a directory, turn on Apache MultiViews, and be at it. That’s what our test service does.

/time/1/listMethods.js

['listMethods', 'getTime']

/time/1/getTime.php

<?php
header("Content-type: text/javascript");
echo time();
?>

Now that we have a service (we have two actually, because the registry is, itself, defined as a service, to be as meta as possible) we should build a client. Obviously, you can just go to http://[proxy]/time/1/getTime and get the result, which is nice.

We don’t limit the Content-Type returned, so if you’re consuming things that aren’t JSON (such as XML, iCal or PDF) you just return an appropriate type, and the code should avoid trying to parse it as Javascript.

Anyway, my first draft at a code-generating Python script is made up of a template (which has some global variables added) and a script to do a bare minimum of introspection:

template.py

import json
import urllib

class Call:
def __init__(self, method):
    self.method = method
def __call__(self, **kwargs):
    u = urllib.urlopen("%s/%s?%s" % (base_url, self.method, urllib.urlencode(kwargs)))
    body = u.read()
    return json.read(body)

class O:
    pass

o = O()

for method in methods:
    setattr(o, method, Call(method))

create_py.py

import urllib
import json
import sys

base = "http://[proxy address]/"
template = "template.py"

def main(name):
    u = urllib.urlopen("%s/discovery/1/discover" % base)
    body = u.read().strip()
    j = eval(body)
    f = open("x_%s.py" % name, 'w')
    for item in j:
        if item['name'] == name:
            version = item['version']
            service_url = '%s/%s/%s' % (base, name, version)
            lm_contents = eval(urllib.urlopen('%s/listMethods' % service_url).read())
            print >>f, "base_url = %s" % repr(service_url)
            print >>f, "methods = %s" % repr(lm_contents)
            print >>f, open(template).read()
    f.close()


if __name__ == '__main__':
    main(*sys.argv[1:])

This is ugly, but it does work. Yes, it’s got hard-coded URLs, and it assumes Javascript and contains basically no error-checking, but this is a Lunch Time Project, and as such, certain time constraints are in play. You could almost say that the quick-and-dirty approach is half the point.

I would like to move the Call object out to a module that just gets imported, and then add the method to the module namespace itself, but I can’t figure out how to do that (the methods in the namespace; I know how to move the code).

At the moment to use it I must do this:

>>> from x_time import o as time
>>> dir(time)
['__doc__', '__module__', 'getTime', 'listMethods']
>>> time.listMethods()
['listMethods', 'getAllTickets']

That import hackery, well, it rubs me up wrong.