Sunday, June 12, 2011

Using Google Appengine Channel API with a Python Client

For a project, I  needed a way to perform long-poll requests with GAE. Just using a normal connection doesn't work because of the 30-Seconds request limit GAE enforces. But starting with Version 1.4 Google released the Google Appengine Channel API which allows long-pull requests with a javascript client library.
It's quite easy to use. create a channel:

from google.appengine.api import channel
token = channel.create_channel(chan_name)


And start sending messages:

channel.send_message(chan_name, msg)


Unfortunately, there is neighter any non-javascript client library for use in desktop applications, nor is there any specifications of the protocol available. So I reverse-engineered the javascript-client and created a python client implementation. Feel free to use it. But be aware that although it seems to work quite well,  it's not well tested yet. I've not yet used it on a real-world application. Be also aware that google could change the underlying protocol without any notice, which would obliviously break this library.

Use it like this:
import gae_channel

channel = gae_channel.Client(token='your channel token')
for msg in chan.messages():
    print msg 
 
Also have a look at /demo. There's a small demo client available.

$ python receiver.py 
Your channel name is: fWVwdXvNJP
now run in an other terminal:
python sender.py fWVwdXvNJP
I'm now listening for messages, dont close this terminal...

Now in another terminal, run:
$ python sender.py fWVwdXvNJP
Enter a message:test

Now you'll see the message appearing in the first terminal.

Download from bitbucket.

9 comments:

  1. Looks great and seems to be working nicely.

    Any idea or sense how stable this protocol is and if Google is likely to change/break it?

    I'll probably need to port this to C since I'm writing a C client. Any idea if such one already exists?

    ReplyDelete
  2. I don't know how stable it is. I think only someone at google could answer that. It's not officially so they could change it at will. Though I hope they will not...

    I don't know of any other non-js implementation out there.

    ReplyDelete
  3. I am trying to add hooks to the demo/server/main.py to the channel_presence (connected + disconnected) calls however it seems like when interrupting receiver.py using CTRL+C, no "disconnect" notice is sent. Connection notification is sent just fine.
    Any idea if the "disconnect" notification is sent explicitly by the client? I would think it should be detected by Google internal service

    ReplyDelete
  4. My bad... it seems like the /disconnect is being sent just fine....

    ReplyDelete
  5. I've indeed observed an explicit disconnect POST sent by the js-client (if the browser window is closed). I'd expect there to be also some implicit handling for disconnects in case of network failures. But I didn't have a closer look on that yet.

    ReplyDelete
  6. Ah so the /disconnect you observed must come from some implicit handling. It nevertheless might be usefull to add support for explicit disconnects.

    ReplyDelete
  7. I am trying to write a client for lua. but i keep getting the following over and over:

    "<{parent.m("[[139,[\42noop\42]\n]\n]\n")} catch(e) {}"

    Did you see this in your trials while attempting to get it working?

    ReplyDelete
    Replies
    1. Hi, Jake! Did you manage to write that client for Lua? I'm faced with the same problem here. Is your implementation open-source? :) Thank you very much.

      Delete
  8. it is showing

    C:\Users\Paxcel\Downloads\lohre-gae_channel-8bd89615ac83\lohre-gae_channel-8bd89
    615ac83\demo>python receiver.py
    Your channel name is: lTHomKAcAs
    now run in an other terminal:
    python sender.py lTHomKAcAs
    I'm now listening for messages, dont close this terminal...
    Fetching sid
    Traceback (most recent call last):
    File "receiver.py", line 36, in
    main()
    File "receiver.py", line 16, in main
    listen(token)
    File "receiver.py", line 31, in listen
    for msg in chan.messages():
    File "../gae_channel\__init__.py", line 135, in messages
    self._fetch_sid()
    File "../gae_channel\__init__.py", line 264, in _fetch_sid
    conn = urllib2.urlopen(url, data=urllib.urlencode(post_data))
    File "C:\Python27\lib\urllib2.py", line 127, in urlopen
    return _opener.open(url, data, timeout)
    File "C:\Python27\lib\urllib2.py", line 410, in open
    response = meth(req, response)
    File "C:\Python27\lib\urllib2.py", line 523, in http_response
    'http', request, response, code, msg, hdrs)
    File "C:\Python27\lib\urllib2.py", line 448, in error
    return self._call_chain(*args)
    File "C:\Python27\lib\urllib2.py", line 382, in _call_chain
    result = func(*args)
    File "C:\Python27\lib\urllib2.py", line 531, in http_error_default
    raise HTTPError(req.get_full_url(), code, msg, hdrs, fp)
    urllib2.HTTPError: HTTP Error 401: Unauthorized

    ReplyDelete