Tornado解析
Tornado是使用Python编写的一个强大的、可扩展的Web服务器。它在处理严峻的网络流量时表现得足够强健,但却在创建和编写时有着足够的轻量级,并能够被用在大量的应用和工具中。
我们现在所知道的Tornado是基于Bret Taylor和其他人员为FriendFeed所开发的网络服务框架,当FriendFeed被Facebook收购后得以开源。不同于那些最多只能达到10,000个并发连接的传统网络服务器,Tornado在设计之初就考虑到了性能因素,旨在解决C10K问题,这样的设计使得其成为一个拥有非常高性能的框架。此外,它还拥有处理安全性、用户验证、社交网络以及与外部服务(如数据库和网站API)进行异步交互的工具。
快速入手
1、安装Tornado
1)pip install tornado 2)源码安装:https://pypi.python.org/packages/source/t/tornado/tornado-4.3.tar.gz
2、首个Tornado程序
#!/usr/bin/env python # -*- coding:utf-8 -*- import tornado.ioloop import tornado.web class MainHandler(tornado.web.RequestHandler): def get(self): self.write("Hello, world") # 可以返回字符串 # self.render("s1.html") # 这个可以返回html # self.redircet("www.baidu.com") # 跳转到某个url application = tornado.web.Application([ (r"/index", MainHandler), ]) if __name__ == "__main__": application.listen(8888) tornado.ioloop.IOLoop.instance().start()
示例:
# !/usr/bin/env python # -*- coding:utf-8 -*- import tornado.ioloop import tornado.web class LoginHandler(tornado.web.RequestHandler): def get(self, *args, **kwargs): # self.render('login.html', state="") # 返回HTML页面 self.render('index.html', k1='123') def post(self, *args, **kwargs): user = self.get_argument('username') pwd = self.get_argument('password') if user == "alex" and pwd == "123": self.redirect("http://www.baidu.com") # 跳转百度的url else: self.write('登录失败,请重新输入') # 返回字符串 settings = { 'template_path': 'views', } application = tornado.web.Application([ # (r"/login", LoginHandler), (r"/index", LoginHandler), ], **settings) if __name__ == "__main__": application.listen(8888) tornado.ioloop.IOLoop.instance().start()
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form method="post" action="/login"> <input id="user" type="text" name="username" /> <input id="pwd" type="password" name="password" /> <input type="submit" value="登录" /> </form> </body> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>{{k1}}</h1> <script> alert({{ k1}}) </script> </body> </html>
基本上所有的Web框架都是以下的流程(以Tornado为例):
准备阶段
加载配置文件
加载路由映射 application = tornado.web.Application([(r"/index", MainHandler),])
创建socket
循环阶段
类似socket Server不断的循环监听文件句柄,当有请求过来的时候,根据用户的请求方法来来判断是什么请求,在通过反射来执行相应的函数或类
流程:
第一步:执行脚本,监听 8888 端口 第二步:浏览器客户端访问 /index --> http://127.0.0.1:8888/index 第三步:服务器接受请求,并交由对应的类处理该请求 第四步:类接受到请求之后,根据请求方式(post(请求) / get(获取) / delete(删除) / pot(设置))的不同调用并执行相应的方法 第五步:方法返回值的字符串内容发送浏览器
3、application配置
application = tornado.web.Application([(r"/index", MainHandler),])
内部在执行的时候执行了两个方法__init__方法和self.add_handlers(".*$", handlers)方法
add_handlers默认传输的".*$" 就是www,他内部生成的路由映射的时候相当于(二级域名的方式)下图:
如果匹配的是shuaige他会去"shuaige"里去找对应关系,如果没有匹配默认就去.*,他这个就类似Django中的URL分类
application = tornado.web.Application([ (r"/index", MainHandler), ]) application.add_handlers("shuaige.com",([ (r"/index", MainHandler), ]) )
路由系统
路由系统其实就是 url 和 类 的对应关系,这里不同于其他框架,其他很多框架均是 url 对应 函数,Tornado中每个url对应的是一个类。
#!/usr/bin/env python #-*- coding:utf-8 -*- import tornado.ioloop import tornado.web class MainHandler(tornado.web.RequestHandler): def get(self): self.write("Hello, world") class Shuaige(tornado.web.RedirectHandler): def get(self): self.write("This is shuaige web site,hello!") application = tornado.web.Application([ (r"/index", MainHandler), ]) application.add_handlers("kongweiqi.com",([ (r"/index", kongweiqi), ]) ) if __name__ == "__main__": application.listen(8888) tornado.ioloop.IOLoop.instance().start()
路由系统可以分为几类:
-
静态路由
-
动态路由
-
请求方法路由
-
二级路由
1、静态路由
@root.route('/hello/') def index(): return template('<b>Hello {{name}}</b>!', name="weiqi")
并且可以绑定多个装饰器如代码(当你访问hello的时候回执行下面的函数,当你访问index的时候也会执行下面的index函数)
@root.route('/index/') @root.route('/hello/') def index(): #return "Hello World" return template('<b style="background-color:red">Hello {{name}}</b>!', name="Tim") root.run(host='localhost', port=8080)
2、动态路由(支持正则)
@root.route('/wiki/<pagename>') def callback(pagename): ... #当请求过来之后@root.route('/wiki/<pagename>') 这个pagename值就会自动把值赋值给def callback(pagename):的参数
@root.route('/wiki/<pagename>') def callback(pagename): ... @root.route('/object/<id:int>') def callback(id): ... @root.route('/show/<name:re:[a-z]+>') def callback(name): ... @root.route('/static/<path:path>') def callback(path): return static_file(path, root='static')
3、请求方法路由
在http访问请求中有很多请求方法比如get、post、delete等
如果使用了@root.get就表示这个装饰器下的函数只接收get请求!
@root.route('/hello/', method='POST') def index(): ... @root.get('/hello/') def index(): ... @root.post('/hello/') def index(): ... @root.put('/hello/') def index(): ... @root.delete('/hello/') def index(): ...
4、二级路由
多个APP把不同app下的请求转发到不同的app下面进行处理
#!/usr/bin/env python # -*- coding:utf-8 -*- from bottle import template, Bottle from bottle import static_file root = Bottle() @root.route('/hello/') def index(): return template('<b>Root {{name}}</b>!', name="Alex") from framwork_bottle import app01 from framwork_bottle import app02 root.mount('app01', app01.app01) root.mount('app02', app02.app02) root.run(host='localhost', port=8888)
#!/usr/bin/env python # -*- coding:utf-8 -*- from bottle import template, Bottle app01 = Bottle() @app01.route('/hello/', method='GET') def index(): return template('<b>App01</b>!'
#!/usr/bin/env python # -*- coding:utf-8 -*- from bottle import template, Bottle app02 = Bottle() @app02.route('/hello/', method='GET') def index(): return template('<b>App02</b>!')
模板引擎
1、解析
Tornao中的模板语言和django中类似,模板引擎将模板文件载入内存,然后将数据嵌入其中,最终获取到一个完整的字符串,再将字符串返回给请求者。
Tornado 的模板支持“控制语句”和“表达语句”,
控制语句是使用 {%
和 %}
包起来的 例如 {% if len(items) > 2 %};
表达语句是使用 {{
和 }}
包起来的,例如 {{ items[0] }};
控制语句和对应的 Python 语句的格式基本完全相同。我们支持 if
、for
、while
和 try
,
这些语句逻辑结束的位置需要用 {% end %}
做标记。还通过 extends
和 block
语句实现了模板继承。这些在 template
模块 的代码文档中有着详细的描述。
2、目录结构
#!/usr/bin/env python # -*- coding:utf-8 -*- import tornado.ioloop import tornado.web class MainHandler(tornado.web.RequestHandler): def get(self): # 用get 的方式接受 self.render('home/index.html') # 可以返回html #self.write("Hello world") # 可以返回字符串 def post(self, *args, **kwargs): # 用post方式接受 name = self.get_argument('xxx') print(name) self.render('index.html') # 模板路径的配置 settings = { 'template_path': 'template', # 配置文件路径,设置完可以把HTML文件放置template 'static_path': 'static', # 静态文件的配置 # 'static_url_prefix':'/sss/' # 静态文件的前缀,在静态文件夹前面加上这个前缀 'cookie_secret': "asdasd", # cookie生成秘钥时候需提前生成随机字符串,需要在这里进行渲染 'xsrf_cokkies':True, # 允许CSRF使用 } # 路由系统 application = tornado.web.Application([ (r"/index", MainHandler), # 匹配url和类 ], **settings) # 接受配置 if __name__ == "__main__": application.listen(80) tornado.ioloop.IOLoop.instance().start()
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <title>老男孩</title> <link href="{{static_url("css/common.css")}}" rel="stylesheet" /> {% block CSS %}{% end %} </head> <body> <div class="pg-header"> </div> {% block RenderBody %}{% end %} <script src="{{static_url("js/jquery-1.8.2.min.js")}}"></script> {% block JavaScript %}{% end %} </body> </html> layout.html
{% extends 'layout.html'%} {% block CSS %} <link href="{{static_url("css/index.css")}}" rel="stylesheet" /> {% end %} {% block RenderBody %} <h1>Index</h1> <ul> {% for item in li %} <li>{{item}}</li> {% end %} </ul> {% end %} {% block JavaScript %} {% end %} index.html
{% extends 'layout.html'%} {% block CSS %} <link href="{{static_url("css/index.css")}}" rel="stylesheet" /> {% end %} {% block RenderBody %} <h1>Index</h1> <ul> {% for item in li %} <li>{{item}}</li> {% end %} </ul> {% end %} {% block JavaScript %} {% end %}
3、语法
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> </head> <body> <h1>1、单值</h1> {{name}} #应用后端返回的数据 <h1>2、单行Python代码</h1> % s1 = "hello" {{ s1 }} #'%s1 这里设置一个变量,我们可以在html调用和python类似' <h1>3、Python代码块</h1> <% # python块代码 name = name.title().strip() if name == "shuaige": name="luotianshuai" %> <h1>4、Python、Html混合</h1> % if True: <span>{{name}}</span> % end <ul> % for item in name: <li>{{item}}</li> % end </ul> </body> </html>
4、函数
include(sub_template, **variables)
# 导入其他模板文件 % include('header.tpl', title='Page Title') Page Content % include('footer.tpl')
rebase(name, **variables)
<html> <head> <title>{{title or 'No title'}}</title> </head> <body> {{!base}} </body> </html>
导入母版
% rebase('base.tpl', title='Page Title') <p>Page Content ...</p>
defined(name) #检查当前变量是否已经被定义,已定义True,未定义False get(name, default=None) #获取某个变量的值,不存在时可设置默认值 setdefault(name, default) #如果变量不存在时,为变量设置默认值
母版继承
①相当于python的字符串格式化一样,先定义一个占位符
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <title>帅哥</title> <link href="{{static_url("css/common.css")}}" rel="stylesheet" /> {% block CSS %}{% end %} </head> <body> <div class="pg-header"> </div> {% block RenderBody %}{% end %} <script src="{{static_url("js/jquery-1.8.2.min.js")}}"></script> {% block JavaScript %}{% end %} </body> </html>
②再子板中相应的位置继承模板的格式
{% extends 'layout.html'%} {% block CSS %} <link href="{{static_url("css/index.css")}}" rel="stylesheet" /> {% end %} {% block RenderBody %} <h1>Index</h1> <ul> {% for item in li %} <li>{{item}}</li> {% end %} </ul> {% end %} {% block JavaScript %} {% end %}
导入内容
<div> <ul> <li>帅</li> <li>很帅</li> <li>非常帅</li> </ul> </div>
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <title>你好</title> <link href="{{static_url("css/common.css")}}" rel="stylesheet" /> </head> <body> <div class="pg-header"> {% include 'header.html' %} </div> <script src="{{static_url("js/jquery-1.8.2.min.js")}}"></script> </body> </html>
在模板中默认提供了一些函数、字段、类以供模板使用:
-
escape
:tornado.escape.xhtml_escape
的別名 -
xhtml_escape
:tornado.escape.xhtml_escape
的別名 -
url_escape
:tornado.escape.url_escape
的別名 -
json_encode
:tornado.escape.json_encode
的別名 -
squeeze
:tornado.escape.squeeze
的別名 -
linkify
:tornado.escape.linkify
的別名 -
datetime
: Python 的datetime
模组 -
handler
: 当前的RequestHandler
对象 -
request
:handler.request
的別名 -
current_user
:handler.current_user
的別名 -
locale
:handler.locale
的別名 -
_
:handler.locale.translate
的別名 -
static_url
: forhandler.static_url
的別名 -
xsrf_form_html
:handler.xsrf_form_html
的別名
5、自定义模板
Tornado默认提供的这些功能其实本质上就是 UIMethod 和 UIModule,我们也可以自定义从而实现类似于Django的simple_tag的功能。
1、定义
def tab(self): return 'UIMethod'
#!/usr/bin/env python # -*- coding:utf-8 -*- from tornado.web import UIModule from tornado import escape class custom(UIModule): def render(self, *args, **kwargs): return escape.xhtml_escape('<h1>wupeiqi</h1>') #return escape.xhtml_escape('<h1>wupeiqi</h1>')
2、注册
#!/usr/bin/env python # -*- coding:utf-8 -*- import tornado.ioloop import tornado.web from tornado.escape import linkify import uimodules as md import uimethods as mt class MainHandler(tornado.web.RequestHandler): def get(self): self.render('index.html') settings = { 'template_path': 'template', 'static_path': 'static', 'static_url_prefix': '/static/', 'ui_methods': mt, 'ui_modules': md, } application = tornado.web.Application([ (r"/index", MainHandler), ], **settings) if __name__ == "__main__": application.listen(8009) tornado.ioloop.IOLoop.instance().start()
3、使用
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> <link href="{{static_url("commons.css")}}" rel="stylesheet" /> </head> <body> <h1>hello</h1> {% module custom(123) %} {{ tab() }} </body>
6、 公共组件
由于Web框架就是用来【接收用户请求】-> 【处理用户请求】-> 【响应相关内容】,对于具体如何处理用户请求,开发人员根据用户请求来进行处理,而对于接收用户请求和相应相关的内容均交给框架本身来处理,其处理完成之后将产出交给开发人员和用户。
【接收用户请求】
当框架接收到用户请求之后,将请求信息封装在Bottle的request中,以供开发人员使用
【响应相关内容】
当开发人员的代码处理完用户请求之后,会将其执行内容相应给用户,相应的内容会封装在Bottle的response中,然后再由框架将内容返回给用户
所以,公共组件本质其实就是为开发人员提供接口,使其能够获取用户信息并配置响应内容。
request.headers #请求头信息,可以通过请求头信息来获取相关客户端的信息 request.query #get请求信息,如果用户访问时这样的:http://127.0.0.1:8000/?page=123就必须使用request.query 使用GET方法是无法取到信息的 request.forms #post请求信息 request.files #上传文件信息 request.params #get和post请求信息,他是GET和POST的总和,其实他内部调用了request.get request.forms request.GET #get请求信息 request.POST #post和上传信息,上传文件信息,和post信息 request.cookies #cookie信息 request.environ #环境相关相关,如果上面的这些请求信息没有满足你的需求,就在这里找!
实用功能
1、静态文件
对于静态文件,可以配置静态文件的目录和前段使用时的前缀,并且Tornaodo还支持静态文件缓存。
静态文件缓存:
<link href="{{static_url('css/index.css')}}" rel="stylesheet" /> """ 在Tornado中写成变量形式的主要是为了以后扩展方便,还有一个缓存的功能 """
原理:
拿一个静态文件来说:/static/commons.js如果用Tornado封装后,类似于给他加了一个版本号/static/commons.js?v=sldkfjsldf123
当客户端访问过来的时候会携带这个值,如果发现没变客户端缓存的静态文件直接渲染就行了,不必再在服务器上下载一下静态文件了。
#!/usr/bin/env python # -*- coding:utf-8 -*- import tornado.ioloop import tornado.web class MainHandler(tornado.web.RequestHandler): def get(self): self.render('index.html') settings = { 'template_path': 'template', 'static_path': 'static', 'static_url_prefix': '/static/', } application = tornado.web.Application([ (r"/index", MainHandler), ], **settings) if __name__ == "__main__": application.listen(8888) tornado.ioloop.IOLoop.instance().start()
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> <link href="{{static_url('commons.css')}}" rel="stylesheet" /> </head> <body> <h1>hello</h1> </body> </html>
Tornado静态文件实现缓存
def get_content_version(cls, abspath): """Returns a version string for the resource at the given path. This class method may be overridden by subclasses. The default implementation is a hash of the file's contents. .. versionadded:: 3.1 """ data = cls.get_content(abspath) hasher = hashlib.md5() if isinstance(data, bytes): hasher.update(data) else: for chunk in data: hasher.update(chunk) return hasher.hexdigest()
2、xss
跨站脚本攻击
恶意攻击者往Web页面里插入恶意Script代码,当用户浏览该页之时,嵌入其中Web里面的Script代码会被执行,从而达到恶意攻击用户的特殊目的。
class IndexHandler(tornado.web.RequestHandler): def get(self, *args, **kwargs): jump = '''<input type="text"><a onclick = "Jump('%s',this);">GO</a>'''%('/index/') script = ''' <script> function Jump(baseUrl,ths){ var val = ths.previousElementSibling.value; if (val.trim().length > 0){ location.href = baseUrl + val; } } </script> ''' self.render('index.html',jump=jump,script=script) #传入两个前端代码的字符串
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> .pager a{ display: inline-block; padding: 5px; margin: 3px; background-color: #00a2ca; } .pager a.active{ background-color: #0f0f0f; color: white; } </style> </head> <body> <div class="pager"> {% raw jump %} {% raw script%} </div> </body> </html>
3、CSRF跨站请求伪造
跨站伪造请求(Cross-site request forgery)
settings = { "xsrf_cookies": True, } application = tornado.web.Application([ (r"/", MainHandler), (r"/login", LoginHandler), ], **settings)
<form action="/new_message" method="post"> {{ xsrf_form_html() }} <input type="text" name="message"/> <input type="submit" value="Post"/> </form>
function getCookie(name) { var r = document.cookie.match("\b" + name + "=([^;]*)\b"); return r ? r[1] : undefined; } jQuery.postJSON = function(url, args, callback) { args._xsrf = getCookie("_xsrf"); $.ajax({url: url, data: $.param(args), dataType: "text", type: "POST", success: function(response) { callback(eval("(" + response + ")")); }}); }; Ajax使用
注:Ajax使用时,本质上就是去获取本地的cookie,携带cookie再来发送请求
Tornado 有内建的 XSRF 的防范机制,要使用此机制,你需要在应用配置中加上 xsrf_cookies
设定:xsrf_cookies=True,再来写一段代码,来表示一下:
#!/usr/bin/env python # -*- coding:utf-8 -*- import tornado.web import tornado.ioloop class CsrfHandler(tornado.web.RequestHandler): def get(self, *args, **kwargs): self.render('csrf.html') def post(self, *args, **kwargs): self.write('张岩林已经收到客户端发的请求伪造') settings = { 'template_path':'views', 'static_path':'statics', 'xsrf_cokkies':True, # 重点在这里,往这里看 } application = tornado.web.Application([ (r'/csrf',CsrfHandler) ],**settings) if __name__ == "__main__": application.listen(8888) tornado.ioloop.IOLoop.instance().start()
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form action="/csrf" method="post"> {% raw xsrf_form_html() %} <p><input name="user" type="text" placeholder="用户"/></p> <p><input name='pwd' type="text" placeholder="密码"/></p> <input type="submit" value="Submit" /> <input type="button" value="Ajax CSRF" onclick="SubmitCsrf();" /> </form> <script src="/statics/jquery-1.12.4.js"></script> <script type="text/javascript"> function ChangeCode() { var code = document.getElementById('imgCode'); code.src += '?'; } function getCookie(name) { var r = document.cookie.match("\b" + name + "=([^;]*)\b"); return r ? r[1] : undefined; } function SubmitCsrf() { var nid = getCookie('_xsrf'); $.post({ url: '/csrf', data: {'k1': 'v1',"_xsrf": nid}, success: function (callback) { // Ajax请求发送成功有,自动执行 // callback,服务器write的数据 callback=“csrf.post” console.log(callback); } }); } </script> </body> </html>
在form验证里面生成了一段类似于自己的身份标识一样,携带着这个标识来访问网页。
4、Cookie
Tornado中可以对cookie进行操作,并且还可以对cookie进行签名以放置伪造。
a、基本操作
#!/usr/bin/env python # -*- coding:utf-8 -*- import tornado.ioloop import tornado.web class MainHandler(tornado.web.RequestHandler): def get(self): print(self.cookies) # 获取所有的cookie self.set_cookie('k1','v1') # 设置cookie print(self.get_cookie('k1')) # 获取指定的cookie self.write("Hello, world") application = tornado.web.Application([ (r"/index", MainHandler), ]) if __name__ == "__main__": application.listen(8888) tornado.ioloop.IOLoop.instance().start()
b、签名
Cookie 很容易被恶意的客户端伪造。加入你想在 cookie 中保存当前登陆用户的 id 之类的信息,你需要对 cookie 作签名以防止伪造。Tornado 通过 set_secure_cookie 和 get_secure_cookie 方法直接支持了这种功能。 要使用这些方法,你需要在创建应用时提供一个密钥,名字为 cookie_secret。 你可以把它作为一个关键词参数传入应用的设置中:
#!/usr/bin/env python # -*- coding:utf-8 -*- import tornado.ioloop import tornado.web class MainHandler(tornado.web.RequestHandler): def get(self): if not self.get_secure_cookie("mycookie"): # 获取带签名的cookie self.set_secure_cookie("mycookie", "myvalue") # 设置带签名的cookie self.write("Your cookie was not set yet!") else: self.write("Your cookie was set!") application = tornado.web.Application([ (r"/index", MainHandler), ]) if __name__ == "__main__": application.listen(8888) tornado.ioloop.IOLoop.instance().start()
签名Cookie的本质是:
写cookie过程:
将值进行base64加密
对除值以外的内容进行签名,哈希算法(无法逆向解析)
拼接 签名 + 加密值
读cookie过程:
读取 签名 + 加密值
对签名进行验证
base64解密,获取值内容
c、自定义session
用cookie做简单的自定义用户验证,下面会写一个绝对牛逼的自定义session用户验证
#!/usr/bin/env python # -*- coding:utf-8 -*- import tornado.ioloop import tornado.web class BaseHandler(tornado.web.RequestHandler): def get_current_user(self): return self.get_secure_cookie("login_user") class MainHandler(BaseHandler): @tornado.web.authenticated def get(self): login_user = self.current_user self.write(login_user) class LoginHandler(tornado.web.RequestHandler): def get(self): self.current_user() self.render('login.html', **{'status': ''}) def post(self, *args, **kwargs): username = self.get_argument('name') password = self.get_argument('pwd') if username == 'alex' and password == '123': self.set_secure_cookie('login_user', 'alex') self.redirect('/') else: self.render('login.html', **{'status': '用户名或密码错误'}) settings = { 'template_path': 'template', 'static_path': 'static', 'static_url_prefix': '/static/', 'cookie_secret': 'helldjkaskak', # 设定加一段字符串的cookie } application = tornado.web.Application([ (r"/index", MainHandler), (r"/login", LoginHandler), ], **settings) if __name__ == "__main__": application.listen(8888) tornado.ioloop.IOLoop.instance().start()
d、JavaScript操作Cookie
由于Cookie保存在浏览器端,所以在浏览器端也可以使用javascript来操作Cookie.
/* 设置cookie,指定秒数过期, name表示传入的key, value表示传入相对应的value值, expires表示当前日期在加5秒过期 */ function setCookie(name,value,expires){ var temp = []; var current_date = new Date(); current_date.setSeconds(current_date.getSeconds() + 5); document.cookie = name + "= "+ value +";expires=" + current_date.toUTCString(); }
5、Session
除请求对象之外,还有一个 session 对象。它允许你在不同请求间存储特定用户的信息。它是在 Cookies 的基础上实现的,并且对 Cookies 进行密钥签名要使用会话,你需要设置一个密钥。
操作session
- 获取session:request.session[key]
- 设置session:reqeust.session[key] = value
- 删除session:del request[key]
区别:cookie机制采用的是在客户端保持状态的方案,而session机制采用的是在服务器端保持状态的方案
#!/usr/bin/env python # -*- coding:utf-8 -*- import tornado.ioloop import tornado.web from hashlib import sha1 import os, time session_container = {} create_session_id = lambda: sha1('%s%s' % (os.urandom(16), time.time())).hexdigest() class Session(object): session_id = "__sessionId__" def __init__(self, request): #当你请求过来的时候,我先去get_cookie看看有没有cookie!目的是看看有没有Cookie如果有的话就不生成了,没有就生成! session_value = request.get_cookie(Session.session_id) #如果没有Cookie生成Cookie[创建随机字符串] if not session_value: self._id = create_session_id() else: #如果有直接将客户端的随机字符串设置给_id这个字段,随机字符串封装到self._id里了 self._id = session_value #在给它设置一下 request.set_cookie(Session.session_id, self._id) def __getitem__(self, key): ret = None try: ret = session_container[self._id][key] except Exception,e: pass return ret def __setitem__(self, key, value): #判断是否有这个随机字符串 if session_container.has_key(self._id): session_container[self._id][key] = value else: #如果没有就生成一个字典 ''' 类似:随机字符串:{'IS_LOGIN':'True'} ''' session_container[self._id] = {key: value} def __delitem__(self, key): del session_container[self._id][key] class BaseHandler(tornado.web.RequestHandler): def initialize(self): ''' 这里initialize的self是谁? obj = LoginHandler() obj.initialize() ==>这里LoginHandler这个类里没有initialize这个方法,在他父类里有 所以initialize得self就是LoginHandler的对象 ''' self.my_session = Session(self) #执行Session的构造方法并且把LoginHandler的对象传过去 ''' 这个self.my_session = Session() 看这个例子: self.xxx = '123' 在这里创建一个对象,在LoginHandler中是否可以通过self.xxx去访问123这个值? 可以,因为self.xxx 是在get之前执行的,他们俩的对象都是LoginHandler对象 ''' class MainHandler(BaseHandler): def get(self): ret = self.my_session['is_login'] if ret: self.write('index') else: self.redirect("/login") class LoginHandler(BaseHandler): def get(self): ''' 当用户访登录的时候我们就得给他写cookie了,但是这里没有写在哪里写了呢? 在哪里呢?之前写的Handler都是继承的RequestHandler,这次继承的是BaseHandler是自己写的Handler 继承自己的类,在类了加扩展initialize! 在这里我们可以在这里做获取用户cookie或者写cookie都可以在这里做 ''' ''' 我们知道LoginHandler对象就是self,我们可不可以self.set_cookie()可不可以self.get_cookie() ''' # self.set_cookie() # self.get_cookie() self.render('login.html', **{'status': ''}) def post(self, *args, **kwargs): #获取用户提交的用户名和密码 username = self.get_argument('username') password = self.get_argument('pwd') if username == 'wupeiqi' and password == '123': #如果认证通过之后就可以访问这个self.my_session对象了!然后我就就可以吧Cookie写入到字典中了,NICE self.my_session['is_login'] = 'true' ''' 这里用到知识点是类里的: class Foo(object): def __getitem__(self,key): print '__getitem__',key def __setitem__(self,key,value): print '__setitem__',key,value def __delitem__(self,key): print '__delitem__',key obj = Foo() result = obj['k1'] #自动触发执行 __getitem__ obj['k2'] = 'wupeiqi' #自动触发执行 __setitem__ del obj['k1'] #自动触发执行 __delitme__ ''' self.redirect('/index') else: self.render('login.html', **{'status': '用户名或密码错误'}) settings = { 'template_path': 'template', 'static_path': 'static', 'static_url_prefix': '/static/', 'cookie_secret': 'aiuasdhflashjdfoiuashdfiuh', 'login_url': '/login' } application = tornado.web.Application([ #创建两个URL 分别对应 MainHandler LoginHandler (r"/index", MainHandler), (r"/login", LoginHandler), ], **settings) if __name__ == "__main__": application.listen(8888) tornado.ioloop.IOLoop.instance().start()
#!/usr/bin/env python # -*- coding:utf-8 -*- import tornado.web import tornado.ioloop container = {} class Session: def __init__(self, handler): self.handler = handler self.random_str = None def __genarate_random_str(self): import hashlib import time obj = hashlib.md5() obj.update(bytes(str(time.time()), encoding='utf-8')) random_str = obj.hexdigest() return random_str def __setitem__(self, key, value): # 在container中加入随机字符串 # 定义专属于自己的数据 # 在客户端中写入随机字符串 # 判断,请求的用户是否已有随机字符串 if not self.random_str: random_str = self.handler.get_cookie('__session__') if not random_str: random_str = self.__genarate_random_str() container[random_str] = {} else: # 客户端有随机字符串 if random_str in container.keys(): pass else: random_str = self.__genarate_random_str() container[random_str] = {} self.random_str = random_str # self.random_str = asdfasdfasdfasdf container[self.random_str][key] = value self.handler.set_cookie("__session__", self.random_str) def __getitem__(self, key): # 获取客户端的随机字符串 # 从container中获取专属于我的数据 # 专属信息【key】 random_str = self.handler.get_cookie("__session__") if not random_str: return None # 客户端有随机字符串 user_info_dict = container.get(random_str,None) if not user_info_dict: return None value = user_info_dict.get(key, None) return value class BaseHandler(tornado.web.RequestHandler): def initialize(self): self.session = Session(self)
6、上传文件
6.1、Form表单上传
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> </head> <body> <input type="file" id="img" /> <input type="button" onclick="UploadFile();" /> <script src="/statics/jquery-1.12.4.js"> function UploadFile(){ var fileObj = $("#img")[0].files[0]; var form = new FormData(); form.append("user", "v1"); form.append("favor", "1"); form.append("fafafa", "fileObj"); $.ajax({ type:'POST', url:'/index', data:form, processData: false, // tell jQuery not to process the data contentType: false, // tell jQuery not to set contentType success: function(arg) { console.log(arg); } }) } </script> </body> </html>
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import tornado.ioloop
import tornado.web
IMG_LIST = []
class IndexHandler(tornado.web.RequestHandler):
def get(self):
self.render('index.html', img_list=IMG_LIST)
def post(self, *args, **kwargs):
print(self.get_argument('user'))
print(self.get_arguments('favor'))
file_metas = self.request.files["fafafa"]
# print(file_metas)
for meta in file_metas:
# 要上传的文件名
file_name = meta['filename']
import os
with open(os.path.join('statics', 'img', file_name), 'wb') as up:
up.write(meta['body'])
IMG_LIST.append(file_name)
self.write('{"status": 1, "message": "mmmm"}')
settings = {
'template_path': 'views',
'static_path': 'statics',
'static_url_prefix': '/statics/',
}
application = tornado.web.Application([
(r"/index", IndexHandler),
], **settings)
if __name__ == "__main__":
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()
源码文件夹:http://pan.baidu.com/s/1slL6gFV
6.2、AJAX上传
xhr上传
jquery上传
基于iframe上传
源码文件夹:http://pan.baidu.com/s/1slL6gFV
7、验证码
验证码原理在于后台自动创建一张带有随机内容的图片,然后将内容通过img标签输出到页面。
下面也有具体的验证代码,可仿照。
源码文件夹:http://pan.baidu.com/s/1slL6gFV
自定义Web组件
1、面向对象基础
面向对象中通过索引的方式访问对象,需要内部实现 __getitem__ 、__delitem__、__setitem__方法
#!/usr/bin/env python # -*- coding:utf-8 -*- class Foo(object): def __getitem__(self, key): print '__getitem__',key def __setitem__(self, key, value): print '__setitem__',key,value def __delitem__(self, key): print '__delitem__',key obj = Foo() result = obj['k1'] #obj['k2'] = 'wupeiqi' #del obj['k1']
2、Tornado扩展
Tornado框架中,默认执行Handler的get/post等方法之前默认会执行 initialize方法,所以可以通过自定义的方式使得所有请求在处理前执行操作
class BaseHandler(tornado.web.RequestHandler): def initialize(self): self.xxoo = "wupeiqi" class MainHandler(BaseHandler): def get(self): print(self.xxoo) self.write('index') class IndexHandler(BaseHandler): def get(self): print(self.xxoo) self.write('index')
3、session
session其实就是定义在服务器端用于保存用户回话的容器,其必须依赖cookie才能实现。
#!/usr/bin/env python # -*- coding:utf-8 -*- import tornado.ioloop import tornado.web from hashlib import sha1 import os, time session_container = {} create_session_id = lambda: sha1('%s%s' % (os.urandom(16), time.time())).hexdigest() class Session(object): session_id = "__sessionId__" def __init__(self, request): session_value = request.get_cookie(Session.session_id) if not session_value: self._id = create_session_id() else: self._id = session_value request.set_cookie(Session.session_id, self._id) def __getitem__(self, key): return session_container[self._id][key] def __setitem__(self, key, value): if session_container.has_key(self._id): session_container[self._id][key] = value else: session_container[self._id] = {key: value} def __delitem__(self, key): del session_container[self._id][key] class BaseHandler(tornado.web.RequestHandler): def initialize(self): # my_session['k1']访问 __getitem__ 方法 self.my_session = Session(self) class MainHandler(BaseHandler): def get(self): print self.my_session['c_user'] print self.my_session['c_card'] self.write('index') class LoginHandler(BaseHandler): def get(self): self.render('login.html', **{'status': ''}) def post(self, *args, **kwargs): username = self.get_argument('name') password = self.get_argument('pwd') if username == 'wupeiqi' and password == '123': self.my_session['c_user'] = 'wupeiqi' self.my_session['c_card'] = '12312312309823012' self.redirect('/index') else: self.render('login.html', **{'status': '用户名或密码错误'}) settings = { 'template_path': 'template', 'static_path': 'static', 'static_url_prefix': '/static/', 'cookie_secret': 'aiuasdhflashjdfoiuashdfiuh', 'login_url': '/login' } application = tornado.web.Application([ (r"/index", MainHandler), (r"/login", LoginHandler), ], **settings) if __name__ == "__main__": application.listen(8888) tornado.ioloop.IOLoop.instance().start() 自定义Session
4、表单验证
在Web程序中往往包含大量的表单验证的工作,如:判断输入是否为空,是否符合规则。
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> <link href="{{static_url("commons.css")}}" rel="stylesheet" /> </head> <body> <h1>hello</h1> <form action="/index" method="post"> <p>hostname: <input type="text" name="host" /> </p> <p>ip: <input type="text" name="ip" /> </p> <p>port: <input type="text" name="port" /> </p> <p>phone: <input type="text" name="phone" /> </p> <input type="submit" /> </form> </body> </html>
#!/usr/bin/env python # -*- coding:utf-8 -*- import tornado.ioloop import tornado.web from hashlib import sha1 import os, time import re class MainForm(object): def __init__(self): self.host = "(.*)" self.ip = "^(25[0-5]|2[0-4]d|[0-1]?d?d)(.(25[0-5]|2[0-4]d|[0-1]?d?d)){3}$" self.port = '(d+)' self.phone = '^1[3|4|5|8][0-9]d{8}$' def check_valid(self, request): form_dict = self.__dict__ for key, regular in form_dict.items(): post_value = request.get_argument(key) # 让提交的数据 和 定义的正则表达式进行匹配 ret = re.match(regular, post_value) print key,ret,post_value class MainHandler(tornado.web.RequestHandler): def get(self): self.render('index.html') def post(self, *args, **kwargs): obj = MainForm() result = obj.check_valid(self) self.write('ok') settings = { 'template_path': 'template', 'static_path': 'static', 'static_url_prefix': '/static/', 'cookie_secret': 'aiuasdhflashjdfoiuashdfiuh', 'login_url': '/login' } application = tornado.web.Application([ (r"/index", MainHandler), ], **settings) if __name__ == "__main__": application.listen(8888) tornado.ioloop.IOLoop.instance().start() Python
由于验证规则可以代码重用,所以可以如此定义:
#!/usr/bin/env python # -*- coding:utf-8 -*- import tornado.ioloop import tornado.web import re class Field(object): def __init__(self, error_msg_dict, required): self.id_valid = False self.value = None self.error = None self.name = None self.error_msg = error_msg_dict self.required = required def match(self, name, value): self.name = name if not self.required: self.id_valid = True self.value = value else: if not value: if self.error_msg.get('required', None): self.error = self.error_msg['required'] else: self.error = "%s is required" % name else: ret = re.match(self.REGULAR, value) if ret: self.id_valid = True self.value = ret.group() else: if self.error_msg.get('valid', None): self.error = self.error_msg['valid'] else: self.error = "%s is invalid" % name class IPField(Field): REGULAR = "^(25[0-5]|2[0-4]d|[0-1]?d?d)(.(25[0-5]|2[0-4]d|[0-1]?d?d)){3}$" def __init__(self, error_msg_dict=None, required=True): error_msg = {} # {'required': 'IP不能为空', 'valid': 'IP格式错误'} if error_msg_dict: error_msg.update(error_msg_dict) super(IPField, self).__init__(error_msg_dict=error_msg, required=required) class IntegerField(Field): REGULAR = "^d+$" def __init__(self, error_msg_dict=None, required=True): error_msg = {'required': '数字不能为空', 'valid': '数字格式错误'} if error_msg_dict: error_msg.update(error_msg_dict) super(IntegerField, self).__init__(error_msg_dict=error_msg, required=required) class CheckBoxField(Field): def __init__(self, error_msg_dict=None, required=True): error_msg = {} # {'required': 'IP不能为空', 'valid': 'IP格式错误'} if error_msg_dict: error_msg.update(error_msg_dict) super(CheckBoxField, self).__init__(error_msg_dict=error_msg, required=required) def match(self, name, value): self.name = name if not self.required: self.id_valid = True self.value = value else: if not value: if self.error_msg.get('required', None): self.error = self.error_msg['required'] else: self.error = "%s is required" % name else: if isinstance(name, list): self.id_valid = True self.value = value else: if self.error_msg.get('valid', None): self.error = self.error_msg['valid'] else: self.error = "%s is invalid" % name class FileField(Field): REGULAR = "^(w+.pdf)|(w+.mp3)|(w+.py)$" def __init__(self, error_msg_dict=None, required=True): error_msg = {} # {'required': '数字不能为空', 'valid': '数字格式错误'} if error_msg_dict: error_msg.update(error_msg_dict) super(FileField, self).__init__(error_msg_dict=error_msg, required=required) def match(self, name, value): self.name = name self.value = [] if not self.required: self.id_valid = True self.value = value else: if not value: if self.error_msg.get('required', None): self.error = self.error_msg['required'] else: self.error = "%s is required" % name else: m = re.compile(self.REGULAR) if isinstance(value, list): for file_name in value: r = m.match(file_name) if r: self.value.append(r.group()) self.id_valid = True else: self.id_valid = False if self.error_msg.get('valid', None): self.error = self.error_msg['valid'] else: self.error = "%s is invalid" % name break else: if self.error_msg.get('valid', None): self.error = self.error_msg['valid'] else: self.error = "%s is invalid" % name def save(self, request, upload_path=""): file_metas = request.files[self.name] for meta in file_metas: file_name = meta['filename'] with open(file_name,'wb') as up: up.write(meta['body']) class Form(object): def __init__(self): self.value_dict = {} self.error_dict = {} self.valid_status = True def validate(self, request, depth=10, pre_key=""): self.initialize() self.__valid(self, request, depth, pre_key) def initialize(self): pass def __valid(self, form_obj, request, depth, pre_key): """ 验证用户表单请求的数据 :param form_obj: Form对象(Form派生类的对象) :param request: Http请求上下文(用于从请求中获取用户提交的值) :param depth: 对Form内容的深度的支持 :param pre_key: Html中name属性值的前缀(多层Form时,内部递归时设置,无需理会) :return: 是否验证通过,True:验证成功;False:验证失败 """ depth -= 1 if depth < 0: return None form_field_dict = form_obj.__dict__ for key, field_obj in form_field_dict.items(): print key,field_obj if isinstance(field_obj, Form) or isinstance(field_obj, Field): if isinstance(field_obj, Form): # 获取以key开头的所有的值,以参数的形式传至 self.__valid(field_obj, request, depth, key) continue if pre_key: key = "%s.%s" % (pre_key, key) if isinstance(field_obj, CheckBoxField): post_value = request.get_arguments(key, None) elif isinstance(field_obj, FileField): post_value = [] file_list = request.request.files.get(key, None) for file_item in file_list: post_value.append(file_item['filename']) else: post_value = request.get_argument(key, None) print post_value # 让提交的数据 和 定义的正则表达式进行匹配 field_obj.match(key, post_value) if field_obj.id_valid: self.value_dict[key] = field_obj.value else: self.error_dict[key] = field_obj.error self.valid_status = False class ListForm(object): def __init__(self, form_type): self.form_type = form_type self.valid_status = True self.value_dict = {} self.error_dict = {} def validate(self, request): name_list = request.request.arguments.keys() + request.request.files.keys() index = 0 flag = False while True: pre_key = "[%d]" % index for name in name_list: if name.startswith(pre_key): flag = True break if flag: form_obj = self.form_type() form_obj.validate(request, depth=10, pre_key="[%d]" % index) if form_obj.valid_status: self.value_dict[index] = form_obj.value_dict else: self.error_dict[index] = form_obj.error_dict self.valid_status = False else: break index += 1 flag = False class MainForm(Form): def __init__(self): # self.ip = IPField(required=True) # self.port = IntegerField(required=True) # self.new_ip = IPField(required=True) # self.second = SecondForm() self.fff = FileField(required=True) super(MainForm, self).__init__() # # class SecondForm(Form): # # def __init__(self): # self.ip = IPField(required=True) # self.new_ip = IPField(required=True) # # super(SecondForm, self).__init__() class MainHandler(tornado.web.RequestHandler): def get(self): self.render('index.html') def post(self, *args, **kwargs): # for i in dir(self.request): # print i # print self.request.arguments # print self.request.files # print self.request.query # name_list = self.request.arguments.keys() + self.request.files.keys() # print name_list # list_form = ListForm(MainForm) # list_form.validate(self) # # print list_form.valid_status # print list_form.value_dict # print list_form.error_dict # obj = MainForm() # obj.validate(self) # # print "验证结果:", obj.valid_status # print "符合验证结果:", obj.value_dict # print "错误信息:" # for key, item in obj.error_dict.items(): # print key,item # print self.get_arguments('favor'),type(self.get_arguments('favor')) # print self.get_argument('favor'),type(self.get_argument('favor')) # print type(self.get_argument('fff')),self.get_argument('fff') # print self.request.files # obj = MainForm() # obj.validate(self) # print obj.valid_status # print obj.value_dict # print obj.error_dict # print self.request,type(self.request) # obj.fff.save(self.request) # from tornado.httputil import HTTPServerRequest # name_list = self.request.arguments.keys() + self.request.files.keys() # print name_list # print self.request.files,type(self.request.files) # print len(self.request.files.get('fff')) # obj = MainForm() # obj.validate(self) # print obj.valid_status # print obj.value_dict # print obj.error_dict # obj.fff.save(self.request) self.write('ok') settings = { 'template_path': 'template', 'static_path': 'static', 'static_url_prefix': '/static/', 'cookie_secret': 'aiuasdhflashjdfoiuashdfiuh', 'login_url': '/login' } application = tornado.web.Application([ (r"/index", MainHandler), ], **settings) if __name__ == "__main__": application.listen(8888) tornado.ioloop.IOLoop.instance().start()
5、tornado生成随机验证码
用python生成随机验证码需要借鉴一个插件,和一个io模块,实现起来也非常容易,当然也需要借鉴session来判断验证码是否错误,
下面写一段用户登录验证带验证码的,再看下效果,插件必须和执行文件必须放在更目录下
#!/usr/bin/env python # -*- coding:utf-8 -*- import tornado.web import tornado.ioloop container = {} class Session: def __init__(self, handler): self.handler = handler self.random_str = None def __genarate_random_str(self): import hashlib import time obj = hashlib.md5() obj.update(bytes(str(time.time()), encoding='utf-8')) random_str = obj.hexdigest() return random_str def __setitem__(self, key, value): # 在container中加入随机字符串 # 定义专属于自己的数据 # 在客户端中写入随机字符串 # 判断,请求的用户是否已有随机字符串 if not self.random_str: random_str = self.handler.get_cookie('__session__') if not random_str: random_str = self.__genarate_random_str() container[random_str] = {} else: # 客户端有随机字符串 if random_str in container.keys(): pass else: random_str = self.__genarate_random_str() container[random_str] = {} self.random_str = random_str # self.random_str = asdfasdfasdfasdf container[self.random_str][key] = value self.handler.set_cookie("__session__", self.random_str) def __getitem__(self, key): # 获取客户端的随机字符串 # 从container中获取专属于我的数据 # 专属信息【key】 random_str = self.handler.get_cookie("__session__") if not random_str: return None # 客户端有随机字符串 user_info_dict = container.get(random_str,None) if not user_info_dict: return None value = user_info_dict.get(key, None) return value class BaseHandler(tornado.web.RequestHandler): def initialize(self): self.session = Session(self) class LoginHandler(BaseHandler): def get(self, *args, **kwargs): self.render('login.html' ,state = "") def post(self, *args, **kwargs): username = self.get_argument('username') password = self.get_argument('password') code =self.get_argument('code') check_code = self.session['CheckCode'] if username =="zhangyanlin" and password == "123" and code.upper() == check_code.upper(): self.write("登录成功") else: self.render('login.html',state = "验证码错误") class CheckCodeHandler(BaseHandler): def get(self, *args, **kwargs): import io import check_code mstream = io.BytesIO() img,code = check_code.create_validate_code() img.save(mstream,"GIF") self.session['CheckCode'] =code self.write(mstream.getvalue()) settings = { 'template_path':'views', 'cookie_secret': "asdasd", } application = tornado.web.Application([ (r'/login',LoginHandler), (r'/check_code',CheckCodeHandler) ],**settings) if __name__ == '__main__': application.listen(8888) tornado.ioloop.IOLoop.instance().start()
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>验证码</title> </head> <body> <form action="/login" method="post"> <p>用户名: <input type="text" name="username"> </p> <p>密码: <input type="password" name="password"> </p> <p>验证码: <input type="text" name="code"><img src="/check_code" onclick="ChangeCode();" id = "checkcode"></p> <input type="submit" value="submit"> <span>{{state}}</span> </form> <script type="text/javascript"> //当点击图片的时候,会刷新图片,这一段代码就可以实现 function ChangeCode() { var code = document.getElementById('checkcode'); code.src += "?"; } </script> </body> </html>
效果如下:
上面实例的源码文件夹:http://pan.baidu.com/s/1slL6gFV
更多Tornado知识详见:http://demo.pythoner.com/itt2zh/ch1.html
http://www.cnblogs.com/wupeiqi/articles/5702910.html
http://www.cnblogs.com/luotianshuai/p/5386494.html