Кратко про классную библиотеку для создания веб-приложений в Python — Tornado

8977e583808b265eb4e1f6fcedc3fbbd.png

Привет, Хабр!

Сегодня мы рассмотрим библиотеку для создания веб-приложений на Python — Tornado.

Tornado был разработан в компании FriendFeed, которая позже была приобретена Facebook в 2009 году. Основная идея создания Tornado заключалась в высокой производительности и масштабируемости при обработке большого числа одновременных соединений. Этого удалось достичь с помощью асинхронности.

Установим Tornado:

pip install tornado

Основные компоненты Tornado

RequestHandler и обработка запросов

RequestHandler — это основной класс в Tornado для обработки HTTP-запросов. Каждый запрос обрабатывается экземпляром класса RequestHandler, который выполняет необходимые действия и формирует ответ.

Пример:

import tornado.ioloop
import tornado.web

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello, world")
    
    def post(self):
        name = self.get_argument('name')
        self.write(f"Hello, {name}")

def make_app():
    return tornado.web.Application([
        (r"/", MainHandler),
    ])

if __name__ == "__main__":
    app = make_app()
    app.listen(8888)
    tornado.ioloop.IOLoop.current().start()

MainHandler обрабатывает GET и POST запросы. Метод get возвращает строку «Hello, world», а метод post принимает аргумент name из тела запроса и возвращает персонализированное приветствие.

RequestHandler содержит множество полезных методов для работы с HTTP-запросами и формирования ответов:

  • get_argument(name, default=None, strip=True) — получает аргумент из строки запроса или тела запроса.

  • get_arguments(name, strip=True) — получает список аргументов.

  • write(chunk) — записывает данные в ответ.

  • render(template_name, **kwargs) — рендерит HTML-шаблон с переданными параметрами.

  • set_header(name, value) — устанавливает HTTP-заголовок.

  • set_status(status_code, reason=None) — устанавливает статус ответа.

Маршрутизация

Маршрутизация в Tornado настраивается с помощью класса Application, который связывает URL-шаблоны с соответствующими обработчиками.

Пример:

import tornado.ioloop
import tornado.web

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("This is the main page")

class AboutHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("This is the about page")

def make_app():
    return tornado.web.Application([
        (r"/", MainHandler),
        (r"/about", AboutHandler),
    ])

if __name__ == "__main__":
    app = make_app()
    app.listen(8888)
    tornado.ioloop.IOLoop.current().start()

Здесь определили два маршрута: корневой URL (»/») обрабатывается MainHandler, а URL »/about» обрабатывается AboutHandler.

Маршрутизация в Tornado определяется с помощью списка кортежей, где каждый кортеж содержит шаблон URL и соответствующий обработчик:

application = tornado.web.Application([
    (r"/", MainHandler),
    (r"/about", AboutHandler),
])

Шаблоны

Tornado поддерживает систему шаблонов для генерации динамических HTML-страниц. Шаблоны позволяют отделить логику приложения от представления.

Пример:

import tornado.ioloop
import tornado.web

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.render("index.html", title="Home Page", items=["Item 1", "Item 2", "Item 3"])

def make_app():
    return tornado.web.Application([
        (r"/", MainHandler),
    ], template_path="templates")

if __name__ == "__main__":
    app = make_app()
    app.listen(8888)
    tornado.ioloop.IOLoop.current().start()

Шаблон index.html:




    {{ title }}


    

{{ title }}

    {% for item in items %}
  • {{ item }}
  • {% end %}

Здесь MainHandler использует метод render для генерации HTML-ответа на основе шаблона index.html, передавая в него переменные title и items.

Tornado использует собственный язык шаблонов, который включает базовые конструкции для вставки данных, циклов и условных выражений. Шаблоны хранятся в отдельной директории, указанной в параметре template_path при создании приложения.

Асинхронное программирование в Tornado

Асинхронное программирование — это фича Tornado, позволяющая создавать масштабируемые веб-приложения. Tornado использует неблокирующий ввод-вывод и event loop для обработки большого количества одновременных соединений.

Асинхронное I/O позволяет обрабатывать другие задачи, пока ожидается завершение операций ввода-вывода. Tornado использует event loop, который непрерывно проверяет события и запускает соответствующие обработчики.

Пример с event loop:

import tornado.ioloop
import tornado.web

class MainHandler(tornado.web.RequestHandler):
    async def get(self):
        await self.some_async_function()
        self.write("Hello, world")

    async def some_async_function(self):
        await tornado.gen.sleep(1)

def make_app():
    return tornado.web.Application([
        (r"/", MainHandler),
    ])

if __name__ == "__main__":
    app = make_app()
    app.listen(8888)
    tornado.ioloop.IOLoop.current().start()

Здесь some_async_function выполняет асинхронную операцию sleep на одну секунду. Метод get ждет завершения этой функции перед отправкой ответа.

Корутины и future-объекты

Корутину можно рассматривать как функцию, которую можно приостановить и возобновить. В Tornado для работы с корутинами используется модуль tornado.gen:

import tornado.ioloop
import tornado.web
import tornado.gen
import asyncio

class MainHandler(tornado.web.RequestHandler):
    @tornado.gen.coroutine
    def get(self):
        result = yield self.some_async_function()
        self.write(f"Result: {result}")

    @tornado.gen.coroutine
    def some_async_function(self):
        yield tornado.gen.sleep(1)
        return "Async operation complete"

def make_app():
    return tornado.web.Application([
        (r"/", MainHandler),
    ])

if __name__ == "__main__":
    app = make_app()
    app.listen(8888)
    tornado.ioloop.IOLoop.current().start()

