概述

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

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

下载安装:

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

框架使用

一、快速上手

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

执行过程:

  • 第一步:执行脚本,监听 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. if __name__ == "__main__":
  40. application.listen(8009)
  41. tornado.ioloop.IOLoop.instance().start()
  42.  
  43. 异步非阻塞示例

二、路由系统

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

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

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

三、模板引擎

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

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

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

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

1、基本使用

  1. #!/usr/bin/env python
  2. # -*- coding:utf-8 -*-
  3.  
  4. import tornado.ioloop
  5. import tornado.web
  6.  
  7. class MainHandler(tornado.web.RequestHandler):
  8. def get(self):
  9. self.render("index.html", list_info = [11,22,33])
  10.  
  11. application = tornado.web.Application([
  12. (r"/index", MainHandler),
  13. ])
  14.  
  15. if __name__ == "__main__":
  16. application.listen(8888)
  17. tornado.ioloop.IOLoop.instance().start()
  18.  
  19. 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>
  22.  
  23. 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 的別名
  17.  
  18. 其他方法

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>
  22.  
  23. 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 %}
  20.  
  21. index.html

3、导入

  1. <div>
  2. <ul>
  3. <li>1024</li>
  4. <li>42区</li>
  5. </ul>
  6. </div>
  7.  
  8. 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>
  18.  
  19. index.html

4、自定义UIMethod以UIModule

a. 定义

  1. # uimethods.py
  2.  
  3. def tab(self):
  4. return 'UIMethod'
  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>')
  11.  
  12. 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. if __name__ == "__main__":
  29. application.listen(8009)
  30. tornado.ioloop.IOLoop.instance().start()

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>

四、静态文件

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

  1. #!/usr/bin/env python
  2. # -*- coding:utf-8 -*-
  3.  
  4. import tornado.ioloop
  5. import tornado.web
  6.  
  7. class MainHandler(tornado.web.RequestHandler):
  8. def get(self):
  9. self.render('home/index.html')
  10.  
  11. settings = {
  12. 'template_path': 'template',
  13. 'static_path': 'static',
  14. 'static_url_prefix': '/static/',
  15. }
  16.  
  17. application = tornado.web.Application([
  18. (r"/index", MainHandler),
  19. ], **settings)
  20.  
  21. if __name__ == "__main__":
  22. application.listen(80)
  23. tornado.ioloop.IOLoop.instance().start()
  24.  
  25. 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>
  12.  
  13. 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()

五、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!")

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=")
  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. def _decode_fields_v2(value):
  92. def _consume_field(s):
  93. length, _, rest = s.partition(b':')
  94. n = int(length)
  95. field_value = rest[:n]
  96. # In python 3, indexing bytes returns small integers; we must
  97. # use a slice to get a byte string as in python 2.
  98. if rest[n:n + 1] != b'|':
  99. raise ValueError("malformed v2 signed value field")
  100. rest = rest[n + 1:]
  101. return field_value, rest
  102.  
  103. rest = value[2:] # remove version number
  104. key_version, rest = _consume_field(rest)
  105. timestamp, rest = _consume_field(rest)
  106. name_field, rest = _consume_field(rest)
  107. value_field, passed_sig = _consume_field(rest)
  108. return int(key_version), timestamp, name_field, value_field, passed_sig
  109.  
  110. def _decode_signed_value_v2(secret, name, value, max_age_days, clock):
  111. try:
  112. key_version, timestamp, name_field, value_field, passed_sig = _decode_fields_v2(value)
  113. except ValueError:
  114. return None
  115. signed_string = value[:-len(passed_sig)]
  116.  
  117. if isinstance(secret, dict):
  118. try:
  119. secret = secret[key_version]
  120. except KeyError:
  121. return None
  122.  
  123. expected_sig = _create_signature_v2(secret, signed_string)
  124. if not _time_independent_equals(passed_sig, expected_sig):
  125. return None
  126. if name_field != utf8(name):
  127. return None
  128. timestamp = int(timestamp)
  129. if timestamp < clock() - max_age_days * 86400:
  130. # The signature has expired.
  131. return None
  132. try:
  133. return base64.b64decode(value_field)
  134. except Exception:
  135. return None
  136.  
  137. def get_signature_key_version(value):
  138. value = utf8(value)
  139. version = _get_version(value)
  140. if version < 2:
  141. return None
  142. try:
  143. key_version, _, _, _, _ = _decode_fields_v2(value)
  144. except ValueError:
  145. return None
  146.  
  147. return key_version
  148.  
  149. 内部算法

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

3、JavaScript操作Cookie

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

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

对于参数:

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

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

