说Tornado之前分享几个前端不错的网站:

  1. -- Bootstrap
  2. http://www.bootcss.com/
  3.  
  4. -- Font Awesome
  5. http://fontawesome.io/
  6.  
  7. -- bxslider
  8. http://bxslider.com/
  9.  
  10. -- jQuery EasyUI
  11. http://www.jeasyui.com/download/index.php
  12.  
  13. -- jQuery UI
  14. http://jqueryui.com/
  15.  
  16. -- parsleyjs
  17. http://parsleyjs.org/
  18.  
  19. -- jQuery Validate
  20. http://jqueryvalidation.org/

web框架的本质

总所周知,对于所有的Web应用,本质上其实就是一个socket服务端,用户的浏览器其实就是一个socket客户端。

  1. #!/usr/bin/env python
  2. # _*_ coding:utf-8 _*_
  3. import socket
  4.  
  5. def request_handler(client):
  6. a = client.recv(1024)
  7. client.send(bytes("HTTP/1.1 200 OK\r\n\r\n",encoding='utf-8'))
  8. client.send(bytes("hello",encoding="utf-8"))
  9.  
  10. def main():
  11. sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
  12. sock.bind(("127.0.0.1",8000))
  13. sock.listen(5)
  14. while True:
  15. con,add = sock.accept()
  16. request_handler(con)
  17. con.close()
  18.  
  19. if __name__ == '__main__':
  20. main()

上述通过socket来实现了其本质,而对于真实开发中的python web程序来说,一般会分为两部分:服务器程序和应用程序。服务器程序负责对socket服务器进行封装,并在请求到来时,对请求的各种数据进行整理。应用程序则负责具体的逻辑处理。为了方便应用程序的开发,就出现了众多的Web框架,例如:Django、Flask、web.py 等。不同的框架有不同的开发方式,但是无论如何,开发出的应用程序都要和服务器程序配合,才能为用户提供服务。这样,服务器程序就需要为不同的框架提供不同的支持。这样混乱的局面无论对于服务器还是框架,都是不好的。对服务器来说,需要支持各种不同框架,对框架来说,只有支持它的服务器才能被开发出的应用使用。这时候,标准化就变得尤为重要。我们可以设立一个标准,只要服务器程序支持这个标准,框架也支持这个标准,那么他们就可以配合使用。一旦标准确定,双方各自实现。这样,服务器可以支持更多支持标准的框架,框架也可以使用更多支持标准的服务器。

WSGI(Web Server Gateway Interface)是一种规范,它定义了使用python编写的web app与web server之间接口格式,实现web app与web server间的解耦。

python标准库提供的独立WSGI服务器称为wsgiref。

2.x环境运行

  1. from wsgiref.simple_server import make_server
  2.  
  3. def RunServer(environ, start_response):
  4. start_response('200 OK', [('Content-Type', 'text/html')])
  5. return '<h1>Hello, web!</h1>'
  6.  
  7. if __name__ == '__main__':
  8. httpd = make_server('', 8888, RunServer)
  9. httpd.serve_forever()

自定义Web框架  

  1. #!/usr/bin/env python
  2. # _*_ coding:utf-8 _*_
  3.  
  4. from wsgiref.simple_server import make_server
  5.  
  6. def index():
  7. return "hi"
  8.  
  9. URLS = {
  10. "/index":index,
  11. }
  12.  
  13. def RunServer(environ, start_response): #environ 封装了请求头信息
  14. start_response('200 OK', [('Content-Type', 'text/html')])
  15. url = environ["PATH_INFO"]
  16. if url in URLS.keys():
  17. ret=URLS[url]()
  18.  
  19. else:
  20. ret = "404"
  21. return ret
  22.  
  23. if __name__ == '__main__':
  24. httpd = make_server('', 8000, RunServer)
  25. httpd.serve_forever()

在上一步骤中,对于所有的index返回给用户浏览器一个简单的字符串,在现实的Web请求中一般会返回一个复杂的符合HTML规则的字符串,所以我们一般将要返回给用户的HTML写在指定文件中,然后再返回。如:  

  1. <!DOCTYPE html>
  2. <html>
  3. <head lang="en">
  4. <meta charset="UTF-8">
  5. <title></title>
  6. </head>
  7. <body>
  8. <form>
  9. <input type="text" />
  10. <input type="text" />
  11. <input type="submit" />
  12. </form>
  13. </body>
  14. </html>

