This is the first long-polling app I've ever created and the second Twisted project, so I'd appreciate any feedback that anyone has anything in my code at all, as I may way.
I combined the various examples as I went, and it almost works, but I cannot find a way to return the data to Javascript. I have a Django site running on Twisted, and it seems to be working fine, so I won’t turn on the Django bit unless someone considers it important, and the only thing Django site does is chat. I initially set it up with a regular poll, but I was asked to change it to a lengthy poll, and I'm almost there (hopefully).
Here's the HTML / JS (long.html):
<div class="chat-messages" style="width:300px;height:400px;border:1px solid black;overflow:scroll;" id="messages"> </div><br/> <form action="javascript:sendMessage();" > <input type="text" id="chat_nickname" name="author"/> <input type="text" id="chat_input" name="message" class="chat-new"/> <button class="submit">Submit</button> </form> </body> <script type="text/javascript"> // keep track of the last time data wes received var last_update = 0; // call getData when the document has loaded $(document).ready(function(){ getData(last_update); }); // execute ajax call to chat_server.py var getData = function(last_update){ $.ajax({ type: "GET", url: "http://"+ window.location.hostname + ":8081?last_update=" + last_update + "&callback=?", dataType: 'json', async: true, cache:false, timeout: 300000, success: function(response){ // append the new message to the message list var messages = response.data.messages; console.log(response); for (i in messages){ $('<p><span class="time">[' + messages[i].time +']</span> - <span class="message">' + messages[i].message + '</span></p>').appendTo('#messages'); if (messages[i].time > last_update){ last_update = messages[i].time; } } console.log("Last_update: " + last_update); // Keep div scrolled to bottom $("#messages").scrollTop($("#messages")[0].scrollHeight); // Check again in a second setTimeout('getData(' + last_update + ');', 1000); }, error: function(XMLHttpRequest, textStatus, errorThrown){ // Try again in 10 seconds setTimeout( "getData(" + last_update + ");", 10000); }, failure: function(){ console.log('fail'); }, }); } // Add a contribution to the conversation function sendMessage(){ var nickname = $('#chat_nickname').val(); var message = $('#chat_input').val(); $('#chat_input').val(""); console.log( "nickname: " + nickname + "; message: " + message ); $.ajax({ type: 'POST', url: '/chat/post_message/', data: { nickname: nickname, message:message }, success: function(data, status, xml){ console.log("Success! - " + status); }, error: function(xml, status, error){ console.log(error + " - Error! - " + status); }, complete: function(xml, status){ console.log("Complete! - " + status); } }); } </script>
sendMessage passes the data from the form to Django, and Django places it in the database (and adds time to it). getData points to: 8081, where Twisted listens on the ### Chat Server part of this second bit of code (chat_server.py):
import datetime, json, sys, time, os, types from twisted.web import client, resource, server, wsgi from twisted.python import threadpool from twisted.internet import defer, task, reactor from twisted.application import internet, service from twisted.enterprise import adbapi from django.core.handlers.wsgi import WSGIHandler ## Django environment variables sys.path.append("mydjangosite") os.environ['DJANGO_SETTINGS_MODULE'] = 'mydjangosite.settings' ## Tying Django WSGIHandler into Twisted def wsgi_resource(): pool = threadpool.ThreadPool() pool.start() # Allow Ctrl-C to get you out cleanly: reactor.addSystemEventTrigger('after', 'shutdown', pool.stop) wsgi_resource = wsgi.WSGIResource(reactor, pool, WSGIHandler()) return wsgi_resource ## Twisted Application Framework application = service.Application('twisted-django') class Root(resource.Resource): def __init__(self, wsgi_resource = None): resource.Resource.__init__(self) if wsgi_resource != None: self.wsgi_resource = wsgi_resource def getChild(self, path, request): child_path = request.prepath.pop(0) request.postpath.insert(0, child_path) return self.wsgi_resource def render_GET(self, request): id = request.args.get('id', [""])[0] command = request.args.get('command', [""])[0] self.get_page(request, id) return server.NOT_DONE_YET @defer.inlineCallbacks def get_page(self, request, id): page = yield client.getPage("/chat/latest/%s" % id) request.write(page) request.finish() ## Create and attach the django site to the reactor django_root = Root(wsgi_resource()) django_factory = server.Site(django_root) reactor.listenTCP(8080, django_factory) ### Chat Server class ChatServer(resource.Resource): isLeaf = True def __init__(self): # throttle in seconds self.throttle = 5 # store client requests self.delayed_requests = [] # setup a loop to process collected requests loopingCall = task.LoopingCall(self.processDelayedRequests) loopingCall.start(self.throttle, False) # Initialize resource.Resource.__init__(self) def render(self, request): """Handle a new request""" request.setHeader('Content-Type', 'applicaton/json') args = request.args # set jsonp callback handler name if it exists if 'callback' in args: request.jsonpcallback = args['callback'][0] # set last_update if it exists if 'last_update' in args: request.last_update = args ['last_update'][0] data = self.getData(request) if type(data) is not types.InstanceType and len(data) > 0: # send the requested messages back return self.__format_response(request, 1, data) else: # or put them in the delayed request list and keep the connection going self.delayed_requests.append(request) return server.NOT_DONE_YET def getData(self, request): data = {} dbpool = adbapi.ConnectionPool("sqlite3", database="/home/server/development/twisted_chat/twisted-wsgi-django/mydjangosite/site.db", check_same_thread=False) last_update = request.last_update print "LAST UPDATE: ", last_update new_messages = dbpool.runQuery("SELECT * FROM chat_message WHERE time > %r" % request.last_update ) return new_messages.addCallback(self.gotRows, request ) def gotRows(self, rows, request): if rows: data = {"messages": [{ 'author': row[1], 'message':row[2],'timestamp': row[3] } for row in rows] } print 'MESSAGES: ', data if len(data) > 0: return self.__format_response(request, 1, data) return data def processDelayedRequests(self): for request in self.delayed_requests: data = self.getData(request) if type(data) is not types.InstanceType and len(data) > 0: try: print "REQUEST DATA:", data request.write(self.__format_response(request, 1, data)) request.finish() except: print 'connection lost before complete.' finally: self.delayed_requests.remove(request) def __format_response(self, request, status, data): response = json.dumps({ "status": status, "time": int(time.time()), "data": data }) if hasattr(request, 'jsonpcallback'): return request.jsonpcallback + '(' + response + ')' else: return response chat_server = ChatServer() chat_factory = server.Site(chat_server) reactor.listenTCP(8081, chat_factory)
Here render tries to getData (maybe never?), And when it cannot send a request to self.delayed_requests . getData uses enterprise.adbapi to query Django db, returning a deferred instance. processedDelayedRequests goes through the delayed request queue and, if the request is completed, the data is transferred to gotRows , which then converts it to the format I want and sends it to __format_response , which sends the data back to JS, where it can be considered. That theory anyway is a previous sentence, where I think my problem .
print "LAST UPDATE: ", last_update always prints "LAST_UPDATE: 0", but last_update is updated via JS, so this is not an error.
print 'MESSAGES: ', data prints "{'messages': [{' timestamp ': u'2013-08-10 16: 59: 07.909350', 'message': u'chat message ',' author ': u' test '}, {' timestamp ': u'2013-08-10 17: 11: 56.893340', 'message': u'hello ',' author ': u'pardon'}]} "and so on, as new posts added to db. It receives new data when messages are created, and it seems to work quite well.
print "REQUEST DATA:", data never works at all ... I think this method has been left out of an earlier attempt to get this to work.
I get the correct output from gotRows , but don’t know how to get this output passed to the client. I’m not even sure of my understanding of Deferral, so I think that where my problem is, but I don’t know what I can do to move on from here. Any help would be greatly appreciated.