CherryChat - an Orbited Tutorial

Goal

The goal of this tutorial is to introduce you to Orbited and the Python Orbited client API, pyorbited. The tangible goal is to produce a simple web-based chat application. There will be one main “chatroom” where every user who signs on is placed. We won’t deal with authentication or presence (detecting if someone timed out or left.) These are all possible with Orbited but are outside the scope of this tutorial. Below is a screenshot of the end goal.

Prerequisites

Orbited

This tutorial assumes you have already installed Orbited. If you haven’t then check out the tutorial on Installing Orbited.

CherryPy 3.0.1+

CherryPy is a Python http server that allows you to expose Python functions to the web. To install CherryPy simply run:

easy_install CherryPy==3.0.1

PyOrbited

PyOrbited is a library of Python implementations of the Orbited client API. It currently consists of a basic socket-based implementation, a twisted implementation, and a pyevent/libevent implementation. For our purposes we only need the basic implementation, but all three come with PyOrbited. To install the library simply run:

easy_install pyorbited

For more information about PyOrbited see the tutorial Using Python with Orbited.

Server-side Python, step by step

This tutorial will keep the Python source as simple as possible, so the chat functionality will be bare-bones. To begin, we create the directory and five empty files we will need for our chat server, as follows:

The chat.py file will contain the bulk of our code. To begin, we import cherrypy, along with the simple orbited client, which we initialize:

import cherrypy
from pyorbited.simple import Client
orbited = Client()
orbited.connect()

Next, we create a ChatServer class, which contains functions for the operations which will be called by our JavaScript, join and msg. user_keys is a simple helper function which returns a list of users, to which we will send events:

class ChatServer(object):
users = []
def user_keys(self):
    return ['%s, %s, /cherrychat' % (u, s) for u,s in self.users]

@cherrypy.expose
def join(self, user, session='0', ie_nocache=None):
    if (user, session) not in self.users:
        self.users.append((user, session))
        orbited.event(self.user_keys(), '<b>%s joined</b>' % user)

@cherrypy.expose
def msg(self, user, msg,session='0', ie_nocache=None):
    orbited.event(self.user_keys(), '<b>%s</b> %s' % (user, msg))

As you can see, these two functions are preceded by a decorator, @cherrypy.expose, which tells CherryPy to expose them to HTTP GET requests from the JavaScript in our chat page. The join function adds the user if he is not among those already logged in, and then sends an event to all users informing them of the newly-joined user. The msg function simply passes along the message to every connected user.

It is worthwhile to look at the user_keys helper function. The purpose of this function is to return a list of orbit keys that correspond to the users that are currently logged in. Each key is composed of three parts, the user id, the session id, and the location of the request. In our application we aren’t using sessions so every user is given the session id of ‘0’. The user id is simply the username we got from the join command, and the location we are choosing arbitrarily to be /cherrychat. Any valid HTTP location would work though. You’ll see this location again in the JavaScript. Finally, some basic configuration code, taken essentially from the CherryPy StaticContent wiki (see that page for details), finishes off our Python code:

if __name__ == '__main__':
import os
current_dir = os.path.dirname(os.path.abspath(__file__))
# Set up site-wide config first so we get a log if errors occur.
cherrypy.config.update({'environment': 'production',
                        'log.screen': True,
                        'server.socket_port': 4700,
                        'server.thread_pool': 0,
                        'tools.staticdir.root': current_dir})

conf = {'/static': {'tools.staticdir.on': True,
                    'tools.staticdir.dir': 'static'}}
cherrypy.quickstart(ChatServer(), '/', config=conf)

You may have noticed in our CherryPy configuration we set the thread pool to 0. This is to avoid any threading complexities that are out of the scope of this tutorial. Having a single thread introduces and issue with keepalive though, and as such we will disable it in our proxy.

Client-side HTML and JavaScript, step by step

HTML

The HTML for our chat example is very simple. It consists of two form text fields with their associated buttons, and a div called #box, into which the content of the chat will go. To begin the file (chat.html), we need a doctype declaration (to trigger browsers’ standards mode), and a header, which links to a CSS stylesheet and the client-side JavaScript. We make sure to include orbited.js, a static JavaScript file served by Orbited, which takes care of the details of connecting to Orbited via the best transport for any particular browser, as well as defining a few useful helper functions:

<!DOCTYPE html>
<html>
  <head>
    <title>CherryChat</title>
    <link rel="stylesheet" href="chat.css">
    <script src="http://yourdomain:8000/static/orbited.js"></script>
    <script src="chat.js"></script>
  </head>

Next, we include a text field and button for the nickname:

    <body>
    <input type="text" id="nickname">
    <input type="button" value="nickname" onclick="connect();">

