Tornado - Введение в асинхронность

Доброго времени суток, сегодня поговорим о Tornado - это асинхронный (неблокирующий) веб-фреймворк. О самих асинхронных серверах и об архитектуре в общем мы говорили тут.
Также об Async Web есть запись видеовстречи iff: https://www.youtube.com/watch?feature=pl...

Итак, Tornado.
Документация к фреймворку основанна на встроенной системе документации python. На момент написания статьи актуальной версией Tornado является 3.2, ну и конечно мы используем python3.
Из-за скудной документации приходится часто рыться в исходниках, чтобы понять как работать с данным чудом, поэтому я буду ждать вопросов от тех, кто выберет данный фреймворк и сервер для своих проектов. В этой статье, я постараюсь рассказать о самых основных проблемных вопросах в Tornado.

Для того чтобы было понятно тем, кто не знаком с Tornado, я приведу типичный код из документации, в котором будет создан самый простой http-сервер, обрабатывающий http запросы.

class MainHandler(RequestHandler):  # класс-обработчик запроса, он будет обрабатывать поступивший url
    def get(self):  # переобределяем метод GET
        self.write("ololo")  # записываем в ответ "ololo"
                     # на этом обработка запроса завершается

class TestHandler(RequestHandler):
    def initialize(self, database):  # здест мы передаём при связке с url параметр
        self.database = database  # см. ниже

    def get(self):
        ...


class HiamHandler(RequestHandler):
    def post(self, username):  # параметр в url присутсвует в параметрах функции обработки запроса
        ...


app_settings = {  # здесь популярные настройки, не нужно обращать на них внимания
    'autoreload': True,
    'debug': True,
    'gzip': False,
    'serve_traceback': True, # True, if Debug
    #'log_function': SOME_LOG_Function,
    #'default_handler_class': SOME_VALUE,
    #'default_handler_args': SOME_VALUE,
    'cookie_secret': "DebuG)",
    #'login_url': "SOME_LOGIN_URL",
    'xsrf_cookies': True,
    #'autoescape': None,
    'compiled_template_cache': False, # False, if Debug
    'template_path': "SOME PATH",
    #'template_loader': SOME_VALUE,
    'static_hash_cache': False, # False, if Debug
    'static_path': "SOME PATH",
    'static_url_prefix': "/static/",
}

if __name__ == "__main__":  # далее код инициализации и запуска сервера
    application = tornado.web.Application([  # создаём наше приложение - сервер
        (r"/", MainHandler),  # тут ставим в соответсвие класс-обработчик запроса и url
        (r"/test/", TestHandler, dict(database=database)),  # возможно также 
                                                    # передавать дополнительный параметр
        (r"/hwoiam/(.*)", HiamHandler),  # параметры url определяются регулярным
                                           # выражением как в других фреймворках
    ], **app_settings)  # словарь с настройками
    application.listen(8888)  # указываем порт, который будем слушать
    tornado.ioloop.IOLoop.instance().start()  # запускаем основную петлю задач, 
                                          # что необходимо при асинхроной архитектуре

Это почти стандартный скелет для реализации собственного http сервера, более подробно смотрите документацию, для самых элементарных вещей она достаточно понятна: http://www.tornadoweb.org/en/stable/docu...

--- Грабля 1: Первое что нужно понять, что тут почти всё надо делать ручками. Для того чтобы использовать встроенную систему аутентификации нужно переопределить функцию get_current_user,а для защиты от XSRF достаточно следовать инструкциям, но у меня не всегда работала их система с JSON поэтому я переопределил функцию проверки так:

class BaseHandler(tornado.web.RequestHandler):
    def get_current_user(self):  # реализуем свою систему аутентификации
        #return None

    def check_xsrf_cookie(self):  # эта переопределённая функция проверки xsrf токена
        token = (self.get_argument("_xsrf", None) or
                 self.request.headers.get("X-Xsrftoken") or
                 self.request.headers.get("X-Csrftoken"))
        if not token:
            raise tornado.web.HTTPError(403, "'_xsrf' argument missing from POST")
        else:
            try:
                token = tornado.escape.json_decode(token)
            except ValueError:
                token = token
        if self.xsrf_token != token:
            raise tornado.web.HTTPError(403, "XSRF cookie does not match POST argument")

После, для тех обработчиков для которых нужна аутентификация и проверка xsrf токена я применял наследование от BaseHandler.

---Грабля 2, она же главная. Это работа с асинхронностью, собственно понимание вопросав, связанных с асинхронной архитектурой и необходимо чтобы дальше продолжить читать материал. А проблема с пониманием того, как с помощью Tornado использовать фикши асинхронной архитектуры. Это будет цикл отдельных заметок, но сегодня я расскажу о самом простом способе - это переадресация задачи на другой сервер))) об этом говорилось на встрече iff. посвящённой асинхроному вебу. Почему так? - Это самый простой способ работы с блокирующими вызовами, и он прекрасно реализован в фраемворке.
Итак:

  • К нам приходит запрос
  • Что-то делаем, блокирующие вызовы и длительные вычисления реализовываем на другом сервере
  • Делаем средствами фреймворка асинхронный запрос

Пример (прям из документации):

class GenAsyncHandler(RequestHandler):
    @gen.coroutine  # с помощью декоратора объявляем
                    # метод асинхронным
    def get(self):
        http_client = AsyncHTTPClient()  # создаём асинхронный клиент
        response = yield http_client.fetch("http://example.com") # делаем неблокирующий запрос
                          #после этого обработка запроса прерывается и выполняются
                          #другие задачи из очереди
                          #когда сервер, выполняющий наши долгие вычисления или обращение к диску
                          #ответит, и торнадо получит результат
                          #обработка продолжится в порядке очереди
        do_something_with_response(response)
        self.render("template.html")  # завершаем обработку

Итак, теперь в принципе понятна архитектура Tornado и далее поговорим о том, как создавать собственные асинхронные обработчики средствами Tornado. Но это потом....