mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-08 00:26:37 +08:00
feat(openresty): add origin error page GET-only option
Allow restricting custom origin error HTML to GET requests so other methods pass through origin responses. Adds option seed, snapshot field, edge limit_except/Lua handling, and admin UI switch.
This commit is contained in:
@@ -35,6 +35,7 @@ sidebar: false
|
|||||||
### 新增
|
### 新增
|
||||||
|
|
||||||
- 新增全局源站错误页:可在「网站管理 → 错误页」配置开关、触发状态码(支持 `500-599` 区间与单码)与自定义 HTML;默认启用 OpenFlare 极简错误页并保持真实 HTTP 状态码,修改后随配置版本发布下发到边缘,关闭后恢复透传。
|
- 新增全局源站错误页:可在「网站管理 → 错误页」配置开关、触发状态码(支持 `500-599` 区间与单码)与自定义 HTML;默认启用 OpenFlare 极简错误页并保持真实 HTTP 状态码,修改后随配置版本发布下发到边缘,关闭后恢复透传。
|
||||||
|
- 源站错误页支持「仅针对 GET 请求」:开启后仅对 GET 的匹配错误状态码返回自定义错误页,其它 HTTP 方法透传源站响应。
|
||||||
- 新增 Cloudflare DNS 指向管理:可复用现有 Cloudflare DNS 账号或配置独立 Token,按分组将 ZoneDomain 的单条 A 记录异步同步到边缘节点 IPv4,并支持成员橙云、同步状态与节点 IP 变更联动。
|
- 新增 Cloudflare DNS 指向管理:可复用现有 Cloudflare DNS 账号或配置独立 Token,按分组将 ZoneDomain 的单条 A 记录异步同步到边缘节点 IPv4,并支持成员橙云、同步状态与节点 IP 变更联动。
|
||||||
|
|
||||||
### 修复
|
### 修复
|
||||||
|
|||||||
@@ -266,6 +266,7 @@ Server 的所有核心基础配置定义在 `config.yaml` 中,且均支持环
|
|||||||
| 配置键 (Key) | 数据类型 | 作用说明 | 默认值 |
|
| 配置键 (Key) | 数据类型 | 作用说明 | 默认值 |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| `origin_error_page_enabled` | `bool` | 是否启用全局源站错误页。开启后,源站或网关返回的匹配状态码由自定义/默认 HTML 替换,**HTTP 状态码保持原值**;关闭后不生成相关指令,恢复透传。修改后需发布配置版本生效 | `true` |
|
| `origin_error_page_enabled` | `bool` | 是否启用全局源站错误页。开启后,源站或网关返回的匹配状态码由自定义/默认 HTML 替换,**HTTP 状态码保持原值**;关闭后不生成相关指令,恢复透传。修改后需发布配置版本生效 | `true` |
|
||||||
|
| `origin_error_page_get_only` | `bool` | 是否仅对 **GET** 请求生效。开启后仅 GET 的匹配错误状态码返回自定义错误页;POST/PUT 等其它方法透传源站响应 | `false` |
|
||||||
| `origin_error_page_status_codes` | `json` | 触发错误页的状态码标签 JSON 数组。支持单码(如 `522`)与闭区间(如 `500-599`);单码与区间两端均须在 **400–599**,且 `lo ≤ hi`。启用时展开结果不能为空 | `["500-599"]` |
|
| `origin_error_page_status_codes` | `json` | 触发错误页的状态码标签 JSON 数组。支持单码(如 `522`)与闭区间(如 `500-599`);单码与区间两端均须在 **400–599**,且 `lo ≤ hi`。启用时展开结果不能为空 | `["500-599"]` |
|
||||||
| `origin_error_page_html` | `string` | 错误页自定义 HTML。空字符串表示使用内置 OpenFlare 默认模板(极简白底);支持占位符 `{{status}}`(与 HTTP 状态码一致)、`{{host}}`(请求 Host)。最大 **256 KiB**(按字节)。勿嵌入不可信第三方脚本 | 空 |
|
| `origin_error_page_html` | `string` | 错误页自定义 HTML。空字符串表示使用内置 OpenFlare 默认模板(极简白底);支持占位符 `{{status}}`(与 HTTP 状态码一致)、`{{host}}`(请求 Host)。最大 **256 KiB**(按字节)。勿嵌入不可信第三方脚本 | 空 |
|
||||||
|
|
||||||
|
|||||||
@@ -10,15 +10,18 @@ export const OPTIONS_QUERY_KEY = ['openflare', 'options'] as const;
|
|||||||
export const KEY_ENABLED = 'origin_error_page_enabled';
|
export const KEY_ENABLED = 'origin_error_page_enabled';
|
||||||
export const KEY_STATUS_CODES = 'origin_error_page_status_codes';
|
export const KEY_STATUS_CODES = 'origin_error_page_status_codes';
|
||||||
export const KEY_HTML = 'origin_error_page_html';
|
export const KEY_HTML = 'origin_error_page_html';
|
||||||
|
export const KEY_GET_ONLY = 'origin_error_page_get_only';
|
||||||
|
|
||||||
export type ErrorPageFields = {
|
export type ErrorPageFields = {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
|
getOnly: boolean;
|
||||||
statusCodes: string[];
|
statusCodes: string[];
|
||||||
html: string;
|
html: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const defaultErrorPageFields: ErrorPageFields = {
|
export const defaultErrorPageFields: ErrorPageFields = {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
getOnly: false,
|
||||||
statusCodes: [...DEFAULT_ORIGIN_ERROR_PAGE_STATUS_TAGS],
|
statusCodes: [...DEFAULT_ORIGIN_ERROR_PAGE_STATUS_TAGS],
|
||||||
html: '',
|
html: '',
|
||||||
};
|
};
|
||||||
@@ -34,8 +37,10 @@ export function mapOptionsToFields(
|
|||||||
optionMap: Record<string, string>,
|
optionMap: Record<string, string>,
|
||||||
): ErrorPageFields {
|
): ErrorPageFields {
|
||||||
const enabledRaw = optionMap[KEY_ENABLED];
|
const enabledRaw = optionMap[KEY_ENABLED];
|
||||||
|
const getOnlyRaw = optionMap[KEY_GET_ONLY];
|
||||||
return {
|
return {
|
||||||
enabled: enabledRaw === undefined ? true : enabledRaw === 'true',
|
enabled: enabledRaw === undefined ? true : enabledRaw === 'true',
|
||||||
|
getOnly: getOnlyRaw === undefined ? false : getOnlyRaw === 'true',
|
||||||
statusCodes: parseStatusCodeTagsJSON(optionMap[KEY_STATUS_CODES]),
|
statusCodes: parseStatusCodeTagsJSON(optionMap[KEY_STATUS_CODES]),
|
||||||
html: optionMap[KEY_HTML] ?? '',
|
html: optionMap[KEY_HTML] ?? '',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import {
|
|||||||
defaultErrorPageFields,
|
defaultErrorPageFields,
|
||||||
invalidateErrorPageQueries,
|
invalidateErrorPageQueries,
|
||||||
KEY_ENABLED,
|
KEY_ENABLED,
|
||||||
|
KEY_GET_ONLY,
|
||||||
KEY_STATUS_CODES,
|
KEY_STATUS_CODES,
|
||||||
mapOptionsToFields,
|
mapOptionsToFields,
|
||||||
OPTIONS_QUERY_KEY,
|
OPTIONS_QUERY_KEY,
|
||||||
@@ -60,12 +61,13 @@ export default function ErrorPagesPage() {
|
|||||||
[fields.html],
|
[fields.html],
|
||||||
);
|
);
|
||||||
|
|
||||||
/** 仅保存启用开关 + 触发状态码(HTML 在编辑页单独保存) */
|
/** 仅保存策略项(HTML 在编辑页单独保存) */
|
||||||
const savePolicyMutation = useMutation({
|
const savePolicyMutation = useMutation({
|
||||||
mutationFn: async () => {
|
mutationFn: async () => {
|
||||||
validateStatusCodeTags(fields.statusCodes);
|
validateStatusCodeTags(fields.statusCodes);
|
||||||
await OptionService.updateBatch([
|
await OptionService.updateBatch([
|
||||||
{ key: KEY_ENABLED, value: String(fields.enabled) },
|
{ key: KEY_ENABLED, value: String(fields.enabled) },
|
||||||
|
{ key: KEY_GET_ONLY, value: String(fields.getOnly) },
|
||||||
{
|
{
|
||||||
key: KEY_STATUS_CODES,
|
key: KEY_STATUS_CODES,
|
||||||
value: JSON.stringify(fields.statusCodes),
|
value: JSON.stringify(fields.statusCodes),
|
||||||
@@ -124,13 +126,34 @@ export default function ErrorPagesPage() {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
<Card className='border-dashed shadow-none'>
|
||||||
|
<CardHeader className='flex flex-row items-center justify-between gap-4'>
|
||||||
|
<div>
|
||||||
|
<CardTitle className='text-base'>仅针对 GET 请求</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
开启后仅对 GET
|
||||||
|
请求的匹配错误状态码返回自定义错误页;POST/PUT
|
||||||
|
等其它方法直接透传源站响应。
|
||||||
|
</CardDescription>
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
checked={fields.getOnly}
|
||||||
|
disabled={!fields.enabled}
|
||||||
|
onCheckedChange={(getOnly) =>
|
||||||
|
setFields((prev) => ({ ...prev, getOnly }))
|
||||||
|
}
|
||||||
|
aria-label='仅针对 GET 请求'
|
||||||
|
/>
|
||||||
|
</CardHeader>
|
||||||
|
</Card>
|
||||||
|
|
||||||
<Card className='border-dashed shadow-none'>
|
<Card className='border-dashed shadow-none'>
|
||||||
<CardHeader className='flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between space-y-0'>
|
<CardHeader className='flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between space-y-0'>
|
||||||
<div className='space-y-1.5'>
|
<div className='space-y-1.5'>
|
||||||
<CardTitle className='text-base'>触发状态码</CardTitle>
|
<CardTitle className='text-base'>触发状态码</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
支持单码(如 502)或闭区间(如 500-599),范围 400–599。默认
|
支持单码(如 502)或闭区间(如 500-599),范围 400–599。默认
|
||||||
500-599。修改启用开关或状态码后需点击保存。
|
500-599。修改策略开关或状态码后需点击保存。
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -537,6 +537,7 @@ func diffOpenRestyOptionDetails(left openRestyConfigSnapshot, right openRestyCon
|
|||||||
appendIfChanged("OriginErrorPageEnabled", fmt.Sprintf("%t", left.OriginErrorPageEnabled), fmt.Sprintf("%t", right.OriginErrorPageEnabled))
|
appendIfChanged("OriginErrorPageEnabled", fmt.Sprintf("%t", left.OriginErrorPageEnabled), fmt.Sprintf("%t", right.OriginErrorPageEnabled))
|
||||||
appendIfChanged("OriginErrorPageStatusCodes", encodeOriginErrorPageStatusCodes(left.OriginErrorPageStatusCodes), encodeOriginErrorPageStatusCodes(right.OriginErrorPageStatusCodes))
|
appendIfChanged("OriginErrorPageStatusCodes", encodeOriginErrorPageStatusCodes(left.OriginErrorPageStatusCodes), encodeOriginErrorPageStatusCodes(right.OriginErrorPageStatusCodes))
|
||||||
appendIfChanged("OriginErrorPageHTML", left.OriginErrorPageHTML, right.OriginErrorPageHTML)
|
appendIfChanged("OriginErrorPageHTML", left.OriginErrorPageHTML, right.OriginErrorPageHTML)
|
||||||
|
appendIfChanged("OriginErrorPageGetOnly", fmt.Sprintf("%t", left.OriginErrorPageGetOnly), fmt.Sprintf("%t", right.OriginErrorPageGetOnly))
|
||||||
return changes
|
return changes
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -603,5 +604,6 @@ func openRestyOptionKeys() []string {
|
|||||||
"OriginErrorPageEnabled",
|
"OriginErrorPageEnabled",
|
||||||
"OriginErrorPageStatusCodes",
|
"OriginErrorPageStatusCodes",
|
||||||
"OriginErrorPageHTML",
|
"OriginErrorPageHTML",
|
||||||
|
"OriginErrorPageGetOnly",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -143,6 +143,7 @@ type openRestyConfigSnapshot struct {
|
|||||||
OriginErrorPageEnabled bool `json:"origin_error_page_enabled"`
|
OriginErrorPageEnabled bool `json:"origin_error_page_enabled"`
|
||||||
OriginErrorPageStatusCodes []string `json:"origin_error_page_status_codes,omitempty"`
|
OriginErrorPageStatusCodes []string `json:"origin_error_page_status_codes,omitempty"`
|
||||||
OriginErrorPageHTML string `json:"origin_error_page_html,omitempty"`
|
OriginErrorPageHTML string `json:"origin_error_page_html,omitempty"`
|
||||||
|
OriginErrorPageGetOnly bool `json:"origin_error_page_get_only,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type snapshotDocument struct {
|
type snapshotDocument struct {
|
||||||
@@ -558,6 +559,7 @@ func buildOpenRestyConfigSnapshot(ctx context.Context) openRestyConfigSnapshot {
|
|||||||
OriginErrorPageEnabled: getBoolConfig(model.ConfigKeyOriginErrorPageEnabled, true),
|
OriginErrorPageEnabled: getBoolConfig(model.ConfigKeyOriginErrorPageEnabled, true),
|
||||||
OriginErrorPageStatusCodes: parseOriginErrorPageStatusCodes(getStringConfig(model.ConfigKeyOriginErrorPageStatusCodes, `["500-599"]`)),
|
OriginErrorPageStatusCodes: parseOriginErrorPageStatusCodes(getStringConfig(model.ConfigKeyOriginErrorPageStatusCodes, `["500-599"]`)),
|
||||||
OriginErrorPageHTML: getStringConfig(model.ConfigKeyOriginErrorPageHTML, ""),
|
OriginErrorPageHTML: getStringConfig(model.ConfigKeyOriginErrorPageHTML, ""),
|
||||||
|
OriginErrorPageGetOnly: getBoolConfig(model.ConfigKeyOriginErrorPageGetOnly, false),
|
||||||
}
|
}
|
||||||
if snapshot.DefaultLimitRate == "0" {
|
if snapshot.DefaultLimitRate == "0" {
|
||||||
snapshot.DefaultLimitRate = ""
|
snapshot.DefaultLimitRate = ""
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ var openRestyOptionValidators = map[string]func(key, value string) error{
|
|||||||
model.ConfigKeyOriginErrorPageEnabled: validateBooleanOption,
|
model.ConfigKeyOriginErrorPageEnabled: validateBooleanOption,
|
||||||
model.ConfigKeyOriginErrorPageStatusCodes: validateOriginErrorPageStatusCodes,
|
model.ConfigKeyOriginErrorPageStatusCodes: validateOriginErrorPageStatusCodes,
|
||||||
model.ConfigKeyOriginErrorPageHTML: validateOriginErrorPageHTML,
|
model.ConfigKeyOriginErrorPageHTML: validateOriginErrorPageHTML,
|
||||||
|
model.ConfigKeyOriginErrorPageGetOnly: validateBooleanOption,
|
||||||
}
|
}
|
||||||
|
|
||||||
var openRestyDefaultLimitRatePattern = regexp.MustCompile(`^\d+[kKmM]?$`)
|
var openRestyDefaultLimitRatePattern = regexp.MustCompile(`^\d+[kKmM]?$`)
|
||||||
|
|||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
-- +goose Up
|
||||||
|
INSERT INTO w_system_configs (key, value, type, visibility, description, created_at, updated_at)
|
||||||
|
VALUES
|
||||||
|
('origin_error_page_get_only', 'false', 'business', 0, '源站错误页是否仅对 GET 请求生效(其它方法透传)', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
||||||
|
ON CONFLICT (key) DO NOTHING;
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
DELETE FROM w_system_configs WHERE key = 'origin_error_page_get_only';
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
-- +goose Up
|
||||||
|
INSERT INTO w_system_configs (key, value, type, visibility, description, created_at, updated_at)
|
||||||
|
VALUES
|
||||||
|
('origin_error_page_get_only', 'false', 'business', 0, '源站错误页是否仅对 GET 请求生效(其它方法透传)', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
||||||
|
ON CONFLICT (key) DO NOTHING;
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
DELETE FROM w_system_configs WHERE key = 'origin_error_page_get_only';
|
||||||
@@ -114,6 +114,7 @@ const (
|
|||||||
ConfigKeyOriginErrorPageEnabled = "origin_error_page_enabled" // 是否启用源站错误页
|
ConfigKeyOriginErrorPageEnabled = "origin_error_page_enabled" // 是否启用源站错误页
|
||||||
ConfigKeyOriginErrorPageStatusCodes = "origin_error_page_status_codes" // 源站错误页触发状态码标签 JSON 数组
|
ConfigKeyOriginErrorPageStatusCodes = "origin_error_page_status_codes" // 源站错误页触发状态码标签 JSON 数组
|
||||||
ConfigKeyOriginErrorPageHTML = "origin_error_page_html" // 源站错误页自定义 HTML(空则内置默认)
|
ConfigKeyOriginErrorPageHTML = "origin_error_page_html" // 源站错误页自定义 HTML(空则内置默认)
|
||||||
|
ConfigKeyOriginErrorPageGetOnly = "origin_error_page_get_only" // 是否仅对 GET 请求返回自定义错误页
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -130,7 +130,17 @@ func renderOriginErrorPageIntercept(cfg ConfigSnapshot) string {
|
|||||||
if _, err := ExpandStatusCodeTags(effectiveOriginErrorPageStatusTags(cfg)); err != nil {
|
if _, err := ExpandStatusCodeTags(effectiveOriginErrorPageStatusTags(cfg)); err != nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
return " proxy_intercept_errors on;\n"
|
// Always enable intercept for GET (and all methods when get_only is false).
|
||||||
|
// limit_except GET applies to non-GET methods: turn intercept off so origin
|
||||||
|
// error bodies (e.g. JSON 5xx) pass through unchanged.
|
||||||
|
var builder strings.Builder
|
||||||
|
builder.WriteString(" proxy_intercept_errors on;\n")
|
||||||
|
if cfg.OriginErrorPageGetOnly {
|
||||||
|
builder.WriteString(" limit_except GET {\n")
|
||||||
|
builder.WriteString(" proxy_intercept_errors off;\n")
|
||||||
|
builder.WriteString(" }\n")
|
||||||
|
}
|
||||||
|
return builder.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// renderOriginErrorPageServerBits emits server-level error_page + internal location.
|
// renderOriginErrorPageServerBits emits server-level error_page + internal location.
|
||||||
@@ -155,22 +165,30 @@ func renderOriginErrorPageServerBits(cfg ConfigSnapshot) string {
|
|||||||
var builder strings.Builder
|
var builder strings.Builder
|
||||||
// No `=` — preserve original error status (502 stays 502).
|
// No `=` — preserve original error status (502 stays 502).
|
||||||
fmt.Fprintf(&builder, " error_page %s %s;\n", strings.Join(parts, " "), OriginErrorPageInternalLocation)
|
fmt.Fprintf(&builder, " error_page %s %s;\n", strings.Join(parts, " "), OriginErrorPageInternalLocation)
|
||||||
builder.WriteString(renderOriginErrorPageInternalLocation())
|
builder.WriteString(renderOriginErrorPageInternalLocation(cfg.OriginErrorPageGetOnly))
|
||||||
return builder.String()
|
return builder.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderOriginErrorPageInternalLocation() string {
|
func renderOriginErrorPageInternalLocation(getOnly bool) string {
|
||||||
// Resolve status from $status (set by error_page internal redirect), then
|
// Resolve status from $status (set by error_page internal redirect), then
|
||||||
// upstream_status, then ngx.status. Force ngx.status so the client receives
|
// upstream_status, then ngx.status. Force ngx.status so the client receives
|
||||||
// the real error code. Use function replacers so host/status with `%` are safe.
|
// the real error code. Use function replacers so host/status with `%` are safe.
|
||||||
//
|
//
|
||||||
// Note: fmt.Sprintf is used only for the two path placeholders; Lua `%` must be
|
// Note: fmt.Sprintf is used only for the path placeholders; Lua `%` must be
|
||||||
// written as `%%` so Sprintf does not treat them as format verbs.
|
// written as `%%` so Sprintf does not treat them as format verbs.
|
||||||
|
//
|
||||||
|
// When getOnly is true, non-GET that still hit this location (e.g. nginx-local
|
||||||
|
// 502 without upstream body) exit with the original status and no HTML body.
|
||||||
|
getOnlyLua := "false"
|
||||||
|
if getOnly {
|
||||||
|
getOnlyLua = "true"
|
||||||
|
}
|
||||||
return fmt.Sprintf(` location = %s {
|
return fmt.Sprintf(` location = %s {
|
||||||
internal;
|
internal;
|
||||||
default_type text/html;
|
default_type text/html;
|
||||||
charset utf-8;
|
charset utf-8;
|
||||||
content_by_lua_block {
|
content_by_lua_block {
|
||||||
|
local get_only = %s
|
||||||
local function resolve_error_status()
|
local function resolve_error_status()
|
||||||
local code = tonumber(ngx.var.status)
|
local code = tonumber(ngx.var.status)
|
||||||
if code and code >= 400 then
|
if code and code >= 400 then
|
||||||
@@ -193,6 +211,11 @@ func renderOriginErrorPageInternalLocation() string {
|
|||||||
local code = resolve_error_status()
|
local code = resolve_error_status()
|
||||||
ngx.status = code
|
ngx.status = code
|
||||||
|
|
||||||
|
if get_only and ngx.req.get_method() ~= "GET" then
|
||||||
|
-- Non-GET: do not replace with HTML; exit with status only.
|
||||||
|
return ngx.exit(code)
|
||||||
|
end
|
||||||
|
|
||||||
local f = io.open("%s", "r")
|
local f = io.open("%s", "r")
|
||||||
if not f then
|
if not f then
|
||||||
ngx.header["Content-Type"] = "text/html; charset=utf-8"
|
ngx.header["Content-Type"] = "text/html; charset=utf-8"
|
||||||
@@ -210,5 +233,5 @@ func renderOriginErrorPageInternalLocation() string {
|
|||||||
ngx.say(body)
|
ngx.say(body)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`, OriginErrorPageInternalLocation, ErrorPageTmplPlaceholder)
|
`, OriginErrorPageInternalLocation, getOnlyLua, ErrorPageTmplPlaceholder)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,6 +68,40 @@ func TestRenderOriginErrorPageEnabled(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRenderOriginErrorPageGetOnly(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
doc := Document{
|
||||||
|
Routes: []Route{{
|
||||||
|
ID: 1, SiteName: "ex", Domains: []string{"ex.test"},
|
||||||
|
OriginURL: "http://127.0.0.1:9", Enabled: true,
|
||||||
|
}},
|
||||||
|
OpenRestyConfig: ConfigSnapshot{
|
||||||
|
OriginErrorPageEnabled: true,
|
||||||
|
OriginErrorPageStatusCodes: []string{"500-599"},
|
||||||
|
OriginErrorPageGetOnly: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
out, err := RenderRouteConfig(doc, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !strings.Contains(out, "proxy_intercept_errors on") {
|
||||||
|
t.Fatal("missing intercept on")
|
||||||
|
}
|
||||||
|
if !strings.Contains(out, "limit_except GET") {
|
||||||
|
t.Fatal("get_only must emit limit_except GET")
|
||||||
|
}
|
||||||
|
if !strings.Contains(out, "proxy_intercept_errors off") {
|
||||||
|
t.Fatal("get_only must turn intercept off for non-GET")
|
||||||
|
}
|
||||||
|
if !strings.Contains(out, `get_only = true`) {
|
||||||
|
t.Fatal("internal location must set get_only = true")
|
||||||
|
}
|
||||||
|
if !strings.Contains(out, `ngx.req.get_method() ~= "GET"`) {
|
||||||
|
t.Fatal("internal location must skip HTML for non-GET")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestRenderOriginErrorPageDisabled(t *testing.T) {
|
func TestRenderOriginErrorPageDisabled(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
doc := Document{
|
doc := Document{
|
||||||
|
|||||||
@@ -318,6 +318,8 @@ type ConfigSnapshot struct {
|
|||||||
OriginErrorPageEnabled bool `json:"origin_error_page_enabled"`
|
OriginErrorPageEnabled bool `json:"origin_error_page_enabled"`
|
||||||
OriginErrorPageStatusCodes []string `json:"origin_error_page_status_codes,omitempty"`
|
OriginErrorPageStatusCodes []string `json:"origin_error_page_status_codes,omitempty"`
|
||||||
OriginErrorPageHTML string `json:"origin_error_page_html,omitempty"`
|
OriginErrorPageHTML string `json:"origin_error_page_html,omitempty"`
|
||||||
|
// OriginErrorPageGetOnly limits custom error HTML to GET requests; other methods pass through.
|
||||||
|
OriginErrorPageGetOnly bool `json:"origin_error_page_get_only,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Document is the top-level input structure for the OpenResty renderer,
|
// Document is the top-level input structure for the OpenResty renderer,
|
||||||
|
|||||||
Reference in New Issue
Block a user