Python的WEB框架

Bottle

Bottle是一个快速、简洁、轻量级的基于WSIG的微型Web框架,此框架只由一个 .py 文件,除了Python的标准库外,其不依赖任何其他模块。

  1. pip install bottle
  2. easy_install bottle
  3. apt-get install python-bottle
  4. wget http://bottlepy.org/bottle.py

Bottle框架大致可以分为以下部分:

  • 路由系统,将不同请求交由指定函数处理
  • 模板系统,将模板中的特殊语法渲染成字符串,值得一说的是Bottle的模板引擎可以任意指定:Bottle内置模板、makojinja2cheetah
  • 公共组件,用于提供处理请求相关的信息,如:表单数据、cookies、请求头等
  • 服务,Bottle默认支持多种基于WSGI的服务,如:
  1. server_names = {
  2. 'cgi': CGIServer,
  3. 'flup': FlupFCGIServer,
  4. 'wsgiref': WSGIRefServer,
  5. 'waitress': WaitressServer,
  6. 'cherrypy': CherryPyServer,
  7. 'paste': PasteServer,
  8. 'fapws3': FapwsServer,
  9. 'tornado': TornadoServer,
  10. 'gae': AppEngineServer,
  11. 'twisted': TwistedServer,
  12. 'diesel': DieselServer,
  13. 'meinheld': MeinheldServer,
  14. 'gunicorn': GunicornServer,
  15. 'eventlet': EventletServer,
  16. 'gevent': GeventServer,
  17. 'geventSocketIO':GeventSocketIOServer,
  18. 'rocket': RocketServer,
  19. 'bjoern' : BjoernServer,
  20. 'auto': AutoServer,
  21. }

框架的基本使用

  1. #!/usr/bin/env python
  2. # -*- coding:utf-8 -*-
  3. from bottle import template, Bottle
  4. root = Bottle()
  5.  
  6. @root.route('/hello/')
  7. def index():
  8. return "Hello World"
  9. # return template('<b>Hello {{name}}</b>!', name="Alex")
  10.  
  11. root.run(host='localhost', port=8080)

一、路由系统

路由系统是的url对应指定函数,当用户请求某个url时,就由指定函数处理当前请求,对于Bottle的路由系统可以分为一下几类:

  • 静态路由
  • 动态路由
  • 请求方法路由
  • 二级路由

1、静态路由

  1. @root.route('/hello/')
  2. def index():
  3. return template('<b>Hello {{name}}</b>!', name="xs")

2、动态路由

  1. @root.route('/wiki/<pagename>')
  2. def callback(pagename):
  3. ...
  4.  
  5. @root.route('/object/<id:int>')
  6. def callback(id):
  7. ...
  8.  
  9. @root.route('/show/<name:re:[a-z]+>')
  10. def callback(name):
  11. ...
  12.  
  13. @root.route('/static/<path:path>')
  14. def callback(path):
  15. return static_file(path, root='static')

3、请求方法路由

  1. @root.route('/hello/', method='POST')
  2. def index():
  3. ...
  4.  
  5. @root.get('/hello/')
  6. def index():
  7. ...
  8.  
  9. @root.post('/hello/')
  10. def index():
  11. ...
  12.  
  13. @root.put('/hello/')
  14. def index():
  15. ...
  16.  
  17. @root.delete('/hello/')
  18. def index():
  19. ...

4、二级路由

  1. #!/usr/bin/env python
  2. # -*- coding:utf-8 -*-
  3. from bottle import template, Bottle
  4.  
  5. app01 = Bottle()
  6.  
  7. @app01.route('/hello/', method='GET')
  8. def index():
  9. return template('<b>App01</b>!')

app01.py

  1. #!/usr/bin/env python
  2. # -*- coding:utf-8 -*-
  3. from bottle import template, Bottle
  4.  
  5. app02 = Bottle()
  6.  
  7. @app02.route('/hello/', method='GET')
  8. def index():
  9. return template('<b>App02</b>!')

app02.py

  1. #!/usr/bin/env python
  2. # -*- coding:utf-8 -*-
  3. from bottle import template, Bottle
  4. from bottle import static_file
  5. root = Bottle()
  6.  
  7. @root.route('/hello/')
  8. def index():
  9. return template('<b>Root {{name}}</b>!', name="Alex")
  10.  
  11. from framwork_bottle import app01
  12. from framwork_bottle import app02
  13.  
  14. root.mount('app01', app01.app01)
  15. root.mount('app02', app02.app02)
  16.  
  17. root.run(host='localhost', port=8080)

