Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Capturing event of client disconnecting! - Gevent/Python

I'm using long polling for a chat with gevent. I'm using Event.wait() when waiting for new messages to be posted on the chat.


I would like to handle the occasion a client disconnects with some functionality:

e.g. Return "client has disconnected" as a message for other chat users


Is this possible? =)

like image 528
RadiantHex Avatar asked Jul 26 '10 12:07

RadiantHex


3 Answers

This depends on which WSGI server you use. AFAIK gevent.wsgi will not notify your handler in any way when the client closes the connection, because libevent-http does not do that. However, with gevent.pywsgi it should be possible. You'll probably need to start an additional greenlet to monitor the socket condition and somehow notify the greenlet that runs the handler, e.g. by killing it. I could be missing an easier way to do this though.

like image 27
Denis Avatar answered Sep 30 '22 11:09

Denis


This is a total stab in the dark as I've never used gevent but wouldn't a client disconnect simply be when the socket is closed. So maybe something like this would work:

if not Event.wait():
    # Client has disconnected, do your magic here!
    return Chat({'status': 'client x has disconnected'})
like image 34
DaveJ Avatar answered Sep 30 '22 13:09

DaveJ


According to the WSGI PEP, if your app returns an iterator with a close() method, the server should call that at the end of the request. Here's an example:

"""
Run this script with 'python sleepy_app.py'.  Then try connecting to the server
with curl:

    curl -N http://localhost:8000/

You should see a counter printed in your terminal, incrementing once every
second.

Hit Ctrl-C on the curl window to disconnect the client.  Then watch the
server's output.  If running with a WSGI-compliant server, you should see
"SLEEPY CONNECTION CLOSE" printed to the terminal.
"""

class SleepyApp(object):
    def __init__(self, environ, start_response):
        self.environ = environ
        self.start_response = start_response

    def __iter__(self):
        self.start_response('200 OK', [('Content-type', 'text/plain')])
        # print out one number every 10 seconds.
        import time  # imported late for easier gevent patching
        counter = 0
        while True:
            print "SLEEPY", counter
            yield str(counter) + '\n'
            counter += 1
            time.sleep(1)

    def close(self):
        print "SLEEPY CONNECTION CLOSE"


def run_gevent():
    from gevent.monkey import patch_all
    patch_all()
    from gevent.pywsgi import WSGIServer
    server = WSGIServer(('0.0.0.0', 8000), SleepyApp)
    print "Server running on port 0.0.0.0:8000. Ctrl+C to quit"
    server.serve_forever()

if __name__ == '__main__':
    run_gevent()

However, there's a bug in Python's wsgiref implementation (and in the Django dev server that inherits from it) that prevents close() from being called on mid-stream client disconnects. So avoid wsgiref and the Django dev server for this case.

Note also that close() won't be fired immediately when the client disconnects. It'll happen when you try to write some message to the client and fail because the connection isn't there anymore.

like image 176
btubbs Avatar answered Sep 30 '22 13:09

btubbs