Web框架系列之Tornado

前言

Tornado是使用Python編寫的一個強大的、可擴展的Web服務器。它在處理嚴峻的網絡流量時表現得足夠強健,但卻在建立和編寫時有着足夠的輕量級,並可以被用在大量的應用和工具中。css

Tornado是基於Bret Taylor和其餘人員爲FriendFeed所開發的網絡服務框架,當FriendFeed被Facebook收購後得以開源。不一樣於那些最多隻能達到10,000個併發鏈接的傳統網絡服務器,Tornado在設計之初就考慮到了性能因素,旨在解決C10K問題,這樣的設計使得其成爲一個擁有很是高性能的框架。此外,它還擁有處理安全性、用戶驗證、社交網絡以及與外部服務(如數據庫和網站API)進行異步交互的工具。html

Tornado所作的是可以快速簡單地編寫高速的Web應用。若是編寫一個可擴展的社交應用、實時分析引擎,或RESTful API,那麼簡單而強大的Python,以及Tornado正是爲你準備的!python

總之,Tornado也很強大!!!jquery

下載和安裝ios

# pip安裝
pip3 install tornado
 
# 源碼安裝
tar xvzf tornado-4.4.1.tar.gz
cd tornado-4.4.1
python setup.py build
sudo python setup.py install

源碼下載:tornado-1.2.1.tar.gz、 tornado-4.4.1.tar.gzgit

Tornado各模塊github

# 主要模塊
    web        - FriendFeed 使用的基礎 Web 框架,包含了 Tornado 的大多數重要的功能
    escape     - XHTML, JSON, URL 的編碼/解碼方法
    database   - 對 MySQLdb 的簡單封裝,使其更容易使用
    template   - 基於 Python 的 web 模板系統
    httpclient - 非阻塞式 HTTP 客戶端,它被設計用來和 web 及 httpserver 協同工做
    auth       - 第三方認證的實現(包括 Google、Facebook、Yahoo BBAuth、FriendFeed...)
    locale     - 針對本地化和翻譯的支持
    options    - 命令行和配置文件解析工具,針對服務器環境作了優化

# 底層模塊
    httpserver - 服務於 web 模塊的一個很是簡單的 HTTP 服務器的實現
    iostream   - 對非阻塞式的 socket 的簡單封裝,以方便經常使用讀寫操做
    ioloop     - 核心的 I/O 循環

Hello,world

# 經典HelloWorld示例

import tornado.ioloop
import tornado.web

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello, world")

application = tornado.web.Application([
    (r"/", MainHandler),
])

if __name__ == "__main__":
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()

路由系統

路由系統其實就是 url 和 類 的對應關係,這裏不一樣於其餘框架,其餘不少框架均是 url 對應 函數,Tornado中每一個url對應的是一個類。web

Tornado中原生支持二級域名的路由,如:正則表達式

方法

一、處理程序和參數算法

  請求來時,程序會用正則匹配相應路由地址,並交付於 tornado.web.RequestHandler 的子類處理;子類會根據請求方式(post / get / delete ...)的不一樣調用並執行相應的方法,方法返回字符串內容併發送到瀏覽器。

self.write("<h1>Hello, World</h1>")    # html代碼直接寫在瀏覽器客戶端
self.render("index.html")              # 返回html文件,調用render_string(),內部實際上是打開並讀取文件,返回內容
self.redirect("http://www.baidu.com",permanent=False) # 跳轉重定向,參數表明是否永久重定向

name = self.get_argument("name")       # 獲取客戶端傳入的參數值
name = self.get_arguments("name")      # 獲取多個值,類別形式
file = self.request.files["filename"]  # 獲取客戶端上傳的文件

raise tornado.web.HTTPError(403)       # 返回錯誤信息給客戶端

二、重寫 RequestHandler 的方法函數

對於一個請求的處理過程代碼調用次序以下:

  1. 程序爲每個請求建立一個 RequestHandler 對象;
  2. 程序調用 initialize() 函數,這個函數的參數是 Application 配置中的關鍵字參數定義。(initialize 方法是 Tornado 1.1 中新添加的,舊版本中你須要重寫 __init__ 以達到一樣的目的) initialize 方法通常只是把傳入的參數存到成員變量中,而不會產生一些輸出或者調用像 send_error 之類的方法。
  3. 程序調用 prepare()。不管使用了哪一種 HTTP 方法,prepare 都會被調用到,所以這個方法一般會被定義在一個基類中,而後在子類中重用。prepare能夠產生輸出信息。若是它調用了finish(或send_error` 等函數),那麼整個處理流程就此結束。
  4. 程序調用某個 HTTP 方法:例如 get()post()put() 等。若是 URL 的正則表達式模式中有分組匹配,那麼相關匹配會做爲參數傳入方法。

 重寫 initialize() 函數(會在建立RequestHandler對象後調用):

class ProfileHandler(tornado.web.RequestHandler):

    def initialize(self,database):
        self.database = database

    def get(self):
        self.write("result:" + self.database)

application = tornado.web.Application([
    (r"/init", ProfileHandler, dict(database="database"))
])

模板引擎

Tornao中的模板語言和django中相似,模板引擎將模板文件載入內存,而後將數據嵌入其中,最終獲取到一個完整的字符串,再將字符串返回給請求者。

Tornado 的模板支持「控制語句」和「表達語句」,控制語句是使用 {% 和 %} 包起來的 例如 {% if len(items) > 2 %}。表達語句是使用 {{ 和 }} 包起來的,例如 {{ items[0] }}

控制語句和對應的 Python 語句的格式基本徹底相同。咱們支持 ifforwhile 和 try,這些語句邏輯結束的位置須要用 {% end %} 作標記。還經過 extends 和 block 語句實現了模板繼承。這些在 template 模塊 的代碼文檔中有着詳細的描述。

注:在使用模板前須要在setting中設置模板路徑:"template_path" : "tpl"

一、基本使用

#!/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", list_info = [11,22,33])
  
application = tornado.web.Application([
    (r"/index", MainHandler),
])
  
  
if __name__ == "__main__":
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()
app.py
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    <title>老男孩</title>
    <link href="{{static_url("css/common.css")}}" rel="stylesheet" />
</head>
<body>

    <div>
        <ul>
            {% for item in list_info %}
                <li>{{item}}</li>
            {% end %}
        </ul>
    </div>
    
    <script src="{{static_url("js/jquery-1.8.2.min.js")}}"></script>
    
</body>
</html>
index.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 的別名
其它方法

二、母版

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    <title>老男孩</title>
    <link href="{{static_url("css/common.css")}}" rel="stylesheet" />
    {% block CSS %}{% end %}
</head>
<body>

    <div class="pg-header">

    </div>
    
    {% block RenderBody %}{% end %}
   
    <script src="{{static_url("js/jquery-1.8.2.min.js")}}"></script>
    
    {% block JavaScript %}{% end %}
</body>
</html>
layout.html
{% extends 'layout.html'%}
{% block CSS %}
    <link href="{{static_url("css/index.css")}}" rel="stylesheet" />
{% end %}

{% block RenderBody %}
    <h1>Index</h1>

    <ul>
    {%  for item in li %}
        <li>{{item}}</li>
    {% end %}
    </ul>

{% end %}

{% block JavaScript %}
    
{% end %}
index.html

三、導入

<div>
    <ul>
        <li>1024</li>
        <li>42區</li>
    </ul>
</div>
header.html
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    <title>老男孩</title>
    <link href="{{static_url("css/common.css")}}" rel="stylesheet" />
</head>
<body>

    <div class="pg-header">
        {% include 'header.html' %}
    </div>
    
    <script src="{{static_url("js/jquery-1.8.2.min.js")}}"></script>
    
</body>
</html>
index.html

四、自定義UIMethod以UIModule

a,定義

# uimethods.py
 
def tab(self):
    return 'UIMethod'
uimethods.py
#!/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>nick</h1>')
        #return escape.xhtml_escape('<h1>suoning</h1>')
uimodule.py

b,註冊

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

c,使用

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>Nick</title>
    <link href="{{static_url("commons.css")}}" rel="stylesheet" />
</head>
<body>
    <h1>hello</h1>
    {% module custom(123) %}
    {{ tab() }}
</body>
DEMO.html

靜態文件

在應用配置 settings 中指定 static_path 選項來提供靜態文件服務;

在應用配置 settings 中指定 static_url_prefix 選項來提供靜態文件前綴服務;

在導入靜態文件時用 {{static_url('XX.css')}} 方式實現主動緩存靜態文件;

settings = {
    'template_path': 'views',
    'static_path': 'static',
    'static_url_prefix': '/static/',
}
# html使用

<head lang="en">
    <title>Nick</title>
    <link href="{{static_url("commons.css")}}" rel="stylesheet" />
</head>

Cookie

一、基本Cookie

set_cookie 方法在用戶的瀏覽中設置 cookie;

get_cookie 方法在用戶的瀏覽中獲取 cookie。

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        if not self.get_cookie("mycookie"):
            self.set_cookie("mycookie", "myvalue")
            self.write("Your cookie was not set yet!")
        else:
            self.write("Your cookie was set!")

二、加密Cookie(簽名)

Cookie 很容易被惡意的客戶端僞造。加入你想在 cookie 中保存當前登錄用戶的 id 之類的信息,你須要對 cookie 做簽名以防止僞造。Tornado 經過 set_secure_cookie 和 get_secure_cookie 方法直接支持了這種功能。 要使用這些方法,你須要在建立應用時提供一個密鑰,名字爲 cookie_secret。 你能夠把它做爲一個關鍵詞參數傳入應用的設置中:

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        if not self.get_secure_cookie("mycookie"):
            self.set_secure_cookie("mycookie", "myvalue")
            self.write("Your cookie was not set yet!")
        else:
            self.write("Your cookie was set!")
             
application = tornado.web.Application([
    (r"/", MainHandler),
], cookie_secret="61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=")

加密Cookice的本質:

寫cookie過程:

  • 將值進行base64加密
  • 對除值之外的內容進行簽名,哈希算法(沒法逆向解析)
  • 拼接 簽名 + 加密值

讀cookie過程:

  • 讀取 簽名 + 加密值
  • 對簽名進行驗證
  • base64解密,獲取值內容

注:許多API驗證機制和安全cookie的實現機制相同。

#!/usr/bin/env python
# -*- coding:utf-8 -*-
 
import tornado.ioloop
import tornado.web
 
 
class MainHandler(tornado.web.RequestHandler):
 
    def get(self):
        login_user = self.get_secure_cookie("login_user", None)
        if login_user:
            self.write(login_user)
        else:
            self.redirect('/login')
 
 
class LoginHandler(tornado.web.RequestHandler):
    def get(self):
        self.current_user()
 
        self.render('login.html', **{'status': ''})
 
    def post(self, *args, **kwargs):
 
        username = self.get_argument('name')
        password = self.get_argument('pwd')
        if username == 'nick' and password == 'nicknick':
            self.set_secure_cookie('login_user', 'nick')
            self.redirect('/')
        else:
            self.render('login.html', **{'status': '用戶名或密碼錯誤'})
 
settings = {
    'template_path': 'template',
    'static_path': 'static',
    'static_url_prefix': '/static/',
    'cookie_secret': 'aiuasdhflashjdfoiuashdfiuh'
}
 
application = tornado.web.Application([
    (r"/index", MainHandler),
    (r"/login", LoginHandler),
], **settings)
 
 
if __name__ == "__main__":
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()
基於cookie實現用戶驗證-DEMO

三、JavaScript操做Cookie

因爲Cookie保存在瀏覽器端,因此在瀏覽器端也可使用JavaScript來操做Cookie

/*
設置cookie,指定秒數過時
 */
function setCookie(name,value,expires){
    var temp = [];
    var current_date = new Date();
    current_date.setSeconds(current_date.getSeconds() + 5);
    document.cookie = name + "= "+ value +";expires=" + current_date.toUTCString();
}

對於參數:

  • domain   指定域名下的cookie
  • path       域名下指定url中的cookie
  • secure    https使用

注:jQuery中也有指定的插件 jQuery Cookie 專門用於操做cookie,猛擊這裏

異步非阻塞

一、基本使用

裝飾器 + Future 從而實現Tornado的異步非阻塞

class AsyncHandler(tornado.web.RequestHandler):
 
    @gen.coroutine
    def get(self):
        future = Future()
        future.add_done_callback(self.doing)
        yield future
        #
        # tornado.ioloop.IOLoop.current().add_future(future,self.doing)
        # yield future
 
    def doing(self,*args, **kwargs):
        self.write('async')
        self.finish()

當發送GET請求時,因爲方法被@gen.coroutine裝飾且yield 一個 Future對象,那麼Tornado會等待,等待用戶向future對象中放置數據或者發送信號,若是獲取到數據或信號以後,就開始執行doing方法。

異步非阻塞體如今當在Tornaod等待用戶向future對象中放置數據時,還能夠處理其餘請求。

注意:在等待用戶向future對象中放置數據或信號時,此鏈接是不斷開的。

2,httpclient類庫

Tornado提供了httpclient類庫用於發送Http請求,其配合Tornado的異步非阻塞使用。

class AsyncHandler(tornado.web.RequestHandler):
    @gen.coroutine
    def get(self):
        from tornado import httpclient
 
        http = httpclient.AsyncHTTPClient()
        yield http.fetch("http://www.google.com", self.endding)
 
 
    def endding(self, response):
        print(len(response.body))
        self.write('ok')
        self.finish()

3,自定義web非阻塞框架

import socket
import select
import time


class HttpRequest(object):
    """
    用戶封裝用戶請求信息
    """

    def __init__(self, content):
        """
        :param content:用戶發送的請求數據:請求頭和請求體
        """
        self.content = content

        self.header_bytes = bytes()
        self.body_bytes = bytes()

        self.header_dict = {}

        self.method = ""
        self.url = ""
        self.protocol = ""

        self.initialize()
        self.initialize_headers()

    def initialize(self):

        temp = self.content.split(b'\r\n\r\n', 1)
        if len(temp) == 1:
            self.header_bytes += temp
        else:
            h, b = temp
            self.header_bytes += h
            self.body_bytes += b

    @property
    def header_str(self):
        return str(self.header_bytes, encoding='utf-8')

    def initialize_headers(self):
        headers = self.header_str.split('\r\n')
        first_line = headers[0].split(' ')
        if len(first_line) == 3:
            self.method, self.url, self.protocol = headers[0].split(' ')
            for line in headers:
                kv = line.split(':')
                if len(kv) == 2:
                    k, v = kv
                    self.header_dict[k] = v


class Future(object):
    def __init__(self, timeout=0):
        self.result = None
        self.timeout = timeout
        self.start = time.time()


def main(request):
    f = Future(5)
    return f


def index(request):
    return "indexasdfasdfasdf"


routers = [
    ('/main/', main),
    ('/index/', index),
]


def run():
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.bind(("127.0.0.1", 9999,))
    sock.setblocking(False)
    sock.listen(128)

    inputs = [sock, ]
    # inputs.append(sock)

    async_request_dict = {
        # 'socket': futrue
    }

    while True:
        rlist, wlist, elist = select.select(inputs, [], [], 0.05)
        for r in rlist:
            if r == sock:
                """新請求到來"""
                conn, addr = sock.accept()
                conn.setblocking(False)
                inputs.append(conn)
            else:
                """客戶端發來數據"""
                data = b""
                while True:
                    try:
                        chunk = r.recv(1024)
                        data = data + chunk
                    except Exception as e:
                        chunk = None
                    if not chunk:
                        break
                # data進行處理:請求頭和請求體
                request = HttpRequest(data)
                # 1. 請求頭中獲取url
                # 2. 去路由中匹配,獲取指定的函數
                # 3. 執行函數,獲取返回值
                # 4. 將返回值 r.sendall(b'alskdjalksdjf;asfd')
                import re
                flag = False
                func = None
                for route in routers:
                    if re.match(route[0], request.url):
                        flag = True
                        func = route[1]
                        break
                if flag:
                    result = func(request)
                    if isinstance(result, Future):
                        async_request_dict[r] = result
                    else:
                        r.sendall(bytes(result, encoding='utf-8'))
                        inputs.remove(r)
                        r.close()
                else:
                    r.sendall(b"404")
                    inputs.remove(r)
                    r.close()

        for conn in async_request_dict.keys():
            future = async_request_dict[conn]
            start = future.start
            timeout = future.timeout
            ctime = time.time()
            if (start + timeout) <= ctime:
                future.result = b"timeout"
            if future.result:
                conn.sendall(future.result)
                conn.close()
                del async_request_dict[conn]
                inputs.remove(conn)


if __name__ == '__main__':
    run()
自定製web非阻塞框架-DEMO

自定製Web組件

一,session

Tornado框架中,默認執行Handler的get/post等方法以前默認會執行 initialize方法,因此能夠經過自定義的方式使得全部請求在處理前執行操做...

session其實就是定義在服務器端用於保存用戶回話的容器,其必須依賴cookie才能實現。

import tornado.ioloop
import tornado.web
from controllers.account import LoginHandler
from controllers.home import HomeHandler

import time
import hashlib

class Cache(object):
    """
    將session保存在內存
    """
    def __init__(self):
        self.container = {}

    def __contains__(self, item):
        return item in self.container

    def initial(self,random_str):
        self.container[random_str] = {}

    def get(self,random_str,key):
        return self.container[random_str].get(key)

    def set(self,random_str,key,value):
        self.container[random_str][key] = value

    def delete(self,random_str,key):
        del self.container[random_str][key]

    def open(self):
        pass

    def close(self):
        pass

    def clear(self,random_str):
        del self.container[random_str]

class Memcache(object):
    def __init__(self):
        pass

    def get(self,key):
        pass

    def set(self,key,value):
        pass

    def delete(self,key):
        pass

    def open(self):
        pass

    def close(self):
        pass

P = Cache

class Session(object):
    def __init__(self,handler):
        self.handler = handler
        self.random_str = None
        self.ppp = P()
        self.ppp.open()
        # 去用戶請求信息中獲取session_id,若是沒有,新用戶
        client_random_str = self.handler.get_cookie('session_id')
        if not client_random_str:
            "新用戶"
            self.random_str = self.create_random_str()
            container[self.random_str] = {}
        else:
            if client_random_str in self.ppp:
                "老用戶"
                self.random_str = client_random_str
            else:
                "非法用戶"
                self.random_str = self.create_random_str()
                self.ppp.initial(self.random_str)
        ctime = time.time()
        self.handler.set_cookie('session_id',self.random_str,expires=ctime+1800)
        self.ppp.close()

    def create_random_str(self):
        v = str(time.time())
        m = hashlib.md5()
        m.update(bytes(v,encoding='utf-8'))
        return m.hexdigest()

    def __setitem__(self, key, value):
        self.ppp.open()
        self.ppp.set(self.random_str,key,value)
        self.ppp.close()
    def __getitem__(self, key):
        self.ppp.open()
        v = self.ppp.get(self.random_str,key)
        self.ppp.close()
        return v
    def __delitem__(self, key):
        self.ppp.open()
        self.ppp.delete(self.random_str,key)
        self.ppp.close()

    def clear(self):
        self.ppp.open()
        self.ppp.clear(self.random_str)
        self.ppp.close()

class Foo(object):
    def initialize(self):
        # self是MainHandler對象
        self.session = Session(self)
        super(Foo,self).initialize()

class HomeHandler(Foo,tornado.web.RequestHandler):

    def get(self):
        user = self.session['uuuuu']
        if not  user:
            self.redirect("http://www.oldboyedu.com")
        else:
            self.write(user)

class LoginHandler(Foo,tornado.web.RequestHandler):

    def get(self):
        self.session['uuuuu'] = 'root'
        self.redirect('/home')

class TestHandler(tornado.web.RequestHandler):
    def get(self):
        self.set_cookie('k1', 'vvv', expires=time.time()+20)

class ShowHandler(tornado.web.RequestHandler):
    def get(self):
        self.write(self.get_cookie('k1'))



application = tornado.web.Application([
    (r"/login", LoginHandler),
    (r"/home", HomeHandler),
    (r"/test", TestHandler),
    (r"/show", ShowHandler),
])

if __name__ == "__main__":
    application.listen(9999)
    tornado.ioloop.IOLoop.instance().start()
自定義session
相關文章
相關標籤/搜索