Comet Chat Server

Here is a demo I wrote that demonstrates how to use the Comet method of http streaming. Of course this was before it was named Comet.

from cgi import escape
from random import uniform
from Queue import Queue,Empty
from sets import Set
from socket import error
from threading import Thread
from urllib import unquote_plus
from wsgiref.simple_server import make_server

class Connection(Queue):
    """Handles the persistant connection between the client and server"""
    #This set could get messed up by multi-threading.
    objects=Set() #set of live connections

    def __init__(self,obj_up_hook=None):
        self.name=""
        self.obj_up_hook=obj_up_hook
        Queue.__init__(self)

    def __str__(self):
        return self.name

    def __repr__(self):
        return "Connection object: " + str(self)

    def online(self):
        self.objects.add(self)
        print '"' + str(self) + '" has joined'
        if self.obj_up_hook:
            self.obj_up_hook(self)

    def offline(self):
        self.objects.discard(self)
        print '"' + str(self) + '" has left'
        if self.obj_up_hook:
            self.obj_up_hook(self)

    def send_to_all(msg):
        """Sends a message to all online objects"""
        for x in Connection.objects:
            x.put(msg)
            
    send_to_all = staticmethod(send_to_all)

    def run(self,write,keep_alive=" "):
        """Waits for messages and outputs them until window is closed"""
        self.online()
        while 1:
            try:
                #Wait for a new message.
                m=self.get(True,uniform(10,15))
            except Empty:
                #The waiting timed out.
                m=keep_alive
            try:
                write(m)
            except error:
                #most likely the client closed the window
                self.offline()
                return

class ChatApp():
    """Handles a Request"""
    def __init__(self, environ, start_response):
        self.environ=environ
        self.start_response=start_response

    def index(self):
        """login page"""
        return """<script>
function submit(e){
 if(!e)e=window.event;
 if(e.keyCode==13){
  url="/main/"+input.value;
  location.href=url
 }
}
</script>
<body>
<table width=100% height=60%>
<td width=100% height=100%><center>Enter your name:<br><input id=input style="width:50%" onkeypress="submit(event)">"""

    def main(self,user):
        """main page"""
        return '''<script>
function submit(e){
 if(!e)e=window.event;
 if(e.keyCode==13){
  url="/ajax/'''+str(user)+'''?"+input.value;
  input.value="";
  if(window.ActiveXObject){ajax=new ActiveXObject("Microsoft.XMLHTTP")};
  if(window.XMLHttpRequest){ajax=new XMLHttpRequest()};
  ajax.open("GET",url,true);
  ajax.send(null);
 }
}
</script>
<body topmargin=0 bottommargin=0 leftmargin=0 rightmargin=0>
<table width=100% height=100% cellspacing=0 cellpadding=0>
<td width=80% height=100%>
<iframe id=thebox style="border-right:0;border-left:0;border-top:0;border-bottom:0" width=100% height=100% src="/top/'''+str(user)+'''"></iframe>
<td width=20% height=100%>
<iframe name=thelist style="border-right:0;border-left:0;border-top:0;border-bottom:0" width=100% height=100% src="/list/'''+str(user)+'''"></iframe>
<tr><td><input id=input style="width:100%" value="Type your message here" onkeypress="submit(event)">'''

    def refresh_online_list(self,connection):
        Connection.send_to_all('<script>u()</script>')
        
    def top(self,write,usern):
        """actual chat window"""
        #create another thread to serve new requests
        Server()

        write("""<body><script>
function u(){parent.frames["thelist"].location.reload();}
function s(str){document.write(str);window.scrollBy(0,100);}
</script>""")
        u=Connection(self.refresh_online_list)
        u.name=usern
        u.run(write)

    def onlinelist(self,user):
        """online list"""
        string =  "<b>" + str(len(Connection.objects)) + " Online:</b><br>"
        for u in Connection.objects:
            string += str(u)+"<br>n"
        return string

    def ajax(self,user,message):
        """page that accepts messages"""
        #this escape function escapes all html and quotes
        print "recieved message: " + message
        print "sending message: " + self.esc(message)
        Connection.send_to_all("<script>s('" + "<b>" + self.esc(str(user)) + 
                ":</b> " + self.esc(message) + "<br>" + "')</script>")
        print "done"

    def esc(string):
        #for html:
        string = escape(string,True)
        #for javascript (order is important)
        string = string.replace("\","\\")
        string = string.replace("'","\'")
        return string

    esc = staticmethod(esc)

    def __iter__(self):
        print "recieved request"
        write = self.start_response('200 OK', [('Content-type', 'text/html')])
        patharray = self.environ['PATH_INFO'].split('/')
        if patharray==["",""]:
            yield self.index()
            return
        if patharray[1]=="favicon.ico":
            return
        command=patharray[1]
        user=patharray[2]
        if command=="main":
            yield self.main(user)
        elif command=="top":
            self.top(write,user)
            yield ""
        elif command=="list":
            yield self.onlinelist(user)
        elif command=="ajax":
            self.ajax(user,unquote_plus(self.environ['QUERY_STRING']))
            yield ""
        else:
            yield "unknown command: "+str(command)


class Server(Thread):
    """A thread that serves requests"""
    def __init__(self):
        Thread.__init__(self)
        self.setDaemon(1)
        self.start()
    def run(self):
        self.httpd.serve_forever()

def start():
    httpd = make_server('0.0.0.0', 9081, ChatApp)
    Server.httpd=httpd
    print "Serving HTTP on port 9081..."
    s=Server()
    s.join()#don't exit

if __name__ == '__main__':
    start()
Advertisements