二、模板系统

模板系统用于将Html和自定的值两者进行渲染,从而得到字符串,然后将该字符串返回给客户端。我们知道在Bottle中可以使用 内置模板系统、makojinja2cheetah等,以内置模板系统为例:

  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. </body>
  10. </html>

hello_template.tpl

  1. #!/usr/bin/env python
  2. # -*- coding:utf-8 -*-
  3. from bottle import template, Bottle
  4. root = Bottle()
  5.  
  6. @root.route('/hello/')
  7. def index():
  8. # 默认情况下去目录:['./', './views/']中寻找模板文件 hello_template.html
  9. # 配置在 bottle.TEMPLATE_PATH 中
  10. return template('hello_template.tpl', name='alex')
  11.  
  12. root.run(host='localhost', port=8080)

1、语法

  • 单值
  • 单行Python代码
  • Python代码快
  • Python、Html混合
  1. <h1>1、单值</h1>
  2. {{name}}
  3.  
  4. <h1>2、单行Python代码</h1>
  5. % s1 = "hello"
  6.  
  7. <h1>3、Python代码块</h1>
  8. <%
  9. # A block of python code
  10. name = name.title().strip()
  11. if name == "Alex":
  12. name="seven"
  13. %>
  14.  
  15. <h1>4、Python、Html混合</h1>
  16.  
  17. % if True:
  18. <span>{{name}}</span>
  19. % end
  20. <ul>
  21. % for item in name:
  22. <li>{{item}}</li>
  23. % end
  24. </ul>

2、函数

include(sub_template, **variables)

  1. # 导入其他模板文件
  2.  
  3. % include('header.tpl', title='Page Title')
  4. Page Content
  5. % include('footer.tpl')

rebase(name, **variables)

  1. <html>
  2. <head>
  3. <title>{{title or 'No title'}}</title>
  4. </head>
  5. <body>
  6. {{!base}}
  7. </body>
  8. </html>

base.tpl

  1. # 导入母版
  2.  
  3. % rebase('base.tpl', title='Page Title')
  4. <p>Page Content ...</p>

defined(name)

  1. # 检查当前变量是否已经被定义,已定义True,未定义False

get(name, default=None)

  1. # 获取某个变量的值,不存在时可设置默认值

setdefault(name, default)

  1. # 如果变量不存在时,为变量设置默认值

扩展:自定义函数

  1. <!DOCTYPE html>
  2. <html>
  3. <head lang="en">
  4. <meta charset="UTF-8">
  5. <title></title>
  6. </head>
  7. <body>
  8. <h1>自定义函数</h1>
  9. {{ wupeiqi() }}
  10.  
  11. </body>
  12. </html>

hello_template.tpl

  1. #!/usr/bin/env python
  2. # -*- coding:utf-8 -*-
  3. from bottle import template, Bottle,SimpleTemplate
  4. root = Bottle()
  5.  
  6. def custom():
  7. return ''
  8.  
  9. @root.route('/hello/')
  10. def index():
  11. # 默认情况下去目录:['./', './views/']中寻找模板文件 hello_template.html
  12. # 配置在 bottle.TEMPLATE_PATH 中
  13. return template('hello_template.html', name='alex', wupeiqi=custom)
  14.  
  15. root.run(host='localhost', port=8080)

main.py

注:变量或函数前添加 【 ! 】,则会关闭转义的功能

三、公共组件

由于Web框架就是用来【接收用户请求】-> 【处理用户请求】-> 【响应相关内容】,对于具体如何处理用户请求,开发人员根据用户请求来进行处理,而对于接收用户请求和相应相关的内容均交给框架本身来处理,其处理完成之后将产出交给开发人员和用户。

【接收用户请求】

当框架接收到用户请求之后,将请求信息封装在Bottle的request中,以供开发人员使用

【响应相关内容】

当开发人员的代码处理完用户请求之后,会将其执行内容相应给用户,相应的内容会封装在Bottle的response中,然后再由框架将内容返回给用户

所以,公共组件本质其实就是为开发人员提供接口,使其能够获取用户信息并配置响应内容。

1、request

