前言

  • 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 安装:

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

demo.py

  1. # -*- coding: utf-8 -*-
  2.  
  3. import tornado.ioloop
  4. import tornado.web
  5.  
  6. class HomeHandler(tornado.web.RequestHandler):
  7. def get(self): # 响应以get方式发送的请求
  8. self.write("hello, world") # 向请求者(浏览器)应答hello, world
  9.  
  10. app = tornado.web.Application([ (r"/", HomeHandler), ]) # URL映射
  11. app.listen(80) # 绑定侦听端口
  12. tornado.ioloop.IOLoop.instance().start() # 启动服务

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

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

最简单的登录

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

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

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

  1. app = tornado.web.Application([
  2. (r"/", HomeHandler),
  3. (r"/login", LoginHandler),
  4. (r"/me", MeHandler)
  5. ])

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

handlers.py

  1. # -*- coding: utf-8 -*-
  2.  
  3. import tornado.web
  4.  
  5. class HomeHandler(tornado.web.RequestHandler):
  6. """响应主页请求"""
  7.  
  8. def get(self): # 以get方式请求
  9. self.write("""<!DOCTYPE html><html><body><a href="login">点此登录</a></body></html>""")
  10.  
  11. class LoginHandler(tornado.web.RequestHandler):
  12. """响应登录页请求"""
  13.  
  14. def get(self): # 以get方式请求
  15. self.write(
  16. """
  17. <!DOCTYPE html><html><body><form method="POST" action="/login">
  18. 账号:<input type="text" name="account" value="" /><br />
  19. 密码:<input type="password" name="passwd" value="" />
  20. <input type="submit" value="确定" />
  21. </form></body></html>
  22. """
  23. )
  24.  
  25. def post(self): # 以post方式请求(本例为提交表单)
  26. account = self.get_argument('account', None)
  27. passwd = self.get_argument('passwd', None)
  28.  
  29. if account == 'xufive' and passwd == 'dgdgwstd':
  30. self.redirect('/me?name=%s'%account)
  31. else:
  32. self.redirect('/')
  33.  
  34. class MeHandler(tornado.web.RequestHandler):
  35. """响应个人信息页请求"""
  36.  
  37. def get(self): # 以get方式请求
  38. name = self.get_argument('name', None)
  39. if name:
  40. self.write(
  41. """
  42. <!DOCTYPE html><html><head><meta charset="UTF-8" /></head>
  43. <body>欢迎你来到这里,%s</body></html>
  44. """%name
  45. )
  46. else:
  47. self.redirect('/')

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

demo.py

  1. # -*- coding: utf-8 -*-
  2.  
  3. import os
  4. import tornado.ioloop
  5. import tornado.web
  6. from tornado.options import parse_command_line
  7.  
  8. from handlers import *
  9.  
  10. parse_command_line()
  11. app = tornado.web.Application(
  12. handlers=[
  13. (r"/", HomeHandler),
  14. (r"/login", LoginHandler),
  15. (r"/me", MeHandler)
  16. ],
  17. template_path = os.path.join(os.path.dirname(__file__), 'templates')
  18. )
  19. app.listen(80) # 绑定侦听端口
  20. 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

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="utf-8">
  5. <!-- 此处省略各种样式表文件 -->
  6. </head>
  7. <body>
  8. {% block block_body %}{% end %}
  9. </body>
  10. <html>

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

me.html

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

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

第3步:如何使用模板?

  • 很简单,前面我们用 tornado.web.RequestHandler.write() 向浏览器应答信息,现在则是这样使用模板:
  1. class MeHandler(tornado.web.RequestHandler):
  2. """响应个人信息页请求"""
  3.  
  4. def get(self): # 以get方式请求
  5. name = self.get_argument('name', None)
  6. if name:
  7. self.render('me.html', name=name )
  8. else:
  9. self.redirect('/')

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

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

