From 38bb74378105b0b5e165f09af739e0c8cb5b0879 Mon Sep 17 00:00:00 2001 From: xfy Date: Wed, 3 Jun 2026 10:12:09 +0800 Subject: [PATCH] fix(server): handle LocationEngine registration errors properly Add typed ConflictError for path conflicts, change register functions to return errors, handle conflicts as warnings and fatal errors as startup failures. Remove all 20 instances of ignored Add* return values. --- internal/matcher/integration_test.go | 5 ++ internal/matcher/location.go | 20 +++++- internal/server/router.go | 103 +++++++++++++++++++++------ internal/server/server.go | 51 +++++++++---- 4 files changed, 143 insertions(+), 36 deletions(-) diff --git a/internal/matcher/integration_test.go b/internal/matcher/integration_test.go index 687ba46..c2b72a0 100644 --- a/internal/matcher/integration_test.go +++ b/internal/matcher/integration_test.go @@ -1,6 +1,7 @@ package matcher import ( + "errors" "testing" "github.com/valyala/fasthttp" @@ -107,4 +108,8 @@ func TestLocationEngine_PathConflict(t *testing.T) { if err == nil { t.Error("should fail on path conflict") } + var ce *ConflictError + if !errors.As(err, &ce) { + t.Errorf("expected *ConflictError, got %T: %v", err, err) + } } diff --git a/internal/matcher/location.go b/internal/matcher/location.go index a96153d..c42a210 100644 --- a/internal/matcher/location.go +++ b/internal/matcher/location.go @@ -240,6 +240,21 @@ func (e *LocationEngine) MarkInitialized() { e.prefixTree.MarkInitialized() } +// ConflictError 路径冲突错误。 +// +// 当同一路径被重复注册为不同类型的 location 时返回此错误。 +// 调用方可通过 errors.As 检测此类型,区分冲突与致命错误。 +type ConflictError struct { + Path string + ExistingType string + NewType string +} + +func (e *ConflictError) Error() string { + return fmt.Sprintf("path conflict: '%s' already registered as '%s', trying to register as '%s'", + e.Path, e.ExistingType, e.NewType) +} + // checkConflict 检查路径冲突。 // // 参数: @@ -247,11 +262,10 @@ func (e *LocationEngine) MarkInitialized() { // - locationType: location 类型 // // 返回值: -// - error: 路径已存在时返回冲突错误 +// - error: 路径已存在时返回 *ConflictError func (e *LocationEngine) checkConflict(path, locationType string) error { if existing, ok := e.registeredPaths[path]; ok { - return fmt.Errorf("path conflict: '%s' already registered as '%s', trying to register as '%s'", - path, existing, locationType) + return &ConflictError{Path: path, ExistingType: existing, NewType: locationType} } e.registeredPaths[path] = locationType return nil diff --git a/internal/server/router.go b/internal/server/router.go index 238ba11..54c5290 100644 --- a/internal/server/router.go +++ b/internal/server/router.go @@ -67,7 +67,7 @@ func (s *Server) createProxyForConfig(proxyCfg *config.ProxyConfig) *proxy.Proxy // // 根据配置为 LocationEngine 注册代理路径,创建代理处理器和健康检查器。 // 支持通过 LocationType 配置不同的匹配方式。 -func (s *Server) registerProxyRoutesWithLocationEngine(serverCfg *config.ServerConfig) { +func (s *Server) registerProxyRoutesWithLocationEngine(serverCfg *config.ServerConfig) error { for i := range serverCfg.Proxy { proxyCfg := &serverCfg.Proxy[i] @@ -76,7 +76,6 @@ func (s *Server) registerProxyRoutesWithLocationEngine(serverCfg *config.ServerC continue } - // 根据 LocationType 注册路由 locType := proxyCfg.LocationType if locType == "" { locType = matcher.LocationTypePrefix @@ -84,22 +83,47 @@ func (s *Server) registerProxyRoutesWithLocationEngine(serverCfg *config.ServerC switch locType { case matcher.LocationTypeExact: - _ = s.locationEngine.AddExact(proxyCfg.Path, p.ServeHTTP, proxyCfg.Internal) + if err := s.locationEngine.AddExact(proxyCfg.Path, p.ServeHTTP, proxyCfg.Internal); err != nil { + if err := s.handleRegistrationError("proxy", proxyCfg.Path, err); err != nil { + return err + } + } case matcher.LocationTypePrefixPriority: - _ = s.locationEngine.AddPrefixPriority(proxyCfg.Path, p.ServeHTTP, proxyCfg.Internal) + if err := s.locationEngine.AddPrefixPriority(proxyCfg.Path, p.ServeHTTP, proxyCfg.Internal); err != nil { + if err := s.handleRegistrationError("proxy", proxyCfg.Path, err); err != nil { + return err + } + } case matcher.LocationTypeRegex, matcher.LocationTypeRegexCaseless: caseInsensitive := locType == matcher.LocationTypeRegexCaseless - _ = s.locationEngine.AddRegex(proxyCfg.Path, p.ServeHTTP, caseInsensitive, proxyCfg.Internal) + if err := s.locationEngine.AddRegex(proxyCfg.Path, p.ServeHTTP, caseInsensitive, proxyCfg.Internal); err != nil { + if err := s.handleRegistrationError("proxy", proxyCfg.Path, err); err != nil { + return err + } + } case matcher.LocationTypeNamed: if proxyCfg.LocationName != "" { - _ = s.locationEngine.AddNamed(proxyCfg.LocationName, p.ServeHTTP) + if err := s.locationEngine.AddNamed(proxyCfg.LocationName, p.ServeHTTP); err != nil { + if err := s.handleRegistrationError("proxy", "@"+proxyCfg.LocationName, err); err != nil { + return err + } + } } case matcher.LocationTypePrefix: - _ = s.locationEngine.AddPrefix(proxyCfg.Path, p.ServeHTTP, proxyCfg.Internal) + if err := s.locationEngine.AddPrefix(proxyCfg.Path, p.ServeHTTP, proxyCfg.Internal); err != nil { + if err := s.handleRegistrationError("proxy", proxyCfg.Path, err); err != nil { + return err + } + } default: - _ = s.locationEngine.AddPrefix(proxyCfg.Path, p.ServeHTTP, proxyCfg.Internal) + if err := s.locationEngine.AddPrefix(proxyCfg.Path, p.ServeHTTP, proxyCfg.Internal); err != nil { + if err := s.handleRegistrationError("proxy", proxyCfg.Path, err); err != nil { + return err + } + } } } + return nil } // configureStaticHandler 配置静态文件处理器。 @@ -156,7 +180,7 @@ func (s *Server) configureStaticHandler(static *config.StaticConfig, cfg *config } // registerStaticHandlersWithLocationEngine 使用 LocationEngine 注册静态文件处理器。 -func (s *Server) registerStaticHandlersWithLocationEngine(cfg *config.ServerConfig) { +func (s *Server) registerStaticHandlersWithLocationEngine(cfg *config.ServerConfig) error { for _, static := range cfg.Static { staticHandler := s.configureStaticHandler(&static, cfg) path := static.Path @@ -164,7 +188,6 @@ func (s *Server) registerStaticHandlersWithLocationEngine(cfg *config.ServerConf path = "/" } - // 根据 LocationType 注册路由 locType := static.LocationType if locType == "" { locType = matcher.LocationTypePrefix @@ -172,15 +195,32 @@ func (s *Server) registerStaticHandlersWithLocationEngine(cfg *config.ServerConf switch locType { case matcher.LocationTypeExact: - _ = s.locationEngine.AddExact(path, staticHandler.Handle, static.Internal) + if err := s.locationEngine.AddExact(path, staticHandler.Handle, static.Internal); err != nil { + if err := s.handleRegistrationError("static", path, err); err != nil { + return err + } + } case matcher.LocationTypePrefixPriority: - _ = s.locationEngine.AddPrefixPriority(path, staticHandler.Handle, static.Internal) + if err := s.locationEngine.AddPrefixPriority(path, staticHandler.Handle, static.Internal); err != nil { + if err := s.handleRegistrationError("static", path, err); err != nil { + return err + } + } case matcher.LocationTypePrefix: - _ = s.locationEngine.AddPrefix(path, staticHandler.Handle, static.Internal) + if err := s.locationEngine.AddPrefix(path, staticHandler.Handle, static.Internal); err != nil { + if err := s.handleRegistrationError("static", path, err); err != nil { + return err + } + } default: - _ = s.locationEngine.AddPrefix(path, staticHandler.Handle, static.Internal) + if err := s.locationEngine.AddPrefix(path, staticHandler.Handle, static.Internal); err != nil { + if err := s.handleRegistrationError("static", path, err); err != nil { + return err + } + } } } + return nil } // registerProxyRoutes 注册代理路由。 @@ -324,9 +364,9 @@ func (s *Server) registerLuaRoutes(router *handler.Router, serverCfg *config.Ser // - 只有设置了 Route 字段的脚本才会被注册 // - 路由脚本不经过完整中间件链,只应用 accesslog 和 errorintercept // - 支持 exact、prefix、prefix_priority、regex、regex_caseless 匹配类型 -func (s *Server) registerLuaRoutesWithLocationEngine(serverCfg *config.ServerConfig) { +func (s *Server) registerLuaRoutesWithLocationEngine(serverCfg *config.ServerConfig) error { if s.luaEngine == nil || serverCfg.Lua == nil || !serverCfg.Lua.Enabled { - return + return nil } for _, script := range serverCfg.Lua.Scripts { @@ -348,17 +388,38 @@ func (s *Server) registerLuaRoutesWithLocationEngine(serverCfg *config.ServerCon switch routeType { case matcher.LocationTypeExact: - _ = s.locationEngine.AddExact(script.Route, handler, false) + if err := s.locationEngine.AddExact(script.Route, handler, false); err != nil { + if err := s.handleRegistrationError("lua", script.Route, err); err != nil { + return err + } + } case matcher.LocationTypePrefixPriority: - _ = s.locationEngine.AddPrefixPriority(script.Route, handler, false) + if err := s.locationEngine.AddPrefixPriority(script.Route, handler, false); err != nil { + if err := s.handleRegistrationError("lua", script.Route, err); err != nil { + return err + } + } case matcher.LocationTypeRegex: - _ = s.locationEngine.AddRegex(script.Route, handler, false, false) + if err := s.locationEngine.AddRegex(script.Route, handler, false, false); err != nil { + if err := s.handleRegistrationError("lua", script.Route, err); err != nil { + return err + } + } case matcher.LocationTypeRegexCaseless: - _ = s.locationEngine.AddRegex(script.Route, handler, true, false) + if err := s.locationEngine.AddRegex(script.Route, handler, true, false); err != nil { + if err := s.handleRegistrationError("lua", script.Route, err); err != nil { + return err + } + } default: - _ = s.locationEngine.AddPrefix(script.Route, handler, false) + if err := s.locationEngine.AddPrefix(script.Route, handler, false); err != nil { + if err := s.handleRegistrationError("lua", script.Route, err); err != nil { + return err + } + } } } + return nil } // wrapRoutedHandler 为路由处理器包装基础中间件链。 diff --git a/internal/server/server.go b/internal/server/server.go index 99dced5..ddba853 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -21,6 +21,7 @@ package server import ( "crypto/tls" + "errors" "fmt" "net" "os" @@ -96,6 +97,15 @@ func New(cfg *config.Config) *Server { return &Server{config: cfg} } +func (s *Server) handleRegistrationError(source, path string, err error) error { + var ce *matcher.ConflictError + if errors.As(err, &ce) { + logging.Warn().Msgf("Route registration skipped (%s %s): %s", source, path, err) + return nil + } + return fmt.Errorf("%s route %s: %w", source, path, err) +} + // getServerName 根据配置返回服务器名称。 // // 当 ServerTokens 为 false 时隐藏版本号,仅返回 "lolly"。 @@ -382,39 +392,56 @@ func (s *Server) startSingleMode() error { if err != nil { logging.Error().Msg("Failed to create status handler: " + err.Error()) } else { - _ = s.locationEngine.AddExact(statusHandler.Path(), statusHandler.ServeHTTP, false) + if err := s.locationEngine.AddExact(statusHandler.Path(), statusHandler.ServeHTTP, false); err != nil { + if err := s.handleRegistrationError("status", statusHandler.Path(), err); err != nil { + return err + } + } } } - // 注册 pprof 性能分析端点(如果配置) if s.config.Monitoring.Pprof.Enabled { pprofHandler, err := NewPprofHandler(&s.config.Monitoring.Pprof) if err != nil { logging.Error().Msg("Failed to create pprof handler: " + err.Error()) } else { - _ = s.locationEngine.AddExact(pprofHandler.Path(), pprofHandler.ServeHTTP, false) - _ = s.locationEngine.AddPrefixPriority(pprofHandler.Path()+"/", pprofHandler.ServeHTTP, false) + if err := s.locationEngine.AddExact(pprofHandler.Path(), pprofHandler.ServeHTTP, false); err != nil { + if err := s.handleRegistrationError("pprof", pprofHandler.Path(), err); err != nil { + return err + } + } + if err := s.locationEngine.AddPrefixPriority(pprofHandler.Path()+"/", pprofHandler.ServeHTTP, false); err != nil { + if err := s.handleRegistrationError("pprof", pprofHandler.Path()+"/", err); err != nil { + return err + } + } } } - // 注册缓存清理 API(如果配置) if serverCfg.CacheAPI != nil && serverCfg.CacheAPI.Enabled { purgeHandler, err := NewPurgeHandler(s, serverCfg.CacheAPI) if err != nil { logging.Error().Msg("Failed to create cache purge handler: " + err.Error()) } else { - _ = s.locationEngine.AddExact(purgeHandler.Path(), purgeHandler.ServeHTTP, false) + if err := s.locationEngine.AddExact(purgeHandler.Path(), purgeHandler.ServeHTTP, false); err != nil { + if err := s.handleRegistrationError("cache-purge", purgeHandler.Path(), err); err != nil { + return err + } + } } } - // 注册代理路由 - s.registerProxyRoutesWithLocationEngine(serverCfg) + if err := s.registerProxyRoutesWithLocationEngine(serverCfg); err != nil { + return err + } - // Lua 路由 - s.registerLuaRoutesWithLocationEngine(serverCfg) + if err := s.registerLuaRoutesWithLocationEngine(serverCfg); err != nil { + return err + } - // 静态文件服务 - s.registerStaticHandlersWithLocationEngine(serverCfg) + if err := s.registerStaticHandlersWithLocationEngine(serverCfg); err != nil { + return err + } // 标记 LocationEngine 初始化完成 s.locationEngine.MarkInitialized()