路由
客户端把请求发送给Web服务器,Web服务器再把请求发送给程序实例。程序实例需要知道对每个URL请求运行哪些代码,所以保存了一个 URL 到 Python
函数的映射关系。处理URL和函数之间关系的程序称路由。
Flask中使用 app.route
装饰器,把装饰器的函数注册到路由。
@app.route('/')
def index():
return '<h1> hello </h1>'
这个例子将 index()
函数注册为程序根 '/'
地址。
访问 http://localhost:5000/
后,会触发服务器执行 index()
函数,这个函数的返回值称为响应,是客户端接收到的内容。
像 index()
这样的函数称为视图函数。视图函数返回的响应可以是包含 HTML 的简单字符串,也可以是复杂的表单。
动态路由
如 Github 中,登录到某人的主页, 这时URL为 https://github.com/<user-name>
,用户名 user-name
是地址的一部分。
在 Flask
中,只需要在 route
装饰器中使用特殊的句法即可。
@app.route('/user/<name>')
def user(name):
return '<h1> hello, %s </h1>' % name
尖括号中的内容就是动态部分,任何匹配静态部分的URL都会映射到这个路由上。调用视图函数时,Flask
会将动态部分作为参数传入函数中。
路由中的动态部分默认使用字符串,不过也可以使用类型定义:int float path
类型。
例如: /user/<int:id>
path
类型也是字符串,但不把斜线视作分隔符。
程序和请求上下文
flask
从客户端收到请求时, 要让视图函数能访问一些对象, 这样才能处理请求。 request
就是一个很好的例子, 它封装了客户端发送的 HTTP 请求。
要想让视图函数能够访问请求对象, 一个显而易见的方式是将其作为参数传入视图函数,不过这会导致程序中的每个视图函数都增加一个参数。除了访问请求对象, 如果视图函数在处理请求时还要访问其他对象,情况会变得更糟。
为了避免大量可有可无的参数把视图函数弄得一团糟, flask
使用 上下文 临时把某些对象变为全局可访问。
from flask import request
@app.route('/')
def index():
user_agent = request.header.get('User-Agent')
return '<p> Your browser is %s </p>' % user_agent
注意这个视图函数中我们如何把 request
当作全局变量使用。事实上, request
不可能是全局变量。在多线程服务器中,多个线程同时处理不同客户端发送的不同请求时,每个线程看到的 request
对象必然不同。flask
使用上下文让特定变量在一个线程中全局可访问, 与此同时却不会干扰其他线程。
在 flask
中有两种上下文: 程序上下文、请求上下文
变量名 | 上下文 | 说明 |
---|---|---|
current_app | 程序上下文 | 当前激活程序的程序实例 |
g | 程序上下文 | 处理请求时用作临时存储的对象,每次请求都会重设这个变量 |
request | 请求上下文 | 请求对象,封装了客户端发出的HTTP请求中的内容 |
session | 请求上下文 | 用户会话,用于存储请求之间需要“记住”的值的词典 |
flask
在分发请求之前激活(或推送)程序和请求上下文,请求处理完成后再将其删除。程序上下文被推送后,就可以在线程中使用 current_app
和 g
变量。类似的,请求上下文被推送后,就可以使用 request
和 session
变量。如果使用这些变量时我们没有激活程序上下文或请求上下文,就会导致错误。
请求调度
程序收到客户端发来的请求时, 要找到处理该请求的视图函数。为了完成这个任务, flask
会在程序的 URL映射 中查找请求的 URL。 URL映射 是 URL 和 视图函数之间的对应关系。 flask
使用 app.route
装饰器或者非装饰器形式的 app.add_url_rule()
生成映射。
可以使用 app.url_map
查看 URL 映射。
>>> from hello import app
>>> app.url_map
Map([<Rule '/' (GET, HEAD, OPTIONS) -> index>,
<Rule '/static/<filename>' (GET, HEAD, OPTIONS) -> static>,
<Rule '/user/<name>' (GET, HEAD, OPTIONS) -> user>])
/
和 /user/<name>
路由是在程序中使用 app.route
装饰器定义。 /static/<filename>
路由是 flask
添加的特殊路由,用于访问静态文件。
URL 映射中 HEAD、Options、GET
是请求方法,由路由进行处理。flask
为每个路由都指定了请求方法,这样不同的请求方法发送到相同的 URL 上时,会使用不同的视图函数进行处理。 HEAD
和 OPTIONS
方法由 flask
自动处理。
请求钩子
有时在处理请求之前或之后执行代码会很有用。例如,在请求开始时,我们可能需要创建数据库连接或认证发起请求的用户。为了避免在每个视图函数中都使用重复的代码, flask 提供了注册通用函数的功能, 注册的函数可在请求被分发到视图函数之前或之后调用。
请求钩子使用装饰器实现,flask 支持以下4中钩子
before_first_request
: 注册一个函数,在处理第一个请求之前运行。before_request
: 注册一个函数,在每次请求之前运行。after_request
: 注册一个函数,如果没有未处理的异常抛出,在每次请求之后运行。teardown_request
: 注册一个函数,即使有未处理的异常抛出,也在每次请求之后运行。
在请求钩子函数和视图函数之间共享数据一般使用上下文全局变量 g
。 例如, before_request
处理程序可以从数据库中加载已登录用户, 并将其保存到 g.user
中。随后调用视图函数时, 视图函数再使用 g.user
获取用户。
响应
flask 调用视图函数后,会将其返回值作为响应的内容。大多数情况下,响应就是一个简单的字符串,作为 HTML 页面回送客户端。
但 HTTP 协议还需要返回 状态码, flask 默认设为 200, 这个代码表明请求已经被成功处理。如果视图函数返回的响应需要使用不同的状态码, 那么可以把数字代码作为第二个返回值, 添加到响应文本之后:
@app.route('/')
def index():
return '<h1>Bad Request</h1>', 400
flask 视图函数还可以返回Response对象。 make_response() 函数可以接收 1个、2个或3个参数,并返回一个 Response 对象。下例创建了一个响应对象,然后设置了 cookie:
from flask import make_response
@app.route('/')
def index():
response = make_response('<h1>Content</h1>')
response.set_cookie('answer', '42')
return response
还有一种特殊响应类型:重定向。这种响应没有页面文档,只告诉浏览器一个新地址用以加载新页面。
flask
提供了 redirect()
辅助函数,用以生成这种响应:
from flask import redirect
@app.route('/')
def index():
return redirect('http://www.example.com')
还有一种处理错误的特殊响应,由 abort
函数生成,在下面例子中,如果 URL 中的动态参数 id
对应的用户不存在,就返回状态码 404
:
from flask import abort
@app.route('/user/<id>')
def get_user(id):
user = load_user(id)
if not user:
abort(404)
return '<h1> Hello, %s</h1>' % user.name
abort
不会把控制权交还给调用它的函数,而是抛出异常把控制权交给 Web服务器。
模版
模版是一个包含响应文本的文件,其中包含用占位变量表示的动态部分,其具体指只在请求的上下文中才能知道。使用真实值替换变量,再返回最终得到的响应字符串,这一过程称为 渲染 。为了渲染模版, Flask
使用了一个名为 Jinja2
的强大模版引擎。
Jinja2 模版引擎
形式最简单的Jinja2模版就是一个包含响应文本的文件。
示例 1 templates/index.html
<h1>Hello World!</h1>
示例 2 templates/user.html
<h1>Hello, {{ name }}!</h1>
示例2 中包含一个使用变量表示的动态部分 {{ name }}
渲染模版
默认情况下, Flask
在程序文件夹中的 templates
子文件夹中寻找模版。我们将 示例1 和 示例2 分别命名为 index.html
和 user.html
存放在 templates
文件夹下。
视图函数对应为:
from flask import Flask, render_template
#...
@app.route('/')
def index():
return render_template('index.html')
@app.route('/user/<name>')
def user(name):
return render_template('user.html', name=name)
Flask
提供的 render_template
函数把 Jinja2
模版引擎集成到程序中。 render_template
函数的第一个参数是模版的文件名。随后的参数都是键值对, 表示模版中变量对应的真实值。在这段代码中,第二个模版收到一个名为 name
的变量。
视图函数中 name=name
是关键字参数,左边的 name
表示参数名,就是模版中的占位符;右边的 name
是当前作用域中的变量,表示同名参数的值。
变量
示例2 在模版中使用的 {{ name }}
结构表示一个变量,它是一种特殊的占位符, 告诉模版引擎这个位置的值从渲染模版时使用的数据中获取。
Jinja2
能识别所有类型的变量,甚至 列表、字典和对象。在模版中使用变量的一些示例如下:
<p>A vakue from a dictionary: {{ mydict['key'] }}.</p>
<p>A vakue from a list: {{ mylist[3] }}.</p>
<p>A vakue from a list, with a variable index: {{ mylist[myintvar] }}.</p>
<p>A vakue from a object's method: {{ myobj.somemethod() }}.</p>
可以使用 过滤器 修改变量, 过滤器名添加在变量名之后, 中间使用竖线分隔。例如,下述模版以首字母大写形式显示变量 name
值:
Hello, {{ name|capitalize }}
常用过滤器:
过滤器名 | 说明 |
---|---|
safe | 渲染值时不转义 |
capitalize | 把值的首字母转换成大写,其他字母转换成小写 |
lower | 把值转换成小写形式 |
upper | 把值转换成大写形式 |
title | 把值中的每个单词的首字母都转成大写 |
trim | 把值的首尾空格去掉 |
scriptags | 渲染之前把值中所有的 HTML 标签都删掉 |
注意:千万别在不可信的值上使用 safe
过滤器,例如用户在表单中输入的文本。
默认情况下,出于安全考虑, Jinja2
会转移所有变量。例如, 如果一个变量的值为 '<h1>Hello</h1>'
,Jinja2
会将其渲染成 '<h1>Hello</h1>'
,浏览器能显示 h1
元素,但不会进行解释。
控制结构
Jinja2 提供了多种控制结构,可用来改变模版的渲染流程。
条件控制语句
{% if user %}
Hello, {{ user }}
{% else %}
Hello, Stranger!
{% endif %}
for 循环
<ul>
{% for comment in comments %}
<li>{{ comment }}</li>
{% endfor %}
</ul>
宏
宏类似 python
中的函数
{% macro render_comment(comment) %}
<li>{{ comment }}</li>
{% endmacro %}
<ul>
{% for comment in comments %}
{{ render_comment(comment) }}
{% endfor %}
</ul>
包含
需要在多出重复使用的模版代码片段可以写入单独的文件,再包含在所有模块中,以避免重复:
{% include 'common.html' %}
继承
另一种重复使用代码的强大方式是模版集成,它类似与 Python 代码中的类继承。首先, 创建一个名为 base.html 的基模版:
<html>
<head>
{% block head %}
<title>{% block title%}{% endblock %} - My Application<title>
{% endblock %}
</head>
<body>
{% block body %}
{% endblock %}
</body>
</head>
block
标签定义的元素可在衍生模版中修改。在 base.html
中,我们定义了名为 head
、title
和 body
的块。注意, title
包含在 head
中。 下面这个示例是基模版的衍生模版:
{% extends "base.html" %}
{% block title %}Index{% endblock %}
{% block head %}
{{ supper() }}
<style>
</style>
{% endblock %}
{% block body %}
<h1>Hello, World!</h1>
{% endblock %}
extends
指令声明这个模版衍生自 base.html
。在 extends
指令后, 基模版中的 3 个块被重新定义, 模版引擎会将其插入适当的位置。注意新定义的 head
块, 在基模版中其内容不是空的, 所以使用 super()
获取原来的内容。
链接
任何具有多个路由的程序都需要可以连接不同页面的链接,例如导航条。
在模板中直接编写简单路由的 URL
链接不难,但对于包含可变部分的动态路由,在模板
中构建正确的 URL
就很困难。而且,直接编写 URL
会对代码中定义的路由产生不必要的
依赖关系。如果重新定义路由,模板中的链接可能会失效。
为了避免这些问题,Flask
提供了 url_for()
辅助函数,它可以使用程序URL 映射中保存
的信息生成 URL
。
url_for()
函数最简单的用法是以视图函数名(或者app.add_url_route()
定义路由时使用
的端点名)作为参数,返回对应的URL。
例如:程序中调用 url_for('index')
得到的结果是 /
。调用 url_for('index', _external=True)
返回的则是绝对地址,比如 http://localhost:5000/
。
使用url_for() 生成动态地址时, 将动态部分作为关键字参数传入。例如,url_for('user', name='john', _external=True)
的返回结果是 http://localhost:5000/user/john
。传入 url_for()
的关键字参数不仅限于动态路由中的参数。函数能将任何额外参数添加到查询字符串中。例如,url_for('index', page=2)
的返回结果是 /?page=2
。
静态文件
Web 程序不是仅由 Python
代码和模板组成。大多数程序还会使用静态文件,例如 HTML 代码中引用的图片、JavaScript
源码文件和 CSS
。
对静态文件的引用被当成一个特殊的路由,即 /static/<filename>
。例如,调用 url_for('static', filename='css/styles.css', _external=True)
得到的结果是 http://localhost:5000/static/css/styles.css
。
默认设置下,Flask
在程序根目录中名为 static
的子目录中寻找静态文件。如果需要,可在 static
文件夹中使用子文件夹存放文件。服务器收到前面那个URL 后,会生成一个响应,包含文件系统中static/css/styles.css
文件的内容。
示例1 展示了如何在程序的基模板中放置 favicon.ico
图标。这个图标会显示在浏览器的地址栏中。
示例1 templates/base.html
定义收藏夹图标
{% block head %}
{{ super() }}
<link rel="shortcut icon" href="{{ url_for('static', filename = 'favicon.ico') }}"
type="image/x-icon">
<link rel="icon" href="{{ url_for('static', filename = 'favicon.ico') }}"
type="image/x-icon">
{% endblock %}
图标的声明会插入 head
块的末尾。注意如何使用 super()
保留基模板中定义的块的原始内容。