Cookie 演练

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

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

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

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

  1. class Application(tornado.web.Application):
  2. def __init__(self):
  3. handlers = [
  4. (r"/", WelcomeHandler), # 欢迎信息
  5. (r"/server_time",ServerTimeHandler) # 显示服务器时间
  6. ]
  7.  
  8. settings = dict(
  9. title = u"网站名称",
  10. template_path = os.path.join(os.path.dirname(__file__), 'templates'),
  11. static_path = os.path.join(os.path.dirname(__file__), 'static'),
  12. cookie_secret = 'rewqr4gfd654fdsg@$%34dfs',
  13. session_expiry = 0,
  14. login_url = "/",
  15. debug = 1
  16. )
  17.  
  18. 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 以后的各种版本下都可运行。

  1. # -*- coding: utf-8 -*-
  2.  
  3. # Tornado Session For py2
  4. #---------------------------------------
  5.  
  6. # version 3.2 / Update: 2018-12-24
  7. #
  8. # 1. 重写get_current_user方法,读取session_id
  9.  
  10. # version 3.1 / Update: 2018-12-21
  11. #
  12. # 1. 以文件作为session的后端
  13. # 2. 私有方法命名规范化
  14.  
  15. # version 3.0 / Update: 2018-12-19
  16. #
  17. # 1. 以cookie形式实现session
  18. # 2. 用装饰器引入db
  19. # 3. 类方法命名规范化(不兼容之前的版本)
  20.  
  21. import os, time
  22. import hashlib
  23. import json
  24. import tornado.web
  25.  
  26. class SessionRH(tornado.web.RequestHandler):
  27. """为RequestHandler增加session机制"""
  28.  
  29. def initialize(self):
  30. """初始化(构造函数完成之后)"""
  31.  
  32. self._init_session()
  33.  
  34. def _init_session(self):
  35. """session初始化"""
  36.  
  37. self.session = dict()
  38. self.session_id = self.get_secure_cookie('session_id')
  39. if self.session_id:
  40. session_file = os.path.join(self.application.settings["session_path"], self.session_id)
  41. if os.path.isfile(session_file):
  42. with open(session_file, 'r') as fp:
  43. self.session.update(json.loads(fp.read().strip()))
  44.  
  45. if not self.session:
  46. self.session_id = None
  47. self.clear_cookie('session_id')
  48.  
  49. def _save_session(self):
  50. """保存session"""
  51.  
  52. try:
  53. expiry = self.application.settings["session_expiry"]
  54. except:
  55. expiry = 0
  56.  
  57. if not expiry:
  58. expiry = None
  59.  
  60. self.set_secure_cookie('session_id', self.session_id, expires_days=expiry)
  61. with open(os.path.join(self.application.settings["session_path"], self.session_id), 'w') as fp:
  62. fp.write(json.dumps(self.session))
  63.  
  64. def create_session(self, **kwargs):
  65. """生成唯一的session_id及其它需要保存的数据项"""
  66.  
  67. rand = os.urandom(16)
  68. now = time.time()
  69. self.session_id = hashlib.sha1("%s%s%s" %(rand, now, self.request.remote_ip)).hexdigest()
  70. self.session.update(kwargs)
  71. self.session.update({'last_active':str(now)})
  72. self._save_session()
  73.  
  74. def destroy_session(self):
  75. """注销session"""
  76.  
  77. session_file = os.path.join(self.application.settings["session_path"], self.session_id)
  78. if os.path.isfile(session_file):
  79. os.remove(session_file)
  80.  
  81. self.session_id = None
  82. self.session = dict()
  83. self.clear_cookie('session_id')
  84.  
  85. def append_session_item(self, **kwargs):
  86. """追加session项"""
  87.  
  88. self.session.update(kwargs)
  89. self._save_session()
  90.  
  91. def delete_session_item(self, name):
  92. """删除session项"""
  93.  
  94. if name in self.session:
  95. self.session.pop(name)
  96. self._save_session()
  97.  
  98. def get_current_user(self):
  99. """重写get_current_user方法,读取session_id"""
  100.  
  101. return self.get_secure_cookie("session_id")
  102.  
  103. @property
  104. def db(self):
  105. """用装饰器引入db(application.db)"""
  106.  
  107. return self.application.db
  108. if __name__ == '__main__':
  109.   pass