Bottle中的request其实是一个LocalReqeust对象,其中封装了用户请求的相关信息:

  1. request.headers
  2. 请求头信息
  3.  
  4. request.query
  5. get请求信息
  6.  
  7. request.forms
  8. post请求信息
  9.  
  10. request.files
  11. 上传文件信息
  12.  
  13. request.params
  14. getpost请求信息
  15.  
  16. request.GET
  17. get请求信息
  18.  
  19. request.POST
  20. post和上传信息
  21.  
  22. request.cookies
  23. cookie信息
  24.  
  25. request.environ
  26. 环境相关相关

2、response

Bottle中的request其实是一个LocalResponse对象,其中框架即将返回给用户的相关信息:

  1. response
  2. response.status_line
  3. 状态行
  4.  
  5. response.status_code
  6. 状态码
  7.  
  8. response.headers
  9. 响应头
  10.  
  11. response.charset
  12. 编码
  13.  
  14. response.set_cookie
  15. 在浏览器上设置cookie
  16.  
  17. response.delete_cookie
  18. 在浏览器上删除cookie

实例:

  1. from bottle import route, request
  2.  
  3. @route('/login')
  4. def login():
  5. return '''
  6. <form action="/login" method="post">
  7. Username: <input name="username" type="text" />
  8. Password: <input name="password" type="password" />
  9. <input value="Login" type="submit" />
  10. </form>
  11. '''
  12.  
  13. @route('/login', method='POST')
  14. def do_login():
  15. username = request.forms.get('username')
  16. password = request.forms.get('password')
  17. if check_login(username, password):
  18. return "<p>Your login information was correct.</p>"
  19. else:
  20. return "<p>Login failed.</p>"

基本Form请求

  1. <form action="/upload" method="post" enctype="multipart/form-data">
  2. Category: <input type="text" name="category" />
  3. Select a file: <input type="file" name="upload" />
  4. <input type="submit" value="Start upload" />
  5. </form>
  6.  
  7. @route('/upload', method='POST')
  8. def do_upload():
  9. category = request.forms.get('category')
  10. upload = request.files.get('upload')
  11. name, ext = os.path.splitext(upload.filename)
  12. if ext not in ('.png','.jpg','.jpeg'):
  13. return 'File extension not allowed.'
  14.  
  15. save_path = get_save_path_for_category(category)
  16. upload.save(save_path) # appends upload.filename automatically
  17. return 'OK'

上传文件

四、服务

对于Bottle框架其本身未实现类似于Tornado自己基于socket实现Web服务,所以必须依赖WSGI,默认Bottle已经实现并且支持的WSGI有:

  1. server_names = {
  2. 'cgi': CGIServer,
  3. 'flup': FlupFCGIServer,
  4. 'wsgiref': WSGIRefServer,
  5. 'waitress': WaitressServer,
  6. 'cherrypy': CherryPyServer,
  7. 'paste': PasteServer,
  8. 'fapws3': FapwsServer,
  9. 'tornado': TornadoServer,
  10. 'gae': AppEngineServer,
  11. 'twisted': TwistedServer,
  12. 'diesel': DieselServer,
  13. 'meinheld': MeinheldServer,
  14. 'gunicorn': GunicornServer,
  15. 'eventlet': EventletServer,
  16. 'gevent': GeventServer,
  17. 'geventSocketIO':GeventSocketIOServer,
  18. 'rocket': RocketServer,
  19. 'bjoern' : BjoernServer,
  20. 'auto': AutoServer,
  21. }

WSGI

使用时,只需在主app执行run方法时指定参数即可:

  1. #!/usr/bin/env python
  2. # -*- coding:utf-8 -*-
  3. from bottle import Bottle
  4. root = Bottle()
  5.  
  6. @root.route('/hello/')
  7. def index():
  8. return "Hello World"
  9. # 默认server ='wsgiref'
  10. root.run(host='localhost', port=8080, server='wsgiref')

默认server="wsgiref",即:使用Python内置模块wsgiref,如果想要使用其他时,则需要首先安装相关类库,然后才能使用。如:

  1. # 如果使用Tornado的服务,则需要首先安装tornado才能使用
  2.  
  3. class TornadoServer(ServerAdapter):
  4. """ The super hyped asynchronous server by facebook. Untested. """
  5. def run(self, handler): # pragma: no cover
  6. # 导入Tornado相关模块
  7. import tornado.wsgi, tornado.httpserver, tornado.ioloop
  8. container = tornado.wsgi.WSGIContainer(handler)
  9. server = tornado.httpserver.HTTPServer(container)
  10. server.listen(port=self.port,address=self.host)
  11. tornado.ioloop.IOLoop.instance().start()

