第4课:Flask请求与响应对象深度解析

📋 目录


📘 课程目标

通过本课学习,你将:

  • 深入理解 Flask 的请求-响应处理机制
  • 熟练使用 request 对象获取各种类型的请求数据
  • 掌握 response 对象的构造与自定义
  • 学会处理 URL 参数、表单数据、JSON 数据和文件上传
  • 掌握返回不同类型响应的最佳实践
  • 理解 HTTP 状态码与错误处理

🔄 Flask 请求与响应机制

Flask 作为 Web 框架,其核心是处理 HTTP 请求并返回响应。理解这个流程对掌握 Flask 至关重要:

复制代码
用户发起请求 → Flask 路由匹配 → 视图函数处理 → 构造响应 → 返回给用户

Flask 提供了强大的 requestresponse 对象来简化这个过程。


🔍 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/&lt;id&gt; - 更新用户</li>
        <li><strong>DELETE</strong> /api/users/&lt;id&gt; - 删除用户</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

🧠 进阶思考题

  1. 请求中间件:如何实现一个中间件来记录所有请求的详细信息?
  2. 响应缓存:如何为某些 API 响应添加缓存控制头?
  3. 内容协商:如何根据客户端的 Accept 头返回不同格式的响应?

参考答案:

  1. 请求日志中间件:
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
  1. 响应缓存控制:
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
  1. 内容协商:
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 消息:页面间消息传递
  • 用户认证系统:登录、注册、权限控制

💡 学习建议

  1. 多练习:用 Postman 测试不同类型的 API 请求
  2. 多思考:每种数据类型适用于什么场景?
  3. 多探索:尝试组合使用不同的请求和响应类型
  4. 多总结:对比不同框架的请求处理方式

保持学习热情,继续 Flask 之旅! 🎯


📚 扩展阅读