On Jul 28, 2008, at 12:52 PM, Etienne Robillard wrote:

On Mon, 18 Feb 2008 04:23:38 -0800 (PST)
est <[EMAIL PROTECTED]> wrote:

I am writing a small 'comet'-like app using flup, something like
this:
<snip>
So is WSGI really synchronous? How can I handle asynchronous outputs
with flup/WSGI ?

WSGI says that the entire body should be written by the time the wsgi application returns. So yes it is really synchronous; as Manlio Perillo said in another message it is possible to abuse generators to allow a wsgi application to operate in the fashion you desire, but both the server and the application have to know how to do this and there is no standardization yet.

maybe start by looking here: 
http://twistedmatrix.com/trac/browser/trunk/twisted/web2/wsgi.py

web2.wsgi's server doesn't really get around the problem. While it does non-blocking i/o for the http request and response, it actually calls the wsgi application in a threadpool, because there's no way for the wsgi application to return before having generated all of the response, and even if there were people's wsgi applications don't work this way.

You might want to check out orbited (http://www.orbited.org/), which doesn't have anything to do with wsgi, but is a Python comet server implemented entirely with non-blocking i/o (using libevent).

However, if you are willing to spend some time getting a custom comet server up and running, you could take a look at eventlet (http://pypi.python.org/pypi/eventlet/ ) and spawning (http://pypi.python.org/pypi/Spawning/). I've been working on eventlet for a couple of years precisely to make implementing scalable and easy to maintain comet applications possible.

Here's a simple Comet server that uses spawning and eventlet. This will give you a comet server that scales to tons of simultaneous connections, because eventlet mashes together greenlet (coroutines, or light-weight cooperative threads) with non-blocking i/o (select, poll, libevent, or libev). This is how Spawning can be used to get around the wsgi restriction that the entire body should be written by the time the wsgi application returns; since spawning uses greenlets instead of posix threads for each wsgi request when --threads=0 is passed, many simultaneous wsgi applications can be running waiting for Comet events with very little memory and CPU overhead.

Save it in a file called spawningcomet.py and run it with:

        spawn spawningcomet.wsgi_application --threads=0

Then, visit http://localhost:8080 in your browser and run this in another terminal:

        python spawningcomet.py hello world

## spawningcomet.py

import struct
import sys
import uuid

from eventlet import api
from eventlet import coros


SEND_EVENT_INTERFACE = ''
SEND_EVENT_PORT = 4200


HTML_TEMPLATE = """<html>
    <head>
        <script type="text/javascript">
<!--
function make_request(event_id) {
    var req = new XMLHttpRequest();

    req.onreadystatechange = function() {
        if (req.readyState == 4) {
            var newdiv = document.createElement("div");
newdiv.appendChild(document.createTextNode(req.responseText));
            document.getElementById("body").appendChild(newdiv);
            var next_event = req.getResponseHeader("X-Next-Event");
            if (next_event) {
                make_request(next_event);
            }
        }
    }
    req.open("GET", event_id);
    req.send(null);
}

make_request("%s");
-->
        </script>
    </head>
    <body id="body">
        <h1>Dynamic content will appear below</h1>
    </body>
</html>
"""

class Comet(object):
    def __init__(self):
        api.spawn(
            api.tcp_server,
            api.tcp_listener((SEND_EVENT_INTERFACE, SEND_EVENT_PORT)),
            self.read_events_forever)

        self.current_event = {'event': coros.event(), 'next': None}
        self.first_event_id = str(uuid.uuid1())
        self.events = {self.first_event_id: self.current_event}

    def read_events_forever(self, (sock, addr)):
        reader = sock.makefile('r')
        try:
            while True:
                ## Read the next event value out of the socket
                valuelen = reader.read(4)
                if not valuelen:
                    break

                valuelen, = struct.unpack('!L', valuelen)
                value = reader.read(valuelen)

                ## Make a new event and link the current event to it
                old_event = self.current_event
                old_event['next'] = str(uuid.uuid1())
                self.current_event = {
                    'event': coros.event(), 'next': None}
                self.events[old_event['next']] = self.current_event

                ## Send the event value to any waiting http requests
                old_event['event'].send(value)
        finally:
            reader.close()
            sock.close()

    def __call__(self, env, start_response):
        if env['REQUEST_METHOD'] != 'GET':
start_response('405 Method Not Allowed', [('Content- type', 'text/plain')])
            return ['Method Not Allowed\n']

        if not env['PATH_INFO'] or env['PATH_INFO'] == '/':
            start_response('200 OK', [('Content-type', 'text/html')])
            return HTML_TEMPLATE % (self.first_event_id, )

        event = self.events.get(env['PATH_INFO'][1:], None)
        if event is None:
start_response('404 Not Found', [('Content-type', 'text/ plain')])
            return ['Not Found\n']

        value = event['event'].wait()
        start_response('200 OK', [
            ('Content-type', 'text/plain'),
            ('X-Next-Event', event['next'])])
        return [value, '\n']


def send_event(where, value):
    sock = api.connect_tcp(where)
    writer = sock.makefile('w')
    writer.write('%s%s' % (struct.pack('!L', len(value)), value))


if __name__ == '__main__':
    if len(sys.argv) > 1:
        value = ' '.join(sys.argv[1:])
    else:
        value = sys.stdin.read()
    send_event((SEND_EVENT_INTERFACE, SEND_EVENT_PORT), value)
else:
    wsgi_application = Comet()

_______________________________________________
Web-SIG mailing list
Web-SIG@python.org
Web SIG: http://www.python.org/sigs/web-sig
Unsubscribe: 
http://mail.python.org/mailman/options/web-sig/archive%40mail-archive.com

Reply via email to