编程语言研究书库

分享你的应用 — 部署上云、调用外部 API,和守住那把钥匙

分享你的应用 — 部署上云、调用外部 API,和守住那把钥匙

这一章讲三件事: 怎么让应用离开你的电脑、别人点一个网址就能用; 怎么让应用调用互联网上的外部服务(API);以及一个安全红线—— API key 绝不能写进代码,那该怎么办。 读完你能把一个会调外部 API 的 Streamlit 应用免费部署上线,并且不泄露任何密钥。

1. 这一章讲什么

前四章的应用都活在你的电脑上。部署(deployment)做的事一句话: 让一台一直开着的服务器替你跑应用,别人通过一个网址访问1。 本章选了最省事的一条路(Streamlit Community Cloud,免费), 然后故意给应用加一点「真实世界」的复杂度:接一个外部 API 讲笑话。 复杂度一上,三个问题立刻浮出水面:依赖版本、API key 安全、执行模型的账单—— 这正是本章的三块核心内容。

2. 顶层全景:四条部署路线与三个新问题

四条路线,按省心程度排:

路线谁跑服务器优点代价 / 限制
局域网共享你的电脑零成本,改代码即时生效电脑得开机、只有同网段设备能访问(防火墙可能挡)2
专用服务器你自己维护的机器完全控制防火墙、系统更新、安全维护全归你3
云服务商(AWS/Azure/GCP)云厂商可扩展、高可用,基础设施归厂商管按用量计费,人一多就贵4
Streamlit Community CloudSnowflake(Streamlit 的母公司)免费、为 Streamlit 定制、连 GitHub 即发布有算力/内存/存储上限;约 12 小时无人访问就休眠5

新问题三连(本章都要解决):

  应用接了外部 API(requests 库)
      │
      ├─ ① 生产环境得装上 requests 的「对版本」   → requirements.txt
      ├─ ② API key 写进代码 = 公开泄露          → st.secrets + secrets.toml + .gitignore
      └─ ③ 每次点击都重跑脚本,每次都调 API      → session_state 缓存结果(第 04 章的武器复用)

  图说:三个问题没一个是部署本身,全是「应用变真实」带出来的。

3. 核心原理(一):部署到 Community Cloud 的三步

前置条件四个:git 命令行、GitHub 账号、Community Cloud 账号、两边账号打通。 书里最细的环节是 GitHub 的 personal access token(PAT,个人访问令牌)—— 命令行推代码时的密码替代品,创建路径(头像 → Settings → Developer Settings → Tokens Classic → Generate)书里给了逐步截图,并提醒:选 Repo 权限、设过期时间、 令牌只显示一次,抄下来存好6。

三步走:

  ① 建 GitHub 仓库    github.com 上 New → 起名 → 选 Public → 记下仓库 URL
  ② 推代码           git init → git add . → git commit -m "…"
                     → git remote add origin <带PAT的URL> → git push -u origin master
  ③ 告诉云在哪       Community Cloud 点 Create an App,
                     填仓库、分支、入口文件(todo_list.py)、想要的网址 → Deploy!

  图说:②里那个「带 PAT 的 URL」长这样:
       <你的PAT>@github.com/<你的名字>/<仓库>.git   ← 前面记得补协议头
       master 推不动就换 main(git branch 看当前分支名)。

一个重要提醒:别把 GitHub 和 git 搞混。 git 是版本控制系统(工具), GitHub 是托管 git 仓库最流行的网站(平台);Community Cloud 只认 GitHub7。

部署成功后,任何能上网的人都能访问 https://你选的名字.streamlit.app8。 注意那条休眠规则:约 12 小时无人访问,应用会被关掉省电;再访问时会看到 「app is waking up」的唤醒页——免费午餐的价格之一,受不了的人看第 12 章9。

改代码怎么更新? push 到 GitHub,Community Cloud 自动跟进。 git push(不用再加 -u origin master,上游已经设好)10。

4. 核心原理(二):调外部 API——HTTP、requests、JSON 一条龙