返回表单

  1. #!/usr/bin/env python
  2. # _*_ coding:utf-8 _*_
  3.  
  4. from wsgiref.simple_server import make_server
  5.  
  6. def login():
  7. file_list = open("temp/s2.html",'r')
  8. data = file_list.read()
  9. return data
  10.  
  11. def index():
  12. file_list = open("temp/s1.html",'r')
  13. data = file_list.read()
  14. return data
  15.  
  16. URLS = {
  17. "/index":index,
  18. "/login":login,
  19. }
  20.  
  21. def RunServer(environ, start_response):
  22. start_response('200 OK', [('Content-Type', 'text/html')])
  23. url = environ["PATH_INFO"]
  24. if url in URLS.keys():
  25. ret=URLS[url]()
  26. else:
  27. ret = "404"
  28. return ret
  29.  
  30. if __name__ == '__main__':
  31. httpd = make_server('', 8000, RunServer)
  32. httpd.serve_forever()

对于上述代码,虽然可以返回给用户HTML的内容以现实复杂的页面,但是还是存在问题:如何给用户返回动态内容?  

  • 自定义一套特殊的语法,进行替换
  • 使用开源工具jinja2,遵循其指定语法
  1. <!DOCTYPE html>
  2. <html>
  3. <head lang="en">
  4. <meta charset="UTF-8">
  5. <title></title>
  6. </head>
  7. <body>
  8. <h1>{{name}}</h1>
  9.  
  10. <ul>
  11. {% for item in user_list %}
  12. <li>{{item}}</li>
  13. {% endfor %}
  14. </ul>
  15.  
  16. </body>
  17. </html>

index

  1. #!/usr/bin/env python
  2. # _*_ coding:utf-8 _*_
  3.  
  4. from wsgiref.simple_server import make_server
  5. from jinja2 import Template
  6.  
  7. def index():
  8. file_list = open("temp/s3.html",'r')
  9. file_text = file_list.read()
  10. temp = Template(file_text)
  11. data = temp.render(name="john Doe",user_list = ['tangseng','wukong'])
  12. return data.encode("utf-8")
  13.  
  14. def login():
  15. file_list = open("temp/s2.html",'r')
  16. data = file_list.read()
  17.  
  18. return data
  19.  
  20. URLS = {
  21. "/index":index,
  22. "/login":login,
  23. }
  24.  
  25. def RunServer(environ, start_response):
  26. start_response('200 OK', [('Content-Type', 'text/html')])
  27. url = environ["PATH_INFO"]
  28. if url in URLS.keys():
  29. ret=URLS[url]()
  30. else:
  31. ret = "404"
  32. return ret
  33.  
  34. if __name__ == '__main__':
  35. httpd = make_server('', 8000, RunServer)
  36. httpd.serve_forever()

遵循jinja2的语法规则,其内部会对指定的语法进行相应的替换,从而达到动态的返回内容

 Tornado 前戏

执行字符串表示的函数,并为该函数提供全局变量

本篇的内容从题目中就可以看出来,就是为之后剖析tornado模板做准备,也是由于该知识点使用的巧妙,所有就单独用一篇来介绍了。废话不多说,直接上代码:

  1. #!usr/bin/env python
  2. #coding:utf-8
  3.  
  4. namespace = {'name':'wukong','data':[18,73,84]}
  5.  
  6. code = '''def hellocute():return "name %s ,age %d" %(name,data[0],) '''
  7.  
  8. func = compile(code, '<string>', "exec")
  9.  
  10. exec func in namespace
  11.  
  12. result = namespace['hellocute']()

此段代码的执行结果是:name wukong,age 18  

上述代码解析:

  • 第6行,code是一个字符串,该字符串的内容是一个函数体。
  • 第8行,将code字符串编译成函数 hello
  • 第10行,将函数 hello 添加到namespace字典中(key为hello),同时也将python的所有内置函数添加到namespace字段中(key为__builtins__),如此一来,namespace中的内容好比是一个个的全局变量,即
  • 第12行,执行Hello函数并将返回值复制给result
  • 第14行,输入result
  1. name wupeiqi
  2. data [18,73,84]
  3.  
  4. def hellocute():
  5. return "name %s ,age %d" %(name,data[0],)

这段代码用的很是巧妙有木有,亮瞎狗眼有木有,居然把字符串变成了函数并且还为该函数提供了全局变量。对于该功能,它就是python的web框架中模板语言部分至关重要的部分,因为在模板处理过程中,首先会读取html文件,然后分割html文件,再然后讲分割的文件组成一个字符串表示的函数,再再然后就是利用上述方法执行字符串表示的函数。

