tornado簡介 1、tornado概述 Tornado就是我們在 FriendFeed 的 Web 伺服器及其常用工具的開源版本。Tornado 和現在的主流 Web 伺服器框架(包括大多數 Python 的框架)有著明顯的區別:它是非阻塞式伺服器,而且速度相當快。得利於其 非阻塞的方式和對ep ...
tornado簡介
1、tornado概述
Tornado就是我們在 FriendFeed 的 Web 伺服器及其常用工具的開源版本。Tornado 和現在的主流 Web 伺服器框架(包括大多數 Python 的框架)有著明顯的區別:它是非阻塞式伺服器,而且速度相當快。得利於其 非阻塞的方式和對epoll的運用,Tornado 每秒可以處理數以千計的連接,因此 Tornado 是實時 Web 服務的一個 理想框架。我們開發這個 Web 伺服器的主要目的就是為了處理 FriendFeed 的實時功能 ——在 FriendFeed 的應用里每一個活動用戶都會保持著一個伺服器連接。(關於如何擴容 伺服器,以處理數以千計的客戶端的連接的問題,請參閱The C10K problem)
Tornado代表嵌入實時應用中最新一代的開發和執行環境。 Tornado 包含三個完整的部分:
(1)、Tornado系列工具, 一套位於主機或目標機上強大的互動式開發工具和使用程式;
(2)、VxWorks 系統, 目標板上高性能可擴展的實時操作系統;
(3)、可選用的連接主機和目標機的通訊軟體包 如乙太網、串列線、線上模擬器或ROM模擬器。
2、tornado特點
Tornado的獨特之處在於其所有開發工具能夠使用在應用開發的任意階段以及任何檔次的硬體資源上。而且,完整集的Tornado工具可以使開發人員完全不用考慮與目標連接的策略或目標存儲區大小。Tornado 結構的專門設計為開發人員和第三方工具廠商提供了一個開放環境。已有部分應用程式介面可以利用並附帶參考書目,內容從開發環境介面到連接實現。Tornado包括強大的開發和調試工具,尤其適用於面對大量問題的嵌入式開發人員。這些工具包括C和C++源碼級別的調試器,目標和工具管理,系統目標跟蹤,記憶體使用分析和自動配置. 另外,所有工具能很方便地同時運行,很容易增加和互動式開發。
3、tornado模塊索引
最重要的一個模塊是web
, 它就是包含了 Tornado 的大部分主要功能的 Web 框架。其它的模塊都是工具性質的, 以便讓 web
模塊更加有用 後面的 Tornado 攻略 詳細講解了 web
模塊的使用方法。
主要模塊
web
- FriendFeed 使用的基礎 Web 框架,包含了 Tornado 的大多數重要的功能escape
- XHTML, JSON, URL 的編碼/解碼方法database
- 對MySQLdb
的簡單封裝,使其更容易使用template
- 基於 Python 的 web 模板系統httpclient
- 非阻塞式 HTTP 客戶端,它被設計用來和web
及httpserver
協同工作auth
- 第三方認證的實現(包括 Google OpenID/OAuth、Facebook Platform、Yahoo BBAuth、FriendFeed OpenID/OAuth、Twitter OAuth)locale
- 針對本地化和翻譯的支持options
- 命令行和配置文件解析工具,針對伺服器環境做了優化
底層模塊
httpserver
- 服務於web
模塊的一個非常簡單的 HTTP 伺服器的實現iostream
- 對非阻塞式的 socket 的簡單封裝,以方便常用讀寫操作ioloop
- 核心的 I/O 迴圈
tornado框架使用
1、安裝tornado
pip install tornado 源碼安裝:https://pypi.python.org/packages/source/t/tornado/tornado-4.3.tar.gz
2、先寫一個入門級的代碼吧,相信大家都能看懂,聲明:tornado內部已經幫我們實現socket。
#!/usr/bin/env python # -*- coding:utf-8 -*- import tornado.web import tornado.ioloop class IndexHandler(tornado.web.RequestHandler): def get(self, *args, **kwargs): self.write("Hello World, My name is 張岩林") application = tornado.web.Application([ (r'/index',IndexHandler), ]) if __name__ == "__main__": application.listen(8080) tornado.ioloop.IOLoop.instance().start()
第一步:執行腳本,監聽 8080 埠
第二步:瀏覽器客戶端訪問 /index --> http://127.0.0.1:8080/index
第三步:伺服器接受請求,並交由對應的類處理該請求
第四步:類接受到請求之後,根據請求方式(post / get / delete ...)的不同調用並執行相應的方法
第五步:然後將類的方法返回給瀏覽器
tornado路由系統
在tornado web框架中,路由表中的任意一項是一個元組,每個元組包含pattern(模式)和handler(處理器)。當httpserver接收到一個http請求,server從接收到的請求中解析出url path(http協議start line中),然後順序遍歷路由表,如果發現url path可以匹配某個pattern,則將此http request交給web應用中對應的handler去處理。
由於有了url路由機制,web應用開發者不必和複雜的http server層代碼打交道,只需要寫好web應用層的邏輯(handler)即可。Tornado中每個url對應的是一個類。
#!/usr/bin/env python # -*- coding:utf-8 -*- __auth__ = "zhangyanlin" import tornado.web import tornado.ioloop class IndexHandler(tornado.web.RequestHandler): def get(self, *args, **kwargs): self.write("Hello World, My name is 張岩林") class LoginHandler(tornado.web.RequestHandler): def get(self, *args, **kwargs): self.write("<input type = 'text'>") class RegisterHandler(tornado.web.RequestHandler): def get(self, *args, **kwargs): self.write("<input type = 'password'>") application = tornado.web.Application([ (r'/index/(?P<page>\d*)',IndexHandler), # 基礎正則路由 (r'/login',LoginHandler), (r'/register',RegisterHandler), ]) # 二級路由映射 application.add_handlers('buy.zhangyanlin.com$',[ (r'/login', LoginHandler), ]) if __name__ == "__main__": application.listen(8080) tornado.ioloop.IOLoop.instance().start()
觀察所有的網頁的內容,下麵都有分頁,當點擊下一頁後面的數字也就跟著變了,這種就可以用基礎正則路由來做,下麵我來給大家下一個網頁分頁的案例吧
#!/usr/bin/env python # -*- coding:utf-8 -*- __auth__ = "zhangyanlin" import tornado.web import tornado.ioloop LIST_INFO = [ {'username':'zhangyanlin','email':'[email protected]'} ] for i in range(200): temp = {'username':str(i)+"zhang",'email':str(i)+"@163.com"} LIST_INFO.append(temp) class Pagenation: def __init__(self,current_page,all_item,base_url): #當前頁 內容總數 目錄 try: page = int(current_page) except: page = 1 if page < 1: page = 1 all_page,c = divmod(all_item,5) if c > 0: all_page +=1 self.current_page = page self.all_page = all_page self.base_url = base_url @property def start(self): return (self.current_page - 1) * 5 @property def end(self): return self.current_page * 5 def string_pager(self): list_page = [] if self.all_page < 11: s = 1 t = self.all_page + 1 else: if self.current_page < 6: s = 1 t = 12 else: if (self.current_page + 5) < self.all_page: s = self.current_page-5 t = self.current_page + 6 else: s = self.all_page - 11 t = self.all_page +1 first = '<a href = "/index/1">首頁</a>' list_page.append(first) # 當前頁 if self.current_page == 1: prev = '<a href = "javascript:void(0):">上一頁</a>' else: prev = '<a href = "/index/%s">上一頁</a>'%(self.current_page-1,) list_page.append(prev) #頁碼 for p in range(s,t): if p== self.current_page: temp = '<a class = "active" href = "/index/%s">%s</a>'%(p,p) else: temp = '<a href = "/index/%s">%s</a>' % (p, p) list_page.append(temp) # 尾頁 if self.current_page == self.all_page: nex = '<a href = "javascript:void(0):">下一頁</a>' else: nex = '<a href = "/index/%s">下一頁</a>' % (self.current_page + 1,) list_page.append(nex) last = '<a href = "/index/%s">尾頁</a>'%(self.all_page) list_page.append(last) #跳轉 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> ''' list_page.append(jump) list_page.append(script) str_page = "".join(list_page) return str_page class IndexHandler(tornado.web.RequestHandler): def get(self, page): obj = Pagenation(page,len(LIST_INFO),'/index/') current_list = LIST_INFO[obj.start:obj.end] str_page = obj.string_pager() self.render('index.html', list_info=current_list, current_page=obj.current_page, str_page=str_page) application = tornado.web.Application([ (r'/index/(?P<page>\d*)',IndexHandler) ]) if __name__ == "__main__": application.listen(8080) tornado.ioloop.IOLoop.instance().start()tornado服務端demo
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> .pager a{ display: inline-block; padding: 5px 6px; margin: 10px 3px; border: 1px solid #2b669a; text-decoration:none; } .pager a.active{ background-color: #2b669a; color: white; } </style> </head> <body> <h3>顯示數據</h3> <table border="1"> <thead> <tr> <th>用戶名</th> <th>郵箱</th> </tr> </thead> <tbody> {% for line in list_info %} <tr> <td>{{line['username']}}</td> <td>{{line['email']}}</td> </tr> {% end %} </tbody> </table> <div class="pager"> {% raw str_page %} </div> </body> </html>前端HTML文件
註:兩個文件必須放在同一個文件夾下,中間前端代碼中有用到XSS攻擊和模板語言,這兩個知識點下麵會詳細解釋
tornado 模板引擎
Tornao中的模板語言和django中類似,模板引擎將模板文件載入記憶體,然後將數據嵌入其中,最終獲取到一個完整的字元串,再將字元串返回給請求者。
Tornado 的模板支持“控制語句”和“表達語句”,控制語句是使用 {%
和 %}
包起來的 例如 {% if len(items) > 2 %}
。表達語句是使用 {{
和 }}
包起來的,例如 {{ items[0] }}
。
控制語句和對應的 Python 語句的格式基本完全相同。我們支持 if
、for
、while
和 try
,這些語句邏輯結束的位置需要用 {% end %}
做標記。還通過 extends
和 block
語句實現了模板繼承。這些在 template
模塊 的代碼文檔中有著詳細的描述。
註:在使用模板前需要在setting中設置模板路徑:"template_path" : "views"
settings = { 'template_path':'views', #設置模板路徑,設置完可以把HTML文件放置views文件夾中 'static_path':'static', # 設置靜態模板路徑,設置完可以把css,JS,Jquery等靜態文件放置static文件夾中 'static_url_prefix': '/sss/', #導入時候需要加上/sss/,例如<script src="/sss/jquery-1.9.1.min.js"></script> 'cookie_secret': "asdasd", #cookie生成秘鑰時候需提前生成隨機字元串,需要在這裡進行渲染 'xsrf_cokkies':True, #允許CSRF使用 }
application = tornado.web.Application([
(r'/index',IndexHandler),
],**settings) #需要在這裡載入
文件目錄結構如下:
1、模板語言基本使用for迴圈,if..else使用,自定義UIMethod以UIModule
#!/usr/bin/env python # -*- coding:utf-8 -*- import tornado.ioloop import tornado.web import uimodule as md import uimethod as mt INPUT_LIST = [] class MainHandler(tornado.web.RequestHandler): def get(self, *args, **kwargs): name = self.get_argument('xxx',None) if name: INPUT_LIST.append(name) self.render("index.html",npm = "NPM88888",xxoo = INPUT_LIST) def post(self, *args, **kwargs): name = self.get_argument('xxx') INPUT_LIST.append(name) self.render("index.html", npm = "NPM88888", xxoo = INPUT_LIST) # self.write("Hello, World!!!") settings = { 'template_path':'tpl', # 模板路徑的配置 'static_path':'statics', # 靜態文件路徑的配置 'ui_methods':mt, # 自定義模板語言 'ui_modules':md, # 自定義模板語言 } #路由映射,路由系統 application = tornado.web.Application([ (r"/index",MainHandler), ],**settings) if __name__ == "__main__": # 運行socket application.listen(8000) tornado.ioloop.IOLoop.instance().start()start.py
from tornado.web import UIModule from tornado import escape class custom(UIModule): def render(self, *args, **kwargs): return "張岩林"uimodule
def func(self,arg): return arg.lower()uimethod
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link type="text/css" rel="stylesheet" href="static/commons.css"> </head> <body> <script src="static/zhang.js"></script> <h1>Hello world</h1> <h1>My name is zhangyanlin</h1> <h1>輸入內容</h1> <form action="/index" method="post"> <input type="text" name="xxx"> <input type="submit" value="提交"> </form> <h1>展示內容</h1> <h3>{{ npm }}</h3> <h3>{{ func(npm)}}</h3> <h3>{% module custom() %}</h3> <ul> {% for item in xxoo %} {% if item == "zhangyanlin" %} <li style="color: red">{{item}}</li> {% else %} <li>{{item}}</li> {% end %} {% end %} </ul> </body> </html>index.html
2、母板繼承
(1)、相當於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>layout.html
(2)、再子板中相應的位置繼承模板的格式
{% 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
3、導入內容
<div> <ul> <li>張岩林帥</li> <li>張岩林很帥</li> <li>張岩林很很帥</li> </ul> </div>content.html
<!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>index.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: for handler.static_url 的別名 xsrf_form_html: handler.xsrf_form_html 的別名
當你製作一個實際應用時,你會需要用到 Tornado 模板的所有功能,尤其是 模板繼承功能。所有這些功能都可以在template
模塊 的代碼文檔中瞭解到。(其中一些功能是在 web
模塊中實現的,例如 UIModules
)
tornado cookie
Cookie,有時也用其複數形式Cookies,指某些網站為了辨別用戶身份、進行session跟蹤而儲存在用戶本地終端上的數據(通常經過加密)。定義於RFC2109和2965都已廢棄,最新取代的規範是RFC6265。(可以叫做瀏覽器緩存)
1、cookie的基本操作
#!/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()
2、加密cookie(簽名)
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解密,獲取值內容
用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 == '張岩林' and password == '123': self.set_secure_cookie('login_user', '張岩林') self.redirect('/') else: self.render('login.html', **{'status': '用戶名或密碼錯誤'}) settings = { 'template_path': 'template', 'static_path': 'static', 'static_url_prefix': '/static/', 'cookie_secret': 'zhangyanlinhaoshuai', } application = tornado.web.Application([ (r"/index", MainHandler), (r"/login", LoginHandler), ], **settings) if __name__ == "__main__": application.listen(8888) tornado.ioloop.IOLoop.instance().start()