API 在这里的准确含义(和第 03 章的「内部契约」对比): 一份规定「外部服务允许你怎么用它」的契约——基底地址(base address)每个请求都带, 端点(endpoint)区分你要哪种服务;https://<基底地址>/<端点>11。 书里的例子:api-ninjas.com 的 Dad Jokes API,免费额度每月 1 万次调用, key 通过名叫 X-Api-Key 的 HTTP 头传12。

HTTP 速览(书里的侧栏):超文本传输协议,网页通信的语法规则。 请求 = 请求行(就是 URL)+ 头(headers,键值对的附加信息)+ 可选的正文(body)。 最常见两种:GET(信息全塞在 URL 里,浏览器地址栏回车就是 GET)和 POST(可以带正文,表单提交那种)13。

requests 库是 Python 里调 HTTP 的首选。 交互式走一遍主线例子:

  >>> import requests
  >>> response = requests.get("api.api-ninjas.com/v1/dadjokes")  # 协议头省略
  >>> response
  <Response [400]>                         ← 400 = 请求有问题
  >>> response.text
  '{"error": "Missing API Key."}'          ← 没带 key,被拒绝了

  >>> headers = {"X-Api-Key": "你的key"}
  >>> response = requests.get(url, headers=headers)
  >>> response.text
  '[{"joke": "I want to name my puppies Rolex and Timex…"}]'   ← 成功

(回应的笑话照原书原文14。)三个部件各就各位:response.status_code 是状态码 (200 = OK,404 = Not Found;requests.codes.ok 是可读的 200 写法); response.text 是原始字符串;response.json() 把 JSON 文本解析成 Python 对象—— 这里的 JSON 是个单元素列表,里面一个 {"joke": "..."} 字典, 所以取笑话要 response.json()[0]['joke']15。

封进 jokes.py(照原书 listing 5.1):

import requests

API_URL = "https://api.api-ninjas.com/v1/dadjokes"

def generate_joke(api_key):
    headers = {"X-Api-Key": api_key}
    response = requests.get(API_URL, headers=headers)
    if response.status_code == requests.codes.ok:
        return response.json()[0]['joke']
    return "Time flies like an arrow. Fruit flies like a banana."  # API 挂了时的兜底

兜底笑话(fallback)这一行是本节最容易被略过、也最职业的一笔: 外部服务必然偶尔失败,失败时应用要优雅,不要白屏16。

5. 核心原理(三):执行模型回来了——每次点击都在烧钱

st.info(generate_joke(api_key)) 一上,应用立刻不对: 用户每点任何东西,脚本重跑,笑话就重新拉一次——「每日一 joke」变成了「每点一 joke」。 免费额度每月 1 万次,认真用起来几天就烧完17。

解法就是第 04 章的老武器,换个用途:

if "joke" not in st.session_state:
    st.session_state.joke = generate_joke(api_key)
st.info(st.session_state.joke)

笑话只在会话里取一次,之后随便点,配额一分不烧18。 **这个模式值得命名记住:「session_state 缓存」——凡是「贵、慢、结果不变」的调用, 都应该套这个 if not in 的壳。**第 06 章会把它升级成正式的 st.cache_data。

6. 核心原理(四):密钥安全——st.secrets 的三层防线

现在到了本章的红线。前面代码里 key 是明文参数,推上公开的 GitHub, 任何人都能拿到你的 key,拿它烧你的额度——金额小的时候是烦,付费 API 时是灾19。 铁律一句话:任何密钥,绝不存在代码里,尤其是要公开的代码20。

三层防线,各管一段:

第一层:密钥搬出代码,进 secrets.toml。 在仓库根建 .streamlit/secrets.toml(TOML 是一种配置文件的格式,键值对 + 方括号分区)21:

[jokes_api]
api_key = "你的key"

代码里改成 api_key = st.secrets["jokes_api"]["api_key"]——st.secrets 和 session_state 一样 是字典式结构,但装的是机密22。本地运行时 Streamlit 自动读这个文件。

