Flask 请求与响应
Web 应用的核心是「接收请求,返回响应」,本章全面讲解 Flask 中如何读取请求数据和构造各种类型的响应。
所有示例都可以在开发服务器中直接运行验证。
请求对象——request
Flask 通过全局代理对象 request 提供所有请求信息。
虽然它是「全局」对象,但 Flask 内部通过线程隔离机制确保每个请求获取的是自己的数据。
实例
from flask import Flask, request
app = Flask(__name__)
@app.route("/debug")
def debug():
# 输出请求的各种信息,帮助理解 request 对象
return f"""
<h1>请求调试信息</h1>
<ul>
<li>方法:{request.method}</li>
<li>路径:{request.path}</li>
<li>完整 URL:{request.url}</li>
<li>主机:{request.host}</li>
<li>客户端 IP:{request.remote_addr}</li>
<li>User-Agent:{request.headers.get('User-Agent')}</li>
</ul>
"""
访问 http://127.0.0.1:5000/debug?foo=bar 即可看到请求的详细信息。

获取查询参数——request.args
URL 中 ? 后面的参数称为查询参数(Query String)。
使用 request.args 读取,它像一个字典:
实例
def search():
# request.args.get() 安全获取参数,key 不存在时返回 None
keyword = request.args.get("keyword", "") # 获取查询关键词
page = request.args.get("page", "1") # 获取页码,默认为 1
return f"""
<h1>搜索结果</h1>
<p>关键词:{keyword}</p>
<p>页码:{page}</p>
"""
# 测试:/search?keyword=flask&page=2
# 测试:/search?keyword=RUNOOB
推荐使用 .get() 方法而不是直接下标访问 request.args["key"],因为后者在 key 不存在时会抛出 KeyError,导致返回 400 错误页面。
获取表单数据——request.form
处理 POST 请求提交的表单数据,使用 request.form:
实例
def register():
if request.method == "POST":
# 使用表单中 name="username" 字段的值
username = request.form.get("username", "")
password = request.form.get("password", "")
if not username or not password:
return "<h1>错误</h1><p>用户名和密码不能为空</p>", 400
return f"<h1>注册成功</h1><p>欢迎 {username} 加入 RUNOOB!</p>"
# GET 请求时显示注册表单
return """
<h1>注册</h1>
<form method="post">
<p>用户名:<input type="text" name="username"></p>
<p>密码:<input type="password" name="password"></p>
<p><input type="submit" value="注册"></p>
</form>
"""
注意:处理表单的 HTML 页面中,<form> 必须设置 method="post",否则数据会通过 URL 的查询字符串发送(GET 方式),敏感信息会暴露在 URL 中。
获取 JSON 数据——request.json
现代 Web 开发中,JSON 是最常见的数据格式。
当客户端发送 Content-Type: application/json 的请求时,使用 request.json 获取解析后的数据:
实例
def create_user():
# request.json 返回已经解析为 Python dict 的 JSON 数据
data = request.json # 例如客户端发送 {"name": "runoob", "email": "test@runoob.com"}
# 安全获取字段
name = data.get("name", "未知")
email = data.get("email", "未提供")
# 直接返回 dict,Flask 自动转换为 JSON 响应
return {
"message": "用户创建成功",
"name": name,
"email": email
}
可以用 curl 或 Postman 测试:
$ curl -X POST http://127.0.0.1:5000/api/user \
-H "Content-Type: application/json" \
-d '{"name": "runoob", "email": "test@runoob.com"}'
{
"message": "用户创建成功",
"name": "runoob",
"email": "test@runoob.com"
}
文件上传
处理上传的文件使用 request.files,配合 Werkzeug 提供的 secure_filename() 确保文件名安全:
实例
from werkzeug.utils import secure_filename # 过滤危险的文件名字符
# 配置上传目录(实际项目中应从配置读取)
UPLOAD_DIR = "uploads"
@app.route("/upload", methods=["GET", "POST"])
def upload_file():
if request.method == "POST":
# 检查是否有文件被上传
if "file" not in request.files:
return "未选择文件", 400
file = request.files["file"]
# 用户可能提交了空表单(未选择文件)
if file.filename == "":
return "文件名为空", 400
# secure_filename 过滤掉路径遍历等危险字符
# 例如 "../../etc/passwd" 会被处理为 "etc_passwd"
filename = secure_filename(file.filename)
# 确保上传目录存在
os.makedirs(UPLOAD_DIR, exist_ok=True)
# 保存文件
file.save(os.path.join(UPLOAD_DIR, filename))
return f"文件 {filename} 上传成功"
# GET 请求显示上传表单
return """
<h1>上传文件</h1>
<form method="post" enctype="multipart/form-data">
<p><input type="file" name="file"></p>
<p><input type="submit" value="上传"></p>
</form>
"""
表单如果有文件上传字段,<form> 标签必须设置 enctype="multipart/form-data",否则文件数据不会被浏览器发送。
响应类型完全指南
视图函数可以返回多种类型的数据,Flask 会自动将它们转换为合适的 HTTP 响应。
理解这个转换规则是掌握 Flask 的关键。
返回字符串
返回的字符串作为 HTML 响应体,状态码默认 200,Content-Type 为 text/html。
返回字典或列表(JSON)
自动调用 jsonify() 转换为 JSON 响应,Content-Type 为 application/json。
实例
def get_posts():
# 返回 dict,Flask 自动转为 JSON
return {
"status": "ok",
"data": [
{"id": 1, "title": "Flask 入门教程"},
{"id": 2, "title": "RESTful API 设计"},
]
}
@app.get("/api/tags")
def get_tags():
# 返回 list,Flask 自动转为 JSON
return ["Python", "Flask", "Web 开发", "RUNOOB"]
返回元组——控制状态码和响应头
元组格式非常实用,有三种形式:
实例
@app.get("/not-found")
def not_found():
return "<h1>页面不存在</h1>", 404
# 形式 2:(body, headers_dict)
@app.get("/custom-header")
def custom_header():
return "OK", {"X-Custom-Header": "runoob"}
# 形式 3:(body, status_code, headers_dict)
@app.post("/api/login")
def api_login():
# 返回 token 和自定义状态码
return {"token": "abc123", "user": "runoob"}, 201, {"X-RateLimit": "100"}
重定向
使用 redirect() 函数将用户引导到另一个 URL:
实例
app = Flask(__name__)
@app.route("/")
def index():
# 访问首页时重定向到登录页
return redirect(url_for("login"))
@app.route("/login")
def login():
return "<h1>请先登录</h1>"
@app.route("/old-page")
def old_page():
# 旧页面永久迁移到新页面,使用 301 状态码
return redirect(url_for("new_page"), code=301)
@app.route("/new-page")
def new_page():
return "<h1>这是新页面</h1>"
默认重定向状态码为 303 See Other,适用于表单提交后重定向(PRG 模式)。
对于永久迁移场景,应使用 code=301。
终止请求——abort
使用 abort() 立即终止当前请求并返回 HTTP 错误:
实例
@app.route("/post/<int:post_id>")
def view_post(post_id):
# 假设文章 ID 只有 1-100
if post_id < 1 or post_id > 100:
# 立即返回 404,后续代码不会执行
abort(404)
return f"<h1>文章 #{post_id}</h1>"
@app.route("/admin")
def admin():
# 未授权访问时返回 403
abort(403, description="你没有权限访问此页面")
make_response——手动控制响应
当需要手动设置 Cookie 或自定义响应头时,使用 make_response():
实例
@app.route("/set-cookie")
def set_cookie():
# make_response 将视图函数的返回值包装为 Response 对象
resp = make_response("<h1>Cookie 已设置</h1>")
# 在 Response 对象上设置 Cookie
resp.set_cookie("theme", "dark", max_age=60*60*24) # 有效期 24 小时
resp.set_cookie("lang", "zh-CN")
# 也可以自定义响应头
resp.headers["X-Powered-By"] = "RUNOOB-Flask"
return resp
request 常用属性速查表
| 属性 | 类型 | 说明 | 示例值 |
|---|---|---|---|
| request.method | str | HTTP 请求方法 | "GET", "POST" |
| request.path | str | URL 路径(不含域名) | "/search" |
| request.args | MultiDict | URL 查询参数 | ?keyword=flask&page=1 |
| request.form | MultiDict | POST 表单数据 | username=admin |
| request.json | dict 或 None | JSON 请求体(已解析) | {"name": "runoob"} |
| request.files | FileMultiDict | 上传的文件 | request.files["avatar"] |
| request.headers | Headers | 请求头 | request.headers["User-Agent"] |
| request.cookies | dict | 客户端发来的 Cookie | request.cookies.get("theme") |
| request.remote_addr | str | 客户端 IP 地址 | "127.0.0.1" |
