Tornado

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

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

pip install tornado

源码安装
    https://pypi.python.org/packages/source/t/tornado/tornado-4.3.tar.gz
 
 
一、快速上手
 

第一步:执行脚本,监听xxxx端口

第二步:浏览器客户端访问 /index  -->  http://127.0.0.1:xxxx/index

第三步:服务器接受请求,并交由对应的类处理该请求

第四步:类接受到请求之后,根据请求方式(post / get / delete ...)的不同调用并执行相应的方法

第五步:方法返回值的字符串内容发送浏览器

 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     ("/index", MainHandler),
14 ])
15   
16   
17 if __name__ == "__main__":
18     application.listen(xxxx端口号)
19     tornado.ioloop.IOLoop.instance().start()
View Code
 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 
 4 import tornado.ioloop
 5 import tornado.web
 6 from tornado import httpclient
 7 from tornado.web import asynchronous
 8 from tornado import gen
 9 
10 import uimodules as md
11 import uimethods as mt
12 
13 class MainHandler(tornado.web.RequestHandler):
14         @asynchronous
15         @gen.coroutine
16         def get(self):
17             print 'start get '
18             http = httpclient.AsyncHTTPClient()
19             http.fetch("http://127.0.0.1:8008/post/", self.callback)
20             self.write('end')
21 
22         def callback(self, response):
23             print response.body
24 
25 settings = {
26     'template_path': 'template',
27     'static_path': 'static',
28     'static_url_prefix': '/static/',
29     'ui_methods': mt,
30     'ui_modules': md,
31 }
32 
33 application = tornado.web.Application([
34     (r"/index", MainHandler),
35 ], **settings)
36 
37 
38 if __name__ == "__main__":
39     application.listen(8009)
40     tornado.ioloop.IOLoop.instance().start()
异步非阻塞例子

二、路由系统

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

 1 #!/usr/bin/env python
 2  2 # -*- coding:utf-8 -*-
 3  3   
 4  4 import tornado.ioloop
 5  5 import tornado.web
 6  6   
 7  7   
 8  8 class MainHandler(tornado.web.RequestHandler):
 9  9     def get(self):
10 10         self.write("Hello, world")
11 11   
12 12 class StoryHandler(tornado.web.RequestHandler):
13 13     def get(self, story_id):
14 14         self.write("You requested the story " + story_id)
15 15   
16 16 class BuyHandler(tornado.web.RequestHandler):
17 17     def get(self):
18 18         self.write("buy.xxx.com/index")
19 19   
20 20 application = tornado.web.Application([
21 21     ("/index", MainHandler),
22 22     ("/story/([0-9]+)", StoryHandler),
23 23 ])
24 24   
25 25 application.add_handlers('buy.xxx.com$', [
26 26     ('/index',BuyHandler),
27 27 ])
28 28   
29 29 if __name__ == "__main__":
30 30     application.listen(80)
31 31     tornado.ioloop.IOLoop.instance().start()
View Code

三、模板

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

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

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

 1 <!DOCTYPE html>
 2 <html>
 3 <head>
 4     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
 5     <link href="{{static_url("css/common.css")}}" rel="stylesheet" />
 6     {% block CSS %}{% end %}
 7 </head>
 8 <body>
 9 
10     <div class="pg-header">
11 
12     </div>
13     
14     {% block RenderBody %}{% end %}
15    
16     <script src="{{static_url("js/jquery-3.4.1.js")}}"></script>
17     
18     {% block JavaScript %}{% end %}
19 </body>
20 </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
 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 }
15   
16 application = tornado.web.Application([
17     (r"/index", MainHandler),
18 ], **settings)
19   
20   
21 if __name__ == "__main__":
22     application.listen(80)
23     tornado.ioloop.IOLoop.instance().start()
xxx.py

