03-Flask请求上下文响应与错误处理机制深度解析

Flask请求上下文、响应与错误处理机制深度解析

本文基于Flask 3.0+版本编写,深入剖析Flask请求处理的核心机制,包括请求上下文管理、响应构建、错误处理及中间件模式,帮助开发者构建健壮的Web应用。


一、请求处理机制概述

1.1 Flask请求处理架构

Flask的请求处理机制基于WSGI规范,通过上下文管理器实现请求隔离和状态管理。
上下文栈
请求处理流程
HTTP请求
WSGI Server
Flask.call
创建RequestContext
创建ApplicationContext
执行before_request
路由匹配
执行视图函数
执行after_request
执行teardown_request
销毁ApplicationContext
销毁RequestContext
返回Response
_app_ctx_stack
_request_ctx_stack

1.2 核心组件关系

contains
references
produces
RequestContext
+Request request
+AppContext app_context
+dict session
+push()
+pop()
+copy()
AppContext
+Flask app
+dict url_adapter
+dict g
+push()
+pop()
Request
+str method
+str path
+dict args
+dict form
+dict json
+dict headers
+dict cookies
+dict files
+str environ
Response
+str data
+int status_code
+dict headers
+set_cookie()
+delete_cookie()


二、请求对象详解

2.1 Request对象结构

Flask的request对象是werkzeug.wrappers.Request的子类,封装了HTTP请求的所有信息。

python 复制代码
"""
Flask Request对象详解

request是一个代理对象,指向当前请求上下文中的Request实例
"""

from flask import Flask, request

app = Flask(__name__)


@app.route('/request-info', methods=['GET', 'POST'])
def request_info():
    """
    Request对象属性完整示例
    """
    # ============================================
    # 请求行信息
    # ============================================
    method = request.method              # HTTP方法: 'GET', 'POST', etc.
    url = request.url                    # 完整URL: 'http://localhost:5000/path?a=1'
    base_url = request.base_url          # 不含查询参数的URL
    url_root = request.url_root          # 应用根URL
    path = request.path                  # 路径部分: '/path'
    full_path = request.full_path        # 路径+查询参数: '/path?a=1'
    script_root = request.script_root    # 脚本根路径
    host = request.host                  # 主机名: 'localhost:5000'
    host_url = request.host_url          # 主机URL: 'http://localhost:5000/'
    
    # ============================================
    # 请求参数
    # ============================================
    args = request.args                  # URL查询参数
    form = request.form                  # 表单数据
    values = request.values              # args + form 合并
    json = request.json                  # JSON数据(自动解析)
    data = request.data                  # 原始请求体
    files = request.files                # 上传文件
    
    # ============================================
    # 请求头
    # ============================================
    headers = request.headers            # 请求头字典
    content_type = request.content_type  # Content-Type头
    content_length = request.content_length  # Content-Length
    mimetype = request.mimetype          # MIME类型
    mimetype_params = request.mimetype_params  # MIME参数
    
    # ============================================
    # Cookie
    # ============================================
    cookies = request.cookies            # Cookie字典
    
    # ============================================
    # 客户端信息
    # ============================================
    remote_addr = request.remote_addr    # 客户端IP
    user_agent = request.user_agent      # User-Agent对象
    referrer = request.referrer          # 来源页面
    origin = request.origin              # Origin头
    
    # ============================================
    # 其他属性
    # ============================================
    environ = request.environ            # WSGI环境字典
    is_json = request.is_json            # 是否为JSON请求
    is_secure = request.is_secure        # 是否为HTTPS
    is_xhr = request.is_xhr              # 是否为AJAX请求(已弃用)
    blueprint = request.blueprint        # 当前蓝图名
    endpoint = request.endpoint          # 当前端点名
    view_args = request.view_args        # URL参数字典
    routing_exception = request.routing_exception  # 路由异常
    
    return {
        'method': method,
        'path': path,
        'args': dict(args),
        'is_json': is_json,
        'remote_addr': remote_addr
    }

2.2 请求参数获取详解

python 复制代码
"""
请求参数获取详解
"""

from flask import Flask, request, jsonify

app = Flask(__name__)


# ============================================
# URL查询参数 (request.args)
# ============================================
@app.route('/search')
def search():
    """
    URL查询参数示例
    URL: /search?q=flask&page=2&sort=date
    
    request.args 是 ImmutableMultiDict 类型
    """
    # 获取单个值
    query = request.args.get('q')              # 'flask'
    query = request.args.get('q', default='')  # 带默认值
    query = request.args['q']                  # 不存在会抛出KeyError
    
    # 获取多个同名参数
    tags = request.args.getlist('tag')         # ['python', 'web']
    
    # 转换类型
    page = request.args.get('page', default=1, type=int)
    price = request.args.get('price', type=float)
    debug = request.args.get('debug', type=bool, default=False)
    
    # 获取所有参数
    all_args = dict(request.args)
    
    return jsonify({
        'query': query,
        'page': page,
        'tags': tags,
        'all_args': all_args
    })


# ============================================
# 表单数据 (request.form)
# ============================================
@app.route('/login', methods=['POST'])
def login():
    """
    表单数据示例
    Content-Type: application/x-www-form-urlencoded
    
    request.form 是 ImmutableMultiDict 类型
    """
    username = request.form.get('username')
    password = request.form.get('password')
    remember = request.form.get('remember', type=bool, default=False)
    
    # 获取所有表单字段
    all_form = dict(request.form)
    
    return jsonify({
        'username': username,
        'remember': remember
    })


# ============================================
# JSON数据 (request.json)
# ============================================
@app.route('/api/users', methods=['POST'])
def create_user():
    """
    JSON数据示例
    Content-Type: application/json
    
    request.json 自动解析JSON请求体
    """
    # 方式1: 使用 request.json
    data = request.json
    
    # 方式2: 使用 request.get_json()
    data = request.get_json()
    data = request.get_json(force=True)    # 强制解析(忽略Content-Type)
    data = request.get_json(silent=True)   # 解析失败返回None
    data = request.get_json(cache=True)    # 缓存解析结果
    
    if data is None:
        return jsonify({'error': 'Invalid JSON'}), 400
    
    username = data.get('username')
    email = data.get('email')
    
    return jsonify({
        'username': username,
        'email': email
    }), 201


# ============================================
# 合并参数 (request.values)
# ============================================
@app.route('/filter')
def filter_items():
    """
    request.values 合并了 args 和 form
    优先级: form > args
    """
    # 可以同时获取URL参数和表单数据
    category = request.values.get('category')
    page = request.values.get('page', default=1, type=int)
    
    return jsonify({
        'category': category,
        'page': page
    })


# ============================================
# 原始请求体 (request.data)
# ============================================
@app.route('/raw', methods=['POST'])
def raw_data():
    """
    原始请求体数据
    
    当Content-Type不是表单或JSON时使用
    """
    # 原始字节
    raw_bytes = request.data
    
    # 获取字符串
    raw_text = request.data.decode('utf-8')
    
    # 获取流
    stream = request.stream
    stream_data = stream.read()
    
    return jsonify({
        'length': len(raw_bytes),
        'content_type': request.content_type
    })