bottle.py源码

PS:以上WSGI中提供了19种,如果想要使期支持其他服务,则需要扩展Bottle源码来自定义一个ServerAdapter

更多参见:http://www.bottlepy.org/docs/dev/index.html

Flask

Flask是一个基于Python开发并且依赖jinja2模板和Werkzeug WSGI服务的一个微型框架,对于Werkzeug本质是Socket服务端,其用于接收http请求并对请求进行预处理,然后触发Flask框架,开发人员基于Flask框架提供的功能对请求进行相应的处理,并返回给用户,如果要返回给用户复杂的内容时,需要借助jinja2模板来实现对模板的处理,即:将模板和数据进行渲染,将渲染后的字符串返回给用户浏览器。

“微”(micro) 并不表示你需要把整个 Web 应用塞进单个 Python 文件(虽然确实可以 ),也不意味着 Flask 在功能上有所欠缺。微框架中的“微”意味着 Flask 旨在保持核心简单而易于扩展。Flask 不会替你做出太多决策——比如使用何种数据库。而那些 Flask 所选择的——比如使用何种模板引擎——则很容易替换。除此之外的一切都由可由你掌握。如此,Flask 可以与您珠联璧合。

默认情况下,Flask 不包含数据库抽象层、表单验证,或是其它任何已有多种库可以胜任的功能。然而,Flask 支持用扩展来给应用添加这些功能,如同是 Flask 本身实现的一样。众多的扩展提供了数据库集成、表单验证、上传处理、各种各样的开放认证技术等功能。Flask 也许是“微小”的,但它已准备好在需求繁杂的生产环境中投入使用。

安装

  1. pip install Flask
  1. #!/usr/bin/env python
  2. # -*- coding:utf-8 -*-
  3. from werkzeug.wrappers import Request, Response
  4.  
  5. @Request.application
  6. def hello(request):
  7. return Response('Hello World!')
  8.  
  9. if __name__ == '__main__':
  10. from werkzeug.serving import run_simple
  11. run_simple('localhost', 4000, hello)

werkzeug

一、第一次

  1. from flask import Flask
  2. app = Flask(__name__)
  3.  
  4. @app.route("/")
  5. def hello():
  6. return "Hello World!"
  7.  
  8. if __name__ == "__main__":
  9. app.run()

二、路由系统

  • @app.route('/user/<username>')
  • @app.route('/post/<int:post_id>')
  • @app.route('/post/<float:post_id>')
  • @app.route('/post/<path:path>')
  • @app.route('/login', methods=['GET', 'POST'])

常用路由系统有以上五种,所有的路由系统都是基于一下对应关系来处理:

  1. DEFAULT_CONVERTERS = {
  2. 'default': UnicodeConverter,
  3. 'string': UnicodeConverter,
  4. 'any': AnyConverter,
  5. 'path': PathConverter,
  6. 'int': IntegerConverter,
  7. 'float': FloatConverter,
  8. 'uuid': UUIDConverter,
  9. }

注:对于Flask默认不支持直接写正则表达式的路由,不过可以通过自定义来实现,见:https://segmentfault.com/q/1010000000125259

三、模板

1、模板的使用

Flask使用的是Jinja2模板,所以其语法和Django无差别

2、自定义模板方法

Flask中自定义模板方法的方式和Bottle相似,创建一个函数并通过参数的形式传入render_template,如:

  1. <!DOCTYPE html>
  2. <html>
  3. <head lang="en">
  4. <meta charset="UTF-8">
  5. <title></title>
  6. </head>
  7. <body>
  8. <h1>自定义函数</h1>
  9. {{ww()|safe}}
  10.  
  11. </body>
  12. </html>

index.html

  1. #!/usr/bin/env python
  2. # -*- coding:utf-8 -*-
  3. from flask import Flask,render_template
  4. app = Flask(__name__)
  5.  
  6. def wupeiqi():
  7. return '<h1>Wupeiqi</h1>'
  8.  
  9. @app.route('/login', methods=['GET', 'POST'])
  10. def login():
  11. return render_template('login.html', ww=wupeiqi)
  12.  
  13. app.run()

四、公共组件

1、请求

