mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-30 06:16:37 +08:00
454542c1d0
- README 默认英文:README.en.md → README.md(英文为默认),中文移至 README.zh-CN.md,语言切换链接同步 - 恢复被删除的 docs/en/ 英文文档(git 历史 cc5e53c5^),删除 4 篇已废弃文件 - 英文导航 config.ts 对齐中文结构(新增 Deployment/Changelog 侧栏,同步 Guide/Design 条目) - 翻译 15 篇中文新增文档:guide 5 篇(certificates/pages-usage/proxy-config/uptime-kuma/zone-domain-migration)+ design 10 篇(zone-design/cloudflare-pointing/waf-orchestration/origin-error-page/edge-cache-design/pages-design/logstore/kuma-design/login-captcha/observability 三篇) - en 首页更新(新增 Pages 特性、tagline 同步);changelog 英文入口指向中文版 - vitepress 构建验证:43 个英文页面全部渲染 注意:29 篇旧英文文档为恢复版,部分内容(如 deployment/server、reference/configuration)可能落后于中文,需后续逐篇同步
265 lines
12 KiB
Markdown
265 lines
12 KiB
Markdown
# Edge Cache Strategy Design
|
||
|
||
You will learn: how OpenFlare's edge `proxy_cache` aligns with the Cloudflare default loop between "should cache" and "should not cache": request eligibility (extension/policy) × response shareability (origin `Cache-Control` / `Expires` / `Set-Cookie`), and the differences from the previous over-strict request bypass.
|
||
|
||
This design is the productized chapter on "basic caching" in [System Architecture](./architecture.md); cache results in access logs are in [Observability Data Model §3.5.1](./observability-data-model.md).
|
||
|
||
---
|
||
|
||
## 1. Goals and Non-Goals
|
||
|
||
### 1.1 Goals
|
||
|
||
* **Close to CF default out of the box**: after enabling cache on a route, **only static extensions are cached by default** — HTML is not cached by default; **request session cookies / Authorization / client Cache-Control no longer cause a blanket BYPASS**.
|
||
* **Cacheable content hits**: a logged-in user visiting `/_app/**/*.js` and other static assets can show `MISS` → `HIT`.
|
||
* **Non-cacheable stays blocked**: policy not eligible (equivalent to CF `DYNAMIC`); origin `private` / `no-store`; responses with **`Set-Cookie` not stored** (aligned with CF OCC default); `all` is an advanced option with documented warnings.
|
||
* **Default Edge TTL when no origin freshness**: aligned with CF's per-status default TTL (see §3.5).
|
||
* **Consistent observability**: keep relying on `$upstream_cache_status` → three-state `cache_status` detail.
|
||
* **Backward compatible**: legacy route `cache_policy=url` maps to `all`; policy enum and migration rules stay in [§5](#5-兼容与迁移).
|
||
|
||
### 1.2 Non-Goals (later iterations)
|
||
|
||
* Cache Rules expression engine
|
||
* Forced Edge TTL ignoring origin `Cache-Control` (CF Cache Rules "Ignore cache-control")
|
||
* Purge (by URL/prefix/site-wide)
|
||
* Browser TTL rewriting, client `CF-Cache-Status` response header
|
||
* Full RFC conditions: `Authorization` cached only when the response has `public`/`s-maxage`/`must-revalidate` (needs Lua; this iteration deletes the request-side bypass entirely, relying on policy + origin headers)
|
||
* HEAD → GET conversion then cache
|
||
* Hit-rate dashboard
|
||
|
||
---
|
||
|
||
## 2. Cloudflare Decision Loop (Alignment Baseline)
|
||
|
||
CF default is a **two-stage decision**, **not** "request has Cookie → don't cache".
|
||
|
||
### 2.1 Stage A — Eligible at Request Time
|
||
|
||
| Condition | CF Result |
|
||
| --- | --- |
|
||
| Non-GET | not cached by default |
|
||
| Extension not in default cacheable table, no Rules forcing eligible | **`DYNAMIC`** (no cache lookup) |
|
||
| Extension in default table, or Rules eligible | continue to Stage B |
|
||
| **Request Cookie** | **no effect by default** |
|
||
| Cache Rules Bypass | `DYNAMIC` |
|
||
|
||
CF's default cacheable extensions are keyed by **extension** rather than MIME; **HTML / JSON are not cached by default**.
|
||
|
||
### 2.2 Stage B — Response Storeable (OCC on, Free/Pro/Biz default)
|
||
|
||
| Condition | Result |
|
||
| --- | --- |
|
||
| `Cache-Control: no-store` / `private` | not stored |
|
||
| `public` + `max-age>0`, or future `Expires` | cacheable |
|
||
| No Cache-Control / Expires | still cacheable with per-status **default Edge TTL** (e.g. 200 → 120m) |
|
||
| Response **`Set-Cookie`** (default cache level + OCC) | **not stored**, status tends toward **BYPASS** |
|
||
| Request `Authorization` | cacheable only when the response also has `public` / `s-maxage` / `must-revalidate` (full condition simplified with Nginx this iteration, see §3.4) |
|
||
|
||
### 2.3 Status Semantics (vs. Observability)
|
||
|
||
| CF | Meaning | OpenFlare `cache_status` |
|
||
| --- | --- | --- |
|
||
| HIT / STALE / UPDATING / REVALIDATED | hit class | same-name or equivalent |
|
||
| MISS / EXPIRED | fetch from origin | same-name |
|
||
| BYPASS | eligible at request time, response not cacheable | `BYPASS` → UI "not cached" |
|
||
| DYNAMIC | not eligible at request time | policy skip mostly `BYPASS` or empty → UI "not cached" |
|
||
|
||
---
|
||
|
||
## 3. Product Semantics
|
||
|
||
### 3.1 Two-Level Switch (unchanged)
|
||
|
||
* **Global** `openresty_cache_enabled`: generates `proxy_cache_path` etc.; when off, route-level cache directives are inert.
|
||
* **Route** `cache_enabled`: whether to enable `proxy_cache` in that site's `location`.
|
||
|
||
Cache logic only runs when both are on.
|
||
|
||
### 3.2 Policy Enum
|
||
|
||
| `cache_policy` | Meaning | New Default | Legacy Compatibility |
|
||
| --- | --- | --- | --- |
|
||
| **`static`** | only eligible when URI matches **standard static extensions** | **yes** | — |
|
||
| **`all`** | after method bypass, no path/extension restriction (advanced; risk similar to CF Cache Everything) | no | legacy `url` → `all` |
|
||
| **`suffix`** | custom extension list (`cache_rules`) | no | kept |
|
||
| **`path_prefix`** | custom path prefix | no | kept |
|
||
| **`path_exact`** | custom exact path | no | kept |
|
||
|
||
Render layer: historical `url` is treated as `all`; API/UI only expose the enum above.
|
||
|
||
### 3.3 Standard Static Extensions (built-in)
|
||
|
||
Aligned with CF default "no HTML/JSON caching"; keeps modern frontend-friendly enhancements:
|
||
|
||
```text
|
||
css js mjs map
|
||
ico cur gif jpg jpeg png webp avif svg svgz
|
||
ttf otf woff woff2 eot
|
||
mp3 mp4 webm ogg flac
|
||
wasm pdf
|
||
zip 7z gz tar
|
||
```
|
||
|
||
* **Excludes** `html` / `htm` / **`json`** (aligned with CF not caching JSON by default).
|
||
* **Includes** `map` / `mjs` / `wasm` (deliberate enhancement for sourcemap / ES module / WASM hits).
|
||
* Matching: `$uri` extension, case-insensitive:
|
||
`if ($uri !~* \.(?:css|js|…)$) { set $openflare_skip_cache 1; }`
|
||
|
||
### 3.4 Request-Side Bypass (after CF alignment)
|
||
|
||
Only kept:
|
||
|
||
1. `$request_method != GET` (HEAD included, consistent with current network; no CF HEAD→GET)
|
||
|
||
**Removed** (previously over-strict, causing low hit rates):
|
||
|
||
* Session-cookie regex
|
||
* `$http_authorization != ""`
|
||
* request `$http_cache_control` matching `no-cache|no-store|private`
|
||
|
||
**How security still holds:**
|
||
|
||
| Threat | Gate |
|
||
| --- | --- |
|
||
| Accidentally caching HTML/API | default `static` extensions (no html/json) |
|
||
| Personalized content | origin `private` / `no-store` (respected by Nginx) |
|
||
| Response writes session | **`Set-Cookie` → not stored** (§3.6) |
|
||
| `all` too broad | UI/doc warning: needs correct origin Cache-Control |
|
||
| API with Bearer | rely on policy (don't use `all` for APIs) + origin headers; full Auth conditional caching is later |
|
||
|
||
### 3.5 Default Edge TTL (no origin freshness)
|
||
|
||
Aligned with CF's per-status default TTL without `Cache-Control`/`Expires`, emitted in cache-enabled locations:
|
||
|
||
| Status | TTL |
|
||
| --- | --- |
|
||
| 200, 206, 301 | 120m |
|
||
| 302, 303 | 20m |
|
||
| 404, 410 | 3m |
|
||
|
||
```nginx
|
||
proxy_cache_valid 200 206 301 120m;
|
||
proxy_cache_valid 302 303 20m;
|
||
proxy_cache_valid 404 410 3m;
|
||
```
|
||
|
||
* When the origin provides valid `Cache-Control` / `Expires`, the origin freshness wins (no `proxy_ignore_headers`).
|
||
* **No** forced Edge TTL override ignoring origin headers.
|
||
|
||
### 3.6 Response Side: Set-Cookie Not Stored
|
||
|
||
Aligned with CF OCC default: an eligible request whose origin returns **`Set-Cookie`** is **not written** into `proxy_cache` (read path may still have MISS/BYPASS semantics).
|
||
|
||
```nginx
|
||
proxy_no_cache $openflare_skip_cache $upstream_http_set_cookie;
|
||
```
|
||
|
||
(`proxy_no_cache` multi-arg: any non-empty and non-`"0"` arg means no write.)
|
||
|
||
`proxy_cache_bypass` still only binds `$openflare_skip_cache` (request-side skip); the response side only affects **writes**, consistent with CF "eligible but response not cacheable".
|
||
|
||
### 3.7 Relationship with Origin Headers
|
||
|
||
* **Eligibility**: policy + method bypass.
|
||
* **Store / duration**: origin `Cache-Control` / `Expires` + default `proxy_cache_valid` + Set-Cookie gate + global `inactive`.
|
||
|
||
---
|
||
|
||
## 4. Rendering and Data Flow
|
||
|
||
```text
|
||
Global cache_enabled?
|
||
│ no → no proxy_cache_* generated
|
||
▼ yes
|
||
Route cache_enabled?
|
||
│ no → location without proxy_cache
|
||
▼ yes
|
||
set $openflare_skip_cache 0
|
||
→ non-GET → set 1
|
||
→ policy if (static/all/suffix/…) → may set 1
|
||
proxy_cache openflare_cache
|
||
proxy_cache_methods GET
|
||
proxy_cache_bypass $openflare_skip_cache
|
||
proxy_no_cache $openflare_skip_cache $upstream_http_set_cookie
|
||
proxy_cache_valid …
|
||
→
|
||
access.log cache_status=$upstream_cache_status
|
||
```
|
||
|
||
### 4.1 Policy → Nginx Conditions
|
||
|
||
| Policy | Extra Condition |
|
||
| --- | --- |
|
||
| `static` | `$uri` not matching built-in extension table → skip |
|
||
| `all` | no extra path condition |
|
||
| `suffix` | not matching `cache_rules` extensions → skip |
|
||
| `path_prefix` / `path_exact` | same as current implementation |
|
||
|
||
### 4.2 Code Areas Involved
|
||
|
||
| Area | Path |
|
||
| --- | --- |
|
||
| Rendering | `pkg/render/openresty/render.go` (bypass, Set-Cookie, `proxy_cache_valid`, extension constants) |
|
||
| Validation | `internal/apps/openflare/proxy_route/helpers.go` |
|
||
| Model/defaults | creating a route defaults `cache_policy=static`; `url`→`all` on read/write |
|
||
| Snapshot | `config_version` snapshot normalization |
|
||
| UI | `proxy-routes/detail/components/cache-section.tsx` |
|
||
|
||
---
|
||
|
||
## 5. Compatibility and Migration
|
||
|
||
| Data | Handling |
|
||
| --- | --- |
|
||
| `cache_policy=''` or `url` in DB (and cache enabled) | read / snapshot / render → **`all`** |
|
||
| API write with enabled and empty policy | normalized to **`all`**; UI new-create with cache on **explicitly submits** `static` |
|
||
| New routes | default **`static`** when cache enabled |
|
||
| Bypass behavior change | **breaking vs. old implementation**: cookie/auth traffic goes from "not cached" to cacheable HIT; requires **republishing node configs** |
|
||
| Default extensions | **remove `json`** from the table; sites relying on caching `*.json` can use custom `suffix` or `all` |
|
||
|
||
**Release note:** document this alignment with the CF default model; hit rate expected to rise; `all` and wrong origin headers need ops self-check.
|
||
|
||
---
|
||
|
||
## 6. UI Copy Points (Cache Tab)
|
||
|
||
* After enabling cache, default: **standard static assets** (summary extensions, **excluding HTML/JSON**; including map/mjs etc.).
|
||
* Options: standard static / all cacheable GET (advanced) / custom suffix / path prefix / exact path.
|
||
* CF-aligned notes:
|
||
* login cookies are **not** separately skipped from caching;
|
||
* origin `private` / `no-store` / response **`Set-Cookie`** are not written to the edge cache;
|
||
* default Edge TTL used when no origin cache headers.
|
||
* **Advanced `all`**: warn "similar to Cache Everything; personalized pages must declare private/no-store from the origin".
|
||
* Global Performance cache master switch must be on.
|
||
|
||
---
|
||
|
||
## 7. Decision Matrix (Avoid Missed Judgments)
|
||
|
||
| Scenario | CF | OpenFlare (this design) |
|
||
| --- | --- | --- |
|
||
| GET static + session Cookie + origin public max-age | HIT | HIT |
|
||
| GET HTML + static policy | DYNAMIC | policy skip → not cached |
|
||
| GET + all + origin private | not stored | not stored |
|
||
| GET static + response Set-Cookie | BYPASS (OCC) | not stored |
|
||
| GET + Authorization + static public | conditional cache | cacheable (simplified; rely on origin not marking sensitive APIs public) |
|
||
| GET + no-CC 200 static | default 120m | `proxy_cache_valid` 120m |
|
||
| DevTools Disable cache (request no-cache) | edge may still HIT by default | edge may still HIT by default |
|
||
| POST | not cached | non-GET skip |
|
||
|
||
---
|
||
|
||
## 8. Decision Record
|
||
|
||
| Decision | Choice | Reason |
|
||
| --- | --- | --- |
|
||
| Request Cookie bypass | **removed** | aligned with CF; restore static hit rate for logged-in users |
|
||
| Request Authorization / Cache-Control bypass | **removed** | aligned with CF request-eligibility model; response gate as backstop |
|
||
| Set-Cookie | **bind to proxy_no_cache** | aligned with CF OCC "response Set-Cookie not stored" |
|
||
| Default Edge TTL | **per-status proxy_cache_valid** | aligned with CF default TTL when headerless, avoiding "never stored" |
|
||
| Remove json from default table | **yes** | aligned with CF not caching JSON by default |
|
||
| Keep map/mjs/wasm | **yes** | useful hits for modern frontend, deliberate enhancement |
|
||
| Default cacheable scope | cache-on defaults to `static` | benchmarked to CF, reduces HTML/API mis-caching |
|
||
| Legacy `url` | maps to `all` | doesn't narrow existing behavior |
|
||
| Full Auth conditions / Purge / Rules | later | close the default loop first, then extend |
|