This commit is contained in:
Ryan Williams
2010-07-04 14:13:01 -07:00
3 changed files with 94 additions and 0 deletions

View File

@@ -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 You can find a slightly more elaborate version of this code in the file
``examples/wsgi.py``. ``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 .. automodule:: eventlet.wsgi
:members: :members:

View File

@@ -376,6 +376,9 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler):
pass pass
finish = time.time() finish = time.time()
for hook, args, kwargs in self.environ['eventlet.posthooks']:
hook(self.environ, *args, **kwargs)
self.server.log_message(self.server.log_format % dict( self.server.log_message(self.server.log_format % dict(
client_ip=self.get_client_ip(), client_ip=self.get_client_ip(),
date_time=self.log_date_time_string(), date_time=self.log_date_time_string(),
@@ -442,6 +445,7 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler):
env['wsgi.input'] = env['eventlet.input'] = Input( env['wsgi.input'] = env['eventlet.input'] = Input(
self.rfile, length, wfile=wfile, wfile_line=wfile_line, self.rfile, length, wfile=wfile, wfile_line=wfile_line,
chunked_input=chunked) chunked_input=chunked)
env['eventlet.posthooks'] = []
return env return env

View File

@@ -818,6 +818,60 @@ class TestHttpd(_TestBase):
pass # TODO: should test with OpenSSL pass # TODO: should test with OpenSSL
greenthread.kill(g) 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 test_zero_length_chunked_response(self):
def zero_chunked_app(env, start_response): def zero_chunked_app(env, start_response):
start_response('200 OK', [('Content-type', 'text/plain')]) start_response('200 OK', [('Content-type', 'text/plain')])