路由 --- 知识点详解
一、注册路由
1.1 路由的概念
路由(Route)是指 URL 与视图函数之间的映射关系。当用户访问某个 URL 时,Flask 会根据路由表找到对应的视图函数来处理请求并返回响应。
1.2 注册路由的方式
方式一:使用 @app.route() 装饰器
python
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return '首页'
@app.route('/hello')
def hello():
return 'Hello, Flask!'
方式二:使用 app.add_url_rule() 方法
python
def index():
return '首页'
app.add_url_rule('/', view_func=index, endpoint='index')
1.3 app.route() 底层原理
@app.route('/') 装饰器本质上是调用了 app.add_url_rule() 方法,执行以下三步:
- 将 URL 规则(rule)和视图函数(view_func)的映射关系保存到
app.url_map中 - 为视图函数设置一个端点名(endpoint),默认为函数名
- 返回被装饰的视图函数本身
1.4 endpoint(端点)
- 每个路由规则都有一个唯一的 endpoint 标识
- 默认情况下,endpoint 就是视图函数的函数名
- 可以通过
endpoint参数自定义:
python
@app.route('/user', endpoint='user_profile')
def user():
return '用户页面'
- 作用 :endpoint 用于 URL 反向解析,通过
url_for()根据端点名生成 URL
1.5 路由注册注意事项
- 同一个 URL 不能注册多个视图函数(endpoint 不能重复)
- 同一个视图函数可以注册多个 URL
- URL 规则默认以
/开头
python
# 同一个函数绑定多个URL
@app.route('/')
@app.route('/index')
def index():
return '首页'
二、URL 传递参数
2.1 URL 参数的概念
URL 中可以携带动态参数,Flask 会将 URL 中的可变部分提取出来,作为参数传递给视图函数。
2.2 URL 传递参数的方式
方式一:在 URL 规则中使用变量规则(路径参数)
python
@app.route('/user/<username>')
def show_user(username):
return f'用户:{username}'
访问 /user/zhangsan,username 的值为 'zhangsan'
方式二:使用查询字符串(Query String)
python
from flask import request
@app.route('/search')
def search():
keyword = request.args.get('keyword', '')
return f'搜索关键词:{keyword}'
访问 /search?keyword=flask,通过 request.args 获取参数
2.3 两种方式的对比
| 特性 | 路径参数 | 查询字符串 |
|---|---|---|
| 语法 | <variable> |
?key=value |
| 获取方式 | 视图函数参数 | request.args.get() |
| 适用场景 | 资源标识(如用户ID) | 筛选、搜索、分页 |
| 是否必须 | 是(缺少则404) | 否(可选参数) |
| 示例URL | /user/123 |
/search?q=flask&page=1 |
三、为参数指定转换器
3.1 转换器的概念
转换器(Converter)用于对 URL 中的变量进行类型验证和转换,确保传入视图函数的参数是预期的类型。
3.2 内置转换器
| 转换器 | 说明 | 示例 | 匹配示例 |
|---|---|---|---|
string |
默认转换器,匹配任意不含斜杠的字符串 | <string:name> |
/user/tom → name='tom' |
int |
匹配正整数 | <int:user_id> |
/user/123 → user_id=123 |
float |
匹配正浮点数 | <float:price> |
/price/9.99 → price=9.99 |
path |
类似 string,但可以包含斜杠 | <path:filepath> |
/file/a/b/c.txt → filepath='a/b/c.txt' |
uuid |
匹配 UUID 字符串 | <uuid:id> |
/item/123e4567-e89b-12d3-a456-426614174000 |
any |
匹配多个可选值中的任意一个 | <any(apple,banana):fruit> |
/fruit/apple 或 /fruit/banana |
3.3 使用示例
python
@app.route('/user/<int:user_id>')
def get_user(user_id):
# user_id 自动转为 int 类型
return f'用户ID:{user_id},类型:{type(user_id).__name__}'
@app.route('/path/<path:subpath>')
def show_subpath(subpath):
return f'子路径:{subpath}'
@app.route('/color/<any(red, green, blue):color>')
def color_page(color):
return f'颜色:{color}'
3.4 自定义转换器
当内置转换器不满足需求时,可以自定义转换器:
python
from werkzeug.routing import BaseConverter
class RegexConverter(BaseConverter):
"""自定义正则转换器"""
def __init__(self, url_map, *items):
super().__init__(url_map)
self.regex = items[0] # 第一个参数作为正则表达式
# 注册自定义转换器
app.url_map.converters['regex'] = RegexConverter
@app.route('/phone/<regex("1[3-9]\\d{9}"):phone_number>')
def show_phone(phone_number):
return f'手机号:{phone_number}'
自定义转换器需要继承 werkzeug.routing.BaseConverter,并实现以下属性/方法:
regex属性:定义匹配规则的正则表达式to_python()方法:将 URL 中的字符串转换为 Python 对象to_url()方法:将 Python 对象转换为 URL 中的字符串(用于反向解析)
python
class ListConverter(BaseConverter):
def to_python(self, value):
return value.split('+')
def to_url(self, value):
return '+'.join(value)
app.url_map.converters['list'] = ListConverter
@app.route('/tags/<list:tags>')
def show_tags(tags):
return f'Tags: {tags}' # tags 是一个列表
四、处理请求
4.1 Request 对象
Flask 通过 request 对象封装了客户端发送的 HTTP 请求信息。使用前需导入:
python
from flask import request
4.2 Request 对象常用属性
| 属性/方法 | 说明 | 示例 |
|---|---|---|
request.method |
请求方法(GET/POST等) | 'GET'、'POST' |
request.url |
完整的请求 URL | 'http://example.com/path?q=1' |
request.base_url |
不含查询字符串的 URL | 'http://example.com/path' |
request.host |
主机名(含端口) | 'example.com:5000' |
request.path |
请求路径 | '/path' |
request.full_path |
路径+查询字符串 | '/path?q=1' |
request.args |
查询字符串参数(ImmutableMultiDict) | request.args.get('q') |
request.form |
表单数据(POST请求) | request.form.get('username') |
request.data |
未解析的原始请求体数据 | b'raw data' |
request.json |
JSON 格式的请求体(Content-Type为application/json) | {'key': 'value'} |
request.values |
合并 args 和 form | request.values.get('key') |
request.headers |
请求头 | request.headers.get('User-Agent') |
request.cookies |
Cookie 字典 | request.cookies.get('session_id') |
request.files |
上传的文件 | request.files['avatar'] |
request.remote_addr |
客户端 IP 地址 | '127.0.0.1' |
4.3 获取查询字符串参数
python
@app.route('/search')
def search():
# 获取单个参数,第二个参数为默认值
keyword = request.args.get('keyword', '')
page = request.args.get('page', 1, type=int)
# 获取同名参数的多个值
tags = request.args.getlist('tags') # /search?tags=python&tags=flask
return f'keyword={keyword}, page={page}, tags={tags}'
4.4 获取表单数据
python
@app.route('/login', methods=['POST'])
def login():
username = request.form.get('username')
password = request.form.get('password')
return f'登录用户:{username}'
4.5 获取 JSON 数据
python
@app.route('/api/data', methods=['POST'])
def receive_data():
data = request.get_json() # 或 request.json
name = data.get('name')
return f'收到数据:{name}'
4.6 文件上传
python
from flask import request
from werkzeug.utils import secure_filename
@app.route('/upload', methods=['POST'])
def upload():
f = request.files['file']
if f:
filename = secure_filename(f.filename) # 安全处理文件名
f.save(f'uploads/{filename}')
return '上传成功'
return '未选择文件'
五、指定请求方式
5.1 HTTP 请求方法
| 方法 | 说明 | 典型用途 |
|---|---|---|
GET |
获取资源 | 查看页面、获取数据 |
POST |
提交数据 | 提交表单、上传文件 |
PUT |
更新资源(完整替换) | 更新用户信息 |
PATCH |
部分更新资源 | 修改部分字段 |
DELETE |
删除资源 | 删除记录 |
HEAD |
获取响应头(不返回body) | 检查资源是否存在 |
OPTIONS |
获取支持的请求方法 | CORS 预检请求 |
5.2 指定请求方式
Flask 路由默认只响应 GET 请求。通过 methods 参数指定允许的请求方法:
python
@app.route('/user', methods=['GET', 'POST'])
def user():
if request.method == 'GET':
return '获取用户列表'
elif request.method == 'POST':
return '创建新用户'
5.3 使用单独的装饰器
python
@app.route('/user', methods=['GET'])
def get_users():
return '用户列表'
@app.route('/user', methods=['POST'])
def create_user():
return '创建用户'
注意 :以上写法 endpoint 会冲突(都是
get_users和create_user的函数名不同但绑定在同一 URL 上,Flask 会正常处理,因为它们的 endpoint 不同)。
5.4 RESTful 风格示例
python
@app.route('/api/articles', methods=['GET'])
def list_articles():
return '文章列表'
@app.route('/api/articles', methods=['POST'])
def create_article():
return '创建文章'
@app.route('/api/articles/<int:article_id>', methods=['GET'])
def get_article(article_id):
return f'文章详情:{article_id}'
@app.route('/api/articles/<int:article_id>', methods=['PUT'])
def update_article(article_id):
return f'更新文章:{article_id}'
@app.route('/api/articles/<int:article_id>', methods=['DELETE'])
def delete_article(article_id):
return f'删除文章:{article_id}'
六、请求钩子
6.1 请求钩子的概念
请求钩子(Hook)是在请求处理的不同阶段执行的函数。通过装饰器注册,可以在请求前、响应后等阶段执行通用逻辑(如数据库连接、身份认证、日志记录等)。
6.2 四种请求钩子
| 钩子 | 装饰器 | 执行时机 | 典型用途 |
|---|---|---|---|
before_first_request |
@app.before_first_request |
第一个请求到达前执行一次 | 初始化操作(已废弃于Flask 2.3) |
before_request |
@app.before_request |
每个请求到达视图函数前执行 | 身份验证、数据库连接、权限检查 |
after_request |
@app.after_request |
每个请求执行视图函数后、响应返回前执行 | 修改响应、设置响应头、日志记录 |
teardown_request |
@app.teardown_request |
每个请求结束后执行(即使发生异常) | 清理资源、关闭数据库连接 |
此外还有应用级别的:
before_first_request→ 应用级别(已废弃)teardown_appcontext→ 应用上下文销毁时执行
6.3 钩子的使用示例
python
from flask import Flask, request, g
import time
app = Flask(__name__)
# before_request:每个请求前执行
@app.before_request
def before_request_func():
# 记录请求开始时间
g.start_time = time.time()
# 身份验证检查
if request.endpoint not in ('login', 'register'):
token = request.headers.get('Authorization')
if not token:
return '未授权访问', 401
# after_request:每个请求后执行
@app.after_request
def after_request_func(response):
# 添加自定义响应头
response.headers['X-Custom-Header'] = 'FlaskApp'
# 计算请求耗时
if hasattr(g, 'start_time'):
elapsed = time.time() - g.start_time
response.headers['X-Response-Time'] = f'{elapsed:.4f}s'
return response # 必须返回 response 对象
# teardown_request:请求结束后执行(即使出错也会执行)
@app.teardown_request
def teardown_request_func(exception):
if exception:
app.logger.error(f'请求异常:{exception}')
# 清理资源
6.4 钩子执行顺序
请求进入
→ before_request(按注册顺序)
→ 视图函数执行
→ after_request(按注册的**逆序**)
响应返回
→ teardown_request(按注册的**逆序**)
6.5 before_request 中止请求
如果 before_request 返回了非 None 的响应对象,后续的 before_request 和视图函数将不会执行,该响应会直接作为最终响应返回:
python
@app.before_request
def check_maintenance():
if app.config.get('MAINTENANCE_MODE'):
return '系统维护中,请稍后再试', 503
七、上下文
7.1 上下文的概念
上下文(Context)是 Flask 中一个核心概念,用于在特定环境中临时提供某些全局可访问的对象。Flask 使用上下文机制来确保线程安全。
7.2 两种上下文
7.2.1 应用上下文(Application Context)
| 对象 | 说明 |
|---|---|
current_app |
当前应用实例,用于访问应用配置、日志等 |
g |
临时存储对象,在一次请求周期内存储数据 |
7.2.2 请求上下文(Request Context)
| 对象 | 说明 |
|---|---|
request |
封装客户端 HTTP 请求信息 |
session |
用户会话,基于 Cookie 实现,存储键值对数据 |
7.3 上下文对象的生命周期
| 对象 | 创建时机 | 销毁时机 |
|---|---|---|
request |
请求到达时 | 响应返回后 |
session |
请求到达时(从Cookie解析) | 响应返回后(写入Cookie) |
current_app |
推入应用上下文时 | 弹出应用上下文时 |
g |
推入应用上下文时 | 应用上下文销毁时 |
7.4 g 对象
g(global 的缩写)是存储请求级别数据的容器,在整个请求处理过程中可访问,请求结束后自动销毁:
python
from flask import g
@app.before_request
def load_user():
g.db = connect_database() # 在 g 上存储数据库连接
g.user = get_current_user() # 在 g 上存储当前用户
@app.route('/profile')
def profile():
user = g.user # 在视图函数中访问
db = g.db
return f'用户:{user.name}'
@app.teardown_request
def close_db(exception):
db = g.pop('db', None)
if db:
db.close()
7.5 session 对象
python
from flask import session
app.secret_key = 'your-secret-key' # 必须设置密钥
@app.route('/login', methods=['POST'])
def login():
username = request.form.get('username')
session['username'] = username # 存储到session
session.permanent = True # 设置持久化
return '登录成功'
@app.route('/profile')
def profile():
username = session.get('username') # 从session读取
if username:
return f'欢迎,{username}'
return '请先登录'
@app.route('/logout')
def logout():
session.pop('username', None) # 清除session
return '已退出登录'
7.6 上下文的工作原理
Flask 使用 上下文栈(Context Stack)来管理上下文:
-
当请求到达时,Flask 自动将应用上下文和请求上下文推入各自的栈中
-
在视图函数中可以直接访问
current_app、request、g、session -
请求处理完毕后,上下文从栈中弹出,这些对象将不可访问
应用上下文栈: 请求上下文栈:
┌──────────┐ ┌──────────┐
│ current_app │ │ request │
│ g │ │ session │
└──────────┘ └──────────┘
7.7 手动推送上下文
在非请求环境中(如命令行脚本)使用 Flask 对象时,需要手动推送上下文:
python
with app.app_context():
# 在此上下文中可以使用 current_app
current_app.logger.info('手动推送的上下文')
print(current_app.config['DEBUG'])
八、处理响应
8.1 响应报文
HTTP 响应报文由以下部分组成:
HTTP/1.1 200 OK ← 状态行
Content-Type: text/html; charset=utf-8 ← 响应头
Content-Length: 1234
Set-Cookie: session=abc123
X-Custom-Header: FlaskApp
← 空行(分隔头和体)
<!DOCTYPE html> ← 响应体
<html>
...
</html>
常见 HTTP 状态码
| 状态码 | 含义 | 说明 |
|---|---|---|
200 |
OK | 请求成功 |
201 |
Created | 资源创建成功 |
204 |
No Content | 成功但无返回内容 |
301 |
Moved Permanently | 永久重定向 |
302 |
Found | 临时重定向 |
304 |
Not Modified | 资源未修改,使用缓存 |
400 |
Bad Request | 请求参数错误 |
401 |
Unauthorized | 未认证 |
403 |
Forbidden | 无权限 |
404 |
Not Found | 资源不存在 |
405 |
Method Not Allowed | 请求方法不允许 |
500 |
Internal Server Error | 服务器内部错误 |
8.2 生成响应的方式
方式一:直接返回字符串
python
@app.route('/')
def index():
return 'Hello, World!' # Flask 自动包装为 200 响应
方式二:返回元组
python
@app.route('/')
def index():
# (响应体, 状态码)
return 'Not Found', 404
# (响应体, 状态码, 响应头)
return 'Hello', 200, {'X-Custom': 'value'}
# (响应体, 响应头字典)
return 'Hello', {'X-Custom': 'value'}
方式三:使用 make_response() 构造完整响应对象
python
from flask import make_response
@app.route('/')
def index():
response = make_response('Hello, World!')
response.status_code = 200
response.headers['X-Custom-Header'] = 'FlaskApp'
response.headers['Content-Type'] = 'text/plain; charset=utf-8'
response.set_cookie('username', 'zhangsan')
return response
方式四:返回 JSON 响应
python
from flask import jsonify
@app.route('/api/user')
def get_user():
return jsonify({
'name': '张三',
'age': 25,
'email': 'zhangsan@example.com'
})
# 自动设置 Content-Type: application/json
# 也可以直接返回字典(Flask 2.0+)
@app.route('/api/data')
def get_data():
return {'key': 'value', 'count': 42}
方式五:返回模板渲染结果
python
from flask import render_template
@app.route('/')
def index():
return render_template('index.html', title='首页', user='张三')
方式六:返回文件响应
python
from flask import send_file, send_from_directory
@app.route('/download')
def download():
return send_file('files/report.pdf', as_attachment=True)
@app.route('/uploads/<filename>')
def uploaded_file(filename):
return send_from_directory('uploads', filename)
8.3 Response 对象的常用属性和方法
| 属性/方法 | 说明 |
|---|---|
response.status_code |
状态码 |
response.headers |
响应头(字典类型) |
response.data |
响应体(bytes 类型) |
response.content_type |
内容类型 |
response.set_cookie(key, value) |
设置 Cookie |
response.delete_cookie(key) |
删除 Cookie |
response.get_data(as_text=True) |
获取响应体文本 |
九、URL 反向解析
9.1 URL 反向解析的概念
URL 反向解析是指通过 endpoint 名称(而非硬编码 URL 字符串)来生成 URL 地址。使用 url_for() 函数实现。
9.2 url_for() 的基本用法
python
from flask import url_for
@app.route('/')
def index():
return '首页'
@app.route('/user/<int:user_id>')
def user_profile(user_id):
return f'用户 {user_id}'
python
# 在视图函数中使用
with app.test_request_context():
print(url_for('index')) # 输出:/
print(url_for('user_profile', user_id=1)) # 输出:/user/1
print(url_for('user_profile', user_id=3, page=2)) # 输出:/user/3?page=2
9.3 url_for() 的参数处理
- URL 规则中的参数会被拼接到路径中
- 多余的参数会被添加为查询字符串
python
url_for('user_profile', user_id=5, tab='posts')
# 输出:/user/5?tab=posts
9.4 url_for() 在模板中使用
html
<a href="{{ url_for('user_profile', user_id=current_user.id) }}">
个人主页
</a>
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
<script src="{{ url_for('static', filename='js/app.js') }}"></script>
<form action="{{ url_for('login') }}" method="post">
...
</form>
9.5 url_for() 的优势
| 对比 | 硬编码 URL | url_for() |
|---|---|---|
| 修改 URL 规则 | 需要全局替换 | 自动更新 |
| 可维护性 | 差 | 好 |
| 生成外部URL | 不支持 | url_for('index', _external=True) → http://localhost:5000/ |
| 静态文件 | 手动拼接 | url_for('static', filename='...') |
9.6 生成外部 URL
python
url_for('index', _external=True)
# 输出:http://127.0.0.1:5000/
9.7 指定 URL scheme
python
url_for('index', _external=True, _scheme='https')
# 输出:https://127.0.0.1:5000/
十、页面重定向
10.1 重定向的概念
重定向(Redirect)是服务器告知客户端(浏览器)去访问另一个 URL 的机制。服务器返回 3xx 状态码和 Location 响应头,浏览器自动跳转。
10.2 redirect() 函数
python
from flask import redirect, url_for
@app.route('/old-page')
def old_page():
return redirect(url_for('new_page')) # 重定向到新页面
@app.route('/new-page')
def new_page():
return '这是新页面'
10.3 常见重定向场景
场景一:登录后重定向
python
from flask import redirect, url_for, request, session
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
# 验证登录...
session['user'] = request.form.get('username')
next_url = request.args.get('next') # 获取登录前的URL
return redirect(next_url or url_for('index'))
return '登录页面'
@app.route('/admin')
def admin():
if 'user' not in session:
return redirect(url_for('login', next=request.url))
return '管理后台'
场景二:表单提交后重定向(PRG 模式)
python
@app.route('/add', methods=['GET', 'POST'])
def add():
if request.method == 'POST':
# 处理表单数据...
# Post/Redirect/Get 模式,防止重复提交
return redirect(url_for('success'))
return '表单页面'
@app.route('/success')
def success():
return '操作成功'
场景三:自定义重定向状态码
python
@app.route('/permanent-move')
def moved():
return redirect(url_for('new_location'), code=301) # 永久重定向
@app.route('/temp-move')
def temp_moved():
return redirect(url_for('new_location'), code=302) # 临时重定向(默认)
10.4 abort() 函数
用于主动抛出 HTTP 错误:
python
from flask import abort
@app.route('/admin')
def admin():
if not is_admin():
abort(403) # 抛出403错误(Forbidden)
return '管理后台'
@app.route('/item/<int:item_id>')
def get_item(item_id):
item = find_item(item_id)
if not item:
abort(404) # 抛出404错误
return f'物品:{item.name}'
10.5 自定义错误页面
python
@app.errorhandler(404)
def page_not_found(error):
return render_template('404.html'), 404
@app.errorhandler(403)
def forbidden(error):
return render_template('403.html'), 403
@app.errorhandler(500)
def internal_error(error):
return render_template('500.html'), 500
十一、本章小结
核心知识点回顾
| 知识点 | 核心内容 |
|---|---|
| 注册路由 | @app.route() 和 app.add_url_rule();endpoint 端点概念 |
| URL 传参 | 路径参数 <type:var> 和查询字符串 request.args 两种方式 |
| 转换器 | 内置:string、int、float、path、uuid、any;可自定义 |
| 处理请求 | request 对象的属性和方法:args、form、json、files、headers、cookies |
| 请求方式 | 通过 methods 参数指定 GET、POST、PUT、DELETE 等 |
| 请求钩子 | before_request、after_request、teardown_request 的执行时机和用途 |
| 上下文 | 应用上下文(current_app、g)和请求上下文(request、session) |
| 处理响应 | 字符串、元组、make_response()、jsonify()、模板渲染等多种方式 |
| URL 反向解析 | url_for() 根据 endpoint 生成 URL,避免硬编码 |
| 页面重定向 | redirect() 重定向和 abort() 终止请求 |
关键导入汇总
python
from flask import (
Flask, # 应用实例
request, # 请求对象
session, # 会话对象
g, # 请求级临时存储
current_app, # 当前应用实例
url_for, # URL反向解析
redirect, # 重定向
abort, # 中止请求
make_response, # 构造响应对象
jsonify, # JSON响应
render_template # 模板渲染
)