coder1.com

  • home
  • drupal articles
  • contact
Home

Twisted, Long Polling, & JSONP

Mike Milano — December 30, 2011 - 10:06pm

I recently decided to use Twisted as the framework for a new project. In short, this app listens to and parses incoming events from multiple servers, as well as issues commands back to them.

Once I had Python doing its job managing the data, the next step was to expose the data in memory from the Twisted app to an existing PHP/Drupal UI. Twisted makes it pretty simple to attach an HTTP server to your app, so I did just that.

Why JSONP?

Because Apache & the PHP app was running on port 80 and my Twisted service was running on 8000, standard AJAX requests were not going to work because of XSS restrictions. My only options were either write a PHP wrapper, or use JSONP. JQuery supports JSONP very elegantly, so I chose to move forward with that option.

Why Long Polling

Standard polling from an AJAX app will typically send a request at a given interval. Even when there's no data to return from the server, the server sends an empty response and the cycle starts all over.

Long polling cuts down on the overhead a bit by keeping the request open until there is actually data to send back. Again Twisted comes through in making it easy to serve data like this.

Example

I took the relevant pieces of code to make a very generic example to provide a starting point for anyone looking to implement something similar.

Note: You will need to modify long_poll.js to point to the destination of the twisted server. Currently it is example.com:8000

index.html (HTML file served from Apache)

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  3. "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
  4. <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  5. <head>
  6. <title>Twisted - Long Polling & JSONP</title>
  7. <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
  8. <script type="text/javascript" src="long_poll.js"></script>
  9. </head>
  10. <body>
  11. <h1>Twisted - Long Polling & JSONP</h1>
  12. <p>New message will appear below as they are received from the server.</p>
  13. <hr />
  14. <ul id="messages"></ul>
  15. </body>
  16. </html>

long_poll.js (JavaScript file served from Apache)

  1. // variable to keep track of the last time we received data
  2. // a time value will be sent back with the response in a unix timestamp format
  3. var lastupdate = 0;
  4.  
  5. // call getData when the document has loaded
  6. $(document).ready(function(){
  7. getData(lastupdate);
  8. });
  9.  
  10. // execute ajax call to server.py
  11. var getData = function(lastupdate) {
  12. $.ajax({
  13. type: "GET",
  14. // set the destination for the query
  15. url: 'http://example.com:8000?lastupdate='+lastupdate+'&callback=?',
  16. // define JSONP because we're using a different port and/or domain
  17. dataType: 'jsonp',
  18. // needs to be set to true to avoid browser loading icons
  19. async: true,
  20. cache: false,
  21. // timeout after 5 minutes
  22. timeout:300000,
  23. // process a successful response
  24. success: function(response) {
  25. // append the message list with the new message
  26. var messages = response.data.messages;
  27. for (x in messages) {
  28. $('<li>'+messages[x].published+' - '+messages[x].message+'</li>').appendTo('#messages');
  29. }
  30. // set lastupdate
  31. lastupdate = response.timestamp;
  32. // call again in 1 second
  33. setTimeout('getData('+lastupdate+');', 1000);
  34. },
  35. // handle error
  36. error: function(XMLHttpRequest, textStatus, errorThrown){
  37. // try again in 10 seconds if there was a request error
  38. setTimeout('getData('+lastupdate+');', 10000);
  39. },
  40. });
  41. };

server.py (start with: $ python server.py)

  1. from twisted.web import server
  2. from twisted.web.server import Site
  3. from twisted.web.resource import Resource
  4. from twisted.internet import reactor, task
  5. import json
  6. import time
  7. # just for simulation in getData
  8. import random
  9.  
  10. class InfoServer(Resource):
  11. isLeaf = True
  12. def __init__(self):
  13. # throttle in seconds to check app for new data
  14. self.throttle = 5
  15. # define a list to store client requests
  16. self.delayed_requests = []
  17. # setup a loop to process delayed requests
  18. loopingCall = task.LoopingCall(self.processDelayedRequests)
  19. loopingCall.start(self.throttle, False)
  20. # initialize parent
  21. Resource.__init__(self)
  22.  
  23. def render(self, request):
  24. """
  25. Handle a new request
  26. """
  27. # set the request content type
  28. request.setHeader('Content-Type', 'application/json')
  29. # set args
  30. args = request.args
  31.  
  32. # set jsonp callback handler name if it exists
  33. if 'callback' in args:
  34. request.jsonpcallback = args['callback'][0]
  35.  
  36. # set lastupdate if it exists
  37. if 'lastupdate' in args:
  38. request.lastupdate = args['lastupdate'][0]
  39. else:
  40. request.lastupdate = 0
  41.  
  42. # if we have data now, send it
  43. data = self.getData(request)
  44. if len(data) > 0:
  45. return self.__format_response(request, 1, data)
  46.  
  47. # otherwise, put it in the delayed request list
  48. self.delayed_requests.append(request)
  49.  
  50. # tell the client we're not done yet
  51. return server.NOT_DONE_YET
  52.  
  53. def getData(self, request):
  54. """
  55. Replace this logic with code that will actually test for
  56. and return data your app should return.
  57.  
  58. You can use request.lastupdate here if you want to pull
  59. data since the last time this request received data.
  60.  
  61. This is just dummy logic to make this demo work.
  62. """
  63. # init data
  64. data = {}
  65.  
  66. #simulate the chance of new data being available or not
  67. new_data_available = bool(random.getrandbits(1))
  68.  
  69. # set some simulated data
  70. if new_data_available:
  71. # you can dynamically add any key/value pair here
  72. data = {'messages':[
  73. {
  74. 'message':'Test Message',
  75. 'published':int(time.time())
  76. },
  77. ]
  78. }
  79.  
  80. return data
  81.  
  82. def processDelayedRequests(self):
  83. """
  84. Processes the delayed requests that did not have
  85. any data to return last time around.
  86. """
  87. # run through delayed requests
  88. for request in self.delayed_requests:
  89. # attempt to get data again
  90. data = self.getData(request)
  91.  
  92. # write response and remove request from list if data is found
  93. if len(data) > 0:
  94. try:
  95. request.write(self.__format_response(request, 1, data))
  96. request.finish()
  97. except:
  98. # Connection was lost
  99. print 'connection lost before complete.'
  100. finally:
  101. # Remove request from list
  102. self.delayed_requests.remove(request)
  103.  
  104. def __format_response(self, request, status, data):
  105. """
  106. Format responses uniformly
  107. """
  108. # Set the response in a json format
  109. response = json.dumps({'status':status,'timestamp': int(time.time()), 'data':data})
  110.  
  111. # Format with callback format if this was a jsonp request
  112. if hasattr(request, 'jsonpcallback'):
  113. return request.jsonpcallback+'('+response+')'
  114. else:
  115. return response
  116.  
  117. #############################################
  118. if __name__ == '__main__':
  119. resource = InfoServer()
  120. factory = Site(resource)
  121. reactor.listenTCP(8000, factory)
  122. reactor.run()

  • JSON
  • JSONP
  • Long Polling
  • Python
  • Twisted

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <img> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <h3> <h4> <h5> <h6> <h7>
  • Lines and paragraphs break automatically.
  • You can enable syntax highlighting of source code with the following tags: <code>, <blockcode>. The supported tag styles are: <foo>, [foo].

More information about formatting options

Poll

Have you used any NoSQL Databases?:

User login

  • Request new password

Navigation

  • Recent posts

  • home
  • drupal articles
  • contact