Non-Message Queue / Simple Long-Polling in Python (and Flask)
Asked Answered
A

2

34

I am looking for a simple (i.e., not one that requires me to setup a separate server to handle a messaging queue) way to do long-polling for a small web-interface that runs calculations and produces a graph. This is what my web-interface needs to do:

  1. User requests a graph/data in a web-interface
  2. Server runs some calculations.
  3. While the server is running calculations, a small container is updated (likely via AJAX/jQuery) with the calculation progress (similar to what you'd do in a consol with print (i.e. print 'calculating density function...'))
  4. Calculation finishes and graph is shown to user.

As the calculation is all done server-side, I'm not really sure how to easily set this up. Obviously I'll want to setup a REST API to handle the polling, which would be easy in Flask. However, I'm not sure how to retrieve the actual updates. An obvious, albeit complicated for this purpose, solution would be to setup a messaging queue and do some long polling. However, I'm not sure sure this is the right approach for something this simple.

Here are my questions:

  1. Is there a way to do this using the file system? Performance isn't a huge issue. Can AJAX/jQuery find messages from a file? Save the progress to some .json file?
  2. What about pickling? (I don't really know much about pickling, but maybe I could pickle a message dict and it could be read by an API that is handling the polling).
  3. Is polling even the right approach? Is there a better or more common pattern to handle this?

I have a feeling I'm overcomplicating things as I know this kind of thing is common on the web. Quite often I see stuff happening and a little "loading.gif" image is running while some calculation is going on (for example, in Google Analytics).

Thanks for your help!

Abhenry answered 22/6, 2012 at 14:25 Comment(0)
T
49

I've built several apps like this using just Flask and jQuery. Based on that experience, I'd say your plan is good.

  1. Do not use the filesystem. You will run into JavaScript security issues/protections. In the unlikely event you find reasonable workarounds, you still wouldn't have anything portable or scalable. Instead, use a small local web serving framework, like Flask.

  2. Do not pickle. Use JSON. It's the language of web apps and REST interfaces. jQuery and those nice jQuery-based plugins for drawing charts, graphs and such will expect JSON. It's easy to use, human-readable, and for small-scale apps, there's no reason to go any place else.

  3. Long-polling is fine for what you want to accomplish. Pure HTTP-based apps have some limitations. And WebSockets and similar socket-ish layers like Socket.IO "are the future." But finding good, simple examples of the server-side implementation has, in my experience, been difficult. I've looked hard. There are plenty of examples that want you to set up Node.js, REDIS, and other pieces of middleware. But why should we have to set up two or three separate middleware servers? It's ludicrous. So long-polling on a simple, pure-Python web framework like Flask is the way to go IMO.

The code is a little more than a snippet, so rather than including it here, I've put a simplified example into a Mercurial repository on bitbucket that you can freely review, copy, or clone. There are three parts:

  • serve.py a Python/Flask based server
  • templates/index.html 98% HTML, 2% template file the Flask-based server will render as HTML
  • static/lpoll.js a jQuery-based client
Tramel answered 22/6, 2012 at 18:13 Comment(10)
Ah, thank you so much for this! I am going to take a look at your code tonight. "But finding good, simple examples of the server-side implementation has, in my experience, been difficult." is the exact experience I had, so I'm glad to hear I'm not alone. Thanks again!!Abhenry
FYI I tried this first with bottle.py (with a long subprocess script) and got some blocking problems. With Flask it works great. Thanks!Tello
How would one synchronise multiple clients to all receive the signal at the same time?Johnstone
If that level of synchronization is required, then long-polling is largely out of the question. It'd be possible to "synchronize watches" and all re-poll at a common timestamp--but given time drift between servers, that's more difficult than it sounds (if you need to be truly precise). It also begs race/convoy conditions (unless the requests are very few/small). If you really need that kind of same-time sync at any scale, look to a push technique, e.g. WebSockets. One of the WS implementations atop Tornado, perhaps, or the very cool Meteor.Tramel
@Jonathan Is there a good resource I can start with to understand things like why long polling is bad, or how why it was developed, and why websockets is a better solution? If you could point me to a source other than say a standard document, that would be great. I am a web dev noob and I'm kinda lost in the bushes.Eddings
@Eddings Long polling isn't bad, per se, if it's the best strategy available. But it doesn't scale well to have thousands or tens of thousands of clients with open connections to your server, just waiting for the server to emit new data. If it's a local app, the number of outstanding long polls will be small (~1), and not a problem. WebSockets are better because they're not a retrofit/make do solution, but designed for this kind of quick-update situation, and much more scalable as a result. There are also fewer failure modes.Tramel
@JonathanEunice looks like the link isnt working - can you please post it here or maybe in github - thanksKusin
@Kusin Bitbucket took repos away. Will try to get my repos moved over to GitHub this weekend.Tramel
Searching fore Junice and lpoll I found this: github.com/chifatty/server-push-exampleStroll
Bitbucket repository has disappeared :(Fogle
T
14

Long-polling was a reasonable work-around before simple, natural support for Web Sockets came to most browsers, and before it was easily integrated alongside Flask apps. But here in mid-2013, Web Socket support has come a long way.

Here is an example, similar to the one above, but integrating Flask and Web Sockets. It runs atop server components from gevent and gevent-websocket.

Note this example is not intended to be a Web Socket masterpiece. It retains a lot of the lpoll structure, to make them more easily comparable. But it immediately improves responsiveness, server overhead, and interactivity of the Web app.

Update for Python 3.7+

5 years since the original answer, WebSocket has become easier to implement. As of Python 3.7, asynchronous operations have matured into mainstream usefulness. Python web apps are the perfect use case. They can now use async just as JavaScript and Node.js have, leaving behind some of the quirks and complexities of "concurrency on the side." In particular, check out Quart. It retains Flask's API and compatibility with a number of Flask extensions, but is async-enabled. A key side-effect is that WebSocket connections can be gracefully handled side-by-side with HTTP connections. E.g.:

from quart import Quart, websocket

app = Quart(__name__)

@app.route('/')
async def hello():
    return 'hello'

@app.websocket('/ws')
async def ws():
    while True:
        await websocket.send('hello')

app.run()

Quart is just one of the many great reasons to upgrade to Python 3.7.

Tramel answered 7/8, 2013 at 1:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.