package openresty import ( "fmt" "strconv" "strings" ) const ( // OriginErrorPageSupportPath is the SupportFile path for the origin error HTML template. OriginErrorPageSupportPath = "error_pages/origin_error.html.tmpl" // OriginErrorPageInternalLocation is the internal nginx location that serves the error body. OriginErrorPageInternalLocation = "/__openflare_origin_error" defaultOriginErrorPageStatusTag = "500-599" ) // DefaultOriginErrorPageHTML is the built-in default (aligned with frontend minimalist). // Placeholders {{status}} and {{host}} are substituted at request time by Lua. const DefaultOriginErrorPageHTML = ` {{status}} | OpenFlare

{{status}}

The upstream server is unreachable. Please try again later or contact the site administrator if the problem persists.

{{host}}

` // EffectiveOriginErrorPageHTML returns custom HTML when set, otherwise the built-in default. func EffectiveOriginErrorPageHTML(cfg ConfigSnapshot) string { if strings.TrimSpace(cfg.OriginErrorPageHTML) == "" { return DefaultOriginErrorPageHTML } return cfg.OriginErrorPageHTML } func effectiveOriginErrorPageStatusTags(cfg ConfigSnapshot) []string { if len(cfg.OriginErrorPageStatusCodes) == 0 { return []string{defaultOriginErrorPageStatusTag} } return cfg.OriginErrorPageStatusCodes } func originErrorPageSupportFile(cfg ConfigSnapshot) SupportFile { return SupportFile{ Path: OriginErrorPageSupportPath, Content: EffectiveOriginErrorPageHTML(cfg), } } func renderOriginErrorPageIntercept(cfg ConfigSnapshot) string { if !cfg.OriginErrorPageEnabled { return "" } if _, err := ExpandStatusCodeTags(effectiveOriginErrorPageStatusTags(cfg)); err != nil { return "" } return " proxy_intercept_errors on;\n" } // renderOriginErrorPageServerBits emits server-level error_page + internal location. // Returns empty string when disabled, expand fails, or no codes remain. // // IMPORTANT: do NOT use `error_page CODE = /uri` (equals without response code). // That form adopts the status returned by the error URI; content_by_lua defaults // to 200 and ngx.status is often 0, so clients saw 200 with body "{{status}}"→"0". // Without `=`, nginx keeps the original error status for the internal redirect. func renderOriginErrorPageServerBits(cfg ConfigSnapshot) string { if !cfg.OriginErrorPageEnabled { return "" } codes, err := ExpandStatusCodeTags(effectiveOriginErrorPageStatusTags(cfg)) if err != nil || len(codes) == 0 { return "" } parts := make([]string, len(codes)) for i, code := range codes { parts[i] = strconv.Itoa(code) } 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()) return builder.String() } func renderOriginErrorPageInternalLocation() 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 // written as `%%` so Sprintf does not treat them as format verbs. return fmt.Sprintf(` location = %s { internal; default_type text/html; charset utf-8; content_by_lua_block { local function resolve_error_status() local code = tonumber(ngx.var.status) if code and code >= 400 then return code end local upstream = ngx.var.upstream_status or "" -- multi-upstream: "502, 502" or failed connect "0" local first = upstream:match("(%%d+)") code = tonumber(first) if code and code >= 400 then return code end code = tonumber(ngx.status) if code and code >= 400 then return code end return 502 end local code = resolve_error_status() ngx.status = code local f = io.open("%s", "r") if not f then ngx.header["Content-Type"] = "text/html; charset=utf-8" ngx.say("Error ", tostring(code)) return end local body = f:read("*a") f:close() local status = tostring(code) local host = ngx.var.host or "" -- function replacer: plain insert, no percent pattern side effects body = body:gsub("{{status}}", function() return status end) body = body:gsub("{{host}}", function() return host end) ngx.header["Content-Type"] = "text/html; charset=utf-8" ngx.say(body) } } `, OriginErrorPageInternalLocation, ErrorPageTmplPlaceholder) }