玩转PythonWeb框架之Tornado
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了玩转PythonWeb框架之Tornado相关的知识,希望对你有一定的参考价值。
(1)简介:
Tornado是一种Web服务器软件的开源版本,Tornado是非阻塞式服务器,速度很快。这得益于其非阻塞式和对epoll的运用,Tornado每秒可以处理数以千计的连接,因此Tornado是实时Web服务的一个理想的框架。
Tornado是现在应用最为广泛的Web框架,其具有以下优势:
1.轻量级Web框架
2.异步非阻塞IO处理
3.出色的抗负载能力
4.优秀的处理性能,不依赖多进程和多线程
(2)Tornado与Django的比较:
1.Django是重量级的Web框架,功能齐全,注重开发效率
2.Django内置管理后台
3.Django内置封装完善的ORM操作
4.Django提供Session功能
5.Django与Tornado相比,Django高耦合
6.Tornado与Django相比,入门门槛较高
(3)使用Tornado:
Tornado应该运行在类Unix平台,为了达到最佳的性能和扩展性,仅推荐Linux和BSD(充分利用Linux的epoll工具和BSD的kqueue达到高性能处理的目的
☆安装Tornado:
pip install tornado
☆在代码中导入Tornado:
import tornado.ioloop
import tornado.web
☆Tornado的执行流程:
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>
(4)路由系统:
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...
(5)模板引擎:
☆模板语法
<!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 的別名
☆自定义UIMethod和UIModule:
定义:
# 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>
(6)静态缓存:
对于静态文件,可以配置静态文件的目录和前段使用时的前缀,并且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()
(7)公共组件:
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 #环境相关,如果上面的这些请求信息没有满足需求,就在这里找
(8)XSS跨站脚本攻击与CSRF跨域伪造请求:
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 + ")"));
}});
};
(9)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的本质:
写入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)
(10)文件上传:
使用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>
以上是关于玩转PythonWeb框架之Tornado的主要内容,如果未能解决你的问题,请参考以下文章