gateway/app_test.go

230 lines
6.8 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
import (
"apigo.cc/go/log"
"apigo.cc/go/redis"
"apigo.cc/go/service"
"context"
"encoding/json"
"io"
"net"
gohttp "net/http"
"os"
"testing"
"time"
)
func TestGateway(t *testing.T) {
// 1. 设置服务发现和 Redis 依赖
registry := "127.0.0.1:6379"
rd := redis.GetRedis(registry, log.DefaultLogger)
if rd.Error != nil {
t.Skip("Redis is not available, skipping gateway integration tests")
return
}
// 清理测试数据
rd.Do("DEL", "gateway:host:gw.test", "gateway:hosts")
// 2. 启动一个独立的后端测试服务 test-backend (不使用全局 DefaultServer)
backend := service.NewWebServer()
backend.Config.App = "test-backend"
backend.Config.Listen = ":0"
backend.Config.Register = registry
backend.Host("*").GET("/hello", func(req *service.Request) string {
return "hello from backend, path: " + req.RequestURI
})
backend.Host("*").GET("/hello/world", func(req *service.Request) string {
return "hello from backend, path: " + req.RequestURI
})
backendCtx, backendCancel := context.WithCancel(context.Background())
defer backendCancel()
go backend.Start(backendCtx, log.DefaultLogger)
defer backend.Stop(backendCtx)
time.Sleep(500 * time.Millisecond) // 等待后端启动和注册
// 3. 配置 Gateway (使用全局 service)
GatewayConf.Redis = registry
GatewayConf.Prefix = "gateway"
// 初始化网关的配置到 Redis
proxyRules := []service.ProxyRule{
{Path: "^/api/(.*)$", ToApp: "test-backend", ToPath: "/$1"},
{Path: "/direct", ToApp: "test-backend", ToPath: "/hello"},
{Path: "/v2/*", ToApp: "test-backend", ToPath: "/hello/*"},
}
rewriteRules := []service.RewriteRule{
{Path: "^/old-api/(.*)$", ToPath: "/api/$1"},
}
proxyJson, _ := json.Marshal(proxyRules)
rewriteJson, _ := json.Marshal(rewriteRules)
rd.Do("HSET", "gateway:host:gw.test", "proxies", string(proxyJson))
rd.Do("HSET", "gateway:host:gw.test", "rewrites", string(rewriteJson))
// 创建临时静态目录
tmpDir, _ := os.MkdirTemp("", "gateway-static")
defer os.RemoveAll(tmpDir)
_ = os.WriteFile(tmpDir+"/index.html", []byte("static content"), 0644)
staticConfig := map[string]string{
"/ui": tmpDir,
}
staticJson, _ := json.Marshal(staticConfig)
rd.Do("HSET", "gateway:host:gw.test", "statics", string(staticJson))
// 4. 启动 Gateway 应用
app := NewGatewayApp()
// 设置独立的端口给 Gateway 的 WebServer
service.Config.App = "gateway"
service.Config.Listen = ":0"
service.Config.Register = registry
service.Config.Calls = map[string]service.CallConfig{
"test-backend": {Timeout: 1000},
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// 这里假设 GatewayApp.Start 内部会调用 service.Start()
// 而 service.Start() 在新版本中如果是阻塞的,我们需要在 goroutine 中运行
go app.Start(ctx, log.DefaultLogger)
defer app.Stop(ctx)
time.Sleep(500 * time.Millisecond)
gwAddr := service.DefaultServer.Addr
if gwAddr == "" {
t.Fatalf("Gateway address is empty")
}
_, gwPort, _ := net.SplitHostPort(gwAddr)
client := &gohttp.Client{Timeout: 5 * time.Second}
// ============================================
// 测试 1: 直接代理匹配 (Discover)
// ============================================
req, _ := gohttp.NewRequest("GET", "http://127.0.0.1:"+gwPort+"/direct", nil)
req.Host = "gw.test"
res, err := client.Do(req)
var body string
if err == nil && res != nil {
b, _ := io.ReadAll(res.Body)
res.Body.Close()
body = string(b)
}
if err != nil || body != "hello from backend, path: /hello" {
t.Fatalf("Proxy /direct failed, got: %s, err: %v", body, err)
}
// ============================================
// 测试 2: 正则代理匹配 (Discover)
// ============================================
req, _ = gohttp.NewRequest("GET", "http://127.0.0.1:"+gwPort+"/api/hello?a=1", nil)
req.Host = "gw.test"
res, err = client.Do(req)
body = ""
if err == nil && res != nil {
b, _ := io.ReadAll(res.Body)
res.Body.Close()
body = string(b)
}
if err != nil || body != "hello from backend, path: /hello" {
t.Fatalf("Proxy /api/ failed, got: %s", body)
}
// ============================================
// 测试 2.1: 极速通配符前缀匹配 (Discover)
// ============================================
req, _ = gohttp.NewRequest("GET", "http://127.0.0.1:"+gwPort+"/v2/world?x=2", nil)
req.Host = "gw.test"
res, err = client.Do(req)
body = ""
if err == nil && res != nil {
b, _ := io.ReadAll(res.Body)
res.Body.Close()
body = string(b)
}
if err != nil || body != "hello from backend, path: /hello/world" {
t.Fatalf("Proxy /v2/* failed, got: %s", body)
}
// ============================================
// 测试 3: Rewrite 后再 Proxy
// ============================================
req, _ = gohttp.NewRequest("GET", "http://127.0.0.1:"+gwPort+"/old-api/hello", nil)
req.Host = "gw.test"
res, err = client.Do(req)
body = ""
if err == nil && res != nil {
b, _ := io.ReadAll(res.Body)
res.Body.Close()
body = string(b)
}
if err != nil || body != "hello from backend, path: /hello" {
t.Fatalf("Rewrite + Proxy failed, got: %s", body)
}
// ============================================
// 测试 4: 静态文件服务
// ============================================
req, _ = gohttp.NewRequest("GET", "http://127.0.0.1:"+gwPort+"/ui/index.html", nil)
req.Host = "gw.test"
res, err = client.Do(req)
body = ""
if err == nil && res != nil {
b, _ := io.ReadAll(res.Body)
res.Body.Close()
body = string(b)
}
if err != nil || body != "static content" {
t.Fatalf("Static service failed, got: %s", body)
}
// ============================================
// 测试 5: 通过 Redis Pub/Sub 动态更新路由
// ============================================
// 新增一个新代理规则,删除老的
newProxyRules := []service.ProxyRule{
{Path: "/new-direct", ToApp: "test-backend", ToPath: "/hello"},
}
newProxyJson, _ := json.Marshal(newProxyRules)
rd.Do("HSET", "gateway:host:gw.test", "proxies", string(newProxyJson))
// 发布更新消息
rd.Do("PUBLISH", "gateway:channel", `"gw.test"`)
// 等待接收和更新
time.Sleep(300 * time.Millisecond)
// 测试新路由应该生效
req, _ = gohttp.NewRequest("GET", "http://127.0.0.1:"+gwPort+"/new-direct", nil)
req.Host = "gw.test"
res, err = client.Do(req)
body = ""
if err == nil && res != nil {
b, _ := io.ReadAll(res.Body)
res.Body.Close()
body = string(b)
}
if err != nil || body != "hello from backend, path: /hello" {
t.Fatalf("Pub/Sub dynamic proxy failed, got: %s", body)
}
// 测试老路由应该 404 (原子替换)
req, _ = gohttp.NewRequest("GET", "http://127.0.0.1:"+gwPort+"/direct", nil)
req.Host = "gw.test"
res, err = client.Do(req)
if err != nil {
t.Fatalf("Request failed: %v", err)
}
if res.StatusCode != 404 {
t.Fatalf("Atomic update failed, old route still exists")
}
}