# ============================================
# 文件上传 (request.files)
# ============================================
@app.route('/upload', methods=['POST'])
def upload_file():
    """
    文件上传示例
    Content-Type: multipart/form-data
    
    request.files 是 MultiDict 类型
    """
    # 获取单个文件
    file = request.files.get('file')
    
    if file is None:
        return jsonify({'error': 'No file uploaded'}), 400
    
    # 文件属性
    filename = file.filename          # 原始文件名
    content_type = file.content_type  # 文件MIME类型
    content_length = file.content_length  # 文件大小
    
    # 读取文件内容
    content = file.read()
    
    # 保存文件
    # file.save('/path/to/save/' + filename)
    
    # 安全文件名处理
    from werkzeug.utils import secure_filename
    safe_name = secure_filename(filename)
    
    # 获取多个文件
    files = request.files.getlist('files')
    
    return jsonify({
        'filename': filename,
        'safe_name': safe_name,
        'content_type': content_type,
        'size': len(content)
    })

2.3 请求头操作

python 复制代码
"""
请求头操作详解
"""

from flask import Flask, request, jsonify

app = Flask(__name__)


@app.route('/headers')
def headers_info():
    """
    请求头信息
    
    request.headers 是 EnvironHeaders 类型
    类似字典,但键不区分大小写
    """
    # 获取单个请求头
    content_type = request.headers.get('Content-Type')
    user_agent = request.headers.get('User-Agent')
    authorization = request.headers.get('Authorization')
    
    # 不区分大小写
    content_type = request.headers.get('content-type')  # 同上
    
    # 获取所有请求头
    all_headers = dict(request.headers)
    
    # 常用请求头快捷属性
    host = request.host
    origin = request.origin
    referrer = request.referrer
    
    # 判断请求头是否存在
    has_auth = 'Authorization' in request.headers
    
    return jsonify({
        'content_type': content_type,
        'user_agent': str(user_agent),
        'has_auth': has_auth,
        'all_headers': all_headers
    })


@app.route('/auth-check')
def auth_check():
    """
    Authorization头解析
    """
    auth_header = request.headers.get('Authorization')
    
    if auth_header is None:
        return jsonify({'error': 'No authorization header'}), 401
    
    # Bearer Token
    if auth_header.startswith('Bearer '):
        token = auth_header[7:]
        return jsonify({'token': token})
    
    # Basic Auth
    if auth_header.startswith('Basic '):
        import base64
        credentials = base64.b64decode(auth_header[6:]).decode('utf-8')
        username, password = credentials.split(':', 1)
        return jsonify({'username': username})
    
    return jsonify({'error': 'Invalid authorization header'}), 401

2.4 Cookie操作

python 复制代码
"""
Cookie操作详解
"""

from flask import Flask, request, make_response

app = Flask(__name__)


@app.route('/get-cookies')
def get_cookies():
    """
    获取Cookie
    
    request.cookies 是字典类型
    """
    # 获取单个Cookie
    session_id = request.cookies.get('session_id')
    user_pref = request.cookies.get('theme', default='light')
    
    # 获取所有Cookie
    all_cookies = dict(request.cookies)
    
    return {
        'session_id': session_id,
        'theme': user_pref,
        'all_cookies': all_cookies
    }


@app.route('/set-cookies')
def set_cookies():
    """
    设置Cookie
    
    通过Response对象设置
    """
    resp = make_response('Cookies set')
    
    # 基本设置
    resp.set_cookie('username', 'john')
    
    # 完整参数
    resp.set_cookie(
        key='session_id',           # Cookie名称
        value='abc123',             # Cookie值
        max_age=3600,               # 最大存活时间(秒)
        expires=None,               # 过期时间
        path='/',                   # 有效路径
        domain=None,                # 有效域名
        secure=True,                # 仅HTTPS传输
        httponly=True,              # 禁止JS访问
        samesite='Lax'              # 同站策略: None, Lax, Strict
    )
    
    # 删除Cookie
    resp.delete_cookie('old_cookie')
    
    return resp

三、请求钩子

3.1 钩子函数详解

python 复制代码
"""
Flask请求钩子详解

请求钩子允许在请求处理的不同阶段执行自定义代码
"""

from flask import Flask, request, g, jsonify
import time
import functools

app = Flask(__name__)


# ============================================
# before_request: 请求前执行
# ============================================
@app.before_request
def before_request_handler():
    """
    每个请求前执行
    
    返回值:
        - None: 继续处理请求
        - Response: 中断请求,直接返回响应
    
    常用场景:
        - 请求日志记录
        - 用户认证检查
        - 请求计时
        - 数据库连接初始化
    """
    # 请求计时
    g.start_time = time.time()
    
    # 请求日志
    app.logger.info(
        f'Request: {request.method} {request.path} '
        f'from {request.remote_addr}'
    )
    
    # 示例: API请求认证检查
    if request.path.startswith('/api/'):
        token = request.headers.get('Authorization')
        if not token:
            return jsonify({'error': 'Unauthorized'}), 401


# ============================================
# before_first_request: 首次请求前执行(已弃用)
# ============================================
# Flask 2.3+ 已弃用,建议使用其他方式初始化
# 如应用工厂模式中的初始化代码


# ============================================
# after_request: 请求后执行
# ============================================
@app.after_request
def after_request_handler(response):
    """
    每个请求后执行(视图函数返回响应后)
    
    参数:
        response: Response对象
    
    返回值:
        必须返回Response对象
    
    特点:
        - 即使视图函数抛出异常,也会执行
        - 可以修改响应对象
    
    常用场景:
        - 添加响应头
        - 响应日志记录
        - 性能监控
    """
    # 计算请求耗时
    if hasattr(g, 'start_time'):
        elapsed = time.time() - g.start_time
        response.headers['X-Response-Time'] = f'{elapsed:.3f}s'
    
    # 添加安全响应头
    response.headers['X-Content-Type-Options'] = 'nosniff'
    response.headers['X-Frame-Options'] = 'SAMEORIGIN'
    
    # 响应日志
    app.logger.info(
        f'Response: {response.status_code} '
        f'for {request.method} {request.path}'
    )
    
    return response


# ============================================
# teardown_request: 请求销毁时执行
# ============================================
@app.teardown_request
def teardown_request_handler(exception=None):
    """
    请求上下文销毁时执行
    
    参数:
        exception: 异常对象(如果有异常)
    
    特点:
        - 无论是否发生异常都会执行
        - 在after_request之后执行
        - 不能返回值
    
    常用场景:
        - 数据库会话关闭
        - 资源清理
        - 错误日志记录
    """
    # 数据库会话清理
    # db.session.remove()
    
    # 异常日志
    if exception:
        app.logger.error(f'Request error: {exception}')


