Created
January 8, 2011 10:40
-
-
Save amcgregor/770743 to your computer and use it in GitHub Desktop.
Some example p-code illustrating an async application, how middleware handles this, and the inner part of a server reactor loop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# This is the initial idea based on a callback-based async framework. | |
# This assumes yield syntax is mandatory; applications and middleware | |
# must be generators. | |
# This idea should be usable as-is and a fork of m.s.http should be | |
# utilized to explore the concept within the context of a real server. | |
# Combine futures with the dubious (and much-argued) yield reactor | |
# syntax. Yield allows the application to return to the reactor in such | |
# a way as to be able to be resumed. | |
def my_awesome_application(environ): | |
future = environ['wsgi.executor'].submit(environ['wsgi.input'].read, 4096) | |
# do some additional work while waiting | |
# when we -need- the value, yield the future instance | |
future = yield future | |
# we get the same future back; this is to allow yield ...submit() | |
# this can be wrapped in try: except: to capture exceptions raised | |
# from within the submitted callable | |
data = future.result() | |
# do something with the data, rinse, repeat | |
yield b'200 OK', [...], body_iter | |
# Example (naieve) middleware layer. No attempt has been made to handle | |
# exceptions. Much of this code could be handled by a decorator that | |
# only calls this inner middleware on non-future yielded values. | |
# A decorator to do this could be provided with wsgi2ref in stdlib. | |
def my_middleware(app): | |
def wrapper(environ): | |
value = None | |
chunk = None | |
iterator = app(environ) | |
try: | |
while True: | |
if value: | |
# Return a value from up-stream to the application. | |
chunk = iterator.send(value) | |
else: | |
# Otherwise just consume. | |
chunk = iterator.next() | |
if not isinstance(i, tuple): | |
# We need to yield data back to the application. | |
value = yield chunk | |
continue | |
# The chunk represents the response; we can do our | |
# work here. We can even generate a different response. | |
yield chunk | |
except StopIteration: | |
pass | |
finally: | |
# middleware cleanup code goes here | |
return wrapper | |
# The reactor calling the application needs to do little bit of work and | |
# use a small number of implementation-specific attributes: | |
if not is_generator(app): | |
resopnse = app(environ) | |
else: | |
iterable = app(environ) | |
def inner_loop(iterable, data=None, exc=NO_DEFAULT): | |
response = None | |
try: | |
while True: | |
if exc: | |
value = iterable.throw(*exc) | |
if data is not NO_DEFAULT: # can be None or an empty string | |
value = iterable.send(data) | |
else: | |
value = iterable.next() | |
assert isinstance(value, tuple) or hasattr(value, 'running') | |
if isinstance(value, tuple): | |
# the response can only be returned once | |
assert response is None | |
response = value | |
# write the status and headers to the async socket | |
# and pass off the response body to the write | |
# success callback | |
# we continue to allow additional processing | |
# (e.g. clean-up) | |
continue | |
# set by executor.submit when detecting operations | |
# across sockets/files. implementation-specific; | |
# doesn't need to be called 'fd'. | |
async_fd = getattr(value, 'fd', None) | |
if async_fd is not None: | |
# add the async_fd and value.operation (read + error | |
# or write + error) to the reactor. the callback | |
# should be a helper that distinguishes between | |
# success and failure, calling this inner_loop | |
# function partial'ed with the iterator | |
return | |
# add a completion callback which re-schedules the | |
# application and .sends() the future instance back up | |
# would need liberal use of functools.partial | |
return | |
except StopIteration: | |
pass | |
inner_loop(iterable) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# This utilizes a decorator to abstract away the ping-pong scheduler. | |
# The decorator should bypass the middleware after it yields None and if | |
# the application raises an uncaught exception, re-raise it in the | |
# middleware. If the middleware doesn't handle it, the decorator would | |
# re-raise. | |
def middleware(app): | |
@wsgi2ref.middleware | |
def inner(environ): | |
# we can do some prep work before asking to be paused pending | |
# response generation, including modifying the environ. | |
future = yield environ['wsgi.executor'].submit(...) | |
# do something with the result; should catch exceptions | |
future.result() | |
# this may raise an uncaught exception generated by the application | |
response = yield app # wait for the application to generate a response | |
# the above allows the middleware to utilize an alternate application | |
# you could just yield app and be done (i.e. no post-processing); the | |
# decorator would handle premature StopIteration by yielding the | |
# response up to the server. | |
# do something with the response | |
yield response # or a new one, or some more futures first, etc. | |
return inner |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment