tornado框架學習

   tornado是一個非阻塞的web服務器框架,每秒能夠處理上千個客戶端鏈接(都是在一個線程中,不須要爲每一個客戶端建立線程,資源消耗少),適合用來開發web長鏈接應用,如long polling(輪詢),WebSocket協議等(http協議爲短鏈接)。javascript

1,簡單使用css

#coding:utf-8
import tornado.ioloop
import tornado.web
from controllers.login import LoginHandler

class HomeHandler(tornado.web.RequestHandler):   #處理'/index'的請求,如果get請求,即調用get方法
    def get(self, *args, **kwargs):
        self.write('home page')

settings = {
    'template_path':'views'   #配置html文件的目錄,即html文件存儲在views文件夾路徑下
  'static_path':'statics', # 配置靜態url路徑,用來存放cssjs文件等
} app = tornado.web.Application([ (r'/index',HomeHandler), # 路由分發器,HomeHandler爲該路由的處理類 (r'/login',LoginHandler), ],**settings) #加入配置文件 if __name__ == '__main__': app.listen(8080) #監聽端口號 tornado.ioloop.IOLoop.instance().start() #開啓服務器

  上面代碼即創建起一個web服務器,在瀏覽器輸入127.0.0.1:8080/index, 就會獲得包含‘home page’字符的網頁。另外,上面將全部代碼寫在了有個代碼文件中,也能夠利用MVC的設計方式分開來寫,以下面的的架構和代碼:將處理‘/login’請求的類LoginHandler放在controllers文件夾下,將視圖文件login.html放在views文件夾下(須要配置‘template_path’),而models文件夾下能夠存放和數據庫處理相關的代碼,statics中存放靜態文件,如css,js等,須要配置路徑:'static_path':'statics'。html

 

#coding:utf-8

import tornado.ioloop
import tornado.web
from controllers.login import LoginHandler

class HomeHandler(tornado.web.RequestHandler):   #處理'/index'的請求,如果get請求,即調用get方法
    def get(self, *args, **kwargs):
        self.write('home page')

settings = {
    'template_path':'views'   #配置html文件的目錄,即html文件存儲在views文件夾路徑下
}
app = tornado.web.Application([
    (r'/index',HomeHandler),   # 路由分發器,HomeHandler爲該路由的處理類
    (r'/login',LoginHandler),
],**settings)  #加入配置文件

if __name__ == '__main__':
    app.listen(8080)        #監聽端口號
    tornado.ioloop.IOLoop.instance().start()  #開啓服務器
app.py
#coding:utf-8

import tornado

class LoginHandler(tornado.web.RequestHandler):

    def get(self):
        self.render('login.html')
login.py

2.模板前端

  tornado也支持和django相似的模板引擎語言,表達語句用{{ item[0] }},控制語句{% if %}。。。。 {% end %},tornado支持if,while,for,try等,但都是以{% end %}結束,不一樣於django。tornado也支持模板繼承,{% extends 'index.html' %} 和 {% block body%}。。。。{% end  %}(也是以{% end %}結尾)。java

http://www.tornadoweb.org/en/stable/template.htmlpython

https://github.com/tornadoweb/tornado/blob/master/tornado/template.pyjquery

Tornado默認提供的這些功能其實本質上就是 UIMethod 和 UIModule,咱們也能夠自定義從而實現相似於Django的simple_tag的功能:git

定義:github

#coding:utf-8
from tornado import escape

def mytag(request,value):  #默認會傳遞一個參數(HomeHandler object),前端須要傳值時須要再加一個參數value
    #print request
    return '<h3>我是tag%s</h3>'%value     # 前端默認會對和h3進行轉義,須要不轉義時前端使用raw 關鍵字
uimethods.py
#coding:utf-8
from tornado import escape
from tornado.web import UIModule

class CustomUIModule(UIModule):
    def embedded_javascript(self):   # render執行時,會在html文件中加入javascript
        return "console.log(123);"
    def javascript_files(self):  ## render執行時,會在html文件中引入javascript文件
        return 'commons.js'
    def embedded_css(self):
        return '.but{color:red}'
    def css_files(self):
        return 'commons.css'
    def render(self, value):
        v = '<h3>我是一個UIModule tag%s</h3>'%value  #默認不轉義</h3>,前端顯示我是一個UIModule tag3
        #v = escape.xhtml_escape(v)                 #  轉義</h3>,前端顯示<h3>我是一個UIModule tag3</h3>
        return v
uimodules.py

設置:web

#coding:utf-8

import tornado.ioloop
import tornado.web
from controllers.login import LoginHandler
import uimethods
import uimodules


class HomeHandler(tornado.web.RequestHandler):   #處理'/index'的請求,如果get請求,即調用get方法
    def get(self, *args, **kwargs):
        #self.write('home page')
        self.render('home.html')

settings = {
    'template_path':'views', #配置html文件的目錄,即html文件存儲在views文件夾路徑下
    'static_path':'statics',  # 配置靜態url路徑,用來存放css,js文件等
    'ui_methods':uimethods,
    'ui_modules':uimodules,
}
app = tornado.web.Application([
    (r'/index',HomeHandler),   # 路由分發器,HomeHandler爲該路由的處理類
    (r'/login',LoginHandler),
],**settings)  #加入配置文件

if __name__ == '__main__':
    app.listen(8080)        #監聽端口號
    tornado.ioloop.IOLoop.instance().start()  #開啓服務器
app.py

使用

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>主頁</title>
</head>
<body>
{{ mytag(1)}}
{% raw mytag(2) %}
{% module CustomUIModule(3) %}
<p class="but">驗證css代碼</p>
<p class="but2">驗證css文件</p>

</body>
</html>
home.html

網頁效果:

注意的是在UIModule中能夠向html文件中加入css,js代碼及文件。

 3,靜態文件設置

app配置

settings = {
  
    'static_path':'statics',  # 配置靜態url路徑,用來存放css,js文件等
    'static_url_prefix':'/statics/',  #href中的起始路徑
}

html

<link rel="stylesheet" href="/statics/commons.css">  #statics目錄下的commons.css

 

 4. 跨站請求僞造(cross site request forgery)

https://www.tornadoweb.org/en/stable/guide/security.html?highlight=ajax

app設置

settings = {
    "xsrf_cookies": True,
}

表單使用

<form action="/new_message" method="post">
  {% module xsrf_form_html() %}
  <input type="text" name="message"/>
  <input type="submit" value="Post"/>
</form>

ajax使用:

本質上去cookie中獲取_xsrf,再攜帶_xsrf值提交數據(document.cookie:_xsrf=2|160fb996|ce7f56d73e0cbe6c89a74cb0f92db4b2|1541324310

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 + ")"));
    }});
};
function getCookie(name) {
        var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
        return r ? r[1] : undefined;
    }
    $('#send').click(function () {
        var _xsrf = getCookie('_xsrf')
        var msg = $('#msg').val();
        $.ajax({
            url:'/login',
            data:{
                '_xsrf':_xsrf,
                'msg':msg,
            },
            type:"POST",
            success:function (callback) {
                console.log(callback);
            }
        });

    });

5,ajax上傳文件

不用ajax前端

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Title</title>
</head>
<body>
    <div>
        <input type="file" id="img"/>
        <button onclick="upload();">上傳</button>
    </div>

</body>
<script src="/statics/jquery-3.3.1.min.js"></script>
<script>
    function upload() {
        var file = document.getElementById('img').files[0];
        var form = new FormData();
        //form.append('k1','v1');
        form.append('fileobj',file);
        var request = new XMLHttpRequest();
        request.open('post','/index',true);
        request.send(form);   
    }
</script>
</html>
View Code

ajax前端

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Title</title>
</head>
<body>
    <div>
        <input type="file" id="img"/>
        <button onclick="upload();">上傳</button>
    </div>

</body>
<script src="/statics/jquery-3.3.1.min.js"></script>
<script>
    function upload() {
        var file = document.getElementById('img').files[0];
        var form = new FormData();
        //form.append('k1','v1');
        form.append('fileobj',file);
        //var request = new XMLHttpRequest();
        //request.open('post','/index',true);
        //request.send(form);
        $.ajax({
            url:'/index',
            type:'POST',
            data:form,
            processData:false,  //讓jquery不處理數據
            contentType:false,    // 讓jquery不設置contentType
            success:function (callback) {
                console.log(callback);
            }
        });
    }

</script>
</html>
View Code

後端

#coding:utf-8


import tornado.web


class HomeHandler(tornado.web.RequestHandler):

    def get(self):

        self.render('LoadFile.html')
    def post(self):
        fileobjs = self.request.files['fileobj']  #fileobjs爲一個列表
        for file in fileobjs:
            file_name = file['filename']  #fileobjs[0]['filename']
            print type(file_name)
            with open(file_name,'wb') as f:
                f.write(file['body'])

settings={
    'template_path':'views',
    'static_path':'statics',
    'static_url_prefix':'/statics/',
}

application = tornado.web.Application([
    (r'/index', HomeHandler)
],**settings)

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

6,cookie

獲取和設置cookie(不加密):

get_cookie(self, name, default=None): 未取到時返回默認值
def set_cookie(self, name, value, domain=None, expires=None, path="/",expires_days=None, **kwargs):
class HomeHandler(tornado.web.RequestHandler):   #處理'/index'的請求,如果get請求,即調用get方法
    def get(self, *args, **kwargs):
        #self.write('home page')
        if self.get_cookie(name='id'):
            print self.get_cookie(name='id')
        else:
            self.set_cookie(name='id',value='asdfg')
        self.render('home.html')
View Code

 獲取和設置cookie(加密):須要在配置中設置祕鑰:'cookie_secret'

get_secure_cookie(self, name, value=None, max_age_days=31, min_version=None): 對於加密後的cookie,get_secure_cookie拿到的爲解密後的cookie值,get_cookie拿到的爲加密的值
set_secure_cookie(self, name, value, expires_days=30, version=None, **kwargs):
class HomeHandler(tornado.web.RequestHandler):   #處理'/index'的請求,如果get請求,即調用get方法
    def get(self, *args, **kwargs):
        if self.get_secure_cookie(name='secret_id'):
            print self.get_secure_cookie(name='secret_id')  ##前端顯示的爲加密後,拿到的爲明文
        else:
            self.set_secure_cookie(name='secret_id',value='message') 

        self.render('home.html')

settings = {
    'template_path':'views', #配置html文件的目錄,即html文件存儲在views文件夾路徑下
    'static_path':'statics',  # 配置靜態url路徑,用來存放css,js文件等
    'static_url_prefix':'/statics/',
    'ui_methods':uimethods,
    'ui_modules':uimodules,
    'xsrf_cookies':True,
    'cookie_secret':'asdfghhj',
}
View Code

cookie兩個版本的加密算法:

def _create_signature_v1(secret, *parts):
    hash = hmac.new(utf8(secret), digestmod=hashlib.sha1)
    for part in parts:
        hash.update(utf8(part))
    return utf8(hash.hexdigest())

def _create_signature_v2(secret, s):
    hash = hmac.new(utf8(secret), digestmod=hashlib.sha256)
    hash.update(utf8(s))
    return utf8(hash.hexdigest())
#加密
def create_signed_value(secret, name, value, version=None, clock=None,
                        key_version=None):
    if version is None:
        version = DEFAULT_SIGNED_VALUE_VERSION
    if clock is None:
        clock = time.time

    timestamp = utf8(str(int(clock())))
    value = base64.b64encode(utf8(value))
    if version == 1:
        signature = _create_signature_v1(secret, name, value, timestamp)
        value = b"|".join([value, timestamp, signature])
        return value
    elif version == 2:
        # The v2 format consists of a version number and a series of
        # length-prefixed fields "%d:%s", the last of which is a
        # signature, all separated by pipes.  All numbers are in
        # decimal format with no leading zeros.  The signature is an
        # HMAC-SHA256 of the whole string up to that point, including
        # the final pipe.
        #
        # The fields are:
        # - format version (i.e. 2; no length prefix)
        # - key version (integer, default is 0)
        # - timestamp (integer seconds since epoch)
        # - name (not encoded; assumed to be ~alphanumeric)
        # - value (base64-encoded)
        # - signature (hex-encoded; no length prefix)
        def format_field(s):
            return utf8("%d:" % len(s)) + utf8(s)
        to_sign = b"|".join([
            b"2",
            format_field(str(key_version or 0)),
            format_field(timestamp),
            format_field(name),
            format_field(value),
            b''])

        if isinstance(secret, dict):
            assert key_version is not None, 'Key version must be set when sign key dict is used'
            assert version >= 2, 'Version must be at least 2 for key version support'
            secret = secret[key_version]

        signature = _create_signature_v2(secret, to_sign)
        return to_sign + signature
    else:
        raise ValueError("Unsupported version %d" % version)
#解密:
def _decode_signed_value_v1(secret, name, value, max_age_days, clock):
    parts = utf8(value).split(b"|")
    if len(parts) != 3:
        return None
    signature = _create_signature_v1(secret, name, parts[0], parts[1])
    if not _time_independent_equals(parts[2], signature):
        gen_log.warning("Invalid cookie signature %r", value)
        return None
    timestamp = int(parts[1])
    if timestamp < clock() - max_age_days * 86400:
        gen_log.warning("Expired cookie %r", value)
        return None
    if timestamp > clock() + 31 * 86400:
        # _cookie_signature does not hash a delimiter between the
        # parts of the cookie, so an attacker could transfer trailing
        # digits from the payload to the timestamp without altering the
        # signature.  For backwards compatibility, sanity-check timestamp
        # here instead of modifying _cookie_signature.
        gen_log.warning("Cookie timestamp in future; possible tampering %r",
                        value)
        return None
    if parts[1].startswith(b"0"):
        gen_log.warning("Tampered cookie %r", value)
        return None
    try:
        return base64.b64decode(parts[0])
    except Exception:
        return None


def _decode_fields_v2(value):
    def _consume_field(s):
        length, _, rest = s.partition(b':')
        n = int(length)
        field_value = rest[:n]
        # In python 3, indexing bytes returns small integers; we must
        # use a slice to get a byte string as in python 2.
        if rest[n:n + 1] != b'|':
            raise ValueError("malformed v2 signed value field")
        rest = rest[n + 1:]
        return field_value, rest

    rest = value[2:]  # remove version number
    key_version, rest = _consume_field(rest)
    timestamp, rest = _consume_field(rest)
    name_field, rest = _consume_field(rest)
    value_field, passed_sig = _consume_field(rest)
    return int(key_version), timestamp, name_field, value_field, passed_sig


def _decode_signed_value_v2(secret, name, value, max_age_days, clock):
    try:
        key_version, timestamp, name_field, value_field, passed_sig = _decode_fields_v2(value)
    except ValueError:
        return None
    signed_string = value[:-len(passed_sig)]

    if isinstance(secret, dict):
        try:
            secret = secret[key_version]
        except KeyError:
            return None

    expected_sig = _create_signature_v2(secret, signed_string)
    if not _time_independent_equals(passed_sig, expected_sig):
        return None
    if name_field != utf8(name):
        return None
    timestamp = int(timestamp)
    if timestamp < clock() - max_age_days * 86400:
        # The signature has expired.
        return None
    try:
        return base64.b64decode(value_field)
    except Exception:
        return None


def get_signature_key_version(value):
    value = utf8(value)
    version = _get_version(value)
    if version < 2:
        return None
    try:
        key_version, _, _, _, _ = _decode_fields_v2(value)
    except ValueError:
        return None

    return key_version
加密和解密算法

tornado自帶的基於cookie的驗證機制:

必須重寫方法get_current_user(self):,self.current_user()會調用該方法,拿到當前用戶
@tornado.web.authenticated,裝飾器修飾的請求會要求驗證,self.current_user()中拿到值時,能進行訪問,無值時跳轉到登陸頁面(必須進行配置:'login_url':'/login')
#!/usr/bin/env python
# -*- coding:utf-8 -*-
 
import tornado.ioloop
import tornado.web
 
class BaseHandler(tornado.web.RequestHandler):
 
    def get_current_user(self):
        return self.get_secure_cookie("login_user")
 
class MainHandler(BaseHandler):
 
    @tornado.web.authenticated    #須要登陸後才能訪問(self.current_user()拿到當前用戶),不然跳轉到登陸頁面
    def get(self):
        login_user = self.current_user
        self.write(login_user)
 
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 == 'wupeiqi' and password == '123':
            self.set_secure_cookie('login_user', 'zack')
            self.redirect('/')
        else:
            self.render('login.html', **{'status': '用戶名或密碼錯誤'})
 
settings = {
    'template_path': 'template',
    'static_path': 'static',
    'static_url_prefix': '/static/',
    'cookie_secret': 'aiuasdhflashjdfoiuashdfiuh',
    'login_url': '/login'
}
 
application = tornado.web.Application([
    (r"/index", MainHandler),
    (r"/login", LoginHandler),
], **settings)
 
 
if __name__ == "__main__":
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()
View Code

7, 自定義session框架

預備知識一:字典

任何類實現了__getitem__(), __setitem__(), __delitem__()方法,就能向字典同樣存取,刪除數據

class Adict(object):
    def __init__(self):
        self.container = {}

    def __getitem__(self, key):
        print 'get'
        if key in self.container:
            return self.container[key]
        else:
            return None
    def __setitem__(self, key, value):
        print 'set'
        self.container[key]=value
    def __delitem__(self, key):
        print 'del'
        del self.container[key]

D = Adict()

D['user']='zack'  #調用 __setitem__方法
D['user']  #調用 __getitem__方法
del D['user']  # 調用 __delitem__方法
View Code

預備知識二:類繼承

#coding:utf-8
#C實例化時,先調用A的實例化方法,而其會調用self.initialize()時會只執行B的initialize()方法
class A(object):
    def __init__(self):
        print 'A'
        self.initialize()

    def initialize(self):
        print 'A初始化'


class B(A):

    def initialize(self):
        print 'B初始化'

class C(B):
    pass

c = C()
單繼承
#coding:utf-8
#C實例化時,先調用A的實例化方法,而其會調用self.initialize()時會只調用B的initialize()方法,而B的initialize()方法又調用了A的initialize方法
class A(object):
    def __init__(self):
        print 'A'
        self.initialize()

    def initialize(self):
        print 'A初始化'

class B(object):

    def initialize(self):
        print 'B初始化'
        super(B,self).initialize()  #此處super先尋找其父類,沒找到,再找A的initialize方法,(先深度,後廣度)

class C(B,A):
    pass

c = C()
多繼承

預備知識三:在RequestHandler的源碼中,__init__()函數調用了self.initialize()函數

class RequestHandler(object):
    """Base class for HTTP request handlers.

    Subclasses must define at least one of the methods defined in the
    "Entry points" section below.
    """
    SUPPORTED_METHODS = ("GET", "HEAD", "POST", "DELETE", "PATCH", "PUT",
                         "OPTIONS")

    _template_loaders = {}  # type: typing.Dict[str, template.BaseLoader]
    _template_loader_lock = threading.Lock()
    _remove_control_chars_regex = re.compile(r"[\x00-\x08\x0e-\x1f]")

    def __init__(self, application, request, **kwargs):
        super(RequestHandler, self).__init__()

        self.application = application
        self.request = request
        self._headers_written = False
        self._finished = False
        self._auto_finish = True
        self._transforms = None  # will be set in _execute
        self._prepared_future = None
        self._headers = None  # type: httputil.HTTPHeaders
        self.path_args = None
        self.path_kwargs = None
        self.ui = ObjectDict((n, self._ui_method(m)) for n, m in
                             application.ui_methods.items())
        # UIModules are available as both `modules` and `_tt_modules` in the
        # template namespace.  Historically only `modules` was available
        # but could be clobbered by user additions to the namespace.
        # The template {% module %} directive looks in `_tt_modules` to avoid
        # possible conflicts.
        self.ui["_tt_modules"] = _UIModuleNamespace(self,
                                                    application.ui_modules)
        self.ui["modules"] = self.ui["_tt_modules"]
        self.clear()
        self.request.connection.set_close_callback(self.on_connection_close)
        self.initialize(**kwargs)

    def initialize(self):
        """Hook for subclass initialization. Called for each request.

        A dictionary passed as the third argument of a url spec will be
        supplied as keyword arguments to initialize().

        Example::

            class ProfileHandler(RequestHandler):
                def initialize(self, database):
                    self.database = database

                def get(self, username):
                    ...

            app = Application([
                (r'/user/(.*)', ProfileHandler, dict(database=database)),
                ])
        """
        pass
源碼

自定義session框架

#coding:utf-8

import tornado.ioloop
import tornado.web
from hashlib import sha1
import time
import os

container={}
create_session_id = lambda: sha1('%s%s' % (os.urandom(16), time.time())).hexdigest()

class Session(object):  #一個類實現了__setitem__,__getitem__就能夠向字典同樣讀取和存取數據

    session_id='session_id'
    def __init__(self,request):
        session_value = request.get_cookie(Session.session_id,None)
        if not session_value:
            self._id = create_session_id()
        else:
            if session_value in container:
                self._id=session_value
            else:
                self._id = create_session_id()
        request.set_cookie(Session.session_id,self._id)
        if self._id not in container:
            container[self._id]={}

    def __setitem__(self, key, value):

        container[self._id][key]=value

        print container
    def __getitem__(self, key):
        if key in container[self._id]:
            return container[self._id][key]
        else:
            return None

    def __delitem__(self, key):
        del container[self._id][key]

    def clear(self):
        del container[self._id]



# class BaseHandler(object):
#     def initialize(self):
#         self.session = Session(self)
#             super(BaseHandler,self).initialize()  #不會覆蓋tornado.web.RequestHandler的initialiaze方法
#
# class HomeHandler(BaseHandler,tornado.web.RequestHandler):
#

class BaseHandler(tornado.web.RequestHandler):
    def initialize(self):          # 覆蓋tornado.web.RequestHandler的initialiaze方法,初始化時父類中會調用該方法
        self.session = Session(self)

class HomeHandler(BaseHandler):

    def get(self):
        user = self.session['user']
        if user:
            self.write(user)
        else:
            self.redirect('/login')

class LoginHandler(BaseHandler):
    def get(self):
        self.render('login.html')

    def post(self):
        username = self.get_body_argument('username')
        password = self.get_body_argument('password')
        if username=='zack' and password=='1234':
            self.session['user']='zack'
            self.session['pwd']='1234'
            self.redirect('/index')
        else:
            self.render('login.html')

settings={
    'template_path':'views'
}
application = tornado.web.Application([
    (r'/index', HomeHandler),
    (r'/login', LoginHandler),
],**settings)

if __name__ == '__main__':
    application.listen(9999)
    tornado.ioloop.IOLoop.instance().start()
session框架

 

 8,異步非阻塞

http://www.tornadoweb.org/en/stable/guide/async.html

  上面都是利用tornado的同步訪問請求,當一個請求被阻塞時,下一個請求訪問時不能被處理。以下面代碼,當先訪問‘/mani’時,因爲MainHandler中,get方法sleep會阻塞在此處,此時若訪問‘/page’,也會阻塞,等待MainHandler中get方法執行完成後,纔會執行PageHandler中的get方法。

#coding:utf-8

import tornado.web
import tornado.ioloop
from tornado.concurrent import Future
import time

class MainHandler(tornado.web.RequestHandler):

    def get(self):
        time.sleep(10)
        self.write('main')

class PageHandler(tornado.web.RequestHandler):

    def get(self):
        self.write('page')

application = tornado.web.Application([
    (r'/main',MainHandler),
    (r'/page',PageHandler)
])

if __name__ == '__main__':
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()
同步阻塞

  tornado中,利用裝飾器@gen.coroutine +yield Future對象,來支持異步非阻塞。以下面代碼,當給MainHandler中get方法加上裝飾器@gen.coroutine,並返回Future對象時,就變成了異步非阻塞,也就是說,當咱們先訪問‘/mani’時,MainHandler中get方法會阻塞在這裏,但當咱們此時去訪問訪問‘/page’,PageHandler中的get方法會當即執行,而不會阻塞。

#coding:utf-8

import tornado.web
import tornado.ioloop
from tornado import gen
from tornado.concurrent import Future
import time

class MainHandler(tornado.web.RequestHandler):

    @gen.coroutine
    def get(self):
        future = Future()
        yield future
        self.write('main')

class PageHandler(tornado.web.RequestHandler):

    def get(self):
        self.write('page')

application = tornado.web.Application([
    (r'/main',MainHandler),
    (r'/page',PageHandler)
])

if __name__ == '__main__':
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()
異步非阻塞

  上面寫的異步非阻塞並沒實際用途,下面是它的一個應用場景,在代碼中,MainHandler的get方法中,fetch()比較耗時,但其返回一Future對象,當咱們先訪問‘/mani’時,MainHandler中get方法會阻塞在這裏,但當咱們此時去訪問訪問‘/page’,PageHandler中的get方法會當即執行

#coding:utf-8

import tornado.web
import tornado.ioloop
from tornado import gen, httpclient
from tornado.concurrent import Future

class MainHandler(tornado.web.RequestHandler):

    @gen.coroutine
    def get(self):
        http = httpclient.AsyncHTTPClient()  #發送異步請求
        data = yield http.fetch('https://www.youtube.com/',raise_error=False)  #其源碼中能夠看到return future,即返回future對象
        print 'done',data
        self.write('main')
        self.finish('dd')

    # 加入回調函數處理
    # @gen.coroutine
    # def get(self):
    #     http = httpclient.AsyncHTTPClient()  #發送異步請求
    #     yield http.fetch('https://www.youtube.com/',callback=self.done,raise_error=False)  #其源碼中能夠看到return future,即返回future對象
    # 
    # def done(self,response):
    #     print 'done',response
    #     self.write('main')
    #     self.finish('dd')

class PageHandler(tornado.web.RequestHandler):

    def get(self):
        self.write('page')

application = tornado.web.Application([
    (r'/main',MainHandler),
    (r'/page',PageHandler)
])

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

  從python 3.5 開始,關鍵字async 和 await能夠用來代替@gen.coroutine +yield,代碼以下:

http://www.tornadoweb.org/en/stable/guide/coroutines.html

async def fetch_coroutine(url):
    http_client = AsyncHTTPClient()
    response = await http_client.fetch(url)
    return response.body

'''
# Decorated:                    # Native:

# Normal function declaration
# with decorator                # "async def" keywords
@gen.coroutine
def a():                        async def a():
    # "yield" all async funcs       # "await" all async funcs
    b = yield c()                   b = await c()
    # "return" and "yield"
    # cannot be mixed in
    # Python 2, so raise a
    # special exception.            # Return normally
    raise gen.Return(b)             return b
'''
View Code

  其實現異步阻塞的關鍵在於Future對象,下面是其部分源碼,能夠看到其_result屬性初始化沒有值,tornado內部會監聽每個Future對象的_result屬性值,若沒有值時,繼續阻塞,如有值時,若某個Future對象的_result屬性值有值了,處理該請求,結束阻塞,繼續監聽其餘Future對象。

關於Future類能夠參考:https://www.cnblogs.com/silence-cho/p/9867499.html

class Future(object):
    """Represents the result of an asynchronous computation."""

    def __init__(self):
        """Initializes the future. Should not be called by clients."""
        self._condition = threading.Condition()
        self._state = PENDING
        self._result = None
        self._exception = None
        self._traceback = None
        self._waiters = []
        self._done_callbacks = []

 

 

 

參考文章:

官方文檔:http://www.tornadoweb.org/en/stable/index.html

http://www.cnblogs.com/wupeiqi/articles/5341480.html

http://www.cnblogs.com/wupeiqi/articles/5702910.html

http://www.cnblogs.com/wupeiqi/p/6536518.html

相關文章
相關標籤/搜索