在模板中默认提供了一些函数、字段、类以供模板使用:

  • escapetornado.escape.xhtml_escape 的別名
  • xhtml_escapetornado.escape.xhtml_escape 的別名
  • url_escapetornado.escape.url_escape 的別名
  • json_encodetornado.escape.json_encode 的別名
  • squeezetornado.escape.squeeze 的別名
  • linkifytornado.escape.linkify 的別名
  • datetime: Python 的 datetime 模组
  • handler: 当前的 RequestHandler 对象
  • requesthandler.request 的別名
  • current_userhandler.current_user 的別名
  • localehandler.locale 的別名
  • _handler.locale.translate 的別名
  • static_url: for handler.static_url 的別名
  • xsrf_form_htmlhandler.xsrf_form_html 的別名

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

1、定义

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>xxx</h1>')
10         #return escape.xhtml_escape('<h1>xxx</h1>')
uimodules.py

2、注册

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

3、使用

 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>
index.html

四、实用功能

1、静态文件

对于静态文件,可以配置静态文件的目录和前段使用时的前缀,并且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()
main.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()
静态文件缓存的实现

2、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 使用时,本质上就是去获取本地的cookie,携带cookie再来发送请求

3、cookie

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

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!")
基本使用

签名

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())
内部算法
 1 def create_signed_value(secret, name, value, version=None, clock=None,
 2                         key_version=None):
 3     if version is None:
 4         version = DEFAULT_SIGNED_VALUE_VERSION
 5     if clock is None:
 6         clock = time.time
 7 
 8     timestamp = utf8(str(int(clock())))
 9     value = base64.b64encode(utf8(value))
10     if version == 1:
11         signature = _create_signature_v1(secret, name, value, timestamp)
12         value = b"|".join([value, timestamp, signature])
13         return value
14     elif version == 2:
15         # The v2 format consists of a version number and a series of
16         # length-prefixed fields "%d:%s", the last of which is a
17         # signature, all separated by pipes.  All numbers are in
18         # decimal format with no leading zeros.  The signature is an
19         # HMAC-SHA256 of the whole string up to that point, including
20         # the final pipe.
21         #
22         # The fields are:
23         # - format version (i.e. 2; no length prefix)
24         # - key version (integer, default is 0)
25         # - timestamp (integer seconds since epoch)
26         # - name (not encoded; assumed to be ~alphanumeric)
27         # - value (base64-encoded)
28         # - signature (hex-encoded; no length prefix)
29         def format_field(s):
30             return utf8("%d:" % len(s)) + utf8(s)
31         to_sign = b"|".join([
32             b"2",
33             format_field(str(key_version or 0)),
34             format_field(timestamp),
35             format_field(name),
36             format_field(value),
37             b''])
38 
39         if isinstance(secret, dict):
40             assert key_version is not None, 'Key version must be set when sign key dict is used'
41             assert version >= 2, 'Version must be at least 2 for key version support'
42             secret = secret[key_version]
43 
44         signature = _create_signature_v2(secret, to_sign)
45         return to_sign + signature
46     else:
47         raise ValueError("Unsupported version %d" % version)
加密
 1 def _decode_signed_value_v1(secret, name, value, max_age_days, clock):
 2     parts = utf8(value).split(b"|")
 3     if len(parts) != 3:
 4         return None
 5     signature = _create_signature_v1(secret, name, parts[0], parts[1])
 6     if not _time_independent_equals(parts[2], signature):
 7         gen_log.warning("Invalid cookie signature %r", value)
 8         return None
 9     timestamp = int(parts[1])
10     if timestamp < clock() - max_age_days * 86400:
11         gen_log.warning("Expired cookie %r", value)
12         return None
13     if timestamp > clock() + 31 * 86400:
14         # _cookie_signature does not hash a delimiter between the
15         # parts of the cookie, so an attacker could transfer trailing
16         # digits from the payload to the timestamp without altering the
17         # signature.  For backwards compatibility, sanity-check timestamp
18         # here instead of modifying _cookie_signature.
19         gen_log.warning("Cookie timestamp in future; possible tampering %r",
20                         value)
21         return None
22     if parts[1].startswith(b"0"):
23         gen_log.warning("Tampered cookie %r", value)
24         return None
25     try:
26         return base64.b64decode(parts[0])
27     except Exception:
28         return None
29 
30 
31 def _decode_fields_v2(value):
32     def _consume_field(s):
33         length, _, rest = s.partition(b':')
34         n = int(length)
35         field_value = rest[:n]
36         # In python 3, indexing bytes returns small integers; we must
37         # use a slice to get a byte string as in python 2.
38         if rest[n:n + 1] != b'|':
39             raise ValueError("malformed v2 signed value field")
40         rest = rest[n + 1:]
41         return field_value, rest
42 
43     rest = value[2:]  # remove version number
44     key_version, rest = _consume_field(rest)
45     timestamp, rest = _consume_field(rest)
46     name_field, rest = _consume_field(rest)
47     value_field, passed_sig = _consume_field(rest)
48     return int(key_version), timestamp, name_field, value_field, passed_sig
49 
50 
51 def _decode_signed_value_v2(secret, name, value, max_age_days, clock):
52     try:
53         key_version, timestamp, name_field, value_field, passed_sig = _decode_fields_v2(value)
54     except ValueError:
55         return None
56     signed_string = value[:-len(passed_sig)]
57 
58     if isinstance(secret, dict):
59         try:
60             secret = secret[key_version]
61         except KeyError:
62             return None
63 
64     expected_sig = _create_signature_v2(secret, signed_string)
65     if not _time_independent_equals(passed_sig, expected_sig):
66         return None
67     if name_field != utf8(name):
68         return None
69     timestamp = int(timestamp)
70     if timestamp < clock() - max_age_days * 86400:
71         # The signature has expired.
72         return None
73     try:
74         return base64.b64decode(value_field)
75     except Exception:
76         return None
77 
78 
79 def get_signature_key_version(value):
80     value = utf8(value)
81     version = _get_version(value)
82     if version < 2:
83         return None
84     try:
85         key_version, _, _, _, _ = _decode_fields_v2(value)
86     except ValueError:
87         return None
88 
89     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 == 'ssss' and password == '123':
29             self.set_secure_cookie('login_user', 'sss')
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用户验证
 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 == 'ssss' and password == '123':
32             self.set_secure_cookie('login_user', 'sss')
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()
tonado内部提供 cookie的用户验证

五、扩展功能

1、自定义Session

 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'] = 'ssss'
20 #del obj['k1']
储备
 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   
 9 session_container = {}
10   
11 create_session_id = lambda: sha1('%s%s' % (os.urandom(16), time.time())).hexdigest()
12   
13   
14 class Session(object):
15   
16     session_id = "__sessionId__"
17   
18     def __init__(self, request):
19         session_value = request.get_cookie(Session.session_id)
20         if not session_value:
21             self._id = create_session_id()
22         else:
23             self._id = session_value
24         request.set_cookie(Session.session_id, self._id)
25   
26     def __getitem__(self, key):
27         return session_container[self._id][key]
28   
29     def __setitem__(self, key, value):
30         if session_container.has_key(self._id):
31             session_container[self._id][key] = value
32         else:
33             session_container[self._id] = {key: value}
34   
35     def __delitem__(self, key):
36         del session_container[self._id][key]
37   
38   
39 class BaseHandler(tornado.web.RequestHandler):
40   
41     def initialize(self):
42         # my_session['k1']访问 __getitem__ 方法
43         self.my_session = Session(self)
44   
45   
46 class MainHandler(BaseHandler):
47   
48     def get(self):
49         print self.my_session['c_user']
50         print self.my_session['c_card']
51         self.write('index')
52   
53 class LoginHandler(BaseHandler):
54   
55     def get(self):
56         self.render('login.html', **{'status': ''})
57   
58     def post(self, *args, **kwargs):
59   
60         username = self.get_argument('name')
61         password = self.get_argument('pwd')
62         if username == 'sss' and password == '123':
63   
64             self.my_session['c_user'] = 'sss'
65             self.my_session['c_card'] = '12312312309823012'
66   
67             self.redirect('/index')
68         else:
69             self.render('login.html', **{'status': '用户名或密码错误'})
70   
71 settings = {
72     'template_path': 'template',
73     'static_path': 'static',
74     'static_url_prefix': '/static/',
75     'cookie_secret': 'aiuasdhflashjdfoiuashdfiuh',
76     'login_url': '/login'
77 }
78   
79 application = tornado.web.Application([
80     (r"/index", MainHandler),
81     (r"/login", LoginHandler),
82 ], **settings)
83   
84   
85 if __name__ == "__main__":
86     application.listen(8888)
87     tornado.ioloop.IOLoop.instance().start()
session实现机制
# 一致性哈希

#!/usr/bin/env python
#coding:utf-8

import sys
import math
from bisect import bisect


if sys.version_info >= (2, 5):
    import hashlib
    md5_constructor = hashlib.md5
else:
    import md5
    md5_constructor = md5.new


class HashRing(object):
    """一致性哈希"""
    
    def __init__(self,nodes):
        '''初始化
        nodes : 初始化的节点,其中包含节点已经节点对应的权重
                默认每一个节点有32个虚拟节点
                对于权重,通过多创建虚拟节点来实现
                如:nodes = [
                        {'host':'127.0.0.1:8000','weight':1},
                        {'host':'127.0.0.1:8001','weight':2},
                        {'host':'127.0.0.1:8002','weight':1},
                    ]
        '''
        
        self.ring = dict()
        self._sorted_keys = []

        self.total_weight = 0
        
        self.__generate_circle(nodes)
        
            
            
    def __generate_circle(self,nodes):
        for node_info in nodes:
            self.total_weight += node_info.get('weight',1)
            
        for node_info in nodes:
            weight = node_info.get('weight',1)
            node = node_info.get('host',None)
                
            virtual_node_count = math.floor((32*len(nodes)*weight) / self.total_weight)
            for i in xrange(0,int(virtual_node_count)):
                key = self.gen_key_thirty_two( '%s-%s' % (node, i) )
                if self._sorted_keys.__contains__(key):
                    raise Exception('该节点已经存在.')
                self.ring[key] = node
                self._sorted_keys.append(key)
            
    def add_node(self,node):
        ''' 新建节点
        node : 要添加的节点,格式为:{'host':'127.0.0.1:8002','weight':1},其中第一个元素表示节点,第二个元素表示该节点的权重。
        '''
        node = node.get('host',None)
        if not node:
                raise Exception('节点的地址不能为空.')
                
        weight = node.get('weight',1)
        
        self.total_weight += weight
        nodes_count = len(self._sorted_keys) + 1
        
        virtual_node_count = math.floor((32 * nodes_count * weight) / self.total_weight)
        for i in xrange(0,int(virtual_node_count)):
            key = self.gen_key_thirty_two( '%s-%s' % (node, i) )
            if self._sorted_keys.__contains__(key):
                raise Exception('该节点已经存在.')
            self.ring[key] = node
            self._sorted_keys.append(key)
        
    def remove_node(self,node):
        ''' 移除节点
        node : 要移除的节点 '127.0.0.1:8000'
        '''
        for key,value in self.ring.items():
            if value == node:
                del self.ring[key]
                self._sorted_keys.remove(key)
    
    def get_node(self,string_key):
        '''获取 string_key 所在的节点'''
        pos = self.get_node_pos(string_key)
        if pos is None:
            return None
        return self.ring[ self._sorted_keys[pos]].split(':')
    
    def get_node_pos(self,string_key):
        '''获取 string_key 所在的节点的索引'''
        if not self.ring:
            return None
            
        key = self.gen_key_thirty_two(string_key)
        nodes = self._sorted_keys
        pos = bisect(nodes, key)
        return pos
    
    def gen_key_thirty_two(self, key):
        
        m = md5_constructor()
        m.update(key)
        return long(m.hexdigest(), 16)
        
    def gen_key_sixteen(self,key):
        
        b_key = self.__hash_digest(key)
        return self.__hash_val(b_key, lambda x: x)

    def __hash_val(self, b_key, entry_fn):
        return (( b_key[entry_fn(3)] << 24)|(b_key[entry_fn(2)] << 16)|(b_key[entry_fn(1)] << 8)| b_key[entry_fn(0)] )

    def __hash_digest(self, key):
        m = md5_constructor()
        m.update(key)
        return map(ord, m.digest())


