mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-09-24 16:05:44 +08:00
fix: normalize inbound endpoints from raw request path
This commit is contained in:
@@ -235,10 +235,13 @@ func responsesSubpathSuffix(rawPath string) string {
|
||||
// Apply this middleware to all gateway route groups.
|
||||
func InboundEndpointMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
path := c.FullPath()
|
||||
if path == "" && c.Request != nil && c.Request.URL != nil {
|
||||
path := ""
|
||||
if c.Request != nil && c.Request.URL != nil {
|
||||
path = c.Request.URL.Path
|
||||
}
|
||||
if path == "" {
|
||||
path = c.FullPath()
|
||||
}
|
||||
c.Set(ctxKeyInboundEndpoint, NormalizeInboundEndpoint(path))
|
||||
c.Next()
|
||||
}
|
||||
@@ -251,7 +254,11 @@ func InboundEndpointMiddleware() gin.HandlerFunc {
|
||||
|
||||
// GetInboundEndpoint returns the canonical inbound endpoint stored by
|
||||
// InboundEndpointMiddleware. If the middleware did not run (e.g. in
|
||||
// tests), it falls back to normalizing c.FullPath() on the fly.
|
||||
// tests), it falls back to normalizing c.Request.URL.Path on the fly
|
||||
// (preferring the raw request path over c.FullPath(), which collapses
|
||||
// wildcard route patterns such as "/v1/responses/*subpath" and would
|
||||
// otherwise mis-normalize concrete requests like "/v1/responses/compact"
|
||||
// to the root Responses endpoint).
|
||||
func GetInboundEndpoint(c *gin.Context) string {
|
||||
if v, ok := c.Get(ctxKeyInboundEndpoint); ok {
|
||||
if s, ok := v.(string); ok && s != "" {
|
||||
@@ -261,10 +268,12 @@ func GetInboundEndpoint(c *gin.Context) string {
|
||||
// Fallback: normalize on the fly.
|
||||
path := ""
|
||||
if c != nil {
|
||||
path = c.FullPath()
|
||||
if path == "" && c.Request != nil && c.Request.URL != nil {
|
||||
if c.Request != nil && c.Request.URL != nil {
|
||||
path = c.Request.URL.Path
|
||||
}
|
||||
if path == "" {
|
||||
path = c.FullPath()
|
||||
}
|
||||
}
|
||||
return NormalizeInboundEndpoint(path)
|
||||
}
|
||||
|
||||
@@ -63,16 +63,6 @@ func TestNormalizeInboundEndpoint(t *testing.T) {
|
||||
{"/foo/responses", "/foo/responses"},
|
||||
{"/foo/responses/compact", "/foo/responses/compact"},
|
||||
|
||||
// Gin route patterns with wildcards. The literal wildcard token
|
||||
// ("*subpath") is not the "compact" segment itself, so these
|
||||
// generic FullPath patterns normalize to the root Responses
|
||||
// endpoint; only a concrete "compact" path segment (tested above)
|
||||
// resolves to EndpointResponsesCompact.
|
||||
{"/v1beta/models/*modelAction", EndpointGeminiModels},
|
||||
{"/v1/responses/*subpath", EndpointResponses},
|
||||
{"/responses/*subpath", EndpointResponses},
|
||||
{"/backend-api/codex/responses/*subpath", EndpointResponses},
|
||||
|
||||
// Unknown path is returned as-is.
|
||||
{"/v1/embeddings", "/v1/embeddings"},
|
||||
{"", ""},
|
||||
@@ -208,6 +198,91 @@ func TestGetInboundEndpoint_FallbackWithoutMiddleware(t *testing.T) {
|
||||
require.Equal(t, EndpointMessages, got)
|
||||
}
|
||||
|
||||
// TestInboundEndpointMiddleware_WildcardRoutes verifies that, when a
|
||||
// gateway route is registered with a Gin wildcard pattern (e.g.
|
||||
// "/v1/responses/*subpath"), InboundEndpointMiddleware normalizes based
|
||||
// on the concrete request path (c.Request.URL.Path) rather than the
|
||||
// route pattern (c.FullPath()). Using c.FullPath() here would collapse
|
||||
// every request under the wildcard — including "/v1/responses/compact"
|
||||
// — down to the literal pattern string, which never matches the
|
||||
// "compact" alias detection and would incorrectly normalize to the root
|
||||
// Responses endpoint.
|
||||
func TestInboundEndpointMiddleware_WildcardRoutes(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
routePath string
|
||||
requestPath string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "v1 responses wildcard route, compact request",
|
||||
routePath: "/v1/responses/*subpath",
|
||||
requestPath: "/v1/responses/compact",
|
||||
want: EndpointResponsesCompact,
|
||||
},
|
||||
{
|
||||
name: "bare responses wildcard route, compact request",
|
||||
routePath: "/responses/*subpath",
|
||||
requestPath: "/responses/compact",
|
||||
want: EndpointResponsesCompact,
|
||||
},
|
||||
{
|
||||
name: "codex direct wildcard route, compact request",
|
||||
routePath: "/backend-api/codex/responses/*subpath",
|
||||
requestPath: "/backend-api/codex/responses/compact",
|
||||
want: EndpointResponsesCompact,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
router := gin.New()
|
||||
router.Use(InboundEndpointMiddleware())
|
||||
|
||||
var captured string
|
||||
router.POST(tt.routePath, func(c *gin.Context) {
|
||||
captured = GetInboundEndpoint(c)
|
||||
c.Status(http.StatusOK)
|
||||
})
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, tt.requestPath, nil)
|
||||
rec := httptest.NewRecorder()
|
||||
router.ServeHTTP(rec, req)
|
||||
|
||||
require.Equal(t, http.StatusOK, rec.Code)
|
||||
require.Equal(t, tt.want, captured)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetInboundEndpoint_FallbackWildcardRouteWithoutMiddleware verifies
|
||||
// that when InboundEndpointMiddleware did NOT run (so no value is stored
|
||||
// in gin.Context), the GetInboundEndpoint fallback path still prefers
|
||||
// c.Request.URL.Path over c.FullPath(). This guards against the fallback
|
||||
// regressing to prefer c.FullPath() again, which would misnormalize
|
||||
// concrete requests matched by a wildcard route pattern (e.g.
|
||||
// "/v1/responses/*subpath" matching "/v1/responses/compact") down to
|
||||
// the root Responses endpoint.
|
||||
func TestGetInboundEndpoint_FallbackWildcardRouteWithoutMiddleware(t *testing.T) {
|
||||
router := gin.New()
|
||||
// Deliberately do NOT register InboundEndpointMiddleware.
|
||||
|
||||
var captured string
|
||||
router.POST("/v1/responses/*subpath", func(c *gin.Context) {
|
||||
// Sanity check: FullPath returns the route pattern, not the
|
||||
// concrete request path, when a wildcard route matches.
|
||||
require.Equal(t, "/v1/responses/*subpath", c.FullPath())
|
||||
captured = GetInboundEndpoint(c)
|
||||
c.Status(http.StatusOK)
|
||||
})
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/v1/responses/compact", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
router.ServeHTTP(rec, req)
|
||||
|
||||
require.Equal(t, http.StatusOK, rec.Code)
|
||||
require.Equal(t, EndpointResponsesCompact, captured)
|
||||
}
|
||||
|
||||
func TestGetUpstreamEndpoint_FullFlow(t *testing.T) {
|
||||
rec := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(rec)
|
||||
|
||||
Reference in New Issue
Block a user