第4课:Flask请求与响应对象深度解析
📋 目录
- 课程目标
- Flask 请求与响应机制
- request 对象详解
- 处理不同类型的请求数据
- response 对象与响应类型
- 错误处理与状态码
- 完整实战示例
- 课后练习
- 学习小结与下节预告
📘 课程目标
通过本课学习,你将:
- 深入理解 Flask 的请求-响应处理机制
- 熟练使用
request
对象获取各种类型的请求数据 - 掌握
response
对象的构造与自定义 - 学会处理 URL 参数、表单数据、JSON 数据和文件上传
- 掌握返回不同类型响应的最佳实践
- 理解 HTTP 状态码与错误处理
🔄 Flask 请求与响应机制
Flask 作为 Web 框架,其核心是处理 HTTP 请求并返回响应。理解这个流程对掌握 Flask 至关重要:
用户发起请求 → Flask 路由匹配 → 视图函数处理 → 构造响应 → 返回给用户
Flask 提供了强大的 request
和 response
对象来简化这个过程。
🔍 request 对象详解
request
是 Flask 提供的全局代理对象,封装了当前 HTTP 请求的所有信息。
from flask import Flask, requestapp = 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 查询参数常用于搜索、筛选和分页功能。
@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 "请输入搜索关键词", 400return 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 应用最常见的数据交互方式。
@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 "用户名和邮箱不能为空", 400return 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 的标准数据格式。
from flask import jsonify@app.route('/api/users', methods=['POST'])
def create_user():"""创建用户 API"""# 检查请求内容类型if not request.is_json:return jsonify({'error': '请求必须是 JSON 格式'}), 400data = 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. 处理文件上传
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 '未选择文件', 400file = request.files['file']description = request.form.get('description', '')if file.filename == '':return '未选择文件', 400if 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. 字符串响应(最简单)
@app.route('/')
def home():return "欢迎来到 Flask 学习之旅!"@app.route('/html')
def html_response():return """<h1>HTML 响应</h1><p>这是一个 <strong>HTML</strong> 响应示例。</p>"""
2. JSON 响应(API 开发推荐)
@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. 重定向响应
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. 自定义响应对象
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 应用的重要组成部分。
@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}), 404return 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': '接口不存在'}), 404else:# 普通路径返回 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
🚀 完整实战示例
下面是一个综合运用请求与响应处理的完整示例:
from flask import Flask, request, jsonify, redirect, url_for, make_response
import json
from datetime import datetimeapp = 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_idif 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_pageend = start + per_pagepaginated_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 格式'}), 400data = 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 += 1response = 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': '用户不存在'}), 404if request.method == 'GET':return jsonify({'user': user})elif request.method == 'PUT':if not request.is_json:return jsonify({'error': '请求必须是 JSON 格式'}), 400data = 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 Contentif __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 处理时需要验证所有字段不为空,并返回美观的确认页面。
答案:
@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>", 400return 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 获取。支持加减乘除运算,需要完整的错误处理。
答案:
@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': '参数必须是有效数字'}), 400else:# 从 JSON 获取数据if not request.is_json:return jsonify({'error': '请求必须是 JSON 格式'}), 400data = 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())}), 400symbol, calc_func = operations[operation]result = calc_func(a, b)if result is None:return jsonify({'error': '除数不能为零'}), 400return 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
:处理纯文本数据
答案:
@app.route('/api/data', methods=['POST'])
def process_data():content_type = request.content_typeif 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 头返回不同格式的响应?
参考答案:
- 请求日志中间件:
@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
- 响应缓存控制:
@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 = Truereturn response
- 内容协商:
@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 之旅! 🎯
📚 扩展阅读
- Flask 官方文档 - 请求对象
- HTTP 状态码详解
- RESTful API 设计最佳实践
- Web 安全基础知识