[优化] 自定义不存在页面返回状态码

This commit is contained in:
ryan
2026-06-02 08:14:15 +08:00
parent d21643feed
commit 3825a7f29a
10 changed files with 196 additions and 110 deletions
+1
View File
@@ -59,6 +59,7 @@ var DatabaseAutoCleanupRetentionDays = 30
// V5 OpenResty performance settings (hot-reloadable via Option table) // V5 OpenResty performance settings (hot-reloadable via Option table)
var OpenRestyDefaultServerReturnStatus = 421
var OpenRestyWorkerProcesses = "auto" var OpenRestyWorkerProcesses = "auto"
var OpenRestyWorkerConnections = 4096 var OpenRestyWorkerConnections = 4096
var OpenRestyWorkerRlimitNofile = 65535 var OpenRestyWorkerRlimitNofile = 65535
+9
View File
@@ -104,6 +104,15 @@ func validateOpenRestyOption(key string, value string) error {
trimmed := strings.TrimSpace(value) trimmed := strings.TrimSpace(value)
switch key { switch key {
case "OpenRestyDefaultServerReturnStatus":
if err := validatePositiveIntegerOption(key, trimmed); err != nil {
return err
}
statusCode, _ := strconv.Atoi(trimmed)
if statusCode < 100 || statusCode > 999 {
return fmt.Errorf("%s 必须在 100 到 999 之间", key)
}
return nil
case "OpenRestyWorkerProcesses": case "OpenRestyWorkerProcesses":
if trimmed == "auto" { if trimmed == "auto" {
return nil return nil
@@ -9,6 +9,11 @@ func TestValidateOpenRestyOption(t *testing.T) {
value string value string
wantErr bool wantErr bool
}{ }{
{name: "default server status valid 421", key: "OpenRestyDefaultServerReturnStatus", value: "421"},
{name: "default server status valid 200", key: "OpenRestyDefaultServerReturnStatus", value: "200"},
{name: "default server status invalid 99", key: "OpenRestyDefaultServerReturnStatus", value: "99", wantErr: true},
{name: "default server status invalid 1000", key: "OpenRestyDefaultServerReturnStatus", value: "1000", wantErr: true},
{name: "default server status invalid abc", key: "OpenRestyDefaultServerReturnStatus", value: "abc", wantErr: true},
{name: "worker processes auto", key: "OpenRestyWorkerProcesses", value: "auto"}, {name: "worker processes auto", key: "OpenRestyWorkerProcesses", value: "auto"},
{name: "worker processes number", key: "OpenRestyWorkerProcesses", value: "8"}, {name: "worker processes number", key: "OpenRestyWorkerProcesses", value: "8"},
{name: "worker processes invalid", key: "OpenRestyWorkerProcesses", value: "0", wantErr: true}, {name: "worker processes invalid", key: "OpenRestyWorkerProcesses", value: "0", wantErr: true},
+5
View File
@@ -53,6 +53,7 @@ func InitOptionMap() {
common.OptionMap["GeoIPProvider"] = common.GeoIPProvider common.OptionMap["GeoIPProvider"] = common.GeoIPProvider
common.OptionMap["DatabaseAutoCleanupEnabled"] = strconv.FormatBool(common.DatabaseAutoCleanupEnabled) common.OptionMap["DatabaseAutoCleanupEnabled"] = strconv.FormatBool(common.DatabaseAutoCleanupEnabled)
common.OptionMap["DatabaseAutoCleanupRetentionDays"] = strconv.Itoa(common.DatabaseAutoCleanupRetentionDays) common.OptionMap["DatabaseAutoCleanupRetentionDays"] = strconv.Itoa(common.DatabaseAutoCleanupRetentionDays)
common.OptionMap["OpenRestyDefaultServerReturnStatus"] = strconv.Itoa(common.OpenRestyDefaultServerReturnStatus)
common.OptionMap["OpenRestyWorkerProcesses"] = common.OpenRestyWorkerProcesses common.OptionMap["OpenRestyWorkerProcesses"] = common.OpenRestyWorkerProcesses
common.OptionMap["OpenRestyWorkerConnections"] = strconv.Itoa(common.OpenRestyWorkerConnections) common.OptionMap["OpenRestyWorkerConnections"] = strconv.Itoa(common.OpenRestyWorkerConnections)
common.OptionMap["OpenRestyWorkerRlimitNofile"] = strconv.Itoa(common.OpenRestyWorkerRlimitNofile) common.OptionMap["OpenRestyWorkerRlimitNofile"] = strconv.Itoa(common.OpenRestyWorkerRlimitNofile)
@@ -213,6 +214,10 @@ func updateOptionMap(key string, value string) {
if v, err := strconv.Atoi(value); err == nil && v >= 1 { if v, err := strconv.Atoi(value); err == nil && v >= 1 {
common.DatabaseAutoCleanupRetentionDays = v common.DatabaseAutoCleanupRetentionDays = v
} }
case "OpenRestyDefaultServerReturnStatus":
if v, err := strconv.Atoi(value); err == nil && v >= 100 && v <= 999 {
common.OpenRestyDefaultServerReturnStatus = v
}
case "OpenRestyWorkerProcesses": case "OpenRestyWorkerProcesses":
if strings.TrimSpace(value) != "" { if strings.TrimSpace(value) != "" {
common.OpenRestyWorkerProcesses = value common.OpenRestyWorkerProcesses = value
+74 -70
View File
@@ -135,41 +135,42 @@ type snapshotWAFDocument struct {
} }
type openRestyConfigSnapshot struct { type openRestyConfigSnapshot struct {
WorkerProcesses string `json:"worker_processes"` DefaultServerReturnStatus int `json:"default_server_return_status"`
WorkerConnections int `json:"worker_connections"` WorkerProcesses string `json:"worker_processes"`
WorkerRlimitNofile int `json:"worker_rlimit_nofile"` WorkerConnections int `json:"worker_connections"`
EventsUse string `json:"events_use,omitempty"` WorkerRlimitNofile int `json:"worker_rlimit_nofile"`
EventsMultiAcceptEnabled bool `json:"events_multi_accept_enabled"` EventsUse string `json:"events_use,omitempty"`
KeepaliveTimeout int `json:"keepalive_timeout"` EventsMultiAcceptEnabled bool `json:"events_multi_accept_enabled"`
KeepaliveRequests int `json:"keepalive_requests"` KeepaliveTimeout int `json:"keepalive_timeout"`
ClientHeaderTimeout int `json:"client_header_timeout"` KeepaliveRequests int `json:"keepalive_requests"`
ClientBodyTimeout int `json:"client_body_timeout"` ClientHeaderTimeout int `json:"client_header_timeout"`
ClientMaxBodySize string `json:"client_max_body_size"` ClientBodyTimeout int `json:"client_body_timeout"`
LargeClientHeaderBuffers string `json:"large_client_header_buffers"` ClientMaxBodySize string `json:"client_max_body_size"`
SendTimeout int `json:"send_timeout"` LargeClientHeaderBuffers string `json:"large_client_header_buffers"`
ProxyConnectTimeout int `json:"proxy_connect_timeout"` SendTimeout int `json:"send_timeout"`
ProxySendTimeout int `json:"proxy_send_timeout"` ProxyConnectTimeout int `json:"proxy_connect_timeout"`
ProxyReadTimeout int `json:"proxy_read_timeout"` ProxySendTimeout int `json:"proxy_send_timeout"`
WebsocketEnabled bool `json:"websocket_enabled"` ProxyReadTimeout int `json:"proxy_read_timeout"`
ProxyRequestBuffering bool `json:"proxy_request_buffering"` WebsocketEnabled bool `json:"websocket_enabled"`
ProxyBufferingEnabled bool `json:"proxy_buffering_enabled"` ProxyRequestBuffering bool `json:"proxy_request_buffering"`
ProxyBuffers string `json:"proxy_buffers"` ProxyBufferingEnabled bool `json:"proxy_buffering_enabled"`
ProxyBufferSize string `json:"proxy_buffer_size"` ProxyBuffers string `json:"proxy_buffers"`
ProxyBusyBuffersSize string `json:"proxy_busy_buffers_size"` ProxyBufferSize string `json:"proxy_buffer_size"`
GzipEnabled bool `json:"gzip_enabled"` ProxyBusyBuffersSize string `json:"proxy_busy_buffers_size"`
GzipMinLength int `json:"gzip_min_length"` GzipEnabled bool `json:"gzip_enabled"`
GzipCompLevel int `json:"gzip_comp_level"` GzipMinLength int `json:"gzip_min_length"`
Resolvers string `json:"resolvers,omitempty"` GzipCompLevel int `json:"gzip_comp_level"`
CacheEnabled bool `json:"cache_enabled"` Resolvers string `json:"resolvers,omitempty"`
CachePath string `json:"cache_path,omitempty"` CacheEnabled bool `json:"cache_enabled"`
CacheLevels string `json:"cache_levels"` CachePath string `json:"cache_path,omitempty"`
CacheInactive string `json:"cache_inactive"` CacheLevels string `json:"cache_levels"`
CacheMaxSize string `json:"cache_max_size"` CacheInactive string `json:"cache_inactive"`
CacheKeyTemplate string `json:"cache_key_template"` CacheMaxSize string `json:"cache_max_size"`
CacheLockEnabled bool `json:"cache_lock_enabled"` CacheKeyTemplate string `json:"cache_key_template"`
CacheLockTimeout string `json:"cache_lock_timeout"` CacheLockEnabled bool `json:"cache_lock_enabled"`
CacheUseStale string `json:"cache_use_stale"` CacheLockTimeout string `json:"cache_lock_timeout"`
MainConfigTemplate string `json:"main_config_template,omitempty"` CacheUseStale string `json:"cache_use_stale"`
MainConfigTemplate string `json:"main_config_template,omitempty"`
} }
type snapshotDocument struct { type snapshotDocument struct {
@@ -934,41 +935,42 @@ func stringSliceEqual(left []string, right []string) bool {
func buildOpenRestyConfigSnapshot() openRestyConfigSnapshot { func buildOpenRestyConfigSnapshot() openRestyConfigSnapshot {
return openRestyConfigSnapshot{ return openRestyConfigSnapshot{
WorkerProcesses: common.OpenRestyWorkerProcesses, DefaultServerReturnStatus: common.OpenRestyDefaultServerReturnStatus,
WorkerConnections: common.OpenRestyWorkerConnections, WorkerProcesses: common.OpenRestyWorkerProcesses,
WorkerRlimitNofile: common.OpenRestyWorkerRlimitNofile, WorkerConnections: common.OpenRestyWorkerConnections,
EventsUse: common.OpenRestyEventsUse, WorkerRlimitNofile: common.OpenRestyWorkerRlimitNofile,
EventsMultiAcceptEnabled: common.OpenRestyEventsMultiAcceptEnabled, EventsUse: common.OpenRestyEventsUse,
KeepaliveTimeout: common.OpenRestyKeepaliveTimeout, EventsMultiAcceptEnabled: common.OpenRestyEventsMultiAcceptEnabled,
KeepaliveRequests: common.OpenRestyKeepaliveRequests, KeepaliveTimeout: common.OpenRestyKeepaliveTimeout,
ClientHeaderTimeout: common.OpenRestyClientHeaderTimeout, KeepaliveRequests: common.OpenRestyKeepaliveRequests,
ClientBodyTimeout: common.OpenRestyClientBodyTimeout, ClientHeaderTimeout: common.OpenRestyClientHeaderTimeout,
ClientMaxBodySize: common.OpenRestyClientMaxBodySize, ClientBodyTimeout: common.OpenRestyClientBodyTimeout,
LargeClientHeaderBuffers: common.OpenRestyLargeClientHeaderBuffers, ClientMaxBodySize: common.OpenRestyClientMaxBodySize,
SendTimeout: common.OpenRestySendTimeout, LargeClientHeaderBuffers: common.OpenRestyLargeClientHeaderBuffers,
ProxyConnectTimeout: common.OpenRestyProxyConnectTimeout, SendTimeout: common.OpenRestySendTimeout,
ProxySendTimeout: common.OpenRestyProxySendTimeout, ProxyConnectTimeout: common.OpenRestyProxyConnectTimeout,
ProxyReadTimeout: common.OpenRestyProxyReadTimeout, ProxySendTimeout: common.OpenRestyProxySendTimeout,
WebsocketEnabled: common.OpenRestyWebsocketEnabled, ProxyReadTimeout: common.OpenRestyProxyReadTimeout,
ProxyRequestBuffering: common.OpenRestyProxyRequestBufferingEnabled, WebsocketEnabled: common.OpenRestyWebsocketEnabled,
ProxyBufferingEnabled: common.OpenRestyProxyBufferingEnabled, ProxyRequestBuffering: common.OpenRestyProxyRequestBufferingEnabled,
ProxyBuffers: common.OpenRestyProxyBuffers, ProxyBufferingEnabled: common.OpenRestyProxyBufferingEnabled,
ProxyBufferSize: common.OpenRestyProxyBufferSize, ProxyBuffers: common.OpenRestyProxyBuffers,
ProxyBusyBuffersSize: common.OpenRestyProxyBusyBuffersSize, ProxyBufferSize: common.OpenRestyProxyBufferSize,
GzipEnabled: common.OpenRestyGzipEnabled, ProxyBusyBuffersSize: common.OpenRestyProxyBusyBuffersSize,
GzipMinLength: common.OpenRestyGzipMinLength, GzipEnabled: common.OpenRestyGzipEnabled,
GzipCompLevel: common.OpenRestyGzipCompLevel, GzipMinLength: common.OpenRestyGzipMinLength,
Resolvers: common.OpenRestyResolvers, GzipCompLevel: common.OpenRestyGzipCompLevel,
CacheEnabled: common.OpenRestyCacheEnabled, Resolvers: common.OpenRestyResolvers,
CachePath: common.OpenRestyCachePath, CacheEnabled: common.OpenRestyCacheEnabled,
CacheLevels: common.OpenRestyCacheLevels, CachePath: common.OpenRestyCachePath,
CacheInactive: common.OpenRestyCacheInactive, CacheLevels: common.OpenRestyCacheLevels,
CacheMaxSize: common.OpenRestyCacheMaxSize, CacheInactive: common.OpenRestyCacheInactive,
CacheKeyTemplate: common.OpenRestyCacheKeyTemplate, CacheMaxSize: common.OpenRestyCacheMaxSize,
CacheLockEnabled: common.OpenRestyCacheLockEnabled, CacheKeyTemplate: common.OpenRestyCacheKeyTemplate,
CacheLockTimeout: common.OpenRestyCacheLockTimeout, CacheLockEnabled: common.OpenRestyCacheLockEnabled,
CacheUseStale: common.OpenRestyCacheUseStale, CacheLockTimeout: common.OpenRestyCacheLockTimeout,
MainConfigTemplate: common.OpenRestyMainConfigTemplate, CacheUseStale: common.OpenRestyCacheUseStale,
MainConfigTemplate: common.OpenRestyMainConfigTemplate,
} }
} }
@@ -1052,6 +1054,7 @@ func diffOpenRestyOptionDetails(left openRestyConfigSnapshot, right openRestyCon
CurrentValue: current, CurrentValue: current,
}) })
} }
appendIfChanged("OpenRestyDefaultServerReturnStatus", fmt.Sprintf("%d", left.DefaultServerReturnStatus), fmt.Sprintf("%d", right.DefaultServerReturnStatus))
appendIfChanged("OpenRestyWorkerProcesses", left.WorkerProcesses, right.WorkerProcesses) appendIfChanged("OpenRestyWorkerProcesses", left.WorkerProcesses, right.WorkerProcesses)
appendIfChanged("OpenRestyWorkerConnections", fmt.Sprintf("%d", left.WorkerConnections), fmt.Sprintf("%d", right.WorkerConnections)) appendIfChanged("OpenRestyWorkerConnections", fmt.Sprintf("%d", left.WorkerConnections), fmt.Sprintf("%d", right.WorkerConnections))
appendIfChanged("OpenRestyWorkerRlimitNofile", fmt.Sprintf("%d", left.WorkerRlimitNofile), fmt.Sprintf("%d", right.WorkerRlimitNofile)) appendIfChanged("OpenRestyWorkerRlimitNofile", fmt.Sprintf("%d", left.WorkerRlimitNofile), fmt.Sprintf("%d", right.WorkerRlimitNofile))
@@ -1099,6 +1102,7 @@ func extractOptionDiffKeys(details []ConfigOptionDiffItem) []string {
func openRestyOptionKeys() []string { func openRestyOptionKeys() []string {
return []string{ return []string{
"OpenRestyDefaultServerReturnStatus",
"OpenRestyWorkerProcesses", "OpenRestyWorkerProcesses",
"OpenRestyWorkerConnections", "OpenRestyWorkerConnections",
"OpenRestyWorkerRlimitNofile", "OpenRestyWorkerRlimitNofile",
@@ -1193,8 +1193,8 @@ func TestRenderConfigUsesDefaultServerFallback(t *testing.T) {
if !strings.Contains(preview.MainConfig, "server_name _;") { if !strings.Contains(preview.MainConfig, "server_name _;") {
t.Fatal("expected preview main config to include default server_name") t.Fatal("expected preview main config to include default server_name")
} }
if !strings.Contains(preview.MainConfig, "return 404;") { if !strings.Contains(preview.MainConfig, "return 421;") {
t.Fatal("expected preview main config to return 404 for unmatched hosts") t.Fatal("expected preview main config to return 421 for unmatched hosts")
} }
if !strings.Contains(preview.MainConfig, "ssl_reject_handshake on;") { if !strings.Contains(preview.MainConfig, "ssl_reject_handshake on;") {
t.Fatal("expected preview main config to reject unmatched https handshakes") t.Fatal("expected preview main config to reject unmatched https handshakes")
@@ -305,7 +305,7 @@ func renderMainConfigTemplate(templateText string, cfg ConfigSnapshot) string {
"{{OpenRestyWorkerConnections}}", fmt.Sprintf("%d", cfg.WorkerConnections), "{{OpenRestyWorkerConnections}}", fmt.Sprintf("%d", cfg.WorkerConnections),
"{{OpenRestyWorkerRlimitNofile}}", fmt.Sprintf("%d", cfg.WorkerRlimitNofile), "{{OpenRestyWorkerRlimitNofile}}", fmt.Sprintf("%d", cfg.WorkerRlimitNofile),
"{{OpenRestyConnectionUpgradeMap}}", renderConnectionUpgradeMap(), "{{OpenRestyConnectionUpgradeMap}}", renderConnectionUpgradeMap(),
"{{OpenRestyDefaultServerBlock}}", renderDefaultServerBlock(), "{{OpenRestyDefaultServerBlock}}", renderDefaultServerBlock(cfg.DefaultServerReturnStatus),
"{{OpenRestyAccessLogPath}}", AccessLogPlaceholder, "{{OpenRestyAccessLogPath}}", AccessLogPlaceholder,
"{{OpenRestyErrorLogPath}}", ErrorLogPlaceholder, "{{OpenRestyErrorLogPath}}", ErrorLogPlaceholder,
"{{OpenRestyEventsUseDirective}}", renderTemplateDirective(cfg.EventsUse != "", fmt.Sprintf("use %s;", cfg.EventsUse)), "{{OpenRestyEventsUseDirective}}", renderTemplateDirective(cfg.EventsUse != "", fmt.Sprintf("use %s;", cfg.EventsUse)),
@@ -586,8 +586,26 @@ func renderConnectionUpgradeMap() string {
return " map $http_upgrade $connection_upgrade {\n default upgrade;\n '' \"\";\n }\n\n" return " map $http_upgrade $connection_upgrade {\n default upgrade;\n '' \"\";\n }\n\n"
} }
func renderDefaultServerBlock() string { func renderDefaultServerBlock(statusCode int) string {
return strings.Join([]string{" server {", " listen 80 default_server;", " server_name _;", "", " return 404;", " }", "", " server {", " listen 443 ssl default_server;", " server_name _;", "", " ssl_reject_handshake on;", " }", ""}, "\n") if statusCode <= 0 {
statusCode = 421
}
return strings.Join([]string{
" server {",
" listen 80 default_server;",
" server_name _;",
"",
fmt.Sprintf(" return %d;", statusCode),
" }",
"",
" server {",
" listen 443 ssl default_server;",
" server_name _;",
"",
" ssl_reject_handshake on;",
" }",
"",
}, "\n")
} }
func normalizedRouteDomains(route Route) []string { func normalizedRouteDomains(route Route) []string {
@@ -158,41 +158,42 @@ type WAFDocument struct {
} }
type ConfigSnapshot struct { type ConfigSnapshot struct {
WorkerProcesses string `json:"worker_processes"` DefaultServerReturnStatus int `json:"default_server_return_status"`
WorkerConnections int `json:"worker_connections"` WorkerProcesses string `json:"worker_processes"`
WorkerRlimitNofile int `json:"worker_rlimit_nofile"` WorkerConnections int `json:"worker_connections"`
EventsUse string `json:"events_use,omitempty"` WorkerRlimitNofile int `json:"worker_rlimit_nofile"`
EventsMultiAcceptEnabled bool `json:"events_multi_accept_enabled"` EventsUse string `json:"events_use,omitempty"`
KeepaliveTimeout int `json:"keepalive_timeout"` EventsMultiAcceptEnabled bool `json:"events_multi_accept_enabled"`
KeepaliveRequests int `json:"keepalive_requests"` KeepaliveTimeout int `json:"keepalive_timeout"`
ClientHeaderTimeout int `json:"client_header_timeout"` KeepaliveRequests int `json:"keepalive_requests"`
ClientBodyTimeout int `json:"client_body_timeout"` ClientHeaderTimeout int `json:"client_header_timeout"`
ClientMaxBodySize string `json:"client_max_body_size"` ClientBodyTimeout int `json:"client_body_timeout"`
LargeClientHeaderBuffers string `json:"large_client_header_buffers"` ClientMaxBodySize string `json:"client_max_body_size"`
SendTimeout int `json:"send_timeout"` LargeClientHeaderBuffers string `json:"large_client_header_buffers"`
ProxyConnectTimeout int `json:"proxy_connect_timeout"` SendTimeout int `json:"send_timeout"`
ProxySendTimeout int `json:"proxy_send_timeout"` ProxyConnectTimeout int `json:"proxy_connect_timeout"`
ProxyReadTimeout int `json:"proxy_read_timeout"` ProxySendTimeout int `json:"proxy_send_timeout"`
WebsocketEnabled bool `json:"websocket_enabled"` ProxyReadTimeout int `json:"proxy_read_timeout"`
ProxyRequestBuffering bool `json:"proxy_request_buffering"` WebsocketEnabled bool `json:"websocket_enabled"`
ProxyBufferingEnabled bool `json:"proxy_buffering_enabled"` ProxyRequestBuffering bool `json:"proxy_request_buffering"`
ProxyBuffers string `json:"proxy_buffers"` ProxyBufferingEnabled bool `json:"proxy_buffering_enabled"`
ProxyBufferSize string `json:"proxy_buffer_size"` ProxyBuffers string `json:"proxy_buffers"`
ProxyBusyBuffersSize string `json:"proxy_busy_buffers_size"` ProxyBufferSize string `json:"proxy_buffer_size"`
GzipEnabled bool `json:"gzip_enabled"` ProxyBusyBuffersSize string `json:"proxy_busy_buffers_size"`
GzipMinLength int `json:"gzip_min_length"` GzipEnabled bool `json:"gzip_enabled"`
GzipCompLevel int `json:"gzip_comp_level"` GzipMinLength int `json:"gzip_min_length"`
Resolvers string `json:"resolvers,omitempty"` GzipCompLevel int `json:"gzip_comp_level"`
CacheEnabled bool `json:"cache_enabled"` Resolvers string `json:"resolvers,omitempty"`
CachePath string `json:"cache_path,omitempty"` CacheEnabled bool `json:"cache_enabled"`
CacheLevels string `json:"cache_levels"` CachePath string `json:"cache_path,omitempty"`
CacheInactive string `json:"cache_inactive"` CacheLevels string `json:"cache_levels"`
CacheMaxSize string `json:"cache_max_size"` CacheInactive string `json:"cache_inactive"`
CacheKeyTemplate string `json:"cache_key_template"` CacheMaxSize string `json:"cache_max_size"`
CacheLockEnabled bool `json:"cache_lock_enabled"` CacheKeyTemplate string `json:"cache_key_template"`
CacheLockTimeout string `json:"cache_lock_timeout"` CacheLockEnabled bool `json:"cache_lock_enabled"`
CacheUseStale string `json:"cache_use_stale"` CacheLockTimeout string `json:"cache_lock_timeout"`
MainConfigTemplate string `json:"main_config_template,omitempty"` CacheUseStale string `json:"cache_use_stale"`
MainConfigTemplate string `json:"main_config_template,omitempty"`
} }
type Document struct { type Document struct {
@@ -31,6 +31,7 @@ const templatePanelClassName =
'h-[560px] w-full box-border overflow-auto rounded-2xl border border-[var(--border-default)] bg-[var(--surface-elevated)] px-4 py-3 font-mono text-xs leading-6'; 'h-[560px] w-full box-border overflow-auto rounded-2xl border border-[var(--border-default)] bg-[var(--surface-elevated)] px-4 py-3 font-mono text-xs leading-6';
const defaultPerformanceFields = { const defaultPerformanceFields = {
OpenRestyDefaultServerReturnStatus: '421',
OpenRestyWorkerProcesses: 'auto', OpenRestyWorkerProcesses: 'auto',
OpenRestyWorkerConnections: '4096', OpenRestyWorkerConnections: '4096',
OpenRestyWorkerRlimitNofile: '65535', OpenRestyWorkerRlimitNofile: '65535',
@@ -69,6 +70,8 @@ const defaultPerformanceFields = {
}; };
const performanceFieldTooltips: Record<string, string> = { const performanceFieldTooltips: Record<string, string> = {
default_server_return_status:
'当客户端请求未匹配任何已配置的网站域名时,默认服务器返回的 HTTP 状态码。通常保持 421 拒绝访问。',
worker_processes: worker_processes:
'Nginx worker 进程数。通常建议保持 auto,让 OpenResty 按 CPU 核数自动分配。', 'Nginx worker 进程数。通常建议保持 auto,让 OpenResty 按 CPU 核数自动分配。',
worker_connections: worker_connections:
@@ -201,6 +204,7 @@ export function PerformancePage() {
const optionMap = optionsToMap(optionsQuery.data); const optionMap = optionsToMap(optionsQuery.data);
setPerformanceFields({ setPerformanceFields({
OpenRestyDefaultServerReturnStatus: optionMap.OpenRestyDefaultServerReturnStatus ?? '421',
OpenRestyWorkerProcesses: optionMap.OpenRestyWorkerProcesses ?? 'auto', OpenRestyWorkerProcesses: optionMap.OpenRestyWorkerProcesses ?? 'auto',
OpenRestyWorkerConnections: OpenRestyWorkerConnections:
optionMap.OpenRestyWorkerConnections ?? '4096', optionMap.OpenRestyWorkerConnections ?? '4096',
@@ -301,6 +305,10 @@ export function PerformancePage() {
} }
const integerFields = [ const integerFields = [
[
'default_server_return_status',
performanceFields.OpenRestyDefaultServerReturnStatus,
],
['worker_connections', performanceFields.OpenRestyWorkerConnections], ['worker_connections', performanceFields.OpenRestyWorkerConnections],
['worker_rlimit_nofile', performanceFields.OpenRestyWorkerRlimitNofile], ['worker_rlimit_nofile', performanceFields.OpenRestyWorkerRlimitNofile],
['keepalive_timeout', performanceFields.OpenRestyKeepaliveTimeout], ['keepalive_timeout', performanceFields.OpenRestyKeepaliveTimeout],
@@ -323,6 +331,17 @@ export function PerformancePage() {
'client_max_body_size 必须为整数或带 k/m/g 单位的大小值。', 'client_max_body_size 必须为整数或带 k/m/g 单位的大小值。',
); );
} }
const defaultServerReturnStatusVal = Number.parseInt(
performanceFields.OpenRestyDefaultServerReturnStatus,
10,
);
if (
Number.isNaN(defaultServerReturnStatusVal) ||
defaultServerReturnStatusVal < 100 ||
defaultServerReturnStatusVal > 999
) {
throw new Error('空白页面返回状态码必须在 100 到 999 之间。');
}
if ( if (
!isProxyBuffersValue( !isProxyBuffersValue(
performanceFields.OpenRestyLargeClientHeaderBuffers, performanceFields.OpenRestyLargeClientHeaderBuffers,
@@ -333,6 +352,10 @@ export function PerformancePage() {
await saveOptionEntries( await saveOptionEntries(
[ [
[
'OpenRestyDefaultServerReturnStatus',
performanceFields.OpenRestyDefaultServerReturnStatus.trim(),
],
[ [
'OpenRestyWorkerProcesses', 'OpenRestyWorkerProcesses',
performanceFields.OpenRestyWorkerProcesses.trim(), performanceFields.OpenRestyWorkerProcesses.trim(),
@@ -893,6 +916,24 @@ export function PerformancePage() {
} }
/> />
</ResourceField> </ResourceField>
<ResourceField
label="空白页面返回状态码"
hint="默认 421,当请求未匹配任何已有反代网站时,默认服务器返回的状态码。"
tooltip={performanceFieldTooltips.default_server_return_status}
>
<ResourceInput
type="number"
min="100"
max="999"
value={performanceFields.OpenRestyDefaultServerReturnStatus}
onChange={(event) =>
setPerformanceFields((previous) => ({
...previous,
OpenRestyDefaultServerReturnStatus: event.target.value,
}))
}
/>
</ResourceField>
</div> </div>
</AppCard> </AppCard>
@@ -87,6 +87,7 @@ const defaultOperationFields = {
NodeOfflineThreshold: '120000', NodeOfflineThreshold: '120000',
AgentUpdateRepo: 'Rain-kl/OpenFlare', AgentUpdateRepo: 'Rain-kl/OpenFlare',
GeoIPProvider: 'ipinfo', GeoIPProvider: 'ipinfo',
OpenRestyDefaultServerReturnStatus: '421',
OpenRestyWorkerProcesses: 'auto', OpenRestyWorkerProcesses: 'auto',
OpenRestyWorkerConnections: '4096', OpenRestyWorkerConnections: '4096',
OpenRestyWorkerRlimitNofile: '65535', OpenRestyWorkerRlimitNofile: '65535',
@@ -373,6 +374,7 @@ export function SettingsPage() {
NodeOfflineThreshold: optionMap.NodeOfflineThreshold ?? '120000', NodeOfflineThreshold: optionMap.NodeOfflineThreshold ?? '120000',
AgentUpdateRepo: optionMap.AgentUpdateRepo ?? 'Rain-kl/OpenFlare', AgentUpdateRepo: optionMap.AgentUpdateRepo ?? 'Rain-kl/OpenFlare',
GeoIPProvider: optionMap.GeoIPProvider ?? 'ipinfo', GeoIPProvider: optionMap.GeoIPProvider ?? 'ipinfo',
OpenRestyDefaultServerReturnStatus: optionMap.OpenRestyDefaultServerReturnStatus ?? '421',
OpenRestyWorkerProcesses: optionMap.OpenRestyWorkerProcesses ?? 'auto', OpenRestyWorkerProcesses: optionMap.OpenRestyWorkerProcesses ?? 'auto',
OpenRestyWorkerConnections: OpenRestyWorkerConnections:
optionMap.OpenRestyWorkerConnections ?? '4096', optionMap.OpenRestyWorkerConnections ?? '4096',