"""
nodes = [
    {'host':'127.0.0.1:8000','weight':1},
    {'host':'127.0.0.1:8001','weight':2},
    {'host':'127.0.0.1:8002','weight':1},
]

ring = HashRing(nodes)
result = ring.get_node('98708798709870987098709879087')
print result

"""











# session

from hashlib import sha1
import os, time


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


class Session(object):

    session_id = "__sessionId__"

    def __init__(self, request):
        session_value = request.get_cookie(Session.session_id)
        if not session_value:
            self._id = create_session_id()
        else:
            self._id = session_value
        request.set_cookie(Session.session_id, self._id)

    def __getitem__(self, key):
        # 根据 self._id ,在一致性哈西中找到其对应的服务器IP
        # 找到相对应的redis服务器,如: r = redis.StrictRedis(host='localhost', port=6379, db=0)
        # 使用python redis api 链接
        # 获取数据,即:
        # return self._redis.hget(self._id, name)

    def __setitem__(self, key, value):
        # 根据 self._id ,在一致性哈西中找到其对应的服务器IP
        # 使用python redis api 链接
        # 设置session
        # self._redis.hset(self._id, name, value)


    def __delitem__(self, key):
        # 根据 self._id 找到相对应的redis服务器
        # 使用python redis api 链接
        # 删除,即:
        return self._redis.hdel(self._id, name)
session框架

2、自定义模型版定

模型绑定有两个主要功能:

  • 自动生成html表单
  • 用户输入验证

在Django中提供了非常便捷的模型绑定功能,但是在Tornado中,一切需要自己动手!!!

<!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>
    <form action="/index" method="post">

        <p>hostname: <input type="text" name="host" /> </p>
        <p>ip: <input type="text" name="ip" /> </p>
        <p>port: <input type="text" name="port" /> </p>
        <p>phone: <input type="text" name="phone" /> </p>
        <input type="submit" />
    </form>
</body>
</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()
py
# 由于请求的验证时,需要考虑是否可以为空以及正则表达式的复用




#!/usr/bin/env python
# -*- coding:utf-8 -*-

import tornado.ioloop
import tornado.web
import re


class Field(object):

    def __init__(self, error_msg_dict, required):
        self.id_valid = False
        self.value = None
        self.error = None
        self.name = None
        self.error_msg = error_msg_dict
        self.required = required

    def match(self, name, value):
        self.name = name

        if not self.required:
            self.id_valid = True
            self.value = value
        else:
            if not value:
                if self.error_msg.get('required', None):
                    self.error = self.error_msg['required']
                else:
                    self.error = "%s is required" % name
            else:
                ret = re.match(self.REGULAR, value)
                if ret:
                    self.id_valid = True
                    self.value = ret.group()
                else:
                    if self.error_msg.get('valid', None):
                        self.error = self.error_msg['valid']
                    else:
                        self.error = "%s is invalid" % name


class IPField(Field):
    REGULAR = "^(25[0-5]|2[0-4]d|[0-1]?d?d)(.(25[0-5]|2[0-4]d|[0-1]?d?d)){3}$"

    def __init__(self, error_msg_dict=None, required=True):

        error_msg = {}  # {'required': 'IP不能为空', 'valid': 'IP格式错误'}
        if error_msg_dict:
            error_msg.update(error_msg_dict)

        super(IPField, self).__init__(error_msg_dict=error_msg, required=required)


