go-zero
go-zero copied to clipboard
Unable to get response when Prefix is configured in http gateway
gateway.yaml
Problem 1:
Unable to get response when Prefix is added
- Name: testservice
Http:
Target: 127.0.0.1:10010
Prefix: /test
Timeout: 3000
Mappings:
- Method: GET
Path: /hello
- Method: POST
Path: /hi
The following configuration works normally
- Name: testservice
Http:
Target: 127.0.0.1:10010
Timeout: 3000
Mappings:
- Method: GET
Path: /test/hello
- Method: POST
Path: /test/hi
Problem 2: When one address has a prefix and another address doesn't; if the suffix names are the same, it cannot start
{"@timestamp":"2025-04-28T18:45:13.681+08:00","caller":"rest/server.go:324","content":"duplicated item for /hello","level":"error"}
- Name: userservice
Http:
Target: 127.0.0.1:10010
# Prefix: /
Timeout: 3000
Mappings:
- Method: GET
Path: /hello
- Method: POST
Path: /hi
- Name: testservice
Http:
Target: 127.0.0.1:10010
Prefix: /test
Timeout: 3000
Mappings:
- Method: GET
Path: /hello
- Method: POST
Path: /hi
gateway.yaml
问题1:
写上Prefix就无法获取响应
- Name: testservice
Http:
Target: 127.0.0.1:10010
Prefix: /test
Timeout: 3000
Mappings:
- Method: GET
Path: /hello
- Method: POST
Path: /hi
下面这样写则正常
- Name: testservice
Http:
Target: 127.0.0.1:10010
Timeout: 3000
Mappings:
- Method: GET
Path: /test/hello
- Method: POST
Path: /test/hi
问题2: 一个地址有前缀,一个地址没有前缀; 如果后缀的名字相同则无法启动
{"@timestamp":"2025-04-28T18:45:13.681+08:00","caller":"rest/server.go:324","content":"duplicated item for /hello","level":"error"}
- Name: userservice
Http:
Target: 127.0.0.1:10010
# Prefix: /
Timeout: 3000
Mappings:
- Method: GET
Path: /hello
- Method: POST
Path: /hi
- Name: testservice
Http:
Target: 127.0.0.1:10010
Prefix: /test
Timeout: 3000
Mappings:
- Method: GET
Path: /hello
- Method: POST
Path: /hi
Hello @wisonlau,
I've written a test case that reproduces both issues you reported:
- Unable to get response when Prefix is added
- Conflict when one address has a prefix and another doesn't when suffix names are the same
The interesting part is that when I run these tests against the current code in the master branch, they pass successfully. This suggests the issue might have been fixed in a newer version.
Here's the test code I used:
func TestHttpPrefixIssue(t *testing.T) {
server := startTestServer(t)
defer server.Close()
// Test case 1: When a Prefix is added to a service in the gateway configuration
t.Run("PrefixAndPathSetup", func(t *testing.T) {
var c GatewayConf
assert.NoError(t, conf.FillDefault(&c))
c.DevServer.Host = "localhost"
c.Host = "localhost"
c.Port = 19001
// Set up a service with Prefix configuration but path doesn't include the prefix
s := MustNewServer(c)
s.upstreams = []Upstream{
{
Name: "testservice",
Mappings: []RouteMapping{
{
Method: "get",
Path: "/hello",
},
},
Http: &HttpClientConf{
Target: "localhost:45678",
Prefix: "/test",
Timeout: 3000,
},
},
}
// Register the handler for the test server
http.HandleFunc("/test/hello", pingHandler)
go s.Start()
defer s.Stop()
time.Sleep(time.Millisecond * 200)
// Try to access the endpoint
resp, err := httpc.Do(context.Background(), http.MethodGet,
"http://localhost:19001/hello", nil)
assert.NoError(t, err)
assert.Equal(t, http.StatusOK, resp.StatusCode)
body, err := io.ReadAll(resp.Body)
if assert.NoError(t, err) {
assert.Equal(t, "pong", string(body))
}
})
// Test case 2: When one address has a prefix and another address doesn't,
// if the suffix names are the same, it cannot start (duplicated item error)
t.Run("ConflictingPrefixes", func(t *testing.T) {
var c GatewayConf
assert.NoError(t, conf.FillDefault(&c))
c.DevServer.Host = "localhost"
c.Host = "localhost"
c.Port = 19002
// Create a server with two upstreams that would conflict:
// 1. One with path "/hello" and no prefix
// 2. One with path "/hello" and prefix "/test"
s := MustNewServer(c)
s.upstreams = []Upstream{
{
Name: "userservice",
Mappings: []RouteMapping{
{
Method: "get",
Path: "/hello",
},
},
Http: &HttpClientConf{
Target: "localhost:45678",
Prefix: "/", // Effectively no prefix
Timeout: 3000,
},
},
{
Name: "testservice",
Mappings: []RouteMapping{
{
Method: "get",
Path: "/hello",
},
},
Http: &HttpClientConf{
Target: "localhost:45678",
Prefix: "/test",
Timeout: 3000,
},
},
}
// This should panic/fail with a "duplicated item" error
defer func() {
r := recover()
assert.NotNil(t, r, "Expected the server to panic due to duplicated routes")
errorMsg, ok := r.(string)
if ok {
assert.Contains(t, errorMsg, "duplicated", "Expected error message to mention 'duplicated'")
}
}()
// This should fail with duplicated item error
s.Start()
})
}
// Helper function used by the test
func pingHandler(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("pong"))
}
func startTestServer(t *testing.T) *http.Server {
http.HandleFunc("/api/ping", pingHandler)
server := &http.Server{
Addr: ":45678",
Handler: http.DefaultServeMux,
}
go func() {
if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
t.Errorf("failed to start server: %v", err)
}
}()
return server
}
Could you please let me know which version of go-zero you're using? This would help us understand if there's a regression in a specific version or if the issue was fixed in a newer release.
Hello @wisonlau,
Thank you for reporting this issue. I'd like to clarify how the current prefix handling works in our gateway component and propose a solution for the issues you're experiencing.
Current behavior
The current implementation of Prefix in the gateway is different from what you might expect. Here's how it works:
-
When you configure a
Prefixlike/testfor an upstream with a path like/hello, the gateway forwards requests to/test/hello, not to/helloon a backend that expects/test/hello. -
This means that if your backend API expects requests at paths like
/test/hello, you should not set a prefix but instead include the full path in your mappings:
Http:
Target: 127.0.0.1:10010
# No prefix
Timeout: 3000
Mappings:
- Method: GET
Path: /test/hello
- For your second issue with duplicate routes, this is expected behavior because our router doesn't distinguish between routes based on their upstream targets. When you have two mappings with the same HTTP method and path (like
/hello), the router sees them as duplicates even if they're meant for different backends.
Proposed enhancement
We understand that this behavior might not be what users expect, especially those familiar with Nginx's proxy_pass directive which has two different behaviors:
- With URI in proxy_pass: Replaces the matched location with the specified URI
- Without URI in proxy_pass: Strips the matched location prefix
We're considering enhancing our gateway to support both behaviors through new configuration options, similar to how Nginx works. This would give you more flexibility in how paths are handled when forwarded to backend services.
Workaround for now
For your current issue, you have a few workarounds:
- For the prefix issue: Define full paths in your mappings as shown above
- For the duplicate routes issue: Use unique path patterns for each service, such as:
/service1/hellofor one service/service2/hellofor another service
Thank you for helping us improve go-zero. We'll consider your feedback as we work on enhancing the gateway's path handling capabilities.