Briefly about a cool library for creating web applications in Python — Tornado
Let's install Tornado:
pip install tornado
Main components of Tornado
RequestHandler and request processing
RequestHandler
— is the main class in Tornado for handling HTTP requests. Each request is handled by an instance of the class RequestHandler
which performs the necessary actions and generates a response.
Example:
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
processes GET and POST requests. Method get
returns the string “Hello, world”, and the method post
takes an argument name
from the request body and returns a personalized greeting.
RequestHandler
contains many useful methods for working with HTTP requests and generating responses:
get_argument(name, default=None, strip=True)
— gets an argument from the query string or request body.get_arguments(name, strip=True)
— receives a list of arguments.write(chunk)
– writes data in response.render(template_name, **kwargs)
— renders an HTML template with the passed parameters.set_header(name, value)
— sets the HTTP header.set_status(status_code, reason=None)
— sets the response status.
Routing
Routing in Tornado is configured using a class Application
which associates URL patterns with the corresponding handlers.
Example:
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()
Here two routes are defined: the root URL (“/”) is processed MainHandler
and the URL “/about” is processed AboutHandler
.
Routing in Tornado is defined using a list of tuples, where each tuple contains a URL pattern and a corresponding handler:
application = tornado.web.Application([
(r"/", MainHandler),
(r"/about", AboutHandler),
])
Templates
Tornado supports a templating system for generating dynamic HTML pages. Templates allow you to separate the application logic from the presentation.
Example:
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()
Sample 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>
Here MainHandler
uses the method render
to generate an HTML response based on a template index.html
passing variables into it title
And items
.
Tornado uses its own template language, which includes basic constructs for data insertion, loops, and conditionals. Templates are stored in a separate directory specified in the parameter template_path
when creating an application.
Asynchronous Programming in Tornado
Asynchronous programming is a feature of Tornado that allows you to create scalable web applications. Tornado uses non-blocking I/O and an event loop to handle large numbers of simultaneous connections.
Asynchronous I/O allows other tasks to be processed while waiting for I/O operations to complete. Tornado uses an event loop that continuously checks for events and runs the appropriate handlers.
Example with 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()
Here some_async_function
performs an asynchronous operation sleep
for one second. Method get
waits for this function to complete before sending a response.
Coroutines and Futures
A coroutine can be thought of as a function that can be paused and resumed. Tornado uses the module to work with coroutines. 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()
We use tornado.gen.coroutine
to define coroutines.some_async_function
performs an asynchronous operation and returns the result.
Examples of asynchronous operations:
Asynchronous operations in Tornado include working with HTTP requests, databases, and other external resources.
Asynchronous HTTP request:
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()
This is how you can make an asynchronous HTTP request using AsyncHTTPClient
.
Asynchronous work with the database:
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()
We use aiomysql
to perform an asynchronous query to a MySQL database.
Other Tornado Features
WebSocket
Tornado has built-in WebSocket support. Example:
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()
Created a simple WebSocket server that sends echo messages back to the client. Class EchoWebSocket
inherited from tornado.websocket.WebSocketHandler
and overrides methods open
, on_message
And on_close
to handle WebSocket events.
Integration with authentication and authorization frameworks
Tornado supports integration with various authentication and authorization frameworks, including OAuth and OpenID.
Example with 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()
The user is redirected to the Google login page and then returned with an authentication token.
International support and localization
Tornado provides built-in tools to support i18n internationalization and localization.
Example code for loading and using locales:
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()
Loading translation files from the directory locale
and then we use the method translate
to get a translated string based on the user's current locale.
In conclusion, I would like to remind you about the open lessons of the course “Python Developer. Professional”, which will soon be held in Otus:
July 3: Tabula rasa Python project. Let's look at best practices for setting up an environment for developing a fresh Python project. Let's talk about all sorts of tools and automation that can be used in such a case. Registration via link
July 16: Introduction to the Django REST API. After completing the webinar, you will learn how to use Views and Middleware to process requests and responses, as well as develop RESTful APIs using the Django REST Framework. Registration via link