Python的WEB框架

Bottle

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

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

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

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

框架的基本使用

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

一、路由系统

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

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

1、静态路由

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

2、动态路由

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

3、请求方法路由

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

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from bottle import template, Bottle
from bottle import static_file
root = Bottle()
 
@root.route('/hello/')
def index():
    return template('<b>Root {{name}}</b>!', name="Alex")
 
from framwork_bottle import app01
from framwork_bottle import app02
 
root.mount('app01', app01.app01)
root.mount('app02', app02.app02)
 
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
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from bottle import template, Bottle
root = Bottle()
 
@root.route('/hello/')
def index():
    # 默认情况下去目录:['./', './views/']中寻找模板文件 hello_template.html
    # 配置在 bottle.TEMPLATE_PATH 中
    return template('hello_template.tpl', name='alex')
 
root.run(host='localhost', port=8080)

1、语法

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

2、函数

include(sub_template, **variables)

1
2
3
4
5
# 导入其他模板文件
 
% include('header.tpl', title='Page Title')
Page Content
% 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
4
# 导入母版
 
% rebase('base.tpl', title='Page Title')
<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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
request.headers
    请求头信息
 
request.query
    get请求信息
 
request.forms
    post请求信息
 
request.files
    上传文件信息
 
request.params
    get和post请求信息
 
request.GET
    get请求信息
 
request.POST
    post和上传信息
 
request.cookies
    cookie信息
     
request.environ
    环境相关相关

2、response

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
response
    response.status_line
        状态行
 
    response.status_code
        状态码
 
    response.headers
        响应头
 
    response.charset
        编码
 
    response.set_cookie
        在浏览器上设置cookie
         
    response.delete_cookie
        在浏览器上删除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
2
3
4
5
6
7
8
9
10
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from bottle import Bottle
root = Bottle()
 
@root.route('/hello/')
def index():
    return "Hello World"
# 默认server ='wsgiref'
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
2
3
4
5
6
7
8
9
from flask import Flask
app = Flask(__name__)
 
@app.route("/")
def hello():
    return "Hello World!"
 
if __name__ == "__main__":
    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
2
3
4
5
6
7
8
9
DEFAULT_CONVERTERS = {
    'default':          UnicodeConverter,
    'string':           UnicodeConverter,
    'any':              AnyConverter,
    'path':             PathConverter,
    'int':              IntegerConverter,
    'float':            FloatConverter,
    'uuid':             UUIDConverter,
}

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

四、公共组件

1、请求

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
request.method
request.args
request.form
request.values
request.files
request.cookies
request.headers
request.path
request.full_path
request.script_root
request.url
request.base_url
request.url_root
request.host_url
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
2
3
@app.route('/index/', methods=['GET', 'POST'])
def index():
    return "index"

b.模板引擎

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

c.重定向

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from flask import Flask, redirect, url_for
app = Flask(__name__)
 
@app.route('/index/', methods=['GET', 'POST'])
def index():
    # return redirect('/login/')
    return redirect(url_for('login'))
 
@app.route('/login/', methods=['GET', 'POST'])
def login():
    return "LOGIN"
 
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
2
3
4
5
6
7
8
9
10
11
12
from flask import Flask, abort, render_template
app = Flask(__name__)
 
@app.route('/index/', methods=['GET', 'POST'])
def index():
    return "OK"
 
@app.errorhandler(404)
def page_not_found(error):
    return render_template('page_not_found.html'), 404
 
app.run()

e.设置相应信息

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

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

3、Session

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

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

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

4.message

message是一个基于Session实现的用于保存数据的集合,其特点是:使用一次就删除

  1. <!DOCTYPE html>
  2. <html>
  3. <head lang="en">
  4. <meta charset="UTF-8">
  5. <title></title>
  6. </head>
  7. <body>
  8. {% with messages = get_flashed_messages() %}
  9. {% if messages %}
  10. <ul class=flashes>
  11. {% for message in messages %}
  12. <li>{{ message }}</li>
  13. {% endfor %}
  14. </ul>
  15. {% endif %}
  16. {% endwith %}
  17. </body>
  18. </html>

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from flask import Flask, flash, redirect, render_template, request
 
app = Flask(__name__)
app.secret_key = 'some_secret'
 
@app.route('/')
def index1():
    return render_template('index.html')
 
@app.route('/set')
def index2():
    v = request.args.get('p')
    flash(v)
    return 'ok'
 
if __name__ == "__main__":
    app.run()

5.中间件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from flask import Flask, flash, redirect, render_template, request
 
app = Flask(__name__)
app.secret_key = 'some_secret'
 
@app.route('/')
def index1():
    return render_template('index.html')
 
@app.route('/set')
def index2():
    v = request.args.get('p')
    flash(v)
    return 'ok'
 
class MiddleWare:
    def __init__(self,wsgi_app):
        self.wsgi_app = wsgi_app
 
    def __call__(self, *args, **kwargs):
 
        return self.wsgi_app(*args, **kwargs)
 
