From 9b99ab4d13a6f8cbb889c8b059f3712aa8d8bbd4 Mon Sep 17 00:00:00 2001 From: Avinash Kumar Deepak Date: Wed, 17 Jun 2026 23:29:41 +0530 Subject: [PATCH] router: match longest entrypoint path prefix Signed-off-by: Avinash Kumar Deepak --- pkg/router/handlers.go | 21 ++++++++++++++-- pkg/router/handlers_test.go | 49 +++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/pkg/router/handlers.go b/pkg/router/handlers.go index 3b44d3fa0..05d01d2db 100644 --- a/pkg/router/handlers.go +++ b/pkg/router/handlers.go @@ -107,11 +107,17 @@ func (s *Server) handleGetSandboxError(c *gin.Context, err error) { func determineUpstreamURL(sandbox *types.SandboxInfo, path string) (*url.URL, error) { // prefer matched entrypoint by path + var matched types.SandboxEntryPoint + found := false for _, ep := range sandbox.EntryPoints { - if strings.HasPrefix(path, ep.Path) { - return buildURL(ep.Protocol, ep.Endpoint) + if entryPointPathMatches(path, ep.Path) && (!found || len(ep.Path) > len(matched.Path)) { + matched = ep + found = true } } + if found { + return buildURL(matched.Protocol, matched.Endpoint) + } // fallback to first entrypoint if len(sandbox.EntryPoints) == 0 { return nil, fmt.Errorf("no entry point found for sandbox") @@ -120,6 +126,17 @@ func determineUpstreamURL(sandbox *types.SandboxInfo, path string) (*url.URL, er return buildURL(ep.Protocol, ep.Endpoint) } +func entryPointPathMatches(path, prefix string) bool { + if path != "" && !strings.HasPrefix(path, "/") { + path = "/" + path + } + prefix = strings.TrimSuffix(prefix, "/") + if prefix == "" { + return true + } + return path == prefix || strings.HasPrefix(path, prefix+"/") +} + func buildURL(protocol, endpoint string) (*url.URL, error) { if protocol != "" && !strings.Contains(endpoint, "://") { endpoint = (strings.ToLower(protocol) + "://" + endpoint) diff --git a/pkg/router/handlers_test.go b/pkg/router/handlers_test.go index 8eb168965..b69ddea69 100644 --- a/pkg/router/handlers_test.go +++ b/pkg/router/handlers_test.go @@ -241,6 +241,55 @@ func TestHandleInvoke_NoEntryPoints(t *testing.T) { } } +func TestDetermineUpstreamURL_PathPrefix(t *testing.T) { + tests := []struct { + name string + path string + entryPoints []types.SandboxEntryPoint + wantHost string + }{ + { + name: "longest prefix wins", + path: "/api/run", + entryPoints: []types.SandboxEntryPoint{ + {Endpoint: "10.0.0.1:8080", Protocol: "HTTP", Path: "/"}, + {Endpoint: "10.0.0.2:8080", Protocol: "HTTP", Path: "/api"}, + }, + wantHost: "10.0.0.2:8080", + }, + { + name: "prefix must end on boundary", + path: "/api2/run", + entryPoints: []types.SandboxEntryPoint{ + {Endpoint: "10.0.0.1:8080", Protocol: "HTTP", Path: "/api"}, + {Endpoint: "10.0.0.2:8080", Protocol: "HTTP", Path: "/"}, + }, + wantHost: "10.0.0.2:8080", + }, + { + name: "normalizes slashes", + path: "api/run", + entryPoints: []types.SandboxEntryPoint{ + {Endpoint: "10.0.0.1:8080", Protocol: "HTTP", Path: "/api/"}, + }, + wantHost: "10.0.0.1:8080", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + sandbox := &types.SandboxInfo{EntryPoints: tt.entryPoints} + upstreamURL, err := determineUpstreamURL(sandbox, tt.path) + if err != nil { + t.Fatalf("determineUpstreamURL returned error: %v", err) + } + if upstreamURL.Host != tt.wantHost { + t.Fatalf("expected host %s, got %s", tt.wantHost, upstreamURL.Host) + } + }) + } +} + func TestHandleAgentInvoke(t *testing.T) { // Set required environment variables setupEnv()