Tornado 【简述】

摘要:
前言在巨蟒的旗帜下,有许多英雄。仅对于web开发,就有webpy、web2py、bottom、金字塔、zope2、flask、tornado、django等。近年来,flask、tornado和django更受欢迎。关于上述web开发框架的性能比较,互联网搜索是压倒性的——这不是本文的重点,但我想提醒您,在上述模块中,只有Torn

前言

  • python 旗下,群英荟萃,豪杰并起。单是用于 web 开发的,就有 webpy、web2py、bottle、pyramid、zope2、flask、tornado、django 等等,不一而足。最近几年较为流行的,大概也就是flask、tornado 和 django 了。
  • 关于以上各个 web 开发框架的性能比较,上网一搜,铺天盖地——这不是本文讨论的重点,但有一点我想提醒大家:在以上众多模块中,只有 tornado 同时具备异步IO、webserver、web框架三大功能,同时又是最简洁的、学习门槛最低的。有 python 语言基础的程序员,只需要花点时间就可以登堂入室了。
  • 如果把 web 开发框架比作程序员手中的冷兵器,我觉得 flask 好比是花枪, 轻灵飘逸,舞之令人眼花缭乱;django 像大戟,合矛戈为一体,可直刺,可横击,威力无比;tornado 秀外而惠中,更像是剑。剑在中国传统武术中有着很高的地位,为兵器之神,被认为有君子之风。

从 hello world 开始

如果你的 python 环境还没有安装 tornado,请直接使用 pip 安装:

pip install tornado
  • 下面的代码,虽然只有区区六行(不包括导入模块的两行),却是一个完整的 web 服务程序。运行下面的代码,就开启了一个 web 服务,从本机浏览器直接访问 http://127.0.0.1,不出意外的话,我们的第一个网页 hello, world 即可正常显示出来。

demo.py

# -*- coding: utf-8 -*-

import tornado.ioloop 
import tornado.web

class HomeHandler(tornado.web.RequestHandler): 
def get(self): # 响应以get方式发送的请求
self.write("hello, world") # 向请求者(浏览器)应答hello, world

app = tornado.web.Application([ (r"/", HomeHandler), ]) # URL映射
app.listen(80) # 绑定侦听端口
tornado.ioloop.IOLoop.instance().start() # 启动服务

如果多少了解一点 http 协议,知道 get / post 方法,相信你一定能够读懂。也许你的项目规划了很多的url,也许你的服务需要监听非80端口,没有关系,在这个代码上扩展就行。仅仅六行!!!请让我们向犀利的、简洁的、无所不能的 python 致敬!

重点:tornado.web.RequestHandler.write() 不只可以接受字符串参数,还可以接受列表或字典参数——如果应答类型为json时,这个重载特性非常高效

最简单的登录

假定我们有这样一个 web 服务需求:

  • 首页:地址“/”,显示“点此登录”两个汉字,点击则跳转到登录页面
  • 登录页:地址“/login”,以 get 方式访问,则显示账号、密码输入框和登录按钮;以 post 方式访问,则是提交表单提交,验证登录信息。登录成功,跳转至个人信息页面,否则,跳转至首页
  • 个人信息页:地址“/me”,显示登录账号

以上面的代码为基础,我们首先要做的工作是 URL 和 对应的处理类之间的关联。这件工作实际上是非常轻松愉快的:

app = tornado.web.Application([
(r"/", HomeHandler), 
(r"/login", LoginHandler), 
(r"/me", MeHandler)
])

接下来,我们要实现 HomeHandler、LoginHandler 和 MeHandler 这三个类了。通常,我们习惯把这些和URL 对应的处理类,保存为一个独立的文件,比如文件名为 handlers.py,然后在服务器脚本 demo.py 中导入它们。

handlers.py

# -*- coding: utf-8 -*-

import tornado.web

class HomeHandler(tornado.web.RequestHandler): 
"""响应主页请求"""

def get(self): # 以get方式请求
self.write("""<!DOCTYPE html><html><body><a href="http://t.zoukankan.com/login">点此登录</a></body></html>""")

class LoginHandler(tornado.web.RequestHandler): 
"""响应登录页请求"""

