tornado

Posted by neverset on September 5, 2020

Tornado is a lightweight web application framework with python based on MVC(Model-View-Controller) model. Tornado is specifically built to handle high parallel asynchronous processes. By using non-blocking network IO, it can easily handle tens of thousands of connections, which makes it ideal for long polling, WebSockets, and applications that need to maintain a connection for each user

Tornado also implements the HTTP protocol client (AsyncHTTPClient) and server (HTTPServer), which can be used to initiate and receive HTTP requests

user case:

  • high concurrency (such as ticket system)
  • large scale http persistent connections (using one TCP for multiple HTTP requests such as chat app)

components

one ioloop has multi apps , one app has one route , one route has multi handler.

ioloop

global event loop for tornado

app

backend app will hook up a server socket port to provide services externally

handler

business logic to handle request from client

route

map url to handler

simple example

#core io loop module, it encapsulates linux epoll
import tornado.ioloop
#basic web framework
import tornado.web


class FactorialService(object):

    def __init__(self):
        self.cache = {}

    def calc(self, n):
        if n in self.cache:
            return self.cache[n]
        s = 1
        for i in range(1, n):
            s *= i
        self.cache[n] = s
        return s

#same as view class
class FactorialHandler(tornado.web.RequestHandler):
    service = FactorialService()
    #override default method
    def get(self):
        n = int(self.get_argument("n"))
        #use write method to return value
        self.write(str(self.service.calc(n)))

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

if __name__ == "__main__":
    app = make_app()
    #use listen method to create a http server with specified port
    app.listen(8888)
    #instanze an IOLoop in current thread und start ioloop
    tornado.ioloop.IOLoop.current().start()

usage

tornado.option.define

used to define option variables, the variable will be defined in config file or command line

tornado.options.options

used to used the defined option variable in functions

tornado.opitions.parse_command_line

parse command line paramenter into option variable

tornado.opitions.parse_config_file(path)

import parameter from config into variable

tornado.options.options.logging

activate or deactivate logging in console

get_query_argument

get query parameter from router

example in MVC

Tornado’s views are class-based, the incoming HTTP request will be caught and assigned to be an attribute of our defined class

view

the entry of web application, define route and log

#app.py
import logging.config
import os

import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web

from . import config
from .account import handler as account_handler

app = tornado.web.Application([
    (r'/register', account_handler.RegisterUserHandler, None, 'register'),
    (r'/login', account_handler.LoginHandler, None, 'login'),
    (r'/isLogined', account_handler.IsLoginedHandler),
    (r'/logout', account_handler.LogoutHandler, None, 'logout'),
    (r'/account/edit', account_handler.EditUserHandler),
    (r'/account/info', account_handler.AccountInfoHandler),
], **config.TORNADO['settings'], session=config.SESSION)

if __name__ == '__main__':
    if not os.path.exists(config.PATH_LOG):
        os.makedirs(config.PATH_LOG)

    if not os.path.exists(config.PATH_UPLOAD):
        os.makedirs(config.PATH_UPLOAD)

    logging.config.dictConfig(config.LOGGING)

    server = tornado.httpserver.HTTPServer(app, xheaders=True)
    if config.DEBUG:
        server.listen(config.TORNADO['server']['port'])
    else:
        server.bind(config.TORNADO['server']['port'])
        server.start(config.TORNADO['server']['numprocs'])
    tornado.ioloop.IOLoop.current().start()

#conf.py
import os
DEBUG = os.environ.get('DEBUG', 'true').lower() in ('true', 'yes', 'y', '1')

PATH_APP = os.environ.get('PATH_APP', os.path.normpath(
    os.path.join(os.path.dirname(__file__), '../..')))
PATH_DATA = os.environ.get('PATH_DATA', '/tmp')
PATH_LOG = os.path.join(PATH_DATA, 'log')
PATH_UPLOAD = os.path.join(PATH_DATA, 'upload')

LOGGING_LOGGER_LEVEL = os.environ.get('LOGGING_LOGGER_LEVEL', 'DEBUG')

TORNADO_SERVER_PORT = int(os.environ.get('TORNADO_SERVER_PORT', '8888'))
TORNADO_SERVER_NUMPROCS = int(os.environ.get('TORNADO_SERVER_NUMPROCS', '0'))

SESSION_COOKIE_SECRET = os.environ.get(
    'SESSION_COOKIE_SECRET', '4zi7D1)uw6VJ&Iz5@924y28Z@3@M3p!H')
SESSION_EXPIRES_SECONDS = int(os.environ.get('SESSION_EXPIRES_SECONDS',
                                            '86400'))

MONGODB_HOST = os.environ.get('MONGODB_HOST', 'localhost')
MONGODB_PORT = int(os.environ.get('MONGODB_PORT', '27017'))
MONGODB_NAME = os.environ.get('MONGODB_NAME', 'jw_tornado_demo')

REDIS_HOST = os.environ.get('REDIS_HOST', 'localhost')
REDIS_PORT = int(os.environ.get('REDIS_PORT', '6379'))
REDIS_DB = int(os.environ.get('REDIS_DB', '0')) ### model  modul to implement functions such as request log, session management

import json
import logging
import re
import traceback
from datetime import datetime
import torndsession.sessionhandler
from . import error as err
from . import cache, vo
from .. import config
class Handler(torndsession.sessionhandler.SessionBaseHandler):
    def prepare(self):
        super().prepare()

        logger = logging.getLogger('app')
        logger.debug('{} {} {} {}'.format(
            self.request.method, self.request.path,
            self.request.arguments, self.request.headers))

    def get_current_user(self):
        return self.session.get('user', None)

    def write_error(self, status_code, **kwargs):
        if self.settings.get('serve_traceback') and 'exc_info' in kwargs:
            message = traceback.format_exception(*kwargs['exc_info'])
        else:
            message = self._reason
        error = err.Error(message)

        return self.response_json(error, status_code)

    def response_json(self, error=None, status_code=200, **kwargs):
        data = {
            'code': err.ERROR_CODE_OK,
            'message': ''
        }
        if error:
            data['code'] = error.code
            data['message'] = (
                error.message if (config.DEBUG and error.message) else
                err.MESSAGES.get(error.code, '')
            )
        data.update(kwargs)

        ua = self.request.headers.get('User-Agent', '')
        if re.match(r'.+\s+MSIE\s+.+', ua):
            content_type = 'text/html; charset=utf-8'
        else:
            content_type = 'application/json; charset=utf-8'
        content = json.dumps(
            vo.jsonable(data),
            indent=(None if not config.DEBUG else 4),
            ensure_ascii=False)
        self.response(content, content_type, status_code)

    def response_html(self, template, error=None, status_code=200, **kwargs):
        data = {
            'code': err.ERROR_CODE_OK,
            'message': ''
        }
        if error:
            data['code'] = error.code
            data['message'] = (
                error.message if (config.DEBUG and error.message) else
                err.MESSAGES.get(error.code, '')
            )
        data.update(kwargs)

        content = self.render_string(template, **data)
        content_type = 'text/html; charset=utf-8'
        self.response(content, content_type, status_code)

    def response(self, content, content_type, status_code=200):
        self.set_status(status_code)
        self.set_header('Content-Type', content_type)
        self.finish(content)

asynchronous

from tornado.gen import coroutine
#decorator to assign asynchronous property
@coroutine

comparison

Flask: Meant for small, simple projects; makes it easy for us to construct views and connect them to routes quickly; can be encapsulated in a single file without much fuss

Pyramid: Meant for projects that may grow; contains a fair bit of configuration to get up and running; separate realms of application components can easily be divided and built out to arbitrary depth without losing sight of the central application

Tornado: Meant for projects benefiting from precise and deliberate I/O control; allows for co-routines and easily exposes methods that can control how requests are received/responses are sent and when those operations occur

Django: meant for big things that may get bigger; large ecosystem of add-ons and mods; very opinionated in its configuration and management in order to keep all the disparate parts in line; famous for its backend management systems, with ORM it can automatically generate database structure and backend with full functionality