# ============================================
# teardown_appcontext: 应用上下文销毁时执行
# ============================================
@app.teardown_appcontext
def teardown_appcontext_handler(exception=None):
    """
    应用上下文销毁时执行
    
    在teardown_request之后执行
    通常用于清理应用级别的资源
    """
    # 清理数据库连接池
    pass


# ============================================
# 钩子执行顺序
# ============================================
"""
请求钩子执行顺序:

正常请求:
1. before_request
2. 视图函数
3. after_request
4. teardown_request
5. teardown_appcontext

异常请求:
1. before_request
2. 视图函数(抛出异常)
3. after_request(不执行)
4. teardown_request(执行,exception参数有值)
5. teardown_appcontext
"""

3.2 钩子函数应用示例

python 复制代码
"""
请求钩子实际应用示例
"""

from flask import Flask, request, g, jsonify
import time
import logging
from functools import wraps

app = Flask(__name__)


# ============================================
# 示例1: 请求性能监控
# ============================================
@app.before_request
def start_timer():
    """开始计时"""
    g.start_time = time.time()


@app.after_request
def end_timer(response):
    """结束计时并记录"""
    if hasattr(g, 'start_time'):
        elapsed = time.time() - g.start_time
        
        # 添加响应头
        response.headers['X-Response-Time'] = f'{elapsed:.4f}s'
        
        # 慢请求告警
        if elapsed > 1.0:
            app.logger.warning(
                f'Slow request: {request.method} {request.path} '
                f'took {elapsed:.4f}s'
            )
    
    return response


# ============================================
# 示例2: 请求日志中间件
# ============================================
import json

@app.before_request
def log_request():
    """记录请求信息"""
    g.request_id = request.headers.get('X-Request-ID', 'N/A')
    
    log_data = {
        'request_id': g.request_id,
        'method': request.method,
        'path': request.path,
        'args': dict(request.args),
        'remote_addr': request.remote_addr,
        'user_agent': str(request.user_agent),
    }
    
    app.logger.info(f'Request: {json.dumps(log_data)}')


@app.after_request
def log_response(response):
    """记录响应信息"""
    log_data = {
        'request_id': getattr(g, 'request_id', 'N/A'),
        'status_code': response.status_code,
        'content_length': response.content_length,
    }
    
    app.logger.info(f'Response: {json.dumps(log_data)}')
    
    return response


# ============================================
# 示例3: 数据库会话管理
# ============================================
@app.before_request
def init_db_session():
    """初始化数据库会话"""
    # g.db_session = db.create_session()
    pass


@app.teardown_request
def close_db_session(exception=None):
    """关闭数据库会话"""
    session = getattr(g, 'db_session', None)
    if session is not None:
        if exception is not None:
            session.rollback()
        session.close()


# ============================================
# 示例4: API认证装饰器与钩子结合
# ============================================
def api_auth_required(f):
    """API认证装饰器"""
    @wraps(f)
    def decorated(*args, **kwargs):
        token = request.headers.get('Authorization')
        
        if not token or not token.startswith('Bearer '):
            return jsonify({'error': 'Invalid token'}), 401
        
        token = token[7:]  # Remove 'Bearer ' prefix
        
        # 验证token
        user = verify_token(token)
        if user is None:
            return jsonify({'error': 'Invalid or expired token'}), 401
        
        g.current_user = user
        return f(*args, **kwargs)
    
    return decorated


def verify_token(token):
    """验证Token(示例)"""
    # 实际项目中使用JWT或其他方式验证
    if token == 'valid-token':
        return {'id': 1, 'username': 'admin'}
    return None


@app.route('/api/protected')
@api_auth_required
def protected_api():
    """受保护的API"""
    return jsonify({
        'message': 'Protected content',
        'user': g.current_user
    })


# ============================================
# 示例5: 蓝图级钩子
# ============================================
from flask import Blueprint

api_bp = Blueprint('api', __name__, url_prefix='/api')


@api_bp.before_request
def api_before_request():
    """蓝图级before_request"""
    # 仅对/api/*路由生效
    if request.method != 'OPTIONS':
        # CORS预检请求不需要认证
        pass


@api_bp.after_request
def api_after_request(response):
    """蓝图级after_request"""
    # 添加CORS头
    response.headers['Access-Control-Allow-Origin'] = '*'
    response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE'
    response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'
    return response


app.register_blueprint(api_bp)

四、响应对象详解

4.1 Response对象构建

python 复制代码
"""
Flask响应对象详解

视图函数可以返回多种类型的值,Flask会自动转换为Response对象
"""

from flask import Flask, make_response, Response, jsonify, redirect

app = Flask(__name__)


# ============================================
# 返回值类型
# ============================================

@app.route('/string')
def return_string():
    """
    返回字符串
    
    自动转换为:
        - Response对象
        - status_code: 200
        - Content-Type: text/html
    """
    return 'Hello World'


@app.route('/tuple-string')
def return_tuple_string():
    """
    返回元组: (body, status)
    """
    return 'Created', 201


@app.route('/tuple-headers')
def return_tuple_headers():
    """
    返回元组: (body, headers)
    """
    return 'OK', {'X-Custom-Header': 'value'}


@app.route('/tuple-full')
def return_tuple_full():
    """
    返回元组: (body, status, headers)
    """
    return 'Created', 201, {'Content-Type': 'application/json'}


@app.route('/response-object')
def return_response():
    """
    返回Response对象
    """
    resp = Response('Hello World')
    resp.status_code = 200
    resp.headers['X-Custom'] = 'value'
    return resp


# ============================================
# make_response() 函数
# ============================================

@app.route('/make-response')
def use_make_response():
    """
    make_response() 创建响应对象
    
    参数可以是:
        - 字符串
        - 元组
        - Response对象
    """
    # 从字符串创建
    resp = make_response('Hello World')
    
    # 从元组创建
    resp = make_response(('Created', 201))
    
    # 设置属性
    resp.status_code = 200
    resp.headers['X-Custom-Header'] = 'value'
    resp.mimetype = 'application/json'
    
    # 设置Cookie
    resp.set_cookie('username', 'john', max_age=3600)
    
    return resp


# ============================================
# Response对象属性和方法
# ============================================

@app.route('/response-details')
def response_details():
    """Response对象详细示例"""
    resp = make_response('Response Body')
    
    # 响应体
    resp.data = 'New Body'                    # 设置响应体(字节或字符串)
    body = resp.get_data(as_text=True)        # 获取响应体(字符串)
    body_bytes = resp.get_data()              # 获取响应体(字节)
    
    # 状态
    resp.status_code = 200                    # 状态码
    resp.status = '200 OK'                    # 状态字符串
    
    # 响应头
    resp.headers['X-Custom'] = 'value'        # 设置响应头
    content_type = resp.headers.get('Content-Type')
    
    # MIME类型
    resp.mimetype = 'application/json'        # MIME类型
    resp.content_type = 'application/json; charset=utf-8'  # 完整Content-Type
    
    # 内容长度
    length = resp.content_length              # 获取内容长度
    
    # Cookie操作
    resp.set_cookie(
        key='session_id',
        value='abc123',
        max_age=3600,
        expires=None,
        path='/',
        domain=None,
        secure=True,
        httponly=True,
        samesite='Lax'
    )
    resp.delete_cookie('old_cookie')
    
    # ETag
    resp.set_etag('unique-etag')
    
    # 缓存控制
    resp.cache_control.max_age = 3600
    resp.cache_control.no_cache = True
    resp.cache_control.public = True
    
    # 最后修改时间
    from datetime import datetime
    resp.last_modified = datetime.utcnow()
    
    return resp