Используем tornado.gen.coroutine для определения корутины.some_async_function выполняет асинхронную операцию и возвращает результат.

Примеры асинхронных операций:

Асинхронные операции в Tornado включают работу с HTTP-запросами, БД и другими внешними ресурсами.

Асинхронный HTTP-запрос:

import tornado.ioloop
import tornado.web
import tornado.httpclient

class MainHandler(tornado.web.RequestHandler):
    async def get(self):
        http_client = tornado.httpclient.AsyncHTTPClient()
        response = await http_client.fetch("http://example.com")
        self.write(response.body)

def make_app():
    return tornado.web.Application([
        (r"/", MainHandler),
    ])

if __name__ == "__main__":
    app = make_app()
    app.listen(8888)
    tornado.ioloop.IOLoop.current().start()

Так можно выполнить асинхронный HTTP-запрос с использованием AsyncHTTPClient.

Асинхронная работа с БД:

import tornado.ioloop
import tornado.web
import aiomysql

class MainHandler(tornado.web.RequestHandler):
    async def get(self):
        pool = await aiomysql.create_pool(host='127.0.0.1', port=3306,
                                          user='root', password='',
                                          db='test', loop=tornado.ioloop.IOLoop.current().asyncio_loop)
        async with pool.acquire() as conn:
            async with conn.cursor() as cur:
                await cur.execute("SELECT 42;")
                result = await cur.fetchone()
        self.write(f"Result: {result[0]}")

def make_app():
    return tornado.web.Application([
        (r"/", MainHandler),
    ])

if __name__ == "__main__":
    app = make_app()
    app.listen(8888)
    tornado.ioloop.IOLoop.current().start()

Используем aiomysql для выполнения асинхронного запроса к MySQL базе данных.

Прочие возможности Tornado

WebSocket

Tornado имеет встроенную поддержку WebSocket. Пример:

import tornado.ioloop
import tornado.web
import tornado.websocket

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello, WebSocket")

class EchoWebSocket(tornado.websocket.WebSocketHandler):
    def open(self):
        print("WebSocket opened")

    def on_message(self, message):
        self.write_message(u"You said: " + message)

    def on_close(self):
        print("WebSocket closed")

def make_app():
    return tornado.web.Application([
        (r"/", MainHandler),
        (r"/websocket", EchoWebSocket),
    ])

if __name__ == "__main__":
    app = make_app()
    app.listen(8888)
    tornado.ioloop.IOLoop.current().start()

Создали простой WebSocket-сервер, который отправляет эхо-сообщения обратно клиенту. Класс EchoWebSocket наследуется от tornado.websocket.WebSocketHandler и переопределяет методы open, on_message и on_close для обработки событий WebSocket.

Интеграция с фреймворками для аутентификации и авторизации

Tornado поддерживает интеграцию с различными фреймворками для аутентификации и авторизации, включая OAuth и OpenID.

Пример с OAuth:

import tornado.ioloop
import tornado.web
import tornado.auth

class GoogleLoginHandler(tornado.web.RequestHandler, tornado.auth.GoogleOAuth2Mixin):
    async def get(self):
        if self.get_argument("code", False):
            user = await self.get_authenticated_user(
                redirect_uri='http://your.site.com/auth/google',
                code=self.get_argument("code"))
            self.set_secure_cookie("user", tornado.escape.json_encode(user))
            self.redirect("/")
        else:
            self.authorize_redirect(
                redirect_uri='http://your.site.com/auth/google',
                client_id=self.settings["google_oauth"]["key"],
                scope=['profile', 'email'],
                response_type='code',
                extra_params={'approval_prompt': 'auto'})

def make_app():
    return tornado.web.Application([
        (r"/auth/google", GoogleLoginHandler),
    ], google_oauth={"key": "YOUR_CLIENT_ID", "secret": "YOUR_CLIENT_SECRET"}, cookie_secret="YOUR_COOKIE_SECRET")

if __name__ == "__main__":
    app = make_app()
    app.listen(8888)
    tornado.ioloop.IOLoop.current().start()

Пользователь перенаправляется на страницу авторизации Google, а затем возвращается с токеном аутентификации.

Международная поддержка и локализация

Tornado предоставляет встроенные инструменты для поддержки интернационализации i18n и локализации.

Пример кода для загрузки и использования локалей:

import tornado.ioloop
import tornado.web
import tornado.locale

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        user_locale = self.get_user_locale()
        greeting = self.locale.translate("Hello")
        self.write(greeting)

def make_app():
    tornado.locale.load_translations("locale/")
    tornado.locale.set_default_locale('en_US')
    return tornado.web.Application([
        (r"/", MainHandler),
    ])

if __name__ == "__main__":
    app = make_app()
    app.listen(8888)
    tornado.ioloop.IOLoop.current().start()

Загружаем файлы переводов из директории locale, а затем используем метод translate для получения переведенной строки на основе текущей локали пользователя.

В заключение напоминаю об открытых уроках курса «Python Developer. Professional», которые скоро пройдут в Otus:

  • 3 июля: Tabula rasa Python проекта.Рассмотрим best practices по настройке окружения для разработки свежего питонячьего проекта. Поговорим про всевозможные инструменты и автоматизации, которые могут применяться в таком случае. Запись по ссылке

  • 16 июля: Введение в Django REST API. После завершения вебинара вы научитесь пользоваться Views и Middleware в процессе обработки запросов и ответов, а также разрабатывать RESTful API с помощью Django REST Framework. Запись по ссылке

Habrahabr.ru прочитано 2104 раза