def get(self): # 以get方式请求
self.write(
"""
<!DOCTYPE html><html><body><form method="POST" action="/login">
账号:<input type="text" name="account" value="" /><br />
密码:<input type="password" name="passwd" value="" />
<input type="submit" value="确定" />
</form></body></html>
"""
)

def post(self): # 以post方式请求(本例为提交表单)
account = self.get_argument('account', None)
passwd = self.get_argument('passwd', None)

if account == 'xufive' and passwd == 'dgdgwstd':
self.redirect('/me?name=%s'%account)
else:
self.redirect('/')

class MeHandler(tornado.web.RequestHandler): 
"""响应个人信息页请求"""

def get(self): # 以get方式请求
name = self.get_argument('name', None)
if name:
self.write(
"""
<!DOCTYPE html><html><head><meta charset="UTF-8" /></head>
<body>欢迎你来到这里,%s</body></html>
"""%name
)
else:
self.redirect('/')

相应地,服务脚本变成了这样:

demo.py

# -*- coding: utf-8 -*-

import os
import tornado.ioloop 
import tornado.web
from tornado.options import parse_command_line

from handlers import *

parse_command_line()
app = tornado.web.Application(
handlers=[
(r"/", HomeHandler), 
(r"/login", LoginHandler), 
(r"/me", MeHandler)
],
template_path = os.path.join(os.path.dirname(__file__), 'templates')
)
app.listen(80) # 绑定侦听端口
tornado.ioloop.IOLoop.instance().start() # 启动服务

重点:tornado.web.RequestHandler.get_argument() 可以读取通过表单和QueryString传递的参数

模板技术

读到这里,你一定会觉得奇怪:为什么服务端程序里面混杂了一大堆的 html 代码?Don’t worry,以上的代码仅仅是帮助你建立基本概念的,实际上,tornado 是为数不多的支持模板技术很到位的框架之一,其模板技术不仅支持继承,支持子模版。让我们一步一步讨论如何使用模板。

第1步:模板保存在哪儿?

  • 在服务端脚本里,当我们使用 tornado.web.Application() 创建一个应用时,通常需要传递一个 template_path 参数,这个参数就是模板文件的保存路径。上面的例子已经增加了这个参数,我们只要把模板文件放在和 demo.py 同级的 templates 文件夹下就可以了。

第2步:怎样写模板?

  • 其实,模板就是 html 文件,只是其中混杂了少量特别约定的符号。一个 web 项目,通常由若干页面组成,这些页面有很多共同的地方,因此一个基类模板是必要的。

base.html

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<!-- 此处省略各种样式表文件 -->
</head>
<body>
{% block block_body %}{% end %}
</body>
<html>

基类模板 base.html 定义了一个 block_body 容器,如果有必要,我们在基类模板的任意位置定义更多的容器。假定我们需要一个个人信息页模板,可以直接继承 base.html,然后只填写 block_body 这一部分就行了。

me.html

{% extends "base.html" %}
{% block block_body %}
<h1>欢迎你来到这里,{{name}}</h1>
{% end %}

个人信息页模板引中,我们使用 {{}} 引用了一个变量 name

第3步:如何使用模板?

  • 很简单,前面我们用 tornado.web.RequestHandler.write() 向浏览器应答信息,现在则是这样使用模板:
class MeHandler(tornado.web.RequestHandler): 
"""响应个人信息页请求"""

def get(self): # 以get方式请求
name = self.get_argument('name', None)
if name:
self.render('me.html', name=name )
else:
self.redirect('/')

常用的模板语法汇总如下:

  • 引用变量:{{…}}
  • 引用 python 表达式:{%…%}
  • 循环:{% for var in expr %}…{% end %}
  • 分支:{% if condition %}…{% elif condition %}…{% else %}…{% end %}
  • 引用原生表达式:{% raw expr %}

Cookie 演练

tornado.web.RequestHandler 的 cookie 操作非常灵活,下面的 handler 展示了 cookie 的基本读写方法:

