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