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