对于Http请求,Flask会讲请求信息封装在request中(werkzeug.wrappers.BaseRequest),提供的如下常用方法和字段以供使用:

  1. request.method
  2. request.args
  3. request.form
  4. request.values
  5. request.files
  6. request.cookies
  7. request.headers
  8. request.path
  9. request.full_path
  10. request.script_root
  11. request.url
  12. request.base_url
  13. request.url_root
  14. request.host_url
  15. request.host
  1. @app.route('/login', methods=['POST', 'GET'])
  2. def login():
  3. error = None
  4. if request.method == 'POST':
  5. if valid_login(request.form['username'],
  6. request.form['password']):
  7. return log_the_user_in(request.form['username'])
  8. else:
  9. error = 'Invalid username/password'
  10. # the code below is executed if the request method
  11. # was GET or the credentials were invalid
  12. return render_template('login.html', error=error)

表单处理Demo

  1. from flask import request
  2. from werkzeug import secure_filename
  3.  
  4. @app.route('/upload', methods=['GET', 'POST'])
  5. def upload_file():
  6. if request.method == 'POST':
  7. f = request.files['the_file']
  8. f.save('/var/www/uploads/' + secure_filename(f.filename))
  9. ...

上传文件Demo

  1. from flask import request
  2.  
  3. @app.route('/setcookie/')
  4. def index():
  5. username = request.cookies.get('username')
  6. # use cookies.get(key) instead of cookies[key] to not get a
  7. # KeyError if the cookie is missing.
  8.  
  9. from flask import make_response
  10.  
  11. @app.route('/getcookie')
  12. def index():
  13. resp = make_response(render_template(...))
  14. resp.set_cookie('username', 'the username')
  15. return resp

Cookie操作

2、响应

当用户请求被开发人员的逻辑处理完成之后,会将结果发送给用户浏览器,那么就需要对请求做出相应的响应。

a.字符串

  1. @app.route('/index/', methods=['GET', 'POST'])
  2. def index():
  3. return "index"

b.模板引擎

  1. from flask import Flask,render_template,request
  2. app = Flask(__name__)
  3.  
  4. @app.route('/index/', methods=['GET', 'POST'])
  5. def index():
  6. return render_template("index.html")
  7.  
  8. app.run()

c.重定向

  1. #!/usr/bin/env python
  2. # -*- coding:utf-8 -*-
  3. from flask import Flask, redirect, url_for
  4. app = Flask(__name__)
  5.  
  6. @app.route('/index/', methods=['GET', 'POST'])
  7. def index():
  8. # return redirect('/login/')
  9. return redirect(url_for('login'))
  10.  
  11. @app.route('/login/', methods=['GET', 'POST'])
  12. def login():
  13. return "LOGIN"
  14.  
  15. app.run()

d.错误页面

  1. from flask import Flask, abort, render_template
  2. app = Flask(__name__)
  3.  
  4. @app.route('/e1/', methods=['GET', 'POST'])
  5. def index():
  6. abort(404, 'Nothing')
  7. app.run()

指定URL,简单错误

  1. from flask import Flask, abort, render_template
  2. app = Flask(__name__)
  3.  
  4. @app.route('/index/', methods=['GET', 'POST'])
  5. def index():
  6. return "OK"
  7.  
  8. @app.errorhandler(404)
  9. def page_not_found(error):
  10. return render_template('page_not_found.html'), 404
  11.  
  12. app.run()

e.设置相应信息

使用make_response可以对相应的内容进行操作

  1. from flask import Flask, abort, render_template,make_response
  2. app = Flask(__name__)
  3.  
  4. @app.route('/index/', methods=['GET', 'POST'])
  5. def index():
  6. response = make_response(render_template('index.html'))
  7. # response是flask.wrappers.Response类型
  8. # response.delete_cookie
  9. # response.set_cookie
  10. # response.headers['X-Something'] = 'A value'
  11. return response
  12.  
  13. app.run()

3、Session

