mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-01 14:46:36 +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:
@@ -130,7 +130,17 @@ func renderOriginErrorPageIntercept(cfg ConfigSnapshot) string {
|
||||
if _, err := ExpandStatusCodeTags(effectiveOriginErrorPageStatusTags(cfg)); err != nil {
|
||||
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.
|
||||
@@ -155,22 +165,30 @@ func renderOriginErrorPageServerBits(cfg ConfigSnapshot) string {
|
||||
var builder strings.Builder
|
||||
// No `=` — preserve original error status (502 stays 502).
|
||||
fmt.Fprintf(&builder, " error_page %s %s;\n", strings.Join(parts, " "), OriginErrorPageInternalLocation)
|
||||
builder.WriteString(renderOriginErrorPageInternalLocation())
|
||||
builder.WriteString(renderOriginErrorPageInternalLocation(cfg.OriginErrorPageGetOnly))
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
func renderOriginErrorPageInternalLocation() string {
|
||||
func renderOriginErrorPageInternalLocation(getOnly bool) string {
|
||||
// Resolve status from $status (set by error_page internal redirect), then
|
||||
// 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.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// 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 {
|
||||
internal;
|
||||
default_type text/html;
|
||||
charset utf-8;
|
||||
content_by_lua_block {
|
||||
local get_only = %s
|
||||
local function resolve_error_status()
|
||||
local code = tonumber(ngx.var.status)
|
||||
if code and code >= 400 then
|
||||
@@ -193,6 +211,11 @@ func renderOriginErrorPageInternalLocation() string {
|
||||
local code = resolve_error_status()
|
||||
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")
|
||||
if not f then
|
||||
ngx.header["Content-Type"] = "text/html; charset=utf-8"
|
||||
@@ -210,5 +233,5 @@ func renderOriginErrorPageInternalLocation() string {
|
||||
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) {
|
||||
t.Parallel()
|
||||
doc := Document{
|
||||
|
||||
@@ -318,6 +318,8 @@ type ConfigSnapshot struct {
|
||||
OriginErrorPageEnabled bool `json:"origin_error_page_enabled"`
|
||||
OriginErrorPageStatusCodes []string `json:"origin_error_page_status_codes,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,
|
||||
|
||||
Reference in New Issue
Block a user