玩轉PythonWeb框架之Tornado

(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.ioloop

import 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 #getpost請求信息,他是GETPOST的總和,其實他內部調用了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>


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章