• tornado框架学习


       tornado是一个非阻塞的web服务器框架,每秒可以处理上千个客户端连接(都是在一个线程中,不需要为每个客户端创建线程,资源消耗少),适合用来开发web长连接应用,如long polling(轮询),WebSocket协议等(http协议为短连接)。

    1,简单使用

    #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'。

    #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 %}结尾)。

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

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

    Tornado默认提供的这些功能其实本质上就是 UIMethod 和 UIModule,我们也可以自定义从而实现类似于Django的simple_tag的功能:

    定义:

    #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

    设置:

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

  • 相关阅读:
    Mac php使用gd库出错 Call to undefined function imagettftext()
    centos 使用 locate
    Mac HomeBrew 安装 mysql
    zsh 命令提示符 PROMPT
    新的开始
    Java 面试题分析
    Java NIO Show All Files
    正确使用 Volatile 变量
    面试题整理 2017
    有10阶梯, 每次走1,2 or 3 阶,有多少种方式???
  • 原文地址:https://www.cnblogs.com/silence-cho/p/9903236.html
Copyright © 2020-2023  润新知