if __name__ == "__main__":
    app.wsgi_app = MiddleWare(app.wsgi_app)
    app.run(port=9999)

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
2
3
pip install tornado
源码安装
    https://pypi.python.org/packages/source/t/tornado/tornado-4.3.tar.gz

一、快速上手

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

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

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

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

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

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

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

异步非阻塞实例

二、路由系统

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#!/usr/bin/env python
# -*- coding:utf-8 -*-
  
import tornado.ioloop
import tornado.web
  
  
class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello, world")
  
class StoryHandler(tornado.web.RequestHandler):
    def get(self, story_id):
        self.write("You requested the story " + story_id)
  
class BuyHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("buy.wupeiqi.com/index")
  
application = tornado.web.Application([
    (r"/index", MainHandler),
    (r"/story/([0-9]+)", StoryHandler),
])
  
application.add_handlers('buy.wupeiqi.com$', [
    (r'/index',BuyHandler),
])
  
if __name__ == "__main__":
    application.listen(80)
    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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/usr/bin/env python
# -*- coding:utf-8 -*-
  
import tornado.ioloop
import tornado.web
  
  
class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.render('home/index.html')
  
settings = {
    'template_path': 'template',
}
  
application = tornado.web.Application([
    (r"/index", MainHandler),
], **settings)
  
  
if __name__ == "__main__":
    application.listen(80)
    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的本质是:

写cookie过程:

  • 将值进行base64加密
  • 对除值以外的内容进行签名,哈希算法(无法逆向解析)
  • 拼接 签名 + 加密值

读cookie过程:

  • 读取 签名 + 加密值
  • 对签名进行验证
  • base64解密,获取值内容

注:许多API验证机制和安全cookie的实现机制相同。

  1. #!/usr/bin/env python
  2. # -*- coding:utf-8 -*-
  3.  
  4. import tornado.ioloop
  5. import tornado.web
  6.  
  7. class MainHandler(tornado.web.RequestHandler):
  8.  
  9. def get(self):
  10. login_user = self.get_secure_cookie("login_user", None)
  11. if login_user:
  12. self.write(login_user)
  13. else:
  14. self.redirect('/login')
  15.  
  16. class LoginHandler(tornado.web.RequestHandler):
  17. def get(self):
  18. self.current_user()
  19.  
  20. self.render('login.html', **{'status': ''})
  21.  
  22. def post(self, *args, **kwargs):
  23.  
  24. username = self.get_argument('name')
  25. password = self.get_argument('pwd')
  26. if username == 'wupeiqi' and password == '':
  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进行用户验证

4、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

  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.  
  9. class MainHandler(tornado.web.RequestHandler):
  10. def get(self):
  11.  
  12. self.render('index.html')
  13.  
  14. def post(self, *args, **kwargs):
  15. file_metas = self.request.files["fff"]
  16. # print(file_metas)
  17. for meta in file_metas:
  18. file_name = meta['filename']
  19. with open(file_name,'wb') as up:
  20. up.write(meta['body'])
  21.  
  22. settings = {
  23. 'template_path': 'template',
  24. }
  25.  
  26. application = tornado.web.Application([
  27. (r"/index", MainHandler),
  28. ], **settings)
  29.  
  30. if __name__ == "__main__":
  31. application.listen(8000)
  32. tornado.ioloop.IOLoop.instance().start()

Python

  1. var fileObj = $("#img")[0].files[0];
  2. var form = new FormData();
  3. form.append("k1", "v1");
  4. form.append("fff", fileObj);
  5.  
  6. $.ajax({
  7. type:'POST',
  8. url: '/index',
  9. data: form,
  10. processData: false, // tell jQuery not to process the data
  11. contentType: false, // tell jQuery not to set contentType
  12. success: function(arg){
  13. console.log(arg);
  14. }
  15. })

jQuery Ajax Upload

五、扩展功能

1、自定义Session

a.知识储备

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

b.session实现机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#!/usr/bin/env python
# -*- coding:utf-8 -*-
  
import tornado.ioloop
import tornado.web
from hashlib import sha1
import os, time
  
session_container = {}
  
create_session_id = lambda: sha1('%s%s' % (os.urandom(16), time.time())).hexdigest()
  
  
class Session(object):
  
    session_id = "__sessionId__"
  
    def __init__(self, request):
        session_value = request.get_cookie(Session.session_id)
        if not session_value:
            self._id = create_session_id()
        else:
            self._id = session_value
        request.set_cookie(Session.session_id, self._id)
  
    def __getitem__(self, key):
        return session_container[self._id][key]
  
    def __setitem__(self, key, value):
        if session_container.has_key(self._id):
            session_container[self._id][key] = value
        else:
            session_container[self._id] = {key: value}
  
    def __delitem__(self, key):
        del session_container[self._id][key]
  
  
