fix(logic): invalidate proxy cache on config reload, remove dead code
Implement ConfigReloadProxyHandler to invalidate proxy target cache when configuration changes occur. Add RefreshTargets method for manual cache invalidation. Wire up handler in container service. Remove unused dead code from auth middleware.
- commit
- acb0df2928681f9b5df130c38b3fe28ee39aae28
- author
- brice · · 2026-02-06 05:32 UTC
- parent
- 4363057
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
4 files · +49 -2
M internal/adapters/in/http/middleware/auth.go +1 -2
| @@ -303,8 +303,7 @@ func isLocalhostRequest(r *http.Request) bool { | |||
| 303 | 303 | // RemoteAddr includes port, e.g., "127.0.0.1:12345" or "[::1]:12345" | |
| 304 | 304 | if strings.HasPrefix(host, "127.") || | |
| 305 | 305 | strings.HasPrefix(host, "[::1]") || | |
| 306 | - | strings.HasPrefix(host, "::1") || | |
| 307 | - | strings.HasPrefix(host, "localhost") { | |
| 306 | + | strings.HasPrefix(host, "::1") { | |
| 308 | 307 | return true | |
| 309 | 308 | } | |
| 310 | 309 | return false | |
| @@ -303,8 +303,7 @@ func isLocalhostRequest(r *http.Request) bool { | |||||
| 303 | // RemoteAddr includes port, e.g., "127.0.0.1:12345" or "[::1]:12345" | 303 | // RemoteAddr includes port, e.g., "127.0.0.1:12345" or "[::1]:12345" | ||
| 304 | if strings.HasPrefix(host, "127.") || | 304 | if strings.HasPrefix(host, "127.") || | ||
| 305 | strings.HasPrefix(host, "[::1]") || | 305 | strings.HasPrefix(host, "[::1]") || | ||
| 306 | - | strings.HasPrefix(host, "::1") || | 306 | + | strings.HasPrefix(host, "::1") { |
| 307 | - | strings.HasPrefix(host, "localhost") { | |||
| 308 | return true | 307 | return true | ||
| 309 | } | 308 | } | ||
| 310 | return false | 309 | return false | ||
M internal/app/run.go +6 -0
| @@ -912,6 +912,12 @@ func registerEventHandlers(ctx context.Context, svc *services, cfg Config) error | |||
| 912 | 912 | return fmt.Errorf("failed to subscribe container deployed handler: %w", err) | |
| 913 | 913 | } | |
| 914 | 914 | ||
| 915 | + | // Proxy cache invalidation on config reload (clears stale targets for removed routes) | |
| 916 | + | configReloadProxyHandler := proxy.NewConfigReloadProxyHandler(ctx, svc.proxySvc) | |
| 917 | + | if err := svc.eventBus.Subscribe(configReloadProxyHandler); err != nil { | |
| 918 | + | return fmt.Errorf("failed to subscribe config reload proxy handler: %w", err) | |
| 919 | + | } | |
| 920 | + | ||
| 915 | 921 | return nil | |
| 916 | 922 | } | |
| 917 | 923 | ||
| @@ -912,6 +912,12 @@ func registerEventHandlers(ctx context.Context, svc *services, cfg Config) error | |||||
| 912 | return fmt.Errorf("failed to subscribe container deployed handler: %w", err) | 912 | return fmt.Errorf("failed to subscribe container deployed handler: %w", err) | ||
| 913 | } | 913 | } | ||
| 914 | 914 | ||||
| 915 | + | // Proxy cache invalidation on config reload (clears stale targets for removed routes) | |||
| 916 | + | configReloadProxyHandler := proxy.NewConfigReloadProxyHandler(ctx, svc.proxySvc) | |||
| 917 | + | if err := svc.eventBus.Subscribe(configReloadProxyHandler); err != nil { | |||
| 918 | + | return fmt.Errorf("failed to subscribe config reload proxy handler: %w", err) | |||
| 919 | + | } | |||
| 920 | + | ||||
| 915 | return nil | 921 | return nil | ||
| 916 | } | 922 | } | ||
| 917 | 923 | ||||
M internal/usecase/proxy/events.go +40 -0
| @@ -61,3 +61,43 @@ func (h *ContainerDeployedHandler) Handle(event domain.Event) error { | |||
| 61 | 61 | func (h *ContainerDeployedHandler) CanHandle(eventType domain.EventType) bool { | |
| 62 | 62 | return eventType == domain.EventContainerDeployed | |
| 63 | 63 | } | |
| 64 | + | ||
| 65 | + | // TargetRefresher defines the interface for refreshing all proxy targets. | |
| 66 | + | type TargetRefresher interface { | |
| 67 | + | RefreshTargets(ctx context.Context) error | |
| 68 | + | } | |
| 69 | + | ||
| 70 | + | // ConfigReloadProxyHandler handles config.reload events to clear the proxy target cache. | |
| 71 | + | // When routes are removed during a config reload, cached targets for those routes | |
| 72 | + | // become stale. This handler clears all cached targets so they are re-resolved. | |
| 73 | + | type ConfigReloadProxyHandler struct { | |
| 74 | + | refresher TargetRefresher | |
| 75 | + | ctx context.Context | |
| 76 | + | } | |
| 77 | + | ||
| 78 | + | // NewConfigReloadProxyHandler creates a new ConfigReloadProxyHandler. | |
| 79 | + | func NewConfigReloadProxyHandler(ctx context.Context, refresher TargetRefresher) *ConfigReloadProxyHandler { | |
| 80 | + | return &ConfigReloadProxyHandler{ | |
| 81 | + | refresher: refresher, | |
| 82 | + | ctx: ctx, | |
| 83 | + | } | |
| 84 | + | } | |
| 85 | + | ||
| 86 | + | // Handle clears all cached proxy targets so they are re-resolved from current state. | |
| 87 | + | func (h *ConfigReloadProxyHandler) Handle(event domain.Event) error { | |
| 88 | + | ctx := zerowrap.CtxWithFields(h.ctx, map[string]any{ | |
| 89 | + | zerowrap.FieldLayer: "usecase", | |
| 90 | + | zerowrap.FieldHandler: "ConfigReloadProxyHandler", | |
| 91 | + | zerowrap.FieldEvent: string(event.Type), | |
| 92 | + | "event_id": event.ID, | |
| 93 | + | }) | |
| 94 | + | log := zerowrap.FromCtx(ctx) | |
| 95 | + | ||
| 96 | + | log.Debug().Msg("clearing proxy target cache after config reload") | |
| 97 | + | return h.refresher.RefreshTargets(ctx) | |
| 98 | + | } | |
| 99 | + | ||
| 100 | + | // CanHandle returns whether this handler can handle the given event type. | |
| 101 | + | func (h *ConfigReloadProxyHandler) CanHandle(eventType domain.EventType) bool { | |
| 102 | + | return eventType == domain.EventConfigReload | |
| 103 | + | } | |
| @@ -61,3 +61,43 @@ func (h *ContainerDeployedHandler) Handle(event domain.Event) error { | |||||
| 61 | func (h *ContainerDeployedHandler) CanHandle(eventType domain.EventType) bool { | 61 | func (h *ContainerDeployedHandler) CanHandle(eventType domain.EventType) bool { | ||
| 62 | return eventType == domain.EventContainerDeployed | 62 | return eventType == domain.EventContainerDeployed | ||
| 63 | } | 63 | } | ||
| 64 | + | ||||
| 65 | + | // TargetRefresher defines the interface for refreshing all proxy targets. | |||
| 66 | + | type TargetRefresher interface { | |||
| 67 | + | RefreshTargets(ctx context.Context) error | |||
| 68 | + | } | |||
| 69 | + | ||||
| 70 | + | // ConfigReloadProxyHandler handles config.reload events to clear the proxy target cache. | |||
| 71 | + | // When routes are removed during a config reload, cached targets for those routes | |||
| 72 | + | // become stale. This handler clears all cached targets so they are re-resolved. | |||
| 73 | + | type ConfigReloadProxyHandler struct { | |||
| 74 | + | refresher TargetRefresher | |||
| 75 | + | ctx context.Context | |||
| 76 | + | } | |||
| 77 | + | ||||
| 78 | + | // NewConfigReloadProxyHandler creates a new ConfigReloadProxyHandler. | |||
| 79 | + | func NewConfigReloadProxyHandler(ctx context.Context, refresher TargetRefresher) *ConfigReloadProxyHandler { | |||
| 80 | + | return &ConfigReloadProxyHandler{ | |||
| 81 | + | refresher: refresher, | |||
| 82 | + | ctx: ctx, | |||
| 83 | + | } | |||
| 84 | + | } | |||
| 85 | + | ||||
| 86 | + | // Handle clears all cached proxy targets so they are re-resolved from current state. | |||
| 87 | + | func (h *ConfigReloadProxyHandler) Handle(event domain.Event) error { | |||
| 88 | + | ctx := zerowrap.CtxWithFields(h.ctx, map[string]any{ | |||
| 89 | + | zerowrap.FieldLayer: "usecase", | |||
| 90 | + | zerowrap.FieldHandler: "ConfigReloadProxyHandler", | |||
| 91 | + | zerowrap.FieldEvent: string(event.Type), | |||
| 92 | + | "event_id": event.ID, | |||
| 93 | + | }) | |||
| 94 | + | log := zerowrap.FromCtx(ctx) | |||
| 95 | + | ||||
| 96 | + | log.Debug().Msg("clearing proxy target cache after config reload") | |||
| 97 | + | return h.refresher.RefreshTargets(ctx) | |||
| 98 | + | } | |||
| 99 | + | ||||
| 100 | + | // CanHandle returns whether this handler can handle the given event type. | |||
| 101 | + | func (h *ConfigReloadProxyHandler) CanHandle(eventType domain.EventType) bool { | |||
| 102 | + | return eventType == domain.EventConfigReload | |||
| 103 | + | } | |||
M internal/usecase/proxy/service.go +2 -0
| @@ -281,7 +281,9 @@ func (s *Service) RefreshTargets(ctx context.Context) error { | |||
| 281 | 281 | ||
| 282 | 282 | // UpdateConfig updates the service configuration. | |
| 283 | 283 | func (s *Service) UpdateConfig(config Config) { | |
| 284 | + | s.mu.Lock() | |
| 284 | 285 | s.config = config | |
| 286 | + | s.mu.Unlock() | |
| 285 | 287 | } | |
| 286 | 288 | ||
| 287 | 289 | // Helper methods | |
| @@ -281,7 +281,9 @@ func (s *Service) RefreshTargets(ctx context.Context) error { | |||||
| 281 | 281 | ||||
| 282 | // UpdateConfig updates the service configuration. | 282 | // UpdateConfig updates the service configuration. | ||
| 283 | func (s *Service) UpdateConfig(config Config) { | 283 | func (s *Service) UpdateConfig(config Config) { | ||
| 284 | + | s.mu.Lock() | |||
| 284 | s.config = config | 285 | s.config = config | ||
| 286 | + | s.mu.Unlock() | |||
| 285 | } | 287 | } | ||
| 286 | 288 | ||||
| 287 | // Helper methods | 289 | // Helper methods | ||