现在位置: 首页 > Flask 教程 > 正文

Flask 请求与响应

Web 应用的核心是「接收请求,返回响应」,本章全面讲解 Flask 中如何读取请求数据和构造各种类型的响应。

所有示例都可以在开发服务器中直接运行验证。


请求对象——request

Flask 通过全局代理对象 request 提供所有请求信息。

虽然它是「全局」对象,但 Flask 内部通过线程隔离机制确保每个请求获取的是自己的数据。

实例

# 导入 request,这是一个线程安全的代理对象
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 读取,它像一个字典:

实例

@app.route("/search")
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

实例

@app.route("/register", methods=["GET", "POST"])
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 获取解析后的数据:

实例

@app.post("/api/user")
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() 确保文件名安全:

实例

import os
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

实例

@app.get("/api/posts")
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"]

返回元组——控制状态码和响应头

元组格式非常实用,有三种形式:

实例

# 形式 1:(body, status_code)
@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:

实例

from flask import Flask, redirect, url_for

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 错误:

实例

from flask import abort

@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()

实例

from flask import 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"