diff --git a/config.go b/config.go index 982335d..4bd6f22 100644 --- a/config.go +++ b/config.go @@ -1,5 +1,11 @@ package service +import ( + "path/filepath" + "regexp" + "strings" +) + // CertSet SSL 证书配置 type CertSet struct { CertFile string @@ -60,6 +66,81 @@ type ServiceConfig struct { MaxUploadBufferPerConnection int32 // 每个连接的最大上传缓冲区大小 MaxUploadBufferPerStream int32 // 每个流的最大上传缓冲区大小 StopTimeout int // 停止服务的超时时间 (ms) + + // 从配置文件中加载的静态路由策略 (按 Host 分组,全局配置用 "" 或 "*") + Proxies map[string][]ProxyRule + Rewrites map[string][]RewriteRule + Statics map[string]map[string]string } var Config = ServiceConfig{} + +// ApplyConfig 将 ServiceConfig 中的路由策略应用到内部的文件级策略中 +func ApplyConfig() { + hostPoliciesLock.Lock() + defer hostPoliciesLock.Unlock() + + // 清理旧的 file 策略 + fileProxies = make(map[string][]*proxyType) + fileRewrites = make(map[string][]*rewriteType) + + for host, rules := range Config.Proxies { + if host == "*" { + host = "" + } + newProxies := make([]*proxyType, 0, len(rules)) + for _, r := range rules { + p := &proxyType{authLevel: r.AuthLevel, fromPath: r.Path, toApp: r.ToApp, toPath: r.ToPath} + if strings.ContainsRune(r.Path, '(') { + matcher, err := regexp.Compile("^" + r.Path + "$") + if err == nil { + p.matcher = matcher + } + } + newProxies = append(newProxies, p) + } + fileProxies[host] = newProxies + rebuildProxiesUnderLock(host) + } + + for host, rules := range Config.Rewrites { + if host == "*" { + host = "" + } + newRewrites := make([]*rewriteType, 0, len(rules)) + for _, r := range rules { + s := &rewriteType{fromPath: r.Path, toPath: r.ToPath} + if strings.ContainsRune(r.Path, '(') { + matcher, err := regexp.Compile("^" + r.Path + "$") + if err == nil { + s.matcher = matcher + } + } + newRewrites = append(newRewrites, s) + } + fileRewrites[host] = newRewrites + rebuildRewritesUnderLock(host) + } + + staticsByHostLock.Lock() + defer staticsByHostLock.Unlock() + fileStatics = make(map[string]map[string]*string) + + for host, config := range Config.Statics { + if host == "*" { + host = "" + } + newStatics := make(map[string]*string, len(config)) + for path, rootPath := range config { + rp := rootPath + if !filepath.IsAbs(rp) { + if absPath, err := filepath.Abs(rp); err == nil { + rp = absPath + } + } + newStatics[path] = &rp + } + fileStatics[host] = newStatics + rebuildStaticsUnderLock(host) + } +} diff --git a/proxy.go b/proxy.go index 4b9b847..3d36148 100644 --- a/proxy.go +++ b/proxy.go @@ -30,10 +30,19 @@ func (hc *HostContext) Proxy(authLevel int, path string, toApp, toPath string) * hostPoliciesLock.Lock() defer hostPoliciesLock.Unlock() - hostProxies[hc.host] = append(hostProxies[hc.host], p) + codeProxies[hc.host] = append(codeProxies[hc.host], p) + rebuildProxiesUnderLock(hc.host) return hc } +func rebuildProxiesUnderLock(host string) { + var combined []*proxyType + combined = append(combined, codeProxies[host]...) + combined = append(combined, fileProxies[host]...) + combined = append(combined, dynamicProxies[host]...) + hostProxies[host] = combined +} + var httpClientPool *gohttp.Client func findProxy(request *Request) (int, *string, *string, string) { @@ -171,8 +180,7 @@ type ProxyRule struct { ToPath string // 目标路径 (可含 $1 变量替换) } -// ReplaceProxies 使用全量指针替换的方式 (Copy-on-Write) 无缝更新指定 host 的所有代理规则。 -// 该方法非常轻量,仅在赋值瞬间短暂持有写锁,不会阻塞任何并发请求,并且自动淘汰旧规则。 +// ReplaceProxies 使用全量指针替换的方式 (Copy-on-Write) 无缝更新指定 host 的动态代理规则,不影响通过代码或文件写入的固定规则。 func ReplaceProxies(host string, rules []ProxyRule) { newProxies := make([]*proxyType, 0, len(rules)) for _, r := range rules { @@ -188,5 +196,6 @@ func ReplaceProxies(host string, rules []ProxyRule) { hostPoliciesLock.Lock() defer hostPoliciesLock.Unlock() - hostProxies[host] = newProxies + dynamicProxies[host] = newProxies + rebuildProxiesUnderLock(host) } diff --git a/rewrite.go b/rewrite.go index c17ce77..3ac6699 100644 --- a/rewrite.go +++ b/rewrite.go @@ -26,10 +26,19 @@ func (hc *HostContext) Rewrite(path string, toPath string) *HostContext { hostPoliciesLock.Lock() defer hostPoliciesLock.Unlock() - hostRewrites[hc.host] = append(hostRewrites[hc.host], s) + codeRewrites[hc.host] = append(codeRewrites[hc.host], s) + rebuildRewritesUnderLock(hc.host) return hc } +func rebuildRewritesUnderLock(host string) { + var combined []*rewriteType + combined = append(combined, codeRewrites[host]...) + combined = append(combined, fileRewrites[host]...) + combined = append(combined, dynamicRewrites[host]...) + hostRewrites[host] = combined +} + func processRewrite(request *Request, response *Response, logger *log.Logger) bool { host := request.Host hostOnly, port, _ := strings.Cut(host, ":") @@ -108,7 +117,7 @@ type RewriteRule struct { ToPath string // 重写后的路径,例如 /new/$1 } -// ReplaceRewrites 使用 Copy-on-Write 机制原子地替换指定 host 下的所有重写规则。 +// ReplaceRewrites 使用 Copy-on-Write 机制原子地替换指定 host 下的动态重写规则。 func ReplaceRewrites(host string, rules []RewriteRule) { newRewrites := make([]*rewriteType, 0, len(rules)) for _, r := range rules { @@ -124,5 +133,6 @@ func ReplaceRewrites(host string, rules []RewriteRule) { hostPoliciesLock.Lock() defer hostPoliciesLock.Unlock() - hostRewrites[host] = newRewrites + dynamicRewrites[host] = newRewrites + rebuildRewritesUnderLock(host) } diff --git a/server.go b/server.go index 60a325e..f936983 100644 --- a/server.go +++ b/server.go @@ -1,6 +1,7 @@ package service import ( + "apigo.cc/go/config" "apigo.cc/go/discover" "apigo.cc/go/log" "apigo.cc/go/redis" @@ -27,6 +28,7 @@ type WebServer struct { Addr string useDiscover bool discoverer *discover.Discoverer + logger *log.Logger } // NewWebServer 创建并返回一个新的 WebServer 实例 @@ -36,6 +38,11 @@ func NewWebServer() *WebServer { // Start 启动服务,实现 starter.Service 接口 func (ws *WebServer) Start(ctx context.Context, logger *log.Logger) error { + if logger == nil { + logger = log.DefaultLogger + } + ws.logger = logger + listenStr := Config.Listen ws.useDiscover = false @@ -182,7 +189,11 @@ func (ws *WebServer) Start(ctx context.Context, logger *log.Logger) error { // Stop 停止服务,实现 starter.Service 接口 func (ws *WebServer) Stop(ctx context.Context) error { - log.DefaultLogger.Info("service stopping") + logger := ws.logger + if logger == nil { + logger = log.DefaultLogger + } + logger.Info("service stopping") if ws.discoverer != nil { ws.discoverer.Stop() } @@ -191,7 +202,7 @@ func (ws *WebServer) Stop(ctx context.Context) error { return err } } - log.DefaultLogger.Info("service stopped") + logger.Info("service stopped") return nil } @@ -205,7 +216,23 @@ func (ws *WebServer) Health() error { // Reload 实现配置重新加载,实现 starter.Reloader 接口 func (ws *WebServer) Reload() error { - log.DefaultLogger.Info("reloading configurations...") + logger := ws.logger + if logger == nil { + logger = log.DefaultLogger + } + logger.Info("reloading configurations...") + + // 重新加载配置文件中的策略 + appName := Config.App + if appName == "" { + appName = GetDefaultName() + } + if err := config.Load(&Config, appName); err != nil { + logger.Error("failed to load config during reload", "error", err.Error()) + } + ApplyConfig() + + // 触发业务挂载的 Hook return triggerReload() } diff --git a/service.go b/service.go index 61fc66b..e22512b 100644 --- a/service.go +++ b/service.go @@ -72,9 +72,19 @@ var ( websocketServicesLock = sync.RWMutex{} websocketServicesList = make([]*websocketServiceType, 0) - // Rewrite 与 Proxy 按 Host 隔离 + // Rewrite 与 Proxy 按 Host 隔离 (编译后的最终路由) hostRewrites = make(map[string][]*rewriteType) hostProxies = make(map[string][]*proxyType) + + // 按来源隔离的策略,避免互相覆盖 + codeProxies = make(map[string][]*proxyType) + fileProxies = make(map[string][]*proxyType) + dynamicProxies = make(map[string][]*proxyType) + + codeRewrites = make(map[string][]*rewriteType) + fileRewrites = make(map[string][]*rewriteType) + dynamicRewrites = make(map[string][]*rewriteType) + hostPoliciesLock = sync.RWMutex{} // 过滤器与拦截器 diff --git a/static.go b/static.go index d86175d..8af5352 100644 --- a/static.go +++ b/static.go @@ -14,6 +14,11 @@ import ( var ( statics = make(map[string]*string) staticsByHost = make(map[string]map[string]*string) + + codeStatics = make(map[string]map[string]*string) + fileStatics = make(map[string]map[string]*string) + dynamicStatics = make(map[string]map[string]*string) + staticsByHostLock = sync.RWMutex{} ) @@ -33,17 +38,14 @@ func StaticByHost(path, rootPath, host string) { staticsByHostLock.Lock() defer staticsByHostLock.Unlock() - if host == "" { - statics[path] = &rootPath - } else { - if staticsByHost[host] == nil { - staticsByHost[host] = make(map[string]*string) - } - staticsByHost[host][path] = &rootPath + if codeStatics[host] == nil { + codeStatics[host] = make(map[string]*string) } + codeStatics[host][path] = &rootPath + rebuildStaticsUnderLock(host) } -// ReplaceStatics 使用 Copy-on-Write 机制原子地替换指定 host 下的所有静态目录规则 +// ReplaceStatics 使用 Copy-on-Write 机制原子地替换指定 host 下的动态静态目录规则 func ReplaceStatics(host string, config map[string]string) { newStatics := make(map[string]*string, len(config)) for path, rootPath := range config { @@ -59,10 +61,28 @@ func ReplaceStatics(host string, config map[string]string) { staticsByHostLock.Lock() defer staticsByHostLock.Unlock() + dynamicStatics[host] = newStatics + rebuildStaticsUnderLock(host) +} + +func rebuildStaticsUnderLock(host string) { + combined := make(map[string]*string) + + // 合并三种来源的静态路由 + for k, v := range codeStatics[host] { + combined[k] = v + } + for k, v := range fileStatics[host] { + combined[k] = v + } + for k, v := range dynamicStatics[host] { + combined[k] = v + } + if host == "" { - statics = newStatics + statics = combined } else { - staticsByHost[host] = newStatics + staticsByHost[host] = combined } }