2026-05-12 23:16:06 +08:00
|
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"apigo.cc/go/cast"
|
|
|
|
|
|
"apigo.cc/go/log"
|
|
|
|
|
|
"apigo.cc/go/redis"
|
|
|
|
|
|
"apigo.cc/go/service"
|
|
|
|
|
|
"context"
|
2026-05-12 23:53:22 +08:00
|
|
|
|
"strings"
|
2026-05-12 23:16:06 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// Config 定义 Gateway 基础配置
|
|
|
|
|
|
type Config struct {
|
2026-05-13 00:18:48 +08:00
|
|
|
|
Redis string // Redis 配置中心 URL (与发现服务的注册中心独立,若为空则不启用动态配置)
|
|
|
|
|
|
Prefix string // Redis Key 的前缀
|
2026-05-12 23:16:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var GatewayConf = Config{
|
2026-05-13 00:18:48 +08:00
|
|
|
|
Prefix: "gateway",
|
2026-05-12 23:16:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-13 00:18:48 +08:00
|
|
|
|
// GatewayApp 定义网关应用,融合了 WebServer 的原生能力
|
2026-05-12 23:16:06 +08:00
|
|
|
|
type GatewayApp struct {
|
2026-05-13 00:18:48 +08:00
|
|
|
|
*service.WebServer
|
|
|
|
|
|
rd *redis.Redis
|
|
|
|
|
|
pubsubChannel string
|
|
|
|
|
|
cancelPubSub context.CancelFunc
|
2026-05-12 23:16:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// NewGatewayApp 创建 Gateway
|
|
|
|
|
|
func NewGatewayApp() *GatewayApp {
|
2026-05-13 00:18:48 +08:00
|
|
|
|
return &GatewayApp{
|
|
|
|
|
|
WebServer: service.NewWebServer(),
|
|
|
|
|
|
}
|
2026-05-12 23:16:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-13 00:18:48 +08:00
|
|
|
|
// Start 启动网关服务 (实现 starter.Service)
|
|
|
|
|
|
func (g *GatewayApp) Start(ctx context.Context, logger *log.Logger) error {
|
|
|
|
|
|
if GatewayConf.Prefix == "" {
|
|
|
|
|
|
GatewayConf.Prefix = "gateway"
|
|
|
|
|
|
}
|
|
|
|
|
|
g.pubsubChannel = GatewayConf.Prefix + ":channel"
|
|
|
|
|
|
|
2026-05-12 23:16:06 +08:00
|
|
|
|
if GatewayConf.Redis != "" {
|
2026-05-13 00:18:48 +08:00
|
|
|
|
g.rd = redis.GetRedis(GatewayConf.Redis, logger)
|
2026-05-12 23:16:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if g.rd != nil && g.rd.Error != nil {
|
2026-05-13 00:18:48 +08:00
|
|
|
|
logger.Error("gateway redis connection failed", "error", g.rd.Error.Error())
|
2026-05-12 23:16:06 +08:00
|
|
|
|
g.rd = nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-13 00:18:48 +08:00
|
|
|
|
// 初始全量加载 Redis 动态配置
|
|
|
|
|
|
g.loadAll(logger)
|
2026-05-12 23:16:06 +08:00
|
|
|
|
|
2026-05-13 00:18:48 +08:00
|
|
|
|
// 开启 Redis 订阅
|
2026-05-12 23:16:06 +08:00
|
|
|
|
if g.rd != nil {
|
2026-05-13 00:18:48 +08:00
|
|
|
|
subCtx, cancel := context.WithCancel(context.Background())
|
2026-05-12 23:16:06 +08:00
|
|
|
|
g.cancelPubSub = cancel
|
2026-05-13 00:18:48 +08:00
|
|
|
|
go g.subscribe(subCtx, logger)
|
2026-05-12 23:16:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-13 00:18:48 +08:00
|
|
|
|
// 启动底层的 WebServer,处理所有实际的 HTTP 连接和发现注册
|
|
|
|
|
|
return g.WebServer.Start(ctx, logger)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Stop 停止网关服务 (实现 starter.Service)
|
|
|
|
|
|
func (g *GatewayApp) Stop(ctx context.Context) error {
|
|
|
|
|
|
if g.cancelPubSub != nil {
|
|
|
|
|
|
g.cancelPubSub()
|
|
|
|
|
|
}
|
|
|
|
|
|
if g.rd != nil {
|
|
|
|
|
|
g.rd.Unsubscribe(g.pubsubChannel)
|
|
|
|
|
|
}
|
|
|
|
|
|
// 停止底层的 WebServer
|
|
|
|
|
|
return g.WebServer.Stop(ctx)
|
2026-05-12 23:16:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-13 00:18:48 +08:00
|
|
|
|
// Reload 重载网关配置 (实现 starter.Reloader)
|
|
|
|
|
|
func (g *GatewayApp) Reload() error {
|
|
|
|
|
|
// 1. 触发底层 WebServer 的重载 (会重新加载本地 yaml 配置文件中的固定路由策略,并触发 OnReload 钩子)
|
|
|
|
|
|
err := g.WebServer.Reload()
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 重新全量拉取 Redis 中的动态配置
|
|
|
|
|
|
logger := log.DefaultLogger
|
|
|
|
|
|
logger.Info("gateway reloading dynamic configurations from redis...")
|
|
|
|
|
|
g.loadAll(logger)
|
|
|
|
|
|
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// loadAll 初始化或 Reload 阶段从 Redis 扫描所有网关配置并加载
|
|
|
|
|
|
func (g *GatewayApp) loadAll(logger *log.Logger) {
|
2026-05-12 23:16:06 +08:00
|
|
|
|
if g.rd == nil {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-13 00:18:48 +08:00
|
|
|
|
logger.Info("gateway loading full configuration")
|
2026-05-12 23:16:06 +08:00
|
|
|
|
|
2026-05-12 23:53:22 +08:00
|
|
|
|
hosts := g.rd.Do("SMEMBERS", GatewayConf.Prefix+":hosts").Strings()
|
|
|
|
|
|
for _, host := range hosts {
|
2026-05-13 00:18:48 +08:00
|
|
|
|
g.loadHost(host, logger)
|
2026-05-12 23:53:22 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// loadHost 加载指定 Host 下的所有类型配置
|
2026-05-13 00:18:48 +08:00
|
|
|
|
func (g *GatewayApp) loadHost(host string, logger *log.Logger) {
|
2026-05-12 23:53:22 +08:00
|
|
|
|
if g.rd == nil || host == "" {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
hashKey := GatewayConf.Prefix + ":host:" + host
|
|
|
|
|
|
hashMap := g.rd.Do("HGETALL", hashKey).StringMap()
|
|
|
|
|
|
|
2026-05-12 23:16:06 +08:00
|
|
|
|
// 1. Proxies
|
2026-05-12 23:53:22 +08:00
|
|
|
|
var proxyRules []service.ProxyRule
|
|
|
|
|
|
if jsonStr := hashMap["proxies"]; jsonStr != "" {
|
|
|
|
|
|
_ = cast.UnmarshalJSON([]byte(jsonStr), &proxyRules)
|
2026-05-12 23:16:06 +08:00
|
|
|
|
}
|
2026-05-12 23:53:22 +08:00
|
|
|
|
service.ReplaceProxies(host, proxyRules)
|
2026-05-12 23:16:06 +08:00
|
|
|
|
|
|
|
|
|
|
// 2. Rewrites
|
2026-05-12 23:53:22 +08:00
|
|
|
|
var rewriteRules []service.RewriteRule
|
|
|
|
|
|
if jsonStr := hashMap["rewrites"]; jsonStr != "" {
|
|
|
|
|
|
_ = cast.UnmarshalJSON([]byte(jsonStr), &rewriteRules)
|
2026-05-12 23:16:06 +08:00
|
|
|
|
}
|
2026-05-12 23:53:22 +08:00
|
|
|
|
service.ReplaceRewrites(host, rewriteRules)
|
2026-05-12 23:16:06 +08:00
|
|
|
|
|
|
|
|
|
|
// 3. Statics
|
2026-05-12 23:53:22 +08:00
|
|
|
|
var staticConfig map[string]string
|
|
|
|
|
|
if jsonStr := hashMap["statics"]; jsonStr != "" {
|
|
|
|
|
|
_ = cast.UnmarshalJSON([]byte(jsonStr), &staticConfig)
|
2026-05-12 23:16:06 +08:00
|
|
|
|
}
|
2026-05-12 23:53:22 +08:00
|
|
|
|
if staticConfig == nil {
|
|
|
|
|
|
staticConfig = make(map[string]string)
|
2026-05-12 23:16:06 +08:00
|
|
|
|
}
|
2026-05-12 23:53:22 +08:00
|
|
|
|
service.ReplaceStatics(host, staticConfig)
|
2026-05-12 23:16:06 +08:00
|
|
|
|
|
2026-05-13 00:18:48 +08:00
|
|
|
|
logger.Info("gateway host configuration updated via pub/sub", "host", host,
|
2026-05-12 23:53:22 +08:00
|
|
|
|
"proxies", len(proxyRules), "rewrites", len(rewriteRules), "statics", len(staticConfig))
|
2026-05-12 23:16:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-13 00:18:48 +08:00
|
|
|
|
func (g *GatewayApp) subscribe(ctx context.Context, logger *log.Logger) {
|
|
|
|
|
|
logger.Info("gateway subscribed to redis channel", "channel", g.pubsubChannel)
|
2026-05-12 23:16:06 +08:00
|
|
|
|
g.rd.Subscribe(g.pubsubChannel, nil, func(data []byte) {
|
2026-05-12 23:53:22 +08:00
|
|
|
|
host := string(data)
|
|
|
|
|
|
host = strings.TrimSpace(strings.Trim(host, "\"")) // 兼容裸字符串和 JSON 字符串
|
2026-05-13 00:18:48 +08:00
|
|
|
|
|
2026-05-12 23:53:22 +08:00
|
|
|
|
if host != "" {
|
2026-05-13 00:18:48 +08:00
|
|
|
|
g.loadHost(host, logger)
|
2026-05-12 23:16:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2026-05-13 00:18:48 +08:00
|
|
|
|
<-ctx.Done()
|
2026-05-12 23:16:06 +08:00
|
|
|
|
}
|