路由与视图函数
2026/3/20大约 12 分钟
路由与视图函数
第一章:路由基础
什么是路由?
路由是 URL 模式与视图函数之间的映射关系。当用户访问某个 URL 时,Flask 会查找匹配的路由,并调用对应的视图函数处理请求。
基本路由定义
from flask import Flask
app = Flask(__name__)
# 基本路由
@app.route('/')
def index():
return 'Home Page'
# 指定路径
@app.route('/about')
def about():
return 'About Page'
# 多级路径
@app.route('/user/profile')
def user_profile():
return 'User Profile'
HTTP 方法
from flask import Flask, request
app = Flask(__name__)
# 默认只接受 GET 请求
@app.route('/get-only')
def get_only():
return 'GET request'
# 指定多个方法
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
return 'Processing login...'
return 'Login form'
# 所有常用 HTTP 方法
@app.route('/resource', methods=['GET', 'POST', 'PUT', 'PATCH', 'DELETE'])
def resource():
if request.method == 'GET':
return 'Get resource'
elif request.method == 'POST':
return 'Create resource'
elif request.method == 'PUT':
return 'Update resource (full)'
elif request.method == 'PATCH':
return 'Update resource (partial)'
elif request.method == 'DELETE':
return 'Delete resource'
方法装饰器(Flask 2.0+)
from flask import Flask
app = Flask(__name__)
@app.get('/items')
def get_items():
return 'List items'
@app.post('/items')
def create_item():
return 'Create item'
@app.put('/items/<int:item_id>')
def update_item(item_id):
return f'Update item {item_id}'
@app.patch('/items/<int:item_id>')
def patch_item(item_id):
return f'Patch item {item_id}'
@app.delete('/items/<int:item_id>')
def delete_item(item_id):
return f'Delete item {item_id}'
第二章:动态路由
基本变量规则
from flask import Flask
app = Flask(__name__)
# 字符串参数(默认)
@app.route('/user/<username>')
def show_user(username):
return f'User: {username}'
# 整数参数
@app.route('/post/<int:post_id>')
def show_post(post_id):
return f'Post ID: {post_id} (type: {type(post_id).__name__})'
# 浮点数参数
@app.route('/price/<float:price>')
def show_price(price):
return f'Price: {price}'
# 路径参数(包含斜杠)
@app.route('/path/<path:subpath>')
def show_path(subpath):
return f'Path: {subpath}'
# UUID 参数
@app.route('/item/<uuid:item_id>')
def show_item(item_id):
return f'Item UUID: {item_id}'
转换器类型
| 转换器 | 说明 | 示例 |
|---|---|---|
string | 默认,任意不含斜杠的文本 | /user/<username> |
int | 正整数 | /post/<int:id> |
float | 正浮点数 | /price/<float:value> |
path | 类似 string,但包含斜杠 | /file/<path:filepath> |
uuid | UUID 字符串 | /item/<uuid:id> |
自定义转换器
from flask import Flask
from werkzeug.routing import BaseConverter
app = Flask(__name__)
# 自定义列表转换器
class ListConverter(BaseConverter):
def to_python(self, value):
return value.split(',')
def to_url(self, values):
return ','.join(values)
# 自定义正则转换器
class RegexConverter(BaseConverter):
def __init__(self, url_map, regex):
super().__init__(url_map)
self.regex = regex
# 手机号转换器
class PhoneConverter(BaseConverter):
regex = r'1[3-9]\d{9}'
# 注册转换器
app.url_map.converters['list'] = ListConverter
app.url_map.converters['regex'] = RegexConverter
app.url_map.converters['phone'] = PhoneConverter
# 使用自定义转换器
@app.route('/tags/<list:tags>')
def show_tags(tags):
return f'Tags: {tags}' # ['python', 'flask', 'web']
@app.route('/user/<regex("[a-z]+"):username>')
def show_user(username):
return f'User: {username}'
@app.route('/contact/<phone:number>')
def contact(number):
return f'Phone: {number}'
多个变量
@app.route('/user/<username>/post/<int:post_id>')
def user_post(username, post_id):
return f'User: {username}, Post: {post_id}'
@app.route('/date/<int:year>/<int:month>/<int:day>')
def show_date(year, month, day):
return f'Date: {year}-{month:02d}-{day:02d}'
第三章:URL 规则详解
尾部斜杠
# 带尾部斜杠:/about/ 是规范 URL
# 访问 /about 会重定向到 /about/
@app.route('/about/')
def about():
return 'About'
# 不带尾部斜杠:/contact 是规范 URL
# 访问 /contact/ 会返回 404
@app.route('/contact')
def contact():
return 'Contact'
严格斜杠模式
# 设置 strict_slashes=False 允许两种访问方式
@app.route('/page', strict_slashes=False)
def page():
return 'Page' # /page 和 /page/ 都可以访问
# 全局配置
app.url_map.strict_slashes = False
路由优先级
# Flask 按照定义顺序和规则特异性匹配路由
# 静态路由优先于动态路由
@app.route('/user/admin')
def admin_user():
return 'Admin user (static)'
@app.route('/user/<username>')
def show_user(username):
return f'User: {username} (dynamic)'
# 访问 /user/admin -> admin_user()
# 访问 /user/john -> show_user('john')
端点(Endpoint)
# 每个路由都有一个端点名称,默认是视图函数名
@app.route('/hello')
def hello():
return 'Hello'
# 端点名:'hello'
# 自定义端点名
@app.route('/greeting', endpoint='greet')
def hello_world():
return 'Hello, World!'
# 端点名:'greet'
# 使用端点构建 URL
from flask import url_for
with app.test_request_context():
print(url_for('hello')) # '/hello'
print(url_for('greet')) # '/greeting'
主机名和子域名匹配
# 需要设置 SERVER_NAME
app.config['SERVER_NAME'] = 'example.com:5000'
# 主域名路由
@app.route('/', host='example.com')
def main_index():
return 'Main site'
# 子域名路由
@app.route('/', subdomain='api')
def api_index():
return 'API site' # api.example.com
# 动态子域名
@app.route('/', subdomain='<tenant>')
def tenant_index(tenant):
return f'Tenant: {tenant}' # xxx.example.com
第四章:视图函数进阶
视图函数返回值
from flask import Flask, jsonify, make_response, redirect, url_for, render_template
app = Flask(__name__)
# 返回字符串
@app.route('/string')
def return_string():
return 'Hello, World!'
# 返回元组 (body, status)
@app.route('/tuple')
def return_tuple():
return 'Created', 201
# 返回元组 (body, status, headers)
@app.route('/full-tuple')
def return_full_tuple():
return 'OK', 200, {'X-Custom-Header': 'value'}
# 返回 Response 对象
@app.route('/response')
def return_response():
response = make_response('Custom Response')
response.headers['X-Custom'] = 'Value'
return response
# 返回 JSON
@app.route('/json')
def return_json():
return jsonify({'name': 'Flask', 'version': '3.0'})
# 返回字典(Flask 1.1+ 自动转 JSON)
@app.route('/dict')
def return_dict():
return {'name': 'Flask', 'version': '3.0'}
# 返回列表(Flask 2.2+)
@app.route('/list')
def return_list():
return [{'id': 1}, {'id': 2}]
# 返回重定向
@app.route('/redirect')
def return_redirect():
return redirect(url_for('index'))
# 返回模板
@app.route('/template')
def return_template():
return render_template('index.html', title='Home')
函数式路由注册
def index():
return 'Index'
def about():
return 'About'
def user_profile(username):
return f'User: {username}'
# 函数式注册路由
app.add_url_rule('/', 'index', index)
app.add_url_rule('/about', 'about', about)
app.add_url_rule('/user/<username>', 'profile', user_profile)
类视图
from flask import Flask, request
from flask.views import View, MethodView
app = Flask(__name__)
# 基础类视图
class HelloView(View):
def dispatch_request(self):
return 'Hello from class view!'
app.add_url_rule('/hello', view_func=HelloView.as_view('hello'))
# 带参数的类视图
class GreetingView(View):
def __init__(self, greeting):
self.greeting = greeting
def dispatch_request(self, name):
return f'{self.greeting}, {name}!'
app.add_url_rule(
'/greet/<name>',
view_func=GreetingView.as_view('greet', greeting='Hello')
)
# 方法视图(RESTful)
class UserAPI(MethodView):
def get(self, user_id=None):
if user_id is None:
return {'users': ['alice', 'bob']}
return {'user_id': user_id, 'name': f'User {user_id}'}
def post(self):
data = request.get_json()
return {'created': data}, 201
def put(self, user_id):
data = request.get_json()
return {'updated': user_id, 'data': data}
def delete(self, user_id):
return {'deleted': user_id}
# 注册方法视图
user_view = UserAPI.as_view('user_api')
app.add_url_rule('/users', defaults={'user_id': None},
view_func=user_view, methods=['GET'])
app.add_url_rule('/users', view_func=user_view, methods=['POST'])
app.add_url_rule('/users/<int:user_id>', view_func=user_view,
methods=['GET', 'PUT', 'DELETE'])
装饰器与视图
from functools import wraps
from flask import Flask, request, jsonify, g
app = Flask(__name__)
# 认证装饰器
def require_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
token = request.headers.get('Authorization')
if not token or not verify_token(token):
return jsonify({'error': 'Unauthorized'}), 401
return f(*args, **kwargs)
return decorated
# 日志装饰器
def log_request(f):
@wraps(f)
def decorated(*args, **kwargs):
app.logger.info(f'Request: {request.method} {request.path}')
return f(*args, **kwargs)
return decorated
# 应用装饰器(注意顺序:从下往上执行)
@app.route('/protected')
@require_auth
@log_request
def protected():
return 'Protected content'
# 装饰器执行顺序:log_request -> require_auth -> protected
第五章:请求数据处理
查询参数
from flask import Flask, request
app = Flask(__name__)
@app.route('/search')
def search():
# 获取单个参数
query = request.args.get('q', '')
page = request.args.get('page', 1, type=int)
per_page = request.args.get('per_page', 10, type=int)
# 获取多值参数
tags = request.args.getlist('tag') # ?tag=python&tag=flask
# 转换为字典
all_args = request.args.to_dict()
return {
'query': query,
'page': page,
'per_page': per_page,
'tags': tags
}
表单数据
from flask import Flask, request
app = Flask(__name__)
@app.route('/register', methods=['POST'])
def register():
# 获取表单字段
username = request.form.get('username')
password = request.form.get('password')
email = request.form.get('email')
# 获取多选值
interests = request.form.getlist('interests')
# 使用 request.values(form + args)
name = request.values.get('name')
return {
'username': username,
'email': email,
'interests': interests
}
JSON 数据
from flask import Flask, request
app = Flask(__name__)
@app.route('/api/data', methods=['POST'])
def receive_data():
# 方式一:get_json()(推荐)
data = request.get_json()
# force=True:忽略 Content-Type
data = request.get_json(force=True)
# silent=True:解析失败返回 None
data = request.get_json(silent=True)
# 方式二:request.json(属性)
data = request.json
# 安全获取嵌套数据
if data:
name = data.get('name')
email = data.get('user', {}).get('email')
return {'received': data}
文件上传
from flask import Flask, request
from werkzeug.utils import secure_filename
import os
app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = 'uploads'
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB
ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'}
def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
@app.route('/upload', methods=['POST'])
def upload_file():
# 检查文件是否存在
if 'file' not in request.files:
return {'error': 'No file part'}, 400
file = request.files['file']
# 检查文件名
if file.filename == '':
return {'error': 'No selected file'}, 400
# 验证文件类型
if not allowed_file(file.filename):
return {'error': 'File type not allowed'}, 400
# 安全处理文件名
filename = secure_filename(file.filename)
# 保存文件
filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
file.save(filepath)
return {'message': 'File uploaded', 'filename': filename}
# 多文件上传
@app.route('/upload-multiple', methods=['POST'])
def upload_multiple():
files = request.files.getlist('files')
filenames = []
for file in files:
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
filenames.append(filename)
return {'uploaded': filenames}
请求头和 Cookies
from flask import Flask, request, make_response
app = Flask(__name__)
@app.route('/headers')
def show_headers():
# 获取请求头
content_type = request.headers.get('Content-Type')
user_agent = request.headers.get('User-Agent')
auth = request.headers.get('Authorization')
custom = request.headers.get('X-Custom-Header')
# 遍历所有请求头
all_headers = dict(request.headers)
return {'headers': all_headers}
@app.route('/cookies')
def handle_cookies():
# 读取 Cookie
user_id = request.cookies.get('user_id')
preferences = request.cookies.get('preferences', '{}')
# 设置 Cookie
response = make_response({'user_id': user_id})
response.set_cookie(
'session_id',
value='abc123',
max_age=3600, # 过期时间(秒)
secure=True, # 仅 HTTPS
httponly=True, # 禁止 JS 访问
samesite='Lax' # SameSite 策略
)
# 删除 Cookie
response.delete_cookie('old_cookie')
return response
第六章:响应处理
自定义响应
from flask import Flask, Response, make_response, stream_with_context
app = Flask(__name__)
# 基础 Response
@app.route('/custom')
def custom_response():
return Response(
'Custom response body',
status=200,
mimetype='text/plain',
headers={'X-Custom': 'Header'}
)
# 使用 make_response
@app.route('/make')
def make():
response = make_response('Response body')
response.status_code = 200
response.mimetype = 'text/html'
response.headers['X-Custom'] = 'Value'
response.set_cookie('test', 'value')
return response
流式响应
from flask import Flask, Response, stream_with_context
import time
app = Flask(__name__)
# 生成器流式响应
def generate_data():
for i in range(10):
yield f'data: {i}\n\n'
time.sleep(1)
@app.route('/stream')
def stream():
return Response(
generate_data(),
mimetype='text/event-stream'
)
# 大文件下载
def generate_large_file():
with open('large_file.csv', 'rb') as f:
while chunk := f.read(8192):
yield chunk
@app.route('/download-large')
def download_large():
return Response(
generate_large_file(),
mimetype='text/csv',
headers={
'Content-Disposition': 'attachment; filename=data.csv'
}
)
# 使用 stream_with_context(保持请求上下文)
@app.route('/stream-context')
def stream_context():
@stream_with_context
def generate():
# 可以在这里访问 request 对象
user = request.args.get('user', 'anonymous')
for i in range(5):
yield f'User {user}: chunk {i}\n'
time.sleep(0.5)
return Response(generate(), mimetype='text/plain')
Server-Sent Events (SSE)
from flask import Flask, Response
import json
import time
app = Flask(__name__)
def event_stream():
count = 0
while True:
count += 1
data = json.dumps({'count': count, 'time': time.time()})
yield f'id: {count}\ndata: {data}\n\n'
time.sleep(1)
@app.route('/events')
def events():
return Response(
event_stream(),
mimetype='text/event-stream',
headers={
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
}
)
文件下载
from flask import Flask, send_file, send_from_directory
import io
app = Flask(__name__)
# 发送静态文件
@app.route('/download/<filename>')
def download_file(filename):
return send_from_directory(
'uploads',
filename,
as_attachment=True
)
# 发送动态生成的文件
@app.route('/generate-pdf')
def generate_pdf():
# 生成 PDF 内容
pdf_content = generate_pdf_content()
return send_file(
io.BytesIO(pdf_content),
mimetype='application/pdf',
as_attachment=True,
download_name='report.pdf'
)
# 条件下载
@app.route('/conditional-download/<filename>')
def conditional_download(filename):
return send_file(
f'files/{filename}',
conditional=True, # 支持 If-Modified-Since
etag=True # 生成 ETag
)
第七章:URL 构建
url_for 详解
from flask import Flask, url_for
app = Flask(__name__)
@app.route('/')
def index():
return 'Index'
@app.route('/user/<username>')
def profile(username):
return f'User: {username}'
@app.route('/post/<int:post_id>')
def show_post(post_id):
return f'Post: {post_id}'
# 在视图函数中使用
@app.route('/demo')
def demo():
# 基本用法
index_url = url_for('index') # '/'
# 带参数
profile_url = url_for('profile', username='john') # '/user/john'
# 带查询参数
search_url = url_for('index', page=1, q='flask') # '/?page=1&q=flask'
# 绝对 URL
abs_url = url_for('index', _external=True)
# 'http://localhost:5000/'
# 指定协议
https_url = url_for('index', _external=True, _scheme='https')
# 'https://localhost:5000/'
# 添加锚点
anchor_url = url_for('index', _anchor='section1') # '/#section1'
return {
'index': index_url,
'profile': profile_url,
'search': search_url,
'absolute': abs_url
}
# 静态文件 URL
@app.route('/page')
def page():
css_url = url_for('static', filename='css/style.css')
js_url = url_for('static', filename='js/app.js')
return f'''
<link rel="stylesheet" href="{css_url}">
<script src="{js_url}"></script>
'''
在模板中使用 url_for
<!-- templates/base.html -->
<!DOCTYPE html>
<html>
<head>
<link
rel="stylesheet"
href="{{ url_for('static', filename='css/style.css') }}"
/>
</head>
<body>
<nav>
<a href="{{ url_for('index') }}">Home</a>
<a href="{{ url_for('profile', username=current_user.username) }}"
>Profile</a
>
<a href="{{ url_for('search', q='flask') }}">Search</a>
</nav>
<form action="{{ url_for('login') }}" method="post">
<!-- 表单内容 -->
</form>
<script src="{{ url_for('static', filename='js/app.js') }}"></script>
</body>
</html>
第八章:路由高级特性
路由默认值
from flask import Flask
app = Flask(__name__)
# 为参数设置默认值
@app.route('/page/', defaults={'page_num': 1})
@app.route('/page/<int:page_num>')
def show_page(page_num):
return f'Page: {page_num}'
# /page/ -> page_num=1
# /page/5 -> page_num=5
路由别名
# 同一视图函数多个路由
@app.route('/index')
@app.route('/')
@app.route('/home')
def index():
return 'Home Page'
路由重定向
from flask import Flask, redirect, url_for
app = Flask(__name__)
# 简单重定向
@app.route('/old-path')
def old_path():
return redirect(url_for('new_path'))
@app.route('/new-path')
def new_path():
return 'New Path'
# 永久重定向 (301)
@app.route('/legacy')
def legacy():
return redirect(url_for('modern'), code=301)
# 带参数重定向
@app.route('/user-old/<int:user_id>')
def user_old(user_id):
return redirect(url_for('user_new', user_id=user_id))
@app.route('/user/<int:user_id>')
def user_new(user_id):
return f'User: {user_id}'
动态路由注册
from flask import Flask
app = Flask(__name__)
# 动态注册路由
def register_dynamic_routes(app, routes):
for route_config in routes:
def make_view(config):
def view(**kwargs):
return config['handler'](**kwargs)
return view
view = make_view(route_config)
view.__name__ = route_config['endpoint']
app.add_url_rule(
route_config['path'],
route_config['endpoint'],
view,
methods=route_config.get('methods', ['GET'])
)
# 使用
routes = [
{'path': '/api/v1/users', 'endpoint': 'users_v1', 'handler': lambda: {'version': 1}},
{'path': '/api/v2/users', 'endpoint': 'users_v2', 'handler': lambda: {'version': 2}},
]
register_dynamic_routes(app, routes)
路由钩子
from flask import Flask, request, g
import time
app = Flask(__name__)
# 全局前置钩子
@app.before_request
def before_request():
g.request_start = time.time()
# 全局后置钩子
@app.after_request
def after_request(response):
duration = time.time() - g.request_start
response.headers['X-Request-Duration'] = f'{duration:.4f}s'
return response
# URL 值预处理器
@app.url_value_preprocessor
def pull_lang_code(endpoint, values):
g.lang_code = values.pop('lang_code', None)
@app.route('/<lang_code>/home')
def home():
return f'Language: {g.lang_code}'
# URL 默认值
@app.url_defaults
def add_language_code(endpoint, values):
if 'lang_code' in values or not g.lang_code:
return
if app.url_map.is_endpoint_expecting(endpoint, 'lang_code'):
values['lang_code'] = g.lang_code
第九章:RESTful 路由设计
REST 设计原则
from flask import Flask, request, jsonify
app = Flask(__name__)
# RESTful 路由设计示例
# 资源:users
# GET /users - 获取用户列表
@app.get('/users')
def get_users():
users = User.query.all()
return jsonify([u.to_dict() for u in users])
# GET /users/<id> - 获取单个用户
@app.get('/users/<int:user_id>')
def get_user(user_id):
user = User.query.get_or_404(user_id)
return jsonify(user.to_dict())
# POST /users - 创建用户
@app.post('/users')
def create_user():
data = request.get_json()
user = User(**data)
db.session.add(user)
db.session.commit()
return jsonify(user.to_dict()), 201
# PUT /users/<id> - 完整更新用户
@app.put('/users/<int:user_id>')
def update_user(user_id):
user = User.query.get_or_404(user_id)
data = request.get_json()
for key, value in data.items():
setattr(user, key, value)
db.session.commit()
return jsonify(user.to_dict())
# PATCH /users/<id> - 部分更新用户
@app.patch('/users/<int:user_id>')
def patch_user(user_id):
user = User.query.get_or_404(user_id)
data = request.get_json()
for key, value in data.items():
if hasattr(user, key):
setattr(user, key, value)
db.session.commit()
return jsonify(user.to_dict())
# DELETE /users/<id> - 删除用户
@app.delete('/users/<int:user_id>')
def delete_user(user_id):
user = User.query.get_or_404(user_id)
db.session.delete(user)
db.session.commit()
return '', 204
嵌套资源路由
# 用户的文章资源
# GET /users/<id>/posts - 获取用户的所有文章
@app.get('/users/<int:user_id>/posts')
def get_user_posts(user_id):
user = User.query.get_or_404(user_id)
return jsonify([p.to_dict() for p in user.posts])
# POST /users/<id>/posts - 为用户创建文章
@app.post('/users/<int:user_id>/posts')
def create_user_post(user_id):
user = User.query.get_or_404(user_id)
data = request.get_json()
post = Post(user_id=user.id, **data)
db.session.add(post)
db.session.commit()
return jsonify(post.to_dict()), 201
# 文章的评论资源
# GET /posts/<id>/comments
@app.get('/posts/<int:post_id>/comments')
def get_post_comments(post_id):
post = Post.query.get_or_404(post_id)
return jsonify([c.to_dict() for c in post.comments])
API 版本控制
from flask import Flask, Blueprint
app = Flask(__name__)
# 方式一:URL 前缀版本控制
api_v1 = Blueprint('api_v1', __name__, url_prefix='/api/v1')
api_v2 = Blueprint('api_v2', __name__, url_prefix='/api/v2')
@api_v1.get('/users')
def get_users_v1():
return {'version': 1, 'users': []}
@api_v2.get('/users')
def get_users_v2():
return {'version': 2, 'users': [], 'metadata': {}}
app.register_blueprint(api_v1)
app.register_blueprint(api_v2)
# 方式二:请求头版本控制
@app.route('/api/users')
def get_users():
version = request.headers.get('API-Version', '1')
if version == '2':
return {'version': 2, 'data': []}
return {'version': 1, 'users': []}
# 方式三:Accept 头版本控制
@app.route('/api/users')
def get_users_accept():
accept = request.headers.get('Accept', '')
if 'application/vnd.api.v2+json' in accept:
return {'version': 2}
return {'version': 1}
第十章:路由调试与优化
查看所有路由
from flask import Flask
app = Flask(__name__)
# 注册一些路由...
# 命令行查看
# flask routes
# 代码中查看
with app.app_context():
for rule in app.url_map.iter_rules():
print(f'{rule.endpoint:30s} {rule.methods} {rule.rule}')
# 输出示例:
# index {'GET', 'HEAD', 'OPTIONS'} /
# profile {'GET', 'HEAD', 'OPTIONS'} /user/<username>
# static {'GET', 'HEAD', 'OPTIONS'} /static/<path:filename>
路由测试
import pytest
from flask import Flask
app = Flask(__name__)
@app.route('/hello/<name>')
def hello(name):
return f'Hello, {name}!'
# 测试路由
def test_routes():
with app.test_client() as client:
# 测试正常请求
response = client.get('/hello/Flask')
assert response.status_code == 200
assert b'Hello, Flask!' in response.data
# 测试 404
response = client.get('/nonexistent')
assert response.status_code == 404
# 测试不同方法
response = client.post('/hello/Flask')
assert response.status_code == 405 # Method Not Allowed
路由性能优化
from flask import Flask
from werkzeug.routing import Map, Rule
app = Flask(__name__)
# 1. 避免过多的动态路由
# 不好:大量相似的动态路由
@app.route('/page/<page_type>') # 可能匹配太多
# 好:使用具体路由或限制转换器
@app.route('/page/home')
@app.route('/page/about')
@app.route('/page/<regex("[a-z]{3,10}"):page_type>')
# 2. 使用蓝图组织路由
# 相关路由放在同一蓝图中,提高查找效率
# 3. 缓存 url_for 结果
# 对于频繁使用的 URL,可以缓存
from functools import lru_cache
@lru_cache(maxsize=100)
def cached_url_for(endpoint, **kwargs):
with app.app_context():
return url_for(endpoint, **kwargs)
总结
本章详细介绍了 Flask 的路由系统:
- 路由基础:路由定义、HTTP 方法、装饰器
- 动态路由:变量规则、转换器、自定义转换器
- URL 规则:尾部斜杠、优先级、端点
- 视图函数:返回值类型、类视图、装饰器
- 请求处理:查询参数、表单、JSON、文件上传
- 响应处理:自定义响应、流式响应、文件下载
- URL 构建:url_for 详解
- 高级特性:默认值、别名、重定向、钩子
- RESTful 设计:REST 原则、嵌套资源、版本控制
- 调试优化:路由查看、测试、性能
下一章我们将深入学习 Jinja2 模板引擎。