文章目錄
- Tornado簡介
- Tornado與Django的比較
- 使用Tornado
-
- (1) 安裝
- (2)導入
- Tornado的執行流程
- 快速開始
-
- (1) 建立處理器類,在類中定義HTTP方法
- (2) 為Tornado設定模闆目錄
- (3) 設定Tornado的路由關系
- (4) 啟動第一個伺服器
- (5) 代碼執行個體
- Tornado路由系統
-
- (1)請求方法路由
- (2) 二級路由
- 模闆引擎
-
- (1) 模闆文法
- (2) 模闆函數
- (3) 自定義UIMethod和UIModule
- 靜态緩存
- 公共元件
- XSS跨站腳本攻擊與CSRF跨域僞造請求
- Cookie與Session
- 檔案上傳
-
- (1) 使用Form表單
- (2) 使用AjaxXMLHttpRequest
- (3) 使用jQuery的Ajax
- (4) 使用iframe預覽圖檔
Tornado簡介
Tornado是一種Web伺服器軟體的開源版本,Tornado是非阻塞式伺服器,速度很快。這得益于其非阻塞式和對epoll的運用,Tornado每秒可以處理數以千計的連接配接,是以Tornado是實時Web服務的一個理想的架構。Tornado是現在應用最為廣泛的Web架構,其具有以下優勢:
- 輕量級Web架構
- 異步非阻塞IO處理
- 出色的抗負載能力
- 優秀的處理性能,不依賴多程序和多線程
Tornado與Django的比較
- Django是重量級的Web架構,功能齊全,注重開發效率
- Django内置管理背景Django Admin
- Django内置封裝完善的ORM操作
- Django提供Session功能
- Django與Tornado相比,Django高耦合
- Tornado與Django相比,入門門檻較高
使用Tornado
(1) 安裝
pip install tornado
(2)導入
import tornado.ioloop
import tornado.web
Tornado的執行流程
- 執行Python檔案,監聽設定的端口
- 浏覽器請求伺服器,經過路由
- 路由比對對應的處理器類
- 根據請求類型執行指定處理器類中的處理函數
- 傳回處理結果,用戶端浏覽器渲染頁面
快速開始
(1) 建立處理器類,在類中定義HTTP方法
class MainHandler(tornado.web.RequestHandler):
def get(self):
pass
def post(self):
pass
... # HTTP中的方法
(2) 為Tornado設定模闆目錄
settings = {
"template_path":"模闆路徑", # 設定模闆目錄
}
(3) 設定Tornado的路由關系
application = tornado.web.Application([(r"/", MainHandler),], **settings) # 傳入設定參數
(4) 啟動第一個伺服器
if __name__ == "__main__":
application.listen(8009) # 設定監聽端口
tornado.ioloop.IOLoop.instance().start() # 啟動服務
(5) 代碼執行個體
# /controller/One.py
import tornado.ioloop
import tornado.web
mylist = []
mylist2 = []
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.render("index.html", list = mylist, list2 = mylist,)
def post(self):
name = self.get_argument("name")
love = self.get_argument("mylove")
mylist.append(name)
mylist2.append(love)
self.render('index.html', list = mylist, list2 = mylist2,)
settings = {
"template_path":"../views", # 設定模闆目錄
}
application = tornado.web.Application([(r"/index", MainHandler),], **settings) # 傳入設定參數
if __name__ == "__main__":
application.listen(8009) # 設定監聽端口
tornado.ioloop.IOLoop.instance().start() # 啟動服務
<!-- /views/index.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Index</title>
</head>
<body style="background-color: darkorange;color: aliceblue">
<center>
<h1>Hello,Tornado.</h1>
</center>
<center>
<form action="/index" name="formdata" method="post">
姓名:<input type="text" name="name">
愛好:<select name="mylove" id="">
<option value="movie">電影</option>
<option value="football">足球</option>
<option value="music">音樂</option>
</select>
<input type="submit" value="送出">
<input type="reset" value="重置">
</form>
</center>
<center>
<h2>送出的資訊</h2>
<h3>
{% for i in list %}
姓名:{{ i }}
{% end %}
{% for x in list2 %}
愛好:{{ x }}
{% end %}
</h3>
</center>
</body>
</html>
Tornado路由系統
Tornado中的URL對應的不是處理函數而是類,在處理類中定義HTTP方法。Tornado中的路由系統可以分為:靜态路由、動态路由、請求方法路由、二級路由。設定路由的時候可以使用URL對應的方式,也可以使用裝飾器的方式進行路由映射。
- 裝飾器映射的方式:
@root.route(‘路由URL’)
def MethodHandler(self):
# Some...
root.run(host=’IP位址’, port=端口)
- 靜态路由
application = tornado.web.Application([(r"/index/", MainHandler),], **settings)
- 基于正則的動态路由
application = tornado.web.Application([(r"/index/(\d+)", MainHandler),], **settings)
同時處理器函數也可以傳入此參數:
class MainHandler(tornado.web.RequestHandler):
def get(self,id):
self.render('index.html',id=id)
def post(self):
pass
使用裝飾器操作:
@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')
(1)請求方法路由
@root.route('/hello/', method='POST')
# 如果使用@root.get()表示裝飾器下的函數隻接受get請求
def index():
...
@root.get('/hello/')
def index():
...
@root.post('/hello/')
def index():
...
@root.put('/hello/')
def index():
...
@root.delete('/hello/')
def index():
...
(2) 二級路由
Host Header | URL reg | Handler |
---|---|---|
safe | /index/\d* | IndexHandler |
safe | /admin/\w* | AdminHandler |
safe | /car/\w* | CarHandler |
.* | /index/\w* | HomeHandler |
.* | /pro/\w* | ProHandler |
.* | /.* | AllHandler |
application = tornado.web.Application('www.test.com$',[(r"/index/", MainHandler),], **settings)
使用裝飾器的方式:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# file:index.py
from bottle import template, Bottle
from bottle import static_file
root = Bottle()
@root.route('/hello/')
def index():
# Some...
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 -*-
# file:app01.py
from bottle import template, Bottle
app01 = Bottle()
@app01.route('/hello/', method='GET')
def index():
# Some...
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# file:app02.py
from bottle import template, Bottle
app02 = Bottle()
@app02.route('/hello/', method='GET')
def index():
# Some...
模闆引擎
(1) 模闆文法
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>模闆引擎文法</title>
</head>
<body>
<!--單個變量的引用-->
{{ x }}
<!--單行Python代碼-->
% temp = "Hello"
<!--多行Python代碼-->
<%
Some python codes...
%>
<!--HTML與Python代碼混合-->
{% for i in list %}
<h3>{{ i }}</h3>
{% end %}
<!—使用模闆自定義編輯塊-->
{% block RenderBody %}{% end %}
<!—在其他HTML檔案中使用同樣的方式在塊中間填充資料-->
</body>
</html>
(2) 模闆函數
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 的別名
(3) 自定義UIMethod和UIModule
定義:
# file:uimethods.py
def 自定義函數(self):
# Some...
# uimodules.py
from tornado.web import UIModule
from tornado import escape
class 自定義類(UIModule):
def render(self, *args, **kwargs):
return escape.xhtml_escape('<h1>Test</h1>')
注冊:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# file:test.py
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()
使用:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<link href="{{static_url(" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" commons.css")}}" rel="stylesheet" />
</head>
<body>
<h1>hello</h1>
{% module 自定義類名(123) %}
{{ 自定義函數() }}
</body>
</html>
靜态緩存
對于靜态檔案,可以配置靜态檔案的目錄和前段使用時的字首,并且Tornaodo還支援靜态檔案緩存。
配置:
settings = {
'static_url_prefix': '/static/',
}
使用:
<link href="{{static_url(" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" commons.css")}}" rel="stylesheet" />
靜态檔案緩存的實作:
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()
公共元件
Web架構的本質就是接受使用者請求,處理使用者請求,響應請求内容。由開發人員定制使用者的請求處理,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 #環境相關,如果上面的這些請求資訊沒有滿足需求,就在這裡找
XSS跨站腳本攻擊與CSRF跨域僞造請求
XSS:惡意攻擊者往Web頁面裡插入惡意腳本代碼,當使用者浏覽該頁之時,嵌入其中Web裡面的腳本代碼會被執行,進而達到惡意攻擊使用者的特殊目的。
- CSRF配置
settings = {
"xsrf_cookies": True,
}
- 普通的表單使用
<form action=" " method="post">
{{ xsrf_form_html() }}
Some...
</form>
- Ajax使用
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 + ")"));
}});
};
Cookie與Session
Tornado中可以對cookie進行操作,使用
set_cookie(‘ Key’,’Value’)
方法進行設定cookie,使用
get_cookie(‘Key’)
擷取Cookie值。由于Cookie很容易被用戶端僞造,假如需要在cookie中儲存目前的使用者登入狀态,需要對cookie進行簽名。通過
set_secure_cookie(‘Key’,’Value’)
和
get_secure_cookie(‘Key’)
方法設定和使用,但是需要在使用的時候建立一個密鑰,叫做
cookie_secret
。
settings = {
'cookie_secret': '一堆字元串'
}
- 寫入cookie的過程
- 将值進行base64加密
- 對除去值以外的内容進行簽名,使用無法逆向破解的雜湊演算法
- 拼接簽名與加密值
- 讀取cookie的過程
- 讀取加密的内容
- 對簽名進行驗證
- 進行base64解密,擷取值的内容
- JavaScript操作cookie
function setCookie(name,value,expires){
var current_date = new Date();
current_date.setSeconds(current_date.getSeconds() + 5);
document.cookie = name + "= "+ value +";expires=" + current_date.toUTCString();
}
- 自定義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
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)
- 一緻性哈希
#!/usr/bin/env python
#coding:utf-8
import sys
import math
from bisect import bisect
if sys.version_info >= (2, 5):
import hashlib
md5_constructor = hashlib.md5
else:
import md5
md5_constructor = md5.new
class HashRing(object):
"""一緻性哈希"""
def __init__(self,nodes):
'''初始化
nodes : 初始化的節點,其中包含節點已經節點對應的權重
預設每一個節點有32個虛拟節點
對于權重,通過多建立虛拟節點來實作
如:nodes = [
{'host':'127.0.0.1:8000','weight':1},
{'host':'127.0.0.1:8001','weight':2},
{'host':'127.0.0.1:8002','weight':1},
]
'''
self.ring = dict()
self._sorted_keys = []
self.total_weight = 0
self.__generate_circle(nodes)
def __generate_circle(self,nodes):
for node_info in nodes:
self.total_weight += node_info.get('weight',1)
for node_info in nodes:
weight = node_info.get('weight',1)
node = node_info.get('host',None)
virtual_node_count = math.floor((32*len(nodes)*weight) / self.total_weight)
for i in xrange(0,int(virtual_node_count)):
key = self.gen_key_thirty_two( '%s-%s' % (node, i) )
if self._sorted_keys.__contains__(key):
raise Exception('該節點已經存在.')
self.ring[key] = node
self._sorted_keys.append(key)
def add_node(self,node):
''' 建立節點
node : 要添加的節點,格式為:{'host':'127.0.0.1:8002','weight':1},其中第一個元素表示節點,第二個元素表示該節點的權重。
'''
node = node.get('host',None)
if not node:
raise Exception('節點的位址不能為空.')
weight = node.get('weight',1)
self.total_weight += weight
nodes_count = len(self._sorted_keys) + 1
virtual_node_count = math.floor((32 * nodes_count * weight) / self.total_weight)
for i in xrange(0,int(virtual_node_count)):
key = self.gen_key_thirty_two( '%s-%s' % (node, i) )
if self._sorted_keys.__contains__(key):
raise Exception('該節點已經存在.')
self.ring[key] = node
self._sorted_keys.append(key)
def remove_node(self,node):
''' 移除節點
node : 要移除的節點 '127.0.0.1:8000'
'''
for key,value in self.ring.items():
if value == node:
del self.ring[key]
self._sorted_keys.remove(key)
def get_node(self,string_key):
'''擷取 string_key 所在的節點'''
pos = self.get_node_pos(string_key)
if pos is None:
return None
return self.ring[ self._sorted_keys[pos]].split(':')
def get_node_pos(self,string_key):
'''擷取 string_key 所在的節點的索引'''
if not self.ring:
return None
key = self.gen_key_thirty_two(string_key)
nodes = self._sorted_keys
pos = bisect(nodes, key)
return pos
def gen_key_thirty_two(self, key):
m = md5_constructor()
m.update(key)
return long(m.hexdigest(), 16)
def gen_key_sixteen(self,key):
b_key = self.__hash_digest(key)
return self.__hash_val(b_key, lambda x: x)
def __hash_val(self, b_key, entry_fn):
return (( b_key[entry_fn(3)] << 24)|(b_key[entry_fn(2)] << 16)|(b_key[entry_fn(1)] << 8)| b_key[entry_fn(0)] )
def __hash_digest(self, key):
m = md5_constructor()
m.update(key)
return map(ord, m.digest())
"""
nodes = [
{'host':'127.0.0.1:8000','weight':1},
{'host':'127.0.0.1:8001','weight':2},
{'host':'127.0.0.1:8002','weight':1},
]
ring = HashRing(nodes)
result = ring.get_node('98708798709870987098709879087')
print result
"""
- 自定義Session
from hashlib import sha1
import os, time
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):
# 根據 self._id ,在一緻性哈希中找到其對應的伺服器IP
# 找到相對應的redis伺服器,如: r = redis.StrictRedis(host='localhost', port=6379, db=0)
# 使用python redis api 連結
# 擷取資料,即:
# return self._redis.hget(self._id, name)
def __setitem__(self, key, value):
# 根據 self._id ,在一緻性哈希中找到其對應的伺服器IP
# 使用python redis api 連結
# 設定session
# self._redis.hset(self._id, name, value)
def __delitem__(self, key):
# 根據 self._id 找到相對應的redis伺服器
# 使用python redis api 連結
# 删除,即:
return self._redis.hdel(self._id, name)
檔案上傳
(1) 使用Form表單
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<title>上傳檔案</title>
</head>
<body>
<form id="my_form" name="form" action="/index" method="POST" enctype="multipart/form-data" >
<input name="fff" id="my_file" type="file" />
<input type="submit" value="送出" />
</form>
</body>
</html>
#!/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')
def post(self, *args, **kwargs):
file_metas = self.request.files["fff"]
# print(file_metas)
for meta in file_metas:
file_name = meta['filename']
with open(file_name,'wb') as up:
up.write(meta['body'])
settings = {
'template_path': 'template',
}
application = tornado.web.Application([
(r"/index", MainHandler),
], **settings)
if __name__ == "__main__":
application.listen(8000)
tornado.ioloop.IOLoop.instance().start()
(2) 使用AjaxXMLHttpRequest
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>上傳檔案</title>
</head>
<body>
<input type="file" id="img" />
<input type="button" onclick="UploadFile();" />
<script>
function UploadFile(){
var fileObj = document.getElementById("img").files[0];
var form = new FormData();
form.append("fff", fileObj);
var xhr = new XMLHttpRequest();
xhr.open("post", '/index', true);
xhr.send(form);
}
</script>
</body>
</html>
(3) 使用jQuery的Ajax
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>上傳檔案</title>
</head>
<body>
<input type="file" id="img" />
<input type="button" onclick="UploadFile();" />
<script>
function UploadFile(){
var fileObj = $("#img")[0].files[0];
var form = new FormData();
form.append("fff", 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>
(4) 使用iframe預覽圖檔
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>上傳檔案</title>
</head>
<body>
<form id="my_form" name="form" action="/index" method="POST" enctype="multipart/form-data" >
<div id="main">
<input name="fff" id="my_file" type="file" />
<input type="button" name="action" value="Upload" onclick="redirect()"/>
<iframe id='my_iframe' name='my_iframe' src="" ></iframe>
</div>
</form>
<script>
function redirect(){
document.getElementById('my_iframe').onload = Testt;
document.getElementById('my_form').target = 'my_iframe';
document.getElementById('my_form').submit();
}
function Testt(ths){
var t = $("#my_iframe").contents().find("body").text();
console.log(t);
}
</script>
</body>
</html>