4.2 特殊响应类型

python 复制代码
"""
特殊响应类型详解
"""

from flask import (
    Flask, redirect, jsonify, send_file, 
    send_from_directory, make_response, Response
)
import io
import os
from datetime import datetime

app = Flask(__name__)


# ============================================
# redirect: 重定向
# ============================================

@app.route('/old-url')
def old_url():
    """
    重定向到新URL
    
    参数:
        location: 目标URL
        code: 状态码(默认302)
    """
    # 基本重定向(302临时重定向)
    return redirect('/new-url')

@app.route('/old-url-permanent')
def old_url_permanent():
    """永久重定向(301)"""
    return redirect('/new-url', code=301)

@app.route('/old-url-307')
def old_url_307():
    """
    307重定向:保留请求方法和请求体
    
    适用场景:
        - POST请求重定向
        - 需要保留请求体
    """
    return redirect('/new-url', code=307)

@app.route('/old-url-308')
def old_url_308():
    """308永久重定向:保留请求方法"""
    return redirect('/new-url', code=308)

@app.route('/new-url')
def new_url():
    return 'This is the new URL'


# 重定向状态码对比
"""
状态码对比:
- 301: 永久重定向,浏览器缓存,POST变GET
- 302: 临时重定向(默认),POST变GET
- 303: 临时重定向,强制GET
- 307: 临时重定向,保留请求方法和请求体
- 308: 永久重定向,保留请求方法和请求体
"""


# ============================================
# jsonify: JSON响应
# ============================================

@app.route('/api/user')
def api_user():
    """
    JSON响应
    
    自动设置:
        - Content-Type: application/json
        - 将Python对象序列化为JSON
    """
    user = {
        'id': 1,
        'username': 'john',
        'email': 'john@example.com',
        'created_at': datetime.utcnow().isoformat()
    }
    return jsonify(user)


@app.route('/api/users')
def api_users():
    """返回列表"""
    users = [
        {'id': 1, 'name': 'John'},
        {'id': 2, 'name': 'Jane'},
    ]
    return jsonify(users)


@app.route('/api/status')
def api_status():
    """带状态码的JSON响应"""
    return jsonify({'status': 'created'}), 201


@app.route('/api/error')
def api_error():
    """错误响应"""
    return jsonify({'error': 'Not found'}), 404


# ============================================
# send_file: 文件响应
# ============================================

@app.route('/download/<filename>')
def download_file(filename):
    """
    文件下载
    
    参数:
        path_or_file: 文件路径或文件对象
        mimetype: MIME类型
        as_attachment: 是否作为附件下载
        download_name: 下载文件名
        conditional: 是否支持条件请求
        etag: ETag生成策略
        last_modified: 最后修改时间
        max_age: 缓存时间
    """
    # 基本用法
    return send_file(
        f'/path/to/files/{filename}',
        as_attachment=True
    )


@app.route('/download/configured')
def download_configured():
    """完整参数示例"""
    return send_file(
        '/path/to/file.pdf',
        mimetype='application/pdf',
        as_attachment=True,
        download_name='document.pdf',
        conditional=True,
        etag=True,
        max_age=3600
    )


@app.route('/download/memory')
def download_from_memory():
    """从内存下载"""
    # 创建内存文件
    data = b'Hello, this is file content from memory.'
    file_obj = io.BytesIO(data)
    
    return send_file(
        file_obj,
        mimetype='text/plain',
        as_attachment=True,
        download_name='memory-file.txt'
    )


@app.route('/download/generated')
def download_generated():
    """生成CSV下载"""
    import csv
    
    # 创建内存文件
    output = io.StringIO()
    writer = csv.writer(output)
    writer.writerow(['ID', 'Name', 'Email'])
    writer.writerow([1, 'John', 'john@example.com'])
    writer.writerow([2, 'Jane', 'jane@example.com'])
    
    output.seek(0)
    
    return send_file(
        io.BytesIO(output.getvalue().encode('utf-8')),
        mimetype='text/csv',
        as_attachment=True,
        download_name='users.csv'
    )


# ============================================
# send_from_directory: 安全文件下载
# ============================================

@app.route('/files/<path:filename>')
def download_from_directory(filename):
    """
    从目录安全下载文件
    
    参数:
        directory: 基础目录(绝对路径)
        path: 相对路径
        **kwargs: 其他参数传递给send_file
    
    安全特性:
        - 防止目录遍历攻击
        - 自动处理路径规范化
    """
    upload_dir = '/path/to/uploads'
    
    return send_from_directory(
        upload_dir,
        filename,
        as_attachment=True
    )


@app.route('/images/<filename>')
def serve_image(filename):
    """服务图片文件"""
    image_dir = os.path.join(app.root_path, 'static', 'images')
    
    return send_from_directory(
        image_dir,
        filename,
        mimetype='image/jpeg'
    )


# ============================================
# 流式响应
# ============================================

@app.route('/stream')
def stream_response():
    """
    流式响应
    
    适用场景:
        - 大文件下载
        - 实时数据推送
        - SSE (Server-Sent Events)
    """
    def generate():
        for i in range(100):
            yield f'data: {i}\n\n'
            import time
            time.sleep(0.1)
    
    return Response(
        generate(),
        mimetype='text/event-stream'
    )


@app.route('/stream/csv')
def stream_csv():
    """流式CSV导出"""
    import csv
    
    def generate():
        output = io.StringIO()
        writer = csv.writer(output)
        
        # 写入表头
        writer.writerow(['ID', 'Name', 'Email'])
        yield output.getvalue()
        output.seek(0)
        output.truncate(0)
        
        # 写入数据
        for i in range(1000):
            writer.writerow([i, f'User {i}', f'user{i}@example.com'])
            yield output.getvalue()
            output.seek(0)
            output.truncate(0)
    
    return Response(
        generate(),
        mimetype='text/csv',
        headers={
            'Content-Disposition': 'attachment; filename=users.csv'
        }
    )


@app.route('/stream/large-file')
def stream_large_file():
    """大文件流式下载"""
    def generate():
        chunk_size = 8192  # 8KB chunks
        with open('/path/to/large/file.bin', 'rb') as f:
            while True:
                chunk = f.read(chunk_size)
                if not chunk:
                    break
                yield chunk
    
    return Response(
        generate(),
        mimetype='application/octet-stream',
        headers={
            'Content-Disposition': 'attachment; filename=large-file.bin'
        }
    )