第二层:.gitignore 挡住手滑。 secrets.toml 还在磁盘上,挨着代码; 危险在于 git add . 一把梭把它提交上去。.gitignore 文件(放仓库根)告诉 git 哪些东西不许进仓库, 加一行 .streamlit/ 就封死了这条路23。有个坑:如果之前已经提交过一次, 光加 .gitignore 不够,要先 git rm -r --cached .streamlit/ 把它从 git 的记录里摘出去24。

第三层:生产环境的 key,在 Community Cloud 的 App Settings → Secrets 里粘贴25。 key 既不在 GitHub 上(第二层保证),也不在部署平台里给外人看——云端界面只有你自己能进。

  key 的三处居所,只有一处是危险的:
  代码     ✗ 绝不
  本地文件  △ 可以,但 .gitignore 必须挡住它
  云端配置  ✓ 正确位置

  图说:本地 secrets.toml 和云端 Secrets 设置,两处内容一样,各管各的环境。

顺带一个容易踩的路径细节:.streamlit 目录要放在你运行 streamlit run 的那个目录下; 本地开发和仓库根目录不一致时,secret 会读不到(书里有专门一段讲这个26)。

7. 核心原理(五):requirements.txt——让「我的电脑上能跑」成为可复现

开发环境(dev,你的笔记本)和生产环境(prod,Community Cloud)是两台机器。 代码一样、依赖版本不一样,照样崩:requests 2.1.1 才有的特性,prod 装的是 2.0.5,应用就炸27。

requirements.txt 的约定:一行一个库,后面钉版本。 三种钉法:

  requests                # 不钉:装最新,风险最高
  requests==2.31.0        # 钉死:最稳,但吃不到修复
  requests>=2.31.0        # 下限钉死:稳 + 能吃后续改进(书中推荐)

  图说:版本信息从哪来?pip show requests 看 dev 里装的是什么;
       或者 pip freeze > requirements.txt 全量导出再删掉用不到的行。

Community Cloud 见到 requirements.txt 会自动装;即便这个例子里 streamlit 和 requests 本来就预装,书里也要求写上、把版本钉住——「能跑」和「可复现地跑」是两回事28。

8. 作者的判断与证据

判断:部署路线的选择是个「需求 → 预算 → 精力」的三元权衡,没有银弹。 四种路线并排陈列,各给适用场景29,证据是特性对比(成本、维护责任、可达范围),客观成分高。

判断:免费路线(Community Cloud)适合学习和小应用,资源上限后再搬家。 书里明写超过限制就考虑付费云(第 12 章兑现)30——这条「先用免费、撞墙再迁」的 策略建议对初学者是对的,但它低估了迁移成本:第 12 章你会看到,搬家涉及 Docker、AWS、 域名、HTTPS,不是点几下的事。我们的补充:从一开始就把 secrets 和依赖管理做好(本章教的), 是降低未来迁移成本的唯一手段。

9. 边界与局限

  • 所有点击流截图(PAT 创建路径、Community Cloud 界面、API Ninjas 注册)都是时点快照, 界面几乎必然改版;概念(令牌、范围、过期)不变,位置会变。
  • PAT 嵌在 remote URL 里是书里为教学选的捷径——它把令牌明文写进了 .git/config。 更稳的做法是 SSH key 或凭据管理器;书里没提,我们标注为缺口。
  • 每月 1 万次免费额度是 API Ninjas 当时的政策,会随时间变;接任何 API 前先读它的最新定价页。
  • 「session_state 缓存」只在会话内有效:换个浏览器标签、刷新,笑话重新拉。 要跨会话缓存,得用第 06 章的 st.cache_data 或更重的方案。
  • 休眠策略(约 12 小时)是 Community Cloud 的政策细节,不是 Streamlit 本身的属性; 自建部署没有这个问题,但你要自己解决「一直开着」。

