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>
index.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> 
View Code

六、上传文件

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>
index.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()
py

 

七、验证码

  验证码原理在于后台自动创建一张带有随机内容的图片,然后将内容通过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>
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())
py

八、自定义分页类

  分页功能十分常见的,所以整理成一个类,当做一个小组件来使用是非常有必要的

  在前端注意因为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

王冬web详解http://www.cnblogs.com/Wxtrkbc/p/5704022.html

Tornado组件http://www.cnblogs.com/Wxtrkbc/p/5710471.html

原文地址:https://www.cnblogs.com/xinsiwei18/p/5816263.html