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.
This commit is contained in:
xfy 2026-06-03 10:12:09 +08:00
parent ac66ea5534
commit 38bb743781
4 changed files with 143 additions and 36 deletions

View File

@ -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)
}
}

View File

@ -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

View File

@ -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 为路由处理器包装基础中间件链。

View File

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