class IntegerField(Field):
    REGULAR = "^d+$"

    def __init__(self, error_msg_dict=None, required=True):
        error_msg = {'required': '数字不能为空', 'valid': '数字格式错误'}
        if error_msg_dict:
            error_msg.update(error_msg_dict)

        super(IntegerField, self).__init__(error_msg_dict=error_msg, required=required)


class CheckBoxField(Field):

    def __init__(self, error_msg_dict=None, required=True):
        error_msg = {}  # {'required': 'IP不能为空', 'valid': 'IP格式错误'}
        if error_msg_dict:
            error_msg.update(error_msg_dict)

        super(CheckBoxField, self).__init__(error_msg_dict=error_msg, required=required)

    def match(self, name, value):
        self.name = name

        if not self.required:
            self.id_valid = True
            self.value = value
        else:
            if not value:
                if self.error_msg.get('required', None):
                    self.error = self.error_msg['required']
                else:
                    self.error = "%s is required" % name
            else:
                if isinstance(name, list):
                    self.id_valid = True
                    self.value = value
                else:
                    if self.error_msg.get('valid', None):
                        self.error = self.error_msg['valid']
                    else:
                        self.error = "%s is invalid" % name


class FileField(Field):
    REGULAR = "^(w+.pdf)|(w+.mp3)|(w+.py)$"

    def __init__(self, error_msg_dict=None, required=True):
        error_msg = {}  # {'required': '数字不能为空', 'valid': '数字格式错误'}
        if error_msg_dict:
            error_msg.update(error_msg_dict)

        super(FileField, self).__init__(error_msg_dict=error_msg, required=required)

    def match(self, name, value):
        self.name = name
        self.value = []
        if not self.required:
            self.id_valid = True
            self.value = value
        else:
            if not value:
                if self.error_msg.get('required', None):
                    self.error = self.error_msg['required']
                else:
                    self.error = "%s is required" % name
            else:
                m = re.compile(self.REGULAR)
                if isinstance(value, list):
                    for file_name in value:
                        r = m.match(file_name)
                        if r:
                            self.value.append(r.group())
                            self.id_valid = True
                        else:
                            self.id_valid = False
                            if self.error_msg.get('valid', None):
                                self.error = self.error_msg['valid']
                            else:
                                self.error = "%s is invalid" % name
                            break
                else:
                    if self.error_msg.get('valid', None):
                        self.error = self.error_msg['valid']
                    else:
                        self.error = "%s is invalid" % name

    def save(self, request, upload_path=""):

        file_metas = request.files[self.name]
        for meta in file_metas:
            file_name = meta['filename']
            with open(file_name,'wb') as up:
                up.write(meta['body'])


class Form(object):

    def __init__(self):
        self.value_dict = {}
        self.error_dict = {}
        self.valid_status = True

    def validate(self, request, depth=10, pre_key=""):

        self.initialize()
        self.__valid(self, request, depth, pre_key)

    def initialize(self):
        pass

    def __valid(self, form_obj, request, depth, pre_key):
        """
        验证用户表单请求的数据
        :param form_obj: Form对象(Form派生类的对象)
        :param request: Http请求上下文(用于从请求中获取用户提交的值)
        :param depth: 对Form内容的深度的支持
        :param pre_key: Html中name属性值的前缀(多层Form时,内部递归时设置,无需理会)
        :return: 是否验证通过,True:验证成功;False:验证失败
        """

        depth -= 1
        if depth < 0:
            return None
        form_field_dict = form_obj.__dict__
        for key, field_obj in form_field_dict.items():
            print key,field_obj
            if isinstance(field_obj, Form) or isinstance(field_obj, Field):
                if isinstance(field_obj, Form):
                    # 获取以key开头的所有的值,以参数的形式传至
                    self.__valid(field_obj, request, depth, key)
                    continue
                if pre_key:
                    key = "%s.%s" % (pre_key, key)

                if isinstance(field_obj, CheckBoxField):
                    post_value = request.get_arguments(key, None)
                elif isinstance(field_obj, FileField):
                    post_value = []
                    file_list = request.request.files.get(key, None)
                    for file_item in file_list:
                        post_value.append(file_item['filename'])
                else:
                    post_value = request.get_argument(key, None)

                print post_value
                # 让提交的数据 和 定义的正则表达式进行匹配
                field_obj.match(key, post_value)
                if field_obj.id_valid:
                    self.value_dict[key] = field_obj.value
                else:
                    self.error_dict[key] = field_obj.error
                    self.valid_status = False


