Tornado是一種Web服務器軟件的開源版本,Tornado是非阻塞式服務器,速度很快。這得益於其非阻塞式和對epoll的運用,Tornado每秒能夠處理數以千計的鏈接,所以Tornado是實時Web服務的一個理想的框架。css
Tornado是如今應用最爲普遍的Web框架,其具備如下優點:html
1.輕量級Web框架node
2.異步非阻塞IO處理python
3.出色的抗負載能力web
4.優秀的處理性能,不依賴多進程和多線程ajax
1.Django是重量級的Web框架,功能齊全,注重開發效率redis
2.Django內置管理後臺算法
3.Django內置封裝完善的ORM操做json
4.Django提供Session功能api
5.Django與Tornado相比,Django高耦合
6.Tornado與Django相比,入門門檻較高
Tornado應該運行在類Unix平臺,爲了達到最佳的性能和擴展性,僅推薦Linux和BSD(充分利用Linux的epoll工具和BSD的kqueue達到高性能處理的目的
pip install tornado
import tornado.ioloop
import tornado.web
1.執行Python文件,監聽設置的端口
2.瀏覽器請求服務器,通過路由
3.路由匹配對應的處理器類
4.根據請求類型執行指定處理器類中的處理函數
5.返回處理結果,客戶端瀏覽器渲染頁面
建立處理器類,在類中定義HTTP方法:
class MainHandler(tornado.web.RequestHandler):
def get(self):
pass
def post(self):
pass
... # HTTP中的方法
爲Tornado設置模板目錄:
settings = {
"template_path":"模板路徑", # 設置模板目錄
}
設置Tornado路由關係:
application = tornado.web.Application([(r"/", MainHandler),], **settings) # 傳入設置參數
啓動服務:
if __name__ == "__main__":
application.listen(8009) # 設置監聽端口
tornado.ioloop.IOLoop.instance().start() # 啓動服務
/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中的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')
@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():
...
主機頭 |
URL正則 |
Handler |
safe |
/index/\d* |
IndexHandler |
/admin/\w* |
AdminHandler |
|
/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...
<!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>
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 的別名
定義:
# file:uimethods.py
def 自定義函數(self):
Some...
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# 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("commons.css")}}" rel="stylesheet" />
</head>
<body>
<h1>hello</h1>
{% module 自定義類名(123) %}
{{ 自定義函數() }}
</body>
對於靜態文件,能夠配置靜態文件的目錄和前段使用時的前綴,而且Tornaodo還支持靜態文件緩存。
配置:
settings = {
'static_url_prefix': '/static/',
}
使用:
<link href="{{static_url("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:
惡意***者往Web頁面裏插入惡意腳本代碼,當用戶瀏覽該頁之時,嵌入其中Web裏面的腳本代碼會被執行,從而達到惡意***用戶的特殊目的。
CSRF配置:
settings = {
"xsrf_cookies": True,
}
1. 普通的表單使用:
<form action=" " method="post">
{{ xsrf_form_html() }}
Some...
</form>
2. 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 + ")"));
}});
};
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的本質:
寫入cookie的過程:
1. 將值進行base64加密
2. 對除去值之外的內容進行簽名,使用沒法逆向破解的哈希算法
3. 拼接簽名與加密值
讀取cookie的過程:
1. 讀取加密的內容
2. 對簽名進行驗證
3. 進行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)
使用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()
使用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>
使用Ajax-jQuery:
<!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>
使用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>