detached acb0df2

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 VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · 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 {
303303 // RemoteAddr includes port, e.g., "127.0.0.1:12345" or "[::1]:12345"
304304 if strings.HasPrefix(host, "127.") ||
305305 strings.HasPrefix(host, "[::1]") ||
306- strings.HasPrefix(host, "::1") ||
307- strings.HasPrefix(host, "localhost") {
306+ strings.HasPrefix(host, "::1") {
308307 return true
309308 }
310309 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 true307 return true
309 }308 }
310 return false309 return false
M internal/app/run.go +6 -0
@@ -912,6 +912,12 @@ func registerEventHandlers(ctx context.Context, svc *services, cfg Config) error
912912 return fmt.Errorf("failed to subscribe container deployed handler: %w", err)
913913 }
914914
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+
915921 return nil
916922 }
917923
@@ -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 nil921 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 {
6161 func (h *ContainerDeployedHandler) CanHandle(eventType domain.EventType) bool {
6262 return eventType == domain.EventContainerDeployed
6363 }
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.EventContainerDeployed62 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 {
281281
282282 // UpdateConfig updates the service configuration.
283283 func (s *Service) UpdateConfig(config Config) {
284+ s.mu.Lock()
284285 s.config = config
286+ s.mu.Unlock()
285287 }
286288
287289 // 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 = config285 s.config = config
286+ s.mu.Unlock()
285 }287 }
286 288
287 // Helper methods289 // Helper methods

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close