• Tornado框架


    一、概述

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

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

    下载安装:

    1 pip3 install tornado
    2  
    3 源码安装
    4 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对应的是一个类。

     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 class StoryHandler(tornado.web.RequestHandler):
    13     def get(self, story_id):
    14         self.write("You requested the story " + story_id)
    15    
    16 class BuyHandler(tornado.web.RequestHandler):
    17     def get(self):
    18         self.write("buy.wupeiqi.com/index")
    19    
    20 application = tornado.web.Application([
    21     (r"/index", MainHandler),
    22     (r"/story/([0-9]+)", StoryHandler),
    23 ])
    24    
    25 application.add_handlers('buy.wupeiqi.com$', [
    26     (r'/index',BuyHandler),
    27 ])
    28    
    29 if __name__ == "__main__":
    30     application.listen(80)
    31     tornado.ioloop.IOLoop.instance().start()
    View Code

    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.html
     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. 使用

     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     {% module custom(123) %}
    11     {{ tab() }}
    12 </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 class MainHandler(tornado.web.RequestHandler):
     2     def get(self):
     3         if not self.get_secure_cookie("mycookie"):
     4             self.set_secure_cookie("mycookie", "myvalue")
     5             self.write("Your cookie was not set yet!")
     6         else:
     7             self.write("Your cookie was set!")
     8              
     9 application = tornado.web.Application([
    10     (r"/", MainHandler),
    11 ], cookie_secret="61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=")
    View Code
      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
    内部算法

    签名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()
    基于Cookie实现用户验证-Demo
     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实现用户验证-Demo

    3、JavaScript操作Cookie

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

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

    对于参数:

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

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

    ㈥ csrf

    Tornado中的夸张请求伪造和Django中的相似,跨站伪造请求(Cross-site request forgery)

    1 settings = {
    2     "xsrf_cookies": True,
    3 }
    4 application = tornado.web.Application([
    5     (r"/", MainHandler),
    6     (r"/login", LoginHandler),
    7 ], **settings)
    配置
    1 <form action="/new_message" method="post">
    2   {{ xsrf_form_html() }}
    3   <input type="text" name="message"/>
    4   <input type="submit" value="Post"/>
    5 </form>
    使用-普通表单
     1 function getCookie(name) {
     2     var r = document.cookie.match("\b" + name + "=([^;]*)\b");
     3     return r ? r[1] : undefined;
     4 }
     5 
     6 jQuery.postJSON = function(url, args, callback) {
     7     args._xsrf = getCookie("_xsrf");
     8     $.ajax({url: url, data: $.param(args), dataType: "text", type: "POST",
     9         success: function(response) {
    10         callback(eval("(" + response + ")"));
    11     }});
    12 };
    使用 - AJAX

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

    ㈦ 上传文件

    1、Form表单上传

     1 <!DOCTYPE html>
     2 <html>
     3 <head>
     4     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
     5     <title>上传文件</title>
     6 </head>
     7 <body>
     8     <form id="my_form" name="form" action="/index" method="POST"  enctype="multipart/form-data" >
     9         <input name="fff" id="my_file"  type="file" />
    10         <input type="submit" value="提交"  />
    11     </form>
    12 </body>
    13 </html>
    HTML
     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 
    11         self.render('index.html')
    12 
    13     def post(self, *args, **kwargs):
    14         file_metas = self.request.files["fff"]
    15         # print(file_metas)
    16         for meta in file_metas:
    17             file_name = meta['filename']
    18             with open(file_name,'wb') as up:
    19                 up.write(meta['body'])
    20 
    21 settings = {
    22     'template_path': 'template',
    23 }
    24 
    25 application = tornado.web.Application([
    26     (r"/index", MainHandler),
    27 ], **settings)
    28 
    29 
    30 if __name__ == "__main__":
    31     application.listen(8000)
    32     tornado.ioloop.IOLoop.instance().start()
    Python

    2、AJAX上传

     1 <!DOCTYPE html>
     2 <html>
     3 <head lang="en">
     4     <meta charset="UTF-8">
     5     <title></title>
     6 </head>
     7 <body>
     8     <input type="file" id="img" />
     9     <input type="button" onclick="UploadFile();" />
    10     <script>
    11         function UploadFile(){
    12             var fileObj = document.getElementById("img").files[0];
    13 
    14             var form = new FormData();
    15             form.append("k1", "v1");
    16             form.append("fff", fileObj);
    17 
    18             var xhr = new XMLHttpRequest();
    19             xhr.open("post", '/index', true);
    20             xhr.send(form);
    21         }
    22     </script>
    23 </body>
    24 </html>
    HTML - XMLHttpRequest
     1 <!DOCTYPE html>
     2 <html>
     3 <head lang="en">
     4     <meta charset="UTF-8">
     5     <title></title>
     6 </head>
     7 <body>
     8     <input type="file" id="img" />
     9     <input type="button" onclick="UploadFile();" />
    10     <script>
    11         function UploadFile(){
    12             var fileObj = $("#img")[0].files[0];
    13             var form = new FormData();
    14             form.append("k1", "v1");
    15             form.append("fff", fileObj);
    16 
    17             $.ajax({
    18                 type:'POST',
    19                 url: '/index',
    20                 data: form,
    21                 processData: false,  // tell jQuery not to process the data
    22                 contentType: false,  // tell jQuery not to set contentType
    23                 success: function(arg){
    24                     console.log(arg);
    25                 }
    26             })
    27         }
    28     </script>
    29 </body>
    30 </html>
    HTML - jQuery
     1 <!DOCTYPE html>
     2 <html>
     3 <head lang="en">
     4     <meta charset="UTF-8">
     5     <title></title>
     6 </head>
     7 <body>
     8     <form id="my_form" name="form" action="/index" method="POST"  enctype="multipart/form-data" >
     9         <div id="main">
    10             <input name="fff" id="my_file"  type="file" />
    11             <input type="button" name="action" value="Upload" onclick="redirect()"/>
    12             <iframe id='my_iframe' name='my_iframe' src=""  class="hide"></iframe>
    13         </div>
    14     </form>
    15 
    16     <script>
    17         function redirect(){
    18             document.getElementById('my_iframe').onload = Testt;
    19             document.getElementById('my_form').target = 'my_iframe';
    20             document.getElementById('my_form').submit();
    21 
    22         }
    23         
    24         function Testt(ths){
    25             var t = $("#my_iframe").contents().find("body").text();
    26             console.log(t);
    27         }
    28     </script>
    29 </body>
    30 </html>
    HTML - iframe
     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 
    11         self.render('index.html')
    12 
    13     def post(self, *args, **kwargs):
    14         file_metas = self.request.files["fff"]
    15         # print(file_metas)
    16         for meta in file_metas:
    17             file_name = meta['filename']
    18             with open(file_name,'wb') as up:
    19                 up.write(meta['body'])
    20 
    21 settings = {
    22     'template_path': 'template',
    23 }
    24 
    25 application = tornado.web.Application([
    26     (r"/index", MainHandler),
    27 ], **settings)
    28 
    29 
    30 if __name__ == "__main__":
    31     application.listen(8000)
    32     tornado.ioloop.IOLoop.instance().start()
    Python
     1 <script type="text/javascript">
     2  
     3     $(document).ready(function () {
     4  
     5         $("#formsubmit").click(function () {
     6  
     7             var iframe = $('<iframe name="postiframe" id="postiframe" style="display: none"></iframe>');
     8  
     9             $("body").append(iframe);
    10  
    11             var form = $('#theuploadform');
    12             form.attr("action", "/upload.aspx");
    13             form.attr("method", "post");
    14  
    15             form.attr("encoding", "multipart/form-data");
    16             form.attr("enctype", "multipart/form-data");
    17  
    18             form.attr("target", "postiframe");
    19             form.attr("file", $('#userfile').val());
    20             form.submit();
    21  
    22             $("#postiframe").load(function () {
    23                 iframeContents = this.contentWindow.document.body.innerHTML;
    24                 $("#textarea").html(iframeContents);
    25             });
    26  
    27             return false;
    28  
    29         });
    30  
    31     });
    32  
    33 </script>
    34  
    35  
    36 <form id="theuploadform">
    37     <input id="userfile" name="userfile" size="50" type="file" />
    38     <input id="formsubmit" type="submit" value="Send File" />
    39 </form>
    40  
    41 <div id="textarea">
    42 </div>
    扩展:基于iframe实现Ajax上传示例
    1  $('#upload_iframe').load(function(){
    2                     var iframeContents = this.contentWindow.document.body.innerText;
    3                     iframeContents = JSON.parse(iframeContents);
    4                    
    5                 })
    View Code
     1 function bindChangeAvatar1() {
     2             $('#avatarImg').change(function () {
     3                 var file_obj = $(this)[0].files[0];
     4                 $('#prevViewImg')[0].src = window.URL.createObjectURL(file_obj)
     5             })
     6         }
     7 
     8         function bindChangeAvatar2() {
     9             $('#avatarImg').change(function () {
    10                 var file_obj = $(this)[0].files[0];
    11                 var reader = new FileReader();
    12                 reader.readAsDataURL(file_obj);
    13                 reader.onload = function (e) {
    14                     $('#previewImg')[0].src = this.result;
    15                 };
    16             })
    17         }
    18 
    19         function bindChangeAvatar3() {
    20             $('#avatarImg').change(function () {
    21                 var file_obj = $(this)[0].files[0];
    22                 var form = new FormData();
    23                 form.add('img_upload', file_obj);
    24 
    25                 $.ajax({
    26                     url: '',
    27                     data: form,
    28                     processData: false,  // tell jQuery not to process the data
    29                     contentType: false,  // tell jQuery not to set contentType
    30                     success: function (arg) {
    31 
    32                     }
    33                 })
    34             })
    35         }
    36 
    37         function bindChangeAvatar4() {
    38             $('#avatarImg').change(function () {
    39                 $(this).parent().submit();
    40 
    41                 $('#upload_iframe').load(function () {
    42                     var iframeContents = this.contentWindow.document.body.innerText;
    43                     iframeContents = JSON.parse(iframeContents);
    44                     if (iframeContents.status) {
    45                         $('#previewImg').attr('src', '/' + iframeContents.data);
    46                     }
    47                 })
    48 
    49             })
    50         }
    其他

    ㈧ 验证码

    验证码原理在于后台自动创建一张带有随机内容的图片,然后将内容通过img标签输出到页面。

    安装图像处理模块:

    1 pip3 install pillow

    示例截图:

    ㈨ 异步非阻塞

    1、基本使用

    装饰器 + Future 从而实现Tornado的异步非阻塞

     1 class AsyncHandler(tornado.web.RequestHandler):
     2  
     3     @gen.coroutine
     4     def get(self):
     5         future = Future()
     6         future.add_done_callback(self.doing)
     7         yield future
     8         #
     9         # tornado.ioloop.IOLoop.current().add_future(future,self.doing)
    10         # yield future
    11  
    12     def doing(self,*args, **kwargs):
    13         self.write('async')
    14         self.finish()
    View Code

    当发送GET请求时,由于方法被@gen.coroutine装饰且yield 一个 Future对象,那么Tornado会等待,等待用户向future对象中放置数据或者发送信号,如果获取到数据或信号之后,就开始执行doing方法。

    异步非阻塞体现在当在Tornaod等待用户向future对象中放置数据时,还可以处理其他请求。

    注意:在等待用户向future对象中放置数据或信号时,此连接是不断开的。

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

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

    3、httpclient类库

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

     1 #!/usr/bin/env python
     2 # -*- coding:utf-8 -*-
     3  
     4  
     5 import tornado.web
     6 from tornado import gen
     7 from tornado import httpclient
     8  
     9 # 方式一:
    10 class AsyncHandler(tornado.web.RequestHandler):
    11     @gen.coroutine
    12     def get(self, *args, **kwargs):
    13         print('进入')
    14         http = httpclient.AsyncHTTPClient()
    15         data = yield http.fetch("http://www.google.com")
    16         print('完事',data)
    17         self.finish('6666')
    18  
    19 # 方式二:
    20 # class AsyncHandler(tornado.web.RequestHandler):
    21 #     @gen.coroutine
    22 #     def get(self):
    23 #         print('进入')
    24 #         http = httpclient.AsyncHTTPClient()
    25 #         yield http.fetch("http://www.google.com", self.done)
    26 #
    27 #     def done(self, response):
    28 #         print('完事')
    29 #         self.finish('666')
    30  
    31  
    32  
    33 application = tornado.web.Application([
    34     (r"/async", AsyncHandler),
    35 ])
    36  
    37 if __name__ == "__main__":
    38     application.listen(8888)
    39     tornado.ioloop.IOLoop.instance().start() 
    View Code
     1 #!/usr/bin/env python
     2 # -*- coding:utf-8 -*-
     3 """
     4 需要先安装支持异步操作Mysql的类库: 
     5     Tornado-MySQL: https://github.com/PyMySQL/Tornado-MySQL#installation
     6     
     7     pip3 install Tornado-MySQL
     8 
     9 """
    10 
    11 import tornado.web
    12 from tornado import gen
    13 
    14 import tornado_mysql
    15 from tornado_mysql import pools
    16 
    17 POOL = pools.Pool(
    18     dict(host='127.0.0.1', port=3306, user='root', passwd='123', db='cmdb'),
    19     max_idle_connections=1,
    20     max_recycle_sec=3)
    21 
    22 
    23 @gen.coroutine
    24 def get_user_by_conn_pool(user):
    25     cur = yield POOL.execute("SELECT SLEEP(%s)", (user,))
    26     row = cur.fetchone()
    27     raise gen.Return(row)
    28 
    29 
    30 @gen.coroutine
    31 def get_user(user):
    32     conn = yield tornado_mysql.connect(host='127.0.0.1', port=3306, user='root', passwd='123', db='cmdb',
    33                                        charset='utf8')
    34     cur = conn.cursor()
    35     # yield cur.execute("SELECT name,email FROM web_models_userprofile where name=%s", (user,))
    36     yield cur.execute("select sleep(10)")
    37     row = cur.fetchone()
    38     cur.close()
    39     conn.close()
    40     raise gen.Return(row)
    41 
    42 
    43 class LoginHandler(tornado.web.RequestHandler):
    44     def get(self, *args, **kwargs):
    45         self.render('login.html')
    46 
    47     @gen.coroutine
    48     def post(self, *args, **kwargs):
    49         user = self.get_argument('user')
    50         data = yield gen.Task(get_user, user)
    51         if data:
    52             print(data)
    53             self.redirect('http://www.oldboyedu.com')
    54         else:
    55             self.render('login.html')
    56 
    57 
    58 application = tornado.web.Application([
    59     (r"/login", LoginHandler),
    60 ])
    61 
    62 if __name__ == "__main__":
    63     application.listen(8888)
    64     tornado.ioloop.IOLoop.instance().start()
    基于异步非阻塞和Tornado-MySQL实现用户登录示例

    三、自定义web组件

    ㈠ Session

    1、面向对象基础

    面向对象中通过索引的方式访问对象,需要内部实现 __getitem__ 、__delitem__、__setitem__方法

     1 #!/usr/bin/env python
     2 # -*- coding:utf-8 -*-
     3    
     4 class Foo(object):
     5    
     6     def __getitem__(self, key):
     7         print  '__getitem__',key
     8    
     9     def __setitem__(self, key, value):
    10         print '__setitem__',key,value
    11    
    12     def __delitem__(self, key):
    13         print '__delitem__',key
    14    
    15    
    16    
    17 obj = Foo()
    18 result = obj['k1']
    19 #obj['k2'] = 'wupeiqi'
    20 #del obj['k1']
    View Code

    2、Tornado扩展

    Tornado框架中,默认执行Handler的get/post等方法之前默认会执行 initialize方法,所以可以通过自定义的方式使得所有请求在处理前执行操作...

     1 class BaseHandler(tornado.web.RequestHandler):
     2    
     3     def initialize(self):
     4         self.xxoo = "wupeiqi"
     5    
     6    
     7 class MainHandler(BaseHandler):
     8    
     9     def get(self):
    10         print(self.xxoo)
    11         self.write('index')
    12  
    13 class IndexHandler(BaseHandler):
    14    
    15     def get(self):
    16         print(self.xxoo)
    17         self.write('index')
    View Code

    3、session

    session其实就是定义在服务器端用于保存用户回话的容器,其必须依赖cookie才能实现。

     1 #!/usr/bin/env python
     2 # -*- coding:utf-8 -*-
     3 import config
     4 from hashlib import sha1
     5 import os
     6 import time
     7 
     8 create_session_id = lambda: sha1(bytes('%s%s' % (os.urandom(16), time.time()), encoding='utf-8')).hexdigest()
     9 
    10 
    11 class SessionFactory:
    12 
    13     @staticmethod
    14     def get_session_obj(handler):
    15         obj = None
    16 
    17         if config.SESSION_TYPE == "cache":
    18             obj = CacheSession(handler)
    19         elif config.SESSION_TYPE == "memcached":
    20             obj = MemcachedSession(handler)
    21         elif config.SESSION_TYPE == "redis":
    22             obj = RedisSession(handler)
    23         return obj
    24 
    25 
    26 class CacheSession:
    27     session_container = {}
    28     session_id = "__sessionId__"
    29 
    30     def __init__(self, handler):
    31         self.handler = handler
    32         client_random_str = handler.get_cookie(CacheSession.session_id, None)
    33         if client_random_str and client_random_str in CacheSession.session_container:
    34             self.random_str = client_random_str
    35         else:
    36             self.random_str = create_session_id()
    37             CacheSession.session_container[self.random_str] = {}
    38 
    39         expires_time = time.time() + config.SESSION_EXPIRES
    40         handler.set_cookie(CacheSession.session_id, self.random_str, expires=expires_time)
    41 
    42     def __getitem__(self, key):
    43         ret = CacheSession.session_container[self.random_str].get(key, None)
    44         return ret
    45 
    46     def __setitem__(self, key, value):
    47         CacheSession.session_container[self.random_str][key] = value
    48 
    49     def __delitem__(self, key):
    50         if key in CacheSession.session_container[self.random_str]:
    51             del CacheSession.session_container[self.random_str][key]
    52 
    53 
    54 class RedisSession:
    55     def __init__(self, handler):
    56         pass
    57 
    58 
    59 class MemcachedSession:
    60     def __init__(self, handler):
    61         pass
    自定义Session

    4、分布式Session

      1 #!/usr/bin/env python
      2 #coding:utf-8
      3 
      4 import sys
      5 import math
      6 from bisect import bisect
      7 
      8 
      9 if sys.version_info >= (2, 5):
     10     import hashlib
     11     md5_constructor = hashlib.md5
     12 else:
     13     import md5
     14     md5_constructor = md5.new
     15 
     16 
     17 class HashRing(object):
     18     """一致性哈希"""
     19     
     20     def __init__(self,nodes):
     21         '''初始化
     22         nodes : 初始化的节点,其中包含节点已经节点对应的权重
     23                 默认每一个节点有32个虚拟节点
     24                 对于权重,通过多创建虚拟节点来实现
     25                 如:nodes = [
     26                         {'host':'127.0.0.1:8000','weight':1},
     27                         {'host':'127.0.0.1:8001','weight':2},
     28                         {'host':'127.0.0.1:8002','weight':1},
     29                     ]
     30         '''
     31         
     32         self.ring = dict()
     33         self._sorted_keys = []
     34 
     35         self.total_weight = 0
     36         
     37         self.__generate_circle(nodes)
     38         
     39             
     40             
     41     def __generate_circle(self,nodes):
     42         for node_info in nodes:
     43             self.total_weight += node_info.get('weight',1)
     44             
     45         for node_info in nodes:
     46             weight = node_info.get('weight',1)
     47             node = node_info.get('host',None)
     48                 
     49             virtual_node_count = math.floor((32*len(nodes)*weight) / self.total_weight)
     50             for i in xrange(0,int(virtual_node_count)):
     51                 key = self.gen_key_thirty_two( '%s-%s' % (node, i) )
     52                 if self._sorted_keys.__contains__(key):
     53                     raise Exception('该节点已经存在.')
     54                 self.ring[key] = node
     55                 self._sorted_keys.append(key)
     56             
     57     def add_node(self,node):
     58         ''' 新建节点
     59         node : 要添加的节点,格式为:{'host':'127.0.0.1:8002','weight':1},其中第一个元素表示节点,第二个元素表示该节点的权重。
     60         '''
     61         node = node.get('host',None)
     62         if not node:
     63                 raise Exception('节点的地址不能为空.')
     64                 
     65         weight = node.get('weight',1)
     66         
     67         self.total_weight += weight
     68         nodes_count = len(self._sorted_keys) + 1
     69         
     70         virtual_node_count = math.floor((32 * nodes_count * weight) / self.total_weight)
     71         for i in xrange(0,int(virtual_node_count)):
     72             key = self.gen_key_thirty_two( '%s-%s' % (node, i) )
     73             if self._sorted_keys.__contains__(key):
     74                 raise Exception('该节点已经存在.')
     75             self.ring[key] = node
     76             self._sorted_keys.append(key)
     77         
     78     def remove_node(self,node):
     79         ''' 移除节点
     80         node : 要移除的节点 '127.0.0.1:8000'
     81         '''
     82         for key,value in self.ring.items():
     83             if value == node:
     84                 del self.ring[key]
     85                 self._sorted_keys.remove(key)
     86     
     87     def get_node(self,string_key):
     88         '''获取 string_key 所在的节点'''
     89         pos = self.get_node_pos(string_key)
     90         if pos is None:
     91             return None
     92         return self.ring[ self._sorted_keys[pos]].split(':')
     93     
     94     def get_node_pos(self,string_key):
     95         '''获取 string_key 所在的节点的索引'''
     96         if not self.ring:
     97             return None
     98             
     99         key = self.gen_key_thirty_two(string_key)
    100         nodes = self._sorted_keys
    101         pos = bisect(nodes, key)
    102         return pos
    103     
    104     def gen_key_thirty_two(self, key):
    105         
    106         m = md5_constructor()
    107         m.update(key)
    108         return long(m.hexdigest(), 16)
    109         
    110     def gen_key_sixteen(self,key):
    111         
    112         b_key = self.__hash_digest(key)
    113         return self.__hash_val(b_key, lambda x: x)
    114 
    115     def __hash_val(self, b_key, entry_fn):
    116         return (( b_key[entry_fn(3)] << 24)|(b_key[entry_fn(2)] << 16)|(b_key[entry_fn(1)] << 8)| b_key[entry_fn(0)] )
    117 
    118     def __hash_digest(self, key):
    119         m = md5_constructor()
    120         m.update(key)
    121         return map(ord, m.digest())
    122 
    123 
    124 """
    125 nodes = [
    126     {'host':'127.0.0.1:8000','weight':1},
    127     {'host':'127.0.0.1:8001','weight':2},
    128     {'host':'127.0.0.1:8002','weight':1},
    129 ]
    130 
    131 ring = HashRing(nodes)
    132 result = ring.get_node('98708798709870987098709879087')
    133 print result
    134 
    135 """
    一致性哈西
     1 from hashlib import sha1
     2 import os, time
     3 
     4 
     5 create_session_id = lambda: sha1('%s%s' % (os.urandom(16), time.time())).hexdigest()
     6 
     7 
     8 class Session(object):
     9 
    10     session_id = "__sessionId__"
    11 
    12     def __init__(self, request):
    13         session_value = request.get_cookie(Session.session_id)
    14         if not session_value:
    15             self._id = create_session_id()
    16         else:
    17             self._id = session_value
    18         request.set_cookie(Session.session_id, self._id)
    19 
    20     def __getitem__(self, key):
    21         # 根据 self._id ,在一致性哈西中找到其对应的服务器IP
    22         # 找到相对应的redis服务器,如: r = redis.StrictRedis(host='localhost', port=6379, db=0)
    23         # 使用python redis api 链接
    24         # 获取数据,即:
    25         # return self._redis.hget(self._id, name)
    26 
    27     def __setitem__(self, key, value):
    28         # 根据 self._id ,在一致性哈西中找到其对应的服务器IP
    29         # 使用python redis api 链接
    30         # 设置session
    31         # self._redis.hset(self._id, name, value)
    32 
    33 
    34     def __delitem__(self, key):
    35         # 根据 self._id 找到相对应的redis服务器
    36         # 使用python redis api 链接
    37         # 删除,即:
    38         return self._redis.hdel(self._id, name)
    session

    ㈡ 表单验证

    在Web程序中往往包含大量的表单验证的工作,如:判断输入是否为空,是否符合规则。

     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     <form action="/index" method="post">
    11 
    12         <p>hostname: <input type="text" name="host" /> </p>
    13         <p>ip: <input type="text" name="ip" /> </p>
    14         <p>port: <input type="text" name="port" /> </p>
    15         <p>phone: <input type="text" name="phone" /> </p>
    16         <input type="submit" />
    17     </form>
    18 </body>
    19 </html>
    HTML
     1 #!/usr/bin/env python
     2 # -*- coding:utf-8 -*-
     3   
     4 import tornado.ioloop
     5 import tornado.web
     6 from hashlib import sha1
     7 import os, time
     8 import re
     9   
    10   
    11 class MainForm(object):
    12     def __init__(self):
    13         self.host = "(.*)"
    14         self.ip = "^(25[0-5]|2[0-4]d|[0-1]?d?d)(.(25[0-5]|2[0-4]d|[0-1]?d?d)){3}$"
    15         self.port = '(d+)'
    16         self.phone = '^1[3|4|5|8][0-9]d{8}$'
    17   
    18     def check_valid(self, request):
    19         form_dict = self.__dict__
    20         for key, regular in form_dict.items():
    21             post_value = request.get_argument(key)
    22             # 让提交的数据 和 定义的正则表达式进行匹配
    23             ret = re.match(regular, post_value)
    24             print key,ret,post_value
    25   
    26   
    27 class MainHandler(tornado.web.RequestHandler):
    28     def get(self):
    29         self.render('index.html')
    30     def post(self, *args, **kwargs):
    31         obj = MainForm()
    32         result = obj.check_valid(self)
    33         self.write('ok')
    34   
    35   
    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 ], **settings)
    48   
    49   
    50 if __name__ == "__main__":
    51     application.listen(8888)
    52     tornado.ioloop.IOLoop.instance().start()
    53 
    54 Python
    View Code

    由于验证规则可以代码重用,所以可以如此定义:

      1 #!/usr/bin/env python
      2 # -*- coding:utf-8 -*-
      3 
      4 import tornado.ioloop
      5 import tornado.web
      6 import re
      7 
      8 
      9 class Field(object):
     10 
     11     def __init__(self, error_msg_dict, required):
     12         self.id_valid = False
     13         self.value = None
     14         self.error = None
     15         self.name = None
     16         self.error_msg = error_msg_dict
     17         self.required = required
     18 
     19     def match(self, name, value):
     20         self.name = name
     21 
     22         if not self.required:
     23             self.id_valid = True
     24             self.value = value
     25         else:
     26             if not value:
     27                 if self.error_msg.get('required', None):
     28                     self.error = self.error_msg['required']
     29                 else:
     30                     self.error = "%s is required" % name
     31             else:
     32                 ret = re.match(self.REGULAR, value)
     33                 if ret:
     34                     self.id_valid = True
     35                     self.value = ret.group()
     36                 else:
     37                     if self.error_msg.get('valid', None):
     38                         self.error = self.error_msg['valid']
     39                     else:
     40                         self.error = "%s is invalid" % name
     41 
     42 
     43 class IPField(Field):
     44     REGULAR = "^(25[0-5]|2[0-4]d|[0-1]?d?d)(.(25[0-5]|2[0-4]d|[0-1]?d?d)){3}$"
     45 
     46     def __init__(self, error_msg_dict=None, required=True):
     47 
     48         error_msg = {}  # {'required': 'IP不能为空', 'valid': 'IP格式错误'}
     49         if error_msg_dict:
     50             error_msg.update(error_msg_dict)
     51 
     52         super(IPField, self).__init__(error_msg_dict=error_msg, required=required)
     53 
     54 
     55 class IntegerField(Field):
     56     REGULAR = "^d+$"
     57 
     58     def __init__(self, error_msg_dict=None, required=True):
     59         error_msg = {'required': '数字不能为空', 'valid': '数字格式错误'}
     60         if error_msg_dict:
     61             error_msg.update(error_msg_dict)
     62 
     63         super(IntegerField, self).__init__(error_msg_dict=error_msg, required=required)
     64 
     65 
     66 class CheckBoxField(Field):
     67 
     68     def __init__(self, error_msg_dict=None, required=True):
     69         error_msg = {}  # {'required': 'IP不能为空', 'valid': 'IP格式错误'}
     70         if error_msg_dict:
     71             error_msg.update(error_msg_dict)
     72 
     73         super(CheckBoxField, self).__init__(error_msg_dict=error_msg, required=required)
     74 
     75     def match(self, name, value):
     76         self.name = name
     77 
     78         if not self.required:
     79             self.id_valid = True
     80             self.value = value
     81         else:
     82             if not value:
     83                 if self.error_msg.get('required', None):
     84                     self.error = self.error_msg['required']
     85                 else:
     86                     self.error = "%s is required" % name
     87             else:
     88                 if isinstance(name, list):
     89                     self.id_valid = True
     90                     self.value = value
     91                 else:
     92                     if self.error_msg.get('valid', None):
     93                         self.error = self.error_msg['valid']
     94                     else:
     95                         self.error = "%s is invalid" % name
     96 
     97 
     98 class FileField(Field):
     99     REGULAR = "^(w+.pdf)|(w+.mp3)|(w+.py)$"
    100 
    101     def __init__(self, error_msg_dict=None, required=True):
    102         error_msg = {}  # {'required': '数字不能为空', 'valid': '数字格式错误'}
    103         if error_msg_dict:
    104             error_msg.update(error_msg_dict)
    105 
    106         super(FileField, self).__init__(error_msg_dict=error_msg, required=required)
    107 
    108     def match(self, name, value):
    109         self.name = name
    110         self.value = []
    111         if not self.required:
    112             self.id_valid = True
    113             self.value = value
    114         else:
    115             if not value:
    116                 if self.error_msg.get('required', None):
    117                     self.error = self.error_msg['required']
    118                 else:
    119                     self.error = "%s is required" % name
    120             else:
    121                 m = re.compile(self.REGULAR)
    122                 if isinstance(value, list):
    123                     for file_name in value:
    124                         r = m.match(file_name)
    125                         if r:
    126                             self.value.append(r.group())
    127                             self.id_valid = True
    128                         else:
    129                             self.id_valid = False
    130                             if self.error_msg.get('valid', None):
    131                                 self.error = self.error_msg['valid']
    132                             else:
    133                                 self.error = "%s is invalid" % name
    134                             break
    135                 else:
    136                     if self.error_msg.get('valid', None):
    137                         self.error = self.error_msg['valid']
    138                     else:
    139                         self.error = "%s is invalid" % name
    140 
    141     def save(self, request, upload_path=""):
    142 
    143         file_metas = request.files[self.name]
    144         for meta in file_metas:
    145             file_name = meta['filename']
    146             with open(file_name,'wb') as up:
    147                 up.write(meta['body'])
    148 
    149 
    150 class Form(object):
    151 
    152     def __init__(self):
    153         self.value_dict = {}
    154         self.error_dict = {}
    155         self.valid_status = True
    156 
    157     def validate(self, request, depth=10, pre_key=""):
    158 
    159         self.initialize()
    160         self.__valid(self, request, depth, pre_key)
    161 
    162     def initialize(self):
    163         pass
    164 
    165     def __valid(self, form_obj, request, depth, pre_key):
    166         """
    167         验证用户表单请求的数据
    168         :param form_obj: Form对象(Form派生类的对象)
    169         :param request: Http请求上下文(用于从请求中获取用户提交的值)
    170         :param depth: 对Form内容的深度的支持
    171         :param pre_key: Html中name属性值的前缀(多层Form时,内部递归时设置,无需理会)
    172         :return: 是否验证通过,True:验证成功;False:验证失败
    173         """
    174 
    175         depth -= 1
    176         if depth < 0:
    177             return None
    178         form_field_dict = form_obj.__dict__
    179         for key, field_obj in form_field_dict.items():
    180             print key,field_obj
    181             if isinstance(field_obj, Form) or isinstance(field_obj, Field):
    182                 if isinstance(field_obj, Form):
    183                     # 获取以key开头的所有的值,以参数的形式传至
    184                     self.__valid(field_obj, request, depth, key)
    185                     continue
    186                 if pre_key:
    187                     key = "%s.%s" % (pre_key, key)
    188 
    189                 if isinstance(field_obj, CheckBoxField):
    190                     post_value = request.get_arguments(key, None)
    191                 elif isinstance(field_obj, FileField):
    192                     post_value = []
    193                     file_list = request.request.files.get(key, None)
    194                     for file_item in file_list:
    195                         post_value.append(file_item['filename'])
    196                 else:
    197                     post_value = request.get_argument(key, None)
    198 
    199                 print post_value
    200                 # 让提交的数据 和 定义的正则表达式进行匹配
    201                 field_obj.match(key, post_value)
    202                 if field_obj.id_valid:
    203                     self.value_dict[key] = field_obj.value
    204                 else:
    205                     self.error_dict[key] = field_obj.error
    206                     self.valid_status = False
    207 
    208 
    209 class ListForm(object):
    210     def __init__(self, form_type):
    211         self.form_type = form_type
    212         self.valid_status = True
    213         self.value_dict = {}
    214         self.error_dict = {}
    215 
    216     def validate(self, request):
    217         name_list = request.request.arguments.keys() + request.request.files.keys()
    218         index = 0
    219         flag = False
    220         while True:
    221             pre_key = "[%d]" % index
    222             for name in name_list:
    223                 if name.startswith(pre_key):
    224                     flag = True
    225                     break
    226             if flag:
    227                 form_obj = self.form_type()
    228                 form_obj.validate(request, depth=10, pre_key="[%d]" % index)
    229                 if form_obj.valid_status:
    230                     self.value_dict[index] = form_obj.value_dict
    231                 else:
    232                     self.error_dict[index] = form_obj.error_dict
    233                     self.valid_status = False
    234             else:
    235                 break
    236 
    237             index += 1
    238             flag = False
    239 
    240 
    241 class MainForm(Form):
    242 
    243     def __init__(self):
    244         # self.ip = IPField(required=True)
    245         # self.port = IntegerField(required=True)
    246         # self.new_ip = IPField(required=True)
    247         # self.second = SecondForm()
    248         self.fff = FileField(required=True)
    249         super(MainForm, self).__init__()
    250 
    251 #
    252 # class SecondForm(Form):
    253 #
    254 #     def __init__(self):
    255 #         self.ip = IPField(required=True)
    256 #         self.new_ip = IPField(required=True)
    257 #
    258 #         super(SecondForm, self).__init__()
    259 
    260 
    261 class MainHandler(tornado.web.RequestHandler):
    262     def get(self):
    263         self.render('index.html')
    264     def post(self, *args, **kwargs):
    265         # for i in  dir(self.request):
    266         #     print i
    267         # print self.request.arguments
    268         # print self.request.files
    269         # print self.request.query
    270         # name_list = self.request.arguments.keys() + self.request.files.keys()
    271         # print name_list
    272 
    273         # list_form = ListForm(MainForm)
    274         # list_form.validate(self)
    275         #
    276         # print list_form.valid_status
    277         # print list_form.value_dict
    278         # print list_form.error_dict
    279 
    280         # obj = MainForm()
    281         # obj.validate(self)
    282         #
    283         # print "验证结果:", obj.valid_status
    284         # print "符合验证结果:", obj.value_dict
    285         # print "错误信息:"
    286         # for key, item in obj.error_dict.items():
    287         #     print key,item
    288         # print self.get_arguments('favor'),type(self.get_arguments('favor'))
    289         # print self.get_argument('favor'),type(self.get_argument('favor'))
    290         # print type(self.get_argument('fff')),self.get_argument('fff')
    291         # print self.request.files
    292         # obj = MainForm()
    293         # obj.validate(self)
    294         # print obj.valid_status
    295         # print obj.value_dict
    296         # print obj.error_dict
    297         # print self.request,type(self.request)
    298         # obj.fff.save(self.request)
    299         # from tornado.httputil import HTTPServerRequest
    300         # name_list = self.request.arguments.keys() + self.request.files.keys()
    301         # print name_list
    302         # print self.request.files,type(self.request.files)
    303         # print len(self.request.files.get('fff'))
    304         
    305         # obj = MainForm()
    306         # obj.validate(self)
    307         # print obj.valid_status
    308         # print obj.value_dict
    309         # print obj.error_dict
    310         # obj.fff.save(self.request)
    311         self.write('ok')
    312 
    313 
    314 
    315 settings = {
    316     'template_path': 'template',
    317     'static_path': 'static',
    318     'static_url_prefix': '/static/',
    319     'cookie_secret': 'aiuasdhflashjdfoiuashdfiuh',
    320     'login_url': '/login'
    321 }
    322 
    323 application = tornado.web.Application([
    324     (r"/index", MainHandler),
    325 ], **settings)
    326 
    327 
    328 if __name__ == "__main__":
    329     application.listen(8888)
    330     tornado.ioloop.IOLoop.instance().start()
    View Code
  • 相关阅读:
    【Azure 应用服务】App Service For Windows 环境中部署Python站点后,如何继续访问静态资源文件呢(Serving Static Files)?
    Go语言基础之函数参数
    Go语言基础之函数定义
    Go语言基础之map
    Go语言基础之切片
    Go语言基础之数组
    Go中nil的定义
    复合数据类型介绍
    Go语言基础之流程控制goto
    Go语言基础之流程控制switch分支
  • 原文地址:https://www.cnblogs.com/wangyongsong/p/7522135.html
Copyright © 2020-2023  润新知