前言
Tornado是使用Python編寫的一個強大的、可擴展的Web服務器。它在處理嚴峻的網絡流量時表現得足夠強健,但卻在建立和編寫時有着足夠的輕量級,並可以被用在大量的應用和工具中。css
Tornado是基於Bret Taylor和其餘人員爲FriendFeed所開發的網絡服務框架,當FriendFeed被Facebook收購後得以開源。不一樣於那些最多隻能達到10,000個併發鏈接的傳統網絡服務器,Tornado在設計之初就考慮到了性能因素,旨在解決C10K問題,這樣的設計使得其成爲一個擁有很是高性能的框架。此外,它還擁有處理安全性、用戶驗證、社交網絡以及與外部服務(如數據庫和網站API)進行異步交互的工具。html
Tornado所作的是可以快速簡單地編寫高速的Web應用。若是編寫一個可擴展的社交應用、實時分析引擎,或RESTful API,那麼簡單而強大的Python,以及Tornado正是爲你準備的!python
總之,Tornado也很強大!!!jquery
下載和安裝ios
# pip安裝 pip3 install tornado # 源碼安裝 tar xvzf tornado-4.4.1.tar.gz cd tornado-4.4.1 python setup.py build sudo python setup.py install
源碼下載:tornado-1.2.1.tar.gz、 tornado-4.4.1.tar.gzgit
Tornado各模塊github
# 主要模塊 web - FriendFeed 使用的基礎 Web 框架,包含了 Tornado 的大多數重要的功能 escape - XHTML, JSON, URL 的編碼/解碼方法 database - 對 MySQLdb 的簡單封裝,使其更容易使用 template - 基於 Python 的 web 模板系統 httpclient - 非阻塞式 HTTP 客戶端,它被設計用來和 web 及 httpserver 協同工做 auth - 第三方認證的實現(包括 Google、Facebook、Yahoo BBAuth、FriendFeed...) locale - 針對本地化和翻譯的支持 options - 命令行和配置文件解析工具,針對服務器環境作了優化 # 底層模塊 httpserver - 服務於 web 模塊的一個很是簡單的 HTTP 服務器的實現 iostream - 對非阻塞式的 socket 的簡單封裝,以方便經常使用讀寫操做 ioloop - 核心的 I/O 循環
Hello,world
# 經典HelloWorld示例 import tornado.ioloop import tornado.web class MainHandler(tornado.web.RequestHandler): def get(self): self.write("Hello, world") application = tornado.web.Application([ (r"/", MainHandler), ]) if __name__ == "__main__": application.listen(8888) tornado.ioloop.IOLoop.instance().start()
路由系統
路由系統其實就是 url 和 類 的對應關係,這裏不一樣於其餘框架,其餘不少框架均是 url 對應 函數,Tornado中每一個url對應的是一個類。web
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
#!/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
StoryHandler(tornado.web.RequestHandler):
def
get(
self
, story_id):
self
.write(
"You requested the story "
+
story_id)
class
BuyHandler(tornado.web.RequestHandler):
def
get(
self
):
self
.write(
"buy.wupeiqi.com/index"
)
application
=
tornado.web.Application([
(r
"/index"
, MainHandler),
(r
"/story/([0-9]+)"
, StoryHandler),
])
application.add_handlers(
'buy.wupeiqi.com$'
, [
(r
'/index'
,BuyHandler),
])
if
__name__
=
=
"__main__"
:
application.listen(
80
)
tornado.ioloop.IOLoop.instance().start()
|
Tornado中原生支持二級域名的路由,如:正則表達式
方法
一、處理程序和參數算法
請求來時,程序會用正則匹配相應路由地址,並交付於 tornado.web.RequestHandler 的子類處理;子類會根據請求方式(post / get / delete ...)的不一樣調用並執行相應的方法,方法返回字符串內容併發送到瀏覽器。
self.write("<h1>Hello, World</h1>") # html代碼直接寫在瀏覽器客戶端 self.render("index.html") # 返回html文件,調用render_string(),內部實際上是打開並讀取文件,返回內容 self.redirect("http://www.baidu.com",permanent=False) # 跳轉重定向,參數表明是否永久重定向 name = self.get_argument("name") # 獲取客戶端傳入的參數值 name = self.get_arguments("name") # 獲取多個值,類別形式 file = self.request.files["filename"] # 獲取客戶端上傳的文件 raise tornado.web.HTTPError(403) # 返回錯誤信息給客戶端
二、重寫 RequestHandler 的方法函數
對於一個請求的處理過程代碼調用次序以下:
- 程序爲每個請求建立一個 RequestHandler 對象;
- 程序調用
initialize()
函數,這個函數的參數是Application
配置中的關鍵字參數定義。(initialize
方法是 Tornado 1.1 中新添加的,舊版本中你須要重寫__init__
以達到一樣的目的)initialize
方法通常只是把傳入的參數存到成員變量中,而不會產生一些輸出或者調用像send_error
之類的方法。 - 程序調用
prepare()
。不管使用了哪一種 HTTP 方法,prepare
都會被調用到,所以這個方法一般會被定義在一個基類中,而後在子類中重用。prepare
能夠產生輸出信息。若是它調用了finish
(或send_error` 等函數),那麼整個處理流程就此結束。 - 程序調用某個 HTTP 方法:例如
get()
、post()
、put()
等。若是 URL 的正則表達式模式中有分組匹配,那麼相關匹配會做爲參數傳入方法。
重寫 initialize()
函數(會在建立RequestHandler對象後調用):
class ProfileHandler(tornado.web.RequestHandler): def initialize(self,database): self.database = database def get(self): self.write("result:" + self.database) application = tornado.web.Application([ (r"/init", ProfileHandler, dict(database="database")) ])
模板引擎
Tornao中的模板語言和django中相似,模板引擎將模板文件載入內存,而後將數據嵌入其中,最終獲取到一個完整的字符串,再將字符串返回給請求者。
Tornado 的模板支持「控制語句」和「表達語句」,控制語句是使用 {%
和 %}
包起來的 例如 {% if len(items) > 2 %}
。表達語句是使用 {{
和 }}
包起來的,例如 {{ items[0] }}
。
控制語句和對應的 Python 語句的格式基本徹底相同。咱們支持 if
、for
、while
和 try
,這些語句邏輯結束的位置須要用 {% end %}
作標記。還經過 extends
和 block
語句實現了模板繼承。這些在 template
模塊 的代碼文檔中有着詳細的描述。
注:在使用模板前須要在setting中設置模板路徑:"template_path" : "tpl"
一、基本使用
#!/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", list_info = [11,22,33]) application = tornado.web.Application([ (r"/index", MainHandler), ]) if __name__ == "__main__": application.listen(8888) 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" /> </head> <body> <div> <ul> {% for item in list_info %} <li>{{item}}</li> {% end %} </ul> </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: for handler.static_url 的別名 xsrf_form_html: handler.xsrf_form_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" /> {% 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>1024</li> <li>42區</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>
四、自定義UIMethod以UIModule
a,定義
# uimethods.py 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>nick</h1>') #return escape.xhtml_escape('<h1>suoning</h1>')
b,註冊
#!/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()
c,使用
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title>Nick</title> <link href="{{static_url("commons.css")}}" rel="stylesheet" /> </head> <body> <h1>hello</h1> {% module custom(123) %} {{ tab() }} </body>
靜態文件
在應用配置 settings 中指定 static_path
選項來提供靜態文件服務;
在應用配置 settings 中指定 static_url_prefix
選項來提供靜態文件前綴服務;
在導入靜態文件時用 {{static_url('XX.css')}}
方式實現主動緩存靜態文件;
settings = { 'template_path': 'views', 'static_path': 'static', 'static_url_prefix': '/static/', }
# html使用 <head lang="en"> <title>Nick</title> <link href="{{static_url("commons.css")}}" rel="stylesheet" /> </head>
Cookie
一、基本Cookie
set_cookie
方法在用戶的瀏覽中設置 cookie;
get_cookie
方法在用戶的瀏覽中獲取 cookie。
class MainHandler(tornado.web.RequestHandler): def get(self): if not self.get_cookie("mycookie"): self.set_cookie("mycookie", "myvalue") self.write("Your cookie was not set yet!") else: self.write("Your cookie was set!")
二、加密Cookie(簽名)
Cookie 很容易被惡意的客戶端僞造。加入你想在 cookie 中保存當前登錄用戶的 id 之類的信息,你須要對 cookie 做簽名以防止僞造。Tornado 經過 set_secure_cookie
和 get_secure_cookie
方法直接支持了這種功能。 要使用這些方法,你須要在建立應用時提供一個密鑰,名字爲 cookie_secret
。 你能夠把它做爲一個關鍵詞參數傳入應用的設置中:
class MainHandler(tornado.web.RequestHandler): def get(self): if not self.get_secure_cookie("mycookie"): self.set_secure_cookie("mycookie", "myvalue") self.write("Your cookie was not set yet!") else: self.write("Your cookie was set!") application = tornado.web.Application([ (r"/", MainHandler), ], cookie_secret="61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=")
加密Cookice的本質:
寫cookie過程:
- 將值進行base64加密
- 對除值之外的內容進行簽名,哈希算法(沒法逆向解析)
- 拼接 簽名 + 加密值
讀cookie過程:
- 讀取 簽名 + 加密值
- 對簽名進行驗證
- base64解密,獲取值內容
注:許多API驗證機制和安全cookie的實現機制相同。
#!/usr/bin/env python # -*- coding:utf-8 -*- import tornado.ioloop import tornado.web class MainHandler(tornado.web.RequestHandler): def get(self): login_user = self.get_secure_cookie("login_user", None) if login_user: self.write(login_user) else: self.redirect('/login') 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 == 'nick' and password == 'nicknick': self.set_secure_cookie('login_user', 'nick') self.redirect('/') else: self.render('login.html', **{'status': '用戶名或密碼錯誤'}) settings = { 'template_path': 'template', 'static_path': 'static', 'static_url_prefix': '/static/', 'cookie_secret': 'aiuasdhflashjdfoiuashdfiuh' } application = tornado.web.Application([ (r"/index", MainHandler), (r"/login", LoginHandler), ], **settings) if __name__ == "__main__": application.listen(8888) tornado.ioloop.IOLoop.instance().start()
三、JavaScript操做Cookie
因爲Cookie保存在瀏覽器端,因此在瀏覽器端也可使用JavaScript來操做Cookie
/* 設置cookie,指定秒數過時 */ 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(); }
對於參數:
- domain 指定域名下的cookie
- path 域名下指定url中的cookie
- secure https使用
注:jQuery中也有指定的插件 jQuery Cookie 專門用於操做cookie,猛擊這裏
異步非阻塞
一、基本使用
裝飾器 + Future 從而實現Tornado的異步非阻塞
class AsyncHandler(tornado.web.RequestHandler): @gen.coroutine def get(self): future = Future() future.add_done_callback(self.doing) yield future # 或 # tornado.ioloop.IOLoop.current().add_future(future,self.doing) # yield future def doing(self,*args, **kwargs): self.write('async') self.finish()
當發送GET請求時,因爲方法被@gen.coroutine裝飾且yield 一個 Future對象,那麼Tornado會等待,等待用戶向future對象中放置數據或者發送信號,若是獲取到數據或信號以後,就開始執行doing方法。
異步非阻塞體如今當在Tornaod等待用戶向future對象中放置數據時,還能夠處理其餘請求。
注意:在等待用戶向future對象中放置數據或信號時,此鏈接是不斷開的。
2,httpclient類庫
Tornado提供了httpclient類庫用於發送Http請求,其配合Tornado的異步非阻塞使用。
class AsyncHandler(tornado.web.RequestHandler): @gen.coroutine def get(self): from tornado import httpclient http = httpclient.AsyncHTTPClient() yield http.fetch("http://www.google.com", self.endding) def endding(self, response): print(len(response.body)) self.write('ok') self.finish()
3,自定義web非阻塞框架
import socket import select import time class HttpRequest(object): """ 用戶封裝用戶請求信息 """ def __init__(self, content): """ :param content:用戶發送的請求數據:請求頭和請求體 """ self.content = content self.header_bytes = bytes() self.body_bytes = bytes() self.header_dict = {} self.method = "" self.url = "" self.protocol = "" self.initialize() self.initialize_headers() def initialize(self): temp = self.content.split(b'\r\n\r\n', 1) if len(temp) == 1: self.header_bytes += temp else: h, b = temp self.header_bytes += h self.body_bytes += b @property def header_str(self): return str(self.header_bytes, encoding='utf-8') def initialize_headers(self): headers = self.header_str.split('\r\n') first_line = headers[0].split(' ') if len(first_line) == 3: self.method, self.url, self.protocol = headers[0].split(' ') for line in headers: kv = line.split(':') if len(kv) == 2: k, v = kv self.header_dict[k] = v class Future(object): def __init__(self, timeout=0): self.result = None self.timeout = timeout self.start = time.time() def main(request): f = Future(5) return f def index(request): return "indexasdfasdfasdf" routers = [ ('/main/', main), ('/index/', index), ] def run(): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind(("127.0.0.1", 9999,)) sock.setblocking(False) sock.listen(128) inputs = [sock, ] # inputs.append(sock) async_request_dict = { # 'socket': futrue } while True: rlist, wlist, elist = select.select(inputs, [], [], 0.05) for r in rlist: if r == sock: """新請求到來""" conn, addr = sock.accept() conn.setblocking(False) inputs.append(conn) else: """客戶端發來數據""" data = b"" while True: try: chunk = r.recv(1024) data = data + chunk except Exception as e: chunk = None if not chunk: break # data進行處理:請求頭和請求體 request = HttpRequest(data) # 1. 請求頭中獲取url # 2. 去路由中匹配,獲取指定的函數 # 3. 執行函數,獲取返回值 # 4. 將返回值 r.sendall(b'alskdjalksdjf;asfd') import re flag = False func = None for route in routers: if re.match(route[0], request.url): flag = True func = route[1] break if flag: result = func(request) if isinstance(result, Future): async_request_dict[r] = result else: r.sendall(bytes(result, encoding='utf-8')) inputs.remove(r) r.close() else: r.sendall(b"404") inputs.remove(r) r.close() for conn in async_request_dict.keys(): future = async_request_dict[conn] start = future.start timeout = future.timeout ctime = time.time() if (start + timeout) <= ctime: future.result = b"timeout" if future.result: conn.sendall(future.result) conn.close() del async_request_dict[conn] inputs.remove(conn) if __name__ == '__main__': run()
自定製Web組件
一,session
Tornado框架中,默認執行Handler的get/post等方法以前默認會執行 initialize方法,因此能夠經過自定義的方式使得全部請求在處理前執行操做...
session其實就是定義在服務器端用於保存用戶回話的容器,其必須依賴cookie才能實現。
import tornado.ioloop import tornado.web from controllers.account import LoginHandler from controllers.home import HomeHandler import time import hashlib class Cache(object): """ 將session保存在內存 """ def __init__(self): self.container = {} def __contains__(self, item): return item in self.container def initial(self,random_str): self.container[random_str] = {} def get(self,random_str,key): return self.container[random_str].get(key) def set(self,random_str,key,value): self.container[random_str][key] = value def delete(self,random_str,key): del self.container[random_str][key] def open(self): pass def close(self): pass def clear(self,random_str): del self.container[random_str] class Memcache(object): def __init__(self): pass def get(self,key): pass def set(self,key,value): pass def delete(self,key): pass def open(self): pass def close(self): pass P = Cache class Session(object): def __init__(self,handler): self.handler = handler self.random_str = None self.ppp = P() self.ppp.open() # 去用戶請求信息中獲取session_id,若是沒有,新用戶 client_random_str = self.handler.get_cookie('session_id') if not client_random_str: "新用戶" self.random_str = self.create_random_str() container[self.random_str] = {} else: if client_random_str in self.ppp: "老用戶" self.random_str = client_random_str else: "非法用戶" self.random_str = self.create_random_str() self.ppp.initial(self.random_str) ctime = time.time() self.handler.set_cookie('session_id',self.random_str,expires=ctime+1800) self.ppp.close() def create_random_str(self): v = str(time.time()) m = hashlib.md5() m.update(bytes(v,encoding='utf-8')) return m.hexdigest() def __setitem__(self, key, value): self.ppp.open() self.ppp.set(self.random_str,key,value) self.ppp.close() def __getitem__(self, key): self.ppp.open() v = self.ppp.get(self.random_str,key) self.ppp.close() return v def __delitem__(self, key): self.ppp.open() self.ppp.delete(self.random_str,key) self.ppp.close() def clear(self): self.ppp.open() self.ppp.clear(self.random_str) self.ppp.close() class Foo(object): def initialize(self): # self是MainHandler對象 self.session = Session(self) super(Foo,self).initialize() class HomeHandler(Foo,tornado.web.RequestHandler): def get(self): user = self.session['uuuuu'] if not user: self.redirect("http://www.oldboyedu.com") else: self.write(user) class LoginHandler(Foo,tornado.web.RequestHandler): def get(self): self.session['uuuuu'] = 'root' self.redirect('/home') class TestHandler(tornado.web.RequestHandler): def get(self): self.set_cookie('k1', 'vvv', expires=time.time()+20) class ShowHandler(tornado.web.RequestHandler): def get(self): self.write(self.get_cookie('k1')) application = tornado.web.Application([ (r"/login", LoginHandler), (r"/home", HomeHandler), (r"/test", TestHandler), (r"/show", ShowHandler), ]) if __name__ == "__main__": application.listen(9999) tornado.ioloop.IOLoop.instance().start()