KEEL · 龙骨 · A CURRICULUM FOR THE AI ERA

07 · 全站切换:消灭所有 http 残留 — keel 龙骨

本章目标:站点里不再有任何一处硬编码的 http 链接。 验收标准:抓下来的 HTML/JSON 里 http:// 出现次数为 0,浏览器控制台无混合内容告警。

本章目标:站点里不再有任何一处硬编码的 http 链接。
验收标准:抓下来的 HTML/JSON 里 http:// 出现次数为 0,浏览器控制台无混合内容告警。

为什么单独一章:HTTPS 配好之后页面"能打开",但如果页面内部还写着 http://,会产生三类问题——混合内容被浏览器拦截(资源加载失败)、canonical/og:url 指向 http 版本(SEO 权重分裂)、重定向链多一跳(性能与抓取预算浪费)。这些问题不会让站点挂掉,只会让它一直不对劲。


一、残留藏在哪:六类常见位置

一次真实的迁移里,我们改了 11 处。它们分布在:

类型 例子 后果
构建脚本默认值 PUBLIC_SITE_URL=http://www.xxxxxx.cn 生成的 canonical/sitemap 全是 http
前端常量 api/paths.ts 里的 base URL 请求发到 http,被 301 后被浏览器拦(POST 更糟)
index.html 模板 og:image / canonical 写死 分享卡片与收录指向 http
SSR 预渲染脚本 生成 route shell 时拼的绝对 URL 爬虫抓到的正文里全是 http 链接
内容生成脚本 文章内链、sitemap.xml 站点地图提交后被判为无效
后端配置/邮件模板 重置密码链接 功能直接不可用

规律:凡是"生成绝对 URL"的地方,都有一个来源常量。找出这个常量统一改,比逐个改字符串靠谱得多。


二、怎么把它们全部找出来

搜索(本地源码)

# 排除 lock 文件、构建产物与依赖目录
grep -rn "http://www\.xxxxxx\.cn" \
  --include="*.ts" --include="*.tsx" --include="*.mjs" --include="*.js" \
  --include="*.py" --include="*.html" --include="*.json" --include="*.sh" \
  --exclude-dir=node_modules --exclude-dir=dist --exclude-dir=.git .

记得也搜一下裸域名与 IP:

grep -rn "http://\(www\.\)\?xxxxxx\.cn\|http://203\.0\.113\.10" --exclude-dir=node_modules .

验证(线上产物)

搜源码只能保证"源头干净",真正要验的是发布出去的东西:

# 首页 HTML 里还有没有 http
curl -s https://www.xxxxxx.cn/learning/ | grep -o 'http://[^"'"'"' ]*' | sort -u

# sitemap 必须全是 https
curl -s https://www.xxxxxx.cn/learning/sitemap.xml | grep -c 'http://'

# robots.txt 里的 Sitemap 行
curl -s https://www.xxxxxx.cn/robots.txt

# 预渲染的 SSR 页面(爬虫看到的那一份)
curl -s https://www.xxxxxx.cn/learning/courses/tools/lessons/1/ | grep -o 'http://[^"'"'"' ]*' | sort -u

期望:全部为空。 只要还有一处,就说明某个生成环节没被覆盖。


三、混合内容(Mixed Content)怎么查

浏览器加载 HTTPS 页面时,页面内的 http 子资源会被拦截:

排查方法

  1. 打开页面 → F12 → Console,看 "Mixed Content" 警告,它会给出具体 URL。
  2. 批量检查:
    curl -s https://www.xxxxxx.cn/learning/ \
      | grep -oE '(src|href)="http://[^"]*"' | sort -u
    

三类解法

<!-- 1. 最推荐:改成协议相对或 https 绝对地址 -->
<img src="https://www.xxxxxx.cn/learning/assets/logo.png">

<!-- 2. 第三方资源不支持 https → 换源,或代理到自己域名下 -->
<!-- 3. 临时兜底(不推荐,只是把问题藏起来):
     upgrade-insecure-requests 会让浏览器自动把 http 请求升级为 https -->
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">

注意 upgrade-insecure-requests 的副作用:如果某个第三方资源确实只有 http,升级后会直接 404/失败,比不升级更糟。它适合作为上线过渡期的保险,而不是最终方案。


四、后端与代理:别让应用自己以为在 http 下

nginx 把请求转发给后端时,后端看到的是 http://127.0.0.1:5177。如果应用根据这个生成绝对 URL,产出的必然是 http。两个层面的修法:

① nginx 传递真实协议(第 06 章配置里已有):

proxy_set_header X-Forwarded-Proto $scheme;

② 应用侧信任这个头。以 FastAPI/uvicorn 为例:

# uvicorn 默认 proxy_headers=True 会改写 request.client,
# 如果你有自己的可信代理判定(TRUSTED_PROXY_IPS),必须关掉它:
uvicorn.run(app, host="127.0.0.1", port=5177, proxy_headers=False)

否则 uvicorn 自带的中间件会覆盖 request.client,让你基于 IP 的可信代理判断失效——表现为"同一份代码,本地对、线上不对"。

③ 站点 URL 用配置而非推导。最稳的做法是显式配置:

PUBLIC_SITE_URL=https://www.xxxxxx.cn

让所有生成绝对 URL 的地方都读这一个变量,而不是从请求头推导。推导逻辑在多层代理下极易出错,且难以测试。


五、切换后的回归检查清单

按这个顺序跑一遍,全绿才算完成:

DOMAIN=www.xxxxxx.cn

# 1. 首页与一个内页都 200
curl -s -o /dev/null -w "%{http_code}\n" https://$DOMAIN/learning/
curl -s -o /dev/null -w "%{http_code}\n" https://$DOMAIN/learning/courses/tools/lessons/1/

# 2. canonical / og:url 是 https
curl -s https://$DOMAIN/learning/ | grep -oE '<link rel="canonical"[^>]*>'
curl -s https://$DOMAIN/learning/ | grep -oE 'property="og:url"[^>]*'

# 3. sitemap 与 robots 可访问且为 https
curl -s -o /dev/null -w "%{http_code}\n" https://$DOMAIN/learning/sitemap.xml
curl -s https://$DOMAIN/robots.txt

# 4. 页面内无 http 残留
curl -s https://$DOMAIN/learning/ | grep -c 'http://' || echo "0 (干净)"

# 5. 接口请求也走 https(前端 network 面板确认,或直接打 API)
curl -s -o /dev/null -w "%{http_code}\n" https://$DOMAIN/learning/api/health

六、一个真实教训:改了源码不等于改了线上

HTTPS 全量切换时遇到过这样一个问题:本地把所有 http:// 改成 https:// 并提交,但线上抓下来仍是 http。

原因:发布脚本只发布"内容"(Markdown → JSON),不发布前端源码。前端的构建发生在服务器上,用的是服务器目录里那份旧源码。所以:

判断方法:抓一个带版本特征的东西。例如新版本的 HTML 里会出现新组件的类名、新的 JS chunk 文件名:

curl -s https://www.xxxxxx.cn/learning/ | grep -oE 'assets/[A-Za-z0-9._-]+\.js'

对比本地 dist/assets/ 里的文件名。对不上,说明线上跑的是旧构建产物——此时再怎么改本地代码也不会生效。

下一章处理最后一个环节:证书的生命周期管理与到期前该做的事。

进入 keel 阅读