class BaseHandler(tornado.web.RequestHandler):
  
    def initialize(self):
        # my_session['k1']访问 __getitem__ 方法
        self.my_session = Session(self)
  
  
class MainHandler(BaseHandler):
  
    def get(self):
        print self.my_session['c_user']
        print self.my_session['c_card']
        self.write('index')
  
class LoginHandler(BaseHandler):
  
    def get(self):
        self.render('login.html', **{'status': ''})
  
    def post(self, *args, **kwargs):
  
        username = self.get_argument('name')
        password = self.get_argument('pwd')
        if username == 'wupeiqi' and password == '123':
  
            self.my_session['c_user'] = 'wupeiqi'
            self.my_session['c_card'] = '12312312309823012'
  
            self.redirect('/index')
        else:
            self.render('login.html', **{'status': '用户名或密码错误'})
  
settings = {
    'template_path': 'template',
    'static_path': 'static',
    'static_url_prefix': '/static/',
    'cookie_secret': 'aiuasdhflashjdfoiuashdfiuh',
    'login_url': '/login'
}
  
application = tornado.web.Application([
    (r"/index", MainHandler),
    (r"/login", LoginHandler),
], **settings)
  
  
if __name__ == "__main__":
    application.listen(8888)
    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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#!/usr/bin/env python
# -*- coding:utf-8 -*-
  
import tornado.ioloop
import tornado.web
from hashlib import sha1
import os, time
import re
  
  
class MainForm(object):
    def __init__(self):
        self.host = "(.*)"
        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}$"
        self.port = '(\d+)'
        self.phone = '^1[3|4|5|8][0-9]\d{8}$'
  
    def check_valid(self, request):
        form_dict = self.__dict__
        for key, regular in form_dict.items():
            post_value = request.get_argument(key)
            # 让提交的数据 和 定义的正则表达式进行匹配
            ret = re.match(regular, post_value)
            print key,ret,post_value
  
  
class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.render('index.html')
    def post(self, *args, **kwargs):
        obj = MainForm()
        result = obj.check_valid(self)
        self.write('ok')
  
  
  
settings = {
    'template_path': 'template',
    'static_path': 'static',
    'static_url_prefix': '/static/',
    'cookie_secret': 'aiuasdhflashjdfoiuashdfiuh',
    'login_url': '/login'
}
  
application = tornado.web.Application([
    (r"/index", MainHandler),
], **settings)
  
  
if __name__ == "__main__":
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()

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

  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框架们

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

  5. python各种web框架对比

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

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

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

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

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

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

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

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

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

  10. Python Flask Web 框架入门

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

随机推荐

  1. 实现个虚拟机只要几百行的 toy 版就够了

    关键是 指令 的 eval 并把 高层代码进行翻译. 典型的项目: 1.  java-compiler (C++) 2. 手把手教你构建 C 语言编译器(0)- 前言 | 三点水  (C)

  2. Vue – 基础学习(4):事件修饰符

    Vue – 基础学习(3):事件修饰符

  3. JDK安装—JAVA

    下载JDK 下载地址:http://www.oracle.com/technetwork/java/javase/downloads/index.html 关于版本选择需要注意的问题: 如果eclip ...

  4. Solr搜索器的特性及相关操作

    一.搜索处理器简介 所有的请求处理器都实现一个Java类,本例实现了solr.SearchHandler.在运行时,solr.SearchHandler被解析为内置的Solr类org.apache.s ...

  5. Cloud Alert 实现告警智能降噪,成功规避告警风暴

    # 前言 睿象云前段时间发表了一篇[< Zabbix 实现电话.邮件.微信告警通知的实践分享>](https://www.toutiao.com/i6734876723126469127/ ...

  6. Mysql 主从一致校验工具------Maatkit工具包

    Maatkit工具包 http://www.maatkit.org/ 简介 maatkit是一个开源的工具包,为mysql日常管理提供了帮助.目前,已被Percona公司收购并维护.其中: mk-ta ...

  7. MySQL Execution Plan--使用Query Rewrite

    安装Query Rewrite Plugin 在MySQL的安装目录的share文件夹下,有两个文件用来安装和卸载Query Rewrite Plugin: install_rewriter.sql: ...

  8. Odoo甘特图

    转载请注明原文地址:https://www.cnblogs.com/ygj0930/p/10826366.html 甘特图(注意:社区版不支持甘特图!所以不会显示) 水平条状的甘特图通常用于显示项目计 ...

  9. linux环境安装配置nginx

    安装依赖 yum install gcc yum install pcre-devel yum install zlib zlib-devel yum install openssl openssl- ...

  10. Mock Server之与被测系统对接(python+flask)

    第一步:获取入参与返回结果 先通过postman.jmeter.自己写脚本之类的方式请求我们的mock server,试着获取入参与对应的返回值,这里我用的是robotframework + Requ ...