Skip to content

Unable to get response when Prefix is configured in http gateway #4813

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wisonlau opened this issue Apr 28, 2025 · 3 comments
Open

Unable to get response when Prefix is configured in http gateway #4813

wisonlau opened this issue Apr 28, 2025 · 3 comments
Assignees
Labels
area/gateway Categorizes issue or PR as related to gateway. bug

Comments

@wisonlau
Copy link

wisonlau commented Apr 28, 2025

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
@kevwan kevwan changed the title http gateway的配置写上Prefix就无法获取响应 Unable to get response when Prefix is configured in http gateway May 1, 2025
@kevwan kevwan self-assigned this May 1, 2025
@kevwan kevwan added bug area/gateway Categorizes issue or PR as related to gateway. labels May 2, 2025
@kevwan
Copy link
Contributor

kevwan commented May 2, 2025

Hello @wisonlau,

I've written a test case that reproduces both issues you reported:

  1. Unable to get response when Prefix is added
  2. 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.

@wisonlau
Copy link
Author

wisonlau commented May 2, 2025

v1.8.2

gateway_gozero.zip

@kevwan
Copy link
Contributor

kevwan commented May 2, 2025

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:

  1. When you configure a Prefix like /test for an upstream with a path like /hello, the gateway forwards requests to /test/hello, not to /hello on a backend that expects /test/hello.

  2. 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
  1. 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:

  1. With URI in proxy_pass: Replaces the matched location with the specified URI
  2. 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:

  1. For the prefix issue: Define full paths in your mappings as shown above
  2. For the duplicate routes issue: Use unique path patterns for each service, such as:
    • /service1/hello for one service
    • /service2/hello for 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/gateway Categorizes issue or PR as related to gateway. bug
Projects
None yet
Development

No branches or pull requests

2 participants