原文地址:http://www.bjhee.com/flask-1.html
Contents
- 1 简介
- 2 目录结构
- 3 开始 Hello world
- 4 路由
- 5 HTTP方法
- 6 唯一 URL / 重定向行为
- 7 静态文件
- 8 Request 对象
- 9 全局对象g
- 10 构建响应
- 11 会话对象session
- 12 模板简介
- 13 错误处理
- 14 日志
- 15 消息闪现
- 16 请求上下文环境
- 17 应用上下文环境
- 18 信号
- 19 视图装饰器
- 20 URL集中映射
- 21 可插拔视图Pluggable View
- 22 文件和流
- 23 Flask Blueprint 蓝图
- 24 部署和分发
- 25 来源
简介
Flask是由python实现的一个web微框架,让我们可以使用Python语言快速实现一个网站或Web服务。而且有对应的python3及python2版本。我这里用的是python3
安装Flask
pip install flask
目录结构
通过别人的目录大致了解一下flask框架的目录结构。
flask-demo/
├ run.py # 应用启动程序
├ config.py # 环境配置
├ requirements.txt # 列出应用程序依赖的所有Python包
├ tests/ # 测试代码包
│ ├ __init__.py
│ └ test_*.py # 测试用例
└ myapp/
├ admin/ # 蓝图目录
├ static/
│ ├ css/ # css文件目录
│ ├ img/ # 图片文件目录
│ └ js/ # js文件目录
├ templates/ # 模板文件目录
├ __init__.py
├ forms.py # 存放所有表单,如果多,将其变为一个包
├ models.py # 存放所有数据模型,如果多,将其变为一个包
└ views.py # 存放所有视图函数,如果多,将其变为一个包
开始 Hello world
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return 'Hello World'
if __name__ == '__main__':
app.debug = True # 设置调试模式,生产模式的时候要关掉debug
app.run()
这是flask框架制作的一个最小的应用。使用python运行后访问localhost:5000就能看到网页显示Hello world。
这里首先引入了Flask类,然后给这个类创建了一个实例,__name__
代表这个模块的名字。因为这个模块是直接被运行的所以此时__name__
的值是__main__
。然后用route()这个修饰器定义了一个路由,告诉flask如何访问该函数。最后用run()函数使这个应用在服务器上运行起来。
路由
flask通过route()装饰器将一个函数绑定到对应的URL上了。
例如:
@app.route('/')
def index():
return 'Hello World!'
@app.route('/home')
def index():
return 'This is home page!'
我访问localhost:5000/home,就能在页面看到This is home page!。
flask支持在路由上制定参数及参数类型,通过<variable_name>
可以标记变量,这个部分将会作为命名参数传递到你的函数,也可以通过<converter:variable_name>
指定一个可选的装饰器:
@app.route('/user/<username>')
def show_user_profile(username):
return 'User %s' % username
@app.route('/post/<int:post_id>')
def show_post(post_id):
return 'Post %d' % post_id
这里访问localhost:5000/user/test,会看到User test。
访问localhost:5000/post/1,会看到Post 1,且必须为int型,因为这里限制了参数类型。
类型转换器 | 作用 |
---|---|
缺省 | 字符型,但不能有斜杠 |
int: | 整型 |
float: | 浮点型 |
path: | 字符型,可有斜杠 |
HTTP方法
如果需要处理具体的HTTP方法,在Flask中也很容易,使用route
装饰器的methods
参数设置即可。
from flask import request
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
do_the_login()
else:
show_the_login_form()
当你请求地址http://localhost:5000/login
,”GET”和”POST”请求会返回不同的内容,其他请求方法则会返回405错误。
唯一 URL / 重定向行为
Flask的URL规则是基于Werkzeug的路由模块。模块背后的思想是基于 Apache 以及更早的 HTTP 服务器主张的先例,保证优雅且唯一的 URL。
@app.route('/projects/')
def projects():
return 'The project page'
@app.route('/about')
def about():
return 'The about page'
访问第一个路由不带/时,Flask会自动重定向到正确地址。
访问第二个路由时末尾带上/后Flask会直接报404 NOT FOUND错误。
构造URL
Flask提供了url_for()
方法来快速获取及构建URL,方法的第一个参数指向函数名(加过@app.route
注解的函数),后续的参数对应于要构建的URL变量。
url_for('login') # 返回/login
url_for('login', id='1') # 将id作为URL参数,返回/login?id=1
url_for('hello', name='man') # 适配hello函数的name参数,返回/hello/man
url_for('static', filename='style.css') # 静态文件地址,返回/static/style.css
静态文件
Web程序中常常需要处理静态文件,在Flask中需要使用url_for
函数并指定static
端点名和文件名。在下面的例子中,实际的文件应放在static/
文件夹下。
url_for('static', filename='style.css')
Request 对象
from flask import request
@app.route('/login', methods=['POST', 'GET'])
def login():
if request.method == 'POST':
if request.form['user'] == 'admin':
return 'Admin login successfully!'
else:
return 'No such user!'
title = request.args.get('title', 'Default')
return render_template('login.html', title=title)
request.args.get()
方法则可以获取Get请求URL中的参数,该函数的第二个参数是默认值,当URL参数不存在时,则返回默认值
templates目录下,添加layout.html
文件
<!doctype html>
<title>Hello Sample</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='style.css') }}">
<div class="page">
{% block body %}
{% endblock %}
</div>
login.html
{% extends "layout.html" %}
{% block body %}
<form name="login" action="/login" method="post">
Hello {{ title }}, please login by:
<input type="text" name="user" />
</form>
{% endblock %}
全局对象g
flask.g
是Flask一个全局对象,g
的作用范围,就在一个请求(也就是一个线程)里,它不能在多个请求间共享。你可以在g
对象里保存任何你想保存的内容。
构建响应
我们可以先构建响应对象,设置一些参数(比如响应头)后,再将其返回。
from flask import request, session, make_response
@app.route('/login', methods=['POST', 'GET'])
def login():
if request.method == 'POST':
...
if 'user' in session:
...
else:
title = request.args.get('title', 'Default')
response = make_response(render_template('login.html', title=title), 200)
response.headers['key'] = 'value'
return response
make_response
方法就是用来构建response对象的,第二个参数代表响应状态码,缺省就是200。
会话对象session
会话可以用来保存当前请求的一些状态,以便于在请求之前共享信息。
登入
from flask import Flask,request,render_template,session
app=Flask(__name__)
@app.route('/login', methods=['POST', 'GET'])
def login():
if request.method == 'POST':
if request.form['user'] == 'admin':
session['user']=request.form['user']
return 'Admin login successfully!'
else:
return 'No such user!'
if 'user' in session:
return 'Hello %s!'%session['user']
else:
title = request.args.get('title', 'Default')
return render_template('login.html', title=title)
app.secret_key='123456'
if __name__=='__main__':
app.run()
使用session时一定要设置一个密钥app.secret_key
,密钥要尽量复杂,最好使用一个随机数。
登出
from flask import request, session, redirect, url_for
@app.route('/logout')
def logout():
session.pop('user', None)
return redirect(url_for('login'))
模板简介
模板生成
Flask默认使用Jinja2
作为模板,默认情况下,模板文件需要放在templates
文件夹下。
使用 Jinja 模板,只需要使用render_template
函数并传入模板文件名和参数名即可。
from flask import render_template
@app.route('/hello/')
@app.route('/hello/<name>')
def hello(name=None):
return render_template('hello.html', name=name)
hello()
函数并不是直接返回字符串,而是调用了render_template()
方法来渲染模板。方法的第一个参数hello.html
指向你想渲染的模板名称,第二个参数name
是你要传到模板去的变量,变量可以传多个。
相应的模板文件hello.html
如下。
<!Doctype html>
<title>Hello from Flask</title>
{% if name %}
<h1>Hello {{ name }}!</h1>
{% else %}
<h1>Hello, World!</h1>
{% endif %}
变量或表达式由{{ }}修饰,而控制语句由{% %}修饰,其他的代码,就是我们常见的HTML。
模板标签
代码块需要包含在{% %}块中
{% extends 'layout.html' %}
{% block title %}主页{% endblock %}
{% block body %}
<div class="jumbotron">
<h1>主页</h1>
</div>
{% endblock %}
双大括号中的内容不会被转义,所有内容都会原样输出,它常常和其他辅助函数一起使用。
<a class="navbar-brand" href={{ url_for('index') }}>Flask小例子</a>
继承
模板可以继承其他模板,我们可以将布局设置为父模板,让其他模板继承,这样可以非常方便的控制整个程序的外观。
例如这里有一个layout.html
模板,它是整个程序的布局文件。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}{% endblock %}</title>
<link rel="stylesheet" href="{{ url_for('static',filename='css/bootstrap.css') }}"/>
<link rel="stylesheet" href="{{ url_for('static',filename='css/bootstrap-theme.css') }}"/>
</head>
<body>
<div class="container body-content">
{% block body %}{% endblock %}
</div>
<div class="container footer">
<hr>
<p>这是页脚</p>
</div>
<script src="{{ url_for('static',filename='js/jquery.js') }}"></script>
<script src="{{ url_for('static',filename='js/bootstrap.js') }}"></script>
</body>
</html>
其他模板可以这么写。对比一下面向对象编程的继承概念:
{% extends 'layout.html' %}
{% block title %}主页{% endblock %}
{% block body %}
<div class="jumbotron">
<h1>主页</h1>
<p>本项目演示了Flask的简单使用方法,点击导航栏上的菜单条查看具体功能。</p>
</div>
{% endblock %}
控制流
条件判断可以这么写,类似于JSP标签中的Java 代码。
{%_%}中也可以写Python代码
<div class=metanav>
{% if not session.logged_in %}
<a href="{{ url_for('login') }}">log in</a>
{% else %}
<a href="{{ url_for('logout') }}">log out</a>
{% endif %}
</div>
循环,和在Python中遍历差不多。
<tbody>
{% for key,value in data.items() %}
<tr>
<td>{{ key }}</td>
<td>{{ value }}</td>
</tr>
{% endfor %}
<tr>
<td>文件</td>
<td></td>
</tr>
</tbody>
不是所有的Python代码都可以写在模板里,如果希望从模板中引用其他文件的函数,需要显式将函数注册到模板中。
错误处理
使用abort()
函数可以直接退出请求,返回错误代码:
from flask import abort
@app.route('/error')
def error():
abort(404)
上例会显示浏览器的404错误页面。我们可以在遇到特定错误代码时做些事情,或者重写错误页面
@app.errorhandler(404)
def page_not_found(error):
return render_template('404.html'), 404
此时,当再次遇到404错误时,即会调用page_not_found()
函数,其返回”404.html”的模板页。第二个参数代表错误代码。
在实际开发过程中,并不会经常使用abort()
来退出,常用的错误处理方法一般都是异常的抛出或捕获。装饰器@app.errorhandler()
除了可以注册错误代码外,还可以注册指定的异常类型。
class InvalidUsage(Exception):
status_code=400
def __init__ (self,message,status_code=400):
Exception.__init__(self)
self.message=message
self.status_code=status_code
@app.errorhandler(InvalidUsage)
def invalid_usage(error):
response=make_response(error.message)
response.status_code=error.status_code
return response
这里定义了一个异常InvalidUsage
,同时我们通过装饰器@app.errorhandler()
修饰了函数invalid_usage()
,装饰器中注册了我们刚定义的异常类。一但遇到InvalidUsage
异常被抛出,这个invalid_usage()
函数就会被调用
@app.route('/exception')
def exception():
raise InvalidUsage('No privilege to access the resource', status_code=403)
日志
Flask提供logger对象,其是一个标准的Python Logger类。
修改上例中的exception()
函数:
@app.route('/exception')
def exception():
app.logger.debug('Enter exception method')
app.logger.error('403 error happened')
raise InvalidUsage('No privilege to access the resource', status_code=403)
执行后,会在控制台看到日志信息。在debug模式下,日志会默认输出到标准错误stderr中。
可以添加FileHandler来使其输出到日志文件中去,也可以修改日志的记录格式。
简单的日志配置代码:
server_log = TimedRotatingFileHandler('server.log','D')
server_log.setLevel(logging.DEBUG)
server_log.setFormatter(logging.Formatter(
'%(asctime)s %(levelname)s: %(message)s'
))
error_log = TimedRotatingFileHandler('error.log', 'D')
error_log.setLevel(logging.ERROR)
error_log.setFormatter(logging.Formatter(
'%(asctime)s: %(message)s [in %(pathname)s:%(lineno)d]'
))
app.logger.addHandler(server_log)
app.logger.addHandler(error_log)
在本地目录下创建了两个日志文件,分别是server.log
记录所有级别日志;error.log
只记录错误日志。
分别给两个文件不同的内容格式。
使用TimedRotatingFileHandler
并给了参数D
,这样日志每天会创建一个新的文件,并将旧文件加日期后缀来归档。
消息闪现
Flask Message
一个操作完成后,在页面上闪出一个消息,告诉用户操作的结果。用户看完后,这个消息就不复存在了。
app.py
from flask import render_template, request, session, url_for, redirect, flash,Flask
app = Flask(__name__)
@app.route('/')
def index():
if 'user' in session:
return render_template('hello.html', name=session['user'])
else:
return redirect(url_for('login'), 302)
@app.route('/login', methods=['POST', 'GET'])
def login():
if request.method == 'POST':
session['user'] = request.form['user']
flash('Login successfully!!!!')
return redirect(url_for('index'))
else:
return '''
<form name="login" action="/login" method="post">
Username: <input type="text" name="user" />
</form>
'''
app.secret_key='123456'
if __name__=='__main__':
app.run()
app.secret_key
不赋值会报错。
hello.html
<!doctype html>
<title>Hello Sample</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='style.css') }}">
{% with messages = get_flashed_messages() %}
{% if messages %}
<ul class="flash">
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
<div class="page">
{% block body %}
{% endblock %}
</div>
get_flashed_messages()
函数会获取我们在login()
中通过flash()
闪出的消息。
再刷新文字就会消失。
flash()
方法的第二个参数是消息类型,可选择的有message
, info
, warning
, error
。可以在获取消息时,同时获取消息类型,还可以过滤特定的消息类型。
请求上下文环境
请求上下文的生命周期
上下文装饰器@app.before_request
,@app.teardown_request
,@app.after_request
,用其修饰的函数也可以称为上下文Hook函数。
被before_request
修饰的函数会在请求处理之前被调用,after_request
和teardown_request
会在请求处理完成后被调用。
区别是after_request
只会在请求正常退出时才会被调用,它必须传入一个参数来接受响应对象,并返回一个响应对象,一般用来统一修改响应的内容。而teardown_request
在任何情况下都会被调用,它必须传入一个参数来接受异常对象,一般用来统一释放请求所占有的资源。同一种类型的Hook函数可以存在多个,程序会按代码中的顺序执行。
from flask import Flask, g, request
app = Flask(__name__)
@app.before_request
def before_request():
print 'before request started'
print request.url
@app.before_request
def before_request2():
print 'before request started 2'
print request.url
g.name="SampleApp"
@app.after_request
def after_request(response):
print 'after request finished'
print request.url
response.headers['key'] = 'value'
return response
@app.teardown_request
def teardown_request(exception):
print 'teardown request'
print request.url
@app.route('/')
def index():
return 'Hello, %s!' % g.name
if __name__ == '__main__':
app.run(host='0.0.0.0', debug=True)
访问http://localhost:5000/
后,会在控制台输出:
如果一个before_request
函数中有返回response,则后面的before_request
以及该请求的处理函数将不再被执行。直接进入after_request
。
@app.before_request
def before_request():
print('before request started')
print(request.url)
return 'Hello'
......
request对象只有在请求上下文的生命周期内才可以访问
构建请求上下文环境
一个请求一般是由客户端发起的,Flask提供了在没有客户端的情况下实现自动测试,可通过test_request_context()
来模拟客户端请求。
from werkzeug.test import EnvironBuilder
ctx = app.request_context(EnvironBuilder('/','http://localhost/').get_environ())
ctx.push()
try:
print request.url
finally:
ctx.pop()
request_context()
会创建一个请求上下文RequestContext
类型的对象,其需接收werkzeug
中的environ
对象为参数。werkzeug
是Flask所依赖的WSGI函数库。
上面的代码可以用with语句来简化:
from werkzeug.test import EnvironBuilder
with app.request_context(EnvironBuilder('/','http://localhost/').get_environ()):
print request.url
请求上下文的实现方式
对于Flask Web应用来说,每个请求就是一个独立的线程。请求之间的信息要完全隔离,避免冲突,这就需要使用本地线程环境(ThreadLocal)。ctx.push()
方法,会将当前请求上下文,压入flask._request_ctx_stack
的栈中,这个_request_ctx_stack
是内部对象,我们在应用开发时最好不要使用它,一般在Flask扩展开发中才会使用。同时这个_request_ctx_stack
栈是个ThreadLocal对象。也就是flask._request_ctx_stack
看似全局对象,其实每个线程的都不一样。请求上下文压入栈后,再次访问其都会从这个栈的顶端通过_request_ctx_stack.top
来获取,所以取到的永远是只属于本线程中的对象,这样不同请求之间的上下文就做到了完全隔离。请求结束后,线程退出,ThreadLocal线程本地变量也随即销毁,ctx.pop()
用来将请求上下文从栈里弹出,避免内存无法回收。
应用上下文环境
current_app代理
from flask import Flask, current_app
app = Flask('SampleApp')
@app.route('/')
def index():
return 'Hello, %s!' % current_app.name
我们可以通过current_app.name
来获取当前应用的名称,也就是SampleApp
。current_app
是一个本地代理,它的类型是werkzeug.local. LocalProxy
,它所代理的即是我们的app对象,也就是说current_app == LocalProxy(app)
。使用current_app
是因为它也是一个ThreadLocal变量,对它的改动不会影响到其他线程。
既然是ThreadLocal对象,那它就只在请求线程内存在,它的生命周期就是在应用上下文里。离开了应用上下文,current_app
一样无法使用。
app = Flask('SampleApp')
print(current_app.name)
RuntimeError: working outside of application context
构建应用上下文环境
with app.app_context():
print(current_app.name)
app_context()
方法会创建一个AppContext
类型对象,即应用上下文对象,此后我们就可以在应用上下文中访问current_app
对象了。
应用上下文的实现方式
在请求线程创建时,Flask会创建应用上下文对象,并将其压入flask._app_ctx_stack
的栈中,然后在线程退出前将其从栈里弹出。这个_app_ctx_stack
同请求上下文中的_request_ctx_stack
一样,都是ThreadLocal变量。也就是说应用上下文的生命周期,也只在一个请求线程内,我们无法通过应用上下文在请求之间传递信息。
应用上下文Hook函数
应用上下文也提供了装饰器来修饰Hook函数,不过只有一个@app.teardown_appcontext
。它会在应用上下文生命周期结束前,也就是从_app_ctx_stack
出栈时被调用。
@app.teardown_appcontext
def teardown_db(exception):
print(teardown application)
信号
信号(Signal)就是两个独立的模块用来传递消息的方式,它有一个消息的发送者Sender,还有一个消息的订阅者Subscriber。信号的存在使得模块之间可以摆脱互相调用的模式,也就是解耦合。发送者无需知道谁会接收消息,接收者也可自由选择订阅何种消息。
订阅一个信号
from flask import template_rendered, request
def print_template_rendered(sender, template, context, **extra):
print 'Using template: %s with context: %s' % (template.name, context)
print request.url
template_rendered.connect(print_template_rendered, app)
访问http://localhost:5000/hello
会在控制台看到
Using template: hello.html with context: {'session': , 'request': , 'name': None, 'g': }
http://localhost:5000/hello
而访问http://localhost:5000/
时,却没有这些信息
flask.template_rendered
是Flask的核心信号。当任意一个模板被渲染成功后,这个信号就会被发出。
信号的connect()
方法用来连接订阅者,它的第一个参数就是订阅者回调函数,当信号发出时,这个回调函数就会被调用;第二个参数是指定消息的发送者,也就是指明只有app
作为发送者发出的template_rendered
消息才会被此订阅者接收。
如果不指定发送者,任何发送者发出的”template_rendered”都会被接收。
connect()
方法可以多次调用,来连接多个订阅者。
这个回调函数,它有四个参数,前三个参数是必须的。
* sender: 获取消息的发送者
* template: 被渲染的模板对象
* context: 当前请求的上下文环境
* **extra: 匹配任意额外的参数。如果上面三个存在,这个参数不加也没问题。但是如果你的参数少于三个,就一定要有这个参数。一般习惯上加上此参数。
我们在回调函数中,可以获取请求上下文,也就是它在一个请求的生命周期和线程内。所以,我们可以在函数中访问request对象。
Flask同时提供了信号装饰器来简化代码,上面的信号订阅也可以写成:
from flask import template_rendered, request
@template_rendered.connect_via(app)
def with_template_rendered(sender, template, context, **extra):
print 'Using template: %s with context: %s' % (template.name, context)
print request.url
Flask核心信号
| 信号 | 作用 | 回调函数参数|
| ------ | ------ |-----|
| request_started |请求开始时发送| 1、sender: 消息的发送者
| request_finished| 请求结束后发送 | 1、sender: 消息的发送者 2、response: 待返回的响应对象
|got_request_exception|请求发生异常时发送|1、sender: 消息的发送者 2、exception: 被抛出的异常对象
|request_tearing_down| 请求被销毁时发送,不管有无异常都会被发送|1、sender: 消息的发送者 2、exc: 有异常时,抛出的异常对象
|appcontext_tearing_down|应用上下文被销毁时发送|1、sender: 消息的发送者
|appcontext_pushed| 应用上下文被压入”_app_ctx_stack”栈后发送|1、sender: 消息的发送者
|appcontext_popped|应用上下文从”_app_ctx_stack”栈中弹出后发送|1、sender: 消息的发送者
|message_flashed|消息闪现时发送|1、sender: 消息的发送者 2、message: 被闪现的消息内容 3、category: 被闪现的消息类别
注,所有回调函数都建议加上**extra
作为最后的参数
自定义信号
from blinker import Namespace
signals = Namespace()
index_called = signals.signal('index-called')
我们在全局定义了一个index_called
信号对象,表示根路径被访问了。然后我们在根路径的请求处理中发出这个信号:
@app.route('/')
def index():
index_called.send(current_app._get_current_object(), method=request.method)
return 'Hello Flask!'
发送信号消息的方法是send()
,它必须包含一个参数指向信号的发送者。current_app._get_current_object()
用来获取应用上下文中的app应用对象。这样每次客户端访问根路径时,都会发送index_called
信号。
视图装饰器
当用户访问admin页面时,必须先登录。可以在admin()
方法里判断会话session。这样的确可以达成目的。不过当我们有n多页面都要进行用户验证的话,判断用户登录的代码就会到处都是。即便我们封装在一个函数里,至少调此函数的代码也会重复出现。Flask没有提供特别的功能来实现这个,因为Python本身有,那就是装饰器。
from functools import wraps
from flask import session, abort
def login_required(func):
@wraps(func)
def decorated_function(*args, **kwargs):
if not 'user' in session:
abort(401)
return func(*args, **kwargs)
return decorated_function
app.secret_key = '12345678'
在调用函数前,先检查session里有没有用户。此后,我们只需将此装饰器加在每个需要验证登录的请求方法上即可:
@app.route('/admin')
@login_required
def admin():
return '<h1>Admin Dashboard</h1>'
这个装饰器就被称为视图装饰器(View Decorator)。
我们也可以把页面的路径作为键,页面内容作为值,放在缓存里。每次进入请求函数前,先判断缓存里有没有该页面,有就直接将缓存里的值返回,没有则执行请求函数,将结果存在缓存后再返回。
URL集中映射
Flask也支持像Django一样,把URL路由规则统一管理,而不是写在视图函数上。
views.py
def foo():
return '<h1>Hello Foo!</h1>'
然后在Flask主程序上调用app.add_url_rule
方法:
app.add_url_rule('/foo', view_func=views.foo)
这样,路由/foo
就绑定在views.foo()
函数上了,效果等同于在views.foo()
函数上加上@app.route(‘/foo’)
装饰器。通过app.add_url_rule
方法,我们就可以将路由同视图分开,将路由统一管理,实现完全的MVC。
定义的装饰器本质上是一个闭包函数,所以我们当然可以把它当函数使用:
app.add_url_rule('/foo', view_func=login_required(views.foo))
可插拔视图Pluggable View
视图类
URL集中映射,就是视图可插拔的基础,因为它可以支持在程序中动态的绑定路由和视图。Flask提供了视图类,使其可以变得更灵活
from flask.views import View
class HelloView(View):
def dispatch_request(self, name=None):
return render_template('hello-view.html', name=name)
view = HelloView.as_view('helloview')
app.add_url_rule('/helloview', view_func=view)
app.add_url_rule('/helloview/<name>', view_func=view)
创建了一个flask.views.View
的子类,并覆盖了其dispatch_request()
数,渲染视图的主要代码必须写在这个函数里。然后我们通过as_view()
方法把类转换为实际的视图函数,as_view()
必须传入一个唯一的视图名。此后,这个视图就可以由app.add_url_rule
方法绑定到路由上了。
例如
class RenderTemplateView(View):
def __init__(self, template):
self.template = template
def dispatch_request(self):
return render_template(self.template)
app.add_url_rule('/hello', view_func=RenderTemplateView.as_view('hello', template='hello-view.html'))
app.add_url_rule('/login', view_func=RenderTemplateView.as_view('login', template='login-view.html'))
视图装饰器支持
在使用视图类的情况下,使用视图装饰器
class HelloView(View):
decorators = [login_required]
def dispatch_request(self, name=None):
return render_template('hello-view.html', name=name)
只需将装饰器函数加入到视图类变量decorators
中即可,它是一个列表,所以能够支持多个装饰器,并按列表中的顺序执行。
请求方法的支持
class MyMethodView(View):
methods = ['GET', 'POST']
def dispatch_request(self):
if request.method == 'GET':
return '<h1>Hello World!</h1>This is GET method.'
elif request.method == 'POST':
return '<h1>Hello World!</h1>This is POST method.'
app.add_url_rule('/mmview', view_func=MyMethodView.as_view('mmview'))
只需将需要支持的HTTP请求方法加入到视图类变量methods
中即可。没加的话,默认只支持GET请求。
基于方法的视图
from flask.views import MethodView
class UserAPI(MethodView):
def get(self, user_id):
if user_id is None:
return 'Get User called, return all users'
else:
return 'Get User called with id %s' % user_id
def post(self):
return 'Post User called'
def put(self, user_id):
return 'Put User called with id %s' % user_id
def delete(self, user_id):
return 'Delete User called with id %s' % user_id
分别定义get, post, put, delete方法来对应四种类型的HTTP请求,注意函数名必须这么写。
绑定到路由
user_view = UserAPI.as_view('users')
# 将GET /users/请求绑定到UserAPI.get()方法上,并将get()方法参数user_id默认为None
app.add_url_rule('/users/', view_func=user_view,
defaults={'user_id': None},
methods=['GET',])
# 将POST /users/请求绑定到UserAPI.post()方法上
app.add_url_rule('/users/', view_func=user_view,
methods=['POST',])
# 将/users/<user_id>URL路径的GET,PUT,DELETE请求,
# 绑定到UserAPI的get(), put(), delete()方法上,并将参数user_id传入。
app.add_url_rule('/users/<user_id>', view_func=user_view,
methods=['GET', 'PUT', 'DELETE'])
app.add_url_rule()
可以传入参数default,来设置默认值;参数methods
,来指定支持的请求方法。
如果API多,可以将其封装成函数:
def register_api(view, endpoint, url, primary_id='id', id_type='int'):
view_func = view.as_view(endpoint)
app.add_url_rule(url, view_func=view_func,
defaults={primary_id: None},
methods=['GET',])
app.add_url_rule(url, view_func=view_func,
methods=['POST',])
app.add_url_rule('%s<%s:%s>' % (url, id_type, primary_id),
view_func=view_func,
methods=['GET', 'PUT', 'DELETE'])
register_api(UserAPI, 'users', '/users/', primary_id='user_id')
一个register_api()
就可以绑定一个API了
文件和流
当我们要往客户端发送大量的数据,比如一个大文件时,将它保存在内存中再一次性发到客户端开销很大。比较好的方式是使用流。
响应流的生成
Flask响应流的实现原理就是通过Python的生成器,也就是yield的表达式,将yield的内容直接发送到客户端。
from flask import Flask, Response
app = Flask(__name__)
@app.route('/large.csv')
def generate_large_csv():
def generate():
for row in range(50000):
line = []
for col in range(500):
line.append(str(col))
if row % 1000 == 0:
print 'row: %d' % row
yield ','.join(line) + '\n'
return Response(generate(), mimetype='text/csv')
这段代码会生成一个5万行100M的csv文件,每一行会通过yield表达式分别发送给客户端。运行时你会发现文件行的生成与浏览器文件的下载是同时进行的,而不是文件全部生成完毕后再开始下载。
这里我们用到了响应类flask.Response
,它是werkzeug.wrappers.Response
类的一个包装,它的初始化方法第一个参数就是我们定义的生成器函数,第二个参数指定了响应类型。
我们将上述方法应用到模板中,如果模板的内容很大,我们要自己写个流式渲染模板的方法。`
# 流式渲染模板
def stream_template(template_name, **context):
# 将app中的请求上下文内容更新至传入的上下文对象context,
# 这样确保请求上下文会传入即将被渲染的模板中
app.update_template_context(context)
# 获取Jinja2的模板对象
template = app.jinja_env.get_template(template_name)
# 获取流式渲染模板的生成器
generator = template.stream(context)
# 启用缓存,这样不会每一条都发送,而是缓存满了再发送
generator.enable_buffering(5)
return generator
现在我们就可以在视图方法中,采用stream_template()
,而不是以前介绍的render_template()
来渲染模板了:
@app.route('/stream.html')
def render_large_template():
file = open('server.log')
return Response(stream_template('stream-view.html',
logs=file.readlines()))
上例的代码会将本地的server.log
日志文件内容传入模板,并以流的方式渲染在页面上。
另外注意,在生成器中是无法访问请求上下文的。不过Flask从版本0.9开始提供了”stream_with_context()”方法,它允许生成器在运行期间获取请求上下文:
from flask import request, stream_with_context
@app.route('/method')
def streamed_response():
def generate():
yield 'Request method is: '
yield request.method
yield '.'
return Response(stream_with_context(generate()))
因为我们初始化Response对象时调用了stream_with_context()
方法,所以才能在yield表达式中访问request对象。
文件上传
首先建立一个让用户上传文件的页面,我们将其放在模板upload.html
中
<!DOCTYPE html>
<title>Upload File</title>
<h1>Upload new File</h1>
<form action="" method="post" enctype="multipart/form-data">
<p><input type="file" name="file">
<input type="submit" value="Upload">
</form>
定义一个文件合法性检查函数
# 设置允许上传的文件类型
ALLOWED_EXTENSIONS = set(['txt', 'pdf', 'png', 'jpg'])
# 检查文件类型是否合法
def allowed_file(filename):
# 判断文件的扩展名是否在配置项ALLOWED_EXTENSIONS中
return '.' in filename and \
filename.rsplit('.', 1)[1] in ALLOWED_EXTENSIONS
这样避免用户上传脚本文件如js或php文件,来干坏事!
文件提交后,在POST请求的视图函数中,通过request.files获取文件对象
这个request.files是一个字典,字典的键值就是之前模板中文件选择框的”name”属性的值,上例中是”file”;键值所对应的内容就是上传过来的文件对象。
检查文件对象的合法性后,通过文件对象的save()方法将文件保存在本地
import os
from flask import flask, render_template
from werkzeug import secure_filename
app = Flask(__name__)
# 设置请求内容的大小限制,即限制了上传文件的大小
app.config['MAX_CONTENT_LENGTH'] = 5 * 1024 * 1024
# 设置上传文件存放的目录
UPLOAD_FOLDER = './uploads'
@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
# 获取上传过来的文件对象
file = request.files['file']
# 检查文件对象是否存在,且文件名合法
if file and allowed_file(file.filename):
# 去除文件名中不合法的内容
filename = secure_filename(file.filename)
# 将文件保存在本地UPLOAD_FOLDER目录下
file.save(os.path.join(UPLOAD_FOLDER, filename))
return 'Upload Successfully'
else: # 文件不合法
return 'Upload Failed'
else: # GET方法
return render_template('upload.html')
Flask的MAX_CONTENT_LENGTH配置项可以限制请求内容的大小默认是没有限制,上例中我们设为5M。
必须调用werkzeug.secure_filename()
来使文件名安全比如用户上传的文件名为../../../../home/username/.bashrc
,secure_filename()
方法可以将其转为home_username_.bashrc
。还是用来避免用户干坏事!
Flask处理文件上传的方式如果上传的文件很小,那么会把它直接存在内存中。否则就会把它保存到一个临时目录下,通过tempfile.gettempdir()
方法可以获取这个临时目录的位置。
另外Flask从0.5版本开始提供了一个简便的方法来让用户获取已上传的文件:
from flask import send_from_directory
@app.route('/uploads/<filename>')
def uploaded_file(filename):
return send_from_directory(UPLOAD_FOLDER, filename)
这个帮助方法send_from_directory()
可以安全地将文件发送给客户端,它还可以接受一个参数mimetype
来指定文件类型,和参数as_attachment=True
来添加响应头Content-Disposition: attachment
。
Flask Blueprint 蓝图
我们的应用经常会区分用户站点和管理员后台,两者虽然都在同一个应用中,但是风格迥异。把它们分成两个应用的话,总有些代码我们想重用;放在一起,耦合度太高,代码不便于管理。所以Flask提供了蓝图(Blueprint)功能。蓝图使用起来就像应用当中的子应用一样,可以有自己的模板,静态目录,有自己的视图函数和URL规则,蓝图之间互相不影响。但是它们又属于应用中,可以共享应用的配置。对于大型应用来说,我们可以通过添加蓝图来扩展应用功能,而不至于影响原来的程序。不过有一点要注意,目前Flask蓝图的注册是静态的,不支持可插拔。
创建一个蓝图
比较好的习惯是将蓝图放在一个单独的包里,所以让我们先创建一个”admin”子目录,并创建一个空的__init__.py
表示它是一个Python的包。现在我们来编写蓝图,将其存在”admin/admin_module.py”文件里:
from flask import Blueprint
admin_bp = Blueprint('admin', __name__)
@admin_bp.route('/')
def index(name):
return '<h1>Hello, this is admin blueprint</h1>'
初始化Blueprint对象的第一个参数’admin’指定了这个蓝图的名称,第二个参数指定了该蓝图所在的模块名。
接下来,我们在应用中注册该蓝图。在Flask应用主程序中,使用app.register_blueprint()
方法即可:
from flask import Flask
from admin.admin_module import admin_bp
app = Flask(__name__)
app.register_blueprint(admin_bp, url_prefix='/admin')
if __name__ == '__main__':
app.run(host='0.0.0.0', debug=True)
app.register_blueprint()
方法的”url_prefix”指定了这个蓝图的URL前缀。现在,访问http://localhost:5000/admin/
就可以加载蓝图的index视图了。
也可以在创建蓝图对象时指定其URL前缀:
admin_bp = Blueprint('admin', __name__, url_prefix='/admin')
这样注册时就无需指定:
app.register_blueprint(admin_bp)
蓝图资源
蓝图有自己的目录,它的所有资源都在其目录下。蓝图的资源目录是由创建Blueprint对象时传入的模块名__name__
所在的位置决定的。
同时,我们可以指定蓝图自己的模板目录和静态目录。比如我们创建蓝图时传入
admin_bp = Blueprint('admin', __name__,
template_folder='templates',
static_folder='static')
这样,该蓝图的模板目录就在admin/templates
下,而静态目录就在admin/static
下。默认值就是这两个位置,不指定也没关系。
构建URL
from flask import url_for
url_for('admin.index') # return /admin/
url_for('admin.static', filename='style.css') # return /admin/static/style.css
如果,url_for()
函数的调用就在本蓝图下,蓝图名可以省略,但必须留下.
表示当前蓝图:
url_for('.index')
url_for('.static', filename='style.css')
部署和分发
到目前为止,我们启动Flask应用都是通过”app.run()”方法,在开发环境中,这样固然可行,不过到了生产环境上,势必需要采用一个健壮的,功能强大的Web应用服务器来处理各种复杂情形。同时,由于开发过程中,应用变化频繁,手动将每次改动部署到生产环境上很是繁琐,最好有一个自动化的工具来简化持续集成的工作。接下来讲如何将Flask的应用程序自动打包,分发,并部署到像Apache, Nginx等服务器中去。
使用setuptools打包Flask应用
作为Python标准的打包及分发工具,setuptools可以说相当地简单易用。它会随着Python一起安装在你的机器上。你只需写一个简短的setup.py安装文件,就可以将你的Python应用打包。
第一个安装文件
假设我们的项目名为setup-demo,包名为myapp,目录结构如下:
setup-demo/
├ setup.py # 安装文件
└ myapp/ # 源代码
├ __init__.py
...
一个最基本的setup.py文件如下:
#coding:utf8
from setuptools import setup
setup(
name='MyApp', # 应用名
version='1.0', # 版本号
packages=['myapp'] # 包括在安装包内的Python包
)
执行安装文件
有了上面的setup.py文件,我们就可以打各种包,也可以将应用安装在本地Python环境中。
创建egg包
$ python setup.py bdist_egg
该命令会在当前目录下的”dist”目录内创建一个egg文件,名为”MyApp-1.0-py2.7.egg”。文件名格式就是”应用名-版本号-Python版本.egg”,我本地Python版本是2.7。同时你会注意到,当前目录多了”build”和”MyApp.egg-info”子目录来存放打包的中间结果。
创建tar.gz包
$ python setup.py sdist --formats=gztar
同上例类似,只不过创建的文件类型是tar.gz,文件名为”MyApp-1.0.tar.gz”。
安装应用
$ python setup.py install
该命令会将当前的Python应用安装到当前Python环境的”site-packages”目录下,这样其他程序就可以像导入标准库一样导入该应用的代码了。
开发方式安装
$ python setup.py develop
如果应用在开发过程中会频繁变更,每次安装还需要先将原来的版本卸掉,很麻烦。使用”develop”开发方式安装的话,应用代码不会真的被拷贝到本地Python环境的”site-packages”目录下,而是在”site-packages”目录里创建一个指向当前应用位置的链接。这样如果当前位置的源码被改动,就会马上反映到”site-packages”里。
引入非Python文件
上例中,我们只会将”myapp”包下的源码打包,如果我们还想将其他非Python文件也打包,比如静态文件(JS,CSS,图片)。
这时我们要在项目目录下添加一个”MANIFEST.in”文件夹。假设我们把所有静态文件都放在”static”子目录下,现在的项目结构如下:
setup-demo/
├ setup.py # 安装文件
├ MANIFEST.in # 清单文件
└ myapp/ # 源代码
├ static/ # 静态文件目录
├ __init__.py
...
我们在清单文件”MANIFEST.in”中,列出想要在包内引入的目录路径:
recursive-include myapp/static *
recursive-include myapp/xxx *
“recursive-include”表明包含子目录。还要在”setup.py”中将” include_package_data”参数设为True:
#coding:utf8
from setuptools import setup
setup(
name='MyApp', # 应用名
version='1.0', # 版本号
packages=['myapp'], # 包括在安装包内的Python包
include_package_data=True # 启用清单文件MANIFEST.in
)
之后再次打包或者安装,”myapp/static”目录下的所有文件都会被包含在内。如果你想排除一部分文件,可以在setup.py中使用”exclude_package_date”参数,比如:
setup(
...
include_package_data=True, # 启用清单文件MANIFEST.in
exclude_package_date={'':['.gitignore']}
)
上面的代码会将所有”.gitignore”文件排除在包外。如果上述”exclude_package_date”对象属性不为空,比如”{‘myapp’:[‘.gitignore’]}”,就表明只排除”myapp”包下的所有”.gitignore”文件。
自动安装依赖
我们的应用会依赖于第三方的Python包,虽然可以在说明文件中要求用户提前安装依赖包,但毕竟很麻烦,用户还有可能装错版本。其实我们可以在setup.py文件中指定依赖包,然后在使用setuptools安装应用时,依赖包的相应版本就会被自动安装。让我们来修改上例中的setup.py文件,加入”install_requires”参数:
#coding:utf8
from setuptools import setup
setup(
name='MyApp', # 应用名
version='1.0', # 版本号
packages=['myapp'], # 包括在安装包内的Python包
include_package_data=True, # 启用清单文件MANIFEST.in
exclude_package_date={'':['.gitignore']},
install_requires=[ # 依赖列表
'Flask>=0.10',
'Flask-SQLAlchemy>=1.5,<=2.1'
]
)
setuptools会先检查本地有没有符合要求的依赖包,如果没有的话,就会从PyPI中获得一个符合条件的最新的包安装到本地。
如果应用依赖的包无法从PyPI中获取,我们需要指定其下载路径:
setup(
...
install_requires=[ # 依赖列表
'Flask>=0.10',
'Flask-SQLAlchemy>=1.5,<=2.1'
],
dependency_links=[ # 依赖包下载路径
'http://example.com/dependency.tar.gz'
]
)
路径应指向一个egg包或tar.gz包,也可以是个包含下载地址(一个egg包或tar.gz包)的页面。
自动搜索Python包
之前我们在setup.py中指定了”packages=[‘myapp’]”,说明将Python包”myapp”下的源码打包。如果我们的应用很大,Python包很多。setuptools提供了”find_packages()”方法来自动搜索可以引入的Python包:
#coding:utf8
from setuptools import setup, find_packages
setup(
name='MyApp', # 应用名
version='1.0', # 版本号
packages=find_packages(), # 包括在安装包内的Python包
include_package_data=True, # 启用清单文件MANIFEST.in
exclude_package_date={'':['.gitignore']},
install_requires=[ # 依赖列表
'Flask>=0.10',
'Flask-SQLAlchemy>=1.5,<=2.1'
]
)
这样当前项目内所有的Python包都会自动被搜索到并引入到打好的包内。”find_packages()”方法可以限定你要搜索的路径,比如使用”find_packages(‘src’)”就表明只在”src”子目录下搜索所有的Python包。
使用setuptools打包Flask应用
setup.py
文件
from setuptools import setup
setup(
name='MyApp',
version='1.0',
long_description=__doc__,
packages=['myapp','myapp.main','myapp.admin'],
include_package_data=True,
zip_safe=False,
install_requires=[
'Flask>=0.10',
'Flask-Mail>=0.9',
'Flask-SQLAlchemy>=2.1'
]
)
把文件放在项目的根目录下,还要写一个MANIFEST.in
文件:
recursive-include myapp/templates *
recursive-include myapp/static *
编写完毕后,你可以创建一个干净的虚拟环境,然后运行安装命令
$ python setup.py install
使用Fabric远程部署Flask应用
Fabric是一个Python的库,它提供了丰富的同SSH交互的接口,可以用来在本地或远程机器上自动化、流水化地执行Shell命令。因此它非常适合用来做应用的远程部署及系统维护。
安装Fabric
首先Python的版本必须是2.7以上,可以通过下面的命令查看当前Python的版本:
$ python -V
Fabric的官网是www.fabfile.org,源码托管在Github上。你可以clone源码到本地,然后通过下面的命令来安装。
$ python setup.py develop
在执行源码安装前,你必须先将Fabric的依赖包Paramiko装上。推荐使用pip安装,只需一条命令即可:
$ pip install fabric
第一个例子
我们创建一个”fabfile.py”文件,然后写个hello函数:
def hello():
print "Hello Fabric!"
现在,让我们在”fabfile.py”的目录下执行命令:
$ fab hello
你可以在终端看到”Hello Fabric!”字样。
fabfile.py
文件中每个函数就是一个任务,任务名即函数名,上例中是hello
。fab
命令就是用来执行fabfile.py
中定义的任务,它必须显式地指定任务名。你可以使用参数-l
来列出当前fabfile.py
文件中定义了哪些任务:
$ fab -l
任务可以带参数,比如我们将hello函数改为:
def hello(name, value):
print "Hello Fabric! %s=%s" % (name,value)
此时执行hello任务时,就要传入参数值:
$ fab hello:name=Year,value=2016
Fabric的脚本建议写在fabfile.py
文件中,如果你想换文件名,那就要在”fab”命令中用”-f”指定。比如我们将脚本放在script.py
中,就要执行:
$ fab -f script.py hello
执行本地命令
fabric.api
包里的local()
方法可以用来执行本地Shell命令,比如让我们列出本地/home/bjhee
目录下的所有文件及目录:
from fabric.api import local
def hello():
local('ls -l /home/bjhee/')
local()
方法有一个capture
参数用来捕获标准输出,比如:
def hello():
output = local('echo Hello', capture=True)
这样,Hello字样不会输出到屏幕上,而是保存在变量output里。capture
参数的默认值是False。
执行远程命令
Fabric真正强大之处不是在执行本地命令,而是可以方便的执行远程机器上的Shell命令。它通过SSH实现,你需要的是在脚本中配置远程机器地址及登录信息:
from fabric.api import run, env
env.hosts = ['example1.com', 'example2.com']
env.user = 'bjhee'
env.password = '111111'
def hello():
run('ls -l /home/bjhee/')
fabric.api
包里的run()
方法可以用来执行远程Shell命令。上面的任务会分别到两台服务器example1.com
和example2.com
上执行ls -l /home/bjhee/
命令。这里假设两台服务器的用户名都是bjhee
,密码都是6个1。你也可以把用户直接写在hosts里,比如:
env.hosts = ['bjhee@example1.com', 'bjhee@example2.com']
如果你的env.hosts
里没有配置某个服务器,但是你又想在这个服务器上执行任务,你可以在命令行中通过-H
指定远程服务器地址,多个服务器地址用逗号分隔:
$ fab -H bjhee@example3.com,bjhee@example4.com hello
另外,多台机器的任务是串行执行的。
如果对于不同的服务器,我们想执行不同的任务,上面的方法似乎做不到,4我们要对服务器定义角色:
from fabric.api import env, roles, run, execute, cd
env.roledefs = {
'staging': ['bjhee@example1.com','bjhee@example2.com'],
'build': ['build@example3.com']
}
env.passwords = {
'staging': '11111',
'build': '123456'
}
@roles('build')
def build():
with cd('/home/build/myapp/'):
run('git pull')
run('python setup.py')
@roles('staging')
def deploy():
run('tar xfz /tmp/myapp.tar.gz')
run('cp /tmp/myapp /home/bjhee/www/')
def task():
execute(build)
execute(deploy)
执行
$ fab task
这时Fabric会先在一台build服务器上执行build任务,然后在两台staging服务器上分别执行deploy任务。@roles
装饰器指定了它所装饰的任务会被哪个角色的服务器执行。
如果某一任务上没有指定某个角色,但是你又想让这个角色的服务器也能运行该任务,你可以通过-R
来指定角色名,多个角色用逗号分隔:
$ fab -R build deploy
这样build
和staging
角色的服务器都会运行deploy
任务了。注:staging
是装饰器默认的,因此不用通过-R
指定。
此外,上面的例子中,服务器的登录密码都是明文写在脚本里的。这样做不安全,推荐的方式是设置SSH自动登录。
其余操作可以去http://www.bjhee.com/fabric.html查看
使用Fabric远程部署Flask应用
编写fabfile.py
文件:
from fabric.api import *
env.hosts = ['example1.com', 'example2.com']
env.user = 'bjhee'
def package():
local('python setup.py sdist --formats=gztar', capture=False)
def deploy():
dist = local('python setup.py --fullname', capture=True).strip()
put('dist/%s.tar.gz' % dist, '/tmp/myapp.tar.gz')
run('mkdir /tmp/myapp')
with cd('/tmp/myapp'):
run('tar xzf /tmp/myapp.tar.gz')
run('/home/bjhee/virtualenv/bin/python setup.py install')
run('rm -rf /tmp/myapp /tmp/myapp.tar.gz')
run('touch /var/www/myapp.wsgi')
上例中,package
任务是用来将应用程序打包,而deploy”
任务是用来将Python包安装到远程服务器的虚拟环境中,这里假设虚拟环境在/home/bjhee/virtualenv
下。安装完后,我们将/var/www/myapp.wsgi
文件的修改时间更新,以通知WSGI服务器(如Apache)重新加载它。对于非WSGI服务器,比如uWSGI,这条语句可以省去。
编写完后,运行部署脚本测试下:
$ fab package deploy
使用Apache+mod_wsgi运行Flask应用
Flask应用是基于WSGI规范的,所以它可以运行在任何一个支持WSGI协议的Web应用服务器中,最常用的就是Apache+mod_wsgi的方式。上面的Fabric脚本已经完成了将Flask应用部署到远程服务器上,接下来要做的就是编写WSGI的入口文件”myapp.wsgi”,我们假设将其放在Apache的文档根目录在”/var/www”下。
activate_this = '/home/bjhee/virtualenv/bin/activate_this.py'
execfile(activate_this, dict(__file__=activate_this))
import os
os.environ['PYTHON_EGG_CACHE'] = '/home/bjhee/.python-eggs'
import sys;
sys.path.append("/var/www")
from myapp import create_app
import config
application = create_app('config')
你需要预先创建配置文件”config.py”,并将其放在远程服务器的Python模块导入路径中。上例中,我们将”/var/www”加入到了Python的模块导入路径,因此可以将”config.py”放在其中。另外,记得用setuptools打包时不能包括”config.py”,以免在部署过程中将开发环境中的配置覆盖了生产环境。
在Apache的”httpd.conf”中加上脚本更新自动重载和URL路径映射:
WSGIScriptReloading On
WSGIScriptAlias /myapp /var/www/myapp.wsgi
重启Apache服务器后,就可以通过”http://example1.com/myapp”来访问应用了。
使用Nginx+uWSGI运行Flask应用
需要先准备好Nginx+uWSGI的环境
uWSGI是一个Web应用服务器,它具有应用服务器,代理,进程管理及应用监控等功能。它支持WSGI协议,同时它也支持自有的uWSGI协议,该协议据说性能非常高,而且内存占用率低,为mod_wsgi的一半左右,我没有实测过。它还支持多应用的管理及应用的性能监控。虽然uWSGI本身就可以直接用来当Web服务器,但一般建议将其作为应用服务器配合Nginx一起使用,这样可以更好的发挥Nginx在Web端的强大功能。
安装uWSGI
$ pip install uwsgi
查看当前的uwsgi的版本:
$ uwsgi --version
让我们来写个Hello World的WSGI应用,并保存在”server.py”文件中:
def application(environ, start_response):
status = '200 OK'
output = 'Hello World!'
response_headers = [('Content-type', 'text/plain'),
('Content-Length', str(len(output)))]
start_response(status, response_headers)
return [output]
在uWSGI中运行它,执行命令:
$ uwsgi --http :9090 --wsgi-file server.py
然后打开浏览器,访问”http://localhost:9090″,你就可以看到”Hello World!”字样了。
上面的命令中”- -http”参数指定了HTTP监听地址和端口,”- -wsgi-file”参数指定了WSGI应用程序入口,uWSGI会自动搜寻名为”application”的应用对象并调用它。
更进一步,uWSGI可以支持多进程和多线程的方式启动应用,也可以监控应用的运行状态。我们将启动的命令改为:
$ uwsgi --http :9090 --wsgi-file server.py --master --processes 4 --threads 2 --stats 127.0.0.1:9191
执行它后,uWSGI将启动4个应用进程,每个进程有2个线程,和一个master主进程(监控其他进程状态,如果有进程死了,则重启)。同时,你可以访问”127.0.0.1:9191″来获取JSON格式的应用运行信息,uWSGI还提供了工具命令”uwsgitop”来像top一样监控应用运行状态,你可以用pip来安装它。
我们可以将参数写在配置文件里,启动uWSGI时指定配置文件即可。配置文件可以是键值对的格式,也可以是XML,YAML格式,这里我们使用键值对的格式。让我们创建一个配置文件”myapp.ini”:
[uwsgi]
http=:9090
wsgi-file=server.py
master=true
processes=4
threads=2
stats=127.0.0.1:9191
然后就可以将启动命令简化为:
$ uwsgi myapp.ini
配置Nginx
首先,我们将uWSGI的HTTP端口监听改为socket端口监听,即将配置文件中的”http”项去掉,改为”socket”项:
[uwsgi]
socket=127.0.0.1:3031
wsgi-file=server.py
master=true
processes=4
threads=2
stats=127.0.0.1:9191
然后,打开Nginx的配置文件,Ubuntu上默认是”/etc/nginx/sites-enabled/default”文件,将其中的根路径部分配置为:
location / {
include uwsgi_params;
uwsgi_pass 127.0.0.1:3031;
}
这段配置表明Nginx会将收到的所有请求都转发到”127.0.0.1:3031″端口上,即uWSGI服务器上。现在让我们重启Nginx,并启动uWSGI服务器:
$ sudo service nginx restart
$ uwsgi myapp.ini
访问”http://localhost”,我们会再次看到”Hello World!”。
部署多个应用
一个Nginx中,可以同时运行多个应用,不一定是Python的应用。我们期望通过不同的路径来路由不同的应用,因此就不能像上例那样直接修改根目录的配置。假设我们希望通过”http://localhost/myapp”来访问我们的应用,首先要在Nginx的配置文件中,加入下面的内容:
location /myapp {
include uwsgi_params;
uwsgi_param SCRIPT_NAME /myapp;
uwsgi_pass 127.0.0.1:3031;
}
这里我们定义了一个uWSGI参数”SCRIPT_NAME”,值为应用的路径”/myapp”。接下来,在uWSGI的启动配置中,去掉”wsgi-file”项,并加上:
[uwsgi]
...
mount=/myapp=server.py
manage-script-name=true
“mount”参数表示将”/myapp”地址路由到”server.py”中,”manage-script-name”参数表示启用之前在Nginx里配置的”SCRIPT_NAME”参数。再次重启Nginx和uWSGI,你就可以通过”http://localhost/myapp”来访问应用了。
使用Nginx+uWSGI运行Flask应用
编写uWSGI的启动文件”myapp.ini”:
[uwsgi]
socket=127.0.0.1:3031
callable=app
mount=/myapp=run.py
manage-script-name=true
master=true
processes=4
threads=2
stats=127.0.0.1:9191
virtualenv=/home/bjhee/virtualenv
再修改Nginx的配置文件,Linux上默认是”/etc/nginx/sites-enabled/default”,加上目录配置:
location /myapp {
include uwsgi_params;
uwsgi_param SCRIPT_NAME /myapp;
uwsgi_pass 127.0.0.1:3031;
}
重启Nginx和uWSGI后,就可以通过”http://example1.com/myapp”来访问应用了。
使用Tornado运行Flask应用
Tornado的强大之处在于它是非阻塞式异步IO及Epoll模型,采用Tornado的可以支持数以万计的并发连接,对于高并发的应用有着很好的性能。本文不会展开Tornado的介绍,感兴趣的朋友们可以参阅其官方文档。使用Tornado来运行Flask应用很简单,只要编写下面的运行程序,并执行它即可:
from tornado.wsgi import WSGIContainer
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
from myapp import create_app
import config
app = create_app('config')
http_server = HTTPServer(WSGIContainer(app))
http_server.listen(5000)
IOLoop.instance().start()
之后你就可以通过”http://example1.com:5000″来访问应用了。
使用Gunicorn运行Flask应用
Gunicorn是一个Python的WSGI Web应用服务器,是从Ruby的Unicorn移植过来的。它基于”pre-fork worker”模型,即预先开启大量的进程,等待并处理收到的请求,每个单独的进程可以同时处理各自的请求,又避免进程启动及销毁的开销。不过Gunicorn是基于阻塞式IO,并发性能无法同Tornado比。更多内容可以参阅其官方网站。另外,Gunicorn同uWSGI一样,一般都是配合着Nginx等Web服务器一同使用。
让我们先将应用安装到远程服务器上,然后采用Gunicorn启动应用,使用下面的命令即可:
$ gunicorn run:app
解释下,因为我们的应用使用了工厂方法,所以只在run.py文件中创建了应用对象app,gunicorn命令的参数必须是应用对象,所以这里是”run:app”。现在你就可以通过”http://example1.com:8000″来访问应用了。默认监听端口是8000。
假设我们想预先开启4个工作进程,并监听本地的5000端口,我们可以将启动命令改为:
$ gunicorn -w 4 -b 127.0.0.1:5000 run:app
来源
https://blog.csdn.net/u011054333/article/details/70151857/