4.3 Cookie完整操作

python 复制代码
"""
Cookie完整操作详解
"""

from flask import Flask, make_response, request

app = Flask(__name__)


@app.route('/cookie/set')
def set_cookie():
    """
    设置Cookie完整示例
    """
    resp = make_response('Cookie set')
    
    # 基本设置
    resp.set_cookie('simple', 'value')
    
    # 完整参数
    resp.set_cookie(
        key='session_id',           # Cookie名称
        value='abc123def456',       # Cookie值
        max_age=3600,               # 最大存活时间(秒)
        # 或使用expires指定过期时间
        # expires=datetime(2024, 12, 31, 23, 59, 59),
        path='/',                   # 有效路径
        domain='.example.com',      # 有效域名(带点表示包含子域名)
        secure=True,                # 仅HTTPS传输
        httponly=True,              # 禁止JavaScript访问
        samesite='Lax'              # 同站策略
    )
    
    # SameSite取值说明:
    # - None: 允许跨站发送(需要Secure=True)
    # - Lax: 允许顶级导航和GET表单跨站发送
    # - Strict: 完全禁止跨站发送
    
    return resp


@app.route('/cookie/get')
def get_cookie():
    """获取Cookie"""
    # 获取单个Cookie
    session_id = request.cookies.get('session_id')
    
    # 带默认值
    theme = request.cookies.get('theme', default='light')
    
    # 获取所有Cookie
    all_cookies = dict(request.cookies)
    
    # 检查Cookie是否存在
    has_session = 'session_id' in request.cookies
    
    return {
        'session_id': session_id,
        'theme': theme,
        'has_session': has_session
    }


@app.route('/cookie/delete')
def delete_cookie():
    """删除Cookie"""
    resp = make_response('Cookie deleted')
    
    # 删除单个Cookie
    resp.delete_cookie('session_id')
    
    # 删除时需要匹配path和domain
    resp.delete_cookie(
        key='session_id',
        path='/',
        domain='.example.com'
    )
    
    return resp


@app.route('/cookie/update')
def update_cookie():
    """更新Cookie(重新设置即可)"""
    resp = make_response('Cookie updated')
    
    # 重新设置即更新
    resp.set_cookie('theme', 'dark', max_age=3600)
    
    return resp

五、错误处理机制

5.1 错误处理器注册

python 复制代码
"""
Flask错误处理机制详解
"""

from flask import Flask, jsonify, render_template, request

app = Flask(__name__)


# ============================================
# HTTP错误处理器
# ============================================

@app.errorhandler(400)
def bad_request(error):
    """400 Bad Request"""
    if request.is_json:
        return jsonify({
            'error': 'Bad Request',
            'message': str(error.description)
        }), 400
    return render_template('errors/400.html', error=error), 400


@app.errorhandler(401)
def unauthorized(error):
    """401 Unauthorized"""
    if request.is_json:
        return jsonify({
            'error': 'Unauthorized',
            'message': 'Authentication required'
        }), 401
    return render_template('errors/401.html'), 401


@app.errorhandler(403)
def forbidden(error):
    """403 Forbidden"""
    if request.is_json:
        return jsonify({
            'error': 'Forbidden',
            'message': 'You do not have permission to access this resource'
        }), 403
    return render_template('errors/403.html'), 403


@app.errorhandler(404)
def not_found(error):
    """404 Not Found"""
    if request.is_json:
        return jsonify({
            'error': 'Not Found',
            'message': f'The requested URL {request.path} was not found'
        }), 404
    return render_template('errors/404.html'), 404


@app.errorhandler(405)
def method_not_allowed(error):
    """405 Method Not Allowed"""
    if request.is_json:
        return jsonify({
            'error': 'Method Not Allowed',
            'message': f'The method {request.method} is not allowed'
        }), 405
    return render_template('errors/405.html'), 405


@app.errorhandler(413)
def request_entity_too_large(error):
    """413 Request Entity Too Large"""
    if request.is_json:
        return jsonify({
            'error': 'Request Entity Too Large',
            'message': 'The uploaded file is too large'
        }), 413
    return render_template('errors/413.html'), 413


@app.errorhandler(422)
def unprocessable_entity(error):
    """422 Unprocessable Entity"""
    if request.is_json:
        return jsonify({
            'error': 'Unprocessable Entity',
            'message': 'Validation failed',
            'details': getattr(error, 'details', {})
        }), 422
    return render_template('errors/422.html'), 422


@app.errorhandler(429)
def too_many_requests(error):
    """429 Too Many Requests"""
    if request.is_json:
        return jsonify({
            'error': 'Too Many Requests',
            'message': 'Rate limit exceeded'
        }), 429
    return render_template('errors/429.html'), 429


@app.errorhandler(500)
def internal_server_error(error):
    """500 Internal Server Error"""
    # 记录错误日志
    app.logger.error(f'Internal Server Error: {error}')
    
    if request.is_json:
        return jsonify({
            'error': 'Internal Server Error',
            'message': 'An unexpected error occurred'
        }), 500
    return render_template('errors/500.html'), 500


@app.errorhandler(502)
def bad_gateway(error):
    """502 Bad Gateway"""
    if request.is_json:
        return jsonify({
            'error': 'Bad Gateway',
            'message': 'Upstream server error'
        }), 502
    return render_template('errors/502.html'), 502


@app.errorhandler(503)
def service_unavailable(error):
    """503 Service Unavailable"""
    if request.is_json:
        return jsonify({
            'error': 'Service Unavailable',
            'message': 'Service temporarily unavailable'
        }), 503
    return render_template('errors/503.html'), 503


# ============================================
# 自定义异常处理器
# ============================================

class APIError(Exception):
    """API错误基类"""
    
    def __init__(self, message, status_code=400, payload=None):
        super().__init__()
        self.message = message
        self.status_code = status_code
        self.payload = payload
    
    def to_dict(self):
        result = {
            'error': self.__class__.__name__,
            'message': self.message
        }
        if self.payload:
            result['details'] = self.payload
        return result


class ValidationError(APIError):
    """验证错误"""
    def __init__(self, message, errors=None):
        super().__init__(message, status_code=422, payload=errors)


class AuthenticationError(APIError):
    """认证错误"""
    def __init__(self, message='Authentication failed'):
        super().__init__(message, status_code=401)


class AuthorizationError(APIError):
    """授权错误"""
    def __init__(self, message='Permission denied'):
        super().__init__(message, status_code=403)


class ResourceNotFoundError(APIError):
    """资源不存在"""
    def __init__(self, resource, resource_id):
        super().__init__(
            f'{resource} with id {resource_id} not found',
            status_code=404
        )


# 注册自定义异常处理器
@app.errorhandler(APIError)
def handle_api_error(error):
    """处理API错误"""
    response = jsonify(error.to_dict())
    response.status_code = error.status_code
    return response


