Skip to content

Instantly share code, notes, and snippets.

@teserak
Created August 25, 2012 09:50
Show Gist options
  • Save teserak/3463087 to your computer and use it in GitHub Desktop.
Save teserak/3463087 to your computer and use it in GitHub Desktop.
Tornado, MongoDB, WebSockets -chat
import logging
import tornado.escape
import tornado.ioloop
import tornado.options
import tornado.web
import tornado.websocket
import os.path
import asyncmongo
import time
import functools
from tornado.options import define, options
define('port', default=81, help='run on the given port', type=int)
class Application(tornado.web.Application):
def __init__(self):
self.db = asyncmongo.Client(pool_id='mydb', host='127.0.0.1', port=27017, dbname='test')
handlers = [
(r'/', MainHandler),
(r'/add_message', AddHandler),
(r'/chatsocket', ChatSocketHandler)
]
settings = dict(
cookie_secret='43oETzKXQAGaYdk6fd8fG1kJFuYh7EQnp2XdTP1o/Vo=',
template_path=os.path.join(os.path.dirname(__file__), 'templates'),
static_path=os.path.join(os.path.dirname(__file__), 'static'),
xsrf_cookies=True,
autoescape=None
)
tornado.web.Application.__init__(self, handlers, **settings)
class MainHandler(tornado.web.RequestHandler):
@property
def db(self):
return self.application.db
@tornado.web.asynchronous
def get(self):
self.db.messages.find({}, limit=50, sort=[('time', -1)], callback=self.on_response)
def on_response(self, response, error):
if error:
raise tornado.web.HTTPError(500)
self.render('index.html', messages=response[::-1])
class AddHandler(tornado.web.RequestHandler):
def post(self):
logging.info('Post message')
self.write('Your browser don\'t support JavaScript or WebSockets or Flash.');
class ChatSocketHandler(tornado.websocket.WebSocketHandler):
waiters = set()
@property
def db(self):
return self.application.db
def open(self):
ChatSocketHandler.waiters.add(self)
logging.info('Add waiter')
def on_close(self):
ChatSocketHandler.waiters.remove(self)
logging.info('Remove waiter')
@classmethod
def send_updates(cls, result, error, chat):
if error:
raise tornado.web.HTTPError(500)
logging.info('Sending message to %d waiters', len(cls.waiters))
for waiter in cls.waiters:
try:
waiter.write_message(chat)
except:
logging.error('Error sending message', exc_info=True)
def on_message(self, message):
logging.info('Got message %r', message)
parsed = tornado.escape.json_decode(message)
chat = {
'body': parsed['body'],
'time': time.time()
}
chat['html'] = self.render_string('message.html', message=chat)
callback = functools.partial(ChatSocketHandler.send_updates, chat=chat)
self.db.messages.insert(chat, callback=callback)
def main():
tornado.options.parse_command_line()
app = Application()
app.listen(options.port)
tornado.ioloop.IOLoop.instance().start()
if __name__ == '__main__':
main()
WEB_SOCKET_SWF_LOCATION = "/static/WebSocketMain.swf";
WEB_SOCKET_DEBUG = true;
$(document).ready(function() {
if (!window.console) window.console = {};
if (!window.console.log) window.console.log = function() {};
$("#messageform").live("submit", function() {
newMessage($(this));
return false;
});
$("#messageform").live("keypress", function(e) {
if (e.keyCode == 13) {
newMessage($(this));
return false;
}
});
$("#message").select();
updater.start();
});
function newMessage(form) {
var message = form.formToDict();
updater.socket.send(JSON.stringify(message));
form.find("input[type=text]").val("").select();
}
jQuery.fn.formToDict = function() {
var fields = this.serializeArray();
var json = {}
for (var i = 0; i < fields.length; i++) {
json[fields[i].name] = fields[i].value;
}
if (json.next) delete json.next;
return json;
};
var updater = {
socket: null,
start: function() {
updater.socket = new WebSocket("ws://"+document.location.host+"/chatsocket");
updater.socket.onmessage = function(event) {
updater.showMessage(JSON.parse(event.data));
}
updater.socket.onclose = function () {
document.location.reload();
}
},
showMessage: function(message) {
var existing = $("#m" + message.id);
if (existing.length > 0) return;
var node = $(message.html);
node.hide();
$("#inbox").append(node);
node.slideDown();
}
};
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<title>Chat Demo</title>
<link rel="stylesheet" href="{{ static_url('chat.css') }}" type="text/css"/>
<script src="{{ static_url('swfobject.js') }}" type="text/javascript"></script>
<script src="{{ static_url('web_socket.js') }}" type="text/javascript"></script>
</head>
<body>
<div id="body">
<div id="inbox">
{% for message in messages %}
{% include "message.html" %}
{% end %}
</div>
<div id="input">
<form action="/add_message" method="post" id="messageform">
<table>
<tr>
<td><input name="body" id="message" style="width:500px"/></td>
<td style="padding-left:5px">
<input type="submit" value="{{ _('Post') }}"/>
<input type="hidden" name="next" value="{{ request.path }}"/>
{{ xsrf_form_html() }}
</td>
</tr>
</table>
</form>
</div>
</div>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3/jquery.min.js" type="text/javascript"></script>
<script src="{{ static_url('chat.js') }}" type="text/javascript"></script>
</body>
</html>
{% import tornado.escape %}
{% import time %}
<div class="message">
<small>{{ time.strftime('%H:%M:%S - %d.%m.%Y', time.localtime(message['time'])) }}</small>
{{ tornado.escape.linkify(message['body']) }}
</div>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment