zoukankan      html  css  js  c++  java
  • Python Tornado简介

    简介

    Tornado 是 FriendFeed 使用的可扩展的非阻塞式 web 服务器及其相关工具的开源版本。这个 Web 框架看起来有些像web.py 或者 Google 的 webapp,不过为了能有效利用非阻塞式服务器环境,这个 Web 框架还包含了一些相关的有用工具 和优化。

    Tornado 和现在的主流 Web 服务器框架(包括大多数 Python 的框架)有着明显的区别:它是非阻塞式服务器,而且速度相当快。得利于其 非阻塞的方式和对epoll的运用,Tornado 每秒可以处理数以千计的连接,这意味着对于实时 Web 服务来说,Tornado 是一个理想的 Web 框架。我们开发这个 Web 服务器的主要目的就是为了处理 FriendFeed 的实时功能 ——在 FriendFeed 的应用里每一个活动用户都会保持着一个服务器连接。

    Tornado安装

    pip3 install tornado

    初识Tornado

    首先打开pycharm,新建一个干净的project

    新建一个py文件:

    import tornado.ioloop
    import tornado.web
    
    
    class MainHandler(tornado.web.RequestHandler):  # 类似于Django里面的CBV
        def get(self):  # get方法
            self.write("Hello, world")
    
    
    application = tornado.web.Application([  # 类似于Django里面的路由系统
        (r"/index", MainHandler),
    ])
    
    if __name__ == "__main__":
        application.listen(8888)
        tornado.ioloop.IOLoop.instance().start()  # 启动
    app.py

    运行文件并打开浏览器访问网址:

    运行该脚本,依次执行:

    • 创建一个Application对象,并把一个正则表达式'/'和类名MainHandler传入构造函数:tornado.web.Application(...)  
    • 执行Application对象的listen(...)方法,即:application.listen(8888)
    • 执行IOLoop类的类的 start() 方法,即:tornado.ioloop.IOLoop.instance().start()

    整个过程其实就是在创建一个socket服务端并监听8888端口,当请求到来时,根据请求中的url和请求方式(post、get或put等)来指定相应的类中的方法来处理本次请求,在上述demo中只为url为http://127.0.0.1:8888/index的请求指定了处理类MainHandler(具体如何寻找见下文)。所以,在浏览器上访问:http://127.0.0.1:8888/index,则服务器给浏览器就会返回 Hello,world ,否则返回 404: Not Found(tornado内部定义的值), 即完成一次http请求和响应。

    由上述分析,我们将整个Web框架分为两大部分:

    • 待请求阶段,即:创建服务端socket并监听端口
    • 处理请求阶段,即:当有客户端连接时,接受请求,并根据请求的不同做出相应的相应

    经典的Login案例

    首先,创建模版文件夹和模版文件:

    login.html文件

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
        <form method="POST" action="/login">
            <input type="text" name="username" />
            <input type="submit" value="提交" />
        </form>
    </body>
    </html>

    新建一个路由:

        (r"/login", LoginHandler),

    新建CBV:

    class LoginHandler(tornado.web.RequestHandler):
        def get(self):
            self.render("login.html")
    
        def post(self, *args, **kwargs):
            # 获取url中以GET形式传递的数据
            self.get_query_argument()
            self.get_query_arguments()
            # 去请求体中获取传递的数据
            self.get_body_argument()
            self.get_body_arguments()
            # 去get&post双方去取数据
            user = self.get_argument('username')
            print(user)
            self.redirect('www.baidu.com')

    注册模版文件路径:

    # 申明settings,字典格式
    settings = {
        'template_path': 'tmp'
    }
    
    # 注册进tornado内部
    application = tornado.web.Application([
        (r"/index", MainHandler),
        (r"/login", LoginHandler),
    ], **settings)

    整体app文件:

    #!/usr/bin/env python3
    # encoding: utf-8
    # Author: Dandy
    
    
    import tornado.ioloop
    import tornado.web
    
    
    class MainHandler(tornado.web.RequestHandler):
        def get(self):
            self.write("Hello, world")
    
    
    class LoginHandler(tornado.web.RequestHandler):
        def get(self):
            self.render("login.html")
    
        def post(self, *args, **kwargs):
            # 获取url中以GET形式传递的数据
            self.get_query_argument()
            self.get_query_arguments()
            # 去请求体中获取传递的数据
            self.get_body_argument()
            self.get_body_arguments()
            # 去get&post双方去取数据
            user = self.get_argument('username')
            print(user)
            self.redirect('www.baidu.com')
    
    
    settings = {
        'template_path': 'tmp'
    }
    
    
    application = tornado.web.Application([
        (r"/index", MainHandler),
        (r"/login", LoginHandler),
    ], **settings)
    
    if __name__ == "__main__":
        application.listen(8888)
        tornado.ioloop.IOLoop.instance().start()
    View Code

    此时便可执行

    关于静态文件

    首先添加静态文件夹,并放入一张图片

    静态路径注册:

    settings = {
        'template_path': 'tmp',
        'static_path': 'statics'
    }

    login页面加入图片、注意以下路径写的是static,标准用法

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
        <form method="POST" action="/login">
            <input type="text" name="username" />
            <input type="submit" value="提交" />
        </form>
        <img src="/static/炫雅.jpeg" />
    </body>
    </html>

    此时访问页面

    那能不能不用static作为静态文件夹名称呢?

    答案当然是可以的,只需要这样注册:

    settings = {
        'template_path': 'tmp',
        'static_path': 'statics',
        'static_url_prefix': '/jingtai/',  # 这里就是用户自定义的静态路径名称
    }

    再去修改login网页里的就好了。

    详细介绍

    上面简单的介绍了一下totornado的一些基本运用搭建。现在花点篇幅详细的解决各个模块的基本问题。

    一、路由系统

    路由系统从前面Django的学习过程中不难总结出来,其实就是url和视图函数的对应关系。当然啦,在Django里面,有FBV和CBV的概念;而在tornado每个url对应的函数均是一个类,即CBV。

    import tornado.ioloop
    import tornado.web
    
    
    class MainHandler(tornado.web.RequestHandler):
        def get(self):
            self.write("Hello, world")
    
    
    class StoryHandler(tornado.web.RequestHandler):
        def get(self, story_id):
            self.write("You requested the story " + story_id)
    
    
    class HomeHandler(tornado.web.RequestHandler):
        def get(self):
            self.write("dandy")
    
    
    application = tornado.web.Application([
        (r"/index", MainHandler),
        (r"/story/([0-9]+)", StoryHandler),
    ])
    
    application.add_handlers(r"^a.com$", [
        (r"/", HomeHandler),  # 路由a.com:8888/
    ])
    
    if __name__ == "__main__":
        application.listen(8888)
        tornado.ioloop.IOLoop.instance().start()
    View Code

    Tornado中原生支持二级域名的路由,如:

    二、模版引擎

    Tornao中的模板语言和django中类似,模板引擎将模板文件载入内存,然后将数据嵌入其中,最终获取到一个完整的字符串,再将字符串返回给请求者。

    Tornado 的模板支持“控制语句”和“表达语句”,控制语句是使用 {% 和 %} 包起来的 例如 {% if len(items) > 2 %}。表达语句是使用 {{ 和 }} 包起来的,例如 {{ items[0] }}

    控制语句和对应的 Python 语句的格式基本完全相同。我们支持 ifforwhile 和 try,这些语句逻辑结束的位置需要用 {% end %} 做标记。还通过 extends 和 block 语句实现了模板继承。

    注:在使用模板前需要在setting中设置模板路径:"template_path" : "tpl"

    1、基本使用

    import tornado.ioloop
    import tornado.web
    
    
    class MainHandler(tornado.web.RequestHandler):
        def get(self):
            self.render("index.html", list_info=[11, 22, 33], key1='value1')
    
    
    settings = {
        'template_path': 'tmp'
    }
    
    application = tornado.web.Application([
        (r"/index", MainHandler),
    ], **settings)
    
    
    
    if __name__ == "__main__":
        application.listen(8888)
        tornado.ioloop.IOLoop.instance().start()
    app.py
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <link href='{{static_url("css/common.css")}}' rel="stylesheet" />
    </head>
    <body>
        <h1>{{ key1 }}</h1>
        <ul>
        {% for item in list_info %}
            {% if item > 12 %}
                <li>{{ item }}</li>
            {% end %}
        {% end %}
        </ul>
    </body>
    </html>
    index.html

    类似于static_url的其他方法

    在模板中默认提供了一些函数、字段、类以供模板使用:
    
    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 的別名
    View Code

    2、母版

    <!DOCTYPE html>
    <html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
        <title>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
    View Code
    {% 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 %}
    继承页

    3、导入小标签

    <div>
        <ul>
            <li>dandy</li>
            <li>durant</li>
        </ul>
    </div>
    tag
    <!DOCTYPE html>
    <html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
        <title></title>
    </head>
    <body>
    
        <div class="pg-header">
            {% include 'header.html' %}
        </div>
        
    </body>
    </html>
    index.html

    4、模版函数扩展UIMethod&UIModule

    a、申明

    def tab(self):
        return 'UIMethod'
    uimethods.py
    from tornado.web import UIModule
    from tornado import escape
    
    class custom(UIModule):
    
        def render(self, *args, **kwargs):
            return escape.xhtml_escape('<h1>wuzdandz</h1>')
    uimodules.py

    b、注册

    import uimodules as md
    import uimethods as mt
    settings = {
        'template_path': 'template',
        'static_path': 'static',
        'ui_methods': mt,
        'ui_modules': md,
    }
    app.py

    c、使用

    <body>
        {% module custom(123) %}
        <br>
        {{ tab() }}
    </body>
    html文本

    注意一下这几个方法:

    embedded_css/javascript 用来产生css或js语句的

    javascript/css_files用来产生link指向文件

    三、静态文件

    对于静态文件,可以配置静态文件的目录和前段使用时的前缀,并且Tornaodo还支持静态文件缓存。 

    settings = {
        'template_path': 'template',
        'static_path': 'static',
        'static_url_prefix': '/static/',
    }
    application = tornado.web.Application([
        (r"/index", MainHandler),
    ], **settings)
    View Code
    <img src="/static/炫雅.jpeg" />
    <link href='{{static_url("commons.css")}}' rel="stylesheet" />
    html
    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()
    实现静态文件缓存

    四、cookie

    Tornado中可以对cookie进行操作,并且还可以对cookie进行签名以放置伪造。

    1、基本操作

    class MainHandler(tornado.web.RequestHandler):
        def get(self):
            if not self.get_cookie("username"):  # 获取cookie
                self.set_cookie("username", "dandy")  # 设置cookie
                self.write("Your cookie was not set yet!")
            else:
                self.write("Your cookie was set!")
    View Code

    2、加密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=")
    View Code
    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
    内部算法

    签名Cookie的本质是:

    写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 == 'wupeiqi' and password == '123':
                self.set_secure_cookie('login_user', '武沛齐')
                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
    #!/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
        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', '武沛齐')
                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()
    基于签名Cookie实现用户验证-Demo

    3、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();
    }
    

    当然更推荐使用jquery cookie来操作cookie

    五、CSRF

    Tornado中的跨站请求伪造和Django中的相似

    1、启用设置

    settings = {
        "xsrf_cookies": True,
    }
    application = tornado.web.Application([
        (r"/", MainHandler),
        (r"/login", LoginHandler),
    ], **settings)
    setting启动xsrf

    2、表单

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

    3、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 + ")"));
        }});
    };
    ajax

    注:Ajax使用时,本质上就是去获取本地的cookie,携带cookie再来发送请求

    六、上传文件

    1、form表单上传

    <body>
        <form name="form" action="/index" method="POST"  enctype="multipart/form-data" >
            <input name="fafafa" id="my_file"  type="file" />
            <input type="submit" value="提交"  />
        </form>
    </body>
    form
    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["fafafa"]
            # 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()
    py

    2、ajax上传

    <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("k1", "v1");
                form.append("fff", fileObj);
    
                var xhr = new XMLHttpRequest();
                xhr.open("post", '/index', true);
                xhr.send(form);
            }
        </script>
    </body>
    html - xmlhttprequest
    <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("k1", "v1");
                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 - jquery
    <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=""  class="hide"></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 - iframe
    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()
    py
    <script type="text/javascript">
     
        $(document).ready(function () {
     
            $("#formsubmit").click(function () {
     
                var iframe = $('<iframe name="postiframe" id="postiframe" style="display: none"></iframe>');
     
                $("body").append(iframe);
     
                var form = $('#theuploadform');
                form.attr("action", "/upload.aspx");
                form.attr("method", "post");
     
                form.attr("encoding", "multipart/form-data");
                form.attr("enctype", "multipart/form-data");
     
                form.attr("target", "postiframe");
                form.attr("file", $('#userfile').val());
                form.submit();
     
                $("#postiframe").load(function () {
                    iframeContents = this.contentWindow.document.body.innerHTML;
                    $("#textarea").html(iframeContents);
                });
     
                return false;
     
            });
     
        });
     
    </script>
     
     
    <form id="theuploadform">
        <input id="userfile" name="userfile" size="50" type="file" />
        <input id="formsubmit" type="submit" value="Send File" />
    </form>
     
    <div id="textarea">
    </div>
    基于iframe实现Ajax上传示例
    $('#upload_iframe').load(function(){
                        var iframeContents = this.contentWindow.document.body.innerText;
                        iframeContents = JSON.parse(iframeContents);
                       
                    })
    View Code
    function bindChangeAvatar1() {
                $('#avatarImg').change(function () {
                    var file_obj = $(this)[0].files[0];
                    $('#prevViewImg')[0].src = window.URL.createObjectURL(file_obj)
                })
            }
    
            function bindChangeAvatar2() {
                $('#avatarImg').change(function () {
                    var file_obj = $(this)[0].files[0];
                    var reader = new FileReader();
                    reader.readAsDataURL(file_obj);
                    reader.onload = function (e) {
                        $('#previewImg')[0].src = this.result;
                    };
                })
            }
    
            function bindChangeAvatar3() {
                $('#avatarImg').change(function () {
                    var file_obj = $(this)[0].files[0];
                    var form = new FormData();
                    form.add('img_upload', file_obj);
    
                    $.ajax({
                        url: '',
                        data: form,
                        processData: false,  // tell jQuery not to process the data
                        contentType: false,  // tell jQuery not to set contentType
                        success: function (arg) {
    
                        }
                    })
                })
            }
    
            function bindChangeAvatar4() {
                $('#avatarImg').change(function () {
                    $(this).parent().submit();
    
                    $('#upload_iframe').load(function () {
                        var iframeContents = this.contentWindow.document.body.innerText;
                        iframeContents = JSON.parse(iframeContents);
                        if (iframeContents.status) {
                            $('#previewImg').attr('src', '/' + iframeContents.data);
                        }
                    })
    
                })
            }
    
    其他
    。。。。

    七、异步非阻塞

    1、基本使用

    首先,先看一段代码

    class AsyncHandler(tornado.web.RequestHandler):
    
        def get(self):
            import time
            time.sleep(20)
            self.write('666')
    
    application = tornado.web.Application([
        (r"/index", MainHandler),
        (r"/async", AsyncHandler),
    ], **settings)

    对于异步非阻塞的了解,第一点肯定是, 单线程;所以测试一下上面的代码,不难发现:先打开async页面,没有任何返回,一直显示在加载,此时打开index,发现index页面同时也在加载出不来。

    当async执行完,index页面也很快就返回了。

    此时修改一下代码:

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    
    import tornado.ioloop
    import tornado.web
    from tornado.web import UIModule
    from tornado import gen
    from tornado.concurrent import Future
    import time
    
    
    class MainHandler(tornado.web.RequestHandler):
        def get(self):
            self.render("index.html", list_info=[11, 22, 33], key1='value1')
    
    
    class AsyncHandler(tornado.web.RequestHandler):
    
        @gen.coroutine
        def get(self):
            future = Future()
            tornado.ioloop.IOLoop.current().add_timeout(time.time() + 20, self.doing)
            yield future
    
        def doing(self, *args, **kwargs):
            self.write('async')
            self.finish()
    
    
    settings = {
        'template_path': 'tmp',
        'static_path': 'statics'
    }
    
    application = tornado.web.Application([
        (r"/index", MainHandler),
        (r"/async", AsyncHandler),
    ], **settings)
    
    
    if __name__ == "__main__":
        application.listen(8888)
        tornado.ioloop.IOLoop.instance().start()

    此时再执行上面的操作,就会发现当async阻塞等待返回的时候,其他网页是可以继续访问的。这样单位时间处理的请求也就更多了。在服务器端将单线程用到极致。

    装饰器 + Future 从而实现Tornado的异步非阻塞,异步非阻塞的核心其实就在这个Future上,这个future类有点类似于一个标记,当装饰器装饰函数,函数内部实例化Future,并添加回调函数后。执行代码:

    from tornado import gen
    from tornado.concurrent import Future
    
    
    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()

    发现请求一直没有返回。实际上实例化future之后,产生了一个flag,只有当准备好响应数据或者满足响应条件的时候才可以回调callback函数,返回给客户端。

    2、同步阻塞与异步非阻塞对比

    前面的第一个实例其实就是同步阻塞的实例了。

    class SyncHandler(tornado.web.RequestHandler):
    
        def get(self):
            self.doing()
            self.write('sync')
    
        def doing(self):
            time.sleep(10)
    同步阻塞
    class AsyncHandler(tornado.web.RequestHandler):
        @gen.coroutine
        def get(self):
            future = Future()
            tornado.ioloop.IOLoop.current().add_timeout(time.time() + 5, self.doing)
            yield future
    
    
        def doing(self, *args, **kwargs):
            self.write('async')
            self.finish()
    异步非阻塞

    3、httpclient类库

    Tornado提供了httpclient类库用于发送Http请求,可以配合异步非阻塞使用。

    import tornado.web
    from tornado import gen
    from tornado import httpclient
     
    # 方式一:
    class AsyncHandler(tornado.web.RequestHandler):
        @gen.coroutine
        def get(self, *args, **kwargs):
            print('进入')
            http = httpclient.AsyncHTTPClient()
            data = yield http.fetch("http://www.google.com")
            print('K.O.',data)
            self.finish('6666')
     
    # 方式二:
    # class AsyncHandler(tornado.web.RequestHandler):
    #     @gen.coroutine
    #     def get(self):
    #         print('进入')
    #         http = httpclient.AsyncHTTPClient()
    #         yield http.fetch("http://www.google.com", self.done)
    #
    #     def done(self, response):
    #         print('K.O.')
    #         self.finish('666')
     
     
     
    application = tornado.web.Application([
        (r"/async", AsyncHandler),
    ])
     
    if __name__ == "__main__":
        application.listen(8888)
        tornado.ioloop.IOLoop.instance().start() 
    View Code

    说到这,我们来测试下通过另一个url来改变future的标记位。

    补充:

    import tornado.ioloop
    import tornado.web
    from tornado.web import UIModule
    from tornado import gen
    from tornado.concurrent import Future
    
    future = None
    
    class MainHandler(tornado.web.RequestHandler):
        def get(self):
            self.render("index.html", list_info=[11, 22, 33], key1='value1')
    
    
    class AsyncHandler(tornado.web.RequestHandler):
    
        @gen.coroutine
        def get(self):
            global future
            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()
    
    
    class StopHandler(tornado.web.RequestHandler):
        def get(self, *args, **kwargs):
            future.set_result('...')
    
    settings = {
        'template_path': 'tmp',
        'static_path': 'statics'
    }
    
    application = tornado.web.Application([
        (r"/index", MainHandler),
        (r"/async", AsyncHandler),
        (r"/stop", StopHandler),
    ], **settings)
    
    
    if __name__ == "__main__":
        application.listen(8888)
        tornado.ioloop.IOLoop.instance().start()
    远端url控制某个url的future

    200行的自定义异步非阻塞web框架

    #!/usr/bin/env python3
    # encoding: utf-8
    # Author: Dandy
    import re
    import socket
    import select
    import time
    
    
    class HttpResponse(object):
        """
        封装响应信息
        """
        def __init__(self, content=''):
            self.content = content
    
            self.headers = {}
            self.cookies = {}
    
        def response(self):
            return bytes(self.content, encoding='utf-8')
    
    
    class HttpNotFound(HttpResponse):
        """
        404时的错误提示
        """
        def __init__(self):
            super(HttpNotFound, self).__init__('404 Not Found')
    
    
    class HttpRequest(object):
        """
        用户封装用户请求信息
        """
        def __init__(self, conn):
            self.conn = conn
    
            self.header_bytes = bytes()
            self.header_dict = {}
            self.body_bytes = bytes()
    
            self.method = ""
            self.url = ""
            self.protocol = ""
    
            self.initialize()
            self.initialize_headers()
    
        def initialize(self):
    
            header_flag = False
            while True:
                try:
                    received = self.conn.recv(8096)
                except Exception as e:
                    received = None
                if not received:
                    break
                if header_flag:
                    self.body_bytes += received
                    continue
                temp = received.split(b'
    
    ', 1)
                if len(temp) == 1:
                    self.header_bytes += temp
                else:
                    h, b = temp
                    self.header_bytes += h
                    self.body_bytes += b
                    header_flag = True
    
        @property
        def header_str(self):
            return str(self.header_bytes, encoding='utf-8')
    
        def initialize_headers(self):
            headers = self.header_str.split('
    ')
            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, callback):
            self.callback = callback
            self._ready = False
            self.value = None
    
        def set_result(self, value=None):
            self.value = value
            self._ready = True
    
        @property
        def ready(self):
            return self._ready
    
    
    class TimeoutFuture(Future):
        """
        异步非阻塞超时
        """
        def __init__(self, timeout):
            super(TimeoutFuture, self).__init__(callback=None)
            self.timeout = timeout
            self.start_time = time.time()
    
        @property
        def ready(self):
            current_time = time.time()
            if current_time > self.start_time + self.timeout:
                self._ready = True
            return self._ready
    
    
    class Snow(object):
        """
        微型Web框架类
        """
        def __init__(self, routes):
            self.routes = routes
            self.inputs = set()
            self.request = None
            self.async_request_handler = {}
    
        def run(self, host='localhost', port=9999):
            """
            事件循环
            :param host:
            :param port:
            :return:
            """
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            sock.bind((host, port,))
            sock.setblocking(False)  # 非阻塞
            sock.listen(128)
            sock.setblocking(0)
            self.inputs.add(sock)  # 将此socket添加到inputs这个集合里
            try:
                while True:
                    readable_list, writeable_list, error_list = select.select(self.inputs, [], self.inputs,0.005)  # 监听
                    for conn in readable_list:  # 循环可读的集合
                        if sock == conn:  # socket有新的连接进来
                            client, address = conn.accept()  # 获取conn、addr
                            client.setblocking(False)
                            self.inputs.add(client)  # 将此连接conn加入监听
                        else:
                            gen = self.process(conn)  # 获取、处理连接conn,拿请求体请求体
                            if isinstance(gen, HttpResponse):  # 如果返回的是HttpResponse类型的,代表是正常的请求
                                conn.sendall(gen.response())  # 发送给客户端
                                self.inputs.remove(conn)  # 既然返回了,则需要从监听队列移除
                                conn.close()  # 关闭此socket连接
                            else:
                                yielded = next(gen)  # 不是正常的请求就必定是yield future
                                self.async_request_handler[conn] = yielded  # future对象加入字典中key==》conn,value==》future对象
                    self.polling_callback()  # for 循环执行完,执行此函数
    
            except Exception as e:
                pass
            finally:
                sock.close()
    
        def polling_callback(self):
            """
            遍历触发异步非阻塞的回调函数
            :return:
            """
            for conn in list(self.async_request_handler.keys()):  # 循环取key
                yielded = self.async_request_handler[conn]  # 根据循环的key取value
                # conn:socket对象
                # yielded:future对象
                if not yielded.ready:  # ready是布尔值,如果执行了set_result,就变成true,表示已经就绪
                    continue  # 否则循环继续
                if yielded.callback:  # 如果被set result
                    ret = yielded.callback(self.request, yielded)
                    conn.sendall(ret.response())  # 发送数据
                self.inputs.remove(conn)  # 从监听内移除此conn
                del self.async_request_handler[conn]  # 从循环字典删除
                conn.close()  # 关闭conn
    
        def process(self, conn):
            """
            处理路由系统以及执行函数
            :param conn:
            :return:
            """
            self.request = HttpRequest(conn)
            func = None
            for route in self.routes:
                if re.match(route[0], self.request.url):
                    func = route[1]
                    break
            if not func:
                return HttpNotFound()
            else:
                return func(self.request)
    自定义异步非阻塞框架

    使用

    from snow import Snow
    from snow import HttpResponse
     
     
    def index(request):
        return HttpResponse('OK')
     
     
    routes = [
        (r'/index/', index),
    ]
     
    app = Snow(routes)
    app.run(port=8012)
    基本使用
    from snow import Snow
    from snow import HttpResponse
    from snow import TimeoutFuture
     
    request_list = []
     
     
    def async(request):
        obj = TimeoutFuture(5)
        yield obj
     
     
    def home(request):
        return HttpResponse('home')
     
     
    routes = [
        (r'/home/', home),
        (r'/async/', async),
    ]
     
    app = Snow(routes)
    app.run(port=8012)
    异步非阻塞:超时
    from snow import Snow
    from snow import HttpResponse
    from snow import Future
     
    request_list = []
     
     
    def callback(request, future):
        return HttpResponse(future.value)
     
     
    def req(request):
        obj = Future(callback=callback)
        request_list.append(obj)
        yield obj
     
     
    def stop(request):
        obj = request_list[0]
        del request_list[0]
        obj.set_result('done')
        return HttpResponse('stop')
     
     
    routes = [
        (r'/req/', req),
        (r'/stop/', stop),
    ]
     
    app = Snow(routes)
    app.run(port=8012)
    异步非阻塞:等待

    基于等待模式可以完成自定制操作

    本blog参考自:https://www.cnblogs.com/wupeiqi/p/6536518.html

  • 相关阅读:
    lcn 分布式事务协调者集群原理
    springboot 监控 Actuator
    springboot 配置文件说明
    docker 安装jenkins
    docker 搭建maven 私服
    docker 安装 gitlab
    docker 安装软件
    docker 部署 java 项目
    mybatis 中between and用法
    vue-router history 模式 iis 配置
  • 原文地址:https://www.cnblogs.com/wuzdandz/p/9416889.html
Copyright © 2011-2022 走看看