diff --git a/VERSION b/VERSION
index 8a77c33a..0f0e2628 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.4.x
\ No newline at end of file
+0.5.x
diff --git a/atsf_server/common/constants.go b/atsf_server/common/constants.go
index db4bf8bd..1046fcd0 100644
--- a/atsf_server/common/constants.go
+++ b/atsf_server/common/constants.go
@@ -55,6 +55,37 @@ var AgentHeartbeatInterval = 30000 // milliseconds
var AgentSyncInterval = 30000 // milliseconds
var AgentUpdateRepo = "Rain-kl/ATSFlare"
+// V5 OpenResty performance settings (hot-reloadable via Option table)
+var OpenRestyWorkerProcesses = "auto"
+var OpenRestyWorkerConnections = 4096
+var OpenRestyWorkerRlimitNofile = 65535
+var OpenRestyEventsUse = ""
+var OpenRestyEventsMultiAcceptEnabled = false
+var OpenRestyKeepaliveTimeout = 65
+var OpenRestyKeepaliveRequests = 1000
+var OpenRestyClientHeaderTimeout = 15
+var OpenRestyClientBodyTimeout = 15
+var OpenRestySendTimeout = 30
+var OpenRestyProxyConnectTimeout = 5
+var OpenRestyProxySendTimeout = 60
+var OpenRestyProxyReadTimeout = 60
+var OpenRestyProxyBufferingEnabled = true
+var OpenRestyProxyBuffers = "16 16k"
+var OpenRestyProxyBufferSize = "8k"
+var OpenRestyProxyBusyBuffersSize = "64k"
+var OpenRestyGzipEnabled = true
+var OpenRestyGzipMinLength = 1024
+var OpenRestyGzipCompLevel = 5
+var OpenRestyCacheEnabled = false
+var OpenRestyCachePath = ""
+var OpenRestyCacheLevels = "1:2"
+var OpenRestyCacheInactive = "30m"
+var OpenRestyCacheMaxSize = "1g"
+var OpenRestyCacheKeyTemplate = "$scheme$proxy_host$request_uri"
+var OpenRestyCacheLockEnabled = true
+var OpenRestyCacheLockTimeout = "5s"
+var OpenRestyCacheUseStale = "error timeout updating http_500 http_502 http_503 http_504"
+
const (
RoleGuestUser = 0
RoleCommonUser = 1
diff --git a/atsf_server/controller/option.go b/atsf_server/controller/option.go
index 1d7638b7..6e493d1b 100644
--- a/atsf_server/controller/option.go
+++ b/atsf_server/controller/option.go
@@ -7,10 +7,18 @@ import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
+ "regexp"
"strconv"
"strings"
)
+var (
+ openRestySizePattern = regexp.MustCompile(`^\d+[kKmMgG]?$`)
+ openRestyProxyBuffersPattern = regexp.MustCompile(`^\d+\s+\d+[kKmMgG]?$`)
+ openRestyCacheLevelsPattern = regexp.MustCompile(`^\d{1,2}(?::\d{1,2}){0,2}$`)
+ openRestyDurationTokenPattern = regexp.MustCompile(`^\d+[smhdwSMHDW]$`)
+)
+
func validateRateLimitOption(key string, value string) error {
maxDurationSeconds := int(common.RateLimitKeyExpirationDuration.Seconds())
@@ -35,6 +43,122 @@ func validateRateLimitOption(key string, value string) error {
}
}
+func validatePositiveIntegerOption(key string, value string) error {
+ intValue, err := strconv.Atoi(value)
+ if err != nil || intValue <= 0 {
+ return fmt.Errorf("%s 必须为大于 0 的整数", key)
+ }
+ return nil
+}
+
+func validateBooleanOption(key string, value string) error {
+ switch value {
+ case "true", "false":
+ return nil
+ default:
+ return fmt.Errorf("%s 必须为 true 或 false", key)
+ }
+}
+
+func validateOpenRestyOption(key string, value string) error {
+ trimmed := strings.TrimSpace(value)
+
+ switch key {
+ case "OpenRestyWorkerProcesses":
+ if trimmed == "auto" {
+ return nil
+ }
+ return validatePositiveIntegerOption(key, trimmed)
+ case "OpenRestyWorkerConnections",
+ "OpenRestyWorkerRlimitNofile",
+ "OpenRestyKeepaliveTimeout",
+ "OpenRestyKeepaliveRequests",
+ "OpenRestyClientHeaderTimeout",
+ "OpenRestyClientBodyTimeout",
+ "OpenRestySendTimeout",
+ "OpenRestyProxyConnectTimeout",
+ "OpenRestyProxySendTimeout",
+ "OpenRestyProxyReadTimeout",
+ "OpenRestyGzipMinLength":
+ return validatePositiveIntegerOption(key, trimmed)
+ case "OpenRestyGzipCompLevel":
+ if err := validatePositiveIntegerOption(key, trimmed); err != nil {
+ return err
+ }
+ level, _ := strconv.Atoi(trimmed)
+ if level > 9 {
+ return fmt.Errorf("%s 不能大于 9", key)
+ }
+ return nil
+ case "OpenRestyEventsUse":
+ if trimmed == "" {
+ return nil
+ }
+ switch trimmed {
+ case "epoll", "kqueue", "poll", "select", "rtsig", "/dev/poll", "eventport":
+ return nil
+ default:
+ return fmt.Errorf("%s 仅支持 epoll、kqueue、poll、select、rtsig、/dev/poll、eventport 或留空", key)
+ }
+ case "OpenRestyEventsMultiAcceptEnabled",
+ "OpenRestyProxyBufferingEnabled",
+ "OpenRestyGzipEnabled",
+ "OpenRestyCacheEnabled",
+ "OpenRestyCacheLockEnabled":
+ return validateBooleanOption(key, trimmed)
+ case "OpenRestyProxyBuffers":
+ if openRestyProxyBuffersPattern.MatchString(trimmed) {
+ return nil
+ }
+ return fmt.Errorf("%s 格式必须类似 \"16 16k\"", key)
+ case "OpenRestyProxyBufferSize", "OpenRestyProxyBusyBuffersSize", "OpenRestyCacheMaxSize":
+ if openRestySizePattern.MatchString(trimmed) {
+ return nil
+ }
+ return fmt.Errorf("%s 格式必须为整数或带 k/m/g 单位的大小值", key)
+ case "OpenRestyCachePath":
+ if strings.ContainsAny(trimmed, "\r\n\t") {
+ return fmt.Errorf("%s 不能包含换行或制表符", key)
+ }
+ return nil
+ case "OpenRestyCacheLevels":
+ if openRestyCacheLevelsPattern.MatchString(trimmed) {
+ return nil
+ }
+ return fmt.Errorf("%s 格式必须类似 \"1:2\" 或 \"1:2:2\"", key)
+ case "OpenRestyCacheInactive", "OpenRestyCacheLockTimeout":
+ if openRestyDurationTokenPattern.MatchString(trimmed) {
+ return nil
+ }
+ return fmt.Errorf("%s 格式必须为带单位的时长,例如 30m 或 5s", key)
+ case "OpenRestyCacheKeyTemplate":
+ if trimmed == "" {
+ return fmt.Errorf("%s 不能为空", key)
+ }
+ if strings.ContainsAny(trimmed, "\r\n") {
+ return fmt.Errorf("%s 不能包含换行", key)
+ }
+ return nil
+ case "OpenRestyCacheUseStale":
+ if trimmed == "" {
+ return fmt.Errorf("%s 不能为空", key)
+ }
+ allowedTokens := map[string]struct{}{
+ "error": {}, "timeout": {}, "invalid_header": {}, "updating": {},
+ "http_500": {}, "http_502": {}, "http_503": {}, "http_504": {},
+ "http_403": {}, "http_404": {}, "http_429": {}, "off": {},
+ }
+ for _, token := range strings.Fields(trimmed) {
+ if _, ok := allowedTokens[token]; !ok {
+ return fmt.Errorf("%s 包含不支持的值 %q", key, token)
+ }
+ }
+ return nil
+ default:
+ return nil
+ }
+}
+
// GetOptions godoc
// @Summary List editable options
// @Tags Options
@@ -114,6 +238,13 @@ func UpdateOption(c *gin.Context) {
})
return
}
+ if err = validateOpenRestyOption(option.Key, option.Value); err != nil {
+ c.JSON(http.StatusOK, gin.H{
+ "success": false,
+ "message": err.Error(),
+ })
+ return
+ }
err = model.UpdateOption(option.Key, option.Value)
if err != nil {
c.JSON(http.StatusOK, gin.H{
diff --git a/atsf_server/controller/option_test.go b/atsf_server/controller/option_test.go
new file mode 100644
index 00000000..8e3ca522
--- /dev/null
+++ b/atsf_server/controller/option_test.go
@@ -0,0 +1,38 @@
+package controller
+
+import "testing"
+
+func TestValidateOpenRestyOption(t *testing.T) {
+ testCases := []struct {
+ name string
+ key string
+ value string
+ wantErr bool
+ }{
+ {name: "worker processes auto", key: "OpenRestyWorkerProcesses", value: "auto"},
+ {name: "worker processes number", key: "OpenRestyWorkerProcesses", value: "8"},
+ {name: "worker processes invalid", key: "OpenRestyWorkerProcesses", value: "0", wantErr: true},
+ {name: "events use empty", key: "OpenRestyEventsUse", value: ""},
+ {name: "events use invalid", key: "OpenRestyEventsUse", value: "io_uring", wantErr: true},
+ {name: "proxy buffers valid", key: "OpenRestyProxyBuffers", value: "16 16k"},
+ {name: "proxy buffers invalid", key: "OpenRestyProxyBuffers", value: "16x16k", wantErr: true},
+ {name: "cache max size valid", key: "OpenRestyCacheMaxSize", value: "2g"},
+ {name: "cache max size invalid", key: "OpenRestyCacheMaxSize", value: "2gb", wantErr: true},
+ {name: "cache inactive valid", key: "OpenRestyCacheInactive", value: "30m"},
+ {name: "cache inactive invalid", key: "OpenRestyCacheInactive", value: "30", wantErr: true},
+ {name: "cache use stale valid", key: "OpenRestyCacheUseStale", value: "error timeout http_500"},
+ {name: "cache use stale invalid", key: "OpenRestyCacheUseStale", value: "error whatever", wantErr: true},
+ {name: "gzip level valid", key: "OpenRestyGzipCompLevel", value: "9"},
+ {name: "gzip level invalid", key: "OpenRestyGzipCompLevel", value: "10", wantErr: true},
+ }
+
+ for _, testCase := range testCases {
+ err := validateOpenRestyOption(testCase.key, testCase.value)
+ if testCase.wantErr && err == nil {
+ t.Fatalf("%s: expected error", testCase.name)
+ }
+ if !testCase.wantErr && err != nil {
+ t.Fatalf("%s: unexpected error: %v", testCase.name, err)
+ }
+ }
+}
diff --git a/atsf_server/model/option.go b/atsf_server/model/option.go
index aa21c5e4..021f2f1c 100644
--- a/atsf_server/model/option.go
+++ b/atsf_server/model/option.go
@@ -55,6 +55,35 @@ func InitOptionMap() {
common.OptionMap["AgentSyncInterval"] = strconv.Itoa(common.AgentSyncInterval)
common.OptionMap["NodeOfflineThreshold"] = strconv.Itoa(int(common.NodeOfflineThreshold.Milliseconds()))
common.OptionMap["AgentUpdateRepo"] = common.AgentUpdateRepo
+ common.OptionMap["OpenRestyWorkerProcesses"] = common.OpenRestyWorkerProcesses
+ common.OptionMap["OpenRestyWorkerConnections"] = strconv.Itoa(common.OpenRestyWorkerConnections)
+ common.OptionMap["OpenRestyWorkerRlimitNofile"] = strconv.Itoa(common.OpenRestyWorkerRlimitNofile)
+ common.OptionMap["OpenRestyEventsUse"] = common.OpenRestyEventsUse
+ common.OptionMap["OpenRestyEventsMultiAcceptEnabled"] = strconv.FormatBool(common.OpenRestyEventsMultiAcceptEnabled)
+ common.OptionMap["OpenRestyKeepaliveTimeout"] = strconv.Itoa(common.OpenRestyKeepaliveTimeout)
+ common.OptionMap["OpenRestyKeepaliveRequests"] = strconv.Itoa(common.OpenRestyKeepaliveRequests)
+ common.OptionMap["OpenRestyClientHeaderTimeout"] = strconv.Itoa(common.OpenRestyClientHeaderTimeout)
+ common.OptionMap["OpenRestyClientBodyTimeout"] = strconv.Itoa(common.OpenRestyClientBodyTimeout)
+ common.OptionMap["OpenRestySendTimeout"] = strconv.Itoa(common.OpenRestySendTimeout)
+ common.OptionMap["OpenRestyProxyConnectTimeout"] = strconv.Itoa(common.OpenRestyProxyConnectTimeout)
+ common.OptionMap["OpenRestyProxySendTimeout"] = strconv.Itoa(common.OpenRestyProxySendTimeout)
+ common.OptionMap["OpenRestyProxyReadTimeout"] = strconv.Itoa(common.OpenRestyProxyReadTimeout)
+ common.OptionMap["OpenRestyProxyBufferingEnabled"] = strconv.FormatBool(common.OpenRestyProxyBufferingEnabled)
+ common.OptionMap["OpenRestyProxyBuffers"] = common.OpenRestyProxyBuffers
+ common.OptionMap["OpenRestyProxyBufferSize"] = common.OpenRestyProxyBufferSize
+ common.OptionMap["OpenRestyProxyBusyBuffersSize"] = common.OpenRestyProxyBusyBuffersSize
+ common.OptionMap["OpenRestyGzipEnabled"] = strconv.FormatBool(common.OpenRestyGzipEnabled)
+ common.OptionMap["OpenRestyGzipMinLength"] = strconv.Itoa(common.OpenRestyGzipMinLength)
+ common.OptionMap["OpenRestyGzipCompLevel"] = strconv.Itoa(common.OpenRestyGzipCompLevel)
+ common.OptionMap["OpenRestyCacheEnabled"] = strconv.FormatBool(common.OpenRestyCacheEnabled)
+ common.OptionMap["OpenRestyCachePath"] = common.OpenRestyCachePath
+ common.OptionMap["OpenRestyCacheLevels"] = common.OpenRestyCacheLevels
+ common.OptionMap["OpenRestyCacheInactive"] = common.OpenRestyCacheInactive
+ common.OptionMap["OpenRestyCacheMaxSize"] = common.OpenRestyCacheMaxSize
+ common.OptionMap["OpenRestyCacheKeyTemplate"] = common.OpenRestyCacheKeyTemplate
+ common.OptionMap["OpenRestyCacheLockEnabled"] = strconv.FormatBool(common.OpenRestyCacheLockEnabled)
+ common.OptionMap["OpenRestyCacheLockTimeout"] = common.OpenRestyCacheLockTimeout
+ common.OptionMap["OpenRestyCacheUseStale"] = common.OpenRestyCacheUseStale
common.OptionMap["GlobalApiRateLimitNum"] = strconv.Itoa(common.GlobalApiRateLimitNum)
common.OptionMap["GlobalApiRateLimitDuration"] = strconv.FormatInt(common.GlobalApiRateLimitDuration, 10)
common.OptionMap["GlobalWebRateLimitNum"] = strconv.Itoa(common.GlobalWebRateLimitNum)
@@ -175,6 +204,108 @@ func updateOptionMap(key string, value string) {
if value != "" {
common.AgentUpdateRepo = value
}
+ case "OpenRestyWorkerProcesses":
+ if strings.TrimSpace(value) != "" {
+ common.OpenRestyWorkerProcesses = value
+ }
+ case "OpenRestyWorkerConnections":
+ if v, err := strconv.Atoi(value); err == nil && v > 0 {
+ common.OpenRestyWorkerConnections = v
+ }
+ case "OpenRestyWorkerRlimitNofile":
+ if v, err := strconv.Atoi(value); err == nil && v > 0 {
+ common.OpenRestyWorkerRlimitNofile = v
+ }
+ case "OpenRestyEventsUse":
+ common.OpenRestyEventsUse = value
+ case "OpenRestyEventsMultiAcceptEnabled":
+ common.OpenRestyEventsMultiAcceptEnabled = value == "true"
+ case "OpenRestyKeepaliveTimeout":
+ if v, err := strconv.Atoi(value); err == nil && v > 0 {
+ common.OpenRestyKeepaliveTimeout = v
+ }
+ case "OpenRestyKeepaliveRequests":
+ if v, err := strconv.Atoi(value); err == nil && v > 0 {
+ common.OpenRestyKeepaliveRequests = v
+ }
+ case "OpenRestyClientHeaderTimeout":
+ if v, err := strconv.Atoi(value); err == nil && v > 0 {
+ common.OpenRestyClientHeaderTimeout = v
+ }
+ case "OpenRestyClientBodyTimeout":
+ if v, err := strconv.Atoi(value); err == nil && v > 0 {
+ common.OpenRestyClientBodyTimeout = v
+ }
+ case "OpenRestySendTimeout":
+ if v, err := strconv.Atoi(value); err == nil && v > 0 {
+ common.OpenRestySendTimeout = v
+ }
+ case "OpenRestyProxyConnectTimeout":
+ if v, err := strconv.Atoi(value); err == nil && v > 0 {
+ common.OpenRestyProxyConnectTimeout = v
+ }
+ case "OpenRestyProxySendTimeout":
+ if v, err := strconv.Atoi(value); err == nil && v > 0 {
+ common.OpenRestyProxySendTimeout = v
+ }
+ case "OpenRestyProxyReadTimeout":
+ if v, err := strconv.Atoi(value); err == nil && v > 0 {
+ common.OpenRestyProxyReadTimeout = v
+ }
+ case "OpenRestyProxyBufferingEnabled":
+ common.OpenRestyProxyBufferingEnabled = value == "true"
+ case "OpenRestyProxyBuffers":
+ if strings.TrimSpace(value) != "" {
+ common.OpenRestyProxyBuffers = value
+ }
+ case "OpenRestyProxyBufferSize":
+ if strings.TrimSpace(value) != "" {
+ common.OpenRestyProxyBufferSize = value
+ }
+ case "OpenRestyProxyBusyBuffersSize":
+ if strings.TrimSpace(value) != "" {
+ common.OpenRestyProxyBusyBuffersSize = value
+ }
+ case "OpenRestyGzipEnabled":
+ common.OpenRestyGzipEnabled = value == "true"
+ case "OpenRestyGzipMinLength":
+ if v, err := strconv.Atoi(value); err == nil && v > 0 {
+ common.OpenRestyGzipMinLength = v
+ }
+ case "OpenRestyGzipCompLevel":
+ if v, err := strconv.Atoi(value); err == nil && v > 0 {
+ common.OpenRestyGzipCompLevel = v
+ }
+ case "OpenRestyCacheEnabled":
+ common.OpenRestyCacheEnabled = value == "true"
+ case "OpenRestyCachePath":
+ common.OpenRestyCachePath = value
+ case "OpenRestyCacheLevels":
+ if strings.TrimSpace(value) != "" {
+ common.OpenRestyCacheLevels = value
+ }
+ case "OpenRestyCacheInactive":
+ if strings.TrimSpace(value) != "" {
+ common.OpenRestyCacheInactive = value
+ }
+ case "OpenRestyCacheMaxSize":
+ if strings.TrimSpace(value) != "" {
+ common.OpenRestyCacheMaxSize = value
+ }
+ case "OpenRestyCacheKeyTemplate":
+ if strings.TrimSpace(value) != "" {
+ common.OpenRestyCacheKeyTemplate = value
+ }
+ case "OpenRestyCacheLockEnabled":
+ common.OpenRestyCacheLockEnabled = value == "true"
+ case "OpenRestyCacheLockTimeout":
+ if strings.TrimSpace(value) != "" {
+ common.OpenRestyCacheLockTimeout = value
+ }
+ case "OpenRestyCacheUseStale":
+ if strings.TrimSpace(value) != "" {
+ common.OpenRestyCacheUseStale = value
+ }
case "GlobalApiRateLimitNum":
if v, err := strconv.Atoi(value); err == nil && v > 0 {
common.GlobalApiRateLimitNum = v
diff --git a/atsf_server/web/features/settings/components/settings-page.tsx b/atsf_server/web/features/settings/components/settings-page.tsx
index 5eaf9183..ecda4999 100644
--- a/atsf_server/web/features/settings/components/settings-page.tsx
+++ b/atsf_server/web/features/settings/components/settings-page.tsx
@@ -35,6 +35,7 @@ import {
PrimaryButton,
ResourceField,
ResourceInput,
+ ResourceSelect,
ResourceTextarea,
SecondaryButton,
ToggleField,
@@ -72,6 +73,36 @@ const defaultOperationFields = {
AgentSyncInterval: '30000',
NodeOfflineThreshold: '120000',
AgentUpdateRepo: 'Rain-kl/ATSFlare',
+ OpenRestyWorkerProcesses: 'auto',
+ OpenRestyWorkerConnections: '4096',
+ OpenRestyWorkerRlimitNofile: '65535',
+ OpenRestyEventsUse: '',
+ OpenRestyEventsMultiAcceptEnabled: false,
+ OpenRestyKeepaliveTimeout: '65',
+ OpenRestyKeepaliveRequests: '1000',
+ OpenRestyClientHeaderTimeout: '15',
+ OpenRestyClientBodyTimeout: '15',
+ OpenRestySendTimeout: '30',
+ OpenRestyProxyConnectTimeout: '5',
+ OpenRestyProxySendTimeout: '60',
+ OpenRestyProxyReadTimeout: '60',
+ OpenRestyProxyBufferingEnabled: true,
+ OpenRestyProxyBuffers: '16 16k',
+ OpenRestyProxyBufferSize: '8k',
+ OpenRestyProxyBusyBuffersSize: '64k',
+ OpenRestyGzipEnabled: true,
+ OpenRestyGzipMinLength: '1024',
+ OpenRestyGzipCompLevel: '5',
+ OpenRestyCacheEnabled: false,
+ OpenRestyCachePath: '',
+ OpenRestyCacheLevels: '1:2',
+ OpenRestyCacheInactive: '30m',
+ OpenRestyCacheMaxSize: '1g',
+ OpenRestyCacheKeyTemplate: '$scheme$proxy_host$request_uri',
+ OpenRestyCacheLockEnabled: true,
+ OpenRestyCacheLockTimeout: '5s',
+ OpenRestyCacheUseStale:
+ 'error timeout updating http_500 http_502 http_503 http_504',
GlobalApiRateLimitNum: '300',
GlobalApiRateLimitDuration: '180',
GlobalWebRateLimitNum: '300',
@@ -170,6 +201,27 @@ function formatSecondsLabel(value: string) {
return `${seconds} 秒`;
}
+function isPositiveInteger(value: string) {
+ const parsed = Number.parseInt(value, 10);
+ return !Number.isNaN(parsed) && parsed > 0;
+}
+
+function isSizeValue(value: string) {
+ return /^\d+[kKmMgG]?$/.test(value.trim());
+}
+
+function isProxyBuffersValue(value: string) {
+ return /^\d+\s+\d+[kKmMgG]?$/.test(value.trim());
+}
+
+function isDurationToken(value: string) {
+ return /^\d+[smhdwSMHDW]$/.test(value.trim());
+}
+
+function isCacheLevelsValue(value: string) {
+ return /^\d{1,2}(?::\d{1,2}){0,2}$/.test(value.trim());
+}
+
function buildDiscoveryCommand(serverUrl: string, discoveryToken: string) {
return [
`curl -fsSL ${installerScriptUrl} | bash -s -- \\`,
@@ -309,6 +361,53 @@ export function SettingsPage() {
AgentSyncInterval: optionMap.AgentSyncInterval ?? '30000',
NodeOfflineThreshold: optionMap.NodeOfflineThreshold ?? '120000',
AgentUpdateRepo: optionMap.AgentUpdateRepo ?? 'Rain-kl/ATSFlare',
+ OpenRestyWorkerProcesses: optionMap.OpenRestyWorkerProcesses ?? 'auto',
+ OpenRestyWorkerConnections:
+ optionMap.OpenRestyWorkerConnections ?? '4096',
+ OpenRestyWorkerRlimitNofile:
+ optionMap.OpenRestyWorkerRlimitNofile ?? '65535',
+ OpenRestyEventsUse: optionMap.OpenRestyEventsUse ?? '',
+ OpenRestyEventsMultiAcceptEnabled: toBoolean(
+ optionMap.OpenRestyEventsMultiAcceptEnabled,
+ false,
+ ),
+ OpenRestyKeepaliveTimeout: optionMap.OpenRestyKeepaliveTimeout ?? '65',
+ OpenRestyKeepaliveRequests:
+ optionMap.OpenRestyKeepaliveRequests ?? '1000',
+ OpenRestyClientHeaderTimeout:
+ optionMap.OpenRestyClientHeaderTimeout ?? '15',
+ OpenRestyClientBodyTimeout: optionMap.OpenRestyClientBodyTimeout ?? '15',
+ OpenRestySendTimeout: optionMap.OpenRestySendTimeout ?? '30',
+ OpenRestyProxyConnectTimeout:
+ optionMap.OpenRestyProxyConnectTimeout ?? '5',
+ OpenRestyProxySendTimeout: optionMap.OpenRestyProxySendTimeout ?? '60',
+ OpenRestyProxyReadTimeout: optionMap.OpenRestyProxyReadTimeout ?? '60',
+ OpenRestyProxyBufferingEnabled: toBoolean(
+ optionMap.OpenRestyProxyBufferingEnabled,
+ true,
+ ),
+ OpenRestyProxyBuffers: optionMap.OpenRestyProxyBuffers ?? '16 16k',
+ OpenRestyProxyBufferSize: optionMap.OpenRestyProxyBufferSize ?? '8k',
+ OpenRestyProxyBusyBuffersSize:
+ optionMap.OpenRestyProxyBusyBuffersSize ?? '64k',
+ OpenRestyGzipEnabled: toBoolean(optionMap.OpenRestyGzipEnabled, true),
+ OpenRestyGzipMinLength: optionMap.OpenRestyGzipMinLength ?? '1024',
+ OpenRestyGzipCompLevel: optionMap.OpenRestyGzipCompLevel ?? '5',
+ OpenRestyCacheEnabled: toBoolean(optionMap.OpenRestyCacheEnabled, false),
+ OpenRestyCachePath: optionMap.OpenRestyCachePath ?? '',
+ OpenRestyCacheLevels: optionMap.OpenRestyCacheLevels ?? '1:2',
+ OpenRestyCacheInactive: optionMap.OpenRestyCacheInactive ?? '30m',
+ OpenRestyCacheMaxSize: optionMap.OpenRestyCacheMaxSize ?? '1g',
+ OpenRestyCacheKeyTemplate:
+ optionMap.OpenRestyCacheKeyTemplate ?? '$scheme$proxy_host$request_uri',
+ OpenRestyCacheLockEnabled: toBoolean(
+ optionMap.OpenRestyCacheLockEnabled,
+ true,
+ ),
+ OpenRestyCacheLockTimeout: optionMap.OpenRestyCacheLockTimeout ?? '5s',
+ OpenRestyCacheUseStale:
+ optionMap.OpenRestyCacheUseStale ??
+ 'error timeout updating http_500 http_502 http_503 http_504',
GlobalApiRateLimitNum: optionMap.GlobalApiRateLimitNum ?? '300',
GlobalApiRateLimitDuration: optionMap.GlobalApiRateLimitDuration ?? '180',
GlobalWebRateLimitNum: optionMap.GlobalWebRateLimitNum ?? '300',
@@ -987,6 +1086,249 @@ export function SettingsPage() {
/>
+
+
Discovery Token
-+
{discoveryToken || '未生成'}