把一个只能在终端运行的 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 }} 会把它作为文本显示,而不是作为脚本执行。这里有两个容易踩中的坑:
- 不要为了“显示原始 HTML”而随意添加
|safe。 - 不要直接拼接 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,会比一开始堆叠功能更稳妥。