fix(nginx): enforce safe web cache policy
这个提交包含在:
父节点
b42c2c686b
当前提交
6868cfcd8e
@ -138,5 +138,8 @@ server {
|
|||||||
sub_filter 'https://dev.xuqinmin.com' 'https://$host';
|
sub_filter 'https://dev.xuqinmin.com' 'https://$host';
|
||||||
sub_filter_once off;
|
sub_filter_once off;
|
||||||
sub_filter_types text/html text/javascript application/javascript;
|
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_read_timeout 300s;
|
||||||
proxy_send_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;
|
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。
|
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-配置)。
|
详见 [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 / 启动失败
|
### 服务 crash-loop / 启动失败
|
||||||
|
|
||||||
**现象**:`docker compose ps` 显示服务 `Restarting`,日志反复出现启动异常。
|
**现象**:`docker compose ps` 显示服务 `Restarting`,日志反复出现启动异常。
|
||||||
|
|||||||
@ -113,6 +113,11 @@ http_body() {
|
|||||||
curl -sL --noproxy '*' --max-time "$timeout" "$url" 2>/dev/null || true
|
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() {
|
http_body_auth() {
|
||||||
local url="$1" token="$2" timeout="${3:-8}"
|
local url="$1" token="$2" timeout="${3:-8}"
|
||||||
curl -sL --noproxy '*' --max-time "$timeout" \
|
curl -sL --noproxy '*' --max-time "$timeout" \
|
||||||
@ -242,6 +247,28 @@ else
|
|||||||
fail "控制台前端不可访问 (HTTP $WEB_CODE)"
|
fail "控制台前端不可访问 (HTTP $WEB_CODE)"
|
||||||
fi
|
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/")
|
DOCS_CODE=$(http_get "$BASE_URL/docs/")
|
||||||
if echo "$DOCS_CODE" | grep -qE '^(200|301|302)$'; then
|
if echo "$DOCS_CODE" | grep -qE '^(200|301|302)$'; then
|
||||||
pass "文档站可访问 (HTTP $DOCS_CODE)"
|
pass "文档站可访问 (HTTP $DOCS_CODE)"
|
||||||
|
|||||||
正在加载...
在新工单中引用
屏蔽一个用户