Finally, we add a div which can accept the actual chat content, followed by another text field and button for adding to the chat, and the closing tags for the body and html elements:

    <div id="box">
    <input type="text" id="chat">
    <input type="submit" value="chat" onclick="send_msg();">
  </body>

</html>

CSS

The CSS stylesheet for our chat page can style it however we like. In this case, we keep things very simple, setting a few margins, and adding dashed blue borders around each displayed chat event:

body {
  margin-left:2em;
}

#box {
  border: 1px solid black;
  width: 80%;
  margin: .5em auto .5em 0;
  height: 10em;
  overflow: scroll;
}

.event {
  border: 1px dashed blue;
  margin: .5em auto;
  padding: .2em;
  width: 90%;
}

JavaScript

The JavaScript is the meat of our client-side code. To begin, we set the variable ORBITED_PORT to 8000, since this is the port on which we will run orbited. We also need to define the variable ie_nocache. It will be used later in the tutorial. Its only purpose is to keep Internet Explorer from caching requests. Also, we make a create_xhr function which papers over browser differences in creating XMLHttpRequest objects.

ORBITED_PORT = 8000

var ie_nocache = 1;

create_xhr = function() {
  return window.ActiveXObject ?
                new ActiveXObject("Microsoft.XMLHTTP") :
                new XMLHttpRequest();
}

The first bit of real code we need will handle connection. After someone types in their nickname we need to do two things. 1) Initialize the Orbited event stream. To do this, we use the connect function of the Orbited object from orbited.js. This function takes care of picking the best transport for the current browser, and dealing with transport-specific JavaScript. We pass it the chat_event callback function, which will be called with the payload of each event. 2) We need to let the chat server know that we are now connected so we receive any future messages. We do this by making a GET request to the server and we include our nickname.

We’ll break this up into two functions. The connect function will take care of connecting to Orbited, and the join function will send the request to the chat server. We’ll have the connect function call the join function instead of the other way around because we want to first connect the iframe before we alert the server, to make sure we don’t miss any messages.

connect = function() {
  var name = document.getElementById('nickname').value;
  Orbited.connect(chat_event, name, "/cherrychat", "0");
  join(name);
}

join = function(user) {
  var xhr = create_xhr();
  xhr.open("GET", "/join?user=" + user, true);
  xhr.send(null);
}

Next, we want to be able to see any events that are sent. If you recall the server code, we’ll be receiving events in the form of simple JavaScript strings. We just want to stick each string in its own div and put that div in our main chat_box div. The CSS will take care of the formatting.

Remember, we told the Orbited.connect function that chat_event was our callback. So when each event comes in, chat_event will be called with its payload.

chat_event = function(data) {
  var chat_box = document.getElementById('box');
  var div = window.parent.document.createElement('div');
  div.className = "event";
  div.innerHTML = data;
  chat_box.appendChild(div);
  chat_box.scrollTop = chat_box.scrollHeight;
}

The final step is to provide a way to send messages. Sending a message is a matter of contacting the chat server to call the msg function. We need to provide the message text as well as the nickname of the sender. Also, remember the variable ie_nocache? We want to increment it every time we send a message, so that each call to the msg function looks different. If we don’t do this, then some browsers—such as Internet Explorer 6—will not send the message a second time, because they will have cached the result of the first request. We don’t actually care about the responses to these requests; we are only interested their effect: dispatching an orbit event. Having the cached result in the browser is useless to us, if the server never gets the message.

Here is the code we need to send a message:

send_msg = function() {
  ie_nocache += 1;
  var xhr = create_xhr();
  var msg = document.getElementById('chat').value;
  var name = document.getElementById('nickname').value;
  xhr.open("GET", "/msg?ie_nocache=" + ie_nocache + "&user=" + name + "&msg=" + msg, true);
  xhr.send(null);
}

And with that we are finished with the JavaScript side of our chat application. Everything else should be in place at this point, so you can open two terminals to the root cherrychat directory, in one starting Orbited:

orbited

And in the other running chat.py:

python chat.py

Then point your browser at http://localhost:4700/static/chat.html, and then open a second browser window and point it at http://127.0.0.1:4700/static/chat.html, and test the application. It’s important to use the two different hostnames localhost and 127.0.0.1 because some browsers limit the number of open connections you can have to each hostname to two. With this limit it is impossible to run two instances of the chat application on the same host. Of course, 127.0.0.1 and localhost should refer to the same local machine, so its a good way of tricking the browser into thinking its talking to separate servers.

Complete source code

CherryChat SVN repository

Conclusion

We now have a basic implementation of a chat application. The next obvious steps are authentication and timeout. You may also want to connect to existing chat servers. For a more advanced example of a chat application checkout the webirc SVN Repository. Webirc is an web-based IRC client written in JavaScript that uses Orbited.

Attachments