class CookieHandler(tornado.web.RequestHandler): 
def get(self): 
visit_num = self.get_cookie('visit_num')
if not visit_num:
visit_num = '0'
visit_num = str(int(visit_num)+1)
#self.set_cookie('visit_num', visit_num, expires=None) # 内存cookie
self.set_cookie('visit_num', visit_num, expires=time.time()+1000) # 持久化的cookie
self.write("这是您第%s次访问本页面"%visit_num) 

如果我们要使用持久化的 Cookie(硬盘 Cookie),为了防止被破解,一般是要加密的,那么,在 tornado.web.Application 中需要设置 cookie_secret 项(加密因子)。

定义tornado.web.Application,这是常用的一个模式:

class Application(tornado.web.Application):
def __init__(self):
handlers = [
(r"/", WelcomeHandler), # 欢迎信息
(r"/server_time",ServerTimeHandler) # 显示服务器时间
]

settings = dict(
title = u"网站名称",
template_path = os.path.join(os.path.dirname(__file__), 'templates'),
static_path = os.path.join(os.path.dirname(__file__), 'static'),
cookie_secret = 'rewqr4gfd654fdsg@$%34dfs',
session_expiry = 0,
login_url = "/",
debug = 1
)

tornado.web.Application.__init__(self, handlers, **settings)

Session 扩展

  • 为 tornado 增加 session 机制,基本思路就是从 tornado.web.RequestHandler 派生新类,重写 initialize() 方法。当类实例被构造函数创建后,会先运行该方法。我们定义 initialize() 方法读取名为 session_id 的 cookie,如果存在,则读取以 session_id 命名的 session 文件,取得 session 内容,否则,session 为空。
  • tornado 是一个非常流行的 web framework,也是一个自带 IO 的 web server,而它作为 web server 采用的是 asynchronous IO 的网络模型,这是一种很高效的模型。网上有很多 tornado 与其他 web 框架的性能比较,其实,它们根本不是一个层次的东西,因为在所有的比较对象中,只有 tornado 同时具备 IO / web server / web framework,并且做到了极致。
  • 开发 web 项目,我一般会首选 tornado 。喜欢 tornado,不是因为它全能,而是因为它简洁。简洁到什么程度呢?作为 web 框架,它连 session 都没有。但是,正是这种简洁,降低了学习成本,给用户提供了无限的开发空间。
  • 为 tornado 增加 session 机制,基本思路就是从 tornado.web.RequestHandler 派生新类,重写 initialize() 方法。当类实例被构造函数创建后,会先运行该方法。我们定义 initialize() 方法读取名为 session_id 的 cookie,如果存在,则读取以 session_id 命名的 session 文件,取得 session 内容,否则,session 为空。

这段代码在 py2 环境 tornado 2.x 以后的各种版本下都可运行。

# -*- coding: utf-8 -*-


# Tornado Session For py2
#---------------------------------------

# version 3.2 / Update: 2018-12-24
#
# 1. 重写get_current_user方法,读取session_id

# version 3.1 / Update: 2018-12-21
#
# 1. 以文件作为session的后端
# 2. 私有方法命名规范化

# version 3.0 / Update: 2018-12-19
#
# 1. 以cookie形式实现session
# 2. 用装饰器引入db
# 3. 类方法命名规范化(不兼容之前的版本)


import os, time
import hashlib
import json
import tornado.web


class SessionRH(tornado.web.RequestHandler):
"""为RequestHandler增加session机制"""

def initialize(self):
"""初始化(构造函数完成之后)"""

self._init_session()

def _init_session(self):
"""session初始化"""

self.session = dict()
self.session_id = self.get_secure_cookie('session_id')
if self.session_id:
session_file = os.path.join(self.application.settings["session_path"], self.session_id)
if os.path.isfile(session_file):
with open(session_file, 'r') as fp:
self.session.update(json.loads(fp.read().strip()))

if not self.session:
self.session_id = None
self.clear_cookie('session_id')

def _save_session(self):
"""保存session"""

try:
expiry = self.application.settings["session_expiry"]
except:
expiry = 0

if not expiry:
expiry = None

self.set_secure_cookie('session_id', self.session_id, expires_days=expiry)
with open(os.path.join(self.application.settings["session_path"], self.session_id), 'w') as fp:
fp.write(json.dumps(self.session))

