This page applies to an older version of Orbited (Version 0.3) For info on the current version see the main documentation.
PylonsChat - 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.
Pylons 0.9.6rc3+
Pylons is a web application framework that helps you build rich applications, using Python. To install Pylons simply run:
easy_install Pylons==0.9.6rc3
Installing Pylons with easy_install will install all of the Pylons dependancies. There are many of them.
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 a new Pylons application, and then change directories into our new application directory, by running on the command line:
paster create --template=pylons pylonschat cd pylonschat
Which creates a massive directory structure, populated with dozens of files. Next, we need to create the controller for our application, by running the controller-creator script:
paster controller chat
Next we want to add three more files to our pylonschat directory: /orbited.cfg, /pylonschat/public/stylesheets/chat.css, and /pylonschat/public/javascripts/chat.js. We can leave all three empty for now. Our pylonschat/ directory should now look something like the following (broken into two columns to save vertical space; pink splotches represent files we care about):
The /pylonschat/controllers/chat.py file will contain the bulk of our code. To begin, we import the simple Orbited client:
from pyorbited.simple import Client
Next, we populate chat controller with functions defining the actions which will be called by our JavaScript, join and msg. We will also create our Orbited client and a list to hold our users. user_keys is a simple helper function defined on the controller which returns the list of users to which we will send events:
import logging from pylonschat.lib.base import * log = logging.getLogger(__name__) class ChatController(BaseController): orbit = Client() users = [] def _user_keys(self): list = ["%s, %s, /orbited" % (user[0], str(user[1])) for user in self.users] return list def index(self): return render('chat/index.html') def join(self, user): if (user, '0') not in self.users: self.users.append((user, '0')) self.orbit.event(self._user_keys(), '<b>%s joined</b>' % user) return "ok." def msg(self, user, msg, ie_nocache=None, session='0'): self.orbit.event(self._user_keys(), '<b>%s</b> %s' % (user, msg)) return "ok."
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 Orbited 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 /pylonschat. Any valid HTTP location would work though. You’ll see this location again in the JavaScript. Finally, we add some routes to our /pylonschat/config/routing.py file to map urls to the actions in the ChatController. The first two routes are the ones that we have added and the third is the standard route that will be in the generated file. It is important that the new routes go before the existing route.
map.connect('chat/join/:user/', controller='chat', action='join') map.connect('chat/msg/:user/:msg/:ie_nocache', controller='chat', action='msg') map.connect(':controller/:action/:id')
We should also take a quick look at orbit.cfg. There is very little in this file as the default configuration is mostly fine.
[global] proxy.enabled = 1 proxy.keepalive = 0 [proxy] /orbited -> ORBITED / -> http://127.0.0.1:5000
To avoid cross-domain scripting problems we will use the Orbited proxy to redirect all requests to our chat application. To JavaScript the Orbited daemon and the PylonsChat? application server will seem to be one and the same.
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, a div called #box, into which the content of the chat will go, and a hidden iframe to accept events from the application.
To begin the file (/pylonschat/template/chat/index.html), we need a doctype declaration, and a header, which links to a CSS stylesheet and the client-side JavaScript:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>PylonsChat</title>
<link rel="stylesheet" href="/stylesheets/chat.css">
<script src="javascripts/chat.js" charset="utf-8"></script>
<script src="/_/orbited.js" charset="utf-8"></script>
</head>
Next, we include a text field and button for the nickname:
<body>
<input type="text" id="nickname">
<input type="button" value="nickname" name="nickname" onclick="connect();"></code></pre>
And then a div which can accept the actual chat content, followed by another text field and button for adding to the chat:
<div id="box"></div>
<input type="text" id="chat">
<input type="submit" value="chat" onClick="send_msg();"></pre></code>
</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 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.
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, "/orbited", "0"); join(name); } join = function(user) { var xhr = create_xhr(); xhr.open("GET", "/chat/join/" + 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", "chat/msg/" + nickname + "/" + msg + "/" + ie_nocache); 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 pylonschat directory, in one starting Orbited:
orbited
And in the other running the PylonsChat application server:
paster serve development.ini
Then point your browser at http://localhost:8000/chat, and then open a second browser window and point it at http://127.0.0.1:8000/chat, 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
Conclusion
We now have a basic implementation of a chat application. The next obvious steps are authentication and timeout. For a more advanced example of a chat application checkout the webirc SVN Repository. Webirc is an web-based IRC client written in Python with Twisted and Orbited. It deals with presence by sending regular pings to the browser. Another issue that we glossed over is that of cross-domain scripting. If you recall our orbited.cfg file for this tutorial, we enabled the Orbited proxy to dispatch requests to the chat server. The effect of this is that the browser only knows about the Orbited server and thinks that all of its communication with the chat server is actually coming from the Orbited server. This is important because browsers do not allow cross-domain scripting. This means that if we received events inside of an iframe that was served from localhost:8000, then we can’t make calls from the iframe to a parent window server from localhost:4700 because it counts these as different domains. The proxy is an easy way to ignore the whole issue.
Of course, on a production site it doesn’t make sense to use the Orbited proxy for a variety of reasons. There will likely be a mismatch between the number of Orbited nodes you have and the number of web nodes you have, so there is no sensible proxy mapping. More importantly though, the proxy adds latency to each request and uses additional processor. The proxy should therefore be treated as a development and testing tool only. Orbited comes with built-in solutions for handling cross-domain scripting. One of those solutions is the iframe_domain transport. It is nearly identical to the iframe transport except that it dispatches an initial <script>document.domain="hostname"</script> to the browser right after its connected. “hostname” will be replaced with the top level hostname from the Host header of the request, with the port also stripped. So if the Host header is some.sub.domain.com:8000, then the initial script sent will be <script>document.domain="domain.com"</script>.
This problem is addressed in the JavaScript source of the webirc client as well, and you should take a look before you try to deploy your Orbited applications in a production environment.
Attachments
- pylonschat.png (50.9 kB) - added by adrian 3 months ago.
- pylonschat-directory.png (52.2 kB) - added by adrian 3 months ago.