快速上手Tornado

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

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

 一、安装如下:

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

第一次运行Tornado代码:  

  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. application = tornado.web.Application([
  12. (r"/index", MainHandler),
  13. ])
  14.  
  15. if __name__ == "__main__":
  16. application.listen(8888)
  17. 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.  
  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. if __name__ == "__main__":
  38. application.listen(8009)
  39. 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. class LoginHandler(tornado.web.RequestHandler):
  8.  
  9. def get(self, *args, **kwargs):
  10. self.render("s2.html")
  11.  
  12. class IndexHandler(tornado.web.RequestHandler):
  13.  
  14. def get(self, *args, **kwargs):
  15. self.render("index.html")
  16. #路由系统
  17. settings = {
  18. "template_path":"templates",
  19.  
  20. }
  21.  
  22. application = tornado.web.Application([
  23. (r"/index",IndexHandler),
  24. (r"/login",LoginHandler),
  25. ])
  26.  
  27. if __name__ == '__main__':
  28. httpd = application.listen(8888)
  29. tornado.ioloop.IOLoop.instance().start()

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

三、模板

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

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

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

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()

app

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
  5. <title>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

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

  1. escape: tornado.escape.xhtml_escape 的別名
  2. xhtml_escape: tornado.escape.xhtml_escape 的別名
  3. url_escape: tornado.escape.url_escape 的別名
  4. json_encode: tornado.escape.json_encode 的別名
  5. squeeze: tornado.escape.squeeze 的別名
  6. linkify: tornado.escape.linkify 的別名
  7. datetime: Python datetime 模组
  8. handler: 当前的 RequestHandler 对象
  9. request: handler.request 的別名
  10. current_user: handler.current_user 的別名
  11. locale: handler.locale 的別名
  12. _: handler.locale.translate 的別名
  13. static_url: for handler.static_url 的別名
  14. 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. <!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

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

uimmodule

注册

  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()

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. {% module custom(123) %}
  11. {{ tab() }}
  12. </body>

index

四、静态文件

对于静态文件,可以配置静态文件的目录和前段使用时的前缀,并且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', //静态资源的的路径比如 css,js
  14. 'static_url_prefix': '/static/', // 静态资源的的 URL 前缀
  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()

备注:静态文件缓存的实现

五、cookie

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

a、基本操作

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

b、加密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=")//随意增加

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. def _create_signature_v2(secret, s):
  8. hash = hmac.new(utf8(secret), digestmod=hashlib.sha256)
  9. hash.update(utf8(s))
  10. 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"",
  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""):
  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. def _decode_fields_v2(value):
  31. def _consume_field(s):
  32. length, _, rest = s.partition(b':')
  33. n = int(length)
  34. field_value = rest[:n]
  35. # In python 3, indexing bytes returns small integers; we must
  36. # use a slice to get a byte string as in python 2.
  37. if rest[n:n + 1] != b'|':
  38. raise ValueError("malformed v2 signed value field")
  39. rest = rest[n + 1:]
  40. return field_value, rest
  41.  
  42. rest = value[2:] # remove version number
  43. key_version, rest = _consume_field(rest)
  44. timestamp, rest = _consume_field(rest)
  45. name_field, rest = _consume_field(rest)
  46. value_field, passed_sig = _consume_field(rest)
  47. return int(key_version), timestamp, name_field, value_field, passed_sig
  48.  
  49. def _decode_signed_value_v2(secret, name, value, max_age_days, clock):
  50. try:
  51. key_version, timestamp, name_field, value_field, passed_sig = _decode_fields_v2(value)
  52. except ValueError:
  53. return None
  54. signed_string = value[:-len(passed_sig)]
  55.  
  56. if isinstance(secret, dict):
  57. try:
  58. secret = secret[key_version]
  59. except KeyError:
  60. return None
  61.  
  62. expected_sig = _create_signature_v2(secret, signed_string)
  63. if not _time_independent_equals(passed_sig, expected_sig):
  64. return None
  65. if name_field != utf8(name):
  66. return None
  67. timestamp = int(timestamp)
  68. if timestamp < clock() - max_age_days * 86400:
  69. # The signature has expired.
  70. return None
  71. try:
  72. return base64.b64decode(value_field)
  73. except Exception:
  74. return None
  75.  
  76. def get_signature_key_version(value):
  77. value = utf8(value)
  78. version = _get_version(value)
  79. if version < 2:
  80. return None
  81. try:
  82. key_version, _, _, _, _ = _decode_fields_v2(value)
  83. except ValueError:
  84. return None
  85.  
  86. return key_version