判断(我们的,不是书里的): 这一章的价值密度在第 4–6 节,而不在部署教程。 部署点击流三年就会过时,但三个模式不会:密钥三层防线、依赖钉版本、 「贵调用套 session_state 缓存」。这三个模式合起来,是「把玩具变成服务」的最小安全集, 任何框架都用得上。对系统研究,这一章还是观察「平台免费策略如何塑造开发者行为」 的好样本:免费额度、休眠、GitHub 绑定,每一条都在悄悄决定你会怎么写代码。 如果错,会错在: 「最小安全集」的边界——对多人团队、合规环境(金融/医疗), 三层防线远远不够(要审计、轮换、最小权限),本章的简化在这些场景里是危险的; 它是个人开发者的安全起点,不是终点。

10. 可带走的

  1. 部署 = 让一台常开的服务器替你跑应用,用户走网址访问。
  2. 四路线:局域网(免费、限网段)< 专用服务器(全控、全责)< 云厂商(弹性、按量计费)< Community Cloud(免费、有限额会休眠)。
  3. Community Cloud 三步:GitHub 仓库 → git push(用 PAT 认证)→ 云端填仓库/分支/入口文件。
  4. 改线上应用 = git commit + git push,云端自动跟进。
  5. API = 基底地址 + 端点;GET 把参数塞 URL,POST 把数据放正文;key 常走 header。
  6. requests 三板斧:requests.get(url, headers=...) → 看 status_code == requests.codes.ok → response.json()。
  7. JSON 解析完就是普通 Python 对象;列表套字典就一层层取。
  8. 外部调用必须有兜底——失败时给默认内容,不白屏。
  9. 密钥三层防线:不进代码、本地 secrets.toml + .gitignore、云端 Secrets 配置。
  10. 凡是「贵/慢/不变」的调用,套 if "x" not in st.session_state 的壳;笑话是这么缓存的,数据库查询也能。
  11. requirements.txt 用 >= 钉下限;版本号用 pip show 查。
  12. 「我的电脑能跑」≠「能复现地跑」;部署失败先查依赖版本。

11. 原文地图

主题原书章原文位置
部署的定义5 Sharing your apps with the worldtext/14-ch05-5-sharing-your-apps-with-the-world.txt:50搜「starting and keeping」)
局域网共享(Network URL)5 Sharing your apps with the worldtext/14-ch05-5-sharing-your-apps-with-the-world.txt:58搜「local network」)
专用服务器与云厂商对比5 Sharing your apps with the worldtext/14-ch05-5-sharing-your-apps-with-the-world.txt:106搜「dedicated server」)
Community Cloud 的定位与限制5 Sharing your apps with the worldtext/14-ch05-5-sharing-your-apps-with-the-world.txt:146搜「completely free」)
GitHub 与 git 的区别5 Sharing your apps with the worldtext/14-ch05-5-sharing-your-apps-with-the-world.txt:177搜「confuse GitHub with Git」)
PAT 创建与保管5 Sharing your apps with the worldtext/14-ch05-5-sharing-your-apps-with-the-world.txt:191搜「personal access tokens」)
三步部署5 Sharing your apps with the worldtext/14-ch05-5-sharing-your-apps-with-the-world.txt:229搜「three-step process」)
git init / add / commit / push 序列5 Sharing your apps with the worldtext/14-ch05-5-sharing-your-apps-with-the-world.txt:262搜「git init」)
master 与 main 分支名注意5 Sharing your apps with the worldtext/14-ch05-5-sharing-your-apps-with-the-world.txt:324搜「Some versions of Git」)
约 12 小时休眠5 Sharing your apps with the worldtext/14-ch05-5-sharing-your-apps-with-the-world.txt:366搜「waking up」)
API/端点/GET 参数5 Sharing your apps with the worldtext/14-ch05-5-sharing-your-apps-with-the-world.txt:416搜「base address」)
HTTP 侧栏(GET/POST/头/正文)5 Sharing your apps with the worldtext/14-ch05-5-sharing-your-apps-with-the-world.txt:442搜「Hypertext Transfer Protocol」)
Dad Jokes API 与 X-Api-Key5 Sharing your apps with the worldtext/14-ch05-5-sharing-your-apps-with-the-world.txt:467搜「Dad Jokes API」)
requests 交互式演示5 Sharing your apps with the worldtext/14-ch05-5-sharing-your-apps-with-the-world.txt:505搜「go-to library」)
jokes.py 与状态码检查、兜底笑话5 Sharing your apps with the worldtext/14-ch05-5-sharing-your-apps-with-the-world.txt:551搜「jokes.py」)
「每点一次都调 API」的账单问题5 Sharing your apps with the worldtext/14-ch05-5-sharing-your-apps-with-the-world.txt:639搜「joke of the」)
session_state 缓存笑话5 Sharing your apps with the worldtext/14-ch05-5-sharing-your-apps-with-the-world.txt:646搜「save the joke」)
绝不把密钥存代码的铁律5 Sharing your apps with the worldtext/14-ch05-5-sharing-your-apps-with-the-world.txt:662搜「security flaw」)
secrets.toml 与 TOML 格式5 Sharing your apps with the worldtext/14-ch05-5-sharing-your-apps-with-the-world.txt:687搜「secrets.toml」)
.gitignore 与 git rm --cached5 Sharing your apps with the worldtext/14-ch05-5-sharing-your-apps-with-the-world.txt:875搜「.gitignore file」)
云端配置 Secrets5 Sharing your apps with the worldtext/14-ch05-5-sharing-your-apps-with-the-world.txt:917搜「App Settings」)
requirements.txt 三种钉法5 Sharing your apps with the worldtext/14-ch05-5-sharing-your-apps-with-the-world.txt:797搜「requirements.txt」)

