feat: support http->http in gateway (#4605)

This commit is contained in:
Kevin Wan
2025-01-27 20:00:58 +08:00
committed by GitHub
parent c71829c8de
commit d415ba39e2
6 changed files with 372 additions and 76 deletions

View File

@@ -2,9 +2,12 @@ package gateway
import (
"context"
"errors"
"io"
"log"
"net"
"net/http"
"net/http/httptest"
"testing"
"time"
@@ -65,7 +68,7 @@ func TestMustNewServer(t *testing.T) {
RpcPath: "mock.DepositService/Deposit",
},
},
Grpc: zrpc.RpcClientConf{
Grpc: &zrpc.RpcClientConf{
Endpoints: []string{"foo"},
Timeout: 1000,
Middlewares: zrpc.ClientMiddlewaresConf{
@@ -98,7 +101,7 @@ func TestServer_ensureUpstreamNames(t *testing.T) {
var s = Server{
upstreams: []Upstream{
{
Grpc: zrpc.RpcClientConf{
Grpc: &zrpc.RpcClientConf{
Target: "target",
},
},
@@ -113,7 +116,7 @@ func TestServer_ensureUpstreamNames_badEtcd(t *testing.T) {
var s = Server{
upstreams: []Upstream{
{
Grpc: zrpc.RpcClientConf{
Grpc: &zrpc.RpcClientConf{
Etcd: discov.EtcdConf{},
},
},
@@ -125,3 +128,193 @@ func TestServer_ensureUpstreamNames_badEtcd(t *testing.T) {
s.Start()
})
}
func TestHttpToHttp(t *testing.T) {
server := startTestServer(t)
defer server.Close()
var c GatewayConf
assert.NoError(t, conf.FillDefault(&c))
c.DevServer.Host = "localhost"
c.Host = "localhost"
c.Port = 18882
s := MustNewServer(c)
s.upstreams = []Upstream{
{
Name: "test",
Mappings: []RouteMapping{
{
Method: "get",
Path: "/api/ping",
},
},
Http: &HttpClientConf{
Target: "localhost:45678",
Timeout: 3000,
},
},
{
Mappings: []RouteMapping{
{
Method: "get",
Path: "/ping",
},
},
Http: &HttpClientConf{
Target: "localhost:45678",
Prefix: "/api",
},
},
}
go s.Start()
defer s.Stop()
time.Sleep(time.Millisecond * 200)
t.Run("/api/ping", func(t *testing.T) {
resp, err := httpc.Do(context.Background(), http.MethodGet,
"http://localhost:18882/api/ping", 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))
}
})
t.Run("/ping", func(t *testing.T) {
resp, err := httpc.Do(context.Background(), http.MethodGet,
"http://localhost:18882/ping", 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))
}
})
t.Run("no upstream", func(t *testing.T) {
resp, err := httpc.Do(context.Background(), http.MethodGet,
"http://localhost:18882/ping/bad", nil)
assert.NoError(t, err)
assert.Equal(t, http.StatusNotFound, resp.StatusCode)
})
}
func TestHttpToHttpBadUpstream(t *testing.T) {
var c GatewayConf
assert.NoError(t, conf.FillDefault(&c))
c.DevServer.Host = "localhost"
c.Host = "localhost"
c.Port = 18883
s := MustNewServer(c)
s.upstreams = []Upstream{
{
Mappings: []RouteMapping{
{
Method: "get",
Path: "/api/ping",
},
},
Http: &HttpClientConf{
Target: "localhost:45678",
Prefix: "\x7f/api",
},
},
}
go s.Start()
defer s.Stop()
time.Sleep(time.Millisecond * 200)
t.Run("/api/ping", func(t *testing.T) {
resp, err := httpc.Do(context.Background(), http.MethodGet,
"http://localhost:18883/api/ping", nil)
assert.NoError(t, err)
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
})
}
func TestHttpToHttpBadWriter(t *testing.T) {
t.Run("bad url", func(t *testing.T) {
handler := new(Server).buildHttpHandler(&HttpClientConf{
Target: "http://example.com",
Timeout: 3000,
})
w := httptest.NewRecorder()
handler.ServeHTTP(&badResponseWriter{w},
httptest.NewRequest(http.MethodGet, "http://localhost:18884", nil))
assert.Equal(t, http.StatusBadRequest, w.Code)
})
t.Run("bad url", func(t *testing.T) {
var c GatewayConf
assert.NoError(t, conf.FillDefault(&c))
c.DevServer.Host = "localhost"
c.Host = "localhost"
c.Port = 18884
s := MustNewServer(c)
s.upstreams = []Upstream{
{
Mappings: []RouteMapping{
{
Method: "get",
Path: "/api/ping",
},
},
Http: &HttpClientConf{
Target: "localhost:45678",
Prefix: "\x7f/api",
},
},
}
go s.Start()
defer s.Stop()
handler := new(Server).buildHttpHandler(&HttpClientConf{
Target: "localhost:18884",
Timeout: 3000,
})
w := httptest.NewRecorder()
handler.ServeHTTP(&badResponseWriter{w},
httptest.NewRequest(http.MethodGet, "http://localhost:18884/api/ping", nil))
assert.Equal(t, http.StatusBadRequest, w.Code)
})
}
// Handler function for the root route
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
}
type badResponseWriter struct {
http.ResponseWriter
}
func (w *badResponseWriter) Write([]byte) (int, error) {
return 0, errors.New("bad writer")
}