算法解密

签名Cookie的本质是:

  1. cookie过程:
  2.  
  3. 将值进行base64加密
  4. 对除值以外的内容进行签名,哈希算法(无法逆向解析)
  5. 拼接 签名 + 加密值
  6. cookie过程:
  7.  
  8. 读取 签名 + 加密值
  9. 对签名进行验证
  10. 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 == 'wukong' and password == '':
  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()

基于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. 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 == 'wukong' and password == '':
  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()

Demo-Toando内部提供基于cookie进行用户验证

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)

限制与POST请求

  1.  
  1. 普通表单使用
  1. 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. 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()

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. 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()

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="" type="file" />
  37. <input id="formsubmit" type="submit" value="Send File" />
  38. </form>
  39.  
  40. <div id="textarea">
  41. </div>

扩展:基于iframe实现Ajax上传示例

八、验证码

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

安装图像处理模块:

  1. pip3 install pillow

示例截图:

  

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

源码中有两个文件要注意Monaco.ttf和check_code.py 要引入到工程内;demo

自定义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. import hashlib
  2. import time
  3.  
  4. container = {}
  5. class Session:
  6. def __init__(self, handler):
  7. self.handler = handler
  8. self.random_str = None
  9.  
  10. def __genarate_random_str(self):
  11. '''
  12. 用于生成加密字符串
  13. :return:
  14. '''
  15.  
  16. obj = hashlib.md5()
  17. obj.update(bytes(str(time.time()), encoding='utf-8'))
  18. random_str = obj.hexdigest()
  19. return random_str
  20.  
  21. def __setitem__(self, key, value):
  22. # 在container中加入随机字符串
  23. # 定义专属于自己的数据
  24. # 在客户端中写入随机字符串
  25. # 判断,请求的用户是否已有随机字符串
  26. if not self.random_str:
  27. random_str = self.handler.get_cookie('__kakaka__')
  28. if not random_str:
  29. random_str = self.__genarate_random_str()
  30. container[random_str] = {}
  31. else:
  32. # 客户端有随机字符串
  33. if random_str in container.keys(): #判断字符串是否在container中
  34. pass
  35. else:
  36. random_str = self.__genarate_random_str() #生成字符串
  37. container[random_str] = {} #生成专属的字典
  38. self.random_str = random_str # self.random_str = asdfasdfasdfasdf
  39.  
  40. container[self.random_str][key] = value
  41. self.handler.set_cookie("__kakaka__", self.random_str)
  42.  
  43. def __getitem__(self,key):
  44. # 获取客户端的随机字符串
  45. # 从container中获取专属于我的数据
  46. # 专属信息【key】
  47. random_str = self.handler.get_cookie("__kakaka__")
  48. if not random_str:
  49. return None
  50. # 客户端有随机字符串
  51. user_info_dict = container.get(random_str,None)
  52. if not user_info_dict:
  53. return None
  54. value = user_info_dict.get(key, None)
  55. return value

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. """

一致性哈西

  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)

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. class MainForm(object):
  11. def __init__(self):
  12. self.host = "(.*)"
  13. 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}$"
  14. self.port = '(\d+)'
  15. self.phone = '^1[3|4|5|8][0-9]\d{8}$'
  16.  
  17. def check_valid(self, request):
  18. form_dict = self.__dict__
  19. for key, regular in form_dict.items():
  20. post_value = request.get_argument(key)
  21. # 让提交的数据 和 定义的正则表达式进行匹配
  22. ret = re.match(regular, post_value)
  23. print key,ret,post_value
  24.  
  25. class MainHandler(tornado.web.RequestHandler):
  26. def get(self):
  27. self.render('index.html')
  28. def post(self, *args, **kwargs):
  29. obj = MainForm()
  30. result = obj.check_valid(self)
  31. self.write('ok')
  32.  
  33. settings = {
  34. 'template_path': 'template',
  35. 'static_path': 'static',
  36. 'static_url_prefix': '/static/',
  37. 'cookie_secret': 'aiuasdhflashjdfoiuashdfiuh',
  38. 'login_url': '/login'
  39. }
  40.  
  41. application = tornado.web.Application([
  42. (r"/index", MainHandler),
  43. ], **settings)
  44.  
  45. if __name__ == "__main__":
  46. application.listen(8888)
  47. tornado.ioloop.IOLoop.instance().start()

Python

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

  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框架之Tornado的简单使用

    python web框架有很多,比如常用的有django,flask等.今天主要介绍Tornado ,Tornado是一个用Python写的相对简单的.不设障碍的Web服务器架构,用以处理上万的同时的 ...

  2. python web框架——初识tornado

    一 Tornado概述 Tornado是FriendFeed使用的可扩展的非阻塞式web框架及其相关工具的开源版本.这个Web框架看起来有些像web.py或者Google的 webapp,不过为了能有 ...

  3. Python Web 框架:Tornado

    1.Tornado Tornado:python编写的web服务器兼web应用框架 1.1.Tornado的优势 轻量级web框架 异步非阻塞IO处理方式 出色的抗负载能力 优异的处理性能,不依赖多进 ...

  4. 浅谈Python web框架

    一.Python web框架 Web Framework,Ruby的世界Rails一统江湖,而Python则是一个百花齐放的世界,各种micro-framework.framework不可胜数,不完全 ...

  5. python web框架——扩展Django&tornado

    一 Django自定义分页 目的:自定义分页功能,并把它写成模块(注意其中涉及到的python基础知识) models.py文件 # Create your models here. class Us ...

  6. 异步非阻塞IO的Python Web框架--Tornado

    Tornado的全称是Torado Web Server,从名字上就可知它可用作Web服务器,但同时它也是一个Python Web的开发框架.最初是在FriendFeed公司的网站上使用,FaceBo ...

  7. Python Web框架 tornado 异步原理

    Python Web框架 tornado 异步原理 参考:http://www.jb51.net/article/64747.htm 待整理

  8. Python web框架——Tornado

    Tornado是一个Python Web框架和异步网络库,最初由FriendFeed开发.通过使用非阻塞网络I / O,Tornado可以扩展到数万个开放连接,使其成为需要长时间连接每个用户的长轮询, ...

  9. Python web框架 Tornado(二)异步非阻塞

    异步非阻塞 阻塞式:(适用于所有框架,Django,Flask,Tornado,Bottle) 一个请求到来未处理完成,后续一直等待 解决方案:多线程,多进程 异步非阻塞(存在IO请求): Torna ...

随机推荐

  1. 【DDD】领域驱动设计实践 —— 业务建模小招数

    本文结合团队在ECO(社区服务系统)业务建模过程中的实践经验,总结得到一些DDD业务建模的小招数,不一定是完美的,但是对我们团队来说很有效用,希望能帮到其他人.后面会陆续将项目中业务建模的一些经典例子 ...

  2. springboot配置swagger2

    .在pom.xml里添加jar包: <dependency> <groupId>io.springfox</groupId> <artifactId>s ...

  3. JavaScript 的使用基础总结②DOM

    HTML DOM 通过 HTML DOM,可访问 JavaScript HTML 文档的所有元素. 当网页被加载时,浏览器会创建页面的文档对象模型(Document Object Model). HT ...

  4. ajax中后台string转json

    首先导入alibaba的fastJson包 后台: String thirdPage1=prop.getProperty("thirdPage1"); String thirdPa ...

  5. 201521123015 《Java程序设计》第4周学习总结

    本周学习总结 1.1 尝试使用思维导图总结有关继承的知识点. 1.2 使用常规方法总结其他上课内容. 1.多态:使用单一接口操作多种类型的对象. 2.private修饰属性,public修饰方法. 3 ...

  6. 201521123036 《Java程序设计》第12周学习总结

    本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多流与文件相关内容. 书面作业 将Student对象(属性:int id, String name,int age,double grad ...

  7. 201521123039《java程序设计》第十四周学习总结

    1. 本周学习总结 2. 书面作业 1. MySQL数据库基本操作 建立数据库,将自己的姓名.学号作为一条记录插入.(截图,需出现自己的学号.姓名) 在自己建立的数据库上执行常见SQL语句(截图) 2 ...

  8. JS运动缓冲的封装函数

    之前经常写运动函数,要写好多好多,后来想办法封装起来.(运动缓冲). /* 物体多属性同时运动的函数 obj:运动的物体 oTarget:对象,属性名为运动的样式名,属性值为样式运动的终点值 rati ...

  9. hdu3037 Saving Beans

    Saving Beans Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Pro ...

  10. python入门之一python安装及程序运行

    Python 程序要运行,需要先安装python解释器 PVM(这里可对照java的JVM来理解)实际上,你不需要单独安装,直接安装python后就可以了 1.安装python 下载地址:http:/ ...