fix(nginx): enforce safe web cache policy
这个提交包含在:
父节点
b42c2c686b
当前提交
6868cfcd8e
@ -138,5 +138,8 @@ server {
|
||||
sub_filter 'https://dev.xuqinmin.com' 'https://$host';
|
||||
sub_filter_once off;
|
||||
sub_filter_types text/html text/javascript application/javascript;
|
||||
proxy_hide_header Cache-Control;
|
||||
proxy_hide_header Expires;
|
||||
add_header Cache-Control $xuqm_web_cache_control always;
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,6 +7,13 @@ http {
|
||||
proxy_read_timeout 300s;
|
||||
proxy_send_timeout 300s;
|
||||
|
||||
# 入口 HTML/manifest 必须每次再验证;只有版本目录和历史迁移期的哈希 assets
|
||||
# 可以永久缓存。外层 nginx 统一覆盖上游头,避免私有部署代理层重新引入旧入口。
|
||||
map $uri $xuqm_web_cache_control {
|
||||
default "no-cache, no-store, must-revalidate";
|
||||
~^/(?:releases/[^/]+/assets|assets)/ "public, max-age=31536000, immutable";
|
||||
~^/docs/(?:releases/[^/]+/assets|assets)/ "public, max-age=31536000, immutable";
|
||||
}
|
||||
|
||||
include /etc/nginx/conf.d/*.conf;
|
||||
}
|
||||
|
||||
|
||||
@ -114,6 +114,11 @@ SDK_TENANT_SERVICE_URL: "http://tenant-service:9001"
|
||||
|
||||
nginx 容器属于 `base` 必启服务,直接绑定宿主机 `0.0.0.0:80`,统一处理所有内部路由。配置文件为 `config/nginx/conf.d/xuqm.conf`,使用 `resolver 127.0.0.11 valid=10s` + 变量化 `proxy_pass`,**容器重建后 nginx 无需 reload**,自动通过 Docker DNS 获取新 IP。
|
||||
|
||||
Web 代理会覆盖上游缓存头:入口 HTML、manifest 和普通路由使用
|
||||
`no-cache, no-store, must-revalidate`;`releases/<版本>/assets/` 及迁移期旧
|
||||
`assets/` 使用一年 `immutable`。tenant-web 镜像保留当前与上一版本资源,因此发布后
|
||||
旧页面仍可完成 lazy chunk 加载,不会回退到 SPA HTML 或返回 404。
|
||||
|
||||
详见 [runbook.md](runbook.md#nginx-配置)。
|
||||
|
||||
---
|
||||
|
||||
@ -170,6 +170,23 @@ bash scripts/reset.sh --no-pull
|
||||
|
||||
---
|
||||
|
||||
### 发布后页面资源 404
|
||||
|
||||
**现象**:发布后已打开的页面跳转功能时,请求旧哈希 JavaScript/CSS 返回 404。
|
||||
**原因**:旧入口 HTML 仍引用上一版本 lazy chunk,但旧镜像已被替换。
|
||||
**当前保障**:
|
||||
|
||||
- 入口 HTML 与 manifest 禁止持久缓存;
|
||||
- 哈希资源使用版本目录并声明 `immutable`;
|
||||
- 新 tenant-web 镜像会保留上一版本资源目录;
|
||||
- 哈希资源不存在时返回真实 404,禁止回退为 `index.html`。
|
||||
|
||||
若升级后仍出现问题,先确认 tenant-web 镜像与本仓库 nginx 配置来自同一发布版本,再
|
||||
执行 `bash scripts/reset.sh --no-pull` 重建容器;不要通过清空所有浏览器缓存掩盖发布
|
||||
配置错误。
|
||||
|
||||
---
|
||||
|
||||
### 服务 crash-loop / 启动失败
|
||||
|
||||
**现象**:`docker compose ps` 显示服务 `Restarting`,日志反复出现启动异常。
|
||||
|
||||
@ -113,6 +113,11 @@ http_body() {
|
||||
curl -sL --noproxy '*' --max-time "$timeout" "$url" 2>/dev/null || true
|
||||
}
|
||||
|
||||
http_headers() {
|
||||
local url="$1" timeout="${2:-8}"
|
||||
curl -sS --noproxy '*' -D - -o /dev/null --max-time "$timeout" "$url" 2>/dev/null || true
|
||||
}
|
||||
|
||||
http_body_auth() {
|
||||
local url="$1" token="$2" timeout="${3:-8}"
|
||||
curl -sL --noproxy '*' --max-time "$timeout" \
|
||||
@ -242,6 +247,28 @@ else
|
||||
fail "控制台前端不可访问 (HTTP $WEB_CODE)"
|
||||
fi
|
||||
|
||||
WEB_CACHE="$(http_headers "$BASE_URL/" | tr -d '\r' | awk 'BEGIN{IGNORECASE=1} /^Cache-Control:/{value=$0} END{print value}')"
|
||||
if printf '%s' "$WEB_CACHE" | grep -qi 'no-store'; then
|
||||
pass "入口 HTML 禁止持久缓存"
|
||||
else
|
||||
fail "入口 HTML 缺少 no-store" "${WEB_CACHE:-无 Cache-Control}"
|
||||
fi
|
||||
|
||||
WEB_HTML="$(http_body "$BASE_URL/")"
|
||||
WEB_ASSET="$(printf '%s' "$WEB_HTML" | grep -oE '/releases/[^/]+/assets/[^"[:space:]]+\.(js|css)' | head -1 || true)"
|
||||
if [ -n "$WEB_ASSET" ]; then
|
||||
ASSET_HEADERS="$(http_headers "$BASE_URL$WEB_ASSET" | tr -d '\r')"
|
||||
ASSET_CODE="$(printf '%s' "$ASSET_HEADERS" | awk '/^HTTP/{code=$2} END{print code}')"
|
||||
ASSET_CACHE="$(printf '%s' "$ASSET_HEADERS" | awk 'BEGIN{IGNORECASE=1} /^Cache-Control:/{value=$0} END{print value}')"
|
||||
if [ "$ASSET_CODE" = "200" ] && printf '%s' "$ASSET_CACHE" | grep -qi 'immutable'; then
|
||||
pass "版本化哈希资源可访问且 immutable"
|
||||
else
|
||||
fail "版本化资源缓存契约异常" "HTTP ${ASSET_CODE:-000}, ${ASSET_CACHE:-无 Cache-Control}"
|
||||
fi
|
||||
else
|
||||
fail "入口 HTML 未引用版本化 releases assets"
|
||||
fi
|
||||
|
||||
DOCS_CODE=$(http_get "$BASE_URL/docs/")
|
||||
if echo "$DOCS_CODE" | grep -qE '^(200|301|302)$'; then
|
||||
pass "文档站可访问 (HTTP $DOCS_CODE)"
|
||||
|
||||
正在加载...
在新工单中引用
屏蔽一个用户