def create_session(self, **kwargs):
"""生成唯一的session_id及其它需要保存的数据项"""

rand = os.urandom(16)
now = time.time()
self.session_id = hashlib.sha1("%s%s%s" %(rand, now, self.request.remote_ip)).hexdigest()
self.session.update(kwargs)
self.session.update({'last_active':str(now)})
self._save_session()

def destroy_session(self):
"""注销session"""

session_file = os.path.join(self.application.settings["session_path"], self.session_id)
if os.path.isfile(session_file):
os.remove(session_file)

self.session_id = None
self.session = dict()
self.clear_cookie('session_id')

def append_session_item(self, **kwargs):
"""追加session项"""

self.session.update(kwargs)
self._save_session()

def delete_session_item(self, name):
"""删除session项"""

if name in self.session:
self.session.pop(name)
self._save_session()

def get_current_user(self):
"""重写get_current_user方法,读取session_id"""

return self.get_secure_cookie("session_id")

@property
def db(self):
"""用装饰器引入db(application.db)"""

return self.application.db
if __name__ == '__main__':
  pass

免责声明:文章转载自《Tornado 【简述】》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇MyBatis(缓存机制)几种实现延时任务的方式下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

5分钟APIG实战: 使用Rust语言快速构建API能力开放

序言:Rust语言简介 参与过C/C++大型项目的同学可能都经历过因为Null Pointer、Memory Leak等问题“被” 加班了不知道多少个晚上。别沮丧,你不是一个人,Mozilla Firefox的开发者们同样经历过这个问题。浏览器可以说是我们日常使用最为频繁的软件了,目前主流的浏览器主要有Google Chrome、Internet Expl...

web html调用百度地图

如果想在自己的网页上面加入百度地图的话,可以用百度地图的api.具体使用方法如下: 第一步:进入百度创建地图的网站http://api.map.baidu.com/lbsapi/creatmap/,搜索出自己要展示的位置,如下图所示。 第二步:设置地图,大家可以对网站显示地图的宽高进行设置,其余选项不动。 第三步:添加标注。点击第一个图标后,在右侧找到自己...

nodejs什么值得买自动签到自动评论定时任务

本项目是基于nodejs开发,实现的功能是,什么值得买自动签到,自动评论功能,自动发邮件,支持多人多账号运行 目的是为了,解放双手,轻松获取什么值得买的经验和积分,得到更高的等级,从而突破很会员等级限制,如领取部分紧俏的优惠券 项目运行即执行签到和评论 目前规则:每天6:10 执行签到和评论功能,17:30执行签到结果邮件发送功能,执行间隔为随机时间,可...

.net 多线程的使用(Thread)

 上篇 net 同步异步   中篇 多线程的使用(Thread)   下篇 net 任务工厂实现异步多线程 Thread多线程概述  上一篇我们介绍了net 的同步与异步,我们异步演示的时候使用的是委托多线程来实现的。今天我们来细细的剖析下 多线程。 多线程的优点:可以同时完成多个任务;可以使程序的响应速度更快;可以让占用大量处理时间的任务或当前没有进...

《Java从入门到放弃》入门篇:hibernate基本配置

hibernate是个什么玩意呢?简单点说,就是别人写好的一套访问数据库的东东,底层其实就是JDBC。 使用JDBC就像以前没有电饭煲,我们要自己根据经验,多少人煮多少米,放多少水,用多大的火,煮多长时间,整个过程都需要自己来把控,如果把控得不好,运气好只是把饭煮成粥,运气不好就是一锅炭了。 而使用hibernate就跟使用电饭煲一样,根据刻度放水和米,...

ExtJS初接触 —— 了解 Ext Core

ExtJS初接触 —— 了解 Ext Core Ext Core是一款和jQuery媲美的轻型JS库,基于MIT许可。对于Dom的操作,我个人还是比较喜欢用jQuery。当然如果项目中用的是ExtJS框架,也就没必要多引用一个jQuery,Ext Core是ExtJS框架的一个小子集。如果仅仅使用Ext Core的功能,则只需引入builds文件夹下的一个...