# 使用示例
@app.route('/api/users/<int:user_id>')
def get_user(user_id):
    from myapp.models import User
    
    user = User.query.get(user_id)
    if user is None:
        raise ResourceNotFoundError('User', user_id)
    
    return jsonify(user.to_dict())


@app.route('/api/users', methods=['POST'])
def create_user():
    data = request.get_json()
    
    # 验证
    errors = validate_user_data(data)
    if errors:
        raise ValidationError('Validation failed', errors)
    
    # 创建用户
    # ...
    
    return jsonify({'id': 1}), 201


def validate_user_data(data):
    """验证用户数据"""
    errors = {}
    
    if not data.get('username'):
        errors['username'] = 'Username is required'
    elif len(data['username']) < 3:
        errors['username'] = 'Username must be at least 3 characters'
    
    if not data.get('email'):
        errors['email'] = 'Email is required'
    
    return errors

5.2 abort函数

python 复制代码
"""
abort函数详解

用于主动中断请求并返回错误响应
"""

from flask import Flask, abort, jsonify, request

app = Flask(__name__)


@app.route('/admin')
def admin():
    """权限检查示例"""
    if not is_admin():
        abort(403)  # 返回403 Forbidden
    return 'Admin Panel'


@app.route('/user/<int:user_id>')
def get_user(user_id):
    """资源检查示例"""
    user = get_user_from_db(user_id)
    if user is None:
        abort(404)  # 返回404 Not Found
    return jsonify(user)


@app.route('/api/data')
def api_data():
    """带描述的abort"""
    if not request.args.get('key'):
        abort(400, description='API key is required')
    return jsonify({'data': 'value'})


# 自定义错误响应
@app.errorhandler(400)
def bad_request(error):
    return jsonify({
        'error': 'Bad Request',
        'message': error.description
    }), 400


# abort的HTTP状态码
"""
常用abort状态码:
- 400: Bad Request - 请求格式错误
- 401: Unauthorized - 未认证
- 403: Forbidden - 无权限
- 404: Not Found - 资源不存在
- 405: Method Not Allowed - 方法不允许
- 406: Not Acceptable - 无法接受
- 409: Conflict - 资源冲突
- 410: Gone - 资源已删除
- 413: Request Entity Too Large - 请求体过大
- 415: Unsupported Media Type - 不支持的媒体类型
- 422: Unprocessable Entity - 验证失败
- 429: Too Many Requests - 请求过多
- 500: Internal Server Error - 服务器错误
- 501: Not Implemented - 未实现
- 503: Service Unavailable - 服务不可用
"""


def is_admin():
    """检查是否为管理员"""
    return False


def get_user_from_db(user_id):
    """从数据库获取用户"""
    return None

5.3 错误处理最佳实践

python 复制代码
"""
错误处理最佳实践
"""

from flask import Flask, jsonify, request, render_template
from functools import wraps
import traceback

app = Flask(__name__)


# ============================================
# 1. 统一错误响应格式
# ============================================

def error_response(message, status_code, details=None):
    """
    统一错误响应格式
    
    参数:
        message: 错误消息
        status_code: HTTP状态码
        details: 详细信息
    """
    response = {
        'success': False,
        'error': {
            'message': message,
            'status_code': status_code
        }
    }
    
    if details:
        response['error']['details'] = details
    
    if app.debug:
        response['error']['trace'] = traceback.format_exc()
    
    return jsonify(response), status_code


# ============================================
# 2. 全局异常捕获
# ============================================

@app.errorhandler(Exception)
def handle_exception(error):
    """
    捕获所有未处理的异常
    
    生产环境不应暴露详细错误信息
    """
    # 记录错误日志
    app.logger.error(
        f'Unhandled exception: {str(error)}\n'
        f'Traceback: {traceback.format_exc()}'
    )
    
    # API请求返回JSON
    if request.path.startswith('/api/'):
        return error_response(
            message='Internal server error',
            status_code=500
        )
    
    # Web请求返回错误页面
    return render_template('errors/500.html'), 500


# ============================================
# 3. 错误处理装饰器
# ============================================

def handle_errors(f):
    """
    错误处理装饰器
    
    捕获视图函数中的异常并返回统一格式
    """
    @wraps(f)
    def decorated(*args, **kwargs):
        try:
            return f(*args, **kwargs)
        except APIError as e:
            return error_response(
                message=e.message,
                status_code=e.status_code,
                details=e.payload
            )
        except Exception as e:
            app.logger.error(f'Error in {f.__name__}: {str(e)}')
            return error_response(
                message='Internal server error',
                status_code=500
            )
    return decorated


@app.route('/api/protected')
@handle_errors
def protected_route():
    """使用错误处理装饰器"""
    # 业务逻辑
    if not validate_request():
        raise ValidationError('Invalid request')
    
    return jsonify({'data': 'success'})


# ============================================
# 4. 错误日志记录
# ============================================

import logging
from logging.handlers import RotatingFileHandler
import os

def setup_error_logging(app):
    """配置错误日志"""
    if not app.debug:
        # 确保日志目录存在
        if not os.path.exists('logs'):
            os.mkdir('logs')
        
        # 文件日志
        file_handler = RotatingFileHandler(
            'logs/errors.log',
            maxBytes=10 * 1024 * 1024,  # 10MB
            backupCount=5
        )
        file_handler.setFormatter(logging.Formatter(
            '%(asctime)s %(levelname)s: %(message)s '
            '[in %(pathname)s:%(lineno)d]'
        ))
        file_handler.setLevel(logging.ERROR)
        app.logger.addHandler(file_handler)
        
        # 邮件通知(严重错误)
        from logging.handlers import SMTPHandler
        
        mail_handler = SMTPHandler(
            mailhost=(app.config['MAIL_SERVER'], app.config['MAIL_PORT']),
            fromaddr=app.config['MAIL_DEFAULT_SENDER'],
            toaddrs=app.config['ADMINS'],
            subject='[MyApp] Application Error',
            credentials=(app.config['MAIL_USERNAME'], app.config['MAIL_PASSWORD'])
        )
        mail_handler.setLevel(logging.ERROR)
        app.logger.addHandler(mail_handler)


# ============================================
# 5. 错误页面模板
# ============================================

"""
templates/errors/404.html:
<!DOCTYPE html>
<html>
<head>
    <title>404 - Page Not Found</title>
</head>
<body>
    <h1>404 - Page Not Found</h1>
    <p>The page you are looking for does not exist.</p>
    <a href="{{ url_for('main.index') }}">Go to Home</a>
</body>
</html>

templates/errors/500.html:
<!DOCTYPE html>
<html>
<head>
    <title>500 - Internal Server Error</title>
</head>
<body>
    <h1>500 - Internal Server Error</h1>
    <p>Something went wrong. Please try again later.</p>
    <a href="{{ url_for('main.index') }}">Go to Home</a>
</body>
</html>
"""


def validate_request():
    return False

六、上下文管理机制

6.1 上下文栈结构

python 复制代码
"""
Flask上下文管理详解

Flask使用两个上下文栈来管理请求状态:
1. _request_ctx_stack: 请求上下文栈
2. _app_ctx_stack: 应用上下文栈
"""

