[Part 1] - [Part 2] - [Part 3] - [Addendums] - [Source]
[Table of Contents]
Thus far, we have a very basic UI framework for a non-functional chat system, served by Django. We’ll now implement a chat server, chat client, and api, with the chat functionality being managed and served by Twisted via websockets.
Install Twisted with Websockets
I used this git branch of the Twisted project, as it’s the most up-to-date as of the time of this writing. The specific steps you’ll have to take to install it depend on your architecture, but you’ll probably want to run commands similar to these ones:
#checkout the twisted project git clone https://github.com/twisted/twisted.git twisted-websocket #switch to the most up to date websocket branch cd twisted-websocket git fetch git checkout websocket-4173-4 #install it - preferably do this in a virtualenv python setup.py install
Write a web socket chat server for twisted
Create a file to store the chat server code, say, chatserver.py, and write the following code into it:
from twisted.protocols import basic from twisted.web.websockets import WebSocketsResource, WebSocketsProtocol, lookupProtocolForFactory #basic protocol/api for handling realtime chat class MyChat(basic.LineReceiver): def connectionMade(self): print "Got new client!" self.transport.write('connected ....\n') self.factory.clients.append(self) def connectionLost(self, reason): print "Lost a client!" self.factory.clients.remove(self) def dataReceived(self, data): print "received", repr(data) for c in self.factory.clients: c.message(data) def message(self, message): self.transport.write(message + '\n') from twisted.web.resource import Resource from twisted.web.server import Site from twisted.internet import protocol from twisted.application import service, internet #Create a protocol factory #The factory is usually a singleton, and #all instantiated protocols should have a reference to it, #so we'll use it to store shared state #(the list of currently connected clients) from twisted.internet.protocol import Factory class ChatFactory(Factory): protocol = MyChat clients = [] resource = WebSocketsResource(lookupProtocolForFactory(ChatFactory())) root = Resource() #serve chat protocol on /ws root.putChild("ws",resource) application = service.Application("chatserver") #run a TCP server on port 1025, serving the chat protocol. internet.TCPServer(1025, Site(root)).setServiceParent(application)
The code above follows one of the twisted code samples/tutorials, implementing a basic telnet chat server, and modifies it to use the web socket protocol and related infrastructure, instead of the basic tcpip one. Note that this example is still using a wrapper around the basic.lineReceiver protocol – nothing too fancy.
Since, a few steps from now, we’re going to want to serve more than one protocol (one for web sockets, and the other for http/long poll requests), the code is explicitly creating a Site(), and building a WebSocketResource/ChatFactory to deploy (as opposed to relying on ServerFactory() to build one for us). The websockets api is being served at http://127.0.0.1:8000/ws/
This is the extent of work required to get a very basic twisted server working. Now, try running it:
bash: twistd -n -y chatserver.py
If the Django server is running, also, you should now be able to connect to
http://127.0.0.1:8000/chats/1
see the chat room, and, well, still not be able to properly send messages back and forth, since we don’t yet have a client-side component connecting to our server over websockets, and handling that side of the communication. So let’s do that next:
Download and install a javascript web socket client library
Not all browsers implement websockets, and not all websockets implementations are equivalent, so it’s difficult to write code which is portable. It’s easier to use a library for handling this kind of work – I used this one: https://jquery-graceful-websocket.googlecode.com/files/jquery.gracefulWebSocket.js
Download the file, and place it under chat/static/. (You might have to create the directory):
bash: mkdir chat/static bash: cp ~/Downloads/jquery.gracefulWebSocket.js chat/static/
Implement client-side web socket logic:
We’re already serving the chat-room scaffolding through a template. One way to provide the clientside components for a websockets connection is through javascript embedded directly in the source html file. So let’s do that – edit chat/templates/chats/chat_room.html, and add the following to the header section:
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script> <script type="text/javascript" src="{% static "jquery.gracefulWebSocket.js" %}"></script> <script> $(document).ready( function() { window.chat = {}; //Instantiate a websocket client connected to our server chat.ws = $.gracefulWebSocket("ws://127.0.0.1:1025/ws"); //Basic message send chat.send = function (message) { chat.ws.send(message); } //Basic message receive chat.ws.onmessage = function (event) { var messageFromServer = event.data; var list_element = document.createElement('li'); list_element.innerHTML = messageFromServer; $("#message_list ul").prepend(list_element); }; var inputBox = document.getElementById("inputbox"); inputbox.addEventListener("keydown", function(e) { if (!e) { var e = window.event; } //keyCode 13 is the enter/return button keyCode if (e.keyCode == 13) { // enter/return probably starts a new line by default e.preventDefault(); chat.send(inputbox.value); inputbox.value=""; } }, false); }); </script>
The script first includes jQuery, which the gracefulWebsocket library relies on. The order of inclusion matters, so don’t switch those lines around. It then defines a namespace for our chat functionality (window.chat), and instantiates a gracefulWebsocket in the namespace, configuring it to connect to our web socket chat server:
chat.ws = $.gracefulWebSocket("ws://127.0.0.1:1025/ws");
We need two functions, let’s call them chat.send(), for sending messages out, and chat.onmessage() for handling messages received from the server – the code is fairly readable. We’re also adding an event listener to our input text box, sending out a message each time the user presses return.
Make sure you’ve saved the file, and, voila, you should now have a working, very basic, web-socket based chat server. The static/html content served by Django, and a websockets based chat api being served/managed by twisted.
Django is smart enough to know to pick up changes to its templates, so all of this should now work, without requiring a restart of the server. Just reload the two windows you have opened on http://127.0.0.1/chats/1 (or go back to http://127.0.0.1/chats/, and open two browser windows on the same chat room) – and enjoy sending messages, in real time, from one window to the next.
Next: add a http api for your chat rooms, and a long-polling based client to connect to them.