文章目录
2.1 Flask介绍及其安装
Flask 是一个轻量级的 Web 应用框架, 使用 Python 编写。
基于 WerkzeugWSGI 工具箱和 Jinja2 模板引擎。使用 BSD 授权。
Flask 也被称为 microframework ,因为它使用简单的核心,用 extension 增加其它功能。
Flask 没有默认使用的数据库、窗体验证工具。
然而,Flask 保留了扩增的弹性,可以用 Flask-extension 加入这些功能:ORM、窗体验证工具、文件上传、各种开放式身份验证技术。
当安装 Flask 时,以下组件也会自动安装:
- Werkzeug: WSGI(web 服务器网关接口)工具,是介于应用和服务器之间标准的接口工具。
- Jinja: web 前端页面中使用的模板语言。
- MarkupSafe: 与 Jinja 配合使用,当表单页面跳转时会进行验证从而避免遭遇不信任的输入带来的攻击。
- ItsDangerous: 安全地注入数据以确保数据的完整性,通常用于保护 Flask 的 session cookie。
- Click: 一个解析命令行的应用,它支持在 Flask 中自定义管理命令。
2.2 Virtualenv
也许 Virtualenv 是你在开发中最愿意使用的,如果你在生产机器上有 shell 权限的时候,也会愿意用上 Virtualenv。
virtualenv 解决了什么问题?如果你像我一样喜欢 Python 的话,有很多机会在基于 Flask 的 web 应用外的其它项目上使用 Python。 然而项目越多,越有可能在不同版本的 python,或者至少在不同 python 库的版本上工作。我们需要面对这样的事实:库破坏向后兼容性的情况相当常见,而且零依赖的正式应用也不大可能存在。如此,当你的项目中的两个或更多出现依赖性冲突,你会怎么做?
Virtualenv 的出现解决这一切!Virtualenv 能够允许多个不同版本的 Python 安装,每一个服务于各自的项目。它实际上并没有安装独立的 Python 副本,只是提供了一种方式使得环境保持独立。让我们见识下 virtualenv 怎么工作的。
如果你在 Mac OS X 或 Windows 下,下面两条命令可能会适用:
sudo easy_install virtualenv
# 或者是:
sudo pip3 install virtualenv
上述的命令会在你的系统中安装 virtualenv。它甚至可能会出现在包管理器中。
如果是在 Windows 下并且没有安装 easy_install 命令,你首先必须安装 easy_install 。要想获取更多的安装信息,请查看 Windows 下的 pip 和 distribute 。一旦安装好 easy_install ,运行上述的命令,但是要去掉 sudo 前缀。
如果你使用 Ubuntu ,请尝试:
sudo apt-get install python-virtualenv
一旦成功安装 virtualenv,运行 shell 创建自己的环境。通常会创建一个项目文件夹 myproject,其下创建 venv 文件夹,该文件夹就是一个虚拟的 Python 环境,同样的,我们可以使用 -p 参数来改变 python 的版本,默认情况下,virtualenv 会优先选取系统默认的 python 环境。本实验中我们使用 python3 。
$ cd /home/shiyanlou/Code
$ mkdir myproject && cd myproject
$ virtualenv -p /usr/bin/python3 venv
New python executable in venv/bin/python
Installing distribute............done.
现在,只要你想要在某个项目上(在本课程中,我们建议你在新建的 myproject 目录下,这样 python 运行环境之间不存在冲突)工作,只要激活相应的环境。在 Mac OS X 和 Linux 下,执行如下命令:
. venv/bin/activate
如果你是 Windows 用户,下面的命令行是为你而准备:
venv\scripts\activate
无论哪种方式,现在都能够使用你的 virtualenv (注意你的 shell 提示符显示的是活动的环境)。
接下来只需要输入以下的命令在 virtualenv 中安装 flask,在本课程中统一使用 Flask 1.0.2 版本:
pip3 install flask==1.0.2
几秒后,一切就为你准备就绪。
检查是否成功安装 Flask:
$ python3
>>> import flask
>>>
2.2.2 全局安装
以下命令安装 Flask 也是可行的,这样就是把 Flask 全局安装在操作环境中。只需要以 root 权限运行 pip:
sudo apt-get update
sudo pip3 install flask==1.0.2
(在 Windows 系统上,在管理员权限的命令提示符中运行这条命令,不需要 sudo。)
2.2.3 体验最新的 Flask (Living on the Edge)
如果你想要使用最新版的 Flask ,可以直接在终端执行如下命令:
sudo pip3 install -U https://github.com/pallets/flask/archive/master.tar.gz
3.1 一个最小的应用
一个最小的应用看起来像这样,在 /home/shiyanlou/Code 目录下新建 hello.py 文件,并向其中写入如下代码:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello, World!'
那么这段代码做了什么?
首先我们导入了类 Flask。这个类的实例化将会是我们的 WSGI 应用。
接着,我们创建一个该类的实例。第一个参数是应用模块或包的名称,这样 Flask 才会知道去哪里寻找模板、静态文件等等。如果你使用的是单一的模块(就如本例),第一个参数应该使用 name。
我们使用装饰器route()告诉 Flask 哪个URL才能触发我们的函数。
定义一个函数,该函数名也是用来给特定函数生成 URLs,并且返回我们想要显示在用户浏览器上的信息。
使用 Python 解释器运行这个文件,注意这个文件不能取名为flask.py,因为这会与 Flask 本身冲突。
运行这个应用既可以使用 flask 命令行也可以使用 Python 的 -m 调用 flask,在运行之前你需要设置 FLASK_APP 的环境变量来告诉终端需要运行哪个应用,在终端执行如下命令: (大家请注意,记得要切回 Code 目录,才能调用,在后续课程实例代码中,将不会再提醒)
$ cd /home/shiyanlou/Code
$ export FLASK_APP=hello.py
$ flask run
* Serving Flask app "hello.py"
* Environment: production
WARNING: Do not use the development server in a production environment.
Use a production WSGI server instead.
* Debug mode: off
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
也可以使用如下命令启动应用:
$ export FLASK_APP=hello.py
$ python3 -m flask run
* Serving Flask app "hello.py"
* Environment: production
WARNING: Do not use the development server in a production environment.
Use a production WSGI server instead.
* Debug mode: off
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
以上的命令启动了一个非常简单的 flask 内置服务器,用于测试已经足够了但可能你并不想用于生产环境。更多配置可以参考开发者选项。
现在使用浏览器浏览http://127.0.0.1:5000/,将会看到页面上的 Hello, World!。
请按Ctrl+c来停止服务器。
3.2 外部课件服务器
一个最小的应用看起来像这样,在 /home/shiyanlou/Code 目录下新建 hello.py 文件,并向其中写入如下代码:
from flask import Flask
app = Flask(name)
@app.route(’/’)
def hello_world():
return ‘Hello, World!’
那么这段代码做了什么?
首先我们导入了类 Flask。这个类的实例化将会是我们的 WSGI 应用。
接着,我们创建一个该类的实例。第一个参数是应用模块或包的名称,这样 Flask 才会知道去哪里寻找模板、静态文件等等。如果你使用的是单一的模块(就如本例),第一个参数应该使用 name。
我们使用装饰器route()告诉 Flask 哪个URL才能触发我们的函数。
定义一个函数,该函数名也是用来给特定函数生成 URLs,并且返回我们想要显示在用户浏览器上的信息。
使用 Python 解释器运行这个文件,注意这个文件不能取名为flask.py,因为这会与 Flask 本身冲突。
运行这个应用既可以使用 flask 命令行也可以使用 Python 的 -m 调用 flask,在运行之前你需要设置 FLASK_APP 的环境变量来告诉终端需要运行哪个应用,在终端执行如下命令: (大家请注意,记得要切回 Code 目录,才能调用,在后续课程实例代码中,将不会再提醒)
$ cd /home/shiyanlou/Code
$ export FLASK_APP=hello.py
$ flask run
* Serving Flask app "hello.py"
* Environment: production
WARNING: Do not use the development server in a production environment.
Use a production WSGI server instead.
* Debug mode: off
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
也可以使用如下命令启动应用:
$ export FLASK_APP=hello.py
$ python3 -m flask run
* Serving Flask app "hello.py"
* Environment: production
WARNING: Do not use the development server in a production environment.
Use a production WSGI server instead.
* Debug mode: off
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
以上的命令启动了一个非常简单的 flask 内置服务器,用于测试已经足够了但可能你并不想用于生产环境。更多配置可以参考开发者选项。
现在使用浏览器浏览http://127.0.0.1:5000/,将会看到页面上的 Hello, World!。
请按Ctrl+c来停止服务器。
3.3 调试模式
使用 flask 命令行可以非常方便的启动一个本地开发服务器,但是每次修改代码后你都需要手动重启服务器。通过前面的启动后输出显示可以发现 Environment 为 production,同时调试模式未开启 Debug mode: off。
这样做并不好,Flask 能做得更好。如果启用了调试支持,在代码修改后服务器能够自动重载,并且如果发生错误,它会提供一个有用的调试器。
为了让所有的开发者特征可用(包括调试模式),在运行服务器之前可以设置 FLASK_ENV 环境变量为 development:
$ export FLASK_ENV=development
$ export FLASK_DEBUG=1
$ flask run
- Serving Flask app “hello.py” (lazy loading)
- Environment: development
- Debug mode: on
- Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
- Restarting with stat
- Debugger is active!
- Debugger PIN: 219-973-102
上述命令做了以下几件事:
使调试器(debugger)可用
启动了代码改变自动的热加载
在 flask 应用中开启了 debug 模式
注意
尽管交互式调试器(debugger)不能在分叉(forking)环境下工作(这使得它几乎不可能在生产服务器上使用),它依然允许执行任意代码。这使它成为一个巨大的安全风险,因此它绝对不能用于生产环境。
运行中的调试器的截图,从截图可以看出在页面上有终端可以执行交互式命令:
本节讲解了一个 Flask 的小例子,以及在命令行中通过设置环境变量来开启调试模式。
参考链接
WSGI 简介(https://blog.csdn.net/on_1y/article/details/18803563)
4.1 路由介绍
现代 Web 应用程序使用有意义的 URLs 去帮助用户。如果一个网站使用有意义的 URL 能够让用户记住并且直接访问这个页面,那么用户会更有可能再一次访问该网站。
正如上面所说,route 装饰器是用于把一个函数绑定到一个 URL 上。修改 /home/shiyanlou/Code/hello.py 文件的代码如下所示:
from flask import Flask
app = Flask(__name__)
# 如果访问 /,返回 Index Page
@app.route('/')
def index():
return 'Index Page'
# 如果访问 /hello,返回 Hello, World!
@app.route('/hello')
def hello():
return 'Hello, World!'
然后在终端执行如下命令启动服务:
export FLASK_APP=hello.py
export FLASK_ENV=development
flask run
访问地址 http://127.0.0.1:5000,浏览器页面会显示 Index Page;如果访问地址 http://127.0.0.1:5000/hello,浏览器页面会显示 Hello, World!。这样就实现了通过访问不同的 URL 地址从而响应不同的页面。
不仅如此!你可以动态地构造 URL 的特定部分,也可以在一个函数上绑定多个不同的规则。
4.2 变量规则
为了给 URL 增加变量的部分,你需要把一些特定的字段标记成<variable_name>。这些特定的字段将作为参数传入到你的函数中。当然也可以指定一个可选的转换器通过规则converter:variable_name将变量值转换为特定的数据类型。 在 /home/shiyanlou/Code/hello.py 文件中添加如下的代码:
@app.route('/user/<username>')
def show_user_profile(username):
# 显示用户名
return 'User {}'.format(username)
@app.route('/post/<int:post_id>')
def show_post(post_id):
# 显示提交整型的用户"id"的结果,注意"int"是将输入的字符串形式转换为整型数据
return 'Post {}'.format(post_id)
@app.route('/path/<path:subpath>')
def show_subpath(subpath):
# 显示 /path/ 之后的路径名
return 'Subpath {}'.format(subpath)
按照前面的方式启动应用,逐个访问地址:
当访问 http://127.0.0.1:5000/user/shiyanlou 时,页面显示为 User shiyanlou。
当访问 http://127.0.0.1:5000/post/3 时,页面显示为 Post 3。用户在浏览器地址栏上输入的都是字符串,但是在传递给 show_post 函数处理时已经被转换为了整型。
当访问 http://127.0.0.1:5000/path/file/A/a.txt 时,页面显示为 Subpath file/A/a.txt。
转换器的主要类型如下:
类型 含义
string 默认的数据类型,接受没有任何斜杠“/”的字符串
int 接受整型
float 接受浮点类型
path 和 string 类似,但是接受斜杠“/”
uuid 只接受 uuid 字符串
唯一 URLs / 重定向行为
Flask 的 URL 规则是基于 Werkzeug 的 routing 模块。该模块背后的思路是基于 Apache 和早期的 HTTP 服务器定下先例确保优雅和唯一的 URL。
以这两个规则为例,在 /home/shiyanlou/Code/hello.py 文件中添加如下的代码:
@app.route('/projects/')
def projects():
return 'The project page'
@app.route('/about')
def about():
return 'The about page'
虽然它们看起来确实相似,但它们结尾斜线的使用在 URL 定义中不同。
第一种情况中,规范的 URL 指向 projects 尾端有一个斜线/。这种感觉很像在文件系统中的文件夹。访问一个结尾不带斜线的 URL 会被 Flask 重定向到带斜线的规范 URL 去。当访问 http://127.0.0.1:5000/projects/ 时,页面会显示 The project page。
然而,第二种情况的 URL 结尾不带斜线,类似 UNIX-like 系统下的文件的路径名。此时如果访问结尾带斜线的 URL 会产生一个404 “Not Found”错误。当访问 http://127.0.0.1:5000/about 时,页面会显示 The about page;但是当访问 http://127.0.0.1:5000/about/ 时,页面就会报错 Not Found。
当用户访问页面忘记结尾斜线时,这个行为允许关联的 URL 继续工作,并且与 Apache 和其它的服务器的行为一致,反之则不行,因此在代码的 URL 设置时斜线只可多写不可少写;另外,URL 会保持唯一,有助于避免搜索引擎索引同一个页面两次。
4.3 构建URL
去构建一个 URL 来匹配一个特定的函数可以使用 url_for() 方法。它接受函数名作为第一个参数,以及一些关键字参数,每一个关键字参数对应于 URL 规则的变量部分。未知变量部分被插入到 URL 中作为查询参数。
为什么你要构建 URLs 而不是在模版中硬编码呢?这里有几个理由:
反向构建通常比硬编码更具备描述性。
它允许你一次性修改 URL,而不是到处找 URL 修改。
构建 URL 能够显式地处理特殊字符和Unicode转义,因此你不必去处理这些。
如果你的应用不在 URL 根目录下(比如,在 /myapplication 而不在 /),url_for()将会适当地替你处理好。
在 Python shell 交互式命令行下运行如下代码:
(记得切回myproject对应目录)
$ python3
>>> 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=%2F # 对字符串进行了转义,未知变量部分被当做查询参数
/user/John%20Doe
test_request_context() 方法告诉 Flask 表现得像是在处理一个请求,即使我们正在通过 Python shell 交互。大家可以仔细分析一下该函数的打印结果。
4.4 HTTP 方法
HTTP (也就是 Web 应用协议) 有不同的方法来访问 URLs 。默认情况下,路由只会响应 GET 请求,但是能够通过给 route() 装饰器提供 methods 参数来改变。这里是一个例子:
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
do_the_login() # 如果是 POST 方法就执行登录操作
else:
show_the_login_form() # 如果是 GET 方法就展示登录表单
如果使用 GET 方法,HEAD 方法将会自动添加进来。你不必处理它们。也能确保 HEAD 请求会按照 HTTP RFC (文档在 HTTP 协议里面描述) 要求来处理,因此你完全可以忽略这部分 HTTP 规范。同样地,自从 Flask 0.6 后,OPTIONS 方法也能自动为你处理。
也许你并不清楚 HTTP 方法是什么?别担心,这里有一个 HTTP 方法的快速入门以及为什么它们重要:
HTTP方法(通常也称为“谓词”)告诉服务器客户端想要对请求的页面做什么。
下面这些方法是比较常见的:
GET:浏览器通知服务器只获取页面上的信息并且发送回来。这可能是最常用的方法。
HEAD:浏览器告诉服务器获取信息,但是只对头信息感兴趣,不需要整个页面的内容。应用应该处理起来像接收到一个 GET 请求但是不传递实际内容。在 Flask 中你完全不需要处理它,底层的 Werkzeug 库会为你处理的。
POST:浏览器通知服务器它要在 URL 上提交一些信息,服务器必须保证数据被存储且只存储一次。这是 HTML 表单通常发送数据到服务器的方法。
PUT:同 POST 类似,但是服务器可能触发了多次存储过程,多次覆盖掉旧值。现在你就会问这有什么用,有许多理由需要如此去做。考虑下在传输过程中连接丢失:在这种情况下浏览器和服务器之间的系统可能安全地第二次接收请求,而不破坏其它东西。该过程操作 POST 方法是不可能实现的,因为它只会被触发一次。
DELETE:移除给定位置的信息。
OPTIONS:给客户端提供一个快速的途径来指出这个 URL 支持哪些 HTTP 方法。从 Flask 0.6 开始,自动实现了该功能。
现在在 HTML4 和 XHTML1 中,表单只能以 GET 和 POST 方法来提交到服务器。在 JavaScript 和以后的 HTML 标准中也能使用其它的方法。同时,HTTP 最近变得十分流行,浏览器不再是唯一使用 HTTP 的客户端。比如许多版本控制系统使用 HTTP。
4 总结
本节讲解了 Flask 的路由,我们可以给 URL 添加规则,也可以动态地构建 URL 。
5.1 静态文件
动态的 web 应用同样需要静态文件。CSS 和 JavaScript 文件通常来源于此。理想情况下,你的 web 服务器已经配置好为它们服务,然而在开发过程中 Flask 就能够做到。只要在你的包中或模块旁边创建一个名为static 的文件夹,在应用中使用 /static 即可访问。
给静态文件生成 URL ,使用特殊的 static 端点名:
url_for('static', filename='style.css')
这个文件是应该存储在文件系统上的static/style.css。
5.2 渲染模板
在 Python 中生成 HTML 并不好玩,实际上是相当繁琐的,因为你必须自行做好 HTML 转义以保持应用程序的安全。由于这个原因,Flask 自动为你配置好 Jinja2 模板。
你可以使用方法 render_template() 来渲染模板。所有你需要做的就是提供模板的名称以及你想要作为关键字参数传入模板的变量。
这里有个渲染模板的简单例子,在 /home/shiyanlou/Code 目录下新建 hello.py 文件,并向其中添加如下代码:
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/hello/')
@app.route('/hello/<name>')
def hello(name=None): # 默认 name 为 None
return render_template('hello.html', name=name) # 将 name 参数传递到模板变量中
Flask 将会在 templates 文件夹中寻找模板。因此如果你的应用是个模块,这个文件夹在模块的旁边,如果它是一个包,那么这个文件夹在你的包里面:
比如,应用是模块(本系列实验的应用结构都是模块型):
/application.py
/templates
/hello.html
比如,应用是包:
/application
/__init__.py
/templates
/hello.html
对于模板,你可以使用 Jinja2 模板的全部能力。详细信息查看官方的 Jinja2 Template Documentation 。
在 /home/shiyanlou/Code 目录下新建 templates 文件夹并在其中新建 hello.html 文件:
cd /home/shiyanlou/Code
mkdir templates && cd templates
touch hello.html
然后向 hello.html 模板文件中添加如下代码:
<!doctype html>
<title>Hello from Flask</title>
{% if name %} <!-- 如果 name 不为空则将 name 渲染出来 -->
<h1>Hello {{ name }}!</h1>
{% else %} <!-- 如果 name 为空则打印 Hello World! -->
<h1>Hello World!</h1>
{% endif %}
按照前面的方法运行应用程序,当访问 http://127.0.0.1:5000/hello/ 时页面显示 Hello World!;当访问 http://127.0.0.1:5000/hello/shiyanlou 时页面显示 Hello shiyanlou!。
在模板中你也可以使用request,session和g对象,也能使用函数get_flashed_messages() 。
模板继承是十分有用的。如果想要知道模板继承如何工作的话,请阅读文档模板继承。基本的模板继承使得某些特定元素(如标题、导航和页脚)在每一页成为可能。
自动转义默认是开启的,因此如name包含 HTML,它将会自动转义。如果你信任一个变量,并且你知道它是安全的(例如一个模块把 wiki 标记转换到 HTML ),你可以用Markup类或|safe过滤器在模板中标记它是安全的。 在 Jinja 2 文档中,你会见到更多例子。
下面有一个Markup类如何工作的基本介绍,在 Python3 交互式命令行中执行如下命令:
$ python3
>>> from flask import Markup
>>> Markup('<strong>Hello %s!</strong>') % '<blink>hacker</blink>'
Markup('<strong>Hello <blink>hacker</blink>!</strong>')
>>> Markup.escape('<blink>hacker</blink>')
Markup('<blink>hacker</blink>')
>>> Markup('<em>Marked up</em> » HTML').striptags()
'Marked up » HTML'
>>>
注意:在后面的0.5版本以上:
自动转义不再在所有模板中启用。模板中下列后缀的文件会触发自动转义:.html, .htm, .xml,.xhtml。从字符串加载的模板会禁用自动转义。
5.3 练习
请创建一个模板和CSS文件,并在模板引入CSS文件,当访问网站首页时显示一个绿色的Hello ShiYanLou字样。
6
对于 Web 应用来说,对客户端发送给服务器的数据做出反应至关重要,本实验将介绍 Flask 是怎样提供这些信息的。
6.1 接收请求数据
在 Flask 中由全局对象 request 来提供这些信息。如果你有一定的 Python 经验,你会好奇这个对象怎么可能是全局的,并且 Flask 是怎么还能保证线程安全。答案是上下文作用域。
局部上下文
注意:如果你想要了解上下文作用域是如何工作的以及如何使用它进行测试,就可以读这一部分,如果暂时不需要的话,可以直接跳过这部分。
Flask 中的某些对象是全局对象,但不是通常的类型。这些对象实际上是给定上下文的局部对象的代理。虽然很拗口,但实际上很容易理解。
想象下线程处理的上下文。一个请求传入,web 服务器决定产生一个新线程(或者其它东西,底层对象比线程更有能力处理并发系统)。当 Flask 开始它内部请求处理时,它认定当前线程是活动的上下文并绑定当前的应用和 WSGI 环境到那个上下文(线程)。它以一种智能的方法来实现,以致一个应用可以调用另一个应用而不会中断。
所以这对你意味着什么呢?除非你是在做一些类似单元测试的事情,否则基本上你可以完全忽略这种情况。你会发现依赖于请求对象的代码会突然中断,因为没有请求对象。解决方案就是自己创建一个请求并把它跟上下文绑定。
针对单元测试最早的解决方案是使用test_request_context()上下文管理器。结合with声明,它将绑定一个测试请求来进行交互。这里是一个例子:
from flask import request
with app.test_request_context('/hello', method='POST'):
# 现在你可以做出请求,比如基本的断言
assert request.path == '/hello'
assert request.method == 'POST'
另一个可能性就是传入整个 WSGI 环境到request_context()方法:
from flask import request
with app.request_context(environ):
assert request.method == 'POST'
参考链接:
Flask 上下文理解 (https://jin-yang.github.io/post/flask-context.html)
Flask 的 Context 机制(https://blog.tonyseek.com/post/the-context-mechanism-of-flask/)
6.2 请求对象
首先你需要从 flask 模块中导入request:
from flask import request
当前请求的方法可以用method属性来访问。你可以用form属性来访问表单数据 (数据在 POST 或者PUT中传输)。这里是上面提及到的两种属性的完整的例子:
@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'
# 当请求形式为“GET”或者认证失败则执行以下代码
return render_template('login.html', error=error)
如果在form属性中不存在上述键值会发生些什么?在这种情况下会触发一个特别的 KeyError。你可以像捕获标准的KeyError一样来捕获它,如果你不这样去做,会显示一个HTTP 400 Bad Request错误页面。所以很多情况下你不需要处理这个问题。
你可以用args属性来接收在URL ( ?key=value )中提交的参数:
searchword = request.args.get('key', '')
我们推荐使用get来访问 URL 参数或捕获KeyError,因为用户可能会修改 URL,向他们显示一个400 bad request页面不是用户友好的。
6.3 文件上传
你能够很容易地用 Flask 处理文件上传。只要确保在你的 HTML 表单中不要忘记设置属性enctype=“multipart/form-data”,否则浏览器将不会传送文件。
上传的文件是存储在内存或者文件系统上一个临时位置。你可以通过请求对象中files属性访问这些文件。每个上传的文件都会存储在这个属性字典里。它表现得像一个标准的 Python file对象,但是它同样具有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 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))
...
6.4 Cookies
你可以用 cookies 属性来访问 Cookies 。你能够用响应对象的 set_cookie 来设置 cookies。请求对象中的 cookies 属性是一个客户端发送所有的 cookies 的字典。
如果你要使用会话(sessions),请不要直接使用 cookies,相反,请用 Flask 中的会话,Flask 已经在cookies 上增加了一些安全细节;关于更多 seesions 和 cookies 的区别与联系,请参见施杨出品的博客。
读取 cookies:
from flask import request
@app.route('/')
def index():
username = request.cookies.get('username')
# 注意这里引用cookies字典的键值对是使用cookies.get(key)
# 而不是cookies[key],这是防止该字典不存在时报错"keyerror"
存储 cookies:
from flask import make_response
@app.route('/')
def index():
resp = make_response(render_template(...))
resp.set_cookie('username', 'the username')
return resp
注意cookies是在响应对象中被设置。由于通常只是从视图函数返回字符串,Flask 会将其转换为响应对象。如果你要显式地这么做,可以使用 make_response() 函数接着修改它。
有时候你可能要在响应对象不存在的地方设置cookie。利用延迟请求回调模式使得这种情况成为可能。
6 总结
本节讲解了 flask 的请求,如果想在没有请求的情况下获取上下文,可以使用test_request_context()或者request_context(),从request对象的form中可以获取表单的数据,args中可以获取 URL 中的参数,files可以获取上传的文件,cookies可以操作cookie。
7
7.1 重定向和错误
你能够用redirect()函数重定向用户到其它地方。能够用abort()函数提前中断一个请求并带有一个错误代码。
下面是一个演示它们如何工作的例子,在 /home/shiyanlou/Code/ 目录下新建 hello.py 文件并向其中写入如下代码:
from flask import Flask
from flask import abort, redirect, url_for
app = Flask(__name__)
@app.route('/')
def index():
return redirect(url_for('login'))
@app.route('/login')
def login():
abort(401)
this_is_never_executed()
按照之前的方式运行应用,这是一个相当无意义的例子因为用户会从主页/重定向到一个不能访问的页面/login( 401 意味着禁止访问),但是它说明了重定向如何工作。
默认情况下,每个错误代码会显示一个黑白错误页面。比如上面的页面会显示 401 Unauthorized。如果你想定制错误页面,可以使用errorhandler()装饰器,向 /home/shiyanlou/Code/hello.py 文件中添加如下代码:
from flask import render_template
@app.errorhandler(401)
def page_not_found(error):
return render_template('page_not_found.html'), 404
注意到 404 是在render_template()调用之后。告诉 Flask 该页的错误代码应是 404 ,即没有找到。默认的 200 被假定为:一切正常。
在 /home/shiyanlou/Code 目录下新建 templates 文件夹,并在其中新建 page_not_found.html 文件。
cd /home/shiyanlou/Code
mkdir templates && cd templates
touch page_not_found.html
向 page_not_found.html 文件中添加如下代码:
<h1>page not found, this is an error page.</h1>
等代码重新热加载后,访问首页就可以看到 page not found, this is an error page.。
7.2 关于响应
一个视图函数的返回值会被自动转换为一个响应对象。如果返回值是一个字符串,它被转换成一个响应主体是该字符串,错误代码为 200 OK ,媒体类型为text/html的响应对象。Flask 把返回值转换成响应对象的逻辑如下:
如果返回的是一个合法的响应对象,它会直接从视图返回。
如果返回的是一个字符串,响应对象会用字符串数据和默认参数创建。
如果返回的是一个元组而且元组中元素能够提供额外的信息。这样的元组必须是(response, status, headers) 形式且至少含有其中的一个元素。status值将会覆盖状态代码,headers可以是一个列表或额外的消息头值字典。
如果上述条件均不满足,Flask 会假设返回值是一个合法的 WSGI 应用程序,并转换为一个请求对象。
如果你想要获取在视图中得到的响应对象,你可以用函数make_response()。
想象你有这样一个视图:
@app.errorhandler(404)
def not_found(error):
return render_template('error.html'), 404
你只需要用make_response()封装返回表达式,获取结果对象并修改,然后返回它:
@app.errorhandler(404)
def not_found(error):
resp = make_response(render_template('error.html'), 404)
resp.headers['X-Something'] = 'A value'
return resp
7.3 会话
除了请求对象,还有第二个称为session对象允许你在不同请求间存储特定用户的信息。这是在 cookies 的基础上实现的,并且在 cookies 中使用加密的签名。这意味着用户可以查看 cookie 的内容,但是不能修改它,除非知道签名的密钥。
要使用会话,你需要设置一个密钥。这里介绍会话如何工作,在 /home/shiyanlou/Code 目录下新建 test.py 文件并写入如下代码:
from flask import Flask, session, redirect, url_for, escape, request
app = Flask(__name__)
# 设置密钥,保证会话安全
app.secret_key = '_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():
# 如果用户名存在,则从会话中移除该用户名
session.pop('username', None)
return redirect(url_for('index'))
这里提到的escape()可以在你不使用模板引擎的时候做转义(如同本例)。其中,login函数中返回的网页源代码可以单独存储在templates文件夹中作为模板文件html,然后使用return render_template()更方便。
按照前面的方式运行程序:
当访问首页 http://127.0.0.1:5000/ 时会显示 You are not logged in;
当访问登录页面 http://127.0.0.1:5000/login 时会出现一个输入框,在输入框中输入用户名 shiyanlou,然后点击 Login 按钮,这时 URL 会重定向到首页上,首页显示 Logged in as shiyanlou;
最后再访问登出页面 http://127.0.0.1:5000/logout,这时从 session 中移除了用户名,URL 重定向到首页显示 You are not logged in;
怎样产生一个好的密钥:
随机的问题在于很难判断什么是真随机。一个密钥应该足够随机。你的操作系统可以基于一个密码随机生成器来生成漂亮的随机值,这个值可以用来做密钥:
$ python3 -c 'import os; print(os.urandom(16))'
b'm \xf8>]?\x86\xcf/y\x0e\xc5\xc7j\xc5/'
把这个值复制粘贴到你的代码,你就搞定了密钥。
使用基于 cookie 的会话需注意: Flask 会将你放进会话(session)对象的值序列化到 cookie 。如果你试图寻找一个跨请求不能存留的值,cookies 确实是启用的,并且你不会获得明确的错误信息,检查你页面请求中 cookie 的大小,并与 web 浏览器所支持的大小对比。
7.4 消息闪烁
好的应用和用户界面全部是关于反馈。如果用户得不到足够的反馈,他们可能会变得讨厌这个应用。Flask 提供了一个真正的简单的方式来通过消息闪现系统给用户反馈。消息闪现系统基本上使得在请求结束时记录信息并在下一个 (且仅在下一个)请求中访问。通常结合模板布局来显示消息。
使用flash()方法来闪现一个消息,使用get_flashed_messages()能够获取消息,get_flashed_messages()也能用于模板中。
下面来看一个简单的例子,在 /home/shiyanlou/Code 目录下新建 flashTest.py 文件,并向其中写入如下代码:
from flask import Flask, flash, redirect, render_template, \
request, url_for
app = Flask(__name__)
app.secret_key = b'_5#y2L"F4Q8z\n\xec]/'
@app.route('/')
def index():
return render_template('index.html')
@app.route('/login', methods=['GET', 'POST'])
def login():
error = None
if request.method == 'POST':
if request.form['username'] != 'admin' or \
request.form['password'] != 'secret':
error = 'Invalid credentials'
else:
flash('You were successfully logged in')
return redirect(url_for('index'))
return render_template('login.html', error=error)
然后在 /home/shiyanlou/Code/templates 目录下新建 base.html 页面,其中写入基本的模板代码,代码主要是从后端获取 flash 消息以及错误信息。
<!doctype html>
<title>My Application</title>
{% with messages = get_flashed_messages() %}
{% if messages %}
<ul class=flashes>
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
{% block body %}{% endblock %}
在该目录下新建 index.html 页面,这个页面继承于 base.html 页面:
{% extends "base.html" %}
{% block body %}
<h1>Overview</h1>
<p>Do you want to <a href="{{ url_for('login') }}">log in?</a>
{% endblock %}
在该目录下新建 login.html 页面,这个页面也继承于 base.html 页面:
{% extends "base.html" %}
{% block body %}
<h1>Login</h1>
{% if error %}
<p class=error><strong>Error:</strong> {{ error }}
{% endif %}
<form method=post>
<dl>
<dt>Username:
<dd><input type=text name=username value="{{
request.form.username }}">
<dt>Password:
<dd><input type=password name=password>
</dl>
<p><input type=submit value=Login>
</form>
{% endblock %}
按照前面的方式运行程序:
当访问首页 http://127.0.0.1:5000,会提示 Do you want to log in?,点击链接跳转到登录页面。
在登录页面 http://127.0.0.1:5000/login,输入用户名和密码,如果输入错误的信息比如两个都为 shiyanlou,点击 Login,就会出现错误提示 Error: Invalid credentials。如果用户名输入 admin、密码输入 secret,点击 Login,就会跳转到首页,同时在首页会显示 flash 消息 You were successfully logged in。
7.5 日志和整合 WSGI 中间件
日志
有时候你会遇到一种情况:理论上来说你处理的数据应该是正确的,然而实际上并不正确的状况。比如你可能有一些客户端代码,代码向服务器发送一个 HTTP 请求但是显然它是错误的。这可能是由于用户篡改数据,或客户端代码失败。大部分时候针对这一情况返回400 Bad Request就可以了,但是有时候不能这样做,代码必须继续工作。
你也有可能想要记录一些发生的不正常事情。这时候日志就派上用处。从 Flask 0.3 开始日志记录是预先配置好的。
这里有一些日志调用的例子:
app.logger.debug('A value for debugging')
app.logger.warning('A warning occurred (%d apples)', 42)
app.logger.error('An error occurred')
附带的 logger 是一个标准的日志类 Logger ,因此更多的信息请查阅官方文档 logging documentation。
整合 WSGI 中间件
如果你想给你的应用添加 WSGI 中间件,你可以封装内部 WSGI 应用。例如如果你想使用 Werkzeug 包中的某个中间件来应付 lighttpd 中的 bugs,你可以这样做:
from werkzeug.contrib.fixers import LighttpdCGIRootFix
app.wsgi_app = LighttpdCGIRootFix(app.wsgi_app)
7 总结
本节讲了 flask 的重定向、响应、会话和扩展,重定向可以使用redirect(),错误处理可以使用errorhander装饰器,session对象保存会话信息,要使用会话需要设置secret_key,可以用make_response函数生成响应,flash可以用于消息闪现,flask 也能够整合 WSGI 中间件。
7 练习
请实现一个完整的用户登录功能:
当访问地址 http://127.0.0.1:5000/login ,出现登录页面,可以使用用户名和密码填写登录表单。
如果用户名和密码都为shiyanlou,那么就把用户名数据放到session中,把地址重定向到首页显示Hello shiyanlou,同时闪现消息 you were logged in。
如果用户名和密码不对,依然把地址重定向到首页显示hello world,同时闪现消息 username or password invalid。