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()