3. 请求对象与响应构造

在 Flask 中,处理一次 HTTP 请求的核心工作可以概括为两件事:把客户端发来的数据读出来,以及把要返回给客户端的内容构造好。前者围绕 request 对象展开,后者围绕 make_response 与 Response 对象展开。理解这两条主线,就掌握了 Flask 视图函数最常用的输入输出机制。

request 是一个“全局代理”,不是全局变量

初学者常对 from flask import request 感到困惑:为什么可以直接导入一个 request 对象,而多个并发请求不会互相串数据?原因在于 request 并不是一个保存了真实数据的普通全局变量,而是一个代理对象(LocalProxy)。它本身不存数据,真正的请求数据存放在与当前线程/协程绑定的“请求上下文”里。当你访问 request.args 时,代理会去当前上下文中查找真正的请求对象,再把属性转发过去。

因此,request 只能在请求上下文被激活的情况下使用。视图函数被调用时,Flask 已经自动压入了请求上下文,所以视图内可以直接用。但如果你在视图外、后台线程里、或者应用启动阶段直接访问 request.args,就会抛出 RuntimeError: Working outside of request context。这不是 bug,而是上下文机制在提醒你:此刻没有“当前请求”。

需要手动进入上下文时,可以使用 with app.test_request_context('/path?x=1'): 或 with app.request_context(environ):,常用于单元测试和脚本调试。

请求上下文与应用上下文的区别

Flask 有两类上下文:应用上下文(current_app、g)和请求上下文(request、session)。请求上下文在每次请求开始时压入,请求结束时弹出;应用上下文通常随请求一起压入,也可以在无请求时单独使用。二者关系可以用下面的结构图理解:

请求上下文与应用上下文的关系 HTTP 请求 GET /search?q=flask 请求上下文 request(LocalProxy) session 应用上下文 current_app g 随请求压入 视图函数 读取 request 构造响应 Response 状态码 响应头/体 关键规则: • request 只能在请求上下文中访问,视图内自动激活 • 请求结束 → 上下文弹出 → request 失效,不可跨请求保存 • 需要跨请求共享数据用 session / 数据库,而非 request

读取查询参数:request.args

URL 中 ? 后面的键值对称为查询参数(query string),例如 /search?q=flask&page=2。Flask 把它们解析到 request.args,这是一个类似字典的 MultiDict,支持同名键重复出现。

from flask import request

@app.route('/search')
def search():
    q = request.args.get('q', '')          # 取单个值,默认空串
    page = request.args.get('page', 1, type=int)  # 转成 int
    tags = request.args.getlist('tag')     # 取同名多值
    return f'q={q}, page={page}, tags={tags}'

要点:get() 只返回第一个值,getlist() 返回全部值;type=int 会在转换失败时返回默认值而不是报错。查询参数始终是字符串,需要数字时应显式转换。

读取表单数据:request.form 与 request.values

当请求方法为 POST 且 Content-Type 为 application/x-www-form-urlencoded 或 multipart/form-data 时,表单字段会进入 request.form。它的用法与 request.args 一致,同样支持 get()、getlist() 和 type 参数。

@app.route('/login', methods=['POST'])
def login():
    username = request.form.get('username', '').strip()
    password = request.form.get('password', '')
    if not username or not password:
        return '缺少字段', 400
    return f'欢迎 {username}'

如果希望同时兼容查询参数和表单数据,可以使用 request.values,它按“先 args 后 form”的顺序合并两者。但要注意:当同名键同时出现在 URL 和表单中时,values 的取值顺序可能造成歧义,生产代码中建议明确区分来源。

属性数据来源典型场景
request.argsURL 查询字符串搜索、分页、筛选
request.formPOST 表单体登录、提交表单
request.valuesargs + form 合并兼容两种来源
request.jsonJSON 请求体前后端分离接口
request.files上传文件图片、附件上传

用 make_response 构造响应

视图函数可以直接返回字符串、元组或 Response 对象。Flask 会把字符串自动包装成 Response,默认状态码 200、Content-Type: text/html; charset=utf-8。当需要精细控制状态码、响应头或 Cookie 时,使用 make_response 更清晰。

from flask import make_response

@app.route('/profile')
def profile():
    resp = make_response('<h1>个人中心</h1>', 201)
    resp.headers['X-App-Version'] = '1.0.3'
    resp.set_cookie('theme', 'dark', max_age=3600)
    return resp

make_response 接受字符串、元组或已有 Response,返回一个可继续修改的 Response 对象。常见元组写法 return body, status, headers 本质上也是被 make_response 处理的。

Response 对象与状态码控制

Response 对象的核心属性包括:status_code(状态码)、headers(响应头)、data(字节响应体)、mimetype(MIME 类型)。状态码应遵循 HTTP 语义:200 成功、201 已创建、204 无内容、301/302 重定向、400 参数错误、401 未认证、403 无权限、404 未找到、500 服务器错误。

@app.route('/item/<int:item_id>')
def get_item(item_id):
    item = find_item(item_id)
    if item is None:
        return {'error': 'not found'}, 404
    return {'id': item.id, 'name': item.name}, 200

返回字典时,Flask 会自动序列化为 JSON 并设置 Content-Type: application/json。若需自定义响应头,可写成 return body, status, {'X-Trace-Id': 'abc123'}。

不要用 200 状态码返回错误信息。前端和网关常依据状态码做重试、跳转或告警,错误语义必须体现在状态码上,否则会掩盖真实故障。

一个完整的读取与响应示例

下面这个视图同时演示了查询参数、表单数据读取和状态码控制:

@app.route('/api/feedback', methods=['POST'])
def feedback():
    topic = request.args.get('topic', 'general')
    content = request.form.get('content', '').strip()
    if not content:
        return {'ok': False, 'msg': 'content 不能为空'}, 400
    if len(content) > 500:
        return {'ok': False, 'msg': 'content 过长'}, 422
    resp = make_response({'ok': True, 'topic': topic}, 201)
    resp.headers['X-Feedback-Topic'] = topic
    return resp

请求 POST /api/feedback?topic=bug,表单体 content=页面加载慢,会得到 201 与 JSON 响应;若 content 为空则返回 400。通过状态码和响应体,调用方可以明确判断本次请求的结果。

总结来说:request 是请求上下文的代理入口,负责读取 args、form、json、files;make_response 与 Response 负责构造可控的响应,状态码是表达结果语义的关键。把这两端用对,视图函数就具备了清晰的输入输出边界。