mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-09 00:56:37 +08:00
feat(cache): 开启缓存默认仅缓存标准静态资源
路由缓存策略新增 static(内置扩展名,不含 HTML)与 all; 存量 url 规范为 all。OpenResty 渲染与代理路由 UI 同步。
This commit is contained in:
@@ -23,6 +23,7 @@ sidebar: false
|
|||||||
|
|
||||||
### 变更
|
### 变更
|
||||||
|
|
||||||
|
- 边缘缓存默认策略调整为「标准静态资源」:开启站点缓存后默认仅缓存 css/js/图片/字体等扩展名(不含 HTML);原「按 URL」行为保留为「所有可缓存 GET」。
|
||||||
- 优化访问日志概览及其他图表全局排行榜 (RankChart) 的样式布局:将每一项改为单行横向排布(左侧标签、中间进度条、右侧数值),数值支持自动格式化为 Compact 形式(如 39.57k、1.2M),同时调整默认高度为 320px 并隐藏滚动条。
|
- 优化访问日志概览及其他图表全局排行榜 (RankChart) 的样式布局:将每一项改为单行横向排布(左侧标签、中间进度条、右侧数值),数值支持自动格式化为 Compact 形式(如 39.57k、1.2M),同时调整默认高度为 320px 并隐藏滚动条。
|
||||||
- 访问日志页面重构为「概览」与「日志明细」两个标签页:概览展示请求量、访问量、带宽趋势与 Top Paths/Hosts/IPs,明细列表保留检索能力;已移除时间折叠与 IP 汇总视图。
|
- 访问日志页面重构为「概览」与「日志明细」两个标签页:概览展示请求量、访问量、带宽趋势与 Top Paths/Hosts/IPs,明细列表保留检索能力;已移除时间折叠与 IP 汇总视图。
|
||||||
- 边缘访问日志现支持上报并存储 User-Agent;概览新增设备类型饼图,以及浏览器、操作系统、User-Agent 排行。
|
- 边缘访问日志现支持上报并存储 User-Agent;概览新增设备类型饼图,以及浏览器、操作系统、User-Agent 排行。
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# 边缘缓存默认 static 策略 — 实现计划
|
||||||
|
|
||||||
|
对应设计:[edge-cache-design.md](../design/edge-cache-design.md)
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
路由开启缓存后默认仅缓存标准静态扩展名(`static`);存量 `url` 映射为 `all`。
|
||||||
|
|
||||||
|
## 修改清单
|
||||||
|
|
||||||
|
1. **渲染** `pkg/render/openresty/render.go`:`static` 内置扩展名;`url`/`all` 无路径限制
|
||||||
|
2. **校验** `internal/apps/openflare/proxy_route/helpers.go`:策略枚举与规范化
|
||||||
|
3. **前端** `cache-section.tsx` + helpers:默认 `static`,选项文案
|
||||||
|
4. **测试** render + helpers
|
||||||
|
5. **changelog**
|
||||||
|
|
||||||
|
## 验证
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go test ./pkg/render/openresty/ ./internal/apps/openflare/proxy_route/
|
||||||
|
make code-check # 或至少 go test + frontend tsc
|
||||||
|
```
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
* [WAF 可编排规则](./20260713-waf-orchestration.md):使用 React Flow 编辑 DAG 规则,发布时编译并由 OpenResty 纯内存执行。
|
* [WAF 可编排规则](./20260713-waf-orchestration.md):使用 React Flow 编辑 DAG 规则,发布时编译并由 OpenResty 纯内存执行。
|
||||||
* [边缘可观测与业务流量统计重构](./20260717-observability-redesign.md):访问日志为业务唯一真相;Agent 只上报明细与主机读数;收敛「出站/已提供」双字段。
|
* [边缘可观测与业务流量统计重构](./20260717-observability-redesign.md):访问日志为业务唯一真相;Agent 只上报明细与主机读数;收敛「出站/已提供」双字段。
|
||||||
* [访问日志 cache_status 明细可见](./20260718-access-log-cache-status.md):上报 `$upstream_cache_status`,明细展示命中/回源/未缓存三态。
|
* [访问日志 cache_status 明细可见](./20260718-access-log-cache-status.md):上报 `$upstream_cache_status`,明细展示命中/回源/未缓存三态。
|
||||||
|
* [边缘缓存默认 static 策略](./20260718-edge-cache-static-default.md):开启缓存默认仅静态扩展名;存量 url→all。
|
||||||
|
|
||||||
## 使用建议
|
## 使用建议
|
||||||
|
|
||||||
|
|||||||
@@ -278,10 +278,10 @@ export function normalizeLimitRate(value: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function validateCacheRules(
|
export function validateCacheRules(
|
||||||
policy: 'url' | 'suffix' | 'path_prefix' | 'path_exact',
|
policy: 'static' | 'all' | 'url' | 'suffix' | 'path_prefix' | 'path_exact',
|
||||||
rules: string[],
|
rules: string[],
|
||||||
) {
|
) {
|
||||||
if (policy === 'url') {
|
if (policy === 'static' || policy === 'all' || policy === 'url') {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -335,7 +335,12 @@ export function buildPayloadFromRoute(
|
|||||||
limit_conn_per_ip: route.limit_conn_per_ip,
|
limit_conn_per_ip: route.limit_conn_per_ip,
|
||||||
limit_rate: route.limit_rate,
|
limit_rate: route.limit_rate,
|
||||||
cache_enabled: route.cache_enabled,
|
cache_enabled: route.cache_enabled,
|
||||||
cache_policy: route.cache_policy || 'url',
|
cache_policy:
|
||||||
|
!route.cache_policy || route.cache_policy === 'url'
|
||||||
|
? route.cache_policy === 'url'
|
||||||
|
? 'all'
|
||||||
|
: 'static'
|
||||||
|
: route.cache_policy,
|
||||||
cache_rules: route.cache_rule_list ?? [],
|
cache_rules: route.cache_rule_list ?? [],
|
||||||
custom_headers: route.custom_header_list ?? [],
|
custom_headers: route.custom_header_list ?? [],
|
||||||
basic_auth_enabled: route.basic_auth_enabled,
|
basic_auth_enabled: route.basic_auth_enabled,
|
||||||
|
|||||||
@@ -229,7 +229,7 @@ export function ProxyRouteCreateSheet({
|
|||||||
limit_conn_per_ip: 0,
|
limit_conn_per_ip: 0,
|
||||||
limit_rate: '',
|
limit_rate: '',
|
||||||
cache_enabled: false,
|
cache_enabled: false,
|
||||||
cache_policy: 'url',
|
cache_policy: 'static',
|
||||||
cache_rules: [],
|
cache_rules: [],
|
||||||
custom_headers: [],
|
custom_headers: [],
|
||||||
basic_auth_enabled: false,
|
basic_auth_enabled: false,
|
||||||
|
|||||||
@@ -36,7 +36,14 @@ import { SectionShell } from './section-shell';
|
|||||||
const cacheSchema = z
|
const cacheSchema = z
|
||||||
.object({
|
.object({
|
||||||
cache_enabled: z.boolean(),
|
cache_enabled: z.boolean(),
|
||||||
cache_policy: z.enum(['url', 'suffix', 'path_prefix', 'path_exact']),
|
cache_policy: z.enum([
|
||||||
|
'static',
|
||||||
|
'all',
|
||||||
|
'url',
|
||||||
|
'suffix',
|
||||||
|
'path_prefix',
|
||||||
|
'path_exact',
|
||||||
|
]),
|
||||||
cache_rules_text: z.string(),
|
cache_rules_text: z.string(),
|
||||||
})
|
})
|
||||||
.superRefine((value, context) => {
|
.superRefine((value, context) => {
|
||||||
@@ -63,6 +70,22 @@ interface CacheSectionProps {
|
|||||||
onSavingChange?: (saving: boolean) => void;
|
onSavingChange?: (saving: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeCachePolicyValue(policy: string | undefined | null) {
|
||||||
|
const value = (policy || '').trim();
|
||||||
|
if (!value || value === 'static') return 'static';
|
||||||
|
if (value === 'url' || value === 'all') return 'all';
|
||||||
|
if (value === 'suffix' || value === 'path_prefix' || value === 'path_exact') {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return 'static';
|
||||||
|
}
|
||||||
|
|
||||||
|
function needsRulesForPolicy(policy: string) {
|
||||||
|
return (
|
||||||
|
policy === 'suffix' || policy === 'path_prefix' || policy === 'path_exact'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function CacheSection({
|
export function CacheSection({
|
||||||
route,
|
route,
|
||||||
onRouteUpdate,
|
onRouteUpdate,
|
||||||
@@ -78,8 +101,9 @@ export function CacheSection({
|
|||||||
resolver: zodResolver(cacheSchema),
|
resolver: zodResolver(cacheSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
cache_enabled: route.cache_enabled,
|
cache_enabled: route.cache_enabled,
|
||||||
cache_policy: (route.cache_policy ||
|
cache_policy: normalizeCachePolicyValue(
|
||||||
'url') as CacheValues['cache_policy'],
|
route.cache_policy,
|
||||||
|
) as CacheValues['cache_policy'],
|
||||||
cache_rules_text: route.cache_rule_list.join('\n'),
|
cache_rules_text: route.cache_rule_list.join('\n'),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -87,14 +111,19 @@ export function CacheSection({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
form.reset({
|
form.reset({
|
||||||
cache_enabled: route.cache_enabled,
|
cache_enabled: route.cache_enabled,
|
||||||
cache_policy: (route.cache_policy ||
|
cache_policy: normalizeCachePolicyValue(
|
||||||
'url') as CacheValues['cache_policy'],
|
route.cache_policy,
|
||||||
|
) as CacheValues['cache_policy'],
|
||||||
cache_rules_text: route.cache_rule_list.join('\n'),
|
cache_rules_text: route.cache_rule_list.join('\n'),
|
||||||
});
|
});
|
||||||
}, [form, route]);
|
}, [form, route]);
|
||||||
|
|
||||||
const watchedEnabled = form.watch('cache_enabled');
|
const watchedEnabled = form.watch('cache_enabled');
|
||||||
const watchedPolicy = form.watch('cache_policy');
|
const watchedPolicy = form.watch('cache_policy');
|
||||||
|
const needsRules =
|
||||||
|
watchedPolicy === 'suffix' ||
|
||||||
|
watchedPolicy === 'path_prefix' ||
|
||||||
|
watchedPolicy === 'path_exact';
|
||||||
|
|
||||||
const rulesHint =
|
const rulesHint =
|
||||||
watchedPolicy === 'suffix'
|
watchedPolicy === 'suffix'
|
||||||
@@ -103,7 +132,9 @@ export function CacheSection({
|
|||||||
? '每行一个路径前缀,例如 /assets、/static。'
|
? '每行一个路径前缀,例如 /assets、/static。'
|
||||||
: watchedPolicy === 'path_exact'
|
: watchedPolicy === 'path_exact'
|
||||||
? '每行一个精确路径,例如 /robots.txt。'
|
? '每行一个精确路径,例如 /robots.txt。'
|
||||||
: '按 URL 缓存时无需额外规则。';
|
: watchedPolicy === 'static'
|
||||||
|
? '标准静态资源使用内置扩展名列表(不含 HTML),无需填写规则。'
|
||||||
|
: '所有可缓存 GET 无需额外规则(仍会绕过登录态与 Authorization)。';
|
||||||
|
|
||||||
const rulesPlaceholder =
|
const rulesPlaceholder =
|
||||||
watchedPolicy === 'suffix'
|
watchedPolicy === 'suffix'
|
||||||
@@ -112,7 +143,7 @@ export function CacheSection({
|
|||||||
? '/assets\n/static'
|
? '/assets\n/static'
|
||||||
: watchedPolicy === 'path_exact'
|
: watchedPolicy === 'path_exact'
|
||||||
? '/robots.txt\n/manifest.json'
|
? '/robots.txt\n/manifest.json'
|
||||||
: '按 URL 缓存时无需额外规则';
|
: '当前策略无需额外规则';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SectionShell
|
<SectionShell
|
||||||
@@ -131,10 +162,11 @@ export function CacheSection({
|
|||||||
{
|
{
|
||||||
cache_enabled: values.cache_enabled,
|
cache_enabled: values.cache_enabled,
|
||||||
cache_policy: values.cache_enabled
|
cache_policy: values.cache_enabled
|
||||||
? values.cache_policy
|
? normalizeCachePolicyValue(values.cache_policy)
|
||||||
: 'url',
|
: 'static',
|
||||||
cache_rules:
|
cache_rules:
|
||||||
values.cache_enabled && values.cache_policy !== 'url'
|
values.cache_enabled &&
|
||||||
|
needsRulesForPolicy(values.cache_policy)
|
||||||
? rules
|
? rules
|
||||||
: [],
|
: [],
|
||||||
},
|
},
|
||||||
@@ -150,8 +182,8 @@ export function CacheSection({
|
|||||||
<div className='space-y-0.5'>
|
<div className='space-y-0.5'>
|
||||||
<FormLabel>启用站点缓存</FormLabel>
|
<FormLabel>启用站点缓存</FormLabel>
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
系统仍会自动绕过非 GET、带 Authorization 或常见登录态 Cookie
|
开启后默认仅缓存标准静态扩展名(不含 HTML)。仍会自动绕过非
|
||||||
的请求。
|
GET、Authorization 与常见登录态 Cookie。
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
</div>
|
</div>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
@@ -181,12 +213,17 @@ export function CacheSection({
|
|||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value='url'>按 URL 缓存</SelectItem>
|
<SelectItem value='static'>标准静态资源(推荐)</SelectItem>
|
||||||
<SelectItem value='suffix'>按后缀缓存</SelectItem>
|
<SelectItem value='all'>所有可缓存 GET(高级)</SelectItem>
|
||||||
<SelectItem value='path_prefix'>按路径前缀缓存</SelectItem>
|
<SelectItem value='suffix'>自定义后缀</SelectItem>
|
||||||
<SelectItem value='path_exact'>按精确路径缓存</SelectItem>
|
<SelectItem value='path_prefix'>路径前缀</SelectItem>
|
||||||
|
<SelectItem value='path_exact'>精确路径</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
|
<FormDescription>
|
||||||
|
标准静态资源含 css/js/图片/字体/媒体等,默认不缓存 HTML
|
||||||
|
与接口路径。
|
||||||
|
</FormDescription>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
@@ -201,7 +238,7 @@ export function CacheSection({
|
|||||||
<FormControl>
|
<FormControl>
|
||||||
<Textarea
|
<Textarea
|
||||||
className='min-h-32'
|
className='min-h-32'
|
||||||
disabled={!watchedEnabled || watchedPolicy === 'url'}
|
disabled={!watchedEnabled || !needsRules}
|
||||||
placeholder={rulesPlaceholder}
|
placeholder={rulesPlaceholder}
|
||||||
{...field}
|
{...field}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -25,7 +25,9 @@ var proxyHeaderKeyPattern = regexp.MustCompile(`^[A-Za-z0-9_-]+$`)
|
|||||||
var proxyRouteLimitRatePattern = regexp.MustCompile(`^\d+[kKmM]?$`)
|
var proxyRouteLimitRatePattern = regexp.MustCompile(`^\d+[kKmM]?$`)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
proxyRouteCachePolicyURL = "url"
|
proxyRouteCachePolicyStatic = "static"
|
||||||
|
proxyRouteCachePolicyAll = "all"
|
||||||
|
proxyRouteCachePolicyURL = "url" // legacy alias of all
|
||||||
proxyRouteCachePolicySuffix = "suffix"
|
proxyRouteCachePolicySuffix = "suffix"
|
||||||
proxyRouteCachePolicyPathPrefix = "path_prefix"
|
proxyRouteCachePolicyPathPrefix = "path_prefix"
|
||||||
proxyRouteCachePolicyPathExact = "path_exact"
|
proxyRouteCachePolicyPathExact = "path_exact"
|
||||||
@@ -438,11 +440,18 @@ func normalizeCachePolicy(enabled bool, raw string) string {
|
|||||||
if !enabled {
|
if !enabled {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
policy := strings.TrimSpace(raw)
|
policy := strings.TrimSpace(strings.ToLower(raw))
|
||||||
if policy == "" {
|
switch policy {
|
||||||
return proxyRouteCachePolicyURL
|
case "", proxyRouteCachePolicyStatic:
|
||||||
|
return proxyRouteCachePolicyStatic
|
||||||
|
case proxyRouteCachePolicyURL, proxyRouteCachePolicyAll:
|
||||||
|
// Legacy url is normalized to all (full GET allow after security bypass).
|
||||||
|
return proxyRouteCachePolicyAll
|
||||||
|
case proxyRouteCachePolicySuffix, proxyRouteCachePolicyPathPrefix, proxyRouteCachePolicyPathExact:
|
||||||
|
return policy
|
||||||
|
default:
|
||||||
|
return policy
|
||||||
}
|
}
|
||||||
return policy
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func normalizeCacheRules(enabled bool, rawPolicy string, rules []string) ([]string, error) {
|
func normalizeCacheRules(enabled bool, rawPolicy string, rules []string) ([]string, error) {
|
||||||
@@ -451,7 +460,7 @@ func normalizeCacheRules(enabled bool, rawPolicy string, rules []string) ([]stri
|
|||||||
}
|
}
|
||||||
policy := normalizeCachePolicy(enabled, rawPolicy)
|
policy := normalizeCachePolicy(enabled, rawPolicy)
|
||||||
switch policy {
|
switch policy {
|
||||||
case proxyRouteCachePolicyURL:
|
case proxyRouteCachePolicyStatic, proxyRouteCachePolicyAll, proxyRouteCachePolicyURL:
|
||||||
return []string{}, nil
|
return []string{}, nil
|
||||||
case proxyRouteCachePolicySuffix:
|
case proxyRouteCachePolicySuffix:
|
||||||
return normalizeCacheSuffixRules(rules)
|
return normalizeCacheSuffixRules(rules)
|
||||||
|
|||||||
@@ -80,3 +80,23 @@ func TestCreateProxyRouteHTTPSRequiresCoveringCertificate(t *testing.T) {
|
|||||||
_, err := CreateProxyRoute(ctx, Input{SiteName: "api", ZoneDomainIDs: []uint{domain.ID}, OriginURL: "http://origin.example.com:8080", EnableHTTPS: true})
|
_, err := CreateProxyRoute(ctx, Input{SiteName: "api", ZoneDomainIDs: []uint{domain.ID}, OriginURL: "http://origin.example.com:8080", EnableHTTPS: true})
|
||||||
require.EqualError(t, err, errProxyRouteCertRequired)
|
require.EqualError(t, err, errProxyRouteCertRequired)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNormalizeCachePolicyDefaultsAndLegacy(t *testing.T) {
|
||||||
|
assert.Equal(t, "", normalizeCachePolicy(false, "static"))
|
||||||
|
assert.Equal(t, proxyRouteCachePolicyStatic, normalizeCachePolicy(true, ""))
|
||||||
|
assert.Equal(t, proxyRouteCachePolicyStatic, normalizeCachePolicy(true, "static"))
|
||||||
|
assert.Equal(t, proxyRouteCachePolicyAll, normalizeCachePolicy(true, "url"))
|
||||||
|
assert.Equal(t, proxyRouteCachePolicyAll, normalizeCachePolicy(true, "all"))
|
||||||
|
assert.Equal(t, proxyRouteCachePolicySuffix, normalizeCachePolicy(true, "suffix"))
|
||||||
|
|
||||||
|
rules, err := normalizeCacheRules(true, "url", []string{"css"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Empty(t, rules)
|
||||||
|
|
||||||
|
rules, err = normalizeCacheRules(true, "static", nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Empty(t, rules)
|
||||||
|
|
||||||
|
_, err = normalizeCacheRules(true, "suffix", nil)
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
|||||||
@@ -479,15 +479,35 @@ func renderRouteLimitBlock(limitConfig routeLimitConfig) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func renderRouteCachePolicyCondition(cacheConfig routeCacheConfig) string {
|
func renderRouteCachePolicyCondition(cacheConfig routeCacheConfig) string {
|
||||||
switch cacheConfig.Policy {
|
policy := normalizeRenderCachePolicy(cacheConfig.Policy)
|
||||||
|
switch policy {
|
||||||
|
case cachePolicyStatic:
|
||||||
|
return fmt.Sprintf(" if ($uri !~* %s) {\n set $openflare_skip_cache 1;\n }\n", quoteNginxStringLiteral(buildSuffixMatchPattern(DefaultStaticCacheExtensions)))
|
||||||
case cachePolicySuffix:
|
case cachePolicySuffix:
|
||||||
return fmt.Sprintf(" if ($uri !~* %s) {\n set $openflare_skip_cache 1;\n }\n", quoteNginxStringLiteral(buildSuffixMatchPattern(cacheConfig.Rules)))
|
return fmt.Sprintf(" if ($uri !~* %s) {\n set $openflare_skip_cache 1;\n }\n", quoteNginxStringLiteral(buildSuffixMatchPattern(cacheConfig.Rules)))
|
||||||
case cachePolicyPathPrefix:
|
case cachePolicyPathPrefix:
|
||||||
return fmt.Sprintf(" if ($uri !~ %s) {\n set $openflare_skip_cache 1;\n }\n", quoteNginxStringLiteral(buildPathPrefixMatchPattern(cacheConfig.Rules)))
|
return fmt.Sprintf(" if ($uri !~ %s) {\n set $openflare_skip_cache 1;\n }\n", quoteNginxStringLiteral(buildPathPrefixMatchPattern(cacheConfig.Rules)))
|
||||||
case cachePolicyPathExact:
|
case cachePolicyPathExact:
|
||||||
return fmt.Sprintf(" if ($uri !~ %s) {\n set $openflare_skip_cache 1;\n }\n", quoteNginxStringLiteral(buildPathExactMatchPattern(cacheConfig.Rules)))
|
return fmt.Sprintf(" if ($uri !~ %s) {\n set $openflare_skip_cache 1;\n }\n", quoteNginxStringLiteral(buildPathExactMatchPattern(cacheConfig.Rules)))
|
||||||
default:
|
case cachePolicyAll, cachePolicyURL:
|
||||||
return ""
|
return ""
|
||||||
|
default:
|
||||||
|
// Unknown policy: treat as static for safety (do not cache everything).
|
||||||
|
return fmt.Sprintf(" if ($uri !~* %s) {\n set $openflare_skip_cache 1;\n }\n", quoteNginxStringLiteral(buildSuffixMatchPattern(DefaultStaticCacheExtensions)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeRenderCachePolicy(raw string) string {
|
||||||
|
policy := strings.TrimSpace(strings.ToLower(raw))
|
||||||
|
switch policy {
|
||||||
|
case "", cachePolicyStatic:
|
||||||
|
return cachePolicyStatic
|
||||||
|
case cachePolicyURL, cachePolicyAll:
|
||||||
|
return cachePolicyAll
|
||||||
|
case cachePolicySuffix, cachePolicyPathPrefix, cachePolicyPathExact:
|
||||||
|
return policy
|
||||||
|
default:
|
||||||
|
return policy
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -398,3 +398,46 @@ func TestRenderRouteConfigPagesWithSPAFallbackServesRoot(t *testing.T) {
|
|||||||
t.Fatalf("expected spa fallback try_files in location /, got:\n%s", routeConfig)
|
t.Fatalf("expected spa fallback try_files in location /, got:\n%s", routeConfig)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRenderRouteCachePolicyConditionStaticDefault(t *testing.T) {
|
||||||
|
staticBlock := renderRouteCachePolicyCondition(routeCacheConfig{Enabled: true, Policy: "static"})
|
||||||
|
if staticBlock == "" {
|
||||||
|
t.Fatal("static policy should emit a path condition")
|
||||||
|
}
|
||||||
|
if !strings.Contains(staticBlock, "css") || !strings.Contains(staticBlock, "woff2") {
|
||||||
|
t.Fatalf("static policy should include default extensions, got:\n%s", staticBlock)
|
||||||
|
}
|
||||||
|
if strings.Contains(staticBlock, "html") {
|
||||||
|
t.Fatalf("static policy must not include html, got:\n%s", staticBlock)
|
||||||
|
}
|
||||||
|
|
||||||
|
emptyPolicy := renderRouteCachePolicyCondition(routeCacheConfig{Enabled: true, Policy: ""})
|
||||||
|
if emptyPolicy == "" {
|
||||||
|
t.Fatal("empty policy should default to static condition")
|
||||||
|
}
|
||||||
|
|
||||||
|
allBlock := renderRouteCachePolicyCondition(routeCacheConfig{Enabled: true, Policy: "all"})
|
||||||
|
if allBlock != "" {
|
||||||
|
t.Fatalf("all policy should not add path condition, got %q", allBlock)
|
||||||
|
}
|
||||||
|
urlBlock := renderRouteCachePolicyCondition(routeCacheConfig{Enabled: true, Policy: "url"})
|
||||||
|
if urlBlock != "" {
|
||||||
|
t.Fatalf("legacy url policy should map to all, got %q", urlBlock)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRenderRouteCacheBlockIncludesStaticWhenEnabled(t *testing.T) {
|
||||||
|
block := renderRouteCacheBlock(
|
||||||
|
routeCacheConfig{Enabled: true, Policy: "static"},
|
||||||
|
ConfigSnapshot{CacheEnabled: true},
|
||||||
|
)
|
||||||
|
if !strings.Contains(block, "proxy_cache openflare_cache") {
|
||||||
|
t.Fatalf("expected proxy_cache, got:\n%s", block)
|
||||||
|
}
|
||||||
|
if !strings.Contains(block, "\\.(?:") {
|
||||||
|
t.Fatalf("expected static suffix pattern, got:\n%s", block)
|
||||||
|
}
|
||||||
|
if !strings.Contains(block, "request_method != GET") {
|
||||||
|
t.Fatalf("expected security bypass for non-GET, got:\n%s", block)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
cachePolicyStatic = "static"
|
||||||
|
cachePolicyAll = "all"
|
||||||
|
cachePolicyURL = "url" // legacy alias of all
|
||||||
cachePolicySuffix = "suffix"
|
cachePolicySuffix = "suffix"
|
||||||
cachePolicyPathPrefix = "path_prefix"
|
cachePolicyPathPrefix = "path_prefix"
|
||||||
cachePolicyPathExact = "path_exact"
|
cachePolicyPathExact = "path_exact"
|
||||||
@@ -33,6 +36,17 @@ const (
|
|||||||
anubisAPIPrefix = "/.within.website/x/cmd/anubis/api/"
|
anubisAPIPrefix = "/.within.website/x/cmd/anubis/api/"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// DefaultStaticCacheExtensions is the built-in suffix allowlist for cache_policy=static.
|
||||||
|
// HTML is intentionally excluded (Cloudflare-like default).
|
||||||
|
var DefaultStaticCacheExtensions = []string{
|
||||||
|
"css", "js", "mjs", "map", "json",
|
||||||
|
"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",
|
||||||
|
}
|
||||||
|
|
||||||
// OpenFlareRuntimeUser is the dedicated service account shared by the agent
|
// OpenFlareRuntimeUser is the dedicated service account shared by the agent
|
||||||
// process and OpenResty worker processes.
|
// process and OpenResty worker processes.
|
||||||
const OpenFlareRuntimeUser = "openflare"
|
const OpenFlareRuntimeUser = "openflare"
|
||||||
|
|||||||
Reference in New Issue
Block a user