I have a python generator function which yields chunks of text. I would like to write a get
method for a tornado.web.RequestHandler
subclass that will iterate over the generator, writing the chunks out to the response as it goes.
Since this is Tornado, and since the generator may take over a second to process, I thought it would be nice to make the handler asynchronous, using this generator as a co-routine and passing off control to the IOLoop after every chunk. However, I can't make heads or tails of how to do this.
Here's my example (blocking) code:
class TextHandler(web.RequestHandler):
@web.asynchronous
def get(self, n):
generator = self.generate_text(100000)
# Clearly, this will block. How to make it asynchronous?
for text in generator:
self.write(text)
def generate_text(n):
for x in xrange(n):
if not x % 15:
yield "FizzBuzz\n"
elif not x % 5:
yield "Buzz\n"
elif not x % 3:
yield "Fizz\n"
else:
yield "%s\n" % x
How can I make this handler work asynchronously?
Here's a basic version of what you are describing. To avoid blocking you can pass your generator to the IOLoop via a callback function. The trick here is since you are not using a process that does actual IO and so has no os level process/file handler to add to the IOLoop via add_handler
, you can instead use a simple add_callback
call and call it repeatedly from within the callback function to keep the function in the IOLoop callback queue until the generator has finished.
import tornado.httpserver
import tornado.ioloop
import tornado.web
class TextHandler(tornado.web.RequestHandler):
@tornado.web.asynchronous
def get(self):
self.generator = self.generate_text(1000)
tornado.ioloop.IOLoop.instance().add_callback(self.loop)
def loop(self):
try:
text = self.generator.next()
self.write(text)
tornado.ioloop.IOLoop.instance().add_callback(self.loop)
except StopIteration:
self.finish()
def generate_text(self, n):
for x in xrange(n):
if not x % 15:
yield "FizzBuzz\n"
elif not x % 5:
yield "Buzz\n"
elif not x % 3:
yield "Fizz\n"
else:
yield "%s\n" % x
application = tornado.web.Application([
(r"/text/", TextHandler),
])
http_server = tornado.httpserver.HTTPServer(application)
http_server.listen(8888)
tornado.ioloop.IOLoop.instance().start()
It is also possible to use the new tornado's gen interface to async processes:
import tornado.httpserver
import tornado.ioloop
import tornado.web
import tornado.gen
class TextHandler(tornado.web.RequestHandler):
@tornado.web.asynchronous
@tornado.gen.engine
def get(self):
def cb(it, callback):
try:
value = it.next()
except StopIteration:
value = None
callback(value)
it = self.generate_text(1000)
while True:
response = yield tornado.gen.Task(cb, it)
if response:
self.write(response)
else:
break
self.finish()
def generate_text(self, n):
for x in xrange(n):
if not x % 15:
yield "FizzBuzz\n"
elif not x % 5:
yield "Buzz\n"
elif not x % 3:
yield "Fizz\n"
else:
yield "%s\n" % x
application = tornado.web.Application([
(r"/text/", TextHandler),
])
http_server = tornado.httpserver.HTTPServer(application)
http_server.listen(8888)
tornado.ioloop.IOLoop.instance().start()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With