[[BR]] {{{ #!html
Warning!

This page applies to an older version of Orbited (Version 0.5) For info on the current version see the main documentation.

}}} [[BR]] = !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. [[Image(cherrychat.png)]] == Prerequisites == === Orbited === This tutorial assumes you have already installed Orbited. If you haven’t then check out the tutorial on [wiki:0.5/Installation 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 === [wiki:0.3/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 [wiki:0.3/UsingPython 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: [[Image(directory-structure.png)]] 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: {{{ #!python 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: {{{ #!python 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(), '%s joined' % user) @cherrypy.expose def msg(self, user, msg,session='0', ie_nocache=None): orbited.event(self.user_keys(), '%s %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 [http://www.cherrypy.org/wiki/StaticContent CherryPy StaticContent wiki] (see that page for details), finishes off our Python code: {{{ #!python 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: {{{ CherryChat }}} Next, we include a text field and button for the nickname: {{{ }}} 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: {{{
}}} === 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. {{{ #!js 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. {{{ #!js 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. {{{ #!js 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: {{{ #!js 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 == [/svn/orbited/branches/0.5/demos/legacy/cherrychat 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 [/svn/orbited/branches/0.5/daemon/orbited/static/demos/chat webirc SVN Repository]. Webirc is an web-based IRC client written in !JavaScript that uses Orbited.