From a6fed3f221eff71de1300464e4caabb7803b3d69 Mon Sep 17 00:00:00 2001 From: yusing Date: Fri, 6 Feb 2026 12:01:09 +0800 Subject: [PATCH] fix: add nil guard before entrypoint retrieval; move config from types/ --- internal/api/v1/favicon.go | 3 ++ internal/api/v1/health.go | 5 +++ internal/api/v1/homepage/categories.go | 5 +++ internal/api/v1/route/by_provider.go | 5 +++ internal/api/v1/route/route.go | 5 +++ internal/config/types/config.go | 2 +- internal/entrypoint/{types => }/config.go | 0 internal/entrypoint/entrypoint.go | 38 ++++++++++--------- .../entrypoint/entrypoint_benchmark_test.go | 13 +++++-- internal/entrypoint/routes.go | 9 +++-- internal/entrypoint/shortlink_test.go | 2 +- internal/entrypoint/types/entrypoint.go | 2 +- internal/route/fileserver.go | 6 ++- internal/route/reverse_proxy.go | 12 ++++-- internal/route/route.go | 8 +++- internal/route/stream.go | 6 ++- internal/route/stream/tcp_tcp.go | 6 ++- 17 files changed, 90 insertions(+), 37 deletions(-) rename internal/entrypoint/{types => }/config.go (100%) diff --git a/internal/api/v1/favicon.go b/internal/api/v1/favicon.go index 835a43c1..6619eeb4 100644 --- a/internal/api/v1/favicon.go +++ b/internal/api/v1/favicon.go @@ -74,6 +74,9 @@ func FavIcon(c *gin.Context) { func GetFavIconFromAlias(ctx context.Context, alias string, variant icons.Variant) (iconfetch.Result, error) { // try with route.Icon ep := entrypoint.FromCtx(ctx) + if ep == nil { // impossible, but just in case + return iconfetch.FetchResultWithErrorf(http.StatusInternalServerError, "entrypoint not initialized") + } r, ok := ep.HTTPRoutes().Get(alias) if !ok { return iconfetch.FetchResultWithErrorf(http.StatusNotFound, "route not found") diff --git a/internal/api/v1/health.go b/internal/api/v1/health.go index 7b5fc960..d2c9c2cb 100644 --- a/internal/api/v1/health.go +++ b/internal/api/v1/health.go @@ -6,6 +6,7 @@ import ( "github.com/gin-gonic/gin" entrypoint "github.com/yusing/godoxy/internal/entrypoint/types" + apitypes "github.com/yusing/goutils/apitypes" "github.com/yusing/goutils/http/httpheaders" "github.com/yusing/goutils/http/websocket" @@ -25,6 +26,10 @@ import ( // @Router /health [get] func Health(c *gin.Context) { ep := entrypoint.FromCtx(c.Request.Context()) + if ep == nil { // impossible, but just in case + c.JSON(http.StatusInternalServerError, apitypes.Error("entrypoint not initialized")) + return + } if httpheaders.IsWebsocket(c.Request.Header) { websocket.PeriodicWrite(c, 1*time.Second, func() (any, error) { return ep.GetHealthInfoSimple(), nil diff --git a/internal/api/v1/homepage/categories.go b/internal/api/v1/homepage/categories.go index a361cfe8..8885d27c 100644 --- a/internal/api/v1/homepage/categories.go +++ b/internal/api/v1/homepage/categories.go @@ -8,6 +8,7 @@ import ( "github.com/yusing/godoxy/internal/homepage" _ "github.com/yusing/goutils/apitypes" + apitypes "github.com/yusing/goutils/apitypes" ) // @x-id "categories" @@ -22,6 +23,10 @@ import ( // @Router /homepage/categories [get] func Categories(c *gin.Context) { ep := entrypoint.FromCtx(c.Request.Context()) + if ep == nil { // impossible, but just in case + c.JSON(http.StatusInternalServerError, apitypes.Error("entrypoint not initialized")) + return + } c.JSON(http.StatusOK, HomepageCategories(ep)) } diff --git a/internal/api/v1/route/by_provider.go b/internal/api/v1/route/by_provider.go index a31fe7fb..6026091c 100644 --- a/internal/api/v1/route/by_provider.go +++ b/internal/api/v1/route/by_provider.go @@ -8,6 +8,7 @@ import ( "github.com/yusing/godoxy/internal/route" _ "github.com/yusing/goutils/apitypes" + apitypes "github.com/yusing/goutils/apitypes" ) type RoutesByProvider map[string][]route.Route @@ -25,5 +26,9 @@ type RoutesByProvider map[string][]route.Route // @Router /route/by_provider [get] func ByProvider(c *gin.Context) { ep := entrypoint.FromCtx(c.Request.Context()) + if ep == nil { // impossible, but just in case + c.JSON(http.StatusInternalServerError, apitypes.Error("entrypoint not initialized")) + return + } c.JSON(http.StatusOK, ep.RoutesByProvider()) } diff --git a/internal/api/v1/route/route.go b/internal/api/v1/route/route.go index d732b0ec..fa118757 100644 --- a/internal/api/v1/route/route.go +++ b/internal/api/v1/route/route.go @@ -33,6 +33,11 @@ func Route(c *gin.Context) { } ep := entrypoint.FromCtx(c.Request.Context()) + if ep == nil { // impossible, but just in case + c.JSON(http.StatusInternalServerError, apitypes.Error("entrypoint not initialized")) + return + } + route, ok := ep.GetRoute(request.Which) if ok { c.JSON(http.StatusOK, route) diff --git a/internal/config/types/config.go b/internal/config/types/config.go index f9ce7312..a982c438 100644 --- a/internal/config/types/config.go +++ b/internal/config/types/config.go @@ -8,7 +8,7 @@ import ( "github.com/yusing/godoxy/agent/pkg/agent" "github.com/yusing/godoxy/internal/acl" "github.com/yusing/godoxy/internal/autocert" - entrypoint "github.com/yusing/godoxy/internal/entrypoint/types" + entrypoint "github.com/yusing/godoxy/internal/entrypoint" homepage "github.com/yusing/godoxy/internal/homepage/types" maxmind "github.com/yusing/godoxy/internal/maxmind/types" "github.com/yusing/godoxy/internal/notif" diff --git a/internal/entrypoint/types/config.go b/internal/entrypoint/config.go similarity index 100% rename from internal/entrypoint/types/config.go rename to internal/entrypoint/config.go diff --git a/internal/entrypoint/entrypoint.go b/internal/entrypoint/entrypoint.go index 5da7cebc..ae7d3d29 100644 --- a/internal/entrypoint/entrypoint.go +++ b/internal/entrypoint/entrypoint.go @@ -27,7 +27,7 @@ type findRouteFunc func(HTTPRoutes, string) types.HTTPRoute type Entrypoint struct { task *task.Task - cfg *entrypoint.Config + cfg *Config middleware *middleware.Middleware notFoundHandler http.Handler @@ -48,9 +48,9 @@ type Entrypoint struct { var _ entrypoint.Entrypoint = &Entrypoint{} -var emptyCfg entrypoint.Config +var emptyCfg Config -func NewEntrypoint(parent task.Parent, cfg *entrypoint.Config) *Entrypoint { +func NewEntrypoint(parent task.Parent, cfg *Config) *Entrypoint { if cfg == nil { cfg = &emptyCfg } @@ -91,12 +91,23 @@ func NewEntrypoint(parent task.Parent, cfg *entrypoint.Config) *Entrypoint { return ep } -func (ep *Entrypoint) ShortLinkMatcher() *ShortLinkMatcher { - return ep.shortLinkMatcher +func (ep *Entrypoint) SupportProxyProtocol() bool { + return ep.cfg.SupportProxyProtocol } -func (ep *Entrypoint) Config() *entrypoint.Config { - return ep.cfg +func (ep *Entrypoint) DisablePoolsLog(v bool) { + ep.httpPoolDisableLog.Store(v) + // apply to all running http servers + for _, srv := range ep.servers.Range { + srv.routes.DisableLog(v) + } + // apply to other pools + ep.streamRoutes.DisableLog(v) + ep.excludedRoutes.DisableLog(v) +} + +func (ep *Entrypoint) ShortLinkMatcher() *ShortLinkMatcher { + return ep.shortLinkMatcher } func (ep *Entrypoint) HTTPRoutes() entrypoint.PoolLike[types.HTTPRoute] { @@ -111,19 +122,12 @@ func (ep *Entrypoint) ExcludedRoutes() entrypoint.RWPoolLike[types.Route] { return ep.excludedRoutes } -func (ep *Entrypoint) GetServer(addr string) (*httpServer, bool) { +func (ep *Entrypoint) GetServer(addr string) (http.Handler, bool) { return ep.servers.Load(addr) } -func (ep *Entrypoint) DisablePoolsLog(v bool) { - ep.httpPoolDisableLog.Store(v) - // apply to all running http servers - for _, srv := range ep.servers.Range { - srv.routes.DisableLog(v) - } - // apply to other pools - ep.streamRoutes.DisableLog(v) - ep.excludedRoutes.DisableLog(v) +func (ep *Entrypoint) PrintServers() { + log.Info().Msgf("servers: %v", xsync.ToPlainMap(ep.servers)) } func (ep *Entrypoint) SetFindRouteDomains(domains []string) { diff --git a/internal/entrypoint/entrypoint_benchmark_test.go b/internal/entrypoint/entrypoint_benchmark_test.go index fef26765..adc1ad75 100644 --- a/internal/entrypoint/entrypoint_benchmark_test.go +++ b/internal/entrypoint/entrypoint_benchmark_test.go @@ -11,6 +11,7 @@ import ( "testing" . "github.com/yusing/godoxy/internal/entrypoint" + entrypoint "github.com/yusing/godoxy/internal/entrypoint/types" "github.com/yusing/godoxy/internal/route" routeTypes "github.com/yusing/godoxy/internal/route/types" "github.com/yusing/godoxy/internal/types" @@ -47,13 +48,15 @@ func (t noopTransport) RoundTrip(req *http.Request) (*http.Response, error) { } func BenchmarkEntrypointReal(b *testing.B) { - var ep Entrypoint + task := task.NewTestTask(b) + ep := NewEntrypoint(task, nil) req := http.Request{ Method: "GET", URL: &url.URL{Path: "/", RawPath: "/"}, Host: "test.domain.tld", } ep.SetFindRouteDomains([]string{}) + entrypoint.SetCtx(task, ep) srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Length", "1") @@ -89,7 +92,7 @@ func BenchmarkEntrypointReal(b *testing.B) { b.Fatal(err) } - err = r.Start(task.NewTestTask(b)) + err = r.Start(task) if err != nil { b.Fatal(err) } @@ -114,13 +117,15 @@ func BenchmarkEntrypointReal(b *testing.B) { } func BenchmarkEntrypoint(b *testing.B) { - var ep Entrypoint + task := task.NewTestTask(b) + ep := NewEntrypoint(task, nil) req := http.Request{ Method: "GET", URL: &url.URL{Path: "/", RawPath: "/"}, Host: "test.domain.tld", } ep.SetFindRouteDomains([]string{}) + entrypoint.SetCtx(task, ep) r := &route.Route{ Alias: "test", @@ -139,7 +144,7 @@ func BenchmarkEntrypoint(b *testing.B) { b.Fatal(err) } - err = r.Start(task.RootTask("test", false)) + err = r.Start(task) if err != nil { b.Fatal(err) } diff --git a/internal/entrypoint/routes.go b/internal/entrypoint/routes.go index 03373683..9fe709d8 100644 --- a/internal/entrypoint/routes.go +++ b/internal/entrypoint/routes.go @@ -74,11 +74,14 @@ func (ep *Entrypoint) AddRoute(r types.Route) { func (ep *Entrypoint) AddHTTPRoute(route types.HTTPRoute) error { if port := route.ListenURL().Port(); port == "" || port == "0" { host := route.ListenURL().Hostname() + var httpAddr, httpsAddr string if host == "" { - host = common.ProxyHTTPHost + httpAddr = common.ProxyHTTPAddr + httpsAddr = common.ProxyHTTPSAddr + } else { + httpAddr = net.JoinHostPort(host, strconv.Itoa(common.ProxyHTTPPort)) + httpsAddr = net.JoinHostPort(host, strconv.Itoa(common.ProxyHTTPSPort)) } - httpAddr := net.JoinHostPort(host, strconv.Itoa(common.ProxyHTTPPort)) - httpsAddr := net.JoinHostPort(host, strconv.Itoa(common.ProxyHTTPSPort)) return errors.Join(ep.addHTTPRoute(route, httpAddr, HTTPProtoHTTP), ep.addHTTPRoute(route, httpsAddr, HTTPProtoHTTPS)) } diff --git a/internal/entrypoint/shortlink_test.go b/internal/entrypoint/shortlink_test.go index 1d60fdc4..c260eabd 100644 --- a/internal/entrypoint/shortlink_test.go +++ b/internal/entrypoint/shortlink_test.go @@ -165,7 +165,7 @@ func TestEntrypoint_ShortLinkDispatch(t *testing.T) { ep.ShortLinkMatcher().AddRoute("app") server := NewHTTPServer(ep) - err := server.Listen("localhost:8080", HTTPProtoHTTP) + err := server.Listen("localhost:0", HTTPProtoHTTP) require.NoError(t, err) t.Run("shortlink host", func(t *testing.T) { diff --git a/internal/entrypoint/types/entrypoint.go b/internal/entrypoint/types/entrypoint.go index f0543bf1..f2ed5ca3 100644 --- a/internal/entrypoint/types/entrypoint.go +++ b/internal/entrypoint/types/entrypoint.go @@ -5,7 +5,7 @@ import ( ) type Entrypoint interface { - Config() *Config + SupportProxyProtocol() bool DisablePoolsLog(v bool) diff --git a/internal/route/fileserver.go b/internal/route/fileserver.go index 62bfb408..ee285d30 100644 --- a/internal/route/fileserver.go +++ b/internal/route/fileserver.go @@ -126,7 +126,11 @@ func (s *FileServer) Start(parent task.Parent) gperr.Error { } } - entrypoint.FromCtx(parent.Context()).AddRoute(s) + ep := entrypoint.FromCtx(parent.Context()) + if ep == nil { + return gperr.New("entrypoint not initialized") + } + ep.AddRoute(s) return nil } diff --git a/internal/route/reverse_proxy.go b/internal/route/reverse_proxy.go index 22a61658..1f2c1254 100755 --- a/internal/route/reverse_proxy.go +++ b/internal/route/reverse_proxy.go @@ -163,10 +163,15 @@ func (r *ReveseProxyRoute) Start(parent task.Parent) gperr.Error { } } + ep := entrypoint.FromCtx(parent.Context()) + if ep == nil { + return gperr.New("entrypoint not initialized") + } + if r.UseLoadBalance() { - r.addToLoadBalancer(parent) + r.addToLoadBalancer(parent, ep) } else { - entrypoint.FromCtx(parent.Context()).AddRoute(r) + ep.AddRoute(r) } return nil } @@ -178,12 +183,11 @@ func (r *ReveseProxyRoute) ServeHTTP(w http.ResponseWriter, req *http.Request) { var lbLock sync.Mutex -func (r *ReveseProxyRoute) addToLoadBalancer(parent task.Parent) { +func (r *ReveseProxyRoute) addToLoadBalancer(parent task.Parent, ep entrypoint.Entrypoint) { var lb *loadbalancer.LoadBalancer cfg := r.LoadBalance lbLock.Lock() - ep := entrypoint.FromCtx(r.task.Context()) l, ok := ep.HTTPRoutes().Get(cfg.Link) var linked *ReveseProxyRoute if ok { diff --git a/internal/route/route.go b/internal/route/route.go index f5c0a7db..a0c70d5b 100644 --- a/internal/route/route.go +++ b/internal/route/route.go @@ -46,7 +46,7 @@ type ( Host string `json:"host,omitempty"` Port route.Port `json:"port"` - Bind string `json:"bind,omitempty" validate:"omitempty,dive,ip_addr" extensions:"x-nullable"` + Bind string `json:"bind,omitempty" validate:"omitempty,ip_addr" extensions:"x-nullable"` Root string `json:"root,omitempty"` SPA bool `json:"spa,omitempty"` // Single-page app mode: serves index for non-existent paths @@ -199,7 +199,11 @@ func (r *Route) validate() gperr.Error { if (r.Proxmox == nil || r.Proxmox.Node == "" || r.Proxmox.VMID == nil) && r.Container == nil { wasNotNil := r.Proxmox != nil - proxmoxProviders := config.WorkingState.Load().Value().Providers.Proxmox + workingState := config.WorkingState.Load() + var proxmoxProviders []*proxmox.Config + if workingState != nil { // nil in tests + proxmoxProviders = workingState.Value().Providers.Proxmox + } if len(proxmoxProviders) > 0 { // it's fine if ip is nil hostname := r.Host diff --git a/internal/route/stream.go b/internal/route/stream.go index 57703f72..1d10f17a 100755 --- a/internal/route/stream.go +++ b/internal/route/stream.go @@ -82,7 +82,11 @@ func (r *StreamRoute) Start(parent task.Parent) gperr.Error { r.l.Info().Msg("stream closed") }) - entrypoint.FromCtx(parent.Context()).AddRoute(r) + ep := entrypoint.FromCtx(parent.Context()) + if ep == nil { + return gperr.New("entrypoint not initialized") + } + ep.AddRoute(r) return nil } diff --git a/internal/route/stream/tcp_tcp.go b/internal/route/stream/tcp_tcp.go index d84a166f..8f7bf95a 100644 --- a/internal/route/stream/tcp_tcp.go +++ b/internal/route/stream/tcp_tcp.go @@ -58,8 +58,10 @@ func (s *TCPTCPStream) ListenAndServe(ctx context.Context, preDial, onRead netty s.listener = acl.WrapTCP(s.listener) } - if proxyProto := entrypoint.FromCtx(ctx).Config().SupportProxyProtocol; proxyProto { - s.listener = &proxyproto.Listener{Listener: s.listener} + if ep := entrypoint.FromCtx(ctx); ep != nil { + if proxyProto := entrypoint.FromCtx(ctx).SupportProxyProtocol(); proxyProto { + s.listener = &proxyproto.Listener{Listener: s.listener} + } } s.preDial = preDial