六、CSRF

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

  1. 配置
  2. settings = {
  3. "xsrf_cookies": True,
  4. }
  5. application = tornado.web.Application([
  6. (r"/", MainHandler),
  7. (r"/login", LoginHandler),
  8. ], **settings)
  9.  
  10. 配置
  11.  
  12. 使用 - 普通表单
  13. <form action="/new_message" method="post">
  14. {{ xsrf_form_html() }}
  15. <input type="text" name="message"/>
  16. <input type="submit" value="Post"/>
  17. </form>
  18.  
  19. 使用 - AJAX
  20. function getCookie(name) {
  21. var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
  22. return r ? r[1] : undefined;
  23. }
  24.  
  25. jQuery.postJSON = function(url, args, callback) {
  26. args._xsrf = getCookie("_xsrf");
  27. $.ajax({url: url, data: $.param(args), dataType: "text", type: "POST",
  28. success: function(response) {
  29. callback(eval("(" + response + ")"));
  30. }});
  31. };
  32.  
  33. 使用 - 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>
  14.  
  15. HTML
  1. #!/usr/bin/env python
  2. # -*- coding:utf-8 -*-
  3.  
  4. import tornado.ioloop
  5. import tornado.web
  6.  
  7. class MainHandler(tornado.web.RequestHandler):
  8. def get(self):
  9.  
  10. self.render('index.html')
  11.  
  12. def post(self, *args, **kwargs):
  13. file_metas = self.request.files["fff"]
  14. # print(file_metas)
  15. for meta in file_metas:
  16. file_name = meta['filename']
  17. with open(file_name,'wb') as up:
  18. up.write(meta['body'])
  19.  
  20. settings = {
  21. 'template_path': 'template',
  22. }
  23.  
  24. application = tornado.web.Application([
  25. (r"/index", MainHandler),
  26. ], **settings)
  27.  
  28. if __name__ == "__main__":
  29. application.listen(8000)
  30. tornado.ioloop.IOLoop.instance().start()
  31.  
  32. 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>
  25.  
  26. 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>
  31.  
  32. 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>
  31.  
  32. HTML - iframe
  1. #!/usr/bin/env python
  2. # -*- coding:utf-8 -*-
  3.  
  4. import tornado.ioloop
  5. import tornado.web
  6.  
  7. class MainHandler(tornado.web.RequestHandler):
  8. def get(self):
  9.  
  10. self.render('index.html')
  11.  
  12. def post(self, *args, **kwargs):
  13. file_metas = self.request.files["fff"]
  14. # print(file_metas)
  15. for meta in file_metas:
  16. file_name = meta['filename']
  17. with open(file_name,'wb') as up:
  18. up.write(meta['body'])
  19.  
  20. settings = {
  21. 'template_path': 'template',
  22. }
  23.  
  24. application = tornado.web.Application([
  25. (r"/index", MainHandler),
  26. ], **settings)
  27.  
  28. if __name__ == "__main__":
  29. application.listen(8000)
  30. tornado.ioloop.IOLoop.instance().start()
  31.  
  32. 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. <form id="theuploadform">
  36. <input id="userfile" name="userfile" size="50" type="file" />
  37. <input id="formsubmit" type="submit" value="Send File" />
  38. </form>
  39.  
  40. <div id="textarea">
  41. </div>
  42.  
  43. 扩展:基于iframe实现Ajax上传示例
  1. $('#upload_iframe').load(function(){
  2. var iframeContents = this.contentWindow.document.body.innerText;
  3. iframeContents = JSON.parse(iframeContents);
  4.  
  5. })
  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. }
  51.  
  52. 其他

八、验证码

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

安装图像处理模块:

1
pip3 install pillow

示例截图:

验证码Demo源码下载:猛击这里

九、异步非阻塞

1、基本使用

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

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

当发送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)
  9.  
  10. 同步阻塞
  11.  
  12. class AsyncHandler(tornado.web.RequestHandler):
  13. @gen.coroutine
  14. def get(self):
  15. future = Future()
  16. tornado.ioloop.IOLoop.current().add_timeout(time.time() + 5, self.doing)
  17. yield future
  18.  
  19. def doing(self, *args, **kwargs):
  20. self.write('async')
  21. self.finish()
  22.  
  23. 异步非阻塞

3、httpclient类库

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

1
2
3
4
5
6
7
8
9
10
11
12
13
class AsyncHandler(tornado.web.RequestHandler):
    @gen.coroutine
    def get(self):
        from tornado import httpclient
 
        http = httpclient.AsyncHTTPClient()
        yield http.fetch("http://www.google.com"self.endding)
 
 
    def endding(self, response):
        print(len(response.body))
        self.write('ok')
        self.finish()

自定义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. obj = Foo()
  16. result = obj['k1']
  17. #obj['k2'] = 'wupeiqi'
  18. #del obj['k1']

2、Tornado扩展

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

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

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

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

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