除请求对象之外,还有一个 session 对象。它允许你在不同请求间存储特定用户的信息。它是在 Cookies 的基础上实现的,并且对 Cookies 进行密钥签名要使用会话,你需要设置一个密钥。

  • 设置:session['username'] = 'xxx'

  • 删除:session.pop('username', None)
  1. from flask import Flask, session, redirect, url_for, escape, request
  2.  
  3. app = Flask(__name__)
  4.  
  5. @app.route('/')
  6. def index():
  7. if 'username' in session:
  8. return 'Logged in as %s' % escape(session['username'])
  9. return 'You are not logged in'
  10.  
  11. @app.route('/login', methods=['GET', 'POST'])
  12. def login():
  13. if request.method == 'POST':
  14. session['username'] = request.form['username']
  15. return redirect(url_for('index'))
  16. return '''
  17. <form action="" method="post">
  18. <p><input type=text name=username>
  19. <p><input type=submit value=Login>
  20. </form>
  21. '''
  22.  
  23. @app.route('/logout')
  24. def logout():
  25. # remove the username from the session if it's there
  26. session.pop('username', None)
  27. return redirect(url_for('index'))
  28.  
  29. # set the secret key. keep this really secret:
  30. app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT'

Flask还有众多其他功能,更多参见:
    http://docs.jinkan.org/docs/flask/
    http://flask.pocoo.org/

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

一、快速上手

  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. #!/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()

异步非阻塞实例

二、路由系统

路由系统其实就是 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()

三、模板

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

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

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

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

  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. }
  14.  
  15. application = tornado.web.Application([
  16. (r"/index", MainHandler),
  17. ], **settings)
  18.  
  19. if __name__ == "__main__":
  20. application.listen(80)
  21. tornado.ioloop.IOLoop.instance().start()

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

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

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

  1. # uimethods.py
  2.  
  3. def tab(self):
  4. return 'UIMethod'

uimethods.py

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

uimodules.py

2、注册

  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

3、使用

  1. <!DOCTYPE html>
  2. <html>
  3. <head lang="en">
  4. <meta charset="UTF-8">
  5. <title></title>
  6. <link href="{{static_url("commons.css")}}" rel="stylesheet" />
  7. </head>
  8. <body>
  9. <h1>hello</h1>
  10. {% module custom(123) %}
  11. {{ tab() }}
  12. </body>

index.html

四、实用功能

1、静态文件

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

  1. #!/usr/bin/env python
  2. # -*- coding:utf-8 -*-
  3.  
  4. import tornado.ioloop
  5. import tornado.web
  6.  
  7. 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()

main.py

  1. <!DOCTYPE html>
  2. <html>
  3. <head lang="en">
  4. <meta charset="UTF-8">
  5. <title></title>
  6. <link href="{{static_url("commons.css")}}" rel="stylesheet" />
  7. </head>
  8. <body>
  9. <h1>hello</h1>
  10. </body>
  11. </html>

index.html

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

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

静态文件缓存源码

2、csrf

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

  1. settings = {
  2. "xsrf_cookies": True,
  3. }
  4. application = tornado.web.Application([
  5. (r"/", MainHandler),
  6. (r"/login", LoginHandler),
  7. ], **settings)

配置

  1. <form action="/new_message" method="post">
  2. {{ xsrf_form_html() }}
  3. <input type="text" name="message"/>
  4. <input type="submit" value="Post"/>
  5. </form>

普通表单使用

  1. function getCookie(name) {
  2. var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
  3. return r ? r[1] : undefined;
  4. }
  5.  
  6. jQuery.postJSON = function(url, args, callback) {
  7. args._xsrf = getCookie("_xsrf");
  8. $.ajax({url: url, data: $.param(args), dataType: "text", type: "POST",
  9. success: function(response) {
  10. callback(eval("(" + response + ")"));
  11. }});
  12. };

Ajax使用

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

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

Code

b、签名

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 == 'wupeiqi' 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()

Demo-基于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 == 'wupeiqi' 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进行用户验证

五、扩展功能

1、自定义Session

a.知识储备

  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'] = 'xs'
  18. #del obj['k1']

b.session实现机制

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

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

2、自定义模型版定

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

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

在之前学习的Django中为程序员提供了非常便捷的模型绑定功能,但是在Tornado中,一切需要自己动手!!!

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

由于请求的验证时,需要考虑是否可以为空以及正则表达式的复用,所以:

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

Form验证框架

