Skip to content

Origin Error Page Design

You will learn: when an origin or the gateway returns a specified error status code, how OpenFlare replaces the pass-through response with a globally configurable page; how the config enters the immutable config version; and how the edge OpenResty keeps the real HTTP status code while displaying it in the page.

This design is the productized complement of the reverse proxy traffic path in System Architecture; the config release model is in Agent & Publish Model.


1. Goals and Non-Goals

1.1 Goals

  • Interceptable: for a user-configured status code set, replace the previously pass-through origin/Nginx default error response with a unified HTML.
  • Disableable: when the global switch is off, behavior matches today (pass-through / Nginx default page).
  • Visible by default: enabled by default, default status code tag 500-599, default minimal OpenFlare error page.
  • Customizable: admins can edit the full HTML online; empty HTML means the built-in default template.
  • Status passthrough: the HTTP response status keeps the original error code (e.g. 502, 522); the page body shows the same value via .
  • Globally unified: a single config under sidebar「Website Management → Response Pages」shared by all reverse proxy routes.
  • Consistent with release: the config persists via Option, enters the config version snapshot, and is distributed with release/rollback.

1.2 Non-Goals

  • Per-route / per-Zone error page overrides
  • Hosting error pages via file upload (online HTML only)
  • Modifying WAF / PoW / rate-limit's own response pages (unless the user adds those status codes to the list)
  • Pages static route error pages
  • Multi-language error pages, brand asset CDN

2. Product Behavior

2.1 When to Replace

ConditionBehavior
Switch on and the response status falls in the expanded setreturn custom/default HTML, status unchanged
Switch on with GET-only enabled, non-GET request returns a matching statuspass through the origin's raw response, no replacement
Switch offno error_page directives generated, pass through
Status not in the setno replacement
Pages upstream routesthis feature is not applied
Origin returns 2xx/3xx/4xx successfully (not configured)no replacement

In all-methods mode, proxy_intercept_errors on is enabled on the reverse proxy location, so origin-returned matching 5xx etc. are also intercepted, not just gateway-local 502s; GET-only mode switches to Lua header/body filters that only replace GET response bodies.

2.2 Status Code Tag Syntax

Each Tags Input entry:

FormExampleMeaning
Single code522only that code
Closed range500-599expand including endpoints
  • Valid range: single codes and range endpoints must be in 400–599; lo ≤ hi.
  • Default tag list: ["500-599"].
  • Persist the raw tags (JSON array string); expand, dedupe, and sort at render time.
  • If the expanded result is empty while enabled → save rejected.
  • Invalid tags → save rejected with a readable error.

2.3 Page Placeholders

PlaceholderMeaning
the current response status code (consistent with the HTTP status)
request Host

Both custom HTML and the default template support these placeholders; replaced at the edge at runtime. Unused placeholders may be omitted from the template.

2.4 Default Page

Built-in minimal white-background OpenFlare default page: large pass-through status code, short English description, Host, and a brand footer. Supports / ; the frontend can load prebuilt styles from the built-in template catalog on the edit page.


3. Config Model

3.1 Option Keys (w_system_configs / OpenFlare Option API)

KeyTypeDefaultDescription
origin_error_page_enabledbool stringtruemaster switch
origin_error_page_status_codesJSON string array["500-599"]raw tags
origin_error_page_htmltext""empty = built-in default; max 256 KiB
origin_error_page_get_onlybool stringfalsereplace error pages only for GET; other methods pass through

Reuses APIs:

  • GET /api/v1/d/option
  • POST /api/v1/d/option/update-batch

No new resource routes. goose migration writes the seed; constants defined in the internal/model config key area.

3.2 Validation (update-batch)

  1. enabled: parseable as bool.
  2. status_codes: valid JSON array; each entry ^\d{3}$ or ^\d{3}-\d{3}$; expanded values all in 400–599; non-empty when enabled.
  3. html: length ≤ 256 KiB (bytes); empty allowed.
  4. Parse/expand logic is a pure function shared by the API and pkg/render/openresty to avoid semantic forks.

No XSS sanitization on HTML: it's an admin global ops config consistent with public edge display; docs warn not to embed untrusted third-party scripts.

3.3 Config Version Snapshot

ConfigSnapshot adds fields:

text
OriginErrorPageEnabled     bool
OriginErrorPageStatusCodes []string  // raw tags
OriginErrorPageHTML        string    // empty => renderer uses built-in default
OriginErrorPageGetOnly     bool

Read from Option when building the snapshot; the Agent only consumes the snapshot, never reading the control-plane DB directly.


4. Edge Rendering

4.1 Content Generated When Enabled

  1. SupportFile: error page template (e.g. error_pages/origin_error.html.tmpl), content is the custom HTML or built-in default, keeping / .
  2. Each reverse proxy server (HTTP/HTTPS proxy; excluding Pages):
nginx
proxy_intercept_errors on;
error_page <expanded codes...> @__openflare_origin_error;

location @__openflare_origin_error {
    default_type text/html;
    charset utf-8;
    content_by_lua_block {
        # read template, replace {{status}} / {{host}}, output body
        # ngx.status keeps the original error code
    }
}

4.2 Runtime Replacement

Use a named location with content_by_lua_block to read the template and replace placeholders — the status is not baked into a static file (status differs per request). GET-only mode uses header_filter_by_lua_block + body_filter_by_lua_block inside the reverse proxy location to replace only GET response bodies; non-GET requests pass through.

Never rewrite the error page to HTTP 200.

4.3 When Disabled

Do not output proxy_intercept_errors, error_page, the internal location, or the corresponding SupportFile (or the file may be written but unreferenced). GET-only mode also omits the Lua filters.

4.4 Interaction with Cache / Stale

If global proxy_cache_use_stale returns stale cache for some error codes, successful stale responses never enter error_page. The error page is only shown when the client actually receives an error status in the configured list. Behavior depends on existing cache directives; this feature does not change stale policy.


5. Frontend

5.1 Entry

  • Sidebar「Website Management → Response Pages」: Error Page tab (/responses), edit page /responses/error-page/edit, preview page /responses/error-page/preview.

5.2 Page Structure

  • Header note: takes effect after releasing via「Version Release」.
  • Switch + Tags Input (shadcn-extension Tags Input: @/components/ui/tags-input): status code tags.
  • HTML editor area +「Load default template」「Restore default (clear)」+ placeholder docs.
  • Client-side preview: replace with sample status=502, host=example.com and preview in sandbox/iframe.
  • Save: OptionService.updateBatch; permissions same as the performance tuning page (admin).

5.3 Component Dependencies

Tags Input and the HTML editor reuse existing shadcn/ui components, consistent with the existing UI style.


6. Data Flow

text
Admin /responses (Error Page tab)
    → Option update-batch (validate tags & HTML)
    → w_system_configs

Release config version
    → snapshot writes OriginErrorPage*
    → render OpenResty conf + SupportFile
    → Agent pulls and reloads

Visitor requests a proxied domain
    → origin/gateway produces a matching status code
    → error_page → named location
    → replace placeholders, keep original status, return HTML

7. Decision Record

DecisionChoiceReason
Config scopeglobalproduct requirement; simple implementation and ops
StorageOption + config versionconsistent with performance tuning, rollbackable
Status inputtags: single code and rangedefault whole 5xx, but can name 522
Response statuskeep originalcorrect for monitoring/SEO/client semantics
Runtime replacementinternal + lightweight template replacementstatus differs per request
Customizationonline HTMLflexible without a file-upload chain

Released under the Apache License 2.0