Python全栈开发:web框架之tornado的更多相关文章

  1. Python全栈开发-web框架之django

    一:web框架 什么是web框架? Web应用框架(Web application framework)是一种开发框架,用来支持动态网站.网络应用程序及网络服务的开发.这种框架有助于减轻网页开发时共通 ...

  2. python全栈开发目录

    python全栈开发目录 Linux系列 python基础 前端~HTML~CSS~JavaScript~JQuery~Vue web框架们~Django~Flask~Tornado 数据库们~MyS ...

  3. Python 全栈开发【第0篇】:目录

    Python 全栈开发[第0篇]:目录   第一阶段:Python 开发入门 Python 全栈开发[第一篇]:计算机原理&Linux系统入门 Python 全栈开发[第二篇]:Python基 ...

  4. 学习笔记之Python全栈开发/人工智能公开课_腾讯课堂

    Python全栈开发/人工智能公开课_腾讯课堂 https://ke.qq.com/course/190378 https://github.com/haoran119/ke.qq.com.pytho ...

  5. Python全栈开发相关课程

    Python全栈开发 Python入门 Python安装 Pycharm安装.激活.使用 Python基础 Python语法 Python数据类型 Python进阶 面向对象 网络编程 并发编程 数据 ...

  6. Win10构建Python全栈开发环境With WSL

    目录 Win10构建Python全栈开发环境With WSL 启动WSL 总结 对<Dev on Windows with WSL>的补充 Win10构建Python全栈开发环境With ...

  7. python全栈开发中级班全程笔记(第二模块、第四章)(常用模块导入)

    python全栈开发笔记第二模块 第四章 :常用模块(第二部分)     一.os 模块的 详解 1.os.getcwd()    :得到当前工作目录,即当前python解释器所在目录路径 impor ...

  8. Python全栈开发记录_第一篇(循环练习及杂碎的知识点)

    Python全栈开发记录只为记录全栈开发学习过程中一些难和重要的知识点,还有问题及课后题目,以供自己和他人共同查看.(该篇代码行数大约:300行) 知识点1:优先级:not>and 短路原则:a ...

  9. python 全栈开发,Day99(作业讲解,DRF版本,DRF分页,DRF序列化进阶)

    昨日内容回顾 1. 为什么要做前后端分离? - 前后端交给不同的人来编写,职责划分明确. - API (IOS,安卓,PC,微信小程序...) - vue.js等框架编写前端时,会比之前写jQuery ...

  10. Python全栈开发【面向对象进阶】

    Python全栈开发[面向对象进阶] 本节内容: isinstance(obj,cls)和issubclass(sub,super) 反射 __setattr__,__delattr__,__geta ...

随机推荐

  1. iOS 获取音频或是视频的时间

    AVURLAsset* audioAsset =[AVURLAssetURLAssetWithURL:audioFileURL options:nil]; CMTime audioDuration = ...

  2. (转)简述负载均衡&CDN技术

    转:http://www.cnblogs.com/mokafamily/p/4402366.html#commentform 曾经见到知乎上有人问“为什么像facebook这类的网站需要上千个工程师维 ...

  3. ionic:目录

    ylbtech-ionic:目录 1.返回顶部 1. http://www.runoob.com/ionic/ionic-tutorial.html 2. 2.返回顶部   3.返回顶部   4.返回 ...

  4. JSP/Servlet笔记

    一.Servlet简介 Servlet程序可以运行于任何服务器,如web.email.FTP等,所有servlet程序必须实现javax.servlet接口.GenericServlet是实现了jav ...

  5. Centos 6 & Centos 7安装rabbitmq3.6.15(单节点)

    系统准备 安装 erlang 语言环境 安装rabbitmq 配置网页插件 配置访问账号密码和权限 系统准备 centos6.5 与 centos7 都可以 ###安装依赖文件 yum -y inst ...

  6. 10、 导出python脚本进行数据驱动的接口测试

    postman自带脚本导出功能,对于代码小白来说,可以不错的学习代码级接口测试 第一步:输入接口地址,点击send 第二步:点击code,导出脚本文件,为python脚本 第三步:安装python3以 ...

  7. 7 Scatter-loading Features

    7.1 About scatter-loading The scatter-loading mechanism enables you to specify the memory map of an ...

  8. Day 11:函数装饰器

    在说装饰器前,先说一个东西,再Python里,有一个 一切皆对象,一切皆变量. 例: def hello(name="sunjinyao"): return "hi &q ...

  9. 15-Ubuntu-文件和目录命令-查看目录内容-ls-2

    4. ls和通配符的使用 通配符适用的地方:shell命令行或者shell脚本中. 正则表达式适用的地方:字符串处理时,一般有一般正则和Perl正则. 正则表达式与通配符有相同的符号但是意义不同!! ...

  10. OpenCV3 VideoCapture buffer

    在ubuntu16.04下写关于opencv的工程,在调用摄像头时发现VideoCapture有5帧的buffer,所以采用5个capture>>mat来处理. if (FlagConti ...