Implement any of the following methods (collectively known as the
HTTP verb methods) to handle the corresponding HTTP method.
+ These methods can be made asynchronous with one of the following
+ decorators: `.gen.coroutine`, `.return_future`, or `asynchronous`.
.. automethod:: RequestHandler.get
.. automethod:: RequestHandler.post
from tornado.concurrent import return_future
from tornado.escape import url_escape
from tornado.httpclient import AsyncHTTPClient
+from tornado.ioloop import IOLoop
from tornado.log import app_log
from tornado import stack_context
from tornado.testing import AsyncHTTPTestCase, AsyncTestCase, ExpectLog, gen_test
from tornado.test.util import unittest, skipOnTravis
-from tornado.web import Application, RequestHandler, asynchronous
+from tornado.web import Application, RequestHandler, asynchronous, HTTPError
from tornado import gen
class GenCoroutineSequenceHandler(RequestHandler):
- @asynchronous
@gen.coroutine
def get(self):
self.io_loop = self.request.connection.stream.io_loop
self.finish('ok')
+class UndecoratedCoroutinesHandler(RequestHandler):
+ @gen.coroutine
+ def prepare(self):
+ self.chunks = []
+ yield gen.Task(IOLoop.current().add_callback)
+ self.chunks.append('1')
+
+ @gen.coroutine
+ def get(self):
+ self.chunks.append('2')
+ yield gen.Task(IOLoop.current().add_callback)
+ self.chunks.append('3')
+ yield gen.Task(IOLoop.current().add_callback)
+ self.write(''.join(self.chunks))
+
+
+class AsyncPrepareErrorHandler(RequestHandler):
+ @gen.coroutine
+ def prepare(self):
+ yield gen.Task(IOLoop.current().add_callback)
+ raise HTTPError(403)
+
+ def get(self):
+ self.finish('ok')
+
+
class GenWebTest(AsyncHTTPTestCase):
def get_app(self):
return Application([
('/exception', GenExceptionHandler),
('/coroutine_exception', GenCoroutineExceptionHandler),
('/yield_exception', GenYieldExceptionHandler),
+ ('/undecorated_coroutine', UndecoratedCoroutinesHandler),
+ ('/async_prepare_error', AsyncPrepareErrorHandler),
])
def test_sequence_handler(self):
response = self.fetch('/yield_exception')
self.assertEqual(response.body, b'ok')
+ def test_undecorated_coroutines(self):
+ response = self.fetch('/undecorated_coroutine')
+ self.assertEqual(response.body, b'123')
+
+ def test_async_prepare_error_handler(self):
+ response = self.fetch('/async_prepare_error')
+ self.assertEqual(response.code, 403)
+
if __name__ == '__main__':
unittest.main()
Override this method to perform common initialization regardless
of the request method.
+
+ Asynchronous support: Decorate this method with `.gen.coroutine`
+ or `.return_future` to make it asynchronous (the
+ `asynchronous` decorator cannot be used on `prepare`).
+ If this method returns a `.Future` execution will not proceed
+ until the `.Future` is done.
"""
pass
if self.request.method not in ("GET", "HEAD", "OPTIONS") and \
self.application.settings.get("xsrf_cookies"):
self.check_xsrf_cookie()
- self.prepare()
- if not self._finished:
- getattr(self, self.request.method.lower())(
- *self.path_args, **self.path_kwargs)
- if self._auto_finish and not self._finished:
- self.finish()
+ self._when_complete(self.prepare(), self._execute_method)
+ except Exception as e:
+ self._handle_request_exception(e)
+
+ def _when_complete(self, result, callback):
+ try:
+ if result is None:
+ callback()
+ elif isinstance(result, Future):
+ if result.done():
+ if result.result() is not None:
+ raise ValueError('Expected None, got %r' % result)
+ callback()
+ else:
+ # Delayed import of IOLoop because it's not available
+ # on app engine
+ from tornado.ioloop import IOLoop
+ IOLoop.current().add_future(
+ result, functools.partial(self._when_complete,
+ callback=callback))
+ else:
+ raise ValueError("Expected Future or None, got %r" % result)
except Exception as e:
self._handle_request_exception(e)
+ def _execute_method(self):
+ method = getattr(self, self.request.method.lower())
+ self._when_complete(method(*self.path_args, **self.path_kwargs),
+ self._execute_finish)
+
+ def _execute_finish(self):
+ if self._auto_finish and not self._finished:
+ self.finish()
+
def _generate_headers(self):
reason = self._reason
lines = [utf8(self.request.version + " " +
def asynchronous(method):
"""Wrap request handler methods with this if they are asynchronous.
+ This decorator is unnecessary if the method is also decorated with
+ ``@gen.coroutine`` (it is legal but unnecessary to use the two
+ decorators together, in which case ``@asynchronous`` must be
+ first).
+
This decorator should only be applied to the :ref:`HTTP verb
methods <verbs>`; its behavior is undefined for any other method.
This decorator does not *make* a method asynchronous; it tells