python基础-第十三篇-13.2Web框架之Tornado
- Tornado是非阻塞异步web frame,而且速度相当快,得力于其非阻塞的方式和对epoll的运用
- Tornado每秒可以处理数以千计的链接,所以它可以有效的处理C10K问题
下载安装
pip3 install tornado- 源码安装
https://pypi.python.org/packages/source/t/tornado/tornado-4.3.tar.gz
框架应用
一、快速上手
# 第一步:导模块
import tornado.ioloop
import tornado.web
# 第二步:创建类,必须继承tornado.web.RequestHandler,按照自己的业务逻辑重写get方法或post方法
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, world")
# 第三步:实例Application对象,构建路由系统
application = tornado.web.Application([
(r"/index", MainHandler),
])
if __name__ == "__main__":
# 第四步:socket运行起来
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()
更多路径配置
'template_path': 'views', # html文件
'static_path': 'statics', # 静态文件(css,js,img)
'static_url_prefix': '/statics/',# 静态文件前缀
'cookie_secret': 'suoning', # cookie自定义字符串加盐
# 'xsrf_cookies': True, # 防止跨站伪造
# 'ui_methods': mt, # 自定义UIMethod函数
# 'ui_modules': md, # 自定义UIModule类
执行过程:
- 第一步:执行脚本,监听 8888 端口
- 第二步:浏览器客户端访问 /index --> http://127.0.0.1:8888/index
- 第三步:服务器接受请求,并交由对应的类处理该请求
- 第四步:类接受到请求之后,根据请求方式(post / get / delete ...)的不同调用并执行相应的方法
- 第五步:方法返回值的字符串内容发送浏览器
二、路由系统
路由系统其实就是url和类的对象关系,这里不同于其他框架,其他很多框架均是url对应函数,Tornado中每个url对应的是一个类
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), #这里添加2级域名,测试的话需要改本地host
])
if __name__ == "__main__":
application.listen(80)
tornado.ioloop.IOLoop.instance().start()
三、模板引擎
模板引擎说简单点就是将原来的html的某些内容用一些特殊的字符串代替,然后再处理用户的不同请求时,将html的字符串替换掉,返回给用户新的一个字符串,这样就达到了动态的html的效果。
Tornado的模板支持“控制语句”和“表达语句”,控制语句格式{% python语句 %} 例如:{% for item in range(10)%},表达语句格式{{变量}} 比如:{{item}},对于控制语句在逻辑结尾的地方还要写上{% end %}
不仅提供通过UIMethod和UIModule来自定义方法和模块,而且Tornado本身就提供了一些方法,其中<link href="{{static_url("commons.css")}}" rel="stylesheet" /> static_url方法可以实现静态文件缓存(更新的内置方法见骚师博客)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> </head> <body> <h1>{{name}}</h1> {% for item in user_list %} <li>{{item}}</li> {% end %} </body> </html>
import tornado.ioloop
import tornado.web
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.render("index.html",name="alex",user_list=[11,22,33])
settings = {
'template_path':'views',
}
application = tornado.web.Application([
(r"/index", MainHandler),
],**settings)
if __name__ == "__main__":
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()
自定义UIMethod和UIModule:通过模板语言的自定义功能,可以让你使用更加熟悉的python代码来实现动态的模板渲染,其中UIMethod中定义函数,UIModule中定义类
实现自定义方法三步走:
- 创建UIMethod.py UIModule.py,定义方法和类(方法定义的时候,必须传入self;类中必须要有render方法,功能代码实现就在这个方法里)
# 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>wupeiqi</h1>')
#return escape.xhtml_escape('<h1>wupeiqi</h1>')
- 导入创建文件,在settings里注册
#!/usr/bin/env python
# -*- coding:utf-8 -*-
#!/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()
- 模块中调用 方法:{{ func() }} 类:{% module 类名() %}
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
<link href="{{static_url("commons.css")}}" rel="stylesheet" />
</head>
<body>
<h1>hello</h1>
{% module custom(123) %}
{{ tab() }}
</body>
四、模板继承和静态缓存
将一些公用的html,css等写到通用的文件,然后通过继承,就可以获取母版的内容,而继承的html里面只需要写特有的东西,模板继承的功能非常实用,而静态缓存则可以减少相应的请求资源。
母版
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link href="{{static_url('css/chouti.css')}}" type="text/css" rel="stylesheet"> // 通过static_url引用静态文件
{% block css %} {% end %}
</head>
<body>
<div class="header">
<div class="header-content">
{% if user_info['is_login'] %}
<div class="account">
<a href="#">{{user_info['username']}}</a>
<a href="/logout">退出</a>
</div>
{% else %}
<div class="account">
<a href="http://127.0.0.1:8888/register">注册</a>
<a href="http://127.0.0.1:8888/login">登陆</a>
</div>
{% end %}
</div>
</div>
<div class="content">
{% block body %}
{% end %}
</div>
<a class="back-to-head" href="javascript:scroll(0,0)"></a>
{% block js %} {% end %}
<script>
</script>
</body>
</html>
子版
{% extends '../base/layout.html' %}
{% block css %}
<link href="{{static_url('css/css/common.css')}}" rel="stylesheet">
<link href="{{static_url('css/css/login.css')}}" rel="stylesheet">
{% end %}
{% block body %}
{% end %}
{% block js %}
<script src="{{static_url('js/jquery-1.12.4.js')}}"></script>
<script src="{{static_url('js/login.js')}}"></script>
{% end %}
五、Xss和csrf
-
Xss跨站脚本攻击
恶意攻击者往web页面里插入恶意script代码,当用户浏览该页时,嵌入web里面的script代码会被执行,从而达到恶意攻击用户的特殊目的
-
csrf跨站请求伪造(对post请求限制)
get请求的时候,会给浏览器发一个id,浏览器post请求的时候,携带这个id,然后服务端对其做验证,如果没有这个id的话,就禁止浏览器提交内容
在Tornado里需要在settings里配置“xsrf_cookies”:True,如果这样做,Tornado将拒绝浏览器请求参数中不包含正确的_xsrf值的post/put/delete请求,并禁止其访问
settings = { "xsrf_cookies": True, } application = tornado.web.Application([ (r"/", MainHandler), (r"/login", LoginHandler), ], **settings)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="/sss/jquery-1.12.4.js"></script> <!--<script src="{{ static_url('jquery-1.12.4.js') }}" ></script>--> </head> <body> <!--{{ xsrf_form_html() }}--> {% raw xsrf_form_html() %} <input type="button" value="ajax_csrf" onclick="SubmitCsrf();"> <script> function getCookie(name) { var r = document.cookie.match("\\b" + name + "=([^;]*)\\b"); return r ? r[1] : undefined; } function SubmitCsrf() { var nid = getCookie('_xsrf'); console.log(nid); $.post({ url: '/csrf', data:{'k1':'v1', "_xsrf":nid}, success:function (callback) { console.log(callback); } }); } </script> </body> </html>
六、上传文件
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>
import tornado.ioloop
import tornado.web
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.render('index.html')
def post(self, *args, **kwargs):
#获取文件方法self.request.files
file_metas = self.request.files["fff"]
# print(file_metas)
#[{'filename':'文件名','body':'文件内容']
for meta in file_metas:
file_name = meta['filename']
with open(file_name,'wb') as up:
up.write(meta['body'])
settings = {
'template_path': 'views',
}
application = tornado.web.Application([
(r"/index", MainHandler),
], **settings)
if __name__ == "__main__":
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()
2、AJAX上传
HTML - XMLHttpRequest
<!DOCTYPE html>
<html>
<head lang="en">
<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];
//创建Formdata对象,作为文件对象的载体
var form = new FormData();
form.append("k1", "v1");
form.append("fff", fileObj);
var xhr = new XMLHttpRequest();
xhr.open("post", '/index', true);
xhr.send(form);
}
</script>
</body>
</html>
HTML - jQuery
<!DOCTYPE html>
<html>
<head lang="en">
<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("k1", "v1");
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>
import tornado.ioloop import tornado.web class MainHandler(tornado.web.RequestHandler): def get(self): self.render('index.html') def post(self, *args, **kwargs): #获取文件方法self.request.files file_metas = self.request.files["fff"] # print(file_metas) #[{'filename':'文件名','body':'文件内容'] for meta in file_metas: file_name = meta['filename'] with open(file_name,'wb') as up: up.write(meta['body']) settings = { 'template_path': 'views', } application = tornado.web.Application([ (r"/index", MainHandler), ], **settings) if __name__ == "__main__": application.listen(8888) tornado.ioloop.IOLoop.instance().start()
七、验证码
验证码原理在于后台自动创建一张带有随机内容的图片,然后将内容通过img标签输出到页面
- 安装图像处理模块:pip3 install pillow
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="statics/jquery-1.12.4.js"></script> </head> <body> <form action="login" method="post"> <input type="text", name="code"> <img src="/check_code" onclick="ChangeCode();" id="imgcode"> <input type="submit" value="submit"> <span>{{status}}</span> </form> <script> function ChangeCode() { var code = document.getElementById('imgcode'); code.src += '?' } </script> </body> </html>
class CheckCodeHandler(BaseHandler): def get(self, *args, **kwargs): import io import check_code mstream = io.BytesIO() img, code = check_code.create_validate_code() img.save(mstream, 'GIF') self.session['CheckCode'] = code self.write(mstream.getvalue())
八、自定义分页类
分页功能十分常见的,所以整理成一个类,当做一个小组件来使用是非常有必要的
在前端注意因为xxs不能显示页面的情况,{% raw data %}
class Pagenation:
def __init__(self, current_page, all_item, each_item):
#all_pager总页数,c余数
all_pager, c = divmod(all_item, each_item)
#余数不为0,总页数要加1
if c > 0:
all_pager += 1
#如果客户在url里没输入页码,默认当前页为1
if current_page == '':
current_page = 1
self.current_page = int(current_page) # 当前页
self.all_pages = all_pager # 总的页面数
self.each_item = each_item # 每页显示的item数
@property
def start_item(self): # 当前页的起始item位置
return (self.current_page - 1) * self.each_item
@property
def end_item(self): # 当前页结束item位置
return self.current_page * self.each_item
@property
def start_end_span(self): # 获取开始和结束页的具体数字
if self.all_pages < 10:
start_page = 1 # 起始页
end_page = self.all_pages + 1 # 结束页
else: # 总页数大于10
if self.current_page < 5:
start_page = 1
end_page = 11
else:
if (self.current_page + 5) < self.all_pages:
start_page = self.current_page - 4
end_page = self.current_page + 5 + 1
else:
start_page = self.all_pages - 10
end_page = self.all_pages + 1
return start_page, end_page
def generate_str_page(self):
list_page = []
start_page, end_page = self.start_end_span
if self.current_page == 1: # 上一页
prev = '<li><a class="pre-page" href="javascript:void(0);">上一页</a></li>'
else:
prev = '<li><a class="pre-page" href="/index/%s">上一页</a></li>' % (self.current_page - 1,)
list_page.append(prev)
for p in range(start_page, end_page): # 1-10
if p == self.current_page:
temp = '<li><a class="li-page" href="/index/%s">%s</a></li>' % (p, p)
else:
temp = '<li><a href="/index/%s">%s</a></li>' % (p, p)
list_page.append(temp)
if self.current_page == self.all_pages: # 下一页
nex = '<li><a class="next-page" href="javascript:void(0);">下一页</a></li>'
else:
nex = '<li><a class="next-page" href="/index/%s">下一页</a></li>' % (self.current_page + 1,)
list_page.append(nex)
# 跳转
jump = """<input type='text' /><a onclick="Jump('%s',this);">GO</a>""" % ('/index/')
script = """<script>
function Jump(baseUrl,ths){
var val = ths.previousElementSibling.value;
if(val.trim().length>0){
location.href = baseUrl + val;
}
}
</script>"""
list_page.append(jump)
list_page.append(script)
str_page = "".join(list_page)
return str_page
更多详细内容请见骚师博客:http://www.cnblogs.com/wupeiqi/articles/5702910.html

浙公网安备 33010602011771号