from flask import Flask, request, current_app, g

app = Flask(__name__)


# ============================================
# 请求上下文
# ============================================
"""
请求上下文包含:
- request: 当前请求对象
- session: 当前会话对象

生命周期: 一个HTTP请求
"""

@app.route('/request-context')
def request_context_demo():
    """
    请求上下文示例
    """
    # request对象在请求上下文中可用
    method = request.method
    path = request.path
    
    # session对象也在请求上下文中
    # session['key'] = 'value'
    
    return {
        'method': method,
        'path': path
    }


# ============================================
# 应用上下文
# ============================================
"""
应用上下文包含:
- current_app: 当前Flask应用实例
- g: 请求期间存储数据的命名空间

生命周期: 一个请求或CLI命令
"""

@app.route('/app-context')
def app_context_demo():
    """
    应用上下文示例
    """
    # current_app指向当前Flask应用
    debug = current_app.debug
    config = current_app.config
    
    # g是请求级别的存储空间
    g.user_id = 123
    g.start_time = time.time()
    
    return {
        'debug': debug,
        'user_id': g.user_id
    }


# ============================================
# 手动创建上下文
# ============================================

import time

def background_task():
    """
    后台任务需要手动创建应用上下文
    """
    with app.app_context():
        # 现在可以访问current_app和g
        print(f'App name: {current_app.name}')
        
        # 可以访问数据库等扩展
        # from myapp.extensions import db
        # users = db.session.query(User).all()


def test_request_context():
    """
    测试请求上下文
    """
    with app.test_request_context('/test?arg=value'):
        # 现在可以访问request
        print(f'Path: {request.path}')
        print(f'Args: {request.args}')
        
        # 可以调用视图函数
        # response = app.view_functions['endpoint']()


# ============================================
# g对象详解
# ============================================

@app.before_request
def before_request():
    """
    在before_request中设置g对象
    """
    g.start_time = time.time()
    g.request_id = request.headers.get('X-Request-ID')


@app.route('/g-demo')
def g_demo():
    """
    g对象使用示例
    """
    # 获取before_request中设置的值
    start_time = g.get('start_time')
    request_id = g.get('request_id')
    
    # 设置新值
    g.processed = True
    
    return {
        'request_id': request_id,
        'elapsed': time.time() - start_time if start_time else None
    }


@app.teardown_request
def teardown_request(exception=None):
    """
    在teardown_request中清理g对象
    """
    # g对象会自动清理,无需手动操作
    pass

6.2 上下文生命周期

View AppContext RequestContext Flask WSGI Client View AppContext RequestContext Flask WSGI Client alt AppContext not exists alt Last request HTTP Request call(environ) Create RequestContext push() to _request_ctx_stack Check AppContext exists? Create AppContext push() to _app_ctx_stack before_request hooks Call view function Response after_request hooks teardown_request hooks pop() Check last request? teardown_appcontext hooks pop() Response HTTP Response

6.3 上下文代理原理

python 复制代码
"""
Flask上下文代理原理

request, current_app, g, session 都是代理对象
它们通过LocalProxy实现对栈顶对象的透明访问
"""

from werkzeug.local import LocalProxy, LocalStack

# Flask内部的实现(简化版)

# 创建栈
_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()


# 创建代理
def _get_request():
    """获取当前请求对象"""
    top = _request_ctx_stack.top
    if top is None:
        raise RuntimeError('Working outside of request context')
    return top.request


request = LocalProxy(_get_request)


def _get_current_app():
    """获取当前应用对象"""
    top = _app_ctx_stack.top
    if top is None:
        raise RuntimeError('Working outside of application context')
    return top.app


current_app = LocalProxy(_get_current_app)


# 使用示例
"""
代理对象的好处:
1. 无需显式传递request对象
2. 线程安全
3. 代码简洁

# 不使用代理(需要显式传递)
def view_function(request):
    return request.method

# 使用代理(自动获取当前请求)
def view_function():
    return request.method
"""

七、WSGI中间件

7.1 中间件模式

python 复制代码
"""
WSGI中间件模式详解

中间件是包裹Flask应用的WSGI应用
可以在请求到达Flask前后进行处理
"""

from flask import Flask

app = Flask(__name__)


# ============================================
# 中间件基本结构
# ============================================

class SimpleMiddleware:
    """
    简单中间件示例
    
    WSGI中间件必须:
    1. 接受一个WSGI应用作为参数
    2. 实现__call__方法
    3. 调用被包裹的应用
    """
    
    def __init__(self, app):
        self.app = app
    
    def __call__(self, environ, start_response):
        """
        处理请求
        
        参数:
            environ: WSGI环境字典
            start_response: 开始响应的回调函数
        """
        # 请求前处理
        print(f'Before request: {environ["PATH_INFO"]}')
        
        # 调用被包裹的应用
        def custom_start_response(status, headers, exc_info=None):
            # 可以修改响应头
            headers.append(('X-Middleware', 'SimpleMiddleware'))
            return start_response(status, headers, exc_info)
        
        # 调用下一个应用
        response = self.app(environ, custom_start_response)
        
        # 响应后处理
        print(f'After response: {status}')
        
        return response


# 应用中间件
app.wsgi_app = SimpleMiddleware(app.wsgi_app)


# ============================================
# ProxyFix中间件
# ============================================

from werkzeug.middleware.proxy_fix import ProxyFix

"""
ProxyFix中间件

用于反向代理环境,修复请求头中的真实IP等信息

参数:
    x_for: X-Forwarded-For头的信任层数
    x_proto: X-Forwarded-Proto头的信任层数
    x_host: X-Forwarded-Host头的信任层数
    x_port: X-Forwarded-Port头的信任层数
    x_prefix: X-Forwarded-Prefix头的信任层数
"""

# 基本配置
app.wsgi_app = ProxyFix(
    app.wsgi_app,
    x_for=1,      # 信任一层代理
    x_proto=1,
    x_host=1,
    x_port=1
)

# 多层代理配置
app.wsgi_app = ProxyFix(
    app.wsgi_app,
    x_for=2,      # 信任两层代理
    x_proto=1,
    x_host=1
)


# ============================================
# 性能分析中间件
# ============================================

from werkzeug.middleware.profiler import ProfilerMiddleware

"""
ProfilerMiddleware

用于性能分析,生成性能报告
"""

# 启用性能分析
app.wsgi_app = ProfilerMiddleware(
    app.wsgi_app,
    profile_dir='profiles'  # 性能报告输出目录
)


# ============================================
# 自定义请求计时中间件
# ============================================

import time

class TimingMiddleware:
    """请求计时中间件"""
    
    def __init__(self, app):
        self.app = app
    
    def __call__(self, environ, start_response):
        start_time = time.time()
        
        def custom_start_response(status, headers, exc_info=None):
            elapsed = time.time() - start_time
            headers.append(('X-Response-Time', f'{elapsed:.4f}s'))
            return start_response(status, headers, exc_info)
        
        return self.app(environ, custom_start_response)