class ListForm(object):
    def __init__(self, form_type):
        self.form_type = form_type
        self.valid_status = True
        self.value_dict = {}
        self.error_dict = {}

    def validate(self, request):
        name_list = request.request.arguments.keys() + request.request.files.keys()
        index = 0
        flag = False
        while True:
            pre_key = "[%d]" % index
            for name in name_list:
                if name.startswith(pre_key):
                    flag = True
                    break
            if flag:
                form_obj = self.form_type()
                form_obj.validate(request, depth=10, pre_key="[%d]" % index)
                if form_obj.valid_status:
                    self.value_dict[index] = form_obj.value_dict
                else:
                    self.error_dict[index] = form_obj.error_dict
                    self.valid_status = False
            else:
                break

            index += 1
            flag = False


class MainForm(Form):

    def __init__(self):
        # self.ip = IPField(required=True)
        # self.port = IntegerField(required=True)
        # self.new_ip = IPField(required=True)
        # self.second = SecondForm()
        self.fff = FileField(required=True)
        super(MainForm, self).__init__()

#
# class SecondForm(Form):
#
#     def __init__(self):
#         self.ip = IPField(required=True)
#         self.new_ip = IPField(required=True)
#
#         super(SecondForm, self).__init__()


class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.render('index.html')
    def post(self, *args, **kwargs):
        # for i in  dir(self.request):
        #     print i
        # print self.request.arguments
        # print self.request.files
        # print self.request.query
        # name_list = self.request.arguments.keys() + self.request.files.keys()
        # print name_list

        # list_form = ListForm(MainForm)
        # list_form.validate(self)
        #
        # print list_form.valid_status
        # print list_form.value_dict
        # print list_form.error_dict

        # obj = MainForm()
        # obj.validate(self)
        #
        # print "验证结果:", obj.valid_status
        # print "符合验证结果:", obj.value_dict
        # print "错误信息:"
        # for key, item in obj.error_dict.items():
        #     print key,item
        # print self.get_arguments('favor'),type(self.get_arguments('favor'))
        # print self.get_argument('favor'),type(self.get_argument('favor'))
        # print type(self.get_argument('fff')),self.get_argument('fff')
        # print self.request.files
        # obj = MainForm()
        # obj.validate(self)
        # print obj.valid_status
        # print obj.value_dict
        # print obj.error_dict
        # print self.request,type(self.request)
        # obj.fff.save(self.request)
        # from tornado.httputil import HTTPServerRequest
        # name_list = self.request.arguments.keys() + self.request.files.keys()
        # print name_list
        # print self.request.files,type(self.request.files)
        # print len(self.request.files.get('fff'))
        
        # obj = MainForm()
        # obj.validate(self)
        # print obj.valid_status
        # print obj.value_dict
        # print obj.error_dict
        # obj.fff.save(self.request)
        self.write('ok')



settings = {
    'template_path': 'template',
    'static_path': 'static',
    'static_url_prefix': '/static/',
    'cookie_secret': 'aiuasdhflashjdfoiuashdfiuh',
    'login_url': '/login'
}

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


if __name__ == "__main__":
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()
form验证框架
原文地址:https://www.cnblogs.com/JcrLive/p/12404972.html