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:
parent
ac66ea5534
commit
38bb743781
@ -1,6 +1,7 @@
|
|||||||
package matcher
|
package matcher
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/valyala/fasthttp"
|
"github.com/valyala/fasthttp"
|
||||||
@ -107,4 +108,8 @@ func TestLocationEngine_PathConflict(t *testing.T) {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("should fail on path conflict")
|
t.Error("should fail on path conflict")
|
||||||
}
|
}
|
||||||
|
var ce *ConflictError
|
||||||
|
if !errors.As(err, &ce) {
|
||||||
|
t.Errorf("expected *ConflictError, got %T: %v", err, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -240,6 +240,21 @@ func (e *LocationEngine) MarkInitialized() {
|
|||||||
e.prefixTree.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 检查路径冲突。
|
// checkConflict 检查路径冲突。
|
||||||
//
|
//
|
||||||
// 参数:
|
// 参数:
|
||||||
@ -247,11 +262,10 @@ func (e *LocationEngine) MarkInitialized() {
|
|||||||
// - locationType: location 类型
|
// - locationType: location 类型
|
||||||
//
|
//
|
||||||
// 返回值:
|
// 返回值:
|
||||||
// - error: 路径已存在时返回冲突错误
|
// - error: 路径已存在时返回 *ConflictError
|
||||||
func (e *LocationEngine) checkConflict(path, locationType string) error {
|
func (e *LocationEngine) checkConflict(path, locationType string) error {
|
||||||
if existing, ok := e.registeredPaths[path]; ok {
|
if existing, ok := e.registeredPaths[path]; ok {
|
||||||
return fmt.Errorf("path conflict: '%s' already registered as '%s', trying to register as '%s'",
|
return &ConflictError{Path: path, ExistingType: existing, NewType: locationType}
|
||||||
path, existing, locationType)
|
|
||||||
}
|
}
|
||||||
e.registeredPaths[path] = locationType
|
e.registeredPaths[path] = locationType
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@ -67,7 +67,7 @@ func (s *Server) createProxyForConfig(proxyCfg *config.ProxyConfig) *proxy.Proxy
|
|||||||
//
|
//
|
||||||
// 根据配置为 LocationEngine 注册代理路径,创建代理处理器和健康检查器。
|
// 根据配置为 LocationEngine 注册代理路径,创建代理处理器和健康检查器。
|
||||||
// 支持通过 LocationType 配置不同的匹配方式。
|
// 支持通过 LocationType 配置不同的匹配方式。
|
||||||
func (s *Server) registerProxyRoutesWithLocationEngine(serverCfg *config.ServerConfig) {
|
func (s *Server) registerProxyRoutesWithLocationEngine(serverCfg *config.ServerConfig) error {
|
||||||
for i := range serverCfg.Proxy {
|
for i := range serverCfg.Proxy {
|
||||||
proxyCfg := &serverCfg.Proxy[i]
|
proxyCfg := &serverCfg.Proxy[i]
|
||||||
|
|
||||||
@ -76,7 +76,6 @@ func (s *Server) registerProxyRoutesWithLocationEngine(serverCfg *config.ServerC
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据 LocationType 注册路由
|
|
||||||
locType := proxyCfg.LocationType
|
locType := proxyCfg.LocationType
|
||||||
if locType == "" {
|
if locType == "" {
|
||||||
locType = matcher.LocationTypePrefix
|
locType = matcher.LocationTypePrefix
|
||||||
@ -84,22 +83,47 @@ func (s *Server) registerProxyRoutesWithLocationEngine(serverCfg *config.ServerC
|
|||||||
|
|
||||||
switch locType {
|
switch locType {
|
||||||
case matcher.LocationTypeExact:
|
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:
|
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:
|
case matcher.LocationTypeRegex, matcher.LocationTypeRegexCaseless:
|
||||||
caseInsensitive := locType == 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:
|
case matcher.LocationTypeNamed:
|
||||||
if proxyCfg.LocationName != "" {
|
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:
|
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:
|
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 配置静态文件处理器。
|
// configureStaticHandler 配置静态文件处理器。
|
||||||
@ -156,7 +180,7 @@ func (s *Server) configureStaticHandler(static *config.StaticConfig, cfg *config
|
|||||||
}
|
}
|
||||||
|
|
||||||
// registerStaticHandlersWithLocationEngine 使用 LocationEngine 注册静态文件处理器。
|
// registerStaticHandlersWithLocationEngine 使用 LocationEngine 注册静态文件处理器。
|
||||||
func (s *Server) registerStaticHandlersWithLocationEngine(cfg *config.ServerConfig) {
|
func (s *Server) registerStaticHandlersWithLocationEngine(cfg *config.ServerConfig) error {
|
||||||
for _, static := range cfg.Static {
|
for _, static := range cfg.Static {
|
||||||
staticHandler := s.configureStaticHandler(&static, cfg)
|
staticHandler := s.configureStaticHandler(&static, cfg)
|
||||||
path := static.Path
|
path := static.Path
|
||||||
@ -164,7 +188,6 @@ func (s *Server) registerStaticHandlersWithLocationEngine(cfg *config.ServerConf
|
|||||||
path = "/"
|
path = "/"
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据 LocationType 注册路由
|
|
||||||
locType := static.LocationType
|
locType := static.LocationType
|
||||||
if locType == "" {
|
if locType == "" {
|
||||||
locType = matcher.LocationTypePrefix
|
locType = matcher.LocationTypePrefix
|
||||||
@ -172,15 +195,32 @@ func (s *Server) registerStaticHandlersWithLocationEngine(cfg *config.ServerConf
|
|||||||
|
|
||||||
switch locType {
|
switch locType {
|
||||||
case matcher.LocationTypeExact:
|
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:
|
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:
|
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:
|
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 注册代理路由。
|
// registerProxyRoutes 注册代理路由。
|
||||||
@ -324,9 +364,9 @@ func (s *Server) registerLuaRoutes(router *handler.Router, serverCfg *config.Ser
|
|||||||
// - 只有设置了 Route 字段的脚本才会被注册
|
// - 只有设置了 Route 字段的脚本才会被注册
|
||||||
// - 路由脚本不经过完整中间件链,只应用 accesslog 和 errorintercept
|
// - 路由脚本不经过完整中间件链,只应用 accesslog 和 errorintercept
|
||||||
// - 支持 exact、prefix、prefix_priority、regex、regex_caseless 匹配类型
|
// - 支持 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 {
|
if s.luaEngine == nil || serverCfg.Lua == nil || !serverCfg.Lua.Enabled {
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, script := range serverCfg.Lua.Scripts {
|
for _, script := range serverCfg.Lua.Scripts {
|
||||||
@ -348,17 +388,38 @@ func (s *Server) registerLuaRoutesWithLocationEngine(serverCfg *config.ServerCon
|
|||||||
|
|
||||||
switch routeType {
|
switch routeType {
|
||||||
case matcher.LocationTypeExact:
|
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:
|
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:
|
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:
|
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:
|
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 为路由处理器包装基础中间件链。
|
// wrapRoutedHandler 为路由处理器包装基础中间件链。
|
||||||
|
|||||||
@ -21,6 +21,7 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
@ -96,6 +97,15 @@ func New(cfg *config.Config) *Server {
|
|||||||
return &Server{config: cfg}
|
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 根据配置返回服务器名称。
|
// getServerName 根据配置返回服务器名称。
|
||||||
//
|
//
|
||||||
// 当 ServerTokens 为 false 时隐藏版本号,仅返回 "lolly"。
|
// 当 ServerTokens 为 false 时隐藏版本号,仅返回 "lolly"。
|
||||||
@ -382,39 +392,56 @@ func (s *Server) startSingleMode() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
logging.Error().Msg("Failed to create status handler: " + err.Error())
|
logging.Error().Msg("Failed to create status handler: " + err.Error())
|
||||||
} else {
|
} 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 {
|
if s.config.Monitoring.Pprof.Enabled {
|
||||||
pprofHandler, err := NewPprofHandler(&s.config.Monitoring.Pprof)
|
pprofHandler, err := NewPprofHandler(&s.config.Monitoring.Pprof)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logging.Error().Msg("Failed to create pprof handler: " + err.Error())
|
logging.Error().Msg("Failed to create pprof handler: " + err.Error())
|
||||||
} else {
|
} else {
|
||||||
_ = s.locationEngine.AddExact(pprofHandler.Path(), pprofHandler.ServeHTTP, false)
|
if err := s.locationEngine.AddExact(pprofHandler.Path(), pprofHandler.ServeHTTP, false); err != nil {
|
||||||
_ = s.locationEngine.AddPrefixPriority(pprofHandler.Path()+"/", pprofHandler.ServeHTTP, false)
|
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 {
|
if serverCfg.CacheAPI != nil && serverCfg.CacheAPI.Enabled {
|
||||||
purgeHandler, err := NewPurgeHandler(s, serverCfg.CacheAPI)
|
purgeHandler, err := NewPurgeHandler(s, serverCfg.CacheAPI)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logging.Error().Msg("Failed to create cache purge handler: " + err.Error())
|
logging.Error().Msg("Failed to create cache purge handler: " + err.Error())
|
||||||
} else {
|
} 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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 注册代理路由
|
if err := s.registerProxyRoutesWithLocationEngine(serverCfg); err != nil {
|
||||||
s.registerProxyRoutesWithLocationEngine(serverCfg)
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Lua 路由
|
if err := s.registerLuaRoutesWithLocationEngine(serverCfg); err != nil {
|
||||||
s.registerLuaRoutesWithLocationEngine(serverCfg)
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// 静态文件服务
|
if err := s.registerStaticHandlersWithLocationEngine(serverCfg); err != nil {
|
||||||
s.registerStaticHandlersWithLocationEngine(serverCfg)
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// 标记 LocationEngine 初始化完成
|
// 标记 LocationEngine 初始化完成
|
||||||
s.locationEngine.MarkInitialized()
|
s.locationEngine.MarkInitialized()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user