# ============================================
# 自定义错误处理中间件
# ============================================

class ErrorHandlingMiddleware:
    """全局错误处理中间件"""
    
    def __init__(self, app):
        self.app = app
    
    def __call__(self, environ, start_response):
        try:
            return self.app(environ, start_response)
        except Exception as e:
            # 记录错误
            import traceback
            error_trace = traceback.format_exc()
            print(f'Error: {error_trace}')
            
            # 返回错误响应
            status = '500 Internal Server Error'
            headers = [('Content-Type', 'application/json')]
            start_response(status, headers)
            
            import json
            return [json.dumps({
                'error': 'Internal Server Error',
                'message': str(e) if environ.get('DEBUG') else 'Server error'
            }).encode('utf-8')]


# ============================================
# 静态文件中间件
# ============================================

from whitenoise import WhiteNoise

"""
WhiteNoise中间件

用于高效服务静态文件,支持缓存和压缩
"""

# 应用WhiteNoise
app.wsgi_app = WhiteNoise(
    app.wsgi_app,
    root='static/',           # 静态文件目录
    prefix='static/',         # URL前缀
    max_age=31536000          # 缓存时间(1年)
)

# 添加额外的静态文件目录
# app.wsgi_app.add_files('media/', prefix='media/')

八、异步视图支持

8.1 异步视图基础

python 复制代码
"""
Flask异步视图支持(Flask 2.0+)

使用async def定义异步视图函数
"""

from flask import Flask, jsonify
import asyncio
import aiohttp

app = Flask(__name__)


# ============================================
# 基本异步视图
# ============================================

@app.route('/async-hello')
async def async_hello():
    """基本异步视图"""
    await asyncio.sleep(0.1)  # 模拟异步操作
    return 'Hello, Async World!'


# ============================================
# 异步HTTP请求
# ============================================

@app.route('/fetch-data')
async def fetch_data():
    """异步获取外部数据"""
    async with aiohttp.ClientSession() as session:
        async with session.get('https://api.example.com/data') as response:
            data = await response.json()
    
    return jsonify(data)


# ============================================
# 并发异步操作
# ============================================

@app.route('/parallel-fetch')
async def parallel_fetch():
    """并发获取多个数据源"""
    async def fetch_url(url):
        async with aiohttp.ClientSession() as session:
            async with session.get(url) as response:
                return await response.json()
    
    # 并发执行多个请求
    urls = [
        'https://api.example.com/users',
        'https://api.example.com/posts',
        'https://api.example.com/comments'
    ]
    
    results = await asyncio.gather(*[fetch_url(url) for url in urls])
    
    return jsonify({
        'users': results[0],
        'posts': results[1],
        'comments': results[2]
    })


# ============================================
# 异步数据库操作
# ============================================

@app.route('/async-users')
async def async_users():
    """
    异步数据库查询
    
    需要使用异步数据库驱动如:
    - SQLAlchemy 2.0+ async
    - asyncpg (PostgreSQL)
    - aiomysql (MySQL)
    """
    # 使用SQLAlchemy 2.0异步示例
    # from sqlalchemy.ext.asyncio import AsyncSession
    # async with AsyncSession(engine) as session:
    #     result = await session.execute(select(User))
    #     users = result.scalars().all()
    
    return jsonify({'users': []})


# ============================================
# 异步请求钩子
# ============================================

@app.before_request
async def async_before_request():
    """异步before_request钩子"""
    # 可以执行异步操作
    # await some_async_operation()
    pass


@app.after_request
async def async_after_request(response):
    """异步after_request钩子"""
    # 可以执行异步操作
    return response


# ============================================
# 异步与同步混用
# ============================================

@app.route('/mixed')
def mixed_view():
    """
    同步视图中调用异步函数
    
    使用asyncio.run()或loop
    """
    async def async_operation():
        await asyncio.sleep(0.1)
        return 'async result'
    
    # 在同步上下文中运行异步函数
    result = asyncio.run(async_operation())
    
    return result

九、最佳实践总结

python 复制代码
"""
Flask请求处理最佳实践
"""

# ============================================
# 1. 请求处理
# ============================================
"""
✅ 使用request对象获取请求数据
✅ 使用适当的参数获取方法
✅ 验证所有输入数据
✅ 处理文件上传时使用secure_filename
✅ 使用g对象存储请求级别数据
"""

# ============================================
# 2. 响应处理
# ============================================
"""
✅ API返回JSON格式响应
✅ 使用正确的HTTP状态码
✅ 设置适当的安全响应头
✅ 大文件使用流式响应
✅ 使用make_response构建复杂响应
"""

# ============================================
# 3. 错误处理
# ============================================
"""
✅ 注册全局错误处理器
✅ 使用自定义异常类
✅ 统一错误响应格式
✅ 记录错误日志
✅ 生产环境不暴露详细错误信息
"""

# ============================================
# 4. 上下文管理
# ============================================
"""
✅ 理解请求上下文和应用上下文
✅ 后台任务使用app_context
✅ 测试使用test_request_context
✅ 避免在全局作用域访问上下文代理
"""

# ============================================
# 5. 性能优化
# ============================================
"""
✅ 使用请求钩子进行预处理
✅ 使用缓存减少重复计算
✅ 异步处理耗时操作
✅ 合理使用中间件
✅ 监控请求处理时间
"""
相关推荐
love530love23 分钟前
LiveTalking 数字人项目 Windows 部署完全指南(EPGF 架构)
人工智能·windows·python·架构·livetalking·epgf
遇事不決洛必達31 分钟前
【Python基础】GIL 锁是什么及其对爬虫的影响
爬虫·python·线程·进程·gil锁
星辰徐哥35 分钟前
Spring Boot 微服务架构设计与实现
spring boot·后端·微服务
星辰徐哥35 分钟前
Spring Boot 数据导入导出与报表生成
spring boot·后端·ui
明夜之约36 分钟前
Spring Boot 自动装配源码
java·spring boot·后端
Leaton Lee37 分钟前
Spring Boot分层架构详解:从Controller到Service再到Mapper的完整流程
java·spring boot·后端·架构
Micro麦可乐38 分钟前
Spring Boot 实战:从零设计一个短链系统(含完整代码与数据库设计)
数据库·spring boot·后端·哈希算法·雪花算法·短链系统
Jinkxs39 分钟前
Resilience4j- 与 Spring Boot 快速集成:自动配置与基础注解使用
java·spring boot·后端
毕设源码_郑学姐40 分钟前
计算机毕业设计springboot网络相册设计与实现 基于Spring Boot框架的在线相册管理系统开发与应用 Spring Boot驱动的网络影集设计与实践
spring boot·后端·课程设计
辣机小司40 分钟前
【踩坑记录:Spring Boot 配置文件读取值不一致?警惕 YAML 的“八进制陷阱”与 SnakeYAML 版本之谜】
java·spring boot·后端·yaml·踩坑记录