安装
Python版本
建议使用Python3。Flask支持Python3.4及以上、Python2.7和PyPy。
依赖
以下依赖包将会随Flask一同安装:
- Werkzeug:执行WSGI,应用和服务间的Python标准接口
- Jinja:一种服务器端用来渲染页面的模板语言
- MarkupSafe:Jinja携带包,转义模板内容
- ItsDangerous:数据签名工具,保证数据的完整性。用来保护Flask的session和cookie
- Click:命令行工具,为flask提供命令行管理
可选依赖
以下依赖包不会随Flask一起安装,Flask会自动检测并使用这些包
- Blinker:为信号提供支持
- SimpleJSON:快速json解释器,兼容Python标准json模块,安装后会被优先使用
- python-dotenv:运行flask应用时,为环境dotenv变量提供支持
- Watchdog:提供更快更高效的开发服务器重启
虚拟环境
python3可以使用venv模块进行虚拟环境创建,而python2需要先安装virtualenv
创建虚拟环境
创建项目目录,并在该目录下创建venv目录:
mkdir myproject
cd myproject
python3 -m venv venv
windows:
py -3 -m venv venv
旧版本Python创建环境:
virtualenv venv
windows:
\Python27\Script\virtualenv.exe venv
启动环境
开发项目前,要开启对应的虚拟环境:
. venv/bin/activate
Windows:
venv\Script\activate
运行环境将切换到当前项目对应的环境中
安装Flask
在虚拟环境里执行:
pip install Flask
安装成功
快速开始
最小应用
最小的Flask应用,hello.py
:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello, World!'
不要把文件起名为flask.py
,不然会覆盖引用进来的flask模块
把hello.py
运行起来:
$ export FLASK_APP=hello.py
$ flask run
* Running on http://127.0.0.1:5000/
Windows:
C:\path\to\app>set FLASK_APP=hello.py
还可以使用python -m flask
命令:
$ export FLASK_APP=hello.py
$ python -m flask run
* Running on http://127.0.0.1:5000/
127.0.0.1
地址只能本机访问,改为0.0.0.0
即可让网络内其他主机访问:
flask run --host=0.0.0.0
可能遇到的问题
import Name问题
FLASK_APP
环境变量是导入的flask模块的名字,当FLASK_APP
和所运行的flask项目文件名不一致时,将会导致import异常。通常导致该问题的原因是并未创建flask app对象
debug 模式
debug过程中希望项目能自动重启,并且显示错误信息,则需要开启debug模式:
$ export FLASK_ENV=development
$ flask run
Windows下将export
改为set
这样配置后开启的功能:
- 开启调试程序
- 开启自动重启功能
- 以debug模式运行Flask项目
还可以通过export参数FLASK_DEBUG=1
的方式开启debug模式
路由
使用route()
装饰器来绑定URL和视图函数:
@app.route('/')
def index():
return 'Index Page'
@app.route('/hello')
def hello():
return 'Hello, World'
不仅如此,还可以将url的部分内容设置为动态
可变规则
可以向URL添加变量,视图函数通过获取动态请求返回不同数据。<变量名>
的方式传入普通变量,通过<转换类型:变量名>
来指定变量转换类型:
@app.route('/user/<username>')
def show_user_profile(username):
# show the user profile for that user
return 'User %s' % username
@app.route('/post/<int:post_id>')
def show_post(post_id):
# show the post with the given id, the id is an integer
return 'Post %d' % post_id
@app.route('/path/<path:subpath>')
def show_subpath(subpath):
# show the subpath after /path/
return 'Subpath %s' % subpath
可转换类型:
string | (默认) 可接受任何不带斜线的参数 |
---|---|
int | 接收正整数参数 |
float | 接收正浮点数参数 |
path | 类似 string ,但可以接收带斜线的参数 |
uuid | 接收UUID参数 |
唯一URL、URL跳转
如下两种方式对斜线的使用不一样。
@app.route('/projects/')
def projects():
return 'The project page'
@app.route('/about')
def about():
return 'The about page'
访问projects的URL时,如果没有在结尾带斜线,则falsk会为这次访问加上斜线
访问about的URL时,如果访问的时候带上了斜线,则访问不成功
这样做是为了保证URL唯一性
URL构造
使用url_for()
函数来反向构造视图函数所对应的URL。url_for()
接收视图函数名为第一个参数,后边可以传关键字参数,作为动态URL的参数。无法与URL参数对上的参数,则会转换为url请求参数跟在URL后边。
举例来说,可以通过使用test_request_context()
方法来区分Flask所对应的URL:
from flask import Flask, url_for
app = Flask(__name__)
@app.route('/')
def index():
return 'index'
@app.route('/login')
def login():
return 'login'
@app.route('/user/<username>')
def profile(username):
return '{}\'s profile'.format(username)
with app.test_request_context():
print(url_for('index'))
print(url_for('login'))
print(url_for('login', next='/'))
print(url_for('profile', username='John Doe'))
/
/login
/login?next=/
/user/John%20Doe
HTTP请求方法
默认情况下Flask的视图函数值接收GET
请求。可以通过route()
的methods
参数来控制请求方法:
from flask import request
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
return do_the_login()
else:
return show_the_login_form()
当允许接收的请求方法中包含GET
方法时,Flask
会自动支持HEAD
方法
静态文件
在项目目录下创建static目录,则在本地开发时,可以通过访问static路径下相应文件名取到对应的静态文件:
url_for('static', filename='style.css')
则该css文件必须存储在static/style.css路径下
模板渲染
使用Python生成HTML内容是个苦力活,更不用说还要做HTML转义的工作。Flask
支持jinja2
模板引擎,可以帮我们自动生成HTML。
可以使用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)
Flask
会自动在template目录下寻找对应名称的模板文件,template目录与应用为同一级目录,或者属于应用的一级子目录:
/application.py
/templates
/hello.html
或者
/application
/__init__.py
/templates
/hello.html
jinja2
模板语言示例:
<!doctype html>
<title>Hello from Flask</title>
{% if name %}
<h1>Hello {{ name }}!</h1>
{% else %}
<h1>Hello, World!</h1>
{% endif %}
模板中可以访问request
、session
以及get_flashed_messages()
函数等内容。
可以通过Markup
类来做HTML转义:
>>> from flask import Markup
>>> Markup('<strong>Hello %s!</strong>') % '<blink>hacker</blink>'
Markup(u'<strong>Hello <blink>hacker</blink>!</strong>')
>>> Markup.escape('<blink>hacker</blink>')
Markup(u'<blink>hacker</blink>')
>>> Markup('<em>Marked up</em> » HTML').striptags()
u'Marked up \xbb HTML'
使用request对象
Flask
中,通过全局request对象来传递和存储客户端请求过程中的所有内容。那request对象是如何成为全局变量,而Flask
又是如何保证线程安全的呢?这就不得不提上下文区域变量(context locals):
Context Locals
Flask
中,一部分对象作为全局对象存在的。这些对象其实是邻近详细上下文对象的代理,
把上下文想象为处理线程,一个请求进来,而服务器产生一个新线程。当Flask
在内部开始处理请求时,会发现当前线程已经是活跃的上下文,且绑定了当前应用和WSGI环境。这样一来,一个应用在不中断的情况下即可唤醒另一个应用。
上面一段讲了什么?除非要进行单元测试,一般情况下可以忽略这些过程。你会发现依赖于request对象的代码由于缺少request对象而停止了。解决办法是自己创建一个request对象并与上下文绑定。单元测试最简单的解决办法是使用test_request_context()
上下文管理器。使用with
语句执行该方法,将绑定一个测试request对象,这样一来就可以与之交互。
from flask import request
with app.test_request_context('/hello', method='POST'):
# now you can do something with the request until the
# end of the with block, such as basic assertions:
assert request.path == '/hello'
assert request.method == 'POST'
另一种方法是将整个WSGI环境作为参数传入request_context()
方法中:
from flask import request
with app.request_context(environ):
assert request.method == 'POST'
Request对象
常用操作概述:
from flask import request
当前请求的方法可以通过method
属性获得,请求的表单数据可通过form
属性获得
@app.route('/login', methods=['POST', 'GET'])
def login():
error = None
if request.method == 'POST':
if valid_login(request.form['username'],
request.form['password']):
return log_the_user_in(request.form['username'])
else:
error = 'Invalid username/password'
# the code below is executed if the request method
# was GET or the credentials were invalid
return render_template('login.html', error=error)
当form
中不存在key值咋整?这种情况会抛出KeyError
异常。可以通过标准KeyError
捕捉异常,如果不进行异常处理,将会返回一个HTTP码为400的错误页面。因此大多数情况下不需要做处理。
通过arg
属性来获取url中的参数:
searchword = request.args.get('key', '')
推荐使用get方法或捕捉异常的方式获取url参数,否则会显示400错误页,对用户来说是不友好的体验
文件上传
设置好 enctype="multipart/form-data
属性后即可便捷地进行文件上传了
上传的文件存储在内存或文件系统的临时位置中。可以通过访问request对象的files
属性来获取文件数据每个上传的文件都存在一个字典中,与普通python文件对象一样,不过有一个save()
方法,用来将文件存储到服务器的文件系统中:
from flask import request
@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
f = request.files['the_file']
f.save('/var/www/uploads/uploaded_file.txt')
...
可以通过访问filename
属性获得客户端上传文件的文件名,然而这些信息是不可信的,需要将文件名通过Werkzeug提供的secure_filename()
方法来做一次安全性处理:
from flask import request
from werkzeug.utils import secure_filename
@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
f = request.files['the_file']
f.save('/var/www/uploads/' + secure_filename(f.filename))
...
Cookies
通过request的cookies
属性访问cookie,通过response的set_cookie
方法设置cookie。cookies
属性是一个包含客户端传来的所有cookie信息的字典。如果想使用sessions,不要直接使用cookie,而是使用Sessions 增加安全性
访问cookie:
from flask import request
@app.route('/')
def index():
username = request.cookies.get('username')
# use cookies.get(key) instead of cookies[key] to not get a
# KeyError if the cookie is missing.
设置cookie:
@app.route('/')
def index():
resp = make_response(render_template(...))
resp.set_cookie('username', 'the username')
return resp
可以看到,cookie是在response对象里进行设置的。由于一般的视图函数返回的都是字符串,Flask
会将字符串转换为response对象。如果真的想设置cookie,可以通过make_response()
函数修改。
跳转与错误
使用redirect()
函数跳转用户请求;使用abort()
函数可以引发错误码,从而中止请求。
from flask import abort, redirect, url_for
@app.route('/')
def index():
return redirect(url_for('login'))
@app.route('/login')
def login():
abort(401)
this_is_never_executed()
默认错误页面对应不同的错误码。如果想改造错误页,可以使用errorhandler()
装饰器实现:
from flask import render_template
@app.errorhandler(404)
def page_not_found(error):
return render_template('page_not_found.html'), 404
留意render_template()
执行后跟着的404,它告诉Flask
该请求需要返回404
Response对象
视图函数返回值会自动转换为response对象。Flask
的转换规则如下:
- 如果视图返回了格式正确的response对象,则会直接返回,也就是不用再转换了;
- 如果视图返回的是字符串,则会将返回的数据和默认参数用来生成一个response对象;
- 如果返回一个元组,则元组元素包含了额外信息,这些元组一般以这种形式呈现;(response, status, headers)或(response, headers),元组里至少需要有一个元素。状态码的值会覆盖原有的状态码,而headers可以为一个列表或字典,作为额外的值添加进原来的header里
- 如果上述情况都没发生,
Flask
会将返回值替换为一个合法的WSGI应用,并将其转换为一个response对象
如果想改动response对象的内容,则需要在视图函数里调用make_response()
方法
假设你有一个这样的视图函数:
@app.errorhandler(404)
def not_found(error):
return render_template('error.html'), 404
你只需要将返回值传到make_response()
函数里,并且在得到response对象后进行修改:
@app.errorhandler(404)
def not_found(error):
resp = make_response(render_template('error.html'), 404)
resp.headers['X-Something'] = 'A value'
return resp
Sessions
想使用session,必须设定secret key,用来加密cookie信息。这样一来用户拿到cookie,在没有secret key的情况下是无法更改cookie中的数据的。
session的使用方法:
from flask import Flask, session, redirect, url_for, escape, request
app = Flask(__name__)
# Set the secret key to some random bytes. Keep this really secret!
app.secret_key = b'_5#y2L"F4Q8z\n\xec]/'
@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 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'))
上面提到的escape()
方法的作用是当你不使用模板引擎时,为你转义返回的内容
如何生成好的secret key:
秘钥需要越随机越好。操作系统提供生成随机数据。使用下面的命令可以快速生成一个secret_key
$ python -c 'import os; print(os.urandom(16))' b'_5#y2L"F4Q8z\n\xec]/'