Привет, Хабр!
Сегодня мы рассмотрим библиотеку для создания веб-приложений на 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
:
<!DOCTYPE html>
<html>
<head>
<title>{{ title }}</title>
</head>
<body>
<h1>{{ title }}</h1>
<ul>
{% for item in items %}
<li>{{ item }}</li>
{% end %}
</ul>
</body>
</html>
Здесь 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. Запись по ссылке
Комментарии (3)
beduin01
01.07.2024 21:45+1Попались курсы OTUS. Был удивлён что эти люди хотят получать деньги за некачественный контент, в то время как на Youtube полно материалов уровнем в разы выше и бесплатно.
soymiguel
Пост ради поста? Ладно бы про Twisted еще, но эту стюардессу к чему откапывать в 2024? Какой тут магией очароваться при наличии FastAPI?
Matshishkapeu
Да, вроде, вполне живая стюардесса, просто не в отдельно стоящем виде. А вот вполне популярный для всякой визуализации данных streamlit под капотом та самая торнада.