Python之Web框架们的更多相关文章

  1. Python之Web框架Django

    Python之Web框架: Django 一. Django Django是一个卓越的新一代Web框架 Django的处理流程 1. 下载地址  Python 下载地址:https://www.pyt ...

  2. Python之Web框架

    Python之Web框架: 一.  Web框架的本质: 对于所有的Web应用,本质上其实就是一个socket服务端,用户的浏览器其实就是一个socket客户端. #!/usr/bin/env pyth ...

  3. python 实现web框架simfish

    python 实现web框架simfish 本文主要记录本人利用python实现web框架simfish的过程.源码github地址:simfish WSGI HTTP Server wsgi模块提供 ...

  4. python各种web框架对比

    0 引言        python在web开发方面有着广泛的应用.鉴于各种各样的框架,对于开发者来说如何选择将成为一个问题.为此,我特此对比较常见的几种框架从性能.使用感受以及应用情况进行一个粗略的 ...

  5. Python3.5学习十八 Python之Web框架 Django

    Python之Web框架: 本质:Socket 引用wsgiref创建web框架 根据web框架创建过程优化所得: 分目录管理 模板单独目录 执行不同函数单独存入一个方法py文件 Web框架的两种形式 ...

  6. python之web框架(3):WSGI之web应用完善

    python之web框架(3):WSGI之web应用完善 1.上篇的web框架太low,只能实现回应固定页面.现在将它进行完善.首先将wsgi和web服务器进行分离,并给予它回复静态页面的能力. we ...

  7. python之web框架(2):了解WSGI接口

    python之web框架(2):了解WSGI接口 1.什么是wsgi接口: wsgi:Web Service Gateway Interface.它不是模块,而只是一种规范,方便web服务器和各种框架 ...

  8. python之web框架(1):完成静态页面web服务器

    python的web框架(1) 1.首先写一个最简单的web服务器,只能给客户回应一个固定的hello world的页面. from socket import * from multiprocess ...

  9. Python Flask Web 框架入门

    Python Flask 目录 本文主要借鉴 letiantian 的文章 http://www.letiantian.me/learn-flask/ 一.简介 二.安装 三.初始化Flask 四.获 ...

随机推荐

  1. Linux网络编程-SIGPIPE信号导致的程序退出问题

    当客户端close关闭连接时,若server端接着发送数据,根据TCP协议的规定,server端会收到RST响应,当server端再次往客户端发送数据时,系统会发出一个SIGPIPE信号给server ...

  2. SegmentFault创始人高阳:辍学后带着500元北漂,4年建成国内最大开发者

    i黑马注:i黑马曾经和高阳聊过几次天,在他身上我看到了90后CEO特别明显的成功特质“敢为天下先”.在别人犹豫的时候敢第一个出手,在互联网时代往往会取得最关键的“先机优势”. 7月19日,“腾讯产品家 ...

  3. HDOJ 1512 几乎模板的左偏树

    题目大意:有n个猴子.每个猴子有一个力量值,力量值越大表示这个猴子打架越厉害.如果2个猴子不认识,他们就会找他们认识的猴子中力量最大的出来单挑,单挑不论输赢,单挑的2个猴子力量值减半,这2拨猴子就都认 ...

  4. 让阿里云支持ipv6(其他多数VPS通用)

    https://www.tunnelbroker.net/tunnel_detail.php?tid=322922

  5. mfc中CString转化为string的方法

    LL(1)分析法实验的mfc做到最后因为CString转化为string的问题卡了一个多小时,也是惨,网上各种方法找过都不行.幸亏最后还是找到几行代码搞定了.特此mark一下. USES_CONVER ...

  6. centos6搭建VPN

    1,检查是否开启PPP #cat /dev/ppp cat: /dev/ppp: No such device or address //表示已经开启 2,安装ppp和iptables #yum in ...

  7. ubuntu下配置lamp环境

    安装MySQL sudo apt-get install mysql-server mysql-client 安装php模块 Sudo apt-get install php5 安装Apache2 S ...

  8. EasyUI DataGrid 配置参数

    var queryParams = $('#SBDiv_1_DateGrid').datagrid('options').queryParams; queryParams.SearchTime = & ...

  9. 再详细的介绍一下Unity5的AssetBundle

    之前曾经写了一篇博客介绍Unity5的AssetBundle,结果似乎很受关注.不过似乎很多人看了之后都不懂,主要是因为不太明白AssetBundle是什么,它的依赖关系和结构是什么的,就直接想拿代码 ...

  10. Ajax前台与Mod_python后台应用示例

    Ajax的好处就是可以实现无刷新动态更新.后台配合Mod_python程序,使后台处理变得非常高效简洁.[index.html] <HTML> <head> <meta ...