From fd62570431cf0cd3660c0e01ef57a4321122c0db Mon Sep 17 00:00:00 2001 From: ryan Date: Thu, 6 Aug 2026 13:42:41 +0800 Subject: [PATCH] docs(design): add origin error page design MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 全局可配置源站错误页:默认 500-599、Cloudflare 风格模板、 状态码透传与在线 HTML;配置进 Option 与配置版本快照。 --- docs/config.ts | 1 + docs/design/architecture.md | 6 +- docs/design/index.md | 1 + docs/design/origin-error-page.md | 239 ++++++++++++++++++ .../2026-08-06-origin-error-page-design.md | 22 ++ 5 files changed, 267 insertions(+), 2 deletions(-) create mode 100644 docs/design/origin-error-page.md create mode 100644 docs/superpowers/specs/2026-08-06-origin-error-page-design.md diff --git a/docs/config.ts b/docs/config.ts index aa661b0f..eda32533 100644 --- a/docs/config.ts +++ b/docs/config.ts @@ -134,6 +134,7 @@ function sidebarDesign(): DefaultTheme.SidebarItem[] { { text: 'WAF 可编排规则设计', link: 'waf-orchestration-design' }, { text: 'Pages 静态托管设计', link: 'pages-design' }, { text: '边缘缓存策略设计', link: 'edge-cache-design' }, + { text: '源站错误页设计', link: 'origin-error-page' }, { text: '边缘可观测与业务流量统计', link: 'observability-design' }, { text: '观测数据传输模型', link: 'observability-transport-model' }, { text: '观测上报协议与表结构', link: 'observability-data-model' }, diff --git a/docs/design/architecture.md b/docs/design/architecture.md index 530aa8fe..0ffc9021 100644 --- a/docs/design/architecture.md +++ b/docs/design/architecture.md @@ -16,13 +16,15 @@ Browser | | HTTPS/HTTP request v -OpenResty (WAF, TLS, Rate Limit) +OpenResty (WAF, TLS, Rate Limit, 可选源站错误页) | | reverse proxy (proxy_pass) v Origin Server (直连公网/局域网上游) ``` +源站或网关返回配置列表内错误状态码时,可返回全局自定义/默认 HTML,且保持真实 HTTP 状态码;详见 [源站错误页设计](./origin-error-page.md)。 + ### 2. 内网穿透流量路径 适用于内网受限服务器上的源站服务接入: ```text @@ -67,7 +69,7 @@ OpenResty (Agent, TLS/WAF) | --------------- | ---------------------------------------------------------------------- | ------------ | | **Server** | 管理端 UI/API、控制面状态持久化、配置编译渲染、发布版本控制、Pages 部署包存储、Cloudflare A 记录指向、访问日志入库与业务流量聚合、Uptime Kuma 监控同步与登录验证码防护 | [Agent 与发布模型](./agent-design.md) / [Cloudflare DNS 指向设计](./cloudflare-pointing.md) / [边缘可观测与业务流量统计](./observability-design.md) / [Uptime Kuma 监控同步设计](./kuma-design.md) / [登录验证码设计](./login-captcha.md) | | **Agent** | 周期心跳与 WS 同步、静态资源包拉取与解压、OpenResty 配置写入/校验/重载与自愈;观测仅上报访问明细与主机/健康读数,不做业务预聚合 | [Agent 与发布模型](./agent-design.md) / [边缘可观测与业务流量统计](./observability-design.md) | -| **OpenResty** | 接收真实流量,执行 WAF 过滤、PoW 防护、Basic Auth 认证与静态/反代服务 | [WAF 设计](./waf-design.md) / [Pages 设计](./pages-design.md) | +| **OpenResty** | 接收真实流量,执行 WAF 过滤、PoW 防护、Basic Auth 认证、静态/反代服务与可选源站错误页 | [WAF 设计](./waf-design.md) / [Pages 设计](./pages-design.md) / [源站错误页设计](./origin-error-page.md) | | **Relay** | 部署于边缘节点,管理 `frps` 守护进程生命周期,接受心跳派发的穿透中继配置 | [内网穿透设计](./tunnel-design.md) | | **OpenFlared** | 部署于内网,管理 `frpc` 进程组,向多个 Relay 建立反向隧道,上报连接状态 | [内网穿透设计](./tunnel-design.md) | diff --git a/docs/design/index.md b/docs/design/index.md index 3748c65b..4512d3f6 100644 --- a/docs/design/index.md +++ b/docs/design/index.md @@ -22,6 +22,7 @@ OpenFlare 适合需要统一管理多台 OpenResty 代理节点的团队,具 | 能力 | 说明 | 详细设计/使用指南 | | --- | --- | --- | | **反代配置管理** | 以网站规则(Proxy Route)为聚合边界,支持多域名与多上游负载均衡 | [新建反代配置](../guide/proxy-config.md) | +| **源站错误页** | 全局可配置:源站/网关匹配状态码时返回 Cloudflare 风格或自定义 HTML,HTTP 状态码保持原值 | [源站错误页设计](./origin-error-page.md) | | **边缘缓存** | 单节点 OpenResty `proxy_cache`;默认 static 扩展名 + 源站头/Set-Cookie 闸门 + 默认 Edge TTL(对标 CF 默认模型) | [边缘缓存策略设计](./edge-cache-design.md) | | **Zone 与域名管理** | 以可注册根域为管理入口,聚合明确域名、域名证书与反代路由 | [Zone 与域名资源设计](./zone-design.md) | | **Cloudflare DNS 指向** | 以 ZoneDomain 为粒度,将单条 Cloudflare A 记录幂等指向边缘节点 IPv4;支持连接配置、分组、成员橙云与异步同步,一期不含自动故障切换 | [Cloudflare DNS 指向设计](./cloudflare-pointing.md) | diff --git a/docs/design/origin-error-page.md b/docs/design/origin-error-page.md new file mode 100644 index 00000000..dc083be1 --- /dev/null +++ b/docs/design/origin-error-page.md @@ -0,0 +1,239 @@ +# 源站错误页设计 + +你会学到:源站或网关返回指定错误状态码时,OpenFlare 如何用全局可配置页面替代透传响应;配置如何进入不可变配置版本,以及边缘 OpenResty 如何保持真实 HTTP 状态码并在页面中展示该状态码。 + +本设计是 [系统架构](./architecture.md) 中反代流量路径的产品化补充;配置发布模型见 [Agent 与发布模型](./agent-design.md)。 + +--- + +## 1. 目标与非目标 + +### 1.1 目标 + +* **可拦截**:在用户配置的状态码集合上,用统一 HTML 替换原先透传的源站/Nginx 默认错误响应。 +* **可关闭**:全局开关关闭后行为与现状一致(透传 / Nginx 默认页)。 +* **默认可视**:默认启用,默认状态码标签 `500-599`,默认 Cloudflare 风格错误页(无 CF 商标)。 +* **可自定义**:管理员可在线编辑完整 HTML;空 HTML 表示使用内置默认模板。 +* **状态码透传**:HTTP 响应 `status` 保持原错误码(如 502、522);页面正文通过 `{{status}}` 展示同一数值。 +* **全局统一**:侧栏「网站管理 → 错误页」单一配置,全站反代路由共用。 +* **与发布一致**:配置经 Option 持久化,进入配置版本快照后随发布/回滚下发。 + +### 1.2 非目标 + +* 按反代路由 / Zone 覆盖错误页 +* 通过上传文件托管错误页(仅在线 HTML) +* 修改 WAF / PoW / 限流自有响应页(除非用户把对应状态码加入列表) +* Pages 静态路由错误页 +* 多语言错误页、品牌资源 CDN + +--- + +## 2. 产品行为 + +### 2.1 何时替换 + +| 条件 | 行为 | +| --- | --- | +| 开关开启,且响应状态码落在展开后的集合内 | 返回自定义/默认 HTML,**status 不变** | +| 开关关闭 | 不生成 `error_page` 相关指令,透传 | +| 状态码不在集合内 | 不替换 | +| Pages 上游路由 | 不应用本功能 | +| 源站成功返回 2xx/3xx/4xx(未配置时) | 不替换 | + +实现上对反代 `location` 启用 `proxy_intercept_errors on`,因此**源站返回的**匹配 5xx 等也会被拦截,而不仅是网关本地生成的 502。 + +### 2.2 状态码标签语法 + +Tags Input 每条标签: + +| 形式 | 示例 | 含义 | +| --- | --- | --- | +| 单码 | `522` | 仅该码 | +| 闭区间 | `500-599` | 含端点展开 | + +* 合法范围:单码与区间两端均在 **400–599**;`lo ≤ hi`。 +* 默认标签列表:`["500-599"]`。 +* 持久化存**原始标签**(JSON 数组字符串);渲染时展开、去重、排序。 +* 启用时展开结果为空 → 保存拒绝。 +* 非法标签 → 保存拒绝并返回可读错误。 + +### 2.3 页面占位符 + +| 占位符 | 含义 | +| --- | --- | +| `{{status}}` | 当前响应状态码(与 HTTP status 一致) | +| `{{host}}` | 请求 Host | + +自定义 HTML 与默认模板均支持上述占位符;运行时在边缘替换。未使用的占位符可不出现在模板中。 + +### 2.4 默认页 + +内置 Cloudflare 风格:浅色居中、大号状态码、简短英文/中性说明、小字 Host。不使用 Cloudflare 商标或 Ray ID 伪造。前后端共用同一默认 HTML 常量(或同源字符串),前端「加载默认模板」直接填入编辑器。 + +--- + +## 3. 配置模型 + +### 3.1 Option keys(`w_system_configs` / OpenFlare Option API) + +| Key | 类型 | 默认 | 说明 | +| --- | --- | --- | --- | +| `origin_error_page_enabled` | bool 字符串 | `true` | 总开关 | +| `origin_error_page_status_codes` | JSON 字符串数组 | `["500-599"]` | 原始标签 | +| `origin_error_page_html` | 文本 | `""` | 空 = 内置默认;最大 **256 KiB** | + +API 复用: + +* `GET /api/v1/d/option` +* `POST /api/v1/d/option/update-batch` + +不新增独立资源路由。goose 迁移写入 seed;常量定义于 `internal/model` 配置 key 区。 + +### 3.2 校验(update-batch) + +1. `enabled`:可解析为 bool。 +2. `status_codes`:合法 JSON 数组;每项 `^\d{3}$` 或 `^\d{3}-\d{3}$`;展开后均在 400–599;启用时非空。 +3. `html`:长度 ≤ 256 KiB(按字节);允许空。 +4. 解析/展开逻辑为**纯函数**,供 API 与 `pkg/render/openresty` 共用,避免前后端/渲染语义分叉。 + +不对 HTML 做 XSS 消毒:属管理员全局运维配置,与边缘公开展示一致;文档提示勿嵌入不可信第三方脚本。 + +### 3.3 配置版本快照 + +`ConfigSnapshot` 增加字段: + +```text +OriginErrorPageEnabled bool +OriginErrorPageStatusCodes []string // 原始标签 +OriginErrorPageHTML string // 空则渲染器用内置默认 +``` + +构建快照时从 Option 读取;Agent 只消费快照,不直读控制面 DB。 + +--- + +## 4. 边缘渲染 + +### 4.1 启用时生成内容 + +1. **SupportFile**:错误页模板(如 `error_pages/origin_error.html.tmpl`),内容为自定义 HTML 或内置默认,保留 `{{status}}` / `{{host}}`。 +2. **每个反代 proxy server**(含 HTTP/HTTPS 反代;不含 Pages): + +```nginx +proxy_intercept_errors on; +error_page = /__openflare_origin_error; + +location = /__openflare_origin_error { + internal; + default_type text/html; + charset utf-8; + # 保持 ngx.status 为原错误码 + # 读取模板,替换 {{status}} / {{host}} 后输出 body +} +``` + +### 4.2 运行时替换 + +采用 **internal location 内轻量 Lua(或现有 resty 能力)** 读模板并 `string.gsub` 替换占位符,**不**把 status 固化进静态文件(请求间状态码不同)。 + +禁止将错误页统一改为 HTTP 200。 + +### 4.3 关闭时 + +不输出 `proxy_intercept_errors`、`error_page`、内部 location 与对应 SupportFile(或文件可写但不被引用)。 + +### 4.4 与缓存 / stale + +若全局 `proxy_cache_use_stale` 在部分错误码上返回过期缓存,**成功返回 stale 内容时不会进入 error_page**。仅当实际上对客户端产生配置列表内错误状态时才展示错误页。行为依赖现有缓存指令,本功能不改 stale 策略。 + +--- + +## 5. 前端 + +### 5.1 入口 + +* 侧栏「网站管理」新增:**错误页** → `/error-pages` +* 更新 `openflareWebsiteNavGroup`、`openflareWebsiteSubNav`(若使用)、全局搜索关键词 + +### 5.2 页面结构 + +* 页头说明:保存后需到「版本发布」发布才生效。 +* **开关 + Tags Input**(shadcn-extension Tags Input:`@/components/ui/tags-input`):状态码标签。 +* **HTML 编辑区** +「加载默认模板」「恢复默认(清空)」+ 占位符说明。 +* **客户端预览**:用示例 `status=502`、`host=example.com` 替换后 sandbox/iframe 预览。 +* 保存:`OptionService.updateBatch`;权限与性能调优页一致(管理员)。 + +### 5.3 组件依赖 + +若仓库尚无 Tags Input,按项目 shadcn 流程添加;样式与现有 UI 一致。 + +--- + +## 6. 数据流 + +```text +管理员 /error-pages + → Option update-batch(校验标签与 HTML) + → w_system_configs + +发布配置版本 + → 快照写入 OriginErrorPage* + → 渲染 OpenResty conf + SupportFile + → Agent 拉取并 reload + +访客请求反代域名 + → 源站/网关产生匹配状态码 + → error_page → internal location + → 替换占位符,status 保持原码,返回 HTML +``` + +--- + +## 7. 测试与验收 + +### 7.1 自动化 + +* 状态码解析:单码、区间、去重、越界、反序、默认 `500-599` +* 渲染:enabled/disabled conf 片段;空 HTML 用默认;自定义进 SupportFile +* Option 校验:非法标签 / 超大 HTML → 4xx + +### 7.2 手动 + +1. 默认配置:源站不可达 → CF 风格页,真实 502/504,页内数字一致 +2. 源站返回 503 → 替换页,status 503 +3. 仅标签 `522` → 仅 522 替换 +4. 关闭开关并发布 → 透传恢复 +5. 自定义 HTML 占位符预览与线上一致 +6. Pages 路由不受影响 + +### 7.3 文档 + +* 本设计文档;`docs/design/index.md` 能力表;`docs/config.ts` 侧栏 +* changelog `[Unreleased]` 用户可读改进条 + +--- + +## 8. 实现要点清单(供计划拆分) + +1. goose seed 三个 Option key + model 常量 +2. 状态码解析/校验纯函数 + 单测 +3. Option update 路径挂接校验 +4. 快照填充 `ConfigSnapshot` 新字段 +5. `pkg/render/openresty`:error_page 块、SupportFile、默认 HTML、单测 +6. Agent 侧若需 Lua 辅助文件,随现有 nginx lua 目录同步 +7. 前端 Tags Input + `/error-pages` 页 + 导航 +8. changelog 与设计索引 + +--- + +## 9. 决策记录 + +| 决策 | 选择 | 原因 | +| --- | --- | --- | +| 配置范围 | 全局 | 产品要求;实现与运维简单 | +| 存储 | Option + 配置版本 | 与性能调优一致,可回滚 | +| 状态码输入 | 标签:单码与区间 | 默认整段 5xx,又可点名 522 | +| 响应 status | 保持原码 | 监控/SEO/客户端语义正确 | +| 运行时替换 | internal + 轻量模板替换 | 每请求 status 不同 | +| 自定义方式 | 在线 HTML | 灵活且无需文件上传链路 | +`} \ No newline at end of file diff --git a/docs/superpowers/specs/2026-08-06-origin-error-page-design.md b/docs/superpowers/specs/2026-08-06-origin-error-page-design.md new file mode 100644 index 00000000..5ad330e0 --- /dev/null +++ b/docs/superpowers/specs/2026-08-06-origin-error-page-design.md @@ -0,0 +1,22 @@ +# 源站错误页设计(Spec) + +> 权威正文与产品文档索引见:[docs/design/origin-error-page.md](../../design/origin-error-page.md) +> 本文为 brainstorming 流程落库副本,内容与上者保持一致。 + +--- + +## 摘要 + +源站/网关在用户配置的状态码(默认标签 `500-599`,支持 `522` 与 `500-599` 区间)上,返回全局可配置 HTML 错误页,替代当前透传行为。默认 Cloudflare 风格页;可在线自定义 HTML。HTTP **status 保持原错误码**,正文通过 `{{status}}` / `{{host}}` 展示。配置挂在 OpenFlare Option,随配置版本发布;侧栏「网站管理 → 错误页」。可关闭以恢复透传。 + +## 方案 + +**方案 A(已采纳)**:全局 Option → 配置快照 `ConfigSnapshot` → OpenResty 渲染 `proxy_intercept_errors` + `error_page` + internal location 模板替换。 + +非目标:按路由覆盖、上传文件、Pages 路由、改 WAF 自有页。 + +## 详细章节 + +完整章节(目标、状态码语法、配置模型、边缘渲染、前端、测试、实现清单、决策记录)见: + +**[docs/design/origin-error-page.md](../../design/origin-error-page.md)**