A quick chat WebSockets/AMQP client

In my previous article I described how to plug WebSockets into AMQP using Tornado and pika. As a follow-up, I’ll show you how this can be used to write the simplest chat client.

First we create a web handler for Tornado that will return a web page containing the Javascript code that will connect and converse with our WebSockets endpoint following the WebSockets API.

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        username = "User%d" % random.randint(0, 100)
        self.write("""<html>
        <head>
          <script type='application/javascript' src='/static/jquery-1.4.2.min.js'> </script>
          <script type='application/javascript'>
            $(document).ready(function() {
              var ws = new WebSocket('ws://localdomain.dom:8888/ws');
              ws.onmessage = function (evt) {
                 $('#chat').val($('#chat').val() + evt.data + '\\n');                  
              };
              $('#chatform').submit(function() {
                 ws.send('%(username)s: ' + $('#message').val());
                 $('#message').val("");
                 return false;
              });
            });
          </script>
        </head>
        <body>
        <form action='/ws' id='chatform' method='post'>
          <textarea id='chat' cols='35' rows='10'></textarea>
          <br />
          <label for='message'>%(username)s: </label><input type='text' id='message' />
          <input type='submit' value='Send' />
          </form>
        </body>
        </html>
        """ % {'username': username})

Every time, the user enters a message and submits it too our WebSockets endpoint which, in return, will forward any messages back to the client. These will be appended to the textarea.

Internally, each client gets notified of any message through AMQP and the bus. Indeed the WebSockets handler are subscribed to a channel that will be notified every time the AMQP server pushes data to the consumer. A side effect of this is that the Javascript code above doesn’t update the textarea when it sends the message the user has entered, but when the server sends it back.

Let’s see how we had to change the Tornado application to support that handler as well as the serving of jQuery as a static resource (you need the jQuery toolkit in the same directory as the Python module).

 
if __name__ == '__main__':
    application = tornado.web.Application([
        (r"/", MainHandler),
        (r"/ws", WebSocket2AMQP),
        ], static_path=".", bus=bus)
 
    http_server = tornado.httpserver.HTTPServer(application)
    http_server.listen(8888)
 
    bus.subscribe("main", poll)
    WS2AMQPPlugin(bus).subscribe()
    bus.start()
    bus.block()

The code is here.

Once the server is running, open two browser windows and access http://localhost:8888/. You should be able to type messages in one and see them appears in both windows.

Note:

This has been tested against the latest Chrome release. You will need to either set the “localdomain.dom” or provide the IP address of your network interface in the Javascript above since Chrome doesn’t allow for localhost nor 127.0.0.1.

6 thoughts on “A quick chat WebSockets/AMQP client

  1. Pingback: Weekly Digest for June 20th | William Stearns

  2. Howie

    With tornado from github, this tanks for me:
    [2010-10-28 15:19:47,579] root – ERROR – Uncaught exception in /ws
    Traceback (most recent call last):
    File “/usr/local/lib/python2.6/dist-packages/tornado/websocket.py”, line 160, in wrapper
    return callback(*args, **kwargs)
    File “chat.py”, line 132, in open
    self.receive_message(self.on_message)
    AttributeError: ‘WebSocket2AMQP’ object has no attribute ‘receive_message’
    [2010-10-28 15:19:47,580] root – ERROR – Exception in I/O handler for fd 9

    tornado.websocket.WebSocketHandler clearly does have receive_message though.

  3. Howie

    Figured it out… for current tornado, this works for me:

    class WebSocket2AMQP(websocket.WebSocketHandler):
    def __init__(self, *args, **kwargs):
    websocket.WebSocketHandler.__init__(self, *args, **kwargs)
    self.settings[‘bus’].subscribe(“amqp2ws”, self.push_message)

    def on_message(self, message):
    self.settings[‘bus’].publish(“ws2amqp”, message)

    def on_connection_close(self):
    self.settings[‘bus’].unsubscribe(“amqp2ws”, self.push_message)

    def push_message(self, message):
    self.write_message(message)

  4. Raj Agarwal

    That code link you posted doesn’t seem to be working anymore. Can you reupload the code files? Thank you.

    1. Sylvain Hellegouarch Post author

      I’m afraid I’ve lost it as well (backup fail). That being said, the code is available on both blog articles but you’ll have to gather it in one module by hand. If I get a chance I’ll do it again and post it on bitbucket.

Comments are closed.