玩转Tornado框架

Tornado简介

Tornado是一种Web服务器软件的开源版本,Tornado是非阻塞式服务器,速度很快。这得益于其非阻塞式和对epoll的运用,Tornado每秒可以处理数以千计的连接,因此Tornado是实时Web服务的一个理想的框架。Tornado是现在应用最为广泛的Web框架,其具有以下优势:

  • 轻量级Web框架
  • 异步非阻塞IO处理
  • 出色的抗负载能力
  • 优秀的处理性能,不依赖多进程和多线程

Tornado与Django的比较

  • Django是重量级的Web框架,功能齐全,注重开发效率
  • Django内置管理后台Django Admin
  • Django内置封装完善的ORM操作
  • Django提供Session功能
  • Django与Tornado相比,Django高耦合
  • Tornado与Django相比,入门门槛较高

使用Tornado

(1) 安装

pip install tornado

(2)导入

import tornado.ioloop
import tornado.web

Tornado的执行流程

  1. 执行Python文件,监听设置的端口
  2. 浏览器请求服务器,经过路由
  3. 路由匹配对应的处理器类
  4. 根据请求类型执行指定处理器类中的处理函数
  5. 返回处理结果,客户端浏览器渲染页面

快速开始

(1) 创建处理器类,在类中定义HTTP方法

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        pass
    def post(self):
        pass
    ... # HTTP中的方法

(2) 为Tornado设置模板目录

settings = {
    "template_path":"模板路径", # 设置模板目录
}

(3) 设置Tornado的路由关系

application = tornado.web.Application([(r"/", MainHandler),], **settings) # 传入设置参数

(4) 启动第一个服务器

if __name__ == "__main__":
    application.listen(8009) # 设置监听端口
    tornado.ioloop.IOLoop.instance().start() # 启动服务

(5) 代码实例

# /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>

Tornado路由系统

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')

(1)请求方法路由

@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():
    ...

(2) 二级路由

Host HeaderURL regHandler
safe/index/\d*IndexHandler
safe/admin/\w*AdminHandler
safe/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...

模板引擎

(1) 模板语法

<!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>

(2) 模板函数

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 的別名

(3) 自定义UIMethod和UIModule

定义:

# file:uimethods.py
def 自定义函数(self):
	# Some...
# 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>
</html>

静态缓存

对于静态文件,可以配置静态文件的目录和前段使用时的前缀,并且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()

公共组件

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 #环境相关,如果上面的这些请求信息没有满足需求,就在这里找

XSS跨站脚本攻击与CSRF跨域伪造请求

XSS:恶意攻击者往Web页面里插入恶意脚本代码,当用户浏览该页之时,嵌入其中Web里面的脚本代码会被执行,从而达到恶意攻击用户的特殊目的。

  • CSRF配置
settings = {
    "xsrf_cookies": True,
}
  • 普通的表单使用
<form action=" " method="post">
  {{ xsrf_form_html() }}
  Some...
</form>
  • 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 + ")"));
    }});
};

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的过程

    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)

文件上传

(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>
#!/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()

(2) 使用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>

(3) 使用jQuery的Ajax

<!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>

(4) 使用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>
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值