Footnotes

  1. 出处:「5 Sharing your apps with the world」第 50–53 段(text/14-ch05-5-sharing-your-apps-with-the-world.txt:50,搜「starting and keeping」)。原文:部署应用 = 启动并维持一个 Streamlit 服务器,让用户通过别的 URL 访问。 ↩

  2. 出处:「5 Sharing your apps with the world」第 58–105 段(text/14-ch05-5-sharing-your-apps-with-the-world.txt:58,搜「Running a server over your local network」)。限制(电脑得开着、限网内)见第 98–101 段。 ↩

  3. 出处:「5 Sharing your apps with the world」第 106–123 段(text/14-ch05-5-sharing-your-apps-with-the-world.txt:106,搜「dedicated server」)。 ↩

  4. 出处:「5 Sharing your apps with the world」第 124–143 段(text/14-ch05-5-sharing-your-apps-with-the-world.txt:126,搜「cloud-based platform」)。原文提醒随应用走红费用会上升。 ↩

  5. 出处:「5 Sharing your apps with the world」第 144–156 段(text/14-ch05-5-sharing-your-apps-with-the-world.txt:144,搜「Community Cloud」)与第 364–368 段(休眠)。 ↩

  6. 出处:「5 Sharing your apps with the world」第 190–217 段(text/14-ch05-5-sharing-your-apps-with-the-world.txt:216,搜「you'll never be able to see it again」)。 ↩

  7. 出处:「5 Sharing your apps with the world」第 176–180 段(text/14-ch05-5-sharing-your-apps-with-the-world.txt:177,搜「Don't confuse GitHub with Git」)。 ↩

  8. 出处:「5 Sharing your apps with the world」第 338–362 段(text/14-ch05-5-sharing-your-apps-with-the-world.txt:343,搜「Deploy an App」)。 ↩

  9. 出处:「5 Sharing your apps with the world」第 364–368 段(text/14-ch05-5-sharing-your-apps-with-the-world.txt:366,搜「waking up」)。 ↩

  10. 出处:「5 Sharing your apps with the world」第 783–786 与 901–909 段(text/14-ch05-5-sharing-your-apps-with-the-world.txt:786,搜「picks them up automatically」)。 ↩

  11. 出处:「5 Sharing your apps with the world」第 403–426 段(text/14-ch05-5-sharing-your-apps-with-the-world.txt:404,搜「fancy term」)。 ↩

  12. 出处:「5 Sharing your apps with the world」第 467–500 段(text/14-ch05-5-sharing-your-apps-with-the-world.txt:467,搜「API Ninjas」)。免费额度与 X-Api-Key 见第 471–499 段。 ↩

  13. 出处:「5 Sharing your apps with the world」第 441–465 段(text/14-ch05-5-sharing-your-apps-with-the-world.txt:443,搜「grammatical structure」)。 ↩

  14. 出处:「5 Sharing your apps with the world」第 519–540 段(text/14-ch05-5-sharing-your-apps-with-the-world.txt:524,搜「Missing API Key」)。 ↩

  15. 出处:「5 Sharing your apps with the world」第 577–612 段(text/14-ch05-5-sharing-your-apps-with-the-world.txt:579,搜「status code」)。 ↩

  16. 出处:「5 Sharing your apps with the world」第 568–569 与 613–617 段(text/14-ch05-5-sharing-your-apps-with-the-world.txt:616,搜「evergreen dad joke」)。 ↩

  17. 出处:「5 Sharing your apps with the world」第 636–644 段(text/14-ch05-5-sharing-your-apps-with-the-world.txt:639,搜「joke. Forget joke of the」)。 ↩

  18. 出处:「5 Sharing your apps with the world」第 645–658 段(text/14-ch05-5-sharing-your-apps-with-the-world.txt:646,搜「save the joke」)。 ↩

  19. 出处:「5 Sharing your apps with the world」第 661–667 段(text/14-ch05-5-sharing-your-apps-with-the-world.txt:666,搜「costing hundreds or even thousands」)。 ↩

  20. 出处:「5 Sharing your apps with the world」第 669–671 段(text/14-ch05-5-sharing-your-apps-with-the-world.txt:669,搜「never store any secret」)。 ↩

  21. 出处:「5 Sharing your apps with the world」第 687–704 段(text/14-ch05-5-sharing-your-apps-with-the-world.txt:697,搜「Tom's Obvious Minimal Language」)。 ↩

  22. 出处:「5 Sharing your apps with the world」第 679–686 与 706–715 段(text/14-ch05-5-sharing-your-apps-with-the-world.txt:680,搜「dictionary-like structure」)。 ↩

  23. 出处:「5 Sharing your apps with the world」第 869–884 段(text/14-ch05-5-sharing-your-apps-with-the-world.txt:876,搜「keep out of commits」)。 ↩

  24. 出处:「5 Sharing your apps with the world」第 886–888 段(text/14-ch05-5-sharing-your-apps-with-the-world.txt:888,搜「rm -r --cached」)。 ↩

  25. 出处:「5 Sharing your apps with the world」第 914–932 段(text/14-ch05-5-sharing-your-apps-with-the-world.txt:929,搜「copy the contents of secrets.toml」)。 ↩

  26. 出处:「5 Sharing your apps with the world」第 717–726 段(text/14-ch05-5-sharing-your-apps-with-the-world.txt:718,搜「from which you run」)。 ↩

  27. 出处:「5 Sharing your apps with the world」第 797–812 段(text/14-ch05-5-sharing-your-apps-with-the-world.txt:797,搜「requirements.txt」)。requests 2.1.1/2.0.5 的例子见第 808–812 段。 ↩

  28. 出处:「5 Sharing your apps with the world」第 860–867 段(text/14-ch05-5-sharing-your-apps-with-the-world.txt:861,搜「preinstalls」)。 ↩

  29. 出处:「5 Sharing your apps with the world」第 55–156 段(text/14-ch05-5-sharing-your-apps-with-the-world.txt:55,搜「Options for deployment」)。 ↩

  30. 出处:「5 Sharing your apps with the world」第 151–154 段(text/14-ch05-5-sharing-your-apps-with-the-world.txt:151,搜「resource limitations」)。 ↩

On this page