把 Python 脚本变成 Flask Web 应用:路由、输入安全与 App Engine 部署

2026-09-30 28 预计阅读时间: 1 分钟
来源: realpython.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:9 分钟

把一个只能在终端运行的 Python 脚本改造成 Web 应用,核心并不只是“加一个 Flask”。你需要重新定义程序入口:用路由接收请求,从查询参数、表单或 JSON 中读取数据,安全地生成响应,并让云平台知道如何启动应用。

下面用一个可直接运行的小项目串起这些关键点,并展示如何将它部署到 Google App Engine。

Flask 路由就是 Web 版的函数入口

普通 Python 脚本通常从 if __name__ == "__main__" 开始执行。Flask 应用则根据 URL 和 HTTP 方法选择要调用的函数:

  • GET /:返回页面。
  • POST /greet:处理 HTML 表单。
  • POST /api/greet:接收 JSON,并返回 JSON。

同一个路径也可以响应多种 HTTP 方法,但在业务代码中显式区分读取和写入操作,通常更容易维护。

可以创建下面的项目结构:

flask-greeting/
├── app.py
├── app.yaml
├── requirements.txt
└── templates/
    └── index.html

app.py:

from flask import Flask, abort, jsonify, render_template, request

app = Flask(__name__)


def normalize_name(value: str) -> str:
    name = value.strip()
    if not name:
        abort(400, description="name is required")
    if len(name) > 80:
        abort(400, description="name is too long")
    return name


@app.get("/")
def index():
    # 例如访问 /?name=Alice 时,从查询字符串读取 name。
    name = request.args.get("name", "访客")
    return render_template("index.html", name=name)


@app.post("/greet")
def greet_from_form():
    # HTML 表单通常通过 request.form 读取。
    name = normalize_name(request.form.get("name", ""))
    return render_template("index.html", name=name)


@app.post("/api/greet")
def greet_from_json():
    # silent=True 可避免无效 JSON 直接触发框架异常页面。
    payload = request.get_json(silent=True) or {}
    name = normalize_name(str(payload.get("name", "")))
    return jsonify(message=f"你好,{name}!")


if __name__ == "__main__":
    app.run(host="127.0.0.1", port=8080, debug=True)

这个例子体现了三种常见请求数据来源:

数据位置 Flask 接口 常见场景
URL 查询字符串 request.args 搜索、筛选、分页
HTML 表单正文 request.form 登录、提交资料
JSON 请求正文 request.get_json() 前后端 API、移动端接口

不要把这三者混为一谈。客户端发送 JSON 时,服务端不应该从 request.form 中寻找字段;反过来也一样。

用户输入不仅要校验,还要正确转义

模板文件 templates/index.html 可以这样写:

<!doctype html>
<html lang="zh-CN">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Flask Greeting</title>
</head>
<body>
  <h1>你好,{{ name }}!</h1>

  <form method="post" action="/greet">
    <label for="name">名字</label>
    <input id="name" name="name" maxlength="80" required>
    <button type="submit">提交</button>
  </form>
</body>
</html>

Jinja 对 .html 模板中的变量默认进行 HTML 转义。因此,如果用户提交:

<script>alert('xss')</script>

{{ name }} 会把它作为文本显示,而不是作为脚本执行。这里有两个容易踩中的坑:

  1. 不要为了“显示原始 HTML”而随意添加 |safe。
  2. 不要直接拼接 HTML,例如 return "<h1>" + name + "</h1>"。

转义解决的是输出上下文安全问题,长度、格式和业务规则仍然需要单独校验。示例中的 normalize_name() 同时拒绝空字符串,并限制输入长度。真实项目还应根据字段类型检查邮箱、日期、枚举值等内容。

在本地运行并验证三种请求

requirements.txt:

Flask>=3.0,<4.0
gunicorn>=21,<24

在 macOS 或 Linux 中执行:

python3 -m venv .venv
source .venv/bin/activate
python -m pip install -r requirements.txt
python app.py

Windows PowerShell 可将激活命令改为:

.venv\Scripts\Activate.ps1

应用启动后,可以分别测试查询参数、表单和 JSON:

# 查询参数
curl --get --data-urlencode "name=Alice" http://127.0.0.1:8080/

# HTML 表单
curl -X POST -d "name=Bob" http://127.0.0.1:8080/greet

# JSON API
curl -X POST http://127.0.0.1:8080/api/greet \
  -H "Content-Type: application/json" \
  -d '{"name":"Carol"}'

# 检查模板是否会转义 HTML
curl --get \
  --data-urlencode "name=<script>alert(1)</script>" \
  http://127.0.0.1:8080/

JSON 接口应返回类似结果:

{"message":"你好,Carol!"}

还要测试失败路径,而不只是正常输入:

curl -i -X POST http://127.0.0.1:8080/api/greet \
  -H "Content-Type: application/json" \
  -d '{}'

这次应得到 400 Bad Request。明确拒绝无效数据,比让错误一路流入数据库或下游服务更安全。

部署到 Google App Engine

下面给出一种可改造的 App Engine 配置。示例假设平台支持 Python 3.11;部署前应根据目标项目当前支持的运行时调整版本。

app.yaml:

runtime: python311

entrypoint: gunicorn -b :$PORT app:app

app:app 中,第一个 app 表示 app.py 模块,第二个 app 表示文件内创建的 Flask 应用对象:

app = Flask(__name__)

安装并初始化 Google Cloud CLI 后,可以执行:

gcloud auth login
gcloud config set project YOUR_PROJECT_ID
gcloud app create
gcloud app deploy
gcloud app browse

运行前需要把 YOUR_PROJECT_ID 替换为自己的 Google Cloud 项目 ID。gcloud app create 通常只需在项目首次启用 App Engine 时执行,并且区域选择需要谨慎,因为它会影响延迟、数据位置和后续架构选择。

生产部署中不要依赖 app.run(debug=True)。本地的开发服务器用于调试,而示例中的 App Engine 配置通过 Gunicorn 监听平台提供的 $PORT。依赖也必须写入 requirements.txt,不能假设云端已经安装了本地环境中的包。

上线前值得逐项检查

将脚本包装成 Flask 应用时,可以用下面的清单做收尾:

  • 路由是否限制了正确的 HTTP 方法。
  • 查询参数、表单和 JSON 是否通过对应的 Flask 接口读取。
  • 缺失字段、超长输入和错误类型是否返回合理的 4xx 响应。
  • 用户输入是否由 Jinja 转义,是否误用了 |safe 或字符串拼接 HTML。
  • 密钥是否来自环境变量,而不是写进源码或 app.yaml。
  • requirements.txt 是否包含所有运行依赖。
  • 云端进程是否监听平台提供的端口。
  • 部署前是否关闭调试模式,并检查日志中是否泄露请求数据。

Flask 让“脚本变服务”的第一步非常轻量,但路由设计、输入处理和输出转义决定了这个服务能否安全地进入真实环境。先用小而完整的请求链路验证行为,再接入数据库、身份认证和外部 API,会比一开始堆叠功能更稳妥。


相关推荐