您的位置:首页 > 编程语言 > Python开发

玩转PythonWeb框架之Tornado

2018-02-23 13:42 337 查看

(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.ioloopimport 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>
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Tornado Python Web