📋 目录
- 课程目标
- [Flask 请求与响应机制](#Flask 请求与响应机制)
- [request 对象详解](#request 对象详解)
- 处理不同类型的请求数据
- [response 对象与响应类型](#response 对象与响应类型)
- 错误处理与状态码
- 完整实战示例
- 课后练习
- 学习小结与下节预告
📘 课程目标
通过本课学习,你将:
- 深入理解 Flask 的请求-响应处理机制
- 熟练使用
request
对象获取各种类型的请求数据 - 掌握
response
对象的构造与自定义 - 学会处理 URL 参数、表单数据、JSON 数据和文件上传
- 掌握返回不同类型响应的最佳实践
- 理解 HTTP 状态码与错误处理
🔄 Flask 请求与响应机制
Flask 作为 Web 框架,其核心是处理 HTTP 请求并返回响应。理解这个流程对掌握 Flask 至关重要:
用户发起请求 → Flask 路由匹配 → 视图函数处理 → 构造响应 → 返回给用户
Flask 提供了强大的 request
和 response
对象来简化这个过程。
🔍 request 对象详解
request
是 Flask 提供的全局代理对象,封装了当前 HTTP 请求的所有信息。
python
from flask import Flask, request
app = Flask(__name__)
核心属性与方法一览
分类 | 属性/方法 | 说明 | 示例 |
---|---|---|---|
基本信息 | request.method |
HTTP 方法 | GET , POST , PUT |
request.path |
请求路径 | /api/users |
|
request.url |
完整 URL | http://localhost:5000/api/users?id=1 |
|
request.remote_addr |
客户端 IP | 127.0.0.1 |
|
参数获取 | request.args |
URL 查询参数 | ?name=flask&version=2.0 |
request.form |
表单数据 | POST 表单提交的数据 | |
request.get_json() |
JSON 数据 | API 请求的 JSON 载荷 | |
request.files |
文件上传 | 文件对象字典 | |
请求头 | request.headers |
所有请求头 | {'Content-Type': 'application/json'} |
request.content_type |
内容类型 | application/json |
|
原始数据 | request.data |
原始请求体 | 二进制数据 |
request.get_data() |
请求体数据 | 解码后的数据 |
📥 处理不同类型的请求数据
1. 处理 URL 查询参数(GET 请求)
URL 查询参数常用于搜索、筛选和分页功能。
python
@app.route('/search')
def search():
"""搜索功能示例"""
keyword = request.args.get('q', '') # 获取搜索关键词,默认为空字符串
page = request.args.get('page', 1, type=int) # 获取页码,默认为1,转换为整数
per_page = request.args.get('per_page', 10, type=int) # 每页数量
if not keyword:
return "请输入搜索关键词", 400
return f"""
<h2>搜索结果</h2>
<p>关键词:{keyword}</p>
<p>第 {page} 页,每页 {per_page} 条</p>
<p>搜索URL:{request.url}</p>
"""
# 示例访问:/search?q=flask&page=2&per_page=20
2. 处理表单数据(POST 请求)
HTML 表单提交是 Web 应用最常见的数据交互方式。
python
@app.route('/register', methods=['GET', 'POST'])
def register():
"""用户注册示例"""
if request.method == 'GET':
# 返回注册表单
return '''
<h2>用户注册</h2>
<form method="post">
<p>用户名:<input type="text" name="username" required></p>
<p>邮箱:<input type="email" name="email" required></p>
<p>年龄:<input type="number" name="age" min="1" max="120"></p>
<p>
性别:
<input type="radio" name="gender" value="男"> 男
<input type="radio" name="gender" value="女"> 女
</p>
<p>
兴趣爱好:
<input type="checkbox" name="hobbies" value="编程"> 编程
<input type="checkbox" name="hobbies" value="运动"> 运动
<input type="checkbox" name="hobbies" value="音乐"> 音乐
</p>
<p><button type="submit">注册</button></p>
</form>
'''
else:
# 处理表单提交
username = request.form.get('username')
email = request.form.get('email')
age = request.form.get('age', type=int)
gender = request.form.get('gender')
hobbies = request.form.getlist('hobbies') # 获取多选框的值
# 简单验证
if not username or not email:
return "用户名和邮箱不能为空", 400
return f"""
<h2>注册成功!</h2>
<p>用户名:{username}</p>
<p>邮箱:{email}</p>
<p>年龄:{age}</p>
<p>性别:{gender}</p>
<p>兴趣爱好:{', '.join(hobbies) if hobbies else '无'}</p>
"""
3. 处理 JSON 数据(API 开发)
JSON 是现代 Web API 的标准数据格式。
python
from flask import jsonify
@app.route('/api/users', methods=['POST'])
def create_user():
"""创建用户 API"""
# 检查请求内容类型
if not request.is_json:
return jsonify({'error': '请求必须是 JSON 格式'}), 400
data = request.get_json()
# 验证必需字段
required_fields = ['username', 'email']
for field in required_fields:
if field not in data:
return jsonify({'error': f'缺少必需字段:{field}'}), 400
# 模拟创建用户
user = {
'id': 123,
'username': data['username'],
'email': data['email'],
'age': data.get('age'),
'created_at': '2024-12-28 10:00:00'
}
return jsonify({
'message': '用户创建成功',
'user': user
}), 201
@app.route('/api/users/<int:user_id>', methods=['PUT'])
def update_user(user_id):
"""更新用户信息 API"""
data = request.get_json()
if not data:
return jsonify({'error': '请提供要更新的数据'}), 400
# 模拟更新操作
updated_user = {
'id': user_id,
'username': data.get('username', '原用户名'),
'email': data.get('email', '原邮箱'),
'updated_at': '2024-12-28 10:30:00'
}
return jsonify({
'message': '用户更新成功',
'user': updated_user
})
4. 处理文件上传
python
import os
from werkzeug.utils import secure_filename
@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
"""文件上传示例"""
if request.method == 'GET':
return '''
<h2>文件上传</h2>
<form method="post" enctype="multipart/form-data">
<p>选择文件:<input type="file" name="file" required></p>
<p>文件描述:<input type="text" name="description"></p>
<p><button type="submit">上传</button></p>
</form>
'''
if 'file' not in request.files:
return '未选择文件', 400
file = request.files['file']
description = request.form.get('description', '')
if file.filename == '':
return '未选择文件', 400
if file:
# 安全的文件名处理
filename = secure_filename(file.filename)
return f"""
<h2>文件上传成功!</h2>
<p>文件名:{filename}</p>
<p>文件大小:{len(file.read())} 字节</p>
<p>文件类型:{file.content_type}</p>
<p>描述:{description}</p>
"""
📤 response 对象与响应类型
Flask 视图函数可以返回多种类型的响应,框架会自动处理或手动构造 Response
对象。
1. 字符串响应(最简单)
python
@app.route('/')
def home():
return "欢迎来到 Flask 学习之旅!"
@app.route('/html')
def html_response():
return """
<h1>HTML 响应</h1>
<p>这是一个 <strong>HTML</strong> 响应示例。</p>
"""
2. JSON 响应(API 开发推荐)
python
@app.route('/api/status')
def api_status():
"""API 状态检查"""
return jsonify({
'status': 'success',
'message': 'API 服务正常运行',
'version': '1.0.0',
'timestamp': '2024-12-28 10:00:00'
})
@app.route('/api/error-demo')
def api_error():
"""API 错误响应示例"""
return jsonify({
'status': 'error',
'message': '权限不足',
'error_code': 'PERMISSION_DENIED'
}), 403
3. 重定向响应
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 "<h1>这是新页面</h1><p>您已被重定向到这里。</p>"
@app.route('/external-redirect')
def external_redirect():
"""重定向到外部网站"""
return redirect('https://flask.palletsprojects.com/')
4. 自定义响应对象
python
from flask import Response, make_response
@app.route('/custom-response')
def custom_response():
"""自定义响应示例"""
content = "这是自定义响应内容"
response = Response(
content,
status=200,
headers={
'X-Custom-Header': 'Flask-Demo',
'Content-Type': 'text/plain; charset=utf-8'
}
)
return response
@app.route('/cookie-demo')
def cookie_demo():
"""设置 Cookie 示例"""
resp = make_response("Cookie 已设置")
resp.set_cookie('username', 'flask_user', max_age=60*60*24) # 24小时有效
return resp
⚠️ 错误处理与状态码
合适的错误处理是 Web 应用的重要组成部分。
python
@app.route('/api/users/<int:user_id>')
def get_user(user_id):
"""获取用户信息(含错误处理)"""
# 模拟用户数据
users = {1: '张三', 2: '李四', 3: '王五'}
if user_id not in users:
return jsonify({
'error': '用户不存在',
'user_id': user_id
}), 404
return jsonify({
'user_id': user_id,
'username': users[user_id],
'status': 'active'
})
@app.errorhandler(404)
def not_found(error):
"""全局 404 错误处理"""
if request.path.startswith('/api/'):
# API 路径返回 JSON 错误
return jsonify({'error': '接口不存在'}), 404
else:
# 普通路径返回 HTML 错误页面
return f"""
<h1>页面未找到</h1>
<p>您访问的页面 <code>{request.path}</code> 不存在。</p>
<a href="/">返回首页</a>
""", 404
@app.errorhandler(500)
def internal_error(error):
"""全局 500 错误处理"""
return jsonify({'error': '服务器内部错误'}), 500
🚀 完整实战示例
下面是一个综合运用请求与响应处理的完整示例:
python
from flask import Flask, request, jsonify, redirect, url_for, make_response
import json
from datetime import datetime
app = Flask(__name__)
# 模拟数据存储
users = []
next_user_id = 1
@app.route('/')
def index():
"""首页 - 展示所有功能"""
return f"""
<h1>🎓 Flask 请求与响应示例</h1>
<h2>功能导航</h2>
<ul>
<li><a href="{url_for('search')}?q=flask&page=1">搜索示例</a></li>
<li><a href="{url_for('register')}">用户注册</a></li>
<li><a href="{url_for('upload_file')}">文件上传</a></li>
<li><a href="{url_for('api_status')}">API 状态</a></li>
<li><a href="{url_for('get_users')}">用户列表 API</a></li>
</ul>
<h2>API 测试</h2>
<p>使用 Postman 或 curl 测试以下 API:</p>
<ul>
<li><strong>POST</strong> /api/users - 创建用户</li>
<li><strong>GET</strong> /api/users - 获取用户列表</li>
<li><strong>PUT</strong> /api/users/<id> - 更新用户</li>
<li><strong>DELETE</strong> /api/users/<id> - 删除用户</li>
</ul>
"""
# ... (前面的搜索、注册、上传功能保持不变) ...
@app.route('/api/users', methods=['GET', 'POST'])
def users_api():
"""用户管理 API"""
global next_user_id
if request.method == 'GET':
# 获取用户列表,支持分页
page = request.args.get('page', 1, type=int)
per_page = request.args.get('per_page', 10, type=int)
start = (page - 1) * per_page
end = start + per_page
paginated_users = users[start:end]
return jsonify({
'users': paginated_users,
'total': len(users),
'page': page,
'per_page': per_page,
'pages': (len(users) + per_page - 1) // per_page
})
elif request.method == 'POST':
# 创建新用户
if not request.is_json:
return jsonify({'error': '请求必须是 JSON 格式'}), 400
data = request.get_json()
# 验证必需字段
if not data.get('username') or not data.get('email'):
return jsonify({'error': '用户名和邮箱不能为空'}), 400
# 检查用户名是否已存在
if any(u['username'] == data['username'] for u in users):
return jsonify({'error': '用户名已存在'}), 409
# 创建用户
user = {
'id': next_user_id,
'username': data['username'],
'email': data['email'],
'age': data.get('age'),
'created_at': datetime.now().isoformat()
}
users.append(user)
next_user_id += 1
response = make_response(jsonify({
'message': '用户创建成功',
'user': user
}), 201)
response.headers['Location'] = f'/api/users/{user["id"]}'
return response
@app.route('/api/users/<int:user_id>', methods=['GET', 'PUT', 'DELETE'])
def user_detail_api(user_id):
"""单个用户操作 API"""
# 查找用户
user = next((u for u in users if u['id'] == user_id), None)
if not user:
return jsonify({'error': '用户不存在'}), 404
if request.method == 'GET':
return jsonify({'user': user})
elif request.method == 'PUT':
if not request.is_json:
return jsonify({'error': '请求必须是 JSON 格式'}), 400
data = request.get_json()
# 更新用户信息
if 'username' in data:
user['username'] = data['username']
if 'email' in data:
user['email'] = data['email']
if 'age' in data:
user['age'] = data['age']
user['updated_at'] = datetime.now().isoformat()
return jsonify({
'message': '用户更新成功',
'user': user
})
elif request.method == 'DELETE':
users.remove(user)
return '', 204 # No Content
if __name__ == '__main__':
print("🚀 Flask 应用启动中...")
print("📱 访问地址:http://127.0.0.1:5000")
app.run(debug=True, host='127.0.0.1', port=5000)
📝 课后练习
🎯 基础练习
练习 1:表单数据处理增强版
题目:
创建一个 /contact
路由,支持 GET(显示联系表单)和 POST(处理表单数据)。表单包含:姓名、邮箱、主题、消息内容。POST 处理时需要验证所有字段不为空,并返回美观的确认页面。
答案:
python
@app.route('/contact', methods=['GET', 'POST'])
def contact():
if request.method == 'GET':
return '''
<h2>联系我们</h2>
<form method="post" style="max-width: 400px;">
<p>
<label>姓名:</label><br>
<input type="text" name="name" required style="width: 100%; padding: 5px;">
</p>
<p>
<label>邮箱:</label><br>
<input type="email" name="email" required style="width: 100%; padding: 5px;">
</p>
<p>
<label>主题:</label><br>
<input type="text" name="subject" required style="width: 100%; padding: 5px;">
</p>
<p>
<label>消息内容:</label><br>
<textarea name="message" required style="width: 100%; height: 100px; padding: 5px;"></textarea>
</p>
<p>
<button type="submit" style="background: #007bff; color: white; padding: 10px 20px; border: none;">
发送消息
</button>
</p>
</form>
'''
else:
name = request.form.get('name')
email = request.form.get('email')
subject = request.form.get('subject')
message = request.form.get('message')
# 验证必填字段
required_fields = {'姓名': name, '邮箱': email, '主题': subject, '消息内容': message}
missing_fields = [field for field, value in required_fields.items() if not value]
if missing_fields:
return f"<h2>错误</h2><p>以下字段不能为空:{', '.join(missing_fields)}</p>", 400
return f'''
<h2>✅ 消息发送成功!</h2>
<div style="border: 1px solid #ddd; padding: 20px; max-width: 500px;">
<p><strong>姓名:</strong>{name}</p>
<p><strong>邮箱:</strong>{email}</p>
<p><strong>主题:</strong>{subject}</p>
<p><strong>消息内容:</strong></p>
<div style="background: #f8f9fa; padding: 10px; border-left: 3px solid #007bff;">
{message.replace(chr(10), '<br>')}
</div>
<p style="color: #666; margin-top: 20px;">我们会尽快回复您的消息。</p>
</div>
'''
练习 2:智能计算器 API
题目:
实现 /api/calc
路由,支持 GET 和 POST 请求。GET 时从 URL 参数获取操作数和运算符,POST 时从 JSON 获取。支持加减乘除运算,需要完整的错误处理。
答案:
python
@app.route('/api/calc', methods=['GET', 'POST'])
def calculator_api():
if request.method == 'GET':
# 从 URL 参数获取数据
try:
a = float(request.args.get('a', 0))
b = float(request.args.get('b', 0))
operation = request.args.get('op', 'add')
except ValueError:
return jsonify({'error': '参数必须是有效数字'}), 400
else:
# 从 JSON 获取数据
if not request.is_json:
return jsonify({'error': '请求必须是 JSON 格式'}), 400
data = request.get_json()
try:
a = float(data.get('a', 0))
b = float(data.get('b', 0))
operation = data.get('operation', 'add')
except (ValueError, TypeError):
return jsonify({'error': '参数必须是有效数字'}), 400
# 执行计算
operations = {
'add': ('+', lambda x, y: x + y),
'subtract': ('-', lambda x, y: x - y),
'multiply': ('×', lambda x, y: x * y),
'divide': ('÷', lambda x, y: x / y if y != 0 else None)
}
if operation not in operations:
return jsonify({
'error': f'不支持的运算类型:{operation}',
'supported_operations': list(operations.keys())
}), 400
symbol, calc_func = operations[operation]
result = calc_func(a, b)
if result is None:
return jsonify({'error': '除数不能为零'}), 400
return jsonify({
'operands': {'a': a, 'b': b},
'operation': operation,
'expression': f'{a} {symbol} {b} = {result}',
'result': result,
'timestamp': datetime.now().isoformat()
})
练习 3:多功能数据处理
题目:
创建 /api/data
路由,根据 Content-Type 自动处理不同格式的数据:
application/json
:处理 JSON 数据application/x-www-form-urlencoded
:处理表单数据text/plain
:处理纯文本数据
答案:
python
@app.route('/api/data', methods=['POST'])
def process_data():
content_type = request.content_type
if content_type == 'application/json':
data = request.get_json()
return jsonify({
'data_type': 'JSON',
'content_type': content_type,
'received_data': data,
'data_keys': list(data.keys()) if isinstance(data, dict) else None
})
elif content_type == 'application/x-www-form-urlencoded':
data = dict(request.form)
return jsonify({
'data_type': 'Form Data',
'content_type': content_type,
'received_data': data,
'form_fields': list(data.keys())
})
elif content_type == 'text/plain':
data = request.get_data(as_text=True)
return jsonify({
'data_type': 'Plain Text',
'content_type': content_type,
'text_content': data,
'text_length': len(data),
'word_count': len(data.split()) if data else 0
})
else:
return jsonify({
'error': f'不支持的内容类型:{content_type}',
'supported_types': [
'application/json',
'application/x-www-form-urlencoded',
'text/plain'
]
}), 415
🧠 进阶思考题
- 请求中间件:如何实现一个中间件来记录所有请求的详细信息?
- 响应缓存:如何为某些 API 响应添加缓存控制头?
- 内容协商:如何根据客户端的 Accept 头返回不同格式的响应?
参考答案:
- 请求日志中间件:
python
@app.before_request
def log_request_info():
app.logger.info(f"""
请求信息:
- 方法: {request.method}
- URL: {request.url}
- IP: {request.remote_addr}
- User-Agent: {request.headers.get('User-Agent')}
- Content-Type: {request.content_type}
""")
@app.after_request
def log_response_info(response):
app.logger.info(f"响应状态码: {response.status_code}")
return response
- 响应缓存控制:
python
@app.route('/api/cached-data')
def cached_data():
data = {'timestamp': datetime.now().isoformat(), 'data': 'cached content'}
response = make_response(jsonify(data))
response.cache_control.max_age = 300 # 缓存5分钟
response.cache_control.public = True
return response
- 内容协商:
python
@app.route('/api/flexible')
def flexible_response():
data = {'message': 'Hello, World!', 'timestamp': datetime.now().isoformat()}
accept_header = request.headers.get('Accept', '')
if 'application/json' in accept_header:
return jsonify(data)
elif 'text/html' in accept_header:
return f"<h1>{data['message']}</h1><p>时间:{data['timestamp']}</p>"
elif 'text/plain' in accept_header:
return f"{data['message']}\n时间:{data['timestamp']}"
else:
return jsonify(data) # 默认返回 JSON
🔎 知识点总结
知识点 | 核心概念 | 实际应用 |
---|---|---|
request 对象 | 封装 HTTP 请求信息 | 获取用户输入、文件上传、API 参数 |
URL 参数 | request.args.get() |
搜索、筛选、分页功能 |
表单数据 | request.form.get() |
用户注册、登录、数据提交 |
JSON 数据 | request.get_json() |
RESTful API、前后端分离 |
文件上传 | request.files |
头像上传、文档管理 |
响应类型 | 字符串、JSON、重定向 | 页面展示、API 返回、页面跳转 |
错误处理 | 状态码、错误页面 | 用户体验、API 规范 |
🚀 学习小结与下节预告
本课收获
通过本课学习,你已经掌握了:
- Flask 请求-响应处理的完整流程
- 各种类型请求数据的获取方法
- 不同响应类型的构造技巧
- 错误处理和状态码的最佳实践
- 实际 Web 应用开发的核心技能
下节课预告
下节课我们将学习:
- Flask 会话管理(Session):用户登录状态保持
- Cookie 操作:客户端数据存储
- Flash 消息:页面间消息传递
- 用户认证系统:登录、注册、权限控制
💡 学习建议
- 多练习:用 Postman 测试不同类型的 API 请求
- 多思考:每种数据类型适用于什么场景?
- 多探索:尝试组合使用不同的请求和响应类型
- 多总结:对比不同框架的请求处理方式
保持学习热情,继续 Flask 之旅! 🎯