views:

127

answers:

2

I have a Pylons controller that is called via jQuery $.ajaxSubmit(). It executes a long-running operation and I want to have it return progress messages before the entire output is available. As a simple proof of concept I've tried this:

response.body_file.write('<script>OnExecutionProgress("Starting");</script>\n')
time.sleep(2)
response.body_file.write('<script>OnExecutionProgress("Finished");</script>\n')

However, this doesn't return the first message to the client immediately - the entire output is returned only at the end.

I've done something like this previously in ASP.NET using Response.Write() and Response.Flush() and it worked well. Calling response.body_file.flush() in Pylons seems to make no difference, though.

A: 

Natively, HTTP does not support the "streaming" you appear to desire -- it's a request/response protocol and there's no protocol-compliant way to send "part of a response". Rather, you may be looking for so-called Comet techniques. A bare-bone example of "comet with pylons" is here.

Alex Martelli
Yeah, I know it's a hack - but it worked with ASP.NET, so I figured there's no reason why it couldn't work with Pylons. I was hoping to avoid a proper Comet implementation.
Evgeny
After more research I figured out why this doesn't work. Both IIS and the ASP.NET development server support chunked encoding and that's how it sends bits of the response before the entire page is rendered. However, Paste does not support it: http://pythonpaste.org/modules/httpserver.html :(
Evgeny
A: 

It's definitely a hack, but in pylons it works the same way as in ASP.NET. I've tested it on Paste server.

As Alex Martelli said it's better to use Comet in this case. Feeding script chunk by chunk could lead to the following problems:

  1. it's unsupported and server implementation dependent;
  2. unreliable since you can't handle connection timeouts;
  3. server need to allocate separate thread for each connection which could became performance bottleneck on high-load sites.

First of all you should turn off debugging in development.ini:

debug = false

Here is a controller code:

class HelloController(BaseController):
    def index(self):
        header = '''\
<html><head><title>test page</title>
<script type="text/javascript">
function foo(i) {
document.getElementById('counter').innerHTML = i;
}
</script>
</head>
<body>
<div id="counter">
</div>
'''
        footer = '''\
</body>
</html>
'''
        progress = ('<script>foo(%i);</script>' % i for i in xrange(0,101,2))
        def f():
            yield header
            for script in progress:
                time.sleep(1)
                yield script
            yield footer
        return f()
Yaroslav
Sorry, that doesn't work for me even with `debug = false`. The entire HTTP response is returned only when the action finishes executing. But thanks for revealing a problem with my code when debug = false and thanks for helping me fix it in the other question.
Evgeny