• Tornado


    概述

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

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

    下载安装:

    pip3 install tornado
     
    源码安装
    https://pypi.python.org/packages/source/t/tornado/tornado-4.3.tar.gz
    

    框架使用

     1 #!/usr/bin/env python
     2 # -*- coding:utf-8 -*-
     3    
     4 import tornado.ioloop
     5 import tornado.web
     6    
     7    
     8 class MainHandler(tornado.web.RequestHandler):
     9     def get(self):
    10         self.write("Hello, world")
    11    
    12 application = tornado.web.Application([
    13     (r"/index", MainHandler),
    14 ])
    15    
    16    
    17 if __name__ == "__main__":
    18     application.listen(8888)
    19     tornado.ioloop.IOLoop.instance().start()
    View Code

    执行过程:

    • 第一步:执行脚本,监听 8888 端口
    • 第二步:浏览器客户端
      访问 /index  -->  http://127.0.0.1:8888/index
    • 第三步:服务器接受请求,并交由对应的类处理该请求
    • 第四步:类接受到请求之后,根据请求方式(post / get / delete ...)的不同调用并执行相应的方法
    • 第五步:方法返回值的字符串内容发送浏览器
     1 #!/usr/bin/env python
     2 # -*- coding:utf-8 -*-
     3 #!/usr/bin/env python
     4 # -*- coding:utf-8 -*-
     5 
     6 import tornado.ioloop
     7 import tornado.web
     8 from tornado import httpclient
     9 from tornado.web import asynchronous
    10 from tornado import gen
    11 
    12 import uimodules as md
    13 import uimethods as mt
    14 
    15 class MainHandler(tornado.web.RequestHandler):
    16         @asynchronous
    17         @gen.coroutine
    18         def get(self):
    19             print 'start get '
    20             http = httpclient.AsyncHTTPClient()
    21             http.fetch("http://127.0.0.1:8008/post/", self.callback)
    22             self.write('end')
    23 
    24         def callback(self, response):
    25             print response.body
    26 
    27 settings = {
    28     'template_path': 'template',
    29     'static_path': 'static',
    30     'static_url_prefix': '/static/',
    31     'ui_methods': mt,
    32     'ui_modules': md,
    33 }
    34 
    35 application = tornado.web.Application([
    36     (r"/index", MainHandler),
    37 ], **settings)
    38 
    39 
    40 if __name__ == "__main__":
    41     application.listen(8009)
    42     tornado.ioloop.IOLoop.instance().start()
    异步非阻塞

    二、路由系统

    路由系统其实就是 url 和 类 的对应关系,这里不同于其他框架,其他很多框架均是 url 对应 函数,Tornado中每个url对应的是一个类。

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
       
    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 BuyHandler(tornado.web.RequestHandler):
        def get(self):
            self.write("buy.wupeiqi.com/index")
       
    application = tornado.web.Application([
        (r"/index", MainHandler),
        (r"/story/([0-9]+)", StoryHandler),
    ])
       
    application.add_handlers('buy.wupeiqi.com$', [
        (r'/index',BuyHandler),
    ])
       
    if __name__ == "__main__":
        application.listen(80)
        tornado.ioloop.IOLoop.instance().start()
    

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

    三、模板引擎

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

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

    控制语句和对应的 Python 语句的格式基本完全相同。我们支持 ifforwhile 和 try,这些语句逻辑结束的位置需要用 {% end %} 做标记。还通过 extends 和 block 语句实现了模板继承。这些在 template 模块 的代码文档中有着详细的描述。

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

    1、基本使用

     1 #!/usr/bin/env python
     2 # -*- coding:utf-8 -*-
     3   
     4 import tornado.ioloop
     5 import tornado.web
     6   
     7   
     8 class MainHandler(tornado.web.RequestHandler):
     9     def get(self):
    10         self.render("index.html", list_info = [11,22,33])
    11   
    12 application = tornado.web.Application([
    13     (r"/index", MainHandler),
    14 ])
    15   
    16   
    17 if __name__ == "__main__":
    18     application.listen(8888)
    19     tornado.ioloop.IOLoop.instance().start()
    app.py
     1 <!DOCTYPE html>
     2 <html>
     3 <head>
     4     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
     5     <title>老男孩</title>
     6     <link href="{{static_url("css/common.css")}}" rel="stylesheet" />
     7 </head>
     8 <body>
     9 
    10     <div>
    11         <ul>
    12             {% for item in list_info %}
    13                 <li>{{item}}</li>
    14             {% end %}
    15         </ul>
    16     </div>
    17     
    18     <script src="{{static_url("js/jquery-1.8.2.min.js")}}"></script>
    19     
    20 </body>
    21 </html>
    index.html
     1 在模板中默认提供了一些函数、字段、类以供模板使用:
     2 
     3 escape: tornado.escape.xhtml_escape 的別名
     4 xhtml_escape: tornado.escape.xhtml_escape 的別名
     5 url_escape: tornado.escape.url_escape 的別名
     6 json_encode: tornado.escape.json_encode 的別名
     7 squeeze: tornado.escape.squeeze 的別名
     8 linkify: tornado.escape.linkify 的別名
     9 datetime: Python 的 datetime 模组
    10 handler: 当前的 RequestHandler 对象
    11 request: handler.request 的別名
    12 current_user: handler.current_user 的別名
    13 locale: handler.locale 的別名
    14 _: handler.locale.translate 的別名
    15 static_url: for handler.static_url 的別名
    16 xsrf_form_html: handler.xsrf_form_html 的別名
    其他方法

    2、母版

     1 <!DOCTYPE html>
     2 <html>
     3 <head>
     4     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
     5     <title>老男孩</title>
     6     <link href="{{static_url("css/common.css")}}" rel="stylesheet" />
     7     {% block CSS %}{% end %}
     8 </head>
     9 <body>
    10 
    11     <div class="pg-header">
    12 
    13     </div>
    14     
    15     {% block RenderBody %}{% end %}
    16    
    17     <script src="{{static_url("js/jquery-1.8.2.min.js")}}"></script>
    18     
    19     {% block JavaScript %}{% end %}
    20 </body>
    21 </html>
    layout.html
     1 {% extends 'layout.html'%}
     2 {% block CSS %}
     3     <link href="{{static_url("css/index.css")}}" rel="stylesheet" />
     4 {% end %}
     5 
     6 {% block RenderBody %}
     7     <h1>Index</h1>
     8 
     9     <ul>
    10     {%  for item in li %}
    11         <li>{{item}}</li>
    12     {% end %}
    13     </ul>
    14 
    15 {% end %}
    16 
    17 {% block JavaScript %}
    18     
    19 {% end %}
    index.html

    3、导入

    1 <div>
    2     <ul>
    3         <li>1024</li>
    4         <li>42区</li>
    5     </ul>
    6 </div>
    header
     1 <!DOCTYPE html>
     2 <html>
     3 <head>
     4     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
     5     <title>老男孩</title>
     6     <link href="{{static_url("css/common.css")}}" rel="stylesheet" />
     7 </head>
     8 <body>
     9 
    10     <div class="pg-header">
    11         {% include 'header.html' %}
    12     </div>
    13     
    14     <script src="{{static_url("js/jquery-1.8.2.min.js")}}"></script>
    15     
    16 </body>
    17 </html>
    index.html

    4、自定义UIMethod以UIModule

    a. 定义

    1 # uimethods.py
    2  
    3 def tab(self):
    4     return 'UIMethod'
    uimethods.py
     1 #!/usr/bin/env python
     2 # -*- coding:utf-8 -*-
     3 from tornado.web import UIModule
     4 from tornado import escape
     5 
     6 class custom(UIModule):
     7 
     8     def render(self, *args, **kwargs):
     9         return escape.xhtml_escape('<h1>wupeiqi</h1>')
    10         #return escape.xhtml_escape('<h1>wupeiqi</h1>')
    uimodules.py

    b. 注册

     1 #!/usr/bin/env python
     2 # -*- coding:utf-8 -*-
     3 #!/usr/bin/env python
     4 # -*- coding:utf-8 -*-
     5 
     6 import tornado.ioloop
     7 import tornado.web
     8 from tornado.escape import linkify
     9 import uimodules as md
    10 import uimethods as mt
    11 
    12 class MainHandler(tornado.web.RequestHandler):
    13     def get(self):
    14         self.render('index.html')
    15 
    16 settings = {
    17     'template_path': 'template',
    18     'static_path': 'static',
    19     'static_url_prefix': '/static/',
    20     'ui_methods': mt,
    21     'ui_modules': md,
    22 }
    23 
    24 application = tornado.web.Application([
    25     (r"/index", MainHandler),
    26 ], **settings)
    27 
    28 
    29 if __name__ == "__main__":
    30     application.listen(8009)
    31     tornado.ioloop.IOLoop.instance().start()
    View Code

    c. 使用

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

    四、静态文件

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

     1 #!/usr/bin/env python
     2 # -*- coding:utf-8 -*-
     3  
     4 import tornado.ioloop
     5 import tornado.web
     6  
     7  
     8 class MainHandler(tornado.web.RequestHandler):
     9     def get(self):
    10         self.render('home/index.html')
    11  
    12 settings = {
    13     'template_path': 'template',
    14     'static_path': 'static',
    15     'static_url_prefix': '/static/',
    16 }
    17  
    18 application = tornado.web.Application([
    19     (r"/index", MainHandler),
    20 ], **settings)
    21  
    22  
    23 if __name__ == "__main__":
    24     application.listen(80)
    25     tornado.ioloop.IOLoop.instance().start()
    app.py
     1 <!DOCTYPE html>
     2 <html>
     3 <head lang="en">
     4     <meta charset="UTF-8">
     5     <title></title>
     6     <link href="{{static_url("commons.css")}}" rel="stylesheet" />
     7 </head>
     8 <body>
     9     <h1>hello</h1>
    10 </body>
    11 </html>
    index.html

    注:静态文件缓存的实现

     1  def get_content_version(cls, abspath):
     2         """Returns a version string for the resource at the given path.
     3 
     4         This class method may be overridden by subclasses.  The
     5         default implementation is a hash of the file's contents.
     6 
     7         .. versionadded:: 3.1
     8         """
     9         data = cls.get_content(abspath)
    10         hasher = hashlib.md5()
    11         if isinstance(data, bytes):
    12             hasher.update(data)
    13         else:
    14             for chunk in data:
    15                 hasher.update(chunk)
    16         return hasher.hexdigest()
    View Code

    五、cookie

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

    1、基本操作

    1 class MainHandler(tornado.web.RequestHandler):
    2     def get(self):
    3         if not self.get_cookie("mycookie"):
    4             self.set_cookie("mycookie", "myvalue")
    5             self.write("Your cookie was not set yet!")
    6         else:
    7             self.write("Your cookie was set!")
    View Code

    2、加密cookie(签名)

    Cookie 很容易被恶意的客户端伪造。加入你想在 cookie 中保存当前登陆用户的 id 之类的信息,你需要对 cookie 作签名以防止伪造。Tornado 通过 set_secure_cookie 和 get_secure_cookie 方法直接支持了这种功能。 要使用这些方法,你需要在创建应用时提供一个密钥,名字为 cookie_secret。 你可以把它作为一个关键词参数传入应用的设置中:

      1 def _create_signature_v1(secret, *parts):
      2     hash = hmac.new(utf8(secret), digestmod=hashlib.sha1)
      3     for part in parts:
      4         hash.update(utf8(part))
      5     return utf8(hash.hexdigest())
      6 
      7 # 加密
      8 def _create_signature_v2(secret, s):
      9     hash = hmac.new(utf8(secret), digestmod=hashlib.sha256)
     10     hash.update(utf8(s))
     11     return utf8(hash.hexdigest())
     12 
     13 def create_signed_value(secret, name, value, version=None, clock=None,
     14                         key_version=None):
     15     if version is None:
     16         version = DEFAULT_SIGNED_VALUE_VERSION
     17     if clock is None:
     18         clock = time.time
     19 
     20     timestamp = utf8(str(int(clock())))
     21     value = base64.b64encode(utf8(value))
     22     if version == 1:
     23         signature = _create_signature_v1(secret, name, value, timestamp)
     24         value = b"|".join([value, timestamp, signature])
     25         return value
     26     elif version == 2:
     27         # The v2 format consists of a version number and a series of
     28         # length-prefixed fields "%d:%s", the last of which is a
     29         # signature, all separated by pipes.  All numbers are in
     30         # decimal format with no leading zeros.  The signature is an
     31         # HMAC-SHA256 of the whole string up to that point, including
     32         # the final pipe.
     33         #
     34         # The fields are:
     35         # - format version (i.e. 2; no length prefix)
     36         # - key version (integer, default is 0)
     37         # - timestamp (integer seconds since epoch)
     38         # - name (not encoded; assumed to be ~alphanumeric)
     39         # - value (base64-encoded)
     40         # - signature (hex-encoded; no length prefix)
     41         def format_field(s):
     42             return utf8("%d:" % len(s)) + utf8(s)
     43         to_sign = b"|".join([
     44             b"2",
     45             format_field(str(key_version or 0)),
     46             format_field(timestamp),
     47             format_field(name),
     48             format_field(value),
     49             b''])
     50 
     51         if isinstance(secret, dict):
     52             assert key_version is not None, 'Key version must be set when sign key dict is used'
     53             assert version >= 2, 'Version must be at least 2 for key version support'
     54             secret = secret[key_version]
     55 
     56         signature = _create_signature_v2(secret, to_sign)
     57         return to_sign + signature
     58     else:
     59         raise ValueError("Unsupported version %d" % version)
     60 
     61 # 解密
     62 def _decode_signed_value_v1(secret, name, value, max_age_days, clock):
     63     parts = utf8(value).split(b"|")
     64     if len(parts) != 3:
     65         return None
     66     signature = _create_signature_v1(secret, name, parts[0], parts[1])
     67     if not _time_independent_equals(parts[2], signature):
     68         gen_log.warning("Invalid cookie signature %r", value)
     69         return None
     70     timestamp = int(parts[1])
     71     if timestamp < clock() - max_age_days * 86400:
     72         gen_log.warning("Expired cookie %r", value)
     73         return None
     74     if timestamp > clock() + 31 * 86400:
     75         # _cookie_signature does not hash a delimiter between the
     76         # parts of the cookie, so an attacker could transfer trailing
     77         # digits from the payload to the timestamp without altering the
     78         # signature.  For backwards compatibility, sanity-check timestamp
     79         # here instead of modifying _cookie_signature.
     80         gen_log.warning("Cookie timestamp in future; possible tampering %r",
     81                         value)
     82         return None
     83     if parts[1].startswith(b"0"):
     84         gen_log.warning("Tampered cookie %r", value)
     85         return None
     86     try:
     87         return base64.b64decode(parts[0])
     88     except Exception:
     89         return None
     90 
     91 
     92 def _decode_fields_v2(value):
     93     def _consume_field(s):
     94         length, _, rest = s.partition(b':')
     95         n = int(length)
     96         field_value = rest[:n]
     97         # In python 3, indexing bytes returns small integers; we must
     98         # use a slice to get a byte string as in python 2.
     99         if rest[n:n + 1] != b'|':
    100             raise ValueError("malformed v2 signed value field")
    101         rest = rest[n + 1:]
    102         return field_value, rest
    103 
    104     rest = value[2:]  # remove version number
    105     key_version, rest = _consume_field(rest)
    106     timestamp, rest = _consume_field(rest)
    107     name_field, rest = _consume_field(rest)
    108     value_field, passed_sig = _consume_field(rest)
    109     return int(key_version), timestamp, name_field, value_field, passed_sig
    110 
    111 
    112 def _decode_signed_value_v2(secret, name, value, max_age_days, clock):
    113     try:
    114         key_version, timestamp, name_field, value_field, passed_sig = _decode_fields_v2(value)
    115     except ValueError:
    116         return None
    117     signed_string = value[:-len(passed_sig)]
    118 
    119     if isinstance(secret, dict):
    120         try:
    121             secret = secret[key_version]
    122         except KeyError:
    123             return None
    124 
    125     expected_sig = _create_signature_v2(secret, signed_string)
    126     if not _time_independent_equals(passed_sig, expected_sig):
    127         return None
    128     if name_field != utf8(name):
    129         return None
    130     timestamp = int(timestamp)
    131     if timestamp < clock() - max_age_days * 86400:
    132         # The signature has expired.
    133         return None
    134     try:
    135         return base64.b64decode(value_field)
    136     except Exception:
    137         return None
    138 
    139 
    140 def get_signature_key_version(value):
    141     value = utf8(value)
    142     version = _get_version(value)
    143     if version < 2:
    144         return None
    145     try:
    146         key_version, _, _, _, _ = _decode_fields_v2(value)
    147     except ValueError:
    148         return None
    149 
    150     return key_version
    View Code

    签名Cookie的本质是:

    写cookie过程:

    • 将值进行base64加密
    • 对除值以外的内容进行签名,哈希算法(无法逆向解析)
    • 拼接 签名 + 加密值

    读cookie过程:

    • 读取 签名 + 加密值
    • 对签名进行验证
    • base64解密,获取值内容

    注:许多API验证机制和安全cookie的实现机制相同。

     1 #!/usr/bin/env python
     2 # -*- coding:utf-8 -*-
     3  
     4 import tornado.ioloop
     5 import tornado.web
     6  
     7  
     8 class MainHandler(tornado.web.RequestHandler):
     9  
    10     def get(self):
    11         login_user = self.get_secure_cookie("login_user", None)
    12         if login_user:
    13             self.write(login_user)
    14         else:
    15             self.redirect('/login')
    16  
    17  
    18 class LoginHandler(tornado.web.RequestHandler):
    19     def get(self):
    20         self.current_user()
    21  
    22         self.render('login.html', **{'status': ''})
    23  
    24     def post(self, *args, **kwargs):
    25  
    26         username = self.get_argument('name')
    27         password = self.get_argument('pwd')
    28         if username == 'wupeiqi' and password == '123':
    29             self.set_secure_cookie('login_user', '武沛齐')
    30             self.redirect('/')
    31         else:
    32             self.render('login.html', **{'status': '用户名或密码错误'})
    33  
    34 settings = {
    35     'template_path': 'template',
    36     'static_path': 'static',
    37     'static_url_prefix': '/static/',
    38     'cookie_secret': 'aiuasdhflashjdfoiuashdfiuh'
    39 }
    40  
    41 application = tornado.web.Application([
    42     (r"/index", MainHandler),
    43     (r"/login", LoginHandler),
    44 ], **settings)
    45  
    46  
    47 if __name__ == "__main__":
    48     application.listen(8888)
    49     tornado.ioloop.IOLoop.instance().start()
     1 #!/usr/bin/env python
     2 # -*- coding:utf-8 -*-
     3  
     4 import tornado.ioloop
     5 import tornado.web
     6  
     7 class BaseHandler(tornado.web.RequestHandler):
     8  
     9     def get_current_user(self):
    10         return self.get_secure_cookie("login_user")
    11  
    12 class MainHandler(BaseHandler):
    13  
    14     @tornado.web.authenticated
    15     def get(self):
    16         login_user = self.current_user
    17         self.write(login_user)
    18  
    19  
    20  
    21 class LoginHandler(tornado.web.RequestHandler):
    22     def get(self):
    23         self.current_user()
    24  
    25         self.render('login.html', **{'status': ''})
    26  
    27     def post(self, *args, **kwargs):
    28  
    29         username = self.get_argument('name')
    30         password = self.get_argument('pwd')
    31         if username == 'wupeiqi' and password == '123':
    32             self.set_secure_cookie('login_user', '武沛齐')
    33             self.redirect('/')
    34         else:
    35             self.render('login.html', **{'status': '用户名或密码错误'})
    36  
    37 settings = {
    38     'template_path': 'template',
    39     'static_path': 'static',
    40     'static_url_prefix': '/static/',
    41     'cookie_secret': 'aiuasdhflashjdfoiuashdfiuh',
    42     'login_url': '/login'
    43 }
    44  
    45 application = tornado.web.Application([
    46     (r"/index", MainHandler),
    47     (r"/login", LoginHandler),
    48 ], **settings)
    49  
    50  
    51 if __name__ == "__main__":
    52     application.listen(8888)
    53     tornado.ioloop.IOLoop.instance().start()
    基于签名Cookie实现用户验证

    3、JavaScript操作Cookie

    由于Cookie保存在浏览器端,所以在浏览器端也可以使用JavaScript来操作Cookie。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    /*
    设置cookie,指定秒数过期
     */
    function setCookie(name,value,expires){
        var temp = [];
        var current_date = new Date();
        current_date.setSeconds(current_date.getSeconds() + 5);
        document.cookie = name + "= "+ value +";expires=" + current_date.toUTCString();
    }

    对于参数:

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

    注:jQuery中也有指定的插件 jQuery Cookie 专门用于操作cookie,猛击这里

  • 相关阅读:
    JAVA 问题
    WebStrom配置多个项目的Dweployment时,设置默认的启动配置
    C#中有关数组和string引用类型或值类型的判断
    Delegate(代理)异常:该委托必须有一个目标
    RMAN BACKUP
    Oracle ORA-01033: 错误解决办法
    微信公众号开发 接口配置信息 配置失败
    使用JAVA开发微信公众平台(一)——环境搭建与开发接入
    微信开发准备(四)--nat123内网地址公网映射实现
    nat123安装启动教程帮助
  • 原文地址:https://www.cnblogs.com/zl-py/p/6010497.html
Copyright © 2020-2023  润新知