Skip to content

Instantly share code, notes, and snippets.

@amcgregor
Created January 8, 2011 10:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save amcgregor/770743 to your computer and use it in GitHub Desktop.
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 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 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