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 RequestHandlerwhich 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 Applicationwhich 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 MainHandlerand 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.htmlpassing 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 localeand 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

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *