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