diff --git a/doc/modules/wsgi.rst b/doc/modules/wsgi.rst index c05485d..a37d35a 100644 --- a/doc/modules/wsgi.rst +++ b/doc/modules/wsgi.rst @@ -21,5 +21,41 @@ To launch a wsgi server, simply create a socket and call :func:`eventlet.wsgi.se You can find a slightly more elaborate version of this code in the file ``examples/wsgi.py``. +Non-Standard Extension to Support Post Hooks +-------------------------------------------- +Eventlet's WSGI server supports a non-standard extension to the WSGI +specification where :samp:`env['eventlet.posthooks']` contains an array of +`post hooks` that will be called after fully sending a response. Each post hook +is a tuple of :samp:`(func, args, kwargs)` and the `func` will be called with +the WSGI environment dictionary, followed by the `args` and then the `kwargs` +in the post hook. + +For example:: + + from eventlet import wsgi + import eventlet + + def hook(env, arg1, arg2, kwarg3=None, kwarg4=None): + print 'Hook called: %s %s %s %s %s' % (env, arg1, arg2, kwarg3, kwarg4) + + def hello_world(env, start_response): + env['eventlet.posthooks'].append( + (hook, ('arg1', 'arg2'), {'kwarg3': 3, 'kwarg4': 4})) + start_response('200 OK', [('Content-Type', 'text/plain')]) + return ['Hello, World!\r\n'] + + wsgi.server(eventlet.listen(('', 8090)), hello_world) + +The above code will print the WSGI environment and the other passed function +arguments for every request processed. + +Post hooks are useful when code needs to be executed after a response has been +fully sent to the client (or when the client disconnects early). One example is +for more accurate logging of bandwidth used, as client disconnects use less +bandwidth than the actual Content-Length. + +API +--- + .. automodule:: eventlet.wsgi :members: diff --git a/tests/wsgi_test.py b/tests/wsgi_test.py index b10dd00..e4046e4 100644 --- a/tests/wsgi_test.py +++ b/tests/wsgi_test.py @@ -818,6 +818,60 @@ class TestHttpd(_TestBase): pass # TODO: should test with OpenSSL greenthread.kill(g) + def test_029_posthooks(self): + posthook1_count = [0] + posthook2_count = [0] + def posthook1(env, value, multiplier=1): + self.assertEquals(env['local.test'], 'test_029_posthooks') + posthook1_count[0] += value * multiplier + def posthook2(env, value, divisor=1): + self.assertEquals(env['local.test'], 'test_029_posthooks') + posthook2_count[0] += value / divisor + + def one_posthook_app(env, start_response): + env['local.test'] = 'test_029_posthooks' + if 'eventlet.posthooks' not in env: + start_response('500 eventlet.posthooks not supported', + [('Content-Type', 'text/plain')]) + else: + env['eventlet.posthooks'].append( + (posthook1, (2,), {'multiplier': 3})) + start_response('200 OK', [('Content-Type', 'text/plain')]) + yield '' + self.site.application = one_posthook_app + sock = eventlet.connect(('localhost', self.port)) + fp = sock.makefile('rw') + fp.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') + fp.flush() + self.assertEquals(fp.readline(), 'HTTP/1.1 200 OK\r\n') + fp.close() + sock.close() + self.assertEquals(posthook1_count[0], 6) + self.assertEquals(posthook2_count[0], 0) + + def two_posthook_app(env, start_response): + env['local.test'] = 'test_029_posthooks' + if 'eventlet.posthooks' not in env: + start_response('500 eventlet.posthooks not supported', + [('Content-Type', 'text/plain')]) + else: + env['eventlet.posthooks'].append( + (posthook1, (4,), {'multiplier': 5})) + env['eventlet.posthooks'].append( + (posthook2, (100,), {'divisor': 4})) + start_response('200 OK', [('Content-Type', 'text/plain')]) + yield '' + self.site.application = two_posthook_app + sock = eventlet.connect(('localhost', self.port)) + fp = sock.makefile('rw') + fp.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') + fp.flush() + self.assertEquals(fp.readline(), 'HTTP/1.1 200 OK\r\n') + fp.close() + sock.close() + self.assertEquals(posthook1_count[0], 26) + self.assertEquals(posthook2_count[0], 25) + def test_zero_length_chunked_response(self): def zero_chunked_app(env, start_response): start_response('200 OK', [('Content-type', 'text/plain')])