Tornado 简述的更多相关文章

  1. Python(九)Tornado web 框架

    一.简介 Tornado 是 FriendFeed 使用的可扩展的非阻塞式 web 服务器及其相关工具的开源版本.这个 Web 框架看起来有些像web.py 或者 Google 的 webapp,不过 ...

  2. 简述 OAuth 2.0 的运作流程

    本文将以用户使用 github 登录网站留言为例,简述 OAuth 2.0 的运作流程. 假如我有一个网站,你是我网站上的访客,看了文章想留言表示「朕已阅」,留言时发现有这个网站的帐号才能够留言,此时 ...

  3. JavaScript单线程和浏览器事件循环简述

    JavaScript单线程 在上篇博客<Promise的前世今生和妙用技巧>的开篇中,我们曾简述了JavaScript的单线程机制和浏览器的事件模型.应很多网友的回复,在这篇文章中将继续展 ...

  4. 使用tornado,我们可以做什么?

    以下介绍都是建立在python2.x的基础上面,tornado使用任意版本皆可. 如果我们需要对外提供一个http server(web api)/websocket server时,我们都可以使用t ...

  5. tornado session

    [转]tornado入门 - session cookie 和session 的区别: 1.cookie数据存放在客户的浏览器上,session数据放在服务器上. 2.cookie不是很安全,别人可以 ...

  6. tornado template

    若果使用Tornado进行web开发可能会用到模板功能,页面继承,嵌套... 多页应用模板的处理多半依赖后端(SPA就可以动态加载局部视图),就算是RESTfull的API设计,也不妨碍同时提供部分模 ...

  7. tornado上手

    http://www.tornadoweb.org/en/stable/ http://www.cnblogs.com/fanweibin/p/5418697.html import tornado. ...

  8. Design Patterns Simplified - Part 3 (Simple Factory)【设计模式简述--第三部分(简单工厂)】

    原文链接:http://www.c-sharpcorner.com/UploadFile/19b1bd/design-patterns-simplified-part3-factory/ Design ...

  9. tornado+sqlalchemy+celery,数据库连接消耗在哪里

    随着公司业务的发展,网站的日活数也逐渐增多,以前只需要考虑将所需要的功能实现就行了,当日活越来越大的时候,就需要考虑对服务器的资源使用消耗情况有一个清楚的认知.     最近老是发现数据库的连接数如果 ...

随机推荐

  1. vitual box 虚拟机调整磁盘大小 resize partiton of vitual os

    key:vitual box, 虚拟机,调整分区大小 引用:http://derekmolloy.ie/resize-a-virtualbox-disk#prettyPhoto 1. 关闭虚拟机,找到 ...

  2. JavaScript 的 URL 对象是什么?

    如果我们自己编写从URL中分析和提取元素的代码,那么有可能会比较痛苦和麻烦.程序员作为这个社会中最“懒”的群体之一,无休止的重复造轮子必然是令人难以容忍的,所以大多数浏览器的标准库中都已经内置了URL ...

  3. 问题:pip命令安装好的库,pycharm却显示没有这个库

    问题: 今天发现pycharm内部安装库出了问题,导致无法安装各种库,我就在cmd下用自己安装好的pip安装各个库,成功安装后发现各个库在idle中可以成功的import,但在pycharm里却显示没 ...

  4. 爬虫笔记(十三)——lxml库的使用

    HTML示例代码: text = ''' <div> <ul> <li class="item-0"><a href="link ...

  5. 监控 Linux 服务器活动的几个命令(watch top ac)

    watch.top 和 ac 命令为我们监视 Linux 服务器上的活动提供了一些十分高效的途径. 为了在获取系统活动时更加轻松,Linux 系统提供了一系列相关的命令.在这篇文章中,我们就一起来看看 ...

  6. Eclipse中配置使用本地schema或dtd文件

    问题:在设备不能正常联网的情况下,无法获取到网络上的 dtd 或 schema,编写配置文件的 XML 文档就没有了提示功能. 一般情况下,下载的 Jar 包或者 Zip 包中都会包含需要的 sche ...

  7. python之urllib模块和requests模块

    一.urllib模块 python标准库自带的发送网络请求的模块. # 用python怎么打开浏览器,发送接口请求 import urllib from urllib.request import u ...

  8. php函数 之 iconv 不是php的默认函数,也是默认安装的模块。需要安装才能用的。

    windows下最近在做一个小偷程序,需要用到iconv函数把抓取来过的utf-8编码的页面转成gb2312, 发现只有用iconv函数把抓取过来的数据一转码数据就会无缘无故的少一些.  让我郁闷了好 ...

  9. perf4j+logback配置 非spring 可使用注解

    最近项目打算使用perf4j进行性能监控,由于项目没有使用spring,而又不想对代码入侵过高,打算使用注解的方式进行接入.perf4j采用AspectJ库实现AOP. 具体接入方法如下: logba ...

  10. POJ-1811-Prime Test(pollard_rho模板,快速找最小素因子)

    题目传送门 sol:Pollard_Rho的模板题,刚看了Pollard_Rho和Miller_Rabin很多原理性的东西看不懂,只是